Renamed koalageddon mode to store mode

master
acidicoala 1 year ago
parent dfbce391a7
commit 4f93515bac
No known key found for this signature in database
GPG Key ID: D24C6065B49C645B

@ -2,6 +2,9 @@
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
<component name="CidrRootsConfiguration">
<libraryRoots>
<file path="$PROJECT_DIR$/KoalaBox/dependencies" />
</libraryRoots>
<excludeRoots>
<file path="$PROJECT_DIR$/build" />
<file path="$PROJECT_DIR$/sdk" />

@ -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 ()

@ -1 +1 @@
Subproject commit c1e97c8101fe0369985aad85298b1b6b3e3c9ca6
Subproject commit 44b0553105af8a7352def9e879f59e1da26bcba6

@ -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.

@ -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 <<How SmokeAPI works in games with large number of DLCs>> 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 <arch> <config>
----
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]

@ -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
<details><summary>What is SmokeAPI?</summary>
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.
</details>
<details><summary>Supported versions</summary>
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
</details>
## 🛠 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

@ -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 <https://unlicense.org>

@ -3,6 +3,6 @@
Set-Location (Get-Item $PSScriptRoot)
. ./KoalaBox/build.ps1 @args
. ./KoalaBox/build.ps1 SmokeAPI @args
Build-Project

@ -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
}

@ -1,4 +1,4 @@
#include <smoke_api/app_cache.hpp>
#include <common/app_cache.hpp>
#include <core/paths.hpp>
#include <koalabox/cache.hpp>
#include <koalabox/logger.hpp>

@ -1,3 +1,4 @@
#include <common/steamclient_exports.hpp>
#include <steam_impl/steam_client.hpp>
DLL_EXPORT(void*) CreateInterface(const char* interface_string, int* out_result) {

@ -0,0 +1,3 @@
#include <core/types.hpp>
DLL_EXPORT(void*) CreateInterface(const char* interface_string, int* out_result);

@ -27,7 +27,7 @@ namespace api {
std::optional<Vector<DLC>> 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<SteamResponse>();
@ -43,17 +43,4 @@ namespace api {
}
}
std::optional<koalageddon::KoalageddonConfig> 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<koalageddon::KoalageddonConfig>();
} catch (const Exception& e) {
LOG_ERROR("Failed to fetch Koalageddon config from GitHub: {}", e.what())
return std::nullopt;
}
}
}

@ -1,6 +1,6 @@
#pragma once
#include <koalageddon/koalageddon.hpp>
#include <core/types.hpp>
namespace api {
@ -8,6 +8,4 @@ namespace api {
std::optional<Vector<DLC>> fetch_dlcs_from_steam(AppId_t app_id) noexcept;
std::optional<koalageddon::KoalageddonConfig> fetch_koalageddon_config() noexcept;
}

@ -1,9 +1,10 @@
#pragma once
#include <koalabox/core.hpp>
#include <koalabox/hook.hpp>
#include <nlohmann/json.hpp>
#include <core/globals.hpp>
#include <koalabox/core.hpp>
#include <koalabox/hook.hpp>
#include <koalabox/logger.hpp>
/**
* 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<DLC> get_dlcs_from_apps(const AppDlcNameMap& apps, AppId_t app_id);
static DlcNameMap get_dlc_map_from_vector(const Vector<DLC>& vector);
};

@ -1,9 +1,9 @@
#include <steam_api_exports/steam_api_exports.hpp>
#include <steam_impl/steam_apps.hpp>
#include <steam_impl/steam_client.hpp>
#include <steam_impl/steam_inventory.hpp>
#include <steam_impl/steam_user.hpp>
#include <steam_impl/steam_impl.hpp>
#include <koalabox/logger.hpp>
// 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);

@ -1,4 +1,3 @@
#include <steam_api_exports/steam_api_exports.hpp>
#include <steam_impl/steam_client.hpp>
DLL_EXPORT(void*) SteamInternal_FindOrCreateUserInterface(HSteamUser hSteamUser, const char* version) {

@ -1,4 +1,3 @@
#include <steam_api_exports/steam_api_exports.hpp>
#include <steam_impl/steam_client.hpp>
#include <koalabox/logger.hpp>
#include <koalabox/win_util.hpp>

@ -1,4 +1,4 @@
#include <steam_api_virtuals/steam_api_virtuals.hpp>
#include <game_mode/virtuals/steam_api_virtuals.hpp>
#include <steam_impl/steam_apps.hpp>
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t appID)) {

@ -1,4 +1,4 @@
#include <steam_api_virtuals/steam_api_virtuals.hpp>
#include <game_mode/virtuals/steam_api_virtuals.hpp>
#include <steam_impl/steam_client.hpp>
VIRTUAL(void*) ISteamClient_GetISteamApps(

@ -1,4 +1,4 @@
#include <steam_api_virtuals/steam_api_virtuals.hpp>
#include <game_mode/virtuals/steam_api_virtuals.hpp>
#include <steam_impl/steam_inventory.hpp>
VIRTUAL(EResult) ISteamInventory_GetResultStatus(PARAMS(SteamInventoryResult_t resultHandle)) {

@ -1,10 +1,18 @@
#include <steam_api_virtuals/steam_api_virtuals.hpp>
#include <game_mode/virtuals/steam_api_virtuals.hpp>
#include <steam_impl/steam_user.hpp>
#include <steam_impl/steam_impl.hpp>
#include <koalabox/logger.hpp>
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));

@ -1,33 +0,0 @@
#include <koalageddon/kg_cache.hpp>
#include <koalabox/cache.hpp>
#include <koalabox/logger.hpp>
constexpr auto KEY_KG_CONFIG = "koalageddon_config";
namespace koalageddon::kg_cache {
std::optional<KoalageddonConfig> get_koalageddon_config() {
try {
const auto config_json = koalabox::cache::read_from_cache(KEY_KG_CONFIG);
return config_json.get<KoalageddonConfig>();
} 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;
}
}
}

@ -1,11 +0,0 @@
#pragma once
#include <koalageddon/koalageddon.hpp>
namespace koalageddon::kg_cache {
std::optional<KoalageddonConfig> get_koalageddon_config();
bool save_koalageddon_config(const KoalageddonConfig& config);
}

@ -1,27 +0,0 @@
#include <koalageddon/koalageddon.hpp>
namespace koalageddon::vstdlib {
struct CallbackData {
uintptr_t get_callback_intercept_address() {
return reinterpret_cast<uintptr_t*>(this)[koalageddon::config.vstdlib_callback_interceptor_address_offset];
}
uintptr_t get_callback_address() {
return reinterpret_cast<uintptr_t*>(this)[koalageddon::config.vstdlib_callback_address_offset];
}
};
struct CoroutineData {
CallbackData* get_callback_data() {
return reinterpret_cast<CallbackData**>(this)[koalageddon::config.vstdlib_callback_data_offset];
}
const char* get_callback_name() {
return reinterpret_cast<const char**>(this)[koalageddon::config.vstdlib_callback_name_offset];
}
};
typedef uint32_t HCoroutine;
DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, CoroutineData* data);
}

@ -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();
}
}

@ -1,7 +1,6 @@
#pragma once
#include <core/types.hpp>
#include <koalabox/core.hpp>
namespace smoke_api::config {
@ -30,7 +29,7 @@ namespace smoke_api::config {
bool auto_inject_inventory = true;
Vector<uint32_t> 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<DLC> get_extra_dlcs(AppId_t app_id);

@ -3,6 +3,7 @@
#include <smoke_api/config.hpp>
#include <core/globals.hpp>
#include <core/paths.hpp>
#include <common/steamclient_exports.hpp>
#include <koalabox/dll_monitor.hpp>
#include <koalabox/logger.hpp>
#include <koalabox/hook.hpp>
@ -10,12 +11,30 @@
#include <koalabox/loader.hpp>
#include <koalabox/win_util.hpp>
#include <koalabox/util.hpp>
#include <steam_api_exports/steam_api_exports.hpp>
//#include <steam_api_exports/steam_api_exports.hpp>
#if COMPILE_KOALAGEDDON
#include <koalageddon/koalageddon.hpp>
#if COMPILE_STORE_MODE
#include <store_mode/store.hpp>
#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();

@ -3,6 +3,7 @@
namespace smoke_api {
void init(HMODULE module_handle);
void shutdown();
}

@ -1,36 +0,0 @@
#pragma once
#include <core/types.hpp>
// 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();

@ -1,6 +1,6 @@
#include <steam_impl/steam_apps.hpp>
#include <steam_impl/steam_impl.hpp>
#include <smoke_api/app_cache.hpp>
#include <common/app_cache.hpp>
#include <smoke_api/config.hpp>
#include <koalabox/logger.hpp>
#include <koalabox/util.hpp>

@ -1,16 +1,15 @@
#include <steam_impl/steam_impl.hpp>
#include <steam_api_virtuals/steam_api_virtuals.hpp>
#include <steam_api_exports/steam_api_exports.hpp>
#include <game_mode/virtuals/steam_api_virtuals.hpp>
#include <common/steamclient_exports.hpp>
#include <core/globals.hpp>
#include <build_config.h>
#include <koalabox/hook.hpp>
#include <koalabox/util.hpp>
#include <koalabox/win_util.hpp>
#include <koalabox/logger.hpp>
#include <koalabox/util.hpp>
#include <polyhook2/Misc.hpp>
#if COMPILE_KOALAGEDDON
#include <koalageddon/steamclient/steamclient.hpp>
#if COMPILE_STORE_MODE
#include <store_mode/steamclient/steamclient.hpp>
#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<uintptr_t>(interface));
#if COMPILE_STORE_MODE
store::steamclient::process_client_engine(reinterpret_cast<uintptr_t>(interface));
#endif
} else {
return;

@ -1,7 +1,6 @@
#pragma once
#include <core/types.hpp>
#include <koalabox/core.hpp>
namespace steam_inventory {

@ -1,3 +1,4 @@
#include <store_mode/steamclient/steamclient.hpp>
#include <steam_impl/steam_apps.hpp>
VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(PARAMS(AppId_t app_id, AppId_t dlc_id)) {

@ -1,5 +1,5 @@
#include <store_mode/steamclient/steamclient.hpp>
#include <steam_impl/steam_apps.hpp>
#include <koalageddon/steamclient/steamclient.hpp>
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;

@ -1,4 +1,4 @@
#include <koalageddon/steamclient/steamclient.hpp>
#include <store_mode/steamclient/steamclient.hpp>
#include <steam_impl/steam_inventory.hpp>
VIRTUAL(EResult) IClientInventory_GetResultStatus(PARAMS(SteamInventoryResult_t resultHandle)) {

@ -1,8 +1,8 @@
#include <koalageddon/steamclient/steamclient.hpp>
#include <store_mode/steamclient/steamclient.hpp>
#include <steam_impl/steam_apps.hpp>
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;

@ -1,4 +1,4 @@
#include <koalageddon/steamclient/steamclient.hpp>
#include <store_mode/steamclient/steamclient.hpp>
VIRTUAL(AppId_t) IClientUtils_GetAppID(PARAMS()) {
GET_ORIGINAL_HOOKED_FUNCTION(IClientUtils_GetAppID)

@ -1,5 +1,5 @@
#include <koalageddon/steamclient/steamclient.hpp>
#include <koalageddon/koalageddon.hpp>
#include <store_mode/steamclient/steamclient.hpp>
#include <store_mode/store.hpp>
#include <koalabox/hook.hpp>
#include <koalabox/logger.hpp>
#include <koalabox/util.hpp>
@ -7,7 +7,7 @@
#include <Zydis/Zydis.h>
#include <Zydis/DecoderTypes.h>
namespace koalageddon::steamclient {
namespace store::steamclient {
using namespace koalabox;
Map<String, void*> 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)

@ -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<String, void*> interface_name_to_address_map;

@ -1,29 +1,28 @@
#include <koalageddon/koalageddon.hpp>
#include <koalageddon/vstdlib.hpp>
#include <koalageddon/kg_cache.hpp>
#include <build_config.h>
#include <store_mode/store.hpp>
#include <store_mode/vstdlib/vstdlib.hpp>
#include <store_mode/store_cache.hpp>
#include <store_mode/store_api.hpp>
#include <smoke_api/config.hpp>
#include <steam_api_exports/steam_api_exports.hpp>
#include <core/api.hpp>
#include <build_config.h>
#include <koalabox/dll_monitor.hpp>
#include <koalabox/logger.hpp>
#include <koalabox/win_util.hpp>
#include <koalabox/ipc.hpp>
#include <common/steamclient_exports.hpp>
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<decltype(config)>();
@ -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") {

@ -2,10 +2,10 @@
#include <core/types.hpp>
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();
}

@ -0,0 +1,19 @@
#include <store_mode/store_api.hpp>
#include <koalabox/http_client.hpp>
namespace store::api {
std::optional<StoreConfig> 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<StoreConfig>();
} catch (const Exception& e) {
LOG_ERROR("Failed to fetch Store config from GitHub: {}", e.what())
return std::nullopt;
}
}
}

@ -0,0 +1,10 @@
#pragma once
#include <core/types.hpp>
#include <store_mode/store.hpp>
namespace store::api {
std::optional<store::StoreConfig> fetch_store_config() noexcept;
}

@ -0,0 +1,32 @@
#include <store_mode/store_cache.hpp>
#include <koalabox/cache.hpp>
constexpr auto KEY_KG_CONFIG = "store_config";
namespace store::store_cache {
std::optional<StoreConfig> get_store_config() {
try {
const auto config_json = koalabox::cache::read_from_cache(KEY_KG_CONFIG);
return config_json.get<StoreConfig>();
} 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;
}
}
}

@ -0,0 +1,11 @@
#pragma once
#include <store_mode/store.hpp>
namespace store::store_cache {
std::optional<StoreConfig> get_store_config();
bool save_store_config(const StoreConfig& config);
}

@ -1,47 +1,32 @@
#include <koalageddon/vstdlib.hpp>
#include <koalabox/hook.hpp>
#include <koalabox/logger.hpp>
#include <store_mode/vstdlib/vstdlib.hpp>
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) {

@ -0,0 +1,27 @@
#include <store_mode/store.hpp>
namespace store::vstdlib {
struct CallbackData {
uintptr_t get_callback_intercept_address() {
return reinterpret_cast<uintptr_t*>(this)[store::config.vstdlib_callback_interceptor_address_offset];
}
uintptr_t get_callback_address() {
return reinterpret_cast<uintptr_t*>(this)[store::config.vstdlib_callback_address_offset];
}
};
struct CoroutineData {
CallbackData* get_callback_data() {
return reinterpret_cast<CallbackData**>(this)[store::config.vstdlib_callback_data_offset];
}
const char* get_callback_name() {
return reinterpret_cast<const char**>(this)[store::config.vstdlib_callback_name_offset];
}
};
typedef uint32_t HCoroutine;
DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, CoroutineData* data);
}
Loading…
Cancel
Save