mirror of
https://github.com/acidicoala/SmokeAPI.git
synced 2026-01-25 14:02:55 -05:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95ceac3d47 | ||
|
|
aa23be373d | ||
|
|
6288aa149c | ||
|
|
6606c01fda | ||
|
|
caf9ef2225 | ||
|
|
4f93515bac | ||
|
|
dfbce391a7 | ||
|
|
d52e752723 | ||
|
|
3e40e4607f | ||
|
|
edd785cfcf | ||
|
|
30f1076261 | ||
|
|
59fa68a7b4 | ||
|
|
a7e912a98a | ||
|
|
3f595f4f88 | ||
|
|
ad1578f556 | ||
|
|
6347e3f148 | ||
|
|
55ada47bef | ||
|
|
a2cbf55819 | ||
|
|
b1680fe3d7 | ||
|
|
b077212d10 | ||
|
|
1d36cfb3be | ||
|
|
eb888b91b5 | ||
|
|
f29fc96dcd | ||
|
|
eaca0bec34 | ||
|
|
011f3fac5d | ||
|
|
b04c96a36d | ||
|
|
6f43e5ee9b | ||
|
|
89fa851943 | ||
|
|
c654f9cbfd | ||
|
|
7a628c1315 | ||
|
|
297861ba88 | ||
|
|
71506bd03c | ||
|
|
0d1ae0fd29 | ||
|
|
1ba84753aa | ||
|
|
d6828e3bfb | ||
|
|
636f9186a3 | ||
|
|
941d5d7d8c | ||
|
|
8cba428c0f | ||
|
|
da43de4065 |
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -4,7 +4,7 @@ on: push
|
|||||||
jobs:
|
jobs:
|
||||||
ci:
|
ci:
|
||||||
name: CI
|
name: CI
|
||||||
uses: acidicoala/KoalaBox/.github/workflows/build-and-package.yml@d9b0d1a00beb065a9a931a60ef615ce8d1fc7164
|
uses: acidicoala/KoalaBox/.github/workflows/build-and-package.yml@15d5cfc2e515bc72e47da6c0c563820cff98551f
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
with:
|
with:
|
||||||
@@ -14,5 +14,6 @@ jobs:
|
|||||||
zip_command: >
|
zip_command: >
|
||||||
zip -j $ZIP_NAME
|
zip -j $ZIP_NAME
|
||||||
artifacts/*/*.dll
|
artifacts/*/*.dll
|
||||||
|
res/SmokeAPI.config.json
|
||||||
|
|
||||||
config: Debug
|
config: Release
|
||||||
|
|||||||
9
.idea/cmake.xml
generated
Normal file
9
.idea/cmake.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CMakeSharedSettings">
|
||||||
|
<configurations>
|
||||||
|
<configuration PROFILE_NAME="Debug [32] (Template: copy and set the 32-bit toolchain)" ENABLED="false" GENERATION_DIR="build/32" CONFIG_NAME="Debug" GENERATION_OPTIONS="-G "Visual Studio 17 2022" -A Win32" />
|
||||||
|
<configuration PROFILE_NAME="Debug [64] (Template: copy and set the 64-bit toolchain)" ENABLED="false" GENERATION_DIR="build/64" CONFIG_NAME="Debug" GENERATION_OPTIONS="-G "Visual Studio 17 2022" -A x64" />
|
||||||
|
</configurations>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
13
.idea/codeStyles/Project.xml
generated
13
.idea/codeStyles/Project.xml
generated
@@ -8,6 +8,8 @@
|
|||||||
<option name="WRAP_TEXT_INSIDE_BLOCKQUOTES" value="false" />
|
<option name="WRAP_TEXT_INSIDE_BLOCKQUOTES" value="false" />
|
||||||
</Markdown>
|
</Markdown>
|
||||||
<Objective-C>
|
<Objective-C>
|
||||||
|
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="FUNCTION_CALL_ARGUMENTS_ALIGN_MULTILINE" value="false" />
|
||||||
<option name="SPACE_BEFORE_POINTER_IN_DECLARATION" value="false" />
|
<option name="SPACE_BEFORE_POINTER_IN_DECLARATION" value="false" />
|
||||||
<option name="SPACE_AFTER_POINTER_IN_DECLARATION" value="true" />
|
<option name="SPACE_AFTER_POINTER_IN_DECLARATION" value="true" />
|
||||||
<option name="SPACE_BEFORE_REFERENCE_IN_DECLARATION" value="false" />
|
<option name="SPACE_BEFORE_REFERENCE_IN_DECLARATION" value="false" />
|
||||||
@@ -17,15 +19,26 @@
|
|||||||
<editorconfig>
|
<editorconfig>
|
||||||
<option name="ENABLED" value="false" />
|
<option name="ENABLED" value="false" />
|
||||||
</editorconfig>
|
</editorconfig>
|
||||||
|
<files>
|
||||||
|
<extensions>
|
||||||
|
<pair source="cpp" header="hpp" fileNamingConvention="SNAKE_CASE" />
|
||||||
|
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||||
|
<pair source="cu" header="cuh" fileNamingConvention="NONE" />
|
||||||
|
</extensions>
|
||||||
|
</files>
|
||||||
<codeStyleSettings language="CMake">
|
<codeStyleSettings language="CMake">
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="ObjectiveC">
|
<codeStyleSettings language="ObjectiveC">
|
||||||
|
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
|
||||||
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
|
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
|
||||||
<option name="BLANK_LINES_AFTER_IMPORTS" value="0" />
|
<option name="BLANK_LINES_AFTER_IMPORTS" value="0" />
|
||||||
|
<option name="BLANK_LINES_AROUND_CLASS" value="0" />
|
||||||
|
<option name="BLANK_LINES_AROUND_METHOD" value="0" />
|
||||||
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="0" />
|
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="0" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
<option name="SOFT_MARGINS" value="120" />
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
|||||||
6
.idea/encodings.xml
generated
6
.idea/encodings.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding">
|
|
||||||
<file url="file://$PROJECT_DIR$/include/sdk/steamtypes.h" charset="windows-1252" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -4,5 +4,6 @@
|
|||||||
<inspection_tool class="ClangTidy" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="ClangTidy" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="clangTidyChecks" value="-*,cppcoreguidelines-interfaces-global-init,cppcoreguidelines-narrowing-conversions,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-pro-type-static-cast-downcast,cppcoreguidelines-slicing,google-default-arguments,google-explicit-constructor,google-runtime-operator,hicpp-exception-baseclass,hicpp-multiway-paths-covered,modernize-avoid-bind,modernize-concat-nested-namespaces,modernize-deprecated-headers,modernize-deprecated-ios-base-aliases,modernize-loop-convert,modernize-make-shared,modernize-make-unique,modernize-pass-by-value,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-replace-auto-ptr,modernize-replace-disallow-copy-and-assign-macro,modernize-replace-random-shuffle,modernize-return-braced-init-list,modernize-shrink-to-fit,modernize-unary-static-assert,modernize-use-auto,modernize-use-bool-literals,modernize-use-emplace,modernize-use-equals-default,modernize-use-equals-delete,modernize-use-nodiscard,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-transparent-functors,modernize-use-uncaught-exceptions,mpi-buffer-deref,mpi-type-mismatch,openmp-use-default-none,performance-faster-string-find,performance-for-range-copy,performance-implicit-conversion-in-loop,performance-inefficient-algorithm,performance-inefficient-string-concatenation,performance-inefficient-vector-operation,performance-move-const-arg,performance-move-constructor-init,performance-no-automatic-move,performance-noexcept-move-constructor,performance-trivially-destructible,performance-type-promotion-in-math-fn,performance-unnecessary-copy-initialization,performance-unnecessary-value-param,portability-simd-intrinsics,bugprone-*,abseil-*,cert-*,objc-*,readability-*,clang-analyzer-*,misc-*,-readability-magic-numbers,-bugprone-easily-swappable-parameters,-readability-implicit-bool-conversion,-readability-identifier-length,-readability-named-parameter,-readability-function-cognitive-complexity" />
|
<option name="clangTidyChecks" value="-*,cppcoreguidelines-interfaces-global-init,cppcoreguidelines-narrowing-conversions,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-pro-type-static-cast-downcast,cppcoreguidelines-slicing,google-default-arguments,google-explicit-constructor,google-runtime-operator,hicpp-exception-baseclass,hicpp-multiway-paths-covered,modernize-avoid-bind,modernize-concat-nested-namespaces,modernize-deprecated-headers,modernize-deprecated-ios-base-aliases,modernize-loop-convert,modernize-make-shared,modernize-make-unique,modernize-pass-by-value,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-replace-auto-ptr,modernize-replace-disallow-copy-and-assign-macro,modernize-replace-random-shuffle,modernize-return-braced-init-list,modernize-shrink-to-fit,modernize-unary-static-assert,modernize-use-auto,modernize-use-bool-literals,modernize-use-emplace,modernize-use-equals-default,modernize-use-equals-delete,modernize-use-nodiscard,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-transparent-functors,modernize-use-uncaught-exceptions,mpi-buffer-deref,mpi-type-mismatch,openmp-use-default-none,performance-faster-string-find,performance-for-range-copy,performance-implicit-conversion-in-loop,performance-inefficient-algorithm,performance-inefficient-string-concatenation,performance-inefficient-vector-operation,performance-move-const-arg,performance-move-constructor-init,performance-no-automatic-move,performance-noexcept-move-constructor,performance-trivially-destructible,performance-type-promotion-in-math-fn,performance-unnecessary-copy-initialization,performance-unnecessary-value-param,portability-simd-intrinsics,bugprone-*,abseil-*,cert-*,objc-*,readability-*,clang-analyzer-*,misc-*,-readability-magic-numbers,-bugprone-easily-swappable-parameters,-readability-implicit-bool-conversion,-readability-identifier-length,-readability-named-parameter,-readability-function-cognitive-complexity" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ConstantFunctionResult" enabled="true" level="INFORMATION" enabled_by_default="true" editorAttributes="INFORMATION_ATTRIBUTES" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@@ -2,6 +2,9 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||||
<component name="CidrRootsConfiguration">
|
<component name="CidrRootsConfiguration">
|
||||||
|
<libraryRoots>
|
||||||
|
<file path="$PROJECT_DIR$/KoalaBox/dependencies" />
|
||||||
|
</libraryRoots>
|
||||||
<excludeRoots>
|
<excludeRoots>
|
||||||
<file path="$PROJECT_DIR$/build" />
|
<file path="$PROJECT_DIR$/build" />
|
||||||
<file path="$PROJECT_DIR$/sdk" />
|
<file path="$PROJECT_DIR$/sdk" />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
cmake_minimum_required(VERSION 3.22)
|
cmake_minimum_required(VERSION 3.24)
|
||||||
|
|
||||||
project(SmokeAPI VERSION 1.0.3)
|
project(SmokeAPI VERSION 2.0.0)
|
||||||
|
|
||||||
include(KoalaBox/cmake/KoalaBox.cmake)
|
include(KoalaBox/cmake/KoalaBox.cmake)
|
||||||
|
|
||||||
add_subdirectory(KoalaBox EXCLUDE_FROM_ALL)
|
add_subdirectory(KoalaBox EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
set_32_and_64(ORIGINAL_DLL steam_api)
|
set_32_and_64(STEAMAPI_DLL steam_api)
|
||||||
set_32_and_64(STEAMCLIENT_DLL steamclient)
|
set_32_and_64(STEAMCLIENT_DLL steamclient)
|
||||||
set_32_and_64(VSTDLIB_DLL vstdlib_s)
|
set_32_and_64(VSTDLIB_DLL vstdlib_s)
|
||||||
|
|
||||||
@@ -19,61 +19,86 @@ file(GLOB DLL_INPUT "res/dll/*/sdk/redistributable_bin/${DLL_SUFFIX}.dll")
|
|||||||
|
|
||||||
set(
|
set(
|
||||||
STEAM_API_EXPORTS
|
STEAM_API_EXPORTS
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/steam_api_exports/steam_api_flat.cpp"
|
"src/game_mode/exports/steam_api_flat.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/steam_api_exports/steam_api_internal.cpp"
|
"src/game_mode/exports/steam_api_internal.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/steam_api_exports/steam_api_unversioned.cpp"
|
"src/game_mode/exports/steam_api_unversioned.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
configure_linker_exports(
|
configure_linker_exports(
|
||||||
FORWARDED_DLL "${ORIGINAL_DLL}_o"
|
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}"
|
INPUT_DLLS "${DLL_INPUT}"
|
||||||
DEP_SOURCES "${STEAM_API_EXPORTS}"
|
DEP_SOURCES "${STEAM_API_EXPORTS}" # Is this redundant?
|
||||||
)
|
)
|
||||||
|
|
||||||
configure_build_config(extra_build_config)
|
configure_build_config(extra_build_config)
|
||||||
|
|
||||||
set(
|
set(
|
||||||
SMOKE_API_SOURCES
|
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
|
||||||
|
src/core/globals.hpp
|
||||||
|
src/core/paths.cpp
|
||||||
|
src/core/paths.hpp
|
||||||
|
src/core/types.cpp
|
||||||
|
src/core/types.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.cpp
|
||||||
src/smoke_api/smoke_api.hpp
|
src/smoke_api/smoke_api.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_impl/steam_apps.cpp
|
src/steam_impl/steam_apps.cpp
|
||||||
|
src/steam_impl/steam_apps.hpp
|
||||||
src/steam_impl/steam_client.cpp
|
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_impl.hpp
|
||||||
src/steam_impl/steam_inventory.cpp
|
src/steam_impl/steam_inventory.cpp
|
||||||
|
src/steam_impl/steam_inventory.hpp
|
||||||
src/steam_impl/steam_user.cpp
|
src/steam_impl/steam_user.cpp
|
||||||
src/steam_functions/steam_functions.cpp
|
src/steam_impl/steam_user.hpp
|
||||||
src/steam_functions/steam_functions.hpp
|
|
||||||
src/steam_types/steam_types.hpp
|
|
||||||
src/steamclient_exports/steamclient.cpp
|
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
${GENERATED_LINKER_EXPORTS}
|
${GENERATED_LINKER_EXPORTS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Include store_mode mode sources only in 32-bit builds
|
||||||
if (CMAKE_SIZEOF_VOID_P EQUAL 4)
|
if (CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||||
set(
|
set(
|
||||||
SMOKE_API_SOURCES ${SMOKE_API_SOURCES}
|
SMOKE_API_SOURCES ${SMOKE_API_SOURCES}
|
||||||
src/koalageddon/vstdlib.cpp
|
src/store_mode/steamclient/client_app_manager.cpp
|
||||||
src/koalageddon/steamclient.cpp
|
src/store_mode/steamclient/client_apps.cpp
|
||||||
src/steamclient_virtuals/client_app_manager.cpp
|
src/store_mode/steamclient/client_inventory.cpp
|
||||||
src/steamclient_virtuals/client_apps.cpp
|
src/store_mode/steamclient/client_user.cpp
|
||||||
src/steamclient_virtuals/client_inventory.cpp
|
src/store_mode/steamclient/client_utils.cpp
|
||||||
src/steamclient_virtuals/client_user.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 ()
|
endif ()
|
||||||
|
|
||||||
add_library(SmokeAPI SHARED ${SMOKE_API_SOURCES} ${VERSION_RESOURCE})
|
add_library(SmokeAPI SHARED ${SMOKE_API_SOURCES} ${VERSION_RESOURCE})
|
||||||
|
|
||||||
configure_output_name(${ORIGINAL_DLL})
|
configure_output_name(${STEAMAPI_DLL})
|
||||||
|
|
||||||
configure_include_directories()
|
configure_include_directories()
|
||||||
|
|
||||||
target_link_libraries(SmokeAPI PRIVATE KoalaBox)
|
target_link_libraries(SmokeAPI PRIVATE KoalaBox)
|
||||||
|
|
||||||
|
|||||||
2
KoalaBox
2
KoalaBox
Submodule KoalaBox updated: d9b0d1a00b...fe45f9c61c
12
LICENSE.txt
12
LICENSE.txt
@@ -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.
|
|
||||||
275
README.adoc
Normal file
275
README.adoc
Normal file
@@ -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 by using https://github.com/acidicoala/Koalageddon2#readme[Koalageddon v2].
|
||||||
|
|
||||||
|
=== 🛍️ 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.config.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 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 rename it to `steam_api_o.dll` / `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 unless you know what you are doing.
|
||||||
|
+
|
||||||
|
[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]
|
||||||
113
README.md
113
README.md
@@ -1,113 +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:
|
|
||||||
- Hero Siege
|
|
||||||
- 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 it's 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. In this case you can use a configuration file [SmokeAPI.json] that you can find here in this repository. 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 future use by tools like GUI config editors | Integer | `1` |
|
|
||||||
| `logging` | Toggles generation of `*.log` file | Boolean | `false` |
|
|
||||||
| `hook_steamclient` | When installed in hook mode, this option toggles between hooking steamclient(64).dll and steam_api(64).dll | Boolean | `true` |
|
|
||||||
| `unlock_all` | Toggles whether all DLCs should be unlocked by default | Boolean | `true` |
|
|
||||||
| `override` | When `unlock_all` is `true`, this option serves as a blacklist of DLC IDs, which should remain locked. When `unlock_all` is `false`, this option serves as a whitelist of DLC IDs, which should become unlocked | List of Integers | `[]` |
|
|
||||||
| `dlc_ids` | When game requests list of all DLCs from Steam and the number of registered DLCs is greater than 64, Steam may not return all of them. In this case, SmokeAPI will fetch all released DLCs from Web API. In some games, however (like Monster Hunter: World), web api also doesn't return all possible DLCs. To address this issue, you can specify the missing DLC IDs¹ in this option. For some games (including MH:W), however, it is not necessary because SmokeAPI will also automatically fetch a [manually maintained list of DLC IDs] that are missing from web api | List of Integers | `[]` |
|
|
||||||
| `auto_inject_inventory` | Toggles whether SmokeAPI should automatically inject a list of all registered inventory items, when a game queries user inventory | Boolean | `true` |
|
|
||||||
| `inventory_items` | A list of inventory items IDs¹ that will be added in addition to the automatically injected items | List of Integers | `[]` |
|
|
||||||
|
|
||||||
¹ 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.json]: res/SmokeAPI.json
|
|
||||||
[manually maintained list of DLC IDs]: https://github.com/acidicoala/public-entitlements/blob/main/steam/v1/dlc.json
|
|
||||||
|
|
||||||
## ℹ Extra info
|
|
||||||
|
|
||||||
### How SmokeAPI works in games with large number of DLCs
|
|
||||||
|
|
||||||
Some games that have a lot of DLCs begin ownership verification by querying the Steamworks API for a list of all available DLCs. Once the game receives the list, it will go over each item and check the ownership. The issue arises from the fact that response from Steamworks SDK may max out at 64, depending on how much unowned DLC the user has. To alleviate this issue, SmokeAPI will make a web request to Steam API for a full list of DLCs, which works well most of the time. Unfortunately, even the web API does not solve all of our problems, because it will only return DLCs that are available in Steam store. This means that DLCs without a dedicated store offer, such as pre-order DLCs will be left out. That's where the `dlc_ids` config option comes into play. You can specify those missing DLC IDs there, and SmokeAPI will make them available to the game. However, this introduces the need for manual configuration, which goes against the ideals of this project. To remedy this issue SmokeAPI will also fetch [this document] stored in a GitHub repository. It contains all the DLC IDs missing from Steam store. The document is hand-crafted using data from https://steamdb.com. This enables SmokeAPI to unlock all DLCs without any config file at all. Feel free to report games that have more than 64 DLCs, *and* have DLCs without a dedicated store page. They will be added to the list of missing DLC IDs to facilitate configless operation.
|
|
||||||
|
|
||||||
[this document]: https://github.com/acidicoala/public-entitlements/blob/main/steam/v1/dlc.json
|
|
||||||
|
|
||||||
## 👋 Acknowledgements
|
|
||||||
|
|
||||||
SmokeAPI makes use of the following open source projects:
|
|
||||||
|
|
||||||
- [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
|
|
||||||
24
UNLICENSE.txt
Normal file
24
UNLICENSE.txt
Normal file
@@ -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)
|
Set-Location (Get-Item $PSScriptRoot)
|
||||||
|
|
||||||
. ./KoalaBox/build.ps1 @args
|
. ./KoalaBox/build.ps1 SmokeAPI @args
|
||||||
|
|
||||||
Build-Project
|
Build-Project
|
||||||
|
|||||||
30
res/SmokeAPI.config.json
Normal file
30
res/SmokeAPI.config.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"$version": 2,
|
||||||
|
"logging": true,
|
||||||
|
"unlock_family_sharing": true,
|
||||||
|
"default_app_status": "unlocked",
|
||||||
|
"override_app_status": {
|
||||||
|
"1234": "original",
|
||||||
|
"4321": "unlocked"
|
||||||
|
},
|
||||||
|
"override_dlc_status": {
|
||||||
|
"1234": "original",
|
||||||
|
"4321": "unlocked",
|
||||||
|
"5678": "locked"
|
||||||
|
},
|
||||||
|
"auto_inject_inventory": true,
|
||||||
|
"extra_inventory_items": [],
|
||||||
|
"extra_dlcs": {
|
||||||
|
"1234": {
|
||||||
|
"dlcs": {
|
||||||
|
"56789": "Example DLC 1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4321": {
|
||||||
|
"dlcs": {
|
||||||
|
"98765": "Example DLC 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"store_config": null
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"$version": 1,
|
|
||||||
"logging": true,
|
|
||||||
"hook_steamclient": true,
|
|
||||||
"unlock_all": true,
|
|
||||||
"override": [],
|
|
||||||
"dlc_ids": [],
|
|
||||||
"auto_inject_inventory": true,
|
|
||||||
"inventory_items": []
|
|
||||||
}
|
|
||||||
BIN
res/dll/steamworks_sdk_155/sdk/redistributable_bin/steam_api.dll
Normal file
BIN
res/dll/steamworks_sdk_155/sdk/redistributable_bin/steam_api.dll
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define ORIGINAL_DLL "${ORIGINAL_DLL}"
|
#define STEAMAPI_DLL "${STEAMAPI_DLL}"
|
||||||
#define STEAMCLIENT_DLL "${STEAMCLIENT_DLL}"
|
#define STEAMCLIENT_DLL "${STEAMCLIENT_DLL}"
|
||||||
#define VSTDLIB_DLL "${VSTDLIB_DLL}"
|
#define VSTDLIB_DLL "${VSTDLIB_DLL}"
|
||||||
|
|||||||
52
src/common/app_cache.cpp
Normal file
52
src/common/app_cache.cpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#include <common/app_cache.hpp>
|
||||||
|
#include <core/paths.hpp>
|
||||||
|
#include <koalabox/cache.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
|
|
||||||
|
constexpr auto KEY_APPS = "apps";
|
||||||
|
|
||||||
|
AppDlcNameMap get_cached_apps() noexcept {
|
||||||
|
try {
|
||||||
|
const auto cache = koalabox::cache::read_from_cache(KEY_APPS);
|
||||||
|
|
||||||
|
return cache.get<AppDlcNameMap>();
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
LOG_WARN("Failed to get cached apps: {}", e.what())
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace smoke_api::app_cache {
|
||||||
|
|
||||||
|
Vector<DLC> get_dlcs(AppId_t app_id) noexcept {
|
||||||
|
try {
|
||||||
|
LOG_DEBUG("Reading cached DLC IDs for the app: {}", app_id)
|
||||||
|
|
||||||
|
const auto apps = get_cached_apps();
|
||||||
|
|
||||||
|
return DLC::get_dlcs_from_apps(apps, app_id);
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
LOG_ERROR("Error reading DLCs from disk cache: ", e.what())
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool save_dlcs(AppId_t app_id, const Vector<DLC>& dlcs) noexcept {
|
||||||
|
try {
|
||||||
|
LOG_DEBUG("Caching DLC IDs for the app: {}", app_id)
|
||||||
|
|
||||||
|
auto apps = get_cached_apps();
|
||||||
|
|
||||||
|
apps[std::to_string(app_id)] = App{.dlcs=DLC::get_dlc_map_from_vector(dlcs)};
|
||||||
|
|
||||||
|
return koalabox::cache::save_to_cache(KEY_APPS, Json(apps));
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
LOG_ERROR("Error saving DLCs to disk cache: {}", e.what())
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
src/common/app_cache.hpp
Normal file
11
src/common/app_cache.hpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
namespace smoke_api::app_cache {
|
||||||
|
|
||||||
|
Vector<DLC> get_dlcs(AppId_t app_id) noexcept;
|
||||||
|
|
||||||
|
bool save_dlcs(AppId_t app_id, const Vector<DLC>& dlcs) noexcept;
|
||||||
|
|
||||||
|
}
|
||||||
12
src/common/steamclient_exports.cpp
Normal file
12
src/common/steamclient_exports.cpp
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#include <common/steamclient_exports.hpp>
|
||||||
|
#include <steam_impl/steam_client.hpp>
|
||||||
|
|
||||||
|
DLL_EXPORT(void*) CreateInterface(const char* interface_string, int* out_result) {
|
||||||
|
return steam_client::GetGenericInterface(
|
||||||
|
__func__, interface_string, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(CreateInterface)
|
||||||
|
|
||||||
|
return CreateInterface_o(interface_string, out_result);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
3
src/common/steamclient_exports.hpp
Normal file
3
src/common/steamclient_exports.hpp
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
DLL_EXPORT(void*) CreateInterface(const char* interface_string, int* out_result);
|
||||||
46
src/core/api.cpp
Normal file
46
src/core/api.cpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include <core/api.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
|
#include <koalabox/http_client.hpp>
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
struct SteamResponse {
|
||||||
|
uint32_t success = 0;
|
||||||
|
Vector<DLC> dlcs;
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(SteamResponse, success, dlcs) // NOLINT(misc-const-correctness)
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<Vector<DLC>> fetch_dlcs_from_github(AppId_t app_id) noexcept {
|
||||||
|
try {
|
||||||
|
const auto* url =
|
||||||
|
"https://raw.githubusercontent.com/acidicoala/public-entitlements/main/steam/v2/dlc.json";
|
||||||
|
const auto json = koalabox::http_client::fetch_json(url);
|
||||||
|
const auto response = json.get<AppDlcNameMap>();
|
||||||
|
|
||||||
|
return DLC::get_dlcs_from_apps(response, app_id);
|
||||||
|
} catch (const Json::exception& e) {
|
||||||
|
LOG_ERROR("Failed to fetch dlc list from GitHub: {}", e.what())
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 json = koalabox::http_client::fetch_json(url);
|
||||||
|
|
||||||
|
const auto response = json.get<SteamResponse>();
|
||||||
|
|
||||||
|
if (response.success != 1) {
|
||||||
|
throw std::runtime_error("Web API responded with 'success' != 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.dlcs;
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
LOG_ERROR("Failed to fetch dlc list from Steam: {}", e.what())
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
src/core/api.hpp
Normal file
11
src/core/api.hpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
namespace api {
|
||||||
|
|
||||||
|
std::optional<Vector<DLC>> fetch_dlcs_from_github(AppId_t app_id) noexcept;
|
||||||
|
|
||||||
|
std::optional<Vector<DLC>> fetch_dlcs_from_steam(AppId_t app_id) noexcept;
|
||||||
|
|
||||||
|
}
|
||||||
11
src/core/globals.cpp
Normal file
11
src/core/globals.cpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include <core/globals.hpp>
|
||||||
|
|
||||||
|
namespace globals {
|
||||||
|
// TODO: Refactor to koalabox?
|
||||||
|
HMODULE smokeapi_handle = nullptr;
|
||||||
|
HMODULE steamapi_module = nullptr;
|
||||||
|
HMODULE vstdlib_module = nullptr;
|
||||||
|
HMODULE steamclient_module = nullptr;
|
||||||
|
Map<String, uintptr_t> address_map; // NOLINT(cert-err58-cpp)
|
||||||
|
|
||||||
|
}
|
||||||
13
src/core/globals.hpp
Normal file
13
src/core/globals.hpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <koalabox/core.hpp>
|
||||||
|
|
||||||
|
namespace globals {
|
||||||
|
|
||||||
|
extern HMODULE smokeapi_handle;
|
||||||
|
extern HMODULE steamclient_module;
|
||||||
|
extern HMODULE steamapi_module;
|
||||||
|
extern HMODULE vstdlib_module;
|
||||||
|
extern Map<String, uintptr_t> address_map;
|
||||||
|
|
||||||
|
}
|
||||||
28
src/core/paths.cpp
Normal file
28
src/core/paths.cpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#include <core/paths.hpp>
|
||||||
|
#include <core/globals.hpp>
|
||||||
|
#include <koalabox/loader.hpp>
|
||||||
|
|
||||||
|
// TODO: Refactor to KoalaBox
|
||||||
|
namespace paths {
|
||||||
|
|
||||||
|
Path get_self_path() {
|
||||||
|
static const auto path = koalabox::loader::get_module_dir(globals::smokeapi_handle);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path get_config_path() {
|
||||||
|
static const auto path = get_self_path() / "SmokeAPI.config.json";
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path get_cache_path() {
|
||||||
|
static const auto path = get_self_path() / "SmokeAPI.cache.json";
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path get_log_path() {
|
||||||
|
static const auto path = get_self_path() / "SmokeAPI.log.log";
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
src/core/paths.hpp
Normal file
15
src/core/paths.hpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <koalabox/core.hpp>
|
||||||
|
|
||||||
|
namespace paths {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return An std::path instance representing the directory containing this DLL
|
||||||
|
*/
|
||||||
|
Path get_self_path();
|
||||||
|
Path get_config_path();
|
||||||
|
Path get_cache_path();
|
||||||
|
Path get_log_path();
|
||||||
|
|
||||||
|
}
|
||||||
26
src/core/types.cpp
Normal file
26
src/core/types.cpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
Vector<DLC> DLC::get_dlcs_from_apps(const AppDlcNameMap& apps, AppId_t app_id) {
|
||||||
|
Vector<DLC> dlcs;
|
||||||
|
|
||||||
|
const auto app_id_str = std::to_string(app_id);
|
||||||
|
if (apps.contains(app_id_str)) {
|
||||||
|
const auto& app = apps.at(app_id_str);
|
||||||
|
|
||||||
|
for (auto const& [id, name]: app.dlcs) {
|
||||||
|
dlcs.emplace_back(id, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dlcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
DlcNameMap DLC::get_dlc_map_from_vector(const Vector<DLC>& dlcs) {
|
||||||
|
DlcNameMap map;
|
||||||
|
|
||||||
|
for (const auto& dlc: dlcs) {
|
||||||
|
map[dlc.get_id_str()] = dlc.get_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
140
src/core/types.hpp
Normal file
140
src/core/types.hpp
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#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
|
||||||
|
* convention, which is normal since they are class members.
|
||||||
|
* But it presents an issue for us, since we cannot pass *this
|
||||||
|
* pointer as a function argument. This is because *this
|
||||||
|
* pointer is passed via register ECX in __thiscall
|
||||||
|
* convention. Hence, to resolve this issue we declare our
|
||||||
|
* hooked functions with __fastcall convention, to trick
|
||||||
|
* the compiler into reading ECX & EDX registers as 1st
|
||||||
|
* and 2nd function arguments respectively. Similarly, __fastcall
|
||||||
|
* makes the compiler push the first argument into the ECX register,
|
||||||
|
* which mimics the __thiscall calling convention. Register EDX
|
||||||
|
* is not used anywhere in this case, but we still pass it along
|
||||||
|
* to conform to the __fastcall convention. This all applies
|
||||||
|
* to the x86 architecture.
|
||||||
|
*
|
||||||
|
* In x86-64 however, there is only one calling convention,
|
||||||
|
* so __fastcall is simply ignored. However, RDX in this case
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#ifdef _WIN64
|
||||||
|
#define PARAMS(...) void* RCX, __VA_ARGS__
|
||||||
|
#define ARGS(...) RCX, __VA_ARGS__
|
||||||
|
#define THIS RCX
|
||||||
|
#else
|
||||||
|
#define PARAMS(...) const void* ECX, const void* EDX, __VA_ARGS__
|
||||||
|
#define ARGS(...) ECX, EDX, __VA_ARGS__
|
||||||
|
#define THIS ECX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Names beginning with $ designate macros that are not meant to be used directly by the sources consuming this file
|
||||||
|
|
||||||
|
#define DLL_EXPORT(TYPE) extern "C" [[maybe_unused]] __declspec( dllexport ) TYPE __cdecl
|
||||||
|
#define VIRTUAL(TYPE) __declspec(noinline) TYPE __fastcall
|
||||||
|
|
||||||
|
#define GET_ORIGINAL_HOOKED_FUNCTION(FUNC) \
|
||||||
|
static const auto FUNC##_o = koalabox::hook::get_original_hooked_function(globals::address_map, #FUNC, FUNC);
|
||||||
|
|
||||||
|
#define GET_ORIGINAL_FUNCTION_STEAMAPI(FUNC) \
|
||||||
|
static const auto FUNC##_o = koalabox::hook::get_original_function(globals::steamapi_module, #FUNC, FUNC);
|
||||||
|
|
||||||
|
|
||||||
|
#define DETOUR_ADDRESS(FUNC, ADDRESS) \
|
||||||
|
koalabox::hook::detour_or_warn(globals::address_map, ADDRESS, #FUNC, reinterpret_cast<uintptr_t>(FUNC));
|
||||||
|
|
||||||
|
#define $DETOUR(FUNC, NAME, MODULE_HANDLE) \
|
||||||
|
koalabox::hook::detour_or_warn(globals::address_map, MODULE_HANDLE, NAME, reinterpret_cast<uintptr_t>(FUNC));
|
||||||
|
|
||||||
|
#define DETOUR_STEAMCLIENT(FUNC) $DETOUR(FUNC, #FUNC, globals::steamclient_module)
|
||||||
|
#define DETOUR_VSTDLIB(FUNC) $DETOUR(vstdlib::FUNC, #FUNC, globals::vstdlib_module)
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
#define COMPILE_STORE_MODE 0
|
||||||
|
#else
|
||||||
|
#define COMPILE_STORE_MODE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr auto STEAM_APPS = "STEAMAPPS_INTERFACE_VERSION";
|
||||||
|
constexpr auto STEAM_CLIENT = "SteamClient";
|
||||||
|
constexpr auto STEAM_USER = "SteamUser";
|
||||||
|
constexpr auto STEAM_INVENTORY = "STEAMINVENTORY_INTERFACE_V";
|
||||||
|
constexpr auto CLIENT_ENGINE = "CLIENTENGINE_INTERFACE_VERSION";
|
||||||
|
|
||||||
|
using AppId_t = uint32_t;
|
||||||
|
using SteamInventoryResult_t = uint32_t;
|
||||||
|
using SteamItemInstanceID_t = uint64_t;
|
||||||
|
using SteamItemDef_t = uint32_t;
|
||||||
|
using HSteamPipe = uint32_t;
|
||||||
|
using HSteamUser = uint32_t;
|
||||||
|
using CSteamID = uint64_t;
|
||||||
|
using EResult = uint32_t;
|
||||||
|
|
||||||
|
struct SteamItemDetails_t {
|
||||||
|
SteamItemInstanceID_t m_itemId;
|
||||||
|
uint32_t m_iDefinition;
|
||||||
|
uint16_t m_unQuantity;
|
||||||
|
uint16_t m_unFlags; // see ESteamItemFlags
|
||||||
|
};
|
||||||
|
|
||||||
|
// results from UserHasLicenseForApp
|
||||||
|
enum EUserHasLicenseForAppResult {
|
||||||
|
k_EUserHasLicenseResultHasLicense = 0, // User has a license for specified app
|
||||||
|
k_EUserHasLicenseResultDoesNotHaveLicense = 1, // User does not have a license for the specified app
|
||||||
|
k_EUserHasLicenseResultNoAuth = 2, // User has not been authenticated
|
||||||
|
};
|
||||||
|
|
||||||
|
// These aliases exist solely to increase code readability
|
||||||
|
|
||||||
|
using AppIdKey = String;
|
||||||
|
using DlcIdKey = String;
|
||||||
|
using DlcNameValue = String;
|
||||||
|
using DlcNameMap = Map<DlcIdKey, DlcNameValue>;
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
DlcNameMap dlcs;
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(App, dlcs) // NOLINT(misc-const-correctness)
|
||||||
|
};
|
||||||
|
|
||||||
|
using AppDlcNameMap = Map<AppIdKey, App>;
|
||||||
|
|
||||||
|
class DLC {
|
||||||
|
private:
|
||||||
|
// These 2 names must match the property names from Steam API
|
||||||
|
String appid;
|
||||||
|
String name;
|
||||||
|
public:
|
||||||
|
explicit DLC() = default;
|
||||||
|
|
||||||
|
explicit DLC(String appid, String name) : appid{std::move(appid)}, name{std::move(name)} {}
|
||||||
|
|
||||||
|
[[nodiscard]] String get_id_str() const {
|
||||||
|
return appid;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] uint32_t get_id() const {
|
||||||
|
return std::stoi(appid);
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] String get_name() const {
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
227
src/game_mode/exports/steam_api_flat.cpp
Normal file
227
src/game_mode/exports/steam_api_flat.cpp
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
#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
|
||||||
|
|
||||||
|
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(void* self, AppId_t dlcID) {
|
||||||
|
return steam_apps::IsDlcUnlocked(
|
||||||
|
__func__, 0, dlcID, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BIsSubscribedApp)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamApps_BIsSubscribedApp_o(self, dlcID);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsDlcInstalled(void* self, AppId_t dlcID) {
|
||||||
|
return steam_apps::IsDlcUnlocked(
|
||||||
|
__func__, 0, dlcID, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BIsDlcInstalled)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamApps_BIsDlcInstalled_o(self, dlcID);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(int) SteamAPI_ISteamApps_GetDLCCount(void* self) {
|
||||||
|
return steam_apps::GetDLCCount(
|
||||||
|
__func__, 0, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_GetDLCCount)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamApps_GetDLCCount_o(self);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(bool) SteamAPI_ISteamApps_BGetDLCDataByIndex(
|
||||||
|
void* self,
|
||||||
|
int iDLC,
|
||||||
|
AppId_t* pDlcID,
|
||||||
|
bool* pbAvailable,
|
||||||
|
char* pchName,
|
||||||
|
int cchNameBufferSize
|
||||||
|
) {
|
||||||
|
return steam_apps::GetDLCDataByIndex(
|
||||||
|
__func__, 0, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize,
|
||||||
|
[&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BGetDLCDataByIndex)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamApps_BGetDLCDataByIndex_o(
|
||||||
|
self, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[&](AppId_t dlc_id) {
|
||||||
|
return SteamAPI_ISteamApps_BIsDlcInstalled(self, dlc_id);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISteamClient
|
||||||
|
|
||||||
|
DLL_EXPORT(void*) SteamAPI_ISteamClient_GetISteamGenericInterface(
|
||||||
|
void* self,
|
||||||
|
HSteamUser hSteamUser,
|
||||||
|
HSteamPipe hSteamPipe,
|
||||||
|
const char* pchVersion
|
||||||
|
) {
|
||||||
|
return steam_client::GetGenericInterface(
|
||||||
|
__func__, pchVersion, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamClient_GetISteamGenericInterface)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamClient_GetISteamGenericInterface_o(self, hSteamUser, hSteamPipe, pchVersion);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISteamInventory
|
||||||
|
|
||||||
|
DLL_EXPORT(EResult) SteamAPI_ISteamInventory_GetResultStatus(
|
||||||
|
void* self,
|
||||||
|
SteamInventoryResult_t resultHandle
|
||||||
|
) {
|
||||||
|
return steam_inventory::GetResultStatus(
|
||||||
|
__func__, resultHandle, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_GetResultStatus)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamInventory_GetResultStatus_o(self, resultHandle);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
SteamItemDetails_t* pOutItemsArray,
|
||||||
|
uint32_t* punOutItemsArraySize
|
||||||
|
) {
|
||||||
|
return steam_inventory::GetResultItems(
|
||||||
|
__func__, resultHandle, pOutItemsArray, punOutItemsArraySize,
|
||||||
|
[&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_GetResultItems)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamInventory_GetResultItems_o(self, resultHandle, pOutItemsArray, punOutItemsArraySize);
|
||||||
|
},
|
||||||
|
[&](SteamItemDef_t* pItemDefIDs, uint32_t* punItemDefIDsArraySize) {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_GetItemDefinitionIDs)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamInventory_GetItemDefinitionIDs_o(self, pItemDefIDs, punItemDefIDsArraySize);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetResultItemProperty(
|
||||||
|
void* self,
|
||||||
|
SteamInventoryResult_t resultHandle,
|
||||||
|
uint32_t unItemIndex,
|
||||||
|
const char* pchPropertyName,
|
||||||
|
char* pchValueBuffer,
|
||||||
|
uint32_t* punValueBufferSizeOut
|
||||||
|
) {
|
||||||
|
return steam_inventory::GetResultItemProperty(
|
||||||
|
__func__, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_GetResultItemProperty)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamInventory_GetResultItemProperty_o(
|
||||||
|
self, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(bool) SteamAPI_ISteamInventory_CheckResultSteamID(
|
||||||
|
void* self,
|
||||||
|
SteamInventoryResult_t resultHandle,
|
||||||
|
CSteamID steamIDExpected
|
||||||
|
) {
|
||||||
|
return steam_inventory::CheckResultSteamID(
|
||||||
|
__func__, resultHandle, steamIDExpected, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_CheckResultSteamID)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamInventory_CheckResultSteamID_o(self, resultHandle, steamIDExpected);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetAllItems(
|
||||||
|
void* self,
|
||||||
|
SteamInventoryResult_t* pResultHandle
|
||||||
|
) {
|
||||||
|
return steam_inventory::GetAllItems(
|
||||||
|
__func__, pResultHandle, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_GetAllItems)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamInventory_GetAllItems_o(self, pResultHandle);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemsByID(
|
||||||
|
void* self,
|
||||||
|
SteamInventoryResult_t* pResultHandle,
|
||||||
|
const SteamItemInstanceID_t* pInstanceIDs,
|
||||||
|
uint32_t unCountInstanceIDs
|
||||||
|
) {
|
||||||
|
return steam_inventory::GetItemsByID(
|
||||||
|
__func__, pResultHandle, pInstanceIDs, unCountInstanceIDs, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_GetItemsByID)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamInventory_GetItemsByID_o(self, pResultHandle, pInstanceIDs, unCountInstanceIDs);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(bool) SteamAPI_ISteamInventory_SerializeResult(
|
||||||
|
void* self,
|
||||||
|
SteamInventoryResult_t resultHandle,
|
||||||
|
void* pOutBuffer,
|
||||||
|
uint32_t* punOutBufferSize
|
||||||
|
) {
|
||||||
|
return steam_inventory::SerializeResult(
|
||||||
|
__func__, resultHandle, pOutBuffer, punOutBufferSize, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamInventory_SerializeResult)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamInventory_SerializeResult_o(self, resultHandle, pOutBuffer, punOutBufferSize);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISteamUser
|
||||||
|
|
||||||
|
DLL_EXPORT(EUserHasLicenseForAppResult) SteamAPI_ISteamUser_UserHasLicenseForApp(
|
||||||
|
void* self,
|
||||||
|
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__, app_id, dlcID, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamUser_UserHasLicenseForApp)
|
||||||
|
|
||||||
|
return SteamAPI_ISteamUser_UserHasLicenseForApp_o(self, steamID, dlcID);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
21
src/game_mode/exports/steam_api_internal.cpp
Normal file
21
src/game_mode/exports/steam_api_internal.cpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include <steam_impl/steam_client.hpp>
|
||||||
|
|
||||||
|
DLL_EXPORT(void*) SteamInternal_FindOrCreateUserInterface(HSteamUser hSteamUser, const char* version) {
|
||||||
|
return steam_client::GetGenericInterface(
|
||||||
|
__func__, version, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamInternal_FindOrCreateUserInterface)
|
||||||
|
|
||||||
|
return SteamInternal_FindOrCreateUserInterface_o(hSteamUser, version);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(void*) SteamInternal_CreateInterface(const char* version) {
|
||||||
|
return steam_client::GetGenericInterface(
|
||||||
|
__func__, version, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamInternal_CreateInterface)
|
||||||
|
|
||||||
|
return SteamInternal_CreateInterface_o(version);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
#include <steam_impl/steam_client.hpp>
|
||||||
#include <steam_impl/steam_impl.hpp>
|
#include <koalabox/logger.hpp>
|
||||||
#include <steam_functions/steam_functions.hpp>
|
|
||||||
|
|
||||||
#include <koalabox/win_util.hpp>
|
#include <koalabox/win_util.hpp>
|
||||||
|
#include <koalabox/util.hpp>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
using namespace steam_functions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches the `.rdata` section of the original dll for the full interface version string
|
* Searches the `.rdata` section of the original dll for the full interface version string
|
||||||
* Results are cached for performance.
|
* Results are cached for performance.
|
||||||
@@ -18,21 +13,21 @@ String get_versioned_interface(const String& version_prefix, const String& fallb
|
|||||||
|
|
||||||
if (not version_map.contains(version_prefix)) {
|
if (not version_map.contains(version_prefix)) {
|
||||||
try {
|
try {
|
||||||
String rdata = win_util::get_pe_section_data_or_throw(original_library, ".rdata");
|
const String rdata = koalabox::win_util::get_pe_section_data_or_throw(globals::steamapi_module, ".rdata");
|
||||||
|
|
||||||
std::regex regex(version_prefix + "\\d{3}");
|
const std::regex regex(version_prefix + "\\d{3}");
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
if (std::regex_search(rdata, match, regex)) {
|
if (std::regex_search(rdata, match, regex)) {
|
||||||
version_map[version_prefix] = match[0];
|
version_map[version_prefix] = match[0];
|
||||||
return version_map[version_prefix];
|
return version_map[version_prefix];
|
||||||
}
|
}
|
||||||
|
|
||||||
throw util::exception("No match found for '{}'", version_prefix);
|
throw koalabox::util::exception("No match found for '{}'", version_prefix);
|
||||||
} catch (const Exception& ex) {
|
} catch (const Exception& ex) {
|
||||||
logger->error(
|
LOG_ERROR(
|
||||||
"Failed to get versioned interface: {}."
|
"Failed to get versioned interface: {}."
|
||||||
"Falling back to version {}", ex.what(), fallback
|
"Falling back to version {}", ex.what(), fallback
|
||||||
);
|
)
|
||||||
|
|
||||||
version_map[version_prefix] = version_prefix + fallback;
|
version_map[version_prefix] = version_prefix + fallback;
|
||||||
}
|
}
|
||||||
@@ -44,40 +39,47 @@ String get_versioned_interface(const String& version_prefix, const String& fallb
|
|||||||
DLL_EXPORT(void*) SteamClient() {
|
DLL_EXPORT(void*) SteamClient() {
|
||||||
static auto version = get_versioned_interface(STEAM_CLIENT, "006");
|
static auto version = get_versioned_interface(STEAM_CLIENT, "006");
|
||||||
|
|
||||||
return steam_client::GetGenericInterface(__func__, version, [&]() {
|
return steam_client::GetGenericInterface(
|
||||||
GET_ORIGINAL_FUNCTION(SteamClient)
|
__func__, version, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamClient)
|
||||||
|
|
||||||
return SteamClient_o();
|
return SteamClient_o();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
DLL_EXPORT(void*) SteamApps() {
|
DLL_EXPORT(void*) SteamApps() {
|
||||||
static auto version = get_versioned_interface(STEAM_APPS, "002");
|
static auto version = get_versioned_interface(STEAM_APPS, "002");
|
||||||
|
|
||||||
return steam_client::GetGenericInterface(__func__, version, [&]() {
|
return steam_client::GetGenericInterface(
|
||||||
GET_ORIGINAL_FUNCTION(SteamApps)
|
__func__, version, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamApps)
|
||||||
|
|
||||||
return SteamApps_o();
|
return SteamApps_o();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
DLL_EXPORT(void*) SteamUser() {
|
DLL_EXPORT(void*) SteamUser() {
|
||||||
static auto version = get_versioned_interface(STEAM_USER, "012");
|
static auto version = get_versioned_interface(STEAM_USER, "012");
|
||||||
|
|
||||||
return steam_client::GetGenericInterface(__func__, version, [&]() {
|
return steam_client::GetGenericInterface(
|
||||||
GET_ORIGINAL_FUNCTION(SteamUser)
|
__func__, version, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamUser)
|
||||||
|
|
||||||
return SteamUser_o();
|
return SteamUser_o();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DLL_EXPORT(void*) SteamInventory() {
|
DLL_EXPORT(void*) SteamInventory() {
|
||||||
static auto version = get_versioned_interface(STEAM_INVENTORY, "001");
|
static auto version = get_versioned_interface(STEAM_INVENTORY, "001");
|
||||||
|
|
||||||
return steam_client::GetGenericInterface(__func__, version, [&]() {
|
return steam_client::GetGenericInterface(
|
||||||
GET_ORIGINAL_FUNCTION(SteamInventory)
|
__func__, version, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamInventory)
|
||||||
|
|
||||||
return SteamInventory_o();
|
return SteamInventory_o();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
56
src/game_mode/virtuals/isteamapps.cpp
Normal file
56
src/game_mode/virtuals/isteamapps.cpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#include <game_mode/virtuals/steam_api_virtuals.hpp>
|
||||||
|
#include <steam_impl/steam_apps.hpp>
|
||||||
|
|
||||||
|
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t appID)) {
|
||||||
|
return steam_apps::IsDlcUnlocked(
|
||||||
|
__func__, 0, appID, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(ISteamApps_BIsSubscribedApp)
|
||||||
|
|
||||||
|
return ISteamApps_BIsSubscribedApp_o(ARGS(appID));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t appID)) {
|
||||||
|
return steam_apps::IsDlcUnlocked(
|
||||||
|
__func__, 0, appID, [&]() {
|
||||||
|
GET_ORIGINAL_FUNCTION_STEAMAPI(ISteamApps_BIsDlcInstalled)
|
||||||
|
|
||||||
|
return ISteamApps_BIsDlcInstalled_o(ARGS(appID));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VIRTUAL(int) ISteamApps_GetDLCCount(PARAMS()) {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_GetDLCCount)
|
||||||
|
|
||||||
|
return steam_apps::GetDLCCount(
|
||||||
|
__func__, 0, [&]() {
|
||||||
|
return ISteamApps_GetDLCCount_o(ARGS());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VIRTUAL(bool) ISteamApps_BGetDLCDataByIndex(
|
||||||
|
PARAMS(
|
||||||
|
int iDLC,
|
||||||
|
AppId_t* pAppID,
|
||||||
|
bool* pbAvailable,
|
||||||
|
char* pchName,
|
||||||
|
int cchNameBufferSize
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return steam_apps::GetDLCDataByIndex(
|
||||||
|
__func__, 0, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize,
|
||||||
|
[&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_BGetDLCDataByIndex)
|
||||||
|
|
||||||
|
return ISteamApps_BGetDLCDataByIndex_o(
|
||||||
|
ARGS(iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[&](AppId_t dlc_id) {
|
||||||
|
return ISteamApps_BIsDlcInstalled(ARGS(dlc_id));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
66
src/game_mode/virtuals/isteamclient.cpp
Normal file
66
src/game_mode/virtuals/isteamclient.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#include <game_mode/virtuals/steam_api_virtuals.hpp>
|
||||||
|
#include <steam_impl/steam_client.hpp>
|
||||||
|
|
||||||
|
VIRTUAL(void*) ISteamClient_GetISteamApps(
|
||||||
|
PARAMS(
|
||||||
|
HSteamUser hSteamUser,
|
||||||
|
HSteamPipe hSteamPipe,
|
||||||
|
const char* version
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return steam_client::GetGenericInterface(
|
||||||
|
__func__, version, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamApps)
|
||||||
|
|
||||||
|
return ISteamClient_GetISteamApps_o(ARGS(hSteamUser, hSteamPipe, version));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VIRTUAL(void*) ISteamClient_GetISteamUser(
|
||||||
|
PARAMS(
|
||||||
|
HSteamUser hSteamUser,
|
||||||
|
HSteamPipe hSteamPipe,
|
||||||
|
const char* version
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return steam_client::GetGenericInterface(
|
||||||
|
__func__, version, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamUser)
|
||||||
|
|
||||||
|
return ISteamClient_GetISteamUser_o(ARGS(hSteamUser, hSteamPipe, version));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VIRTUAL(void*) ISteamClient_GetISteamGenericInterface(
|
||||||
|
PARAMS(
|
||||||
|
HSteamUser hSteamUser,
|
||||||
|
HSteamPipe hSteamPipe,
|
||||||
|
const char* pchVersion
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return steam_client::GetGenericInterface(
|
||||||
|
__func__, pchVersion, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamGenericInterface)
|
||||||
|
|
||||||
|
return ISteamClient_GetISteamGenericInterface_o(ARGS(hSteamUser, hSteamPipe, pchVersion));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VIRTUAL(void*) ISteamClient_GetISteamInventory(
|
||||||
|
PARAMS(
|
||||||
|
HSteamUser hSteamUser,
|
||||||
|
HSteamPipe hSteamPipe,
|
||||||
|
const char* pchVersion
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return steam_client::GetGenericInterface(
|
||||||
|
__func__, pchVersion, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamInventory)
|
||||||
|
|
||||||
|
return ISteamClient_GetISteamInventory_o(ARGS(hSteamUser, hSteamPipe, pchVersion));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
#include <game_mode/virtuals/steam_api_virtuals.hpp>
|
||||||
#include <steam_impl/steam_impl.hpp>
|
#include <steam_impl/steam_inventory.hpp>
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
VIRTUAL(EResult) ISteamInventory_GetResultStatus(PARAMS(SteamInventoryResult_t resultHandle)) {
|
VIRTUAL(EResult) ISteamInventory_GetResultStatus(PARAMS(SteamInventoryResult_t resultHandle)) {
|
||||||
return steam_inventory::GetResultStatus(__func__, resultHandle, [&]() {
|
return steam_inventory::GetResultStatus(
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamInventory_GetResultStatus)
|
__func__, resultHandle, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamInventory_GetResultStatus)
|
||||||
|
|
||||||
return ISteamInventory_GetResultStatus_o(ARGS(resultHandle));
|
return ISteamInventory_GetResultStatus_o(ARGS(resultHandle));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
VIRTUAL(bool) ISteamInventory_GetResultItems(
|
VIRTUAL(bool) ISteamInventory_GetResultItems(
|
||||||
@@ -18,16 +18,15 @@ VIRTUAL(bool) ISteamInventory_GetResultItems(
|
|||||||
uint32_t * punOutItemsArraySize
|
uint32_t * punOutItemsArraySize
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return steam_inventory::GetResultItems(
|
return steam_inventory::GetResultItems(
|
||||||
__func__, resultHandle, pOutItemsArray, punOutItemsArraySize,
|
__func__, resultHandle, pOutItemsArray, punOutItemsArraySize,
|
||||||
[&]() {
|
[&]() {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamInventory_GetResultItems)
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamInventory_GetResultItems)
|
||||||
|
|
||||||
return ISteamInventory_GetResultItems_o(ARGS(resultHandle, pOutItemsArray, punOutItemsArraySize));
|
return ISteamInventory_GetResultItems_o(ARGS(resultHandle, pOutItemsArray, punOutItemsArraySize));
|
||||||
},
|
},
|
||||||
[&](SteamItemDef_t* pItemDefIDs, uint32_t* punItemDefIDsArraySize) {
|
[&](SteamItemDef_t* pItemDefIDs, uint32_t* punItemDefIDsArraySize) {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamInventory_GetItemDefinitionIDs)
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamInventory_GetItemDefinitionIDs)
|
||||||
|
|
||||||
return ISteamInventory_GetItemDefinitionIDs_o(ARGS(pItemDefIDs, punItemDefIDsArraySize));
|
return ISteamInventory_GetItemDefinitionIDs_o(ARGS(pItemDefIDs, punItemDefIDsArraySize));
|
||||||
}
|
}
|
||||||
@@ -45,7 +44,7 @@ VIRTUAL(bool) ISteamInventory_GetResultItemProperty(
|
|||||||
) {
|
) {
|
||||||
return steam_inventory::GetResultItemProperty(
|
return steam_inventory::GetResultItemProperty(
|
||||||
__func__, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut, [&]() {
|
__func__, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut, [&]() {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamInventory_GetResultItemProperty)
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamInventory_GetResultItemProperty)
|
||||||
|
|
||||||
return ISteamInventory_GetResultItemProperty_o(
|
return ISteamInventory_GetResultItemProperty_o(
|
||||||
ARGS(resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut)
|
ARGS(resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut)
|
||||||
@@ -60,19 +59,23 @@ VIRTUAL(bool) ISteamInventory_CheckResultSteamID(
|
|||||||
CSteamID steamIDExpected
|
CSteamID steamIDExpected
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return steam_inventory::CheckResultSteamID(__func__, resultHandle, steamIDExpected, [&]() {
|
return steam_inventory::CheckResultSteamID(
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamInventory_CheckResultSteamID)
|
__func__, resultHandle, steamIDExpected, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamInventory_CheckResultSteamID)
|
||||||
|
|
||||||
return ISteamInventory_CheckResultSteamID_o(ARGS(resultHandle, steamIDExpected));
|
return ISteamInventory_CheckResultSteamID_o(ARGS(resultHandle, steamIDExpected));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
VIRTUAL(bool) ISteamInventory_GetAllItems(PARAMS(SteamInventoryResult_t* pResultHandle)) {
|
VIRTUAL(bool) ISteamInventory_GetAllItems(PARAMS(SteamInventoryResult_t* pResultHandle)) {
|
||||||
return steam_inventory::GetAllItems(__func__, pResultHandle, [&]() {
|
return steam_inventory::GetAllItems(
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamInventory_GetAllItems)
|
__func__, pResultHandle, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamInventory_GetAllItems)
|
||||||
|
|
||||||
return ISteamInventory_GetAllItems_o(ARGS(pResultHandle));
|
return ISteamInventory_GetAllItems_o(ARGS(pResultHandle));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
VIRTUAL(bool) ISteamInventory_GetItemsByID(
|
VIRTUAL(bool) ISteamInventory_GetItemsByID(
|
||||||
@@ -82,11 +85,13 @@ VIRTUAL(bool) ISteamInventory_GetItemsByID(
|
|||||||
uint32_t unCountInstanceIDs
|
uint32_t unCountInstanceIDs
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return steam_inventory::GetItemsByID(__func__, pResultHandle, pInstanceIDs, unCountInstanceIDs, [&]() {
|
return steam_inventory::GetItemsByID(
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamInventory_GetItemsByID)
|
__func__, pResultHandle, pInstanceIDs, unCountInstanceIDs, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamInventory_GetItemsByID)
|
||||||
|
|
||||||
return ISteamInventory_GetItemsByID_o(ARGS(pResultHandle, pInstanceIDs, unCountInstanceIDs));
|
return ISteamInventory_GetItemsByID_o(ARGS(pResultHandle, pInstanceIDs, unCountInstanceIDs));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
VIRTUAL(bool) ISteamInventory_SerializeResult(
|
VIRTUAL(bool) ISteamInventory_SerializeResult(
|
||||||
@@ -96,11 +101,13 @@ VIRTUAL(bool) ISteamInventory_SerializeResult(
|
|||||||
uint32_t * punOutBufferSize
|
uint32_t * punOutBufferSize
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return steam_inventory::SerializeResult(__func__, resultHandle, pOutBuffer, punOutBufferSize, [&]() {
|
return steam_inventory::SerializeResult(
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamInventory_SerializeResult)
|
__func__, resultHandle, pOutBuffer, punOutBufferSize, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamInventory_SerializeResult)
|
||||||
|
|
||||||
return ISteamInventory_SerializeResult_o(ARGS(resultHandle, pOutBuffer, punOutBufferSize));
|
return ISteamInventory_SerializeResult_o(ARGS(resultHandle, pOutBuffer, punOutBufferSize));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
VIRTUAL(bool) ISteamInventory_GetItemDefinitionIDs(
|
VIRTUAL(bool) ISteamInventory_GetItemDefinitionIDs(
|
||||||
@@ -109,9 +116,11 @@ VIRTUAL(bool) ISteamInventory_GetItemDefinitionIDs(
|
|||||||
uint32_t * punItemDefIDsArraySize
|
uint32_t * punItemDefIDsArraySize
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return steam_inventory::GetItemDefinitionIDs(__func__, pItemDefIDs, punItemDefIDsArraySize, [&]() {
|
return steam_inventory::GetItemDefinitionIDs(
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamInventory_GetItemDefinitionIDs)
|
__func__, pItemDefIDs, punItemDefIDsArraySize, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamInventory_GetItemDefinitionIDs)
|
||||||
|
|
||||||
return ISteamInventory_GetItemDefinitionIDs_o(ARGS(pItemDefIDs, punItemDefIDsArraySize));
|
return ISteamInventory_GetItemDefinitionIDs_o(ARGS(pItemDefIDs, punItemDefIDsArraySize));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
21
src/game_mode/virtuals/isteamuser.cpp
Normal file
21
src/game_mode/virtuals/isteamuser.cpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#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__, app_id, dlcID, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(ISteamUser_UserHasLicenseForApp)
|
||||||
|
|
||||||
|
return ISteamUser_UserHasLicenseForApp_o(ARGS(steamID, dlcID));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
31
src/game_mode/virtuals/steam_api_virtuals.hpp
Normal file
31
src/game_mode/virtuals/steam_api_virtuals.hpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
// ISteamApps
|
||||||
|
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t));
|
||||||
|
VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t));
|
||||||
|
VIRTUAL(int) ISteamApps_GetDLCCount(PARAMS());
|
||||||
|
VIRTUAL(bool) ISteamApps_BGetDLCDataByIndex(PARAMS(int, AppId_t*, bool*, char*, int));
|
||||||
|
|
||||||
|
// ISteamClient
|
||||||
|
VIRTUAL(void*) ISteamClient_GetISteamApps(PARAMS(HSteamUser, HSteamPipe, const char*));
|
||||||
|
VIRTUAL(void*) ISteamClient_GetISteamUser(PARAMS(HSteamUser, HSteamPipe, const char*));
|
||||||
|
VIRTUAL(void*) ISteamClient_GetISteamGenericInterface(PARAMS(HSteamUser, HSteamPipe, const char*));
|
||||||
|
VIRTUAL(void*) ISteamClient_GetISteamInventory(PARAMS(HSteamUser, HSteamPipe, const char*));
|
||||||
|
|
||||||
|
|
||||||
|
// ISteamInventory
|
||||||
|
VIRTUAL(EResult) ISteamInventory_GetResultStatus(PARAMS(SteamInventoryResult_t));
|
||||||
|
VIRTUAL(bool) ISteamInventory_GetResultItems(PARAMS(SteamInventoryResult_t, SteamItemDetails_t*, uint32_t*));
|
||||||
|
VIRTUAL(bool) ISteamInventory_GetResultItemProperty(
|
||||||
|
PARAMS(SteamInventoryResult_t, uint32_t, const char*, char*, uint32_t*)
|
||||||
|
);
|
||||||
|
VIRTUAL(bool) ISteamInventory_GetAllItems(PARAMS(SteamInventoryResult_t*));
|
||||||
|
VIRTUAL(bool) ISteamInventory_GetItemsByID(PARAMS(SteamInventoryResult_t*, const SteamItemInstanceID_t*, uint32_t));
|
||||||
|
VIRTUAL(bool) ISteamInventory_SerializeResult(PARAMS(SteamInventoryResult_t, void*, uint32_t*));
|
||||||
|
VIRTUAL(bool) ISteamInventory_GetItemDefinitionIDs(PARAMS(SteamItemDef_t*, uint32_t*));
|
||||||
|
VIRTUAL(bool) ISteamInventory_CheckResultSteamID(PARAMS(SteamInventoryResult_t, CSteamID));
|
||||||
|
|
||||||
|
// ISteamUser
|
||||||
|
VIRTUAL(EUserHasLicenseForAppResult) ISteamUser_UserHasLicenseForApp(PARAMS(CSteamID, AppId_t));
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_functions/steam_functions.hpp>
|
|
||||||
|
|
||||||
#include <koalabox/hook.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
|
|
||||||
DLL_EXPORT(void) Log_Interface(const char* interface_name, const char* function_name) {
|
|
||||||
try {
|
|
||||||
void**** parent_ebp;
|
|
||||||
|
|
||||||
__asm mov parent_ebp, ebp
|
|
||||||
|
|
||||||
auto* interface_address = (*parent_ebp)[2];
|
|
||||||
|
|
||||||
static Set<String> hooked_functions;
|
|
||||||
|
|
||||||
auto hook_function = [&](const auto hook_function, const String& name, const int ordinal) {
|
|
||||||
if (hooked_functions.contains(name)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hook::swap_virtual_func_or_throw(interface_address, name, ordinal, (FunctionAddress) (hook_function));
|
|
||||||
|
|
||||||
hooked_functions.insert(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto compound_name = interface_name + String("::") + function_name;
|
|
||||||
|
|
||||||
#define HOOK(FUNC, ORDINAL) hook_function(FUNC, #FUNC, ORDINAL);
|
|
||||||
|
|
||||||
if (util::strings_are_equal(interface_name, "IClientAppManager")) {
|
|
||||||
HOOK(IClientAppManager_IsAppDlcInstalled, 8)
|
|
||||||
} else if (util::strings_are_equal(interface_name, "IClientApps")) {
|
|
||||||
HOOK(IClientApps_GetDLCCount, 8)
|
|
||||||
HOOK(IClientApps_BGetDLCDataByIndex, 9)
|
|
||||||
} else if (util::strings_are_equal(interface_name, "IClientInventory")) {
|
|
||||||
HOOK(IClientInventory_GetResultStatus, 0)
|
|
||||||
HOOK(IClientInventory_GetResultItems, 2)
|
|
||||||
HOOK(IClientInventory_GetResultItemProperty, 3)
|
|
||||||
HOOK(IClientInventory_CheckResultSteamID, 5)
|
|
||||||
HOOK(IClientInventory_GetAllItems, 8)
|
|
||||||
HOOK(IClientInventory_GetItemsByID, 9)
|
|
||||||
HOOK(IClientInventory_SerializeResult, 6)
|
|
||||||
HOOK(IClientInventory_GetItemDefinitionIDs, 19)
|
|
||||||
}
|
|
||||||
|
|
||||||
GET_ORIGINAL_FUNCTION(Log_Interface)
|
|
||||||
Log_Interface_o(interface_name, function_name);
|
|
||||||
} catch (const Exception& ex) {
|
|
||||||
logger->error("{} -> Error: {}", __func__, ex.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_functions/steam_functions.hpp>
|
|
||||||
|
|
||||||
#include <koalabox/hook.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
#define DETOUR(FUNC, address) hook::detour((FunctionAddress) (address), #FUNC, (FunctionAddress) (FUNC));
|
|
||||||
|
|
||||||
VIRTUAL(bool) SharedLicensesLockStatus(PARAMS(void* arg)) { // NOLINT(misc-unused-parameters)
|
|
||||||
logger->debug("{} -> instance: {}, arg: {}", __func__, fmt::ptr(THIS), fmt::ptr(arg));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL(bool) SharedLibraryStopPlaying(PARAMS(void* arg)) { // NOLINT(misc-unused-parameters)
|
|
||||||
logger->debug("{} -> instance: {}, arg: {}", __func__, fmt::ptr(THIS), fmt::ptr(arg));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CallbackData {
|
|
||||||
[[maybe_unused]] void* pad1[1];
|
|
||||||
void* set_callback_name_address; // to_do: fetch online
|
|
||||||
[[maybe_unused]] void* pad19[17];
|
|
||||||
void* callback_address; // to_do: fetch online
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CoroutineData {
|
|
||||||
CallbackData* callback_data; // to_do: fetch online
|
|
||||||
[[maybe_unused]] uint32_t pad4[3];
|
|
||||||
const char* callback_name; // to_do: fetch online
|
|
||||||
};
|
|
||||||
|
|
||||||
VIRTUAL(void) set_callback_name(PARAMS(const char** p_name)) {
|
|
||||||
GET_ORIGINAL_FUNCTION(set_callback_name)
|
|
||||||
|
|
||||||
set_callback_name_o(ARGS(p_name));
|
|
||||||
|
|
||||||
static auto hooked_functions = 0;
|
|
||||||
|
|
||||||
if (hooked_functions == 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* const data = (CoroutineData*) THIS;
|
|
||||||
|
|
||||||
if (data && data->callback_name) {
|
|
||||||
const auto name = String(data->callback_name);
|
|
||||||
// logger->trace("{} -> instance: {}, name: '{}'", __func__, fmt::ptr(THIS), name);
|
|
||||||
|
|
||||||
if (name == "SharedLicensesLockStatus") {
|
|
||||||
static std::once_flag flag;
|
|
||||||
std::call_once(flag, [&]() {
|
|
||||||
DETOUR(SharedLicensesLockStatus, data->callback_data->callback_address)
|
|
||||||
hooked_functions++;
|
|
||||||
});
|
|
||||||
} else if (name == "SharedLibraryStopPlaying") {
|
|
||||||
static std::once_flag flag;
|
|
||||||
std::call_once(flag, [&]() {
|
|
||||||
DETOUR(SharedLibraryStopPlaying, data->callback_data->callback_address)
|
|
||||||
hooked_functions++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initially, callback data passed into this function is not complete,
|
|
||||||
* hence we must hook an interface method that sets the callback name.
|
|
||||||
*/
|
|
||||||
DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, CoroutineData* data) {
|
|
||||||
GET_ORIGINAL_FUNCTION(Coroutine_Create)
|
|
||||||
|
|
||||||
const auto result = Coroutine_Create_o(callback_address, data);
|
|
||||||
|
|
||||||
// Coroutine callback appears to be always the same
|
|
||||||
static std::once_flag flag;
|
|
||||||
std::call_once(flag, [&]() {
|
|
||||||
logger->debug("Coroutine_Create -> callback: {}, data: {}", callback_address, fmt::ptr(data));
|
|
||||||
DETOUR(set_callback_name, data->callback_data->set_callback_name_address)
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
// This header will be populated at build time
|
// This header will be populated at build time
|
||||||
#include <linker_exports.h>
|
#include <linker_exports.h>
|
||||||
|
|
||||||
EXTERN_C [[maybe_unused]] BOOL WINAPI DllMain(HMODULE module, DWORD reason, LPVOID) {
|
EXTERN_C [[maybe_unused]] BOOL WINAPI DllMain(HMODULE module_handle, DWORD reason, LPVOID) {
|
||||||
if (reason == DLL_PROCESS_ATTACH) {
|
if (reason == DLL_PROCESS_ATTACH) {
|
||||||
smoke_api::init(module);
|
smoke_api::init(module_handle);
|
||||||
} else if (reason == DLL_PROCESS_DETACH) {
|
} else if (reason == DLL_PROCESS_DETACH) {
|
||||||
smoke_api::shutdown();
|
smoke_api::shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/smoke_api/config.cpp
Normal file
72
src/smoke_api/config.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include <smoke_api/config.hpp>
|
||||||
|
#include <core/paths.hpp>
|
||||||
|
#include <koalabox/util.hpp>
|
||||||
|
#include <koalabox/io.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
|
|
||||||
|
namespace smoke_api::config {
|
||||||
|
Config instance; // NOLINT(cert-err58-cpp)
|
||||||
|
|
||||||
|
// TODO: Refactor to Koalabox
|
||||||
|
void init_config() {
|
||||||
|
const auto path = paths::get_config_path();
|
||||||
|
|
||||||
|
if (exists(path)) {
|
||||||
|
try {
|
||||||
|
const auto config_str = koalabox::io::read_file(path);
|
||||||
|
|
||||||
|
instance = Json::parse(config_str).get<Config>();
|
||||||
|
|
||||||
|
LOG_DEBUG("Parsed config:\n{}", Json(instance).dump(2))
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
const auto message = fmt::format("Error parsing config file: {}", e.what());
|
||||||
|
koalabox::util::error_box("SmokeAPI Error", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<DLC> get_extra_dlcs(AppId_t app_id) {
|
||||||
|
return DLC::get_dlcs_from_apps(instance.extra_dlcs, app_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_dlc_unlocked(AppId_t app_id, AppId_t dlc_id, const Function<bool()>& original_function) {
|
||||||
|
auto status = instance.default_app_status;
|
||||||
|
|
||||||
|
const auto app_id_str = std::to_string(app_id);
|
||||||
|
if (instance.override_app_status.contains(app_id_str)) {
|
||||||
|
status = instance.override_app_status[app_id_str];
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto dlc_id_str = std::to_string(dlc_id);
|
||||||
|
if (instance.override_dlc_status.contains(dlc_id_str)) {
|
||||||
|
status = instance.override_dlc_status[dlc_id_str];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_unlocked;
|
||||||
|
switch (status) {
|
||||||
|
case AppStatus::UNLOCKED:
|
||||||
|
is_unlocked = true;
|
||||||
|
break;
|
||||||
|
case AppStatus::LOCKED:
|
||||||
|
is_unlocked = false;
|
||||||
|
break;
|
||||||
|
case AppStatus::ORIGINAL:
|
||||||
|
case AppStatus::UNDEFINED:
|
||||||
|
is_unlocked = original_function();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_TRACE(
|
||||||
|
"App ID: {}, DLC ID: {}, Status: {}, Original: {}, Unlocked: {}",
|
||||||
|
app_id_str, dlc_id_str, Json(status).dump(), original_function(), is_unlocked
|
||||||
|
)
|
||||||
|
|
||||||
|
return is_unlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
DLL_EXPORT(void) ReloadConfig() {
|
||||||
|
LOG_INFO("Reloading config")
|
||||||
|
|
||||||
|
init_config();
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/smoke_api/config.hpp
Normal file
58
src/smoke_api/config.hpp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
namespace smoke_api::config {
|
||||||
|
|
||||||
|
enum class AppStatus {
|
||||||
|
UNDEFINED,
|
||||||
|
ORIGINAL,
|
||||||
|
UNLOCKED,
|
||||||
|
LOCKED,
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(AppStatus, {
|
||||||
|
{ AppStatus::UNDEFINED, nullptr },
|
||||||
|
{ AppStatus::ORIGINAL, "original" },
|
||||||
|
{ AppStatus::UNLOCKED, "unlocked" },
|
||||||
|
{ AppStatus::LOCKED, "locked" },
|
||||||
|
})
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
uint32_t $version = 2;
|
||||||
|
bool logging = false;
|
||||||
|
bool unlock_family_sharing = true;
|
||||||
|
AppStatus default_app_status = AppStatus::UNLOCKED;
|
||||||
|
Map<String, AppStatus> override_app_status;
|
||||||
|
Map<String, AppStatus> override_dlc_status;
|
||||||
|
AppDlcNameMap extra_dlcs;
|
||||||
|
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 store_config;
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(
|
||||||
|
Config, // NOLINT(misc-const-correctness)
|
||||||
|
$version,
|
||||||
|
logging,
|
||||||
|
unlock_family_sharing,
|
||||||
|
default_app_status,
|
||||||
|
override_app_status,
|
||||||
|
override_dlc_status,
|
||||||
|
extra_dlcs,
|
||||||
|
auto_inject_inventory,
|
||||||
|
extra_inventory_items,
|
||||||
|
store_config
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Config instance;
|
||||||
|
|
||||||
|
void init_config();
|
||||||
|
|
||||||
|
Vector<DLC> get_extra_dlcs(AppId_t app_id);
|
||||||
|
|
||||||
|
bool is_dlc_unlocked(uint32_t app_id, uint32_t dlc_id, const Function<bool()>& original_function);
|
||||||
|
|
||||||
|
DLL_EXPORT(void) ReloadConfig();
|
||||||
|
}
|
||||||
@@ -1,141 +1,136 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
#include <smoke_api/smoke_api.hpp>
|
||||||
#include <steam_functions/steam_functions.hpp>
|
|
||||||
#include <build_config.h>
|
#include <build_config.h>
|
||||||
|
#include <smoke_api/config.hpp>
|
||||||
#include <koalabox/config_parser.hpp>
|
#include <core/globals.hpp>
|
||||||
|
#include <core/paths.hpp>
|
||||||
|
#include <common/steamclient_exports.hpp>
|
||||||
#include <koalabox/dll_monitor.hpp>
|
#include <koalabox/dll_monitor.hpp>
|
||||||
#include <koalabox/file_logger.hpp>
|
#include <koalabox/logger.hpp>
|
||||||
#include <koalabox/hook.hpp>
|
#include <koalabox/hook.hpp>
|
||||||
|
#include <koalabox/cache.hpp>
|
||||||
#include <koalabox/loader.hpp>
|
#include <koalabox/loader.hpp>
|
||||||
#include <koalabox/win_util.hpp>
|
#include <koalabox/win_util.hpp>
|
||||||
#ifndef _WIN64
|
#include <koalabox/util.hpp>
|
||||||
#include <koalabox/patcher.hpp>
|
//#include <steam_api_exports/steam_api_exports.hpp>
|
||||||
|
|
||||||
|
#if COMPILE_STORE_MODE
|
||||||
|
#include <store_mode/store.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DETOUR_EX(FUNC, ADDRESS) hook::detour_or_warn(ADDRESS, #FUNC, reinterpret_cast<FunctionAddress>(FUNC));
|
// Hooking steam_api has shown itself to be less desirable than steamclient
|
||||||
#define DETOUR(FUNC) hook::detour_or_warn(original_library, #FUNC, reinterpret_cast<FunctionAddress>(FUNC));
|
// 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")
|
||||||
|
|
||||||
|
globals::steamapi_module = koalabox::loader::load_original_library(paths::get_self_path(), STEAMAPI_DLL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_hook_mode() {
|
||||||
|
LOG_INFO("🪝 Detected hook mode")
|
||||||
|
|
||||||
|
koalabox::dll_monitor::init_listener(
|
||||||
|
STEAMCLIENT_DLL, [](const HMODULE& library) {
|
||||||
|
globals::steamclient_module = library;
|
||||||
|
|
||||||
|
DETOUR_STEAMCLIENT(CreateInterface)
|
||||||
|
|
||||||
|
koalabox::dll_monitor::shutdown_listener();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_valve_steam(const String& exe_name) noexcept {
|
||||||
|
try {
|
||||||
|
if (exe_name < not_equals > "steam.exe") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that it's steam from valve, and not some other executable coincidentally named steam
|
||||||
|
|
||||||
|
const HMODULE steam_handle = koalabox::win_util::get_module_handle_or_throw(nullptr);
|
||||||
|
const auto manifest = koalabox::win_util::get_module_manifest(steam_handle);
|
||||||
|
|
||||||
|
// Steam.exe manifest is expected to contain this string
|
||||||
|
return manifest < contains > "valvesoftware.steam.steam";
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
LOG_ERROR("{} -> {}", __func__, e.what())
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace smoke_api {
|
namespace smoke_api {
|
||||||
Config config = {}; // NOLINT(cert-err58-cpp)
|
|
||||||
|
|
||||||
HMODULE original_library = nullptr;
|
void init(HMODULE module_handle) {
|
||||||
|
|
||||||
bool is_hook_mode = false;
|
|
||||||
|
|
||||||
Path self_directory;
|
|
||||||
|
|
||||||
void init(HMODULE self_module) {
|
|
||||||
try {
|
try {
|
||||||
DisableThreadLibraryCalls(self_module);
|
DisableThreadLibraryCalls(module_handle);
|
||||||
|
|
||||||
koalabox::project_name = PROJECT_NAME;
|
globals::smokeapi_handle = module_handle;
|
||||||
|
|
||||||
self_directory = loader::get_module_dir(self_module);
|
config::init_config();
|
||||||
|
|
||||||
config = config_parser::parse<Config>(self_directory / PROJECT_NAME".json");
|
if (config::instance.logging) {
|
||||||
|
koalabox::logger::init_file_logger(paths::get_log_path());
|
||||||
const auto exe_path = Path(win_util::get_module_file_name_or_throw(nullptr));
|
|
||||||
const auto exe_name = exe_path.filename().string();
|
|
||||||
const auto exe_bitness = util::is_x64() ? 64 : 32;
|
|
||||||
|
|
||||||
if (config.logging) {
|
|
||||||
logger = file_logger::create(self_directory / fmt::format("{}.log", PROJECT_NAME));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger->info("🐨 {} v{}", PROJECT_NAME, PROJECT_VERSION);
|
// This kind of timestamp is reliable only for CI builds, as it will reflect the compilation
|
||||||
|
// time stamp only when this file gets recompiled.
|
||||||
|
LOG_INFO("🐨 {} v{} | Compiled at '{}'", PROJECT_NAME, PROJECT_VERSION, __TIMESTAMP__)
|
||||||
|
|
||||||
logger->debug(R"(Process name: "{}" [{}-bit])", exe_name, exe_bitness);
|
koalabox::cache::init_cache(paths::get_cache_path());
|
||||||
|
|
||||||
is_hook_mode = hook::is_hook_mode(self_module, ORIGINAL_DLL);
|
const auto exe_path = Path(koalabox::win_util::get_module_file_name_or_throw(nullptr));
|
||||||
|
const auto exe_name = exe_path.filename().string();
|
||||||
|
|
||||||
if (is_hook_mode) {
|
LOG_DEBUG("Process name: '{}' [{}-bit]", exe_name, BITNESS)
|
||||||
hook::init(true);
|
|
||||||
|
|
||||||
if (util::strings_are_equal(exe_name, "steam.exe")) {
|
if (koalabox::hook::is_hook_mode(globals::smokeapi_handle, STEAMAPI_DLL)) {
|
||||||
#ifndef _WIN64
|
koalabox::hook::init(true);
|
||||||
logger->info("🐨 Detected Koalageddon mode 💥");
|
|
||||||
|
|
||||||
dll_monitor::init({VSTDLIB_DLL, STEAMCLIENT_DLL}, [](const HMODULE& library, const String& name) {
|
if (is_valve_steam(exe_name)) {
|
||||||
original_library = library; // TODO: Is this necessary?
|
#if COMPILE_STORE_MODE
|
||||||
|
LOG_INFO("🛍️ Detected Store mode")
|
||||||
if (name == VSTDLIB_DLL) {
|
store::init_store_mode();
|
||||||
// Family Sharing functions
|
|
||||||
DETOUR(Coroutine_Create)
|
|
||||||
} else if (name == STEAMCLIENT_DLL) {
|
|
||||||
// Unlocking functions
|
|
||||||
// TODO: Un-hardcode the pattern
|
|
||||||
const String pattern("55 8B EC 8B ?? ?? ?? ?? ?? 81 EC ?? ?? ?? ?? 53 FF 15");
|
|
||||||
auto Log_Interface_address = (FunctionAddress) patcher::find_pattern_address(
|
|
||||||
win_util::get_module_info(library), "Log_Interface", pattern
|
|
||||||
);
|
|
||||||
if (Log_Interface_address) {
|
|
||||||
DETOUR_EX(Log_Interface, Log_Interface_address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
} else if (config.hook_steamclient) { // target steamclient(64).dll
|
} else {
|
||||||
logger->info("🪝 Detected hook mode for SteamClient");
|
init_hook_mode();
|
||||||
|
|
||||||
dll_monitor::init(STEAMCLIENT_DLL, [](const HMODULE& library) {
|
|
||||||
original_library = library;
|
|
||||||
|
|
||||||
DETOUR(CreateInterface)
|
|
||||||
});
|
|
||||||
} else { // target steam_api.dll
|
|
||||||
logger->info("🪝 Detected hook mode for Steam_API");
|
|
||||||
|
|
||||||
dll_monitor::init(ORIGINAL_DLL, [](const HMODULE& library) {
|
|
||||||
original_library = library;
|
|
||||||
|
|
||||||
DETOUR(SteamInternal_FindOrCreateUserInterface)
|
|
||||||
DETOUR(SteamInternal_CreateInterface)
|
|
||||||
DETOUR(SteamApps)
|
|
||||||
DETOUR(SteamClient)
|
|
||||||
DETOUR(SteamUser)
|
|
||||||
|
|
||||||
DETOUR(SteamAPI_ISteamApps_BIsSubscribedApp)
|
|
||||||
DETOUR(SteamAPI_ISteamApps_BIsDlcInstalled)
|
|
||||||
DETOUR(SteamAPI_ISteamApps_GetDLCCount)
|
|
||||||
DETOUR(SteamAPI_ISteamApps_BGetDLCDataByIndex)
|
|
||||||
DETOUR(SteamAPI_ISteamClient_GetISteamGenericInterface)
|
|
||||||
|
|
||||||
DETOUR(SteamAPI_ISteamInventory_GetResultStatus)
|
|
||||||
DETOUR(SteamAPI_ISteamInventory_GetResultItems)
|
|
||||||
DETOUR(SteamAPI_ISteamInventory_GetResultItemProperty)
|
|
||||||
DETOUR(SteamAPI_ISteamInventory_CheckResultSteamID)
|
|
||||||
DETOUR(SteamAPI_ISteamInventory_GetAllItems)
|
|
||||||
DETOUR(SteamAPI_ISteamInventory_GetItemsByID)
|
|
||||||
DETOUR(SteamAPI_ISteamInventory_GetItemDefinitionIDs)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger->info("🔀 Detected proxy mode for Steam_API");
|
init_proxy_mode();
|
||||||
|
|
||||||
original_library = loader::load_original_library(self_directory, ORIGINAL_DLL);
|
|
||||||
}
|
}
|
||||||
logger->info("🚀 Initialization complete");
|
|
||||||
|
LOG_INFO("🚀 Initialization complete")
|
||||||
} catch (const Exception& ex) {
|
} catch (const Exception& ex) {
|
||||||
util::panic(fmt::format("Initialization error: {}", ex.what()));
|
koalabox::util::panic(fmt::format("Initialization error: {}", ex.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
try {
|
try {
|
||||||
if (is_hook_mode) {
|
if (globals::steamapi_module != nullptr) {
|
||||||
dll_monitor::shutdown();
|
koalabox::win_util::free_library(globals::steamapi_module);
|
||||||
} else {
|
|
||||||
win_util::free_library(original_library);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger->info("💀 Shutdown complete");
|
LOG_INFO("💀 Shutdown complete")
|
||||||
} catch (const Exception& ex) {
|
} catch (const Exception& ex) {
|
||||||
logger->error("Shutdown error: {}", ex.what());
|
LOG_ERROR("Shutdown error: {}", ex.what())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool should_unlock(uint32_t app_id) {
|
|
||||||
return config.unlock_all != config.override.contains(app_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <koalabox/koalabox.hpp>
|
|
||||||
#include <koalabox/hook.hpp> // For macros
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
#define GET_ORIGINAL_FUNCTION(FUNC) \
|
|
||||||
static const auto FUNC##_o = hook::get_original_function( \
|
|
||||||
smoke_api::is_hook_mode, \
|
|
||||||
smoke_api::original_library, \
|
|
||||||
#FUNC, \
|
|
||||||
FUNC \
|
|
||||||
);
|
|
||||||
|
|
||||||
#define GET_ORIGINAL_VIRTUAL_FUNCTION(FUNC) \
|
|
||||||
static const auto FUNC##_o = hook::get_original_function( \
|
|
||||||
true, \
|
|
||||||
smoke_api::original_library, \
|
|
||||||
#FUNC, \
|
|
||||||
FUNC \
|
|
||||||
);
|
|
||||||
|
|
||||||
namespace smoke_api {
|
namespace smoke_api {
|
||||||
using namespace koalabox;
|
|
||||||
|
|
||||||
struct Config {
|
void init(HMODULE module_handle);
|
||||||
uint32_t $version = 1;
|
|
||||||
bool logging = false;
|
|
||||||
bool hook_steamclient = true;
|
|
||||||
bool unlock_all = true;
|
|
||||||
Set<uint32_t> override;
|
|
||||||
Vector<uint32_t> dlc_ids;
|
|
||||||
bool auto_inject_inventory = true;
|
|
||||||
Vector<uint32_t> inventory_items;
|
|
||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
|
|
||||||
Config, $version,
|
|
||||||
logging,
|
|
||||||
unlock_all,
|
|
||||||
hook_steamclient,
|
|
||||||
override,
|
|
||||||
dlc_ids,
|
|
||||||
auto_inject_inventory,
|
|
||||||
inventory_items
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Config config;
|
|
||||||
|
|
||||||
extern HMODULE original_library;
|
|
||||||
|
|
||||||
extern bool is_hook_mode;
|
|
||||||
|
|
||||||
extern Path self_directory;
|
|
||||||
|
|
||||||
void init(HMODULE self_module);
|
|
||||||
|
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
bool should_unlock(uint32_t app_id);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,182 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_impl/steam_impl.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
// ISteamApps
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(ISteamApps*, AppId_t appID) {
|
|
||||||
return steam_apps::IsDlcUnlocked(__func__, 0, appID);
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsDlcInstalled(ISteamApps*, AppId_t appID) {
|
|
||||||
return steam_apps::IsDlcUnlocked(__func__, 0, appID);
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(int) SteamAPI_ISteamApps_GetDLCCount(ISteamApps* self) {
|
|
||||||
return steam_apps::GetDLCCount(__func__, 0, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamApps_GetDLCCount)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamApps_GetDLCCount_o(self);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamApps_BGetDLCDataByIndex(
|
|
||||||
ISteamApps* self,
|
|
||||||
int iDLC,
|
|
||||||
AppId_t* pAppID,
|
|
||||||
bool* pbAvailable,
|
|
||||||
char* pchName,
|
|
||||||
int cchNameBufferSize
|
|
||||||
) {
|
|
||||||
return steam_apps::GetDLCDataByIndex(__func__, 0, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamApps_BGetDLCDataByIndex)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamApps_BGetDLCDataByIndex_o(
|
|
||||||
self, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ISteamUser
|
|
||||||
|
|
||||||
DLL_EXPORT(EUserHasLicenseForAppResult) SteamAPI_ISteamUser_UserHasLicenseForApp(
|
|
||||||
ISteamUser* self,
|
|
||||||
CSteamID steamID,
|
|
||||||
AppId_t appID
|
|
||||||
) {
|
|
||||||
return steam_user::UserHasLicenseForApp(__func__, appID, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamUser_UserHasLicenseForApp)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamUser_UserHasLicenseForApp_o(self, steamID, appID);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ISteamClient
|
|
||||||
|
|
||||||
DLL_EXPORT(void*) SteamAPI_ISteamClient_GetISteamGenericInterface(
|
|
||||||
ISteamClient* self,
|
|
||||||
HSteamUser hSteamUser,
|
|
||||||
HSteamPipe hSteamPipe,
|
|
||||||
const char* pchVersion
|
|
||||||
) {
|
|
||||||
return steam_client::GetGenericInterface(__func__, pchVersion, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamClient_GetISteamGenericInterface)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamClient_GetISteamGenericInterface_o(self, hSteamUser, hSteamPipe, pchVersion);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ISteamInventory
|
|
||||||
|
|
||||||
DLL_EXPORT(EResult) SteamAPI_ISteamInventory_GetResultStatus(
|
|
||||||
ISteamInventory* self,
|
|
||||||
SteamInventoryResult_t resultHandle
|
|
||||||
) {
|
|
||||||
return steam_inventory::GetResultStatus(__func__, resultHandle, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_GetResultStatus)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamInventory_GetResultStatus_o(self, resultHandle);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetResultItems(
|
|
||||||
ISteamInventory* self,
|
|
||||||
SteamInventoryResult_t resultHandle,
|
|
||||||
SteamItemDetails_t* pOutItemsArray,
|
|
||||||
uint32_t* punOutItemsArraySize
|
|
||||||
) {
|
|
||||||
return steam_inventory::GetResultItems(
|
|
||||||
__func__, resultHandle, pOutItemsArray, punOutItemsArraySize,
|
|
||||||
[&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_GetResultItems)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamInventory_GetResultItems_o(self, resultHandle, pOutItemsArray, punOutItemsArraySize);
|
|
||||||
},
|
|
||||||
[&](SteamItemDef_t* pItemDefIDs, uint32_t* punItemDefIDsArraySize) {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_GetItemDefinitionIDs)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamInventory_GetItemDefinitionIDs_o(self, pItemDefIDs, punItemDefIDsArraySize);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetResultItemProperty(
|
|
||||||
ISteamInventory* self,
|
|
||||||
SteamInventoryResult_t resultHandle,
|
|
||||||
uint32_t unItemIndex,
|
|
||||||
const char* pchPropertyName,
|
|
||||||
char* pchValueBuffer,
|
|
||||||
uint32_t* punValueBufferSizeOut
|
|
||||||
) {
|
|
||||||
return steam_inventory::GetResultItemProperty(
|
|
||||||
__func__, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_GetResultItemProperty)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamInventory_GetResultItemProperty_o(
|
|
||||||
self, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_CheckResultSteamID(
|
|
||||||
ISteamInventory* self,
|
|
||||||
SteamInventoryResult_t resultHandle,
|
|
||||||
CSteamID steamIDExpected
|
|
||||||
) {
|
|
||||||
return steam_inventory::CheckResultSteamID(__func__, resultHandle, steamIDExpected, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_CheckResultSteamID)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamInventory_CheckResultSteamID_o(self, resultHandle, steamIDExpected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetAllItems(
|
|
||||||
ISteamInventory* self,
|
|
||||||
SteamInventoryResult_t* pResultHandle
|
|
||||||
) {
|
|
||||||
return steam_inventory::GetAllItems(__func__, pResultHandle, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_GetAllItems)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamInventory_GetAllItems_o(self, pResultHandle);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemsByID(
|
|
||||||
ISteamInventory* self,
|
|
||||||
SteamInventoryResult_t* pResultHandle,
|
|
||||||
const SteamItemInstanceID_t* pInstanceIDs,
|
|
||||||
uint32_t unCountInstanceIDs
|
|
||||||
) {
|
|
||||||
return steam_inventory::GetItemsByID(__func__, pResultHandle, pInstanceIDs, unCountInstanceIDs, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_GetItemsByID)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamInventory_GetItemsByID_o(self, pResultHandle, pInstanceIDs, unCountInstanceIDs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_SerializeResult(
|
|
||||||
ISteamInventory* self,
|
|
||||||
SteamInventoryResult_t resultHandle,
|
|
||||||
void* pOutBuffer,
|
|
||||||
uint32_t* punOutBufferSize
|
|
||||||
) {
|
|
||||||
return steam_inventory::SerializeResult(__func__, resultHandle, pOutBuffer, punOutBufferSize, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_SerializeResult)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamInventory_SerializeResult_o(self, resultHandle, pOutBuffer, punOutBufferSize);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemDefinitionIDs(
|
|
||||||
ISteamInventory* self,
|
|
||||||
SteamItemDef_t* pItemDefIDs,
|
|
||||||
uint32_t* punItemDefIDsArraySize
|
|
||||||
) {
|
|
||||||
return steam_inventory::GetItemDefinitionIDs(__func__, pItemDefIDs, punItemDefIDsArraySize, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamAPI_ISteamInventory_GetItemDefinitionIDs)
|
|
||||||
|
|
||||||
return SteamAPI_ISteamInventory_GetItemDefinitionIDs_o(self, pItemDefIDs, punItemDefIDsArraySize);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_impl/steam_impl.hpp>
|
|
||||||
#include <steam_functions/steam_functions.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
DLL_EXPORT(void*) SteamInternal_FindOrCreateUserInterface(HSteamUser hSteamUser, const char* version) {
|
|
||||||
return steam_client::GetGenericInterface(__func__, version, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamInternal_FindOrCreateUserInterface)
|
|
||||||
|
|
||||||
return SteamInternal_FindOrCreateUserInterface_o(hSteamUser, version);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
DLL_EXPORT(void*) SteamInternal_CreateInterface(const char* version) {
|
|
||||||
return steam_client::GetGenericInterface(__func__, version, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(SteamInternal_CreateInterface)
|
|
||||||
|
|
||||||
return SteamInternal_CreateInterface_o(version);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_impl/steam_impl.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t appID)) { // NOLINT(misc-unused-parameters)
|
|
||||||
return steam_apps::IsDlcUnlocked(__func__, 0, appID);
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t appID)) { // NOLINT(misc-unused-parameters)
|
|
||||||
return steam_apps::IsDlcUnlocked(__func__, 0, appID);
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL(int) ISteamApps_GetDLCCount(PARAMS()) {
|
|
||||||
return steam_apps::GetDLCCount(__func__, 0, [&]() {
|
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamApps_GetDLCCount)
|
|
||||||
|
|
||||||
return ISteamApps_GetDLCCount_o(ARGS());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL(bool) ISteamApps_BGetDLCDataByIndex(
|
|
||||||
PARAMS(
|
|
||||||
int iDLC,
|
|
||||||
AppId_t* pAppID,
|
|
||||||
bool* pbAvailable,
|
|
||||||
char* pchName,
|
|
||||||
int cchNameBufferSize
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return steam_apps::GetDLCDataByIndex(__func__, 0, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize, [&]() {
|
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamApps_BGetDLCDataByIndex)
|
|
||||||
|
|
||||||
return ISteamApps_BGetDLCDataByIndex_o(
|
|
||||||
ARGS(iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_impl/steam_impl.hpp>
|
|
||||||
#include <steam_functions/steam_functions.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
VIRTUAL(void*) ISteamClient_GetISteamApps(
|
|
||||||
PARAMS(
|
|
||||||
HSteamUser hSteamUser,
|
|
||||||
HSteamPipe hSteamPipe,
|
|
||||||
const char* version
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return steam_client::GetGenericInterface(__func__, version, [&]() {
|
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamClient_GetISteamApps)
|
|
||||||
|
|
||||||
return ISteamClient_GetISteamApps_o(ARGS(hSteamUser, hSteamPipe, version));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL(void*) ISteamClient_GetISteamUser(
|
|
||||||
PARAMS(
|
|
||||||
HSteamUser hSteamUser,
|
|
||||||
HSteamPipe hSteamPipe,
|
|
||||||
const char* version
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return steam_client::GetGenericInterface(__func__, version, [&]() {
|
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamClient_GetISteamUser)
|
|
||||||
|
|
||||||
return ISteamClient_GetISteamUser_o(ARGS(hSteamUser, hSteamPipe, version));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL(void*) ISteamClient_GetISteamGenericInterface(
|
|
||||||
PARAMS(
|
|
||||||
HSteamUser hSteamUser,
|
|
||||||
HSteamPipe hSteamPipe,
|
|
||||||
const char* pchVersion
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return steam_client::GetGenericInterface(__func__, pchVersion, [&]() {
|
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamClient_GetISteamGenericInterface)
|
|
||||||
|
|
||||||
return ISteamClient_GetISteamGenericInterface_o(ARGS(hSteamUser, hSteamPipe, pchVersion));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL(void*) ISteamClient_GetISteamInventory(
|
|
||||||
PARAMS(
|
|
||||||
HSteamUser hSteamUser,
|
|
||||||
HSteamPipe hSteamPipe,
|
|
||||||
const char* pchVersion
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return steam_client::GetGenericInterface(__func__, pchVersion, [&]() {
|
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamClient_GetISteamInventory)
|
|
||||||
|
|
||||||
return ISteamClient_GetISteamInventory_o(ARGS(hSteamUser, hSteamPipe, pchVersion));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_impl/steam_impl.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
VIRTUAL(EUserHasLicenseForAppResult) ISteamUser_UserHasLicenseForApp(PARAMS(CSteamID steamID, AppId_t appID)) {
|
|
||||||
return steam_user::UserHasLicenseForApp(__func__, appID, [&]() {
|
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(ISteamUser_UserHasLicenseForApp)
|
|
||||||
|
|
||||||
return ISteamUser_UserHasLicenseForApp_o(ARGS(steamID, appID));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <koalabox/koalabox.hpp>
|
|
||||||
|
|
||||||
#include <steam_types/steam_types.hpp>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* By default, virtual functions are declared with __thiscall
|
|
||||||
* convention, which is normal since they are class members.
|
|
||||||
* But it presents an issue for us, since we cannot pass *this
|
|
||||||
* pointer as a function argument. This is because *this
|
|
||||||
* pointer is passed via register ECX in __thiscall
|
|
||||||
* convention. Hence, to resolve this issue we declare our
|
|
||||||
* hooked functions with __fastcall convention, to trick
|
|
||||||
* the compiler into reading ECX & EDX registers as 1st
|
|
||||||
* and 2nd function arguments respectively. Similarly, __fastcall
|
|
||||||
* makes the compiler push the first argument into the ECX register,
|
|
||||||
* which mimics the __thiscall calling convention. Register EDX
|
|
||||||
* is not used anywhere in this case, but we still pass it along
|
|
||||||
* to conform to the __fastcall convention. This all applies
|
|
||||||
* to the x86 architecture.
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
* have to omit it from the function signature.
|
|
||||||
*
|
|
||||||
* The macros below implement the above-mentioned considerations.
|
|
||||||
*/
|
|
||||||
#ifdef _WIN64
|
|
||||||
#define PARAMS(...) void* RCX, ##__VA_ARGS__
|
|
||||||
#define ARGS(...) RCX, ##__VA_ARGS__
|
|
||||||
#define THIS RCX
|
|
||||||
#else
|
|
||||||
#define PARAMS(...) void* ECX, void* EDX, ##__VA_ARGS__
|
|
||||||
#define ARGS(...) ECX, EDX, ##__VA_ARGS__
|
|
||||||
#define THIS ECX
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define DLL_EXPORT(TYPE) extern "C" __declspec( dllexport ) TYPE __cdecl
|
|
||||||
#define VIRTUAL(TYPE) __declspec(noinline) TYPE __fastcall
|
|
||||||
|
|
||||||
class ISteamClient;
|
|
||||||
|
|
||||||
class ISteamApps;
|
|
||||||
|
|
||||||
class ISteamUser;
|
|
||||||
|
|
||||||
class ISteamInventory;
|
|
||||||
|
|
||||||
typedef __int32 HSteamPipe;
|
|
||||||
typedef __int32 HSteamUser;
|
|
||||||
typedef uint32_t AppId_t;
|
|
||||||
typedef uint64_t CSteamID;
|
|
||||||
|
|
||||||
typedef uint32_t HCoroutine;
|
|
||||||
|
|
||||||
// ISteamClient
|
|
||||||
VIRTUAL(void*) ISteamClient_GetISteamApps(PARAMS(HSteamUser, HSteamPipe, const char*));
|
|
||||||
VIRTUAL(void*) ISteamClient_GetISteamUser(PARAMS(HSteamUser, HSteamPipe, const char*));
|
|
||||||
VIRTUAL(void*) ISteamClient_GetISteamGenericInterface(PARAMS(HSteamUser, HSteamPipe, const char*));
|
|
||||||
VIRTUAL(void*) ISteamClient_GetISteamInventory(PARAMS(HSteamUser, HSteamPipe, const char*));
|
|
||||||
|
|
||||||
// ISteamApps
|
|
||||||
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t));
|
|
||||||
VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t));
|
|
||||||
VIRTUAL(int) ISteamApps_GetDLCCount(PARAMS());
|
|
||||||
VIRTUAL(bool) ISteamApps_BGetDLCDataByIndex(PARAMS(int, AppId_t*, bool*, char*, int));
|
|
||||||
|
|
||||||
// ISteamUser
|
|
||||||
VIRTUAL(EUserHasLicenseForAppResult) ISteamUser_UserHasLicenseForApp(PARAMS(CSteamID, AppId_t));
|
|
||||||
|
|
||||||
// ISteamInventory
|
|
||||||
VIRTUAL(EResult) ISteamInventory_GetResultStatus(PARAMS(uint32_t));
|
|
||||||
VIRTUAL(bool) ISteamInventory_GetResultItems(PARAMS(SteamInventoryResult_t, SteamItemDetails_t*, uint32_t*));
|
|
||||||
VIRTUAL(bool) ISteamInventory_GetResultItemProperty(
|
|
||||||
PARAMS(SteamInventoryResult_t, uint32_t, const char*, char*, uint32_t*)
|
|
||||||
);
|
|
||||||
VIRTUAL(bool) ISteamInventory_GetAllItems(PARAMS(SteamInventoryResult_t*));
|
|
||||||
VIRTUAL(bool) ISteamInventory_GetItemsByID(PARAMS(SteamInventoryResult_t*, const SteamItemInstanceID_t*, uint32_t));
|
|
||||||
VIRTUAL(bool) ISteamInventory_SerializeResult(PARAMS(SteamInventoryResult_t, void*, uint32_t*));
|
|
||||||
VIRTUAL(bool) ISteamInventory_GetItemDefinitionIDs(PARAMS(SteamItemDef_t*, uint32_t*));
|
|
||||||
VIRTUAL(bool) ISteamInventory_CheckResultSteamID(PARAMS(SteamInventoryResult_t, CSteamID));
|
|
||||||
|
|
||||||
// API
|
|
||||||
|
|
||||||
DLL_EXPORT(void*) CreateInterface(const char*, int*);
|
|
||||||
DLL_EXPORT(void*) SteamInternal_FindOrCreateUserInterface(HSteamUser, const char*);
|
|
||||||
DLL_EXPORT(void*) SteamInternal_CreateInterface(const char*);
|
|
||||||
DLL_EXPORT(void*) SteamApps();
|
|
||||||
DLL_EXPORT(void*) SteamClient();
|
|
||||||
DLL_EXPORT(void*) SteamUser();
|
|
||||||
|
|
||||||
// Flat interfaces
|
|
||||||
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(ISteamApps*, AppId_t);
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsDlcInstalled(ISteamApps*, AppId_t);
|
|
||||||
DLL_EXPORT(int) SteamAPI_ISteamApps_GetDLCCount(ISteamApps*);
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamApps_BGetDLCDataByIndex(ISteamApps*, int, AppId_t*, bool*, char*, int);
|
|
||||||
DLL_EXPORT(EUserHasLicenseForAppResult) SteamAPI_ISteamUser_UserHasLicenseForApp(ISteamUser*, CSteamID, AppId_t);
|
|
||||||
DLL_EXPORT(void*) SteamAPI_ISteamClient_GetISteamGenericInterface(ISteamClient*, HSteamUser, HSteamPipe, const char*);
|
|
||||||
DLL_EXPORT(EResult) SteamAPI_ISteamInventory_GetResultStatus(ISteamInventory*, SteamInventoryResult_t);
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetResultItems(
|
|
||||||
ISteamInventory*, SteamInventoryResult_t, SteamItemDetails_t*, uint32_t*
|
|
||||||
);
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetResultItemProperty(
|
|
||||||
ISteamInventory*, SteamInventoryResult_t, uint32_t, const char*, char*, uint32_t*
|
|
||||||
);
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_CheckResultSteamID(ISteamInventory*, SteamInventoryResult_t, CSteamID);
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetAllItems(ISteamInventory*, SteamInventoryResult_t*);
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemsByID(
|
|
||||||
ISteamInventory*, SteamInventoryResult_t*, const SteamItemInstanceID_t*, uint32_t
|
|
||||||
);
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_SerializeResult(ISteamInventory*, SteamInventoryResult_t, void*, uint32_t*);
|
|
||||||
DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemDefinitionIDs(ISteamInventory*, SteamItemDef_t*, uint32_t*);
|
|
||||||
|
|
||||||
// Koalageddon mode
|
|
||||||
|
|
||||||
DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, struct CoroutineData* data);
|
|
||||||
DLL_EXPORT(void) Log_Interface(const char* interface_name, const char* function_name);
|
|
||||||
|
|
||||||
// IClientApps
|
|
||||||
VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t));
|
|
||||||
VIRTUAL(bool) IClientApps_BGetDLCDataByIndex(PARAMS(AppId_t, int, AppId_t*, bool*, char*, int));
|
|
||||||
|
|
||||||
// IClientAppManager
|
|
||||||
VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(PARAMS(AppId_t, AppId_t));
|
|
||||||
|
|
||||||
// IClientUser
|
|
||||||
VIRTUAL(bool) IClientUser_IsSubscribedApp(PARAMS(AppId_t));
|
|
||||||
|
|
||||||
// IClientInventory
|
|
||||||
VIRTUAL(EResult) IClientInventory_GetResultStatus(PARAMS(SteamInventoryResult_t));
|
|
||||||
VIRTUAL(bool) IClientInventory_GetResultItems(
|
|
||||||
PARAMS(SteamInventoryResult_t, SteamItemDetails_t*, uint32_t, uint32_t *)
|
|
||||||
);
|
|
||||||
//////
|
|
||||||
VIRTUAL(bool) IClientInventory_GetResultItemProperty(
|
|
||||||
PARAMS(SteamInventoryResult_t, uint32_t, const char*, char*, uint32_t, uint32_t*)
|
|
||||||
);
|
|
||||||
VIRTUAL(bool) IClientInventory_CheckResultSteamID(PARAMS(SteamInventoryResult_t, CSteamID));
|
|
||||||
VIRTUAL(bool) IClientInventory_GetAllItems(PARAMS(SteamInventoryResult_t*));
|
|
||||||
VIRTUAL(bool) IClientInventory_GetItemsByID(PARAMS(SteamInventoryResult_t*, const SteamItemInstanceID_t*, uint32_t));
|
|
||||||
VIRTUAL(bool) IClientInventory_SerializeResult(PARAMS(SteamInventoryResult_t, void*, uint32_t, uint32_t *));
|
|
||||||
VIRTUAL(bool) IClientInventory_GetItemDefinitionIDs(PARAMS(SteamItemDef_t*, uint32_t, uint32_t *));
|
|
||||||
|
|
||||||
namespace steam_functions {
|
|
||||||
using namespace koalabox;
|
|
||||||
|
|
||||||
const String STEAM_APPS = "STEAMAPPS_INTERFACE_VERSION"; // NOLINT(cert-err58-cpp)
|
|
||||||
const String STEAM_CLIENT = "SteamClient"; // NOLINT(cert-err58-cpp)
|
|
||||||
const String STEAM_USER = "SteamUser"; // NOLINT(cert-err58-cpp)
|
|
||||||
const String STEAM_INVENTORY = "STEAMINVENTORY_INTERFACE_V"; // NOLINT(cert-err58-cpp)
|
|
||||||
|
|
||||||
void hook_virtuals(void* interface, const String& version_string);
|
|
||||||
uint32_t get_app_id_or_throw();
|
|
||||||
}
|
|
||||||
@@ -1,190 +1,131 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
#include <steam_impl/steam_apps.hpp>
|
||||||
#include <steam_impl/steam_impl.hpp>
|
#include <steam_impl/steam_impl.hpp>
|
||||||
|
#include <common/app_cache.hpp>
|
||||||
#include <koalabox/io.hpp>
|
#include <smoke_api/config.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
#include <cpr/cpr.h>
|
#include <koalabox/util.hpp>
|
||||||
|
#include <core/types.hpp>
|
||||||
using namespace smoke_api;
|
#include <core/api.hpp>
|
||||||
|
|
||||||
constexpr auto max_dlc = 64;
|
|
||||||
|
|
||||||
Vector<AppId_t> cached_dlcs;
|
|
||||||
int original_dlc_count = 0;
|
|
||||||
|
|
||||||
Path get_cache_path() {
|
|
||||||
static const auto path = self_directory / "SmokeAPI.cache.json";
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
void read_from_cache(const String& app_id_str) {
|
|
||||||
try {
|
|
||||||
const auto text = io::read_file(get_cache_path());
|
|
||||||
if (text.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto json = nlohmann::json::parse(text);
|
|
||||||
|
|
||||||
cached_dlcs = json[app_id_str]["dlc"].get<decltype(cached_dlcs)>();
|
|
||||||
|
|
||||||
logger->debug("Read {} DLCs from cache", cached_dlcs.size());
|
|
||||||
} catch (const Exception& ex) {
|
|
||||||
logger->error("Error reading DLCs from cache: {}", ex.what());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void save_to_cache(const String& app_id_str) {
|
|
||||||
try {
|
|
||||||
logger->debug("Saving {} DLCs to cache", cached_dlcs.size());
|
|
||||||
|
|
||||||
const nlohmann::json json = {
|
|
||||||
{app_id_str, {
|
|
||||||
{"dlc", cached_dlcs}
|
|
||||||
}}
|
|
||||||
};
|
|
||||||
|
|
||||||
io::write_file(get_cache_path(), json.dump(2));
|
|
||||||
} catch (const Exception& ex) {
|
|
||||||
logger->error("Error saving DLCs to cache: {}", ex.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void fetch_and_cache_dlcs(AppId_t app_id) {
|
|
||||||
if (not app_id) {
|
|
||||||
try {
|
|
||||||
app_id = steam_functions::get_app_id_or_throw();
|
|
||||||
logger->info("Detected App ID: {}", app_id);
|
|
||||||
} catch (const Exception& ex) {
|
|
||||||
logger->error("Failed to get app ID: {}", ex.what());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto app_id_str = std::to_string(app_id);
|
|
||||||
|
|
||||||
const auto fetch_from_steam = [&]() {
|
|
||||||
const auto url = fmt::format("https://store.steampowered.com/dlc/{}/ajaxgetdlclist", app_id_str);
|
|
||||||
const auto res = cpr::Get(cpr::Url{url});
|
|
||||||
|
|
||||||
if (res.status_code != cpr::status::HTTP_OK) {
|
|
||||||
throw util::exception(
|
|
||||||
"Steam Web API didn't responded with HTTP_OK result. Code: {}, Error: {},\n"
|
|
||||||
"Headers:\n{}\nBody:\n{}",
|
|
||||||
res.status_code, (int) res.error.code, res.raw_header, res.text
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto json = nlohmann::json::parse(res.text);
|
|
||||||
|
|
||||||
|
|
||||||
if (json["success"] != 1) {
|
|
||||||
throw util::exception("Web API responded with 'success': 1.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector<AppId_t> dlcs;
|
|
||||||
|
|
||||||
for (const auto& dlc: json["dlcs"]) {
|
|
||||||
const auto app_id = dlc["appid"].get<String>();
|
|
||||||
dlcs.emplace_back(std::stoi(app_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
return dlcs;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto fetch_from_github = [&]() {
|
|
||||||
const String url = "https://raw.githubusercontent.com/acidicoala/public-entitlements/main/steam/v1/dlc.json";
|
|
||||||
const auto res = cpr::Get(cpr::Url{url});
|
|
||||||
|
|
||||||
if (res.status_code != cpr::status::HTTP_OK) {
|
|
||||||
throw util::exception(
|
|
||||||
"Github Web API didn't responded with HTTP_OK result. Code: {}, Error: {},\n"
|
|
||||||
"Headers:\n{}\nBody:\n{}",
|
|
||||||
res.status_code, (int) res.error.code, res.raw_header, res.text
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto json = nlohmann::json::parse(res.text);
|
|
||||||
|
|
||||||
if (json.contains(app_id_str)) {
|
|
||||||
return json[app_id_str].get<decltype(cached_dlcs)>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Vector<AppId_t>{};
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
read_from_cache(app_id_str);
|
|
||||||
|
|
||||||
auto list1 = fetch_from_steam();
|
|
||||||
auto list2 = fetch_from_github();
|
|
||||||
list1.insert(list1.end(), list2.begin(), list2.end());
|
|
||||||
Set<AppId_t> fetched_dlcs(list1.begin(), list1.end());
|
|
||||||
|
|
||||||
if (fetched_dlcs.size() > cached_dlcs.size()) {
|
|
||||||
cached_dlcs = Vector<AppId_t>(fetched_dlcs.begin(), fetched_dlcs.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
save_to_cache(app_id_str);
|
|
||||||
} catch (const Exception& ex) {
|
|
||||||
logger->error("Failed to fetch DLC: {}", ex.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String get_app_id_log(const AppId_t app_id) {
|
|
||||||
return app_id ? fmt::format("App ID: {}, ", app_id) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace steam_apps {
|
namespace steam_apps {
|
||||||
|
/// Steamworks may max GetDLCCount value at 64, depending on how much unowned DLCs the user has.
|
||||||
|
/// Despite this limit, some games with more than 64 DLCs still keep using this method.
|
||||||
|
/// This means we have to get extra DLC IDs from local config, remote config, or cache.
|
||||||
|
constexpr auto MAX_DLC = 64;
|
||||||
|
|
||||||
bool IsDlcUnlocked(const String& function_name, AppId_t app_id, AppId_t dlc_id) {
|
Map<AppId_t, Vector<DLC>> app_dlcs; // NOLINT(cert-err58-cpp)
|
||||||
const auto app_id_unlocked = not app_id or should_unlock(app_id); // true if app_id == 0
|
Set<AppId_t> fully_fetched; // NOLINT(cert-err58-cpp)
|
||||||
const auto dlc_id_unlocked = should_unlock(dlc_id);
|
|
||||||
|
|
||||||
const auto installed = app_id_unlocked and dlc_id_unlocked;
|
String get_app_id_log(const AppId_t app_id) {
|
||||||
|
return app_id ? fmt::format("App ID: {:>8}, ", app_id) : "";
|
||||||
logger->info("{} -> {}DLC ID: {}, Unlocked: {}", function_name, get_app_id_log(app_id), dlc_id, installed);
|
|
||||||
|
|
||||||
return installed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetDLCCount(const String& function_name, const AppId_t app_id, const std::function<int()>& original_function) {
|
/**
|
||||||
static std::mutex section;
|
* @param app_id
|
||||||
std::lock_guard<std::mutex> guard(section);
|
* @return boolean indicating if the function was able to successfully fetch DLC IDs from all sources.
|
||||||
|
*/
|
||||||
|
void fetch_and_cache_dlcs(AppId_t app_id) {
|
||||||
|
static std::mutex mutex;
|
||||||
|
const std::lock_guard<std::mutex> guard(mutex);
|
||||||
|
|
||||||
if (app_id) {
|
if (not app_id) {
|
||||||
logger->debug("{} -> App ID: {}", function_name, app_id);
|
// No app id means we are operating in game mode.
|
||||||
|
// Hence, we need to use utility functions to get app id.
|
||||||
|
try {
|
||||||
|
app_id = steam_impl::get_app_id_or_throw();
|
||||||
|
LOG_INFO("Detected App ID: {}", app_id)
|
||||||
|
} catch (const Exception& ex) {
|
||||||
|
LOG_ERROR("Failed to get app ID: {}", ex.what())
|
||||||
|
app_dlcs[app_id] = {}; // Dummy value to avoid checking for presence on each access
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute count only once // FIXME: This doesn't work in Koalageddon mode
|
// We want to fetch data only once. However, if any of the remote sources have failed
|
||||||
static int total_count = [&]() {
|
// previously, we want to attempt fetching again.
|
||||||
original_dlc_count = original_function();
|
if (fully_fetched.contains(app_id)) {
|
||||||
logger->debug("{} -> Original DLC count: {}", function_name, original_dlc_count);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto injected_count = static_cast<int>(config.dlc_ids.size());
|
// Any of the sources might fail, so we try to get optimal result
|
||||||
logger->debug("{} -> Injected DLC count: {}", function_name, injected_count);
|
// by aggregating results from all the sources into a single set.
|
||||||
|
Vector<DLC> aggregated_dlcs;
|
||||||
|
|
||||||
if (original_dlc_count < max_dlc) {
|
const auto append_dlcs = [&](const Vector<DLC>& source, const String& source_name) {
|
||||||
// Steamworks may max out this value at 64, depending on how much unowned DLCs the user has.
|
LOG_DEBUG("App ID {} has {} DLCs defined in {}", app_id, source.size(), source_name)
|
||||||
// Despite this limit, some games with more than 64 DLCs still keep using this method.
|
aggregated_dlcs < append > source;
|
||||||
// This means we have to fetch full list of IDs from web api.
|
};
|
||||||
|
|
||||||
return original_dlc_count + injected_count;
|
append_dlcs(smoke_api::config::get_extra_dlcs(app_id), "local config");
|
||||||
|
|
||||||
|
const auto github_dlcs_opt = api::fetch_dlcs_from_github(app_id);
|
||||||
|
if (github_dlcs_opt) {
|
||||||
|
append_dlcs(*github_dlcs_opt, "GitHub repository");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto steam_dlcs_opt = api::fetch_dlcs_from_steam(app_id);
|
||||||
|
if (steam_dlcs_opt) {
|
||||||
|
append_dlcs(*steam_dlcs_opt, "Steam API");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (github_dlcs_opt && steam_dlcs_opt) {
|
||||||
|
fully_fetched.insert(app_id);
|
||||||
|
} else {
|
||||||
|
append_dlcs(smoke_api::app_cache::get_dlcs(app_id), "disk cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache DLCs in memory and cache for future use
|
||||||
|
|
||||||
|
app_dlcs[app_id] = aggregated_dlcs;
|
||||||
|
|
||||||
|
smoke_api::app_cache::save_dlcs(app_id, aggregated_dlcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDlcUnlocked(
|
||||||
|
const String& function_name,
|
||||||
|
AppId_t app_id,
|
||||||
|
AppId_t dlc_id,
|
||||||
|
const Function<bool()>& original_function
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const auto unlocked = smoke_api::config::is_dlc_unlocked(app_id, dlc_id, original_function);
|
||||||
|
|
||||||
|
LOG_INFO("{} -> {}DLC ID: {:>8}, Unlocked: {}", function_name, get_app_id_log(app_id), dlc_id, unlocked)
|
||||||
|
|
||||||
|
return unlocked;
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
LOG_ERROR("Uncaught exception: {}", e.what())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetDLCCount(const String& function_name, const AppId_t app_id, const Function<int()>& original_function) {
|
||||||
|
try {
|
||||||
|
const auto total_count = [&](int count) {
|
||||||
|
LOG_INFO("{} -> Responding with DLC count: {}", function_name, count)
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (app_id != 0) {
|
||||||
|
LOG_DEBUG("{} -> App ID: {}", function_name, app_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger->debug("Game has {} or more DLCs. Fetching DLCs from a web API.", max_dlc);
|
const auto original_count = original_function();
|
||||||
|
LOG_DEBUG("{} -> Original DLC count: {}", function_name, original_count)
|
||||||
|
|
||||||
|
if (original_count < MAX_DLC) {
|
||||||
|
return total_count(original_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("Game has {} or more DLCs. Fetching DLCs from remote sources.", original_count)
|
||||||
|
|
||||||
fetch_and_cache_dlcs(app_id);
|
fetch_and_cache_dlcs(app_id);
|
||||||
|
|
||||||
const auto fetched_count = static_cast<int>(cached_dlcs.size());
|
return total_count(static_cast<int>(app_dlcs[app_id].size()));
|
||||||
logger->debug("{} -> Fetched/cached DLC count: {}", function_name, fetched_count);
|
} catch (const Exception& e) {
|
||||||
|
LOG_ERROR(" Uncaught exception: {}", function_name, e.what())
|
||||||
return fetched_count + injected_count;
|
return 0;
|
||||||
}();
|
}
|
||||||
|
|
||||||
logger->info("{} -> Responding with DLC count: {}", function_name, total_count);
|
|
||||||
|
|
||||||
return total_count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetDLCDataByIndex(
|
bool GetDLCDataByIndex(
|
||||||
@@ -195,78 +136,62 @@ namespace steam_apps {
|
|||||||
bool* pbAvailable,
|
bool* pbAvailable,
|
||||||
char* pchName,
|
char* pchName,
|
||||||
int cchNameBufferSize,
|
int cchNameBufferSize,
|
||||||
const std::function<bool()>& original_function
|
const Function<bool()>& original_function,
|
||||||
|
const Function<bool(AppId_t)>& is_originally_unlocked
|
||||||
) {
|
) {
|
||||||
const auto print_dlc_info = [&](const String& tag) {
|
try {
|
||||||
logger->info(
|
LOG_DEBUG("{} -> {}index: {:>3}", function_name, get_app_id_log(app_id), iDLC)
|
||||||
"{} -> [{}] {}index: {}, DLC ID: {}, available: {}, name: '{}'",
|
|
||||||
function_name, tag, get_app_id_log(app_id), iDLC, *pDlcId, *pbAvailable, pchName
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto fill_dlc_info = [&](const AppId_t id) {
|
const auto print_dlc_info = [&](const String& tag) {
|
||||||
*pDlcId = id;
|
LOG_INFO(
|
||||||
*pbAvailable = should_unlock(id);
|
R"({} -> [{:^12}] {}index: {:>3}, DLC ID: {:>8}, available: {:5}, name: "{}")",
|
||||||
|
function_name, tag, get_app_id_log(app_id), iDLC, *pDlcId, *pbAvailable, pchName
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
auto name = fmt::format("DLC #{} with ID: {} ", iDLC, id);
|
const auto inject_dlc = [&](const DLC& dlc) {
|
||||||
name = name.substr(0, cchNameBufferSize);
|
// Fill the output pointers
|
||||||
*name.rbegin() = '\0';
|
*pDlcId = dlc.get_id();
|
||||||
memcpy_s(pchName, cchNameBufferSize, name.c_str(), name.size());
|
*pbAvailable = smoke_api::config::is_dlc_unlocked(
|
||||||
};
|
app_id, *pDlcId, [&]() {
|
||||||
|
return is_originally_unlocked(*pDlcId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const auto inject_dlc = [&](const int index) {
|
auto name = dlc.get_name();
|
||||||
if (index >= config.dlc_ids.size()) {
|
name = name.substr(0, cchNameBufferSize + 1);
|
||||||
logger->error("{} -> Out of bounds injected index: {}", function_name, index);
|
memcpy_s(pchName, cchNameBufferSize, name.c_str(), name.size());
|
||||||
|
};
|
||||||
|
|
||||||
|
if (app_dlcs.contains(app_id)) {
|
||||||
|
const auto& dlcs = app_dlcs[app_id];
|
||||||
|
|
||||||
|
if (iDLC >= 0 && iDLC < dlcs.size()) {
|
||||||
|
inject_dlc(dlcs[iDLC]);
|
||||||
|
print_dlc_info("injected");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_WARN("{} -> Out of bounds DLC index: {}", function_name, iDLC)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto dlc_id = config.dlc_ids[index];
|
const auto success = original_function();
|
||||||
fill_dlc_info(dlc_id);
|
|
||||||
print_dlc_info("injected");
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Original response
|
if (success) {
|
||||||
if (cached_dlcs.empty()) {
|
*pbAvailable = smoke_api::config::is_dlc_unlocked(
|
||||||
// Original DLC index
|
app_id, *pDlcId, [&]() { return *pbAvailable; }
|
||||||
if (iDLC < original_dlc_count) {
|
);
|
||||||
const auto success = original_function();
|
print_dlc_info("original");
|
||||||
|
} else {
|
||||||
if (success) {
|
LOG_WARN("{} -> original call failed for index: {}", function_name, iDLC)
|
||||||
*pbAvailable = should_unlock(*pDlcId);
|
|
||||||
print_dlc_info("original");
|
|
||||||
} else {
|
|
||||||
logger->warn("{} -> original function failed for index: {}", function_name, iDLC);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Injected DLC index (after original)
|
return success;
|
||||||
const auto index = iDLC - original_dlc_count;
|
} catch (const Exception& e) {
|
||||||
return inject_dlc(index);
|
LOG_ERROR("{} -> Uncaught exception: {}", function_name, e.what())
|
||||||
}
|
|
||||||
|
|
||||||
// Cached response
|
|
||||||
const auto total_size = cached_dlcs.size() + config.dlc_ids.size();
|
|
||||||
if (iDLC < 0 or iDLC >= total_size) {
|
|
||||||
logger->error(
|
|
||||||
"{} -> Game accessed out of bounds DLC index: {}. Total size: {}",
|
|
||||||
function_name, iDLC, total_size
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cached index
|
|
||||||
if (iDLC < cached_dlcs.size()) {
|
|
||||||
const auto dlc_id = cached_dlcs[iDLC];
|
|
||||||
fill_dlc_info(dlc_id);
|
|
||||||
print_dlc_info("cached");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Injected DLC index (after cached)
|
|
||||||
|
|
||||||
const auto index = iDLC - static_cast<int>(cached_dlcs.size());
|
|
||||||
return inject_dlc(index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/steam_impl/steam_apps.hpp
Normal file
32
src/steam_impl/steam_apps.hpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
namespace steam_apps {
|
||||||
|
|
||||||
|
bool IsDlcUnlocked(
|
||||||
|
const String& function_name,
|
||||||
|
AppId_t app_id,
|
||||||
|
AppId_t dlc_id,
|
||||||
|
const Function<bool()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
int GetDLCCount(
|
||||||
|
const String& function_name,
|
||||||
|
AppId_t app_id,
|
||||||
|
const Function<int()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
bool GetDLCDataByIndex(
|
||||||
|
const String& dlc_id,
|
||||||
|
AppId_t dlc_ids,
|
||||||
|
int iDLC,
|
||||||
|
AppId_t* pDlcId,
|
||||||
|
bool* pbAvailable,
|
||||||
|
char* pchName,
|
||||||
|
int cchNameBufferSize,
|
||||||
|
const Function<bool()>& original_function,
|
||||||
|
const Function<bool(AppId_t)>& is_originally_unlocked // Aux function to resolve original dlc status
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
|
#include <steam_impl/steam_client.hpp>
|
||||||
#include <steam_impl/steam_impl.hpp>
|
#include <steam_impl/steam_impl.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
|
|
||||||
namespace steam_client{
|
namespace steam_client {
|
||||||
|
|
||||||
void* GetGenericInterface(
|
void* GetGenericInterface(
|
||||||
const String& function_name,
|
const String& function_name,
|
||||||
const String& interface_version,
|
const String& interface_version,
|
||||||
const std::function<void*()>& original_function
|
const Function<void*()>& original_function
|
||||||
) {
|
) {
|
||||||
logger->debug("{} -> Version: '{}'", function_name, interface_version);
|
|
||||||
|
|
||||||
auto* const interface = original_function();
|
auto* const interface = original_function();
|
||||||
|
|
||||||
logger->debug("{} -> Result: {}", function_name, fmt::ptr(interface));
|
LOG_DEBUG("{} -> '{}' @ {}", function_name, interface_version, interface)
|
||||||
|
|
||||||
steam_functions::hook_virtuals(interface, interface_version);
|
steam_impl::hook_virtuals(interface, interface_version);
|
||||||
|
|
||||||
return interface;
|
return interface;
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/steam_impl/steam_client.hpp
Normal file
13
src/steam_impl/steam_client.hpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
namespace steam_client {
|
||||||
|
|
||||||
|
void* GetGenericInterface(
|
||||||
|
const String& function_name,
|
||||||
|
const String& interface_version,
|
||||||
|
const Function<void*()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
#include <steam_functions/steam_functions.hpp>
|
#include <steam_impl/steam_impl.hpp>
|
||||||
|
#include <game_mode/virtuals/steam_api_virtuals.hpp>
|
||||||
|
#include <common/steamclient_exports.hpp>
|
||||||
|
#include <core/globals.hpp>
|
||||||
#include <build_config.h>
|
#include <build_config.h>
|
||||||
|
#include <koalabox/util.hpp>
|
||||||
#include <koalabox/hook.hpp>
|
|
||||||
#include <koalabox/win_util.hpp>
|
#include <koalabox/win_util.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
#include <polyhook2/Misc.hpp>
|
#include <polyhook2/Misc.hpp>
|
||||||
|
|
||||||
namespace steam_functions {
|
#if COMPILE_STORE_MODE
|
||||||
|
#include <store_mode/steamclient/steamclient.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace steam_impl {
|
||||||
|
|
||||||
typedef Map<String, Map<int, int>> FunctionOrdinalMap;
|
typedef Map<String, Map<int, int>> FunctionOrdinalMap;
|
||||||
|
|
||||||
@@ -15,7 +21,7 @@ namespace steam_functions {
|
|||||||
{
|
{
|
||||||
{6, 16},
|
{6, 16},
|
||||||
{7, 18},
|
{7, 18},
|
||||||
{8, 15},
|
{8, 15},
|
||||||
{9, 16},
|
{9, 16},
|
||||||
{12, 15},
|
{12, 15},
|
||||||
}
|
}
|
||||||
@@ -83,25 +89,25 @@ namespace steam_functions {
|
|||||||
int min_version,
|
int min_version,
|
||||||
int max_version
|
int max_version
|
||||||
) {
|
) {
|
||||||
logger->debug("Hooking interface '{}'", version_string);
|
LOG_DEBUG("Hooking interface '{}'", version_string)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const auto version_number = stoi(version_string.substr(prefix.length()));
|
const auto version_number = stoi(version_string.substr(prefix.length()));
|
||||||
|
|
||||||
if (version_number < min_version) {
|
if (version_number < min_version) {
|
||||||
logger->warn("Legacy version of {}: {}", version_string, version_number);
|
LOG_WARN("Legacy version of {}: {}", version_string, version_number)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version_number > max_version) {
|
if (version_number > max_version) {
|
||||||
logger->warn(
|
LOG_WARN(
|
||||||
"Unsupported new version of {}: {}. Fallback version {} will be used",
|
"Unsupported new version of {}: {}. Fallback version {} will be used",
|
||||||
version_string, version_number, max_version
|
version_string, version_number, max_version
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return version_number;
|
return version_number;
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
util::panic("Failed to extract version number from: '{}'", version_string);
|
koalabox::util::panic("Failed to extract version number from: '{}'", version_string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,21 +122,22 @@ namespace steam_functions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
util::panic("Invalid interface version ({}) for function {}", interface_version, function_name);
|
koalabox::util::panic("Invalid interface version ({}) for function {}", interface_version, function_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HOOK(MAP, FUNC) \
|
#define HOOK_VIRTUALS(MAP, FUNC) \
|
||||||
hook::swap_virtual_func( \
|
koalabox::hook::swap_virtual_func( \
|
||||||
interface, \
|
globals::address_map, \
|
||||||
|
interface, \
|
||||||
#FUNC, \
|
#FUNC, \
|
||||||
get_ordinal(MAP, #FUNC, version_number), \
|
get_ordinal(MAP, #FUNC, version_number), \
|
||||||
(FunctionAddress) (FUNC) \
|
reinterpret_cast<uintptr_t>(FUNC) \
|
||||||
);
|
);
|
||||||
|
|
||||||
#define HOOK_STEAM_CLIENT(FUNC) HOOK(steam_client_ordinal_map, FUNC)
|
#define HOOK_STEAM_CLIENT(FUNC) HOOK_VIRTUALS(steam_client_ordinal_map, FUNC)
|
||||||
#define HOOK_STEAM_APPS(FUNC) HOOK(steam_apps_ordinal_map, FUNC)
|
#define HOOK_STEAM_APPS(FUNC) HOOK_VIRTUALS(steam_apps_ordinal_map, FUNC)
|
||||||
#define HOOK_STEAM_USER(FUNC) HOOK(steam_user_ordinal_map, FUNC)
|
#define HOOK_STEAM_USER(FUNC) HOOK_VIRTUALS(steam_user_ordinal_map, FUNC)
|
||||||
#define HOOK_STEAM_INVENTORY(FUNC) HOOK(steam_inventory_ordinal_map, FUNC)
|
#define HOOK_STEAM_INVENTORY(FUNC) HOOK_VIRTUALS(steam_inventory_ordinal_map, FUNC)
|
||||||
|
|
||||||
void hook_virtuals(void* interface, const String& version_string) {
|
void hook_virtuals(void* interface, const String& version_string) {
|
||||||
if (interface == nullptr) {
|
if (interface == nullptr) {
|
||||||
@@ -138,17 +145,15 @@ namespace steam_functions {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Set<std::pair<void*, String>> hooked_interfaces;
|
static Set<void*> hooked_interfaces;
|
||||||
|
|
||||||
const auto interface_pair = std::pair{interface, version_string};
|
if (hooked_interfaces.contains(interface)) {
|
||||||
|
|
||||||
if (hooked_interfaces.contains(interface_pair)) {
|
|
||||||
// This interface is already hooked. Skipping it.
|
// This interface is already hooked. Skipping it.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::mutex section;
|
static Mutex section;
|
||||||
std::lock_guard<std::mutex> guard(section);
|
const MutexLockGuard guard(section);
|
||||||
|
|
||||||
if (version_string.starts_with(STEAM_CLIENT)) {
|
if (version_string.starts_with(STEAM_CLIENT)) {
|
||||||
const auto version_number = extract_version_number(version_string, STEAM_CLIENT, 6, 20);
|
const auto version_number = extract_version_number(version_string, STEAM_CLIENT, 6, 20);
|
||||||
@@ -194,22 +199,26 @@ namespace steam_functions {
|
|||||||
if (version_number >= 2) {
|
if (version_number >= 2) {
|
||||||
HOOK_STEAM_INVENTORY(ISteamInventory_GetResultItemProperty)
|
HOOK_STEAM_INVENTORY(ISteamInventory_GetResultItemProperty)
|
||||||
}
|
}
|
||||||
|
} else if (version_string.starts_with(CLIENT_ENGINE)) {
|
||||||
|
#if COMPILE_STORE_MODE
|
||||||
|
store::steamclient::process_client_engine(reinterpret_cast<uintptr_t>(interface));
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hooked_interfaces.insert(interface_pair);
|
hooked_interfaces.insert(interface);
|
||||||
}
|
}
|
||||||
|
|
||||||
HSteamPipe get_steam_pipe_or_throw() {
|
HSteamPipe get_steam_pipe_or_throw() {
|
||||||
const auto& steam_api_module = win_util::get_module_handle_or_throw(ORIGINAL_DLL);
|
const auto& steam_api_module = koalabox::win_util::get_module_handle_or_throw(STEAMAPI_DLL);
|
||||||
void* GetHSteamPipe_address;
|
void* GetHSteamPipe_address;
|
||||||
try {
|
try {
|
||||||
GetHSteamPipe_address = (void*) win_util::get_proc_address_or_throw(
|
GetHSteamPipe_address = (void*) koalabox::win_util::get_proc_address_or_throw(
|
||||||
steam_api_module, "SteamAPI_GetHSteamPipe"
|
steam_api_module, "SteamAPI_GetHSteamPipe"
|
||||||
);
|
);
|
||||||
} catch (const Exception& ex) {
|
} catch (const Exception& ex) {
|
||||||
GetHSteamPipe_address = (void*) win_util::get_proc_address_or_throw(
|
GetHSteamPipe_address = (void*) koalabox::win_util::get_proc_address_or_throw(
|
||||||
steam_api_module, "GetHSteamPipe"
|
steam_api_module, "GetHSteamPipe"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -236,10 +245,10 @@ namespace steam_functions {
|
|||||||
return function(ARGS(args...));
|
return function(ARGS(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t get_app_id_or_throw() {
|
AppId_t get_app_id_or_throw() {
|
||||||
// Get CreateInterface
|
// Get CreateInterface
|
||||||
const auto& steam_client_module = win_util::get_module_handle_or_throw(STEAMCLIENT_DLL);
|
const auto& steam_client_module = koalabox::win_util::get_module_handle_or_throw(STEAMCLIENT_DLL);
|
||||||
auto* CreateInterface_address = (void*) win_util::get_proc_address_or_throw(
|
auto* CreateInterface_address = (void*) koalabox::win_util::get_proc_address_or_throw(
|
||||||
steam_client_module, "CreateInterface"
|
steam_client_module, "CreateInterface"
|
||||||
);
|
);
|
||||||
auto* CreateInterface_o = PLH::FnCast(CreateInterface_address, CreateInterface);
|
auto* CreateInterface_o = PLH::FnCast(CreateInterface_address, CreateInterface);
|
||||||
@@ -248,7 +257,7 @@ namespace steam_functions {
|
|||||||
int result;
|
int result;
|
||||||
auto* i_steam_client = CreateInterface_o("SteamClient006", &result);
|
auto* i_steam_client = CreateInterface_o("SteamClient006", &result);
|
||||||
if (i_steam_client == nullptr) {
|
if (i_steam_client == nullptr) {
|
||||||
throw util::exception("Failed to obtain SteamClient006 interface. Result: {}", result);
|
throw koalabox::util::exception("Failed to obtain SteamClient006 interface. Result: {}", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get GetISteamUtils
|
// Get GetISteamUtils
|
||||||
@@ -1,107 +1,11 @@
|
|||||||
#include <steam_functions/steam_functions.hpp>
|
#pragma once
|
||||||
|
|
||||||
using namespace koalabox;
|
#include <core/types.hpp>
|
||||||
|
|
||||||
namespace steam_apps {
|
namespace steam_impl {
|
||||||
|
|
||||||
bool IsDlcUnlocked(const String& function_name, AppId_t app_id, AppId_t dlc_id);
|
void hook_virtuals(void* interface, const String& version_string);
|
||||||
|
|
||||||
int GetDLCCount(const String& function_name, AppId_t app_id, const std::function<int()>& original_function);
|
uint32_t get_app_id_or_throw();
|
||||||
|
|
||||||
bool GetDLCDataByIndex(
|
|
||||||
const String& function_name,
|
|
||||||
AppId_t app_id,
|
|
||||||
int iDLC,
|
|
||||||
AppId_t* pDlcId,
|
|
||||||
bool* pbAvailable,
|
|
||||||
char* pchName,
|
|
||||||
int cchNameBufferSize,
|
|
||||||
const std::function<bool()>& original_function
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace steam_user {
|
|
||||||
|
|
||||||
EUserHasLicenseForAppResult UserHasLicenseForApp(
|
|
||||||
const String& function_name,
|
|
||||||
AppId_t appID,
|
|
||||||
const std::function<EUserHasLicenseForAppResult()>& original_function
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace steam_client {
|
|
||||||
|
|
||||||
void* GetGenericInterface(
|
|
||||||
const String& function_name,
|
|
||||||
const String& interface_version,
|
|
||||||
const std::function<void*()>& original_function
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace steam_inventory {
|
|
||||||
|
|
||||||
EResult GetResultStatus(
|
|
||||||
const String& function_name,
|
|
||||||
SteamInventoryResult_t resultHandle,
|
|
||||||
const std::function<EResult()>& original_function
|
|
||||||
);
|
|
||||||
|
|
||||||
bool GetResultItems(
|
|
||||||
const String& function_name,
|
|
||||||
SteamInventoryResult_t resultHandle,
|
|
||||||
SteamItemDetails_t* pOutItemsArray,
|
|
||||||
uint32_t* punOutItemsArraySize,
|
|
||||||
const std::function<bool()>& original_function,
|
|
||||||
const std::function<bool(SteamItemDef_t*, uint32_t*)>& get_item_definition_ids
|
|
||||||
);
|
|
||||||
|
|
||||||
bool GetResultItemProperty(
|
|
||||||
const String& function_name,
|
|
||||||
SteamInventoryResult_t resultHandle,
|
|
||||||
uint32_t unItemIndex,
|
|
||||||
const char* pchPropertyName,
|
|
||||||
char* pchValueBuffer,
|
|
||||||
uint32_t* punValueBufferSizeOut,
|
|
||||||
const std::function<bool()>& original_function
|
|
||||||
);
|
|
||||||
|
|
||||||
bool GetAllItems(
|
|
||||||
const String& function_name,
|
|
||||||
const SteamInventoryResult_t* pResultHandle,
|
|
||||||
const std::function<bool()>& original_function
|
|
||||||
);
|
|
||||||
|
|
||||||
bool GetItemsByID(
|
|
||||||
const String& function_name,
|
|
||||||
SteamInventoryResult_t* pResultHandle,
|
|
||||||
const SteamItemInstanceID_t* pInstanceIDs,
|
|
||||||
uint32_t unCountInstanceIDs,
|
|
||||||
const std::function<bool()>& original_function
|
|
||||||
);
|
|
||||||
|
|
||||||
bool SerializeResult(
|
|
||||||
const String& function_name,
|
|
||||||
SteamInventoryResult_t resultHandle,
|
|
||||||
void* pOutBuffer,
|
|
||||||
uint32_t* punOutBufferSize,
|
|
||||||
const std::function<bool()>& original_function
|
|
||||||
);
|
|
||||||
|
|
||||||
bool GetItemDefinitionIDs(
|
|
||||||
const String& function_name,
|
|
||||||
const SteamItemDef_t* pItemDefIDs,
|
|
||||||
uint32_t* punItemDefIDsArraySize,
|
|
||||||
const std::function<bool()>& original_function
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
bool CheckResultSteamID(
|
|
||||||
const String& function_name,
|
|
||||||
SteamInventoryResult_t resultHandle,
|
|
||||||
CSteamID steamIDExpected,
|
|
||||||
const std::function<bool()>& original_function
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
#include <steam_impl/steam_impl.hpp>
|
#include <steam_impl/steam_inventory.hpp>
|
||||||
#include <smoke_api/smoke_api.hpp>
|
#include <smoke_api/config.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
|
|
||||||
namespace steam_inventory {
|
namespace steam_inventory {
|
||||||
|
|
||||||
EResult GetResultStatus(
|
EResult GetResultStatus(
|
||||||
const String& function_name,
|
const String& function_name,
|
||||||
const SteamInventoryResult_t resultHandle,
|
const SteamInventoryResult_t resultHandle,
|
||||||
const std::function<EResult()>& original_function
|
const Function<EResult()>& original_function
|
||||||
) {
|
) {
|
||||||
const auto status = original_function();
|
const auto status = original_function();
|
||||||
|
|
||||||
logger->debug("{} -> handle: {}, status: {}", function_name, resultHandle, (int) status);
|
LOG_DEBUG("{} -> handle: {}, status: {}", function_name, resultHandle, (int) status)
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -20,62 +21,61 @@ namespace steam_inventory {
|
|||||||
const SteamInventoryResult_t resultHandle,
|
const SteamInventoryResult_t resultHandle,
|
||||||
SteamItemDetails_t* pOutItemsArray,
|
SteamItemDetails_t* pOutItemsArray,
|
||||||
uint32_t* punOutItemsArraySize,
|
uint32_t* punOutItemsArraySize,
|
||||||
const std::function<bool()>& original_function,
|
const Function<bool()>& original_function,
|
||||||
const std::function<bool(SteamItemDef_t*, uint32_t*)>& get_item_definition_ids
|
const Function<bool(SteamItemDef_t*, uint32_t*)>& get_item_definition_ids
|
||||||
) {
|
) {
|
||||||
static std::mutex section;
|
static std::mutex section;
|
||||||
std::lock_guard<std::mutex> guard(section);
|
const std::lock_guard<std::mutex> guard(section);
|
||||||
|
|
||||||
const auto success = original_function();
|
const auto success = original_function();
|
||||||
|
|
||||||
auto print_item = [](const String& tag, const SteamItemDetails_t& item) {
|
auto print_item = [](const String& tag, const SteamItemDetails_t& item) {
|
||||||
logger->debug(
|
LOG_DEBUG(
|
||||||
" [{}] definitionId: {}, itemId: {}, quantity: {}, flags: {}",
|
" [{}] definitionId: {}, itemId: {}, quantity: {}, flags: {}",
|
||||||
tag, item.m_iDefinition, item.m_itemId, item.m_unQuantity, item.m_unFlags
|
tag, item.m_iDefinition, item.m_itemId, item.m_unQuantity, item.m_unFlags
|
||||||
);
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (not success) {
|
if (not success) {
|
||||||
logger->debug("{} -> original result is false", function_name);
|
LOG_DEBUG("{} -> original result is false", function_name)
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (punOutItemsArraySize == nullptr) {
|
if (punOutItemsArraySize == nullptr) {
|
||||||
logger->error("{} -> arraySize pointer is null", function_name);
|
LOG_ERROR("{} -> arraySize pointer is null", function_name)
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger->debug(
|
LOG_DEBUG(
|
||||||
"{} -> handle: {}, pOutItemsArray: {}, arraySize: {}",
|
"{} -> handle: {}, pOutItemsArray: {}, arraySize: {}",
|
||||||
function_name, resultHandle, fmt::ptr(pOutItemsArray), *punOutItemsArraySize
|
function_name, resultHandle, fmt::ptr(pOutItemsArray), *punOutItemsArraySize
|
||||||
);
|
)
|
||||||
|
|
||||||
static uint32_t original_count = 0;
|
static uint32_t original_count = 0;
|
||||||
const auto injected_count = smoke_api::config.inventory_items.size();
|
const auto injected_count = smoke_api::config::instance.extra_inventory_items.size();
|
||||||
|
|
||||||
// Automatically get inventory items from steam
|
// Automatically get inventory items from steam
|
||||||
static Vector<SteamItemDef_t> auto_inventory_items;
|
static Vector<SteamItemDef_t> auto_inventory_items;
|
||||||
if (smoke_api::config.auto_inject_inventory) {
|
if (smoke_api::config::instance.auto_inject_inventory) {
|
||||||
static std::once_flag flag;
|
CALL_ONCE({
|
||||||
std::call_once(flag, [&]() {
|
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
if (get_item_definition_ids(nullptr, &count)) {
|
if (get_item_definition_ids(nullptr, &count)) {
|
||||||
auto_inventory_items.resize(count);
|
auto_inventory_items.resize(count);
|
||||||
get_item_definition_ids(auto_inventory_items.data(), &count);
|
get_item_definition_ids(auto_inventory_items.data(), &count);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
const auto auto_injected_count = auto_inventory_items.size();
|
|
||||||
|
|
||||||
|
const auto auto_injected_count = auto_inventory_items.size();
|
||||||
|
|
||||||
if (not pOutItemsArray) {
|
if (not pOutItemsArray) {
|
||||||
// If pOutItemsArray is NULL then we must set the array size.
|
// If pOutItemsArray is NULL then we must set the array size.
|
||||||
original_count = *punOutItemsArraySize;
|
original_count = *punOutItemsArraySize;
|
||||||
*punOutItemsArraySize += auto_injected_count + injected_count;
|
*punOutItemsArraySize += auto_injected_count + injected_count;
|
||||||
logger->debug(
|
LOG_DEBUG(
|
||||||
"{} -> Original count: {}, Total count: {}",
|
"{} -> Original count: {}, Total count: {}",
|
||||||
function_name, original_count, *punOutItemsArraySize
|
function_name, original_count, *punOutItemsArraySize
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we modify the array
|
// Otherwise, we modify the array
|
||||||
for (int i = 0; i < original_count; i++) {
|
for (int i = 0; i < original_count; i++) {
|
||||||
@@ -102,7 +102,7 @@ namespace steam_inventory {
|
|||||||
|
|
||||||
for (int i = 0; i < injected_count; i++) {
|
for (int i = 0; i < injected_count; i++) {
|
||||||
auto& item = pOutItemsArray[original_count + auto_injected_count + i];
|
auto& item = pOutItemsArray[original_count + auto_injected_count + i];
|
||||||
const auto item_def_id = smoke_api::config.inventory_items[i];
|
const auto item_def_id = smoke_api::config::instance.extra_inventory_items[i];
|
||||||
|
|
||||||
item = new_item(item_def_id);
|
item = new_item(item_def_id);
|
||||||
|
|
||||||
@@ -119,8 +119,8 @@ namespace steam_inventory {
|
|||||||
uint32_t unItemIndex,
|
uint32_t unItemIndex,
|
||||||
const char* pchPropertyName,
|
const char* pchPropertyName,
|
||||||
char* pchValueBuffer,
|
char* pchValueBuffer,
|
||||||
uint32_t* punValueBufferSizeOut,
|
const uint32_t* punValueBufferSizeOut,
|
||||||
const std::function<bool()>& original_function
|
const Function<bool()>& original_function
|
||||||
) {
|
) {
|
||||||
const auto common_info = fmt::format(
|
const auto common_info = fmt::format(
|
||||||
"{} -> Handle: {}, Index: {}, Name: '{}'", function_name, resultHandle, unItemIndex, pchPropertyName
|
"{} -> Handle: {}, Index: {}, Name: '{}'", function_name, resultHandle, unItemIndex, pchPropertyName
|
||||||
@@ -129,11 +129,11 @@ namespace steam_inventory {
|
|||||||
const auto success = original_function();
|
const auto success = original_function();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
logger->warn("{}, Result is false", common_info);
|
LOG_WARN("{}, Result is false", common_info)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger->debug("{}, Buffer: '{}'", common_info, String(pchValueBuffer, *punValueBufferSizeOut - 1));
|
LOG_DEBUG("{}, Buffer: '{}'", common_info, String(pchValueBuffer, *punValueBufferSizeOut - 1))
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
@@ -141,11 +141,11 @@ namespace steam_inventory {
|
|||||||
bool GetAllItems(
|
bool GetAllItems(
|
||||||
const String& function_name,
|
const String& function_name,
|
||||||
const SteamInventoryResult_t* pResultHandle,
|
const SteamInventoryResult_t* pResultHandle,
|
||||||
const std::function<bool()>& original_function
|
const Function<bool()>& original_function
|
||||||
) {
|
) {
|
||||||
const auto success = original_function();
|
const auto success = original_function();
|
||||||
|
|
||||||
logger->debug("{} -> Handle: {}", function_name, fmt::ptr(pResultHandle));
|
LOG_DEBUG("{} -> Handle: {}", function_name, fmt::ptr(pResultHandle))
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
@@ -156,15 +156,15 @@ namespace steam_inventory {
|
|||||||
SteamInventoryResult_t* pResultHandle,
|
SteamInventoryResult_t* pResultHandle,
|
||||||
const SteamItemInstanceID_t* pInstanceIDs,
|
const SteamItemInstanceID_t* pInstanceIDs,
|
||||||
const uint32_t unCountInstanceIDs,
|
const uint32_t unCountInstanceIDs,
|
||||||
const std::function<bool()>& original_function
|
const Function<bool()>& original_function
|
||||||
) {
|
) {
|
||||||
const auto success = original_function();
|
const auto success = original_function();
|
||||||
|
|
||||||
logger->debug("{} -> Handle: {}", function_name, fmt::ptr(pResultHandle));
|
LOG_DEBUG("{} -> Handle: {}", function_name, fmt::ptr(pResultHandle))
|
||||||
|
|
||||||
if (success && pInstanceIDs != nullptr) {
|
if (success && pInstanceIDs != nullptr) {
|
||||||
for (int i = 0; i < unCountInstanceIDs; i++) {
|
for (int i = 0; i < unCountInstanceIDs; i++) {
|
||||||
logger->debug(" Index: {}, ItemId: {}", i, pInstanceIDs[i]);
|
LOG_DEBUG(" Index: {}, ItemId: {}", i, pInstanceIDs[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,15 +176,15 @@ namespace steam_inventory {
|
|||||||
SteamInventoryResult_t resultHandle,
|
SteamInventoryResult_t resultHandle,
|
||||||
void* pOutBuffer,
|
void* pOutBuffer,
|
||||||
uint32_t* punOutBufferSize,
|
uint32_t* punOutBufferSize,
|
||||||
const std::function<bool()>& original_function
|
const Function<bool()>& original_function
|
||||||
) {
|
) {
|
||||||
const auto success = original_function();
|
const auto success = original_function();
|
||||||
|
|
||||||
if (pOutBuffer != nullptr) {
|
if (pOutBuffer != nullptr) {
|
||||||
String buffer((char*) pOutBuffer, *punOutBufferSize);
|
String buffer((char*) pOutBuffer, *punOutBufferSize);
|
||||||
logger->debug("{} -> Handle: {}, Buffer: '{}'", function_name, resultHandle, buffer);
|
LOG_DEBUG("{} -> Handle: {}, Buffer: '{}'", function_name, resultHandle, buffer)
|
||||||
} else {
|
} else {
|
||||||
logger->debug("{} -> Handle: {}, Size: '{}'", function_name, resultHandle, *punOutBufferSize);
|
LOG_DEBUG("{} -> Handle: {}, Size: '{}'", function_name, resultHandle, *punOutBufferSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
@@ -194,23 +194,23 @@ namespace steam_inventory {
|
|||||||
const String& function_name,
|
const String& function_name,
|
||||||
const SteamItemDef_t* pItemDefIDs,
|
const SteamItemDef_t* pItemDefIDs,
|
||||||
uint32_t* punItemDefIDsArraySize,
|
uint32_t* punItemDefIDsArraySize,
|
||||||
const std::function<bool()>& original_function
|
const Function<bool()>& original_function
|
||||||
) {
|
) {
|
||||||
const auto success = original_function();
|
const auto success = original_function();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
logger->warn("{} -> Result is false", function_name);
|
LOG_WARN("{} -> Result is false", function_name)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (punItemDefIDsArraySize) {
|
if (punItemDefIDsArraySize) {
|
||||||
logger->debug("{} -> Size: {}", function_name, *punItemDefIDsArraySize);
|
LOG_DEBUG("{} -> Size: {}", function_name, *punItemDefIDsArraySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pItemDefIDs) { // Definitions were copied
|
if (pItemDefIDs) { // Definitions were copied
|
||||||
for (int i = 0; i < *punItemDefIDsArraySize; i++) {
|
for (int i = 0; i < *punItemDefIDsArraySize; i++) {
|
||||||
const auto& def = pItemDefIDs[i];
|
const auto& def = pItemDefIDs[i];
|
||||||
logger->debug(" Index: {}, ID: {}", i, def);
|
LOG_DEBUG(" Index: {}, ID: {}", i, def)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,14 +221,14 @@ namespace steam_inventory {
|
|||||||
const String& function_name,
|
const String& function_name,
|
||||||
SteamInventoryResult_t resultHandle,
|
SteamInventoryResult_t resultHandle,
|
||||||
CSteamID steamIDExpected,
|
CSteamID steamIDExpected,
|
||||||
const std::function<bool()>& original_function
|
const Function<bool()>& original_function
|
||||||
) {
|
) {
|
||||||
const auto result = original_function();
|
const auto result = original_function();
|
||||||
|
|
||||||
logger->debug(
|
LOG_DEBUG(
|
||||||
"{} -> handle: {}, steamID: {}, original result: {}",
|
"{} -> handle: {}, steamID: {}, original result: {}",
|
||||||
function_name, resultHandle, steamIDExpected, result
|
function_name, resultHandle, steamIDExpected, result
|
||||||
);
|
)
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
68
src/steam_impl/steam_inventory.hpp
Normal file
68
src/steam_impl/steam_inventory.hpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
namespace steam_inventory {
|
||||||
|
|
||||||
|
EResult GetResultStatus(
|
||||||
|
const String& function_name,
|
||||||
|
SteamInventoryResult_t resultHandle,
|
||||||
|
const Function<EResult()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
bool GetResultItems(
|
||||||
|
const String& function_name,
|
||||||
|
SteamInventoryResult_t resultHandle,
|
||||||
|
SteamItemDetails_t* pOutItemsArray,
|
||||||
|
uint32_t* punOutItemsArraySize,
|
||||||
|
const Function<bool()>& original_function,
|
||||||
|
const Function<bool(SteamItemDef_t*, uint32_t*)>& get_item_definition_ids
|
||||||
|
);
|
||||||
|
|
||||||
|
bool GetResultItemProperty(
|
||||||
|
const String& function_name,
|
||||||
|
SteamInventoryResult_t resultHandle,
|
||||||
|
uint32_t unItemIndex,
|
||||||
|
const char* pchPropertyName,
|
||||||
|
char* pchValueBuffer,
|
||||||
|
const uint32_t* punValueBufferSizeOut,
|
||||||
|
const Function<bool()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
bool GetAllItems(
|
||||||
|
const String& function_name,
|
||||||
|
const SteamInventoryResult_t* pResultHandle,
|
||||||
|
const Function<bool()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
bool GetItemsByID(
|
||||||
|
const String& function_name,
|
||||||
|
SteamInventoryResult_t* pResultHandle,
|
||||||
|
const SteamItemInstanceID_t* pInstanceIDs,
|
||||||
|
uint32_t unCountInstanceIDs,
|
||||||
|
const Function<bool()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
bool SerializeResult(
|
||||||
|
const String& function_name,
|
||||||
|
SteamInventoryResult_t resultHandle,
|
||||||
|
void* pOutBuffer,
|
||||||
|
uint32_t* punOutBufferSize,
|
||||||
|
const Function<bool()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
bool GetItemDefinitionIDs(
|
||||||
|
const String& function_name,
|
||||||
|
const SteamItemDef_t* pItemDefIDs,
|
||||||
|
uint32_t* punItemDefIDsArraySize,
|
||||||
|
const Function<bool()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
bool CheckResultSteamID(
|
||||||
|
const String& function_name,
|
||||||
|
SteamInventoryResult_t resultHandle,
|
||||||
|
CSteamID steamIDExpected,
|
||||||
|
const Function<bool()>& original_function
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,23 +1,29 @@
|
|||||||
#include <steam_impl/steam_impl.hpp>
|
#include <steam_impl/steam_user.hpp>
|
||||||
#include <smoke_api/smoke_api.hpp>
|
#include <smoke_api/config.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
|
|
||||||
namespace steam_user {
|
namespace steam_user {
|
||||||
|
|
||||||
EUserHasLicenseForAppResult UserHasLicenseForApp(
|
EUserHasLicenseForAppResult UserHasLicenseForApp(
|
||||||
const String& function_name,
|
const String& function_name,
|
||||||
AppId_t appID,
|
AppId_t appId,
|
||||||
const std::function<EUserHasLicenseForAppResult()>& original_function
|
AppId_t dlcId,
|
||||||
|
const Function<EUserHasLicenseForAppResult()>& original_function
|
||||||
) {
|
) {
|
||||||
const auto result = original_function();
|
const auto result = original_function();
|
||||||
|
|
||||||
if (result == k_EUserHasLicenseResultNoAuth) {
|
if (result == k_EUserHasLicenseResultNoAuth) {
|
||||||
logger->warn("{} -> App ID: {}, Result: NoAuth", function_name, appID);
|
LOG_WARN("{} -> App ID: {:>8}, Result: NoAuth", function_name, dlcId)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto has_license = smoke_api::should_unlock(appID);
|
const auto has_license = smoke_api::config::is_dlc_unlocked(
|
||||||
|
appId, dlcId, [&]() {
|
||||||
|
return result == k_EUserHasLicenseResultHasLicense;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
logger->info("{} -> App ID: {}, HasLicense: {}", function_name, appID, has_license);
|
LOG_INFO("{} -> App ID: {:>8}, HasLicense: {}", function_name, dlcId, has_license)
|
||||||
|
|
||||||
return has_license
|
return has_license
|
||||||
? k_EUserHasLicenseResultHasLicense
|
? k_EUserHasLicenseResultHasLicense
|
||||||
|
|||||||
14
src/steam_impl/steam_user.hpp
Normal file
14
src/steam_impl/steam_user.hpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
namespace steam_user {
|
||||||
|
|
||||||
|
EUserHasLicenseForAppResult UserHasLicenseForApp(
|
||||||
|
const String& function_name,
|
||||||
|
AppId_t appId,
|
||||||
|
AppId_t dlcId,
|
||||||
|
const Function<EUserHasLicenseForAppResult()>& original_function
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
// results from UserHasLicenseForApp
|
|
||||||
enum EUserHasLicenseForAppResult {
|
|
||||||
k_EUserHasLicenseResultHasLicense = 0, // User has a license for specified app
|
|
||||||
k_EUserHasLicenseResultDoesNotHaveLicense = 1, // User does not have a license for the specified app
|
|
||||||
k_EUserHasLicenseResultNoAuth = 2, // User has not been authenticated
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef uint32_t SteamInventoryResult_t;
|
|
||||||
typedef uint64_t SteamItemInstanceID_t;
|
|
||||||
typedef uint32_t SteamItemDef_t;
|
|
||||||
|
|
||||||
struct SteamItemDetails_t {
|
|
||||||
SteamItemInstanceID_t m_itemId;
|
|
||||||
uint32_t m_iDefinition;
|
|
||||||
uint16_t m_unQuantity;
|
|
||||||
uint16_t m_unFlags; // see ESteamItemFlags
|
|
||||||
};
|
|
||||||
|
|
||||||
enum EResult {
|
|
||||||
k_EResultNone = 0, // no result
|
|
||||||
k_EResultOK = 1, // success
|
|
||||||
k_EResultFail = 2, // generic failure
|
|
||||||
k_EResultNoConnection = 3, // no/failed network connection
|
|
||||||
// k_EResultNoConnectionRetry = 4, // OBSOLETE - removed
|
|
||||||
k_EResultInvalidPassword = 5, // password/ticket is invalid
|
|
||||||
k_EResultLoggedInElsewhere = 6, // same user logged in elsewhere
|
|
||||||
k_EResultInvalidProtocolVer = 7, // protocol version is incorrect
|
|
||||||
k_EResultInvalidParam = 8, // a parameter is incorrect
|
|
||||||
k_EResultFileNotFound = 9, // file was not found
|
|
||||||
k_EResultBusy = 10, // called method busy - action not taken
|
|
||||||
k_EResultInvalidState = 11, // called object was in an invalid state
|
|
||||||
k_EResultInvalidName = 12, // name is invalid
|
|
||||||
k_EResultInvalidEmail = 13, // email is invalid
|
|
||||||
k_EResultDuplicateName = 14, // name is not unique
|
|
||||||
k_EResultAccessDenied = 15, // access is denied
|
|
||||||
k_EResultTimeout = 16, // operation timed out
|
|
||||||
k_EResultBanned = 17, // VAC2 banned
|
|
||||||
k_EResultAccountNotFound = 18, // account not found
|
|
||||||
k_EResultInvalidSteamID = 19, // steamID is invalid
|
|
||||||
k_EResultServiceUnavailable = 20, // The requested service is currently unavailable
|
|
||||||
k_EResultNotLoggedOn = 21, // The user is not logged on
|
|
||||||
k_EResultPending = 22, // Request is pending (may be in process, or waiting on third party)
|
|
||||||
k_EResultEncryptionFailure = 23, // Encryption or Decryption failed
|
|
||||||
k_EResultInsufficientPrivilege = 24, // Insufficient privilege
|
|
||||||
k_EResultLimitExceeded = 25, // Too much of a good thing
|
|
||||||
k_EResultRevoked = 26, // Access has been revoked (used for revoked guest passes)
|
|
||||||
k_EResultExpired = 27, // License/Guest pass the user is trying to access is expired
|
|
||||||
k_EResultAlreadyRedeemed = 28, // Guest pass has already been redeemed by account, cannot be acked again
|
|
||||||
k_EResultDuplicateRequest = 29, // The request is a duplicate and the action has already occurred in the past, ignored this time
|
|
||||||
k_EResultAlreadyOwned = 30, // All the games in this guest pass redemption request are already owned by the user
|
|
||||||
k_EResultIPNotFound = 31, // IP address not found
|
|
||||||
k_EResultPersistFailed = 32, // failed to write change to the data store
|
|
||||||
k_EResultLockingFailed = 33, // failed to acquire access lock for this operation
|
|
||||||
k_EResultLogonSessionReplaced = 34,
|
|
||||||
k_EResultConnectFailed = 35,
|
|
||||||
k_EResultHandshakeFailed = 36,
|
|
||||||
k_EResultIOFailure = 37,
|
|
||||||
k_EResultRemoteDisconnect = 38,
|
|
||||||
k_EResultShoppingCartNotFound = 39, // failed to find the shopping cart requested
|
|
||||||
k_EResultBlocked = 40, // a user didn't allow it
|
|
||||||
k_EResultIgnored = 41, // target is ignoring sender
|
|
||||||
k_EResultNoMatch = 42, // nothing matching the request found
|
|
||||||
k_EResultAccountDisabled = 43,
|
|
||||||
k_EResultServiceReadOnly = 44, // this service is not accepting content changes right now
|
|
||||||
k_EResultAccountNotFeatured = 45, // account doesn't have value, so this feature isn't available
|
|
||||||
k_EResultAdministratorOK = 46, // allowed to take this action, but only because requester is admin
|
|
||||||
k_EResultContentVersion = 47, // A Version mismatch in content transmitted within the Steam protocol.
|
|
||||||
k_EResultTryAnotherCM = 48, // The current CM can't service the user making a request, user should try another.
|
|
||||||
k_EResultPasswordRequiredToKickSession = 49,// You are already logged in elsewhere, this cached credential login has failed.
|
|
||||||
k_EResultAlreadyLoggedInElsewhere = 50, // You are already logged in elsewhere, you must wait
|
|
||||||
k_EResultSuspended = 51, // Long running operation (content download) suspended/paused
|
|
||||||
k_EResultCancelled = 52, // Operation canceled (typically by user: content download)
|
|
||||||
k_EResultDataCorruption = 53, // Operation canceled because data is ill formed or unrecoverable
|
|
||||||
k_EResultDiskFull = 54, // Operation canceled - not enough disk space.
|
|
||||||
k_EResultRemoteCallFailed = 55, // an remote call or IPC call failed
|
|
||||||
k_EResultPasswordUnset = 56, // Password could not be verified as it's unset server side
|
|
||||||
k_EResultExternalAccountUnlinked = 57, // External account (PSN, Facebook...) is not linked to a Steam account
|
|
||||||
k_EResultPSNTicketInvalid = 58, // PSN ticket was invalid
|
|
||||||
k_EResultExternalAccountAlreadyLinked = 59, // External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first
|
|
||||||
k_EResultRemoteFileConflict = 60, // The sync cannot resume due to a conflict between the local and remote files
|
|
||||||
k_EResultIllegalPassword = 61, // The requested new password is not legal
|
|
||||||
k_EResultSameAsPreviousValue = 62, // new value is the same as the old one ( secret question and answer )
|
|
||||||
k_EResultAccountLogonDenied = 63, // account login denied due to 2nd factor authentication failure
|
|
||||||
k_EResultCannotUseOldPassword = 64, // The requested new password is not legal
|
|
||||||
k_EResultInvalidLoginAuthCode = 65, // account login denied due to auth code invalid
|
|
||||||
k_EResultAccountLogonDeniedNoMail = 66, // account login denied due to 2nd factor auth failure - and no mail has been sent
|
|
||||||
k_EResultHardwareNotCapableOfIPT = 67, //
|
|
||||||
k_EResultIPTInitError = 68, //
|
|
||||||
k_EResultParentalControlRestricted = 69, // operation failed due to parental control restrictions for current user
|
|
||||||
k_EResultFacebookQueryError = 70, // Facebook query returned an error
|
|
||||||
k_EResultExpiredLoginAuthCode = 71, // account login denied due to auth code expired
|
|
||||||
k_EResultIPLoginRestrictionFailed = 72,
|
|
||||||
k_EResultAccountLockedDown = 73,
|
|
||||||
k_EResultAccountLogonDeniedVerifiedEmailRequired = 74,
|
|
||||||
k_EResultNoMatchingURL = 75,
|
|
||||||
k_EResultBadResponse = 76, // parse failure, missing field, etc.
|
|
||||||
k_EResultRequirePasswordReEntry = 77, // The user cannot complete the action until they re-enter their password
|
|
||||||
k_EResultValueOutOfRange = 78, // the value entered is outside the acceptable range
|
|
||||||
k_EResultUnexpectedError = 79, // something happened that we didn't expect to ever happen
|
|
||||||
k_EResultDisabled = 80, // The requested service has been configured to be unavailable
|
|
||||||
k_EResultInvalidCEGSubmission = 81, // The set of files submitted to the CEG server are not valid !
|
|
||||||
k_EResultRestrictedDevice = 82, // The device being used is not allowed to perform this action
|
|
||||||
k_EResultRegionLocked = 83, // The action could not be complete because it is region restricted
|
|
||||||
k_EResultRateLimitExceeded = 84, // Temporary rate limit exceeded, try again later, different from k_EResultLimitExceeded which may be permanent
|
|
||||||
k_EResultAccountLoginDeniedNeedTwoFactor = 85, // Need two-factor code to login
|
|
||||||
k_EResultItemDeleted = 86, // The thing we're trying to access has been deleted
|
|
||||||
k_EResultAccountLoginDeniedThrottle = 87, // login attempt failed, try to throttle response to possible attacker
|
|
||||||
k_EResultTwoFactorCodeMismatch = 88, // two factor code mismatch
|
|
||||||
k_EResultTwoFactorActivationCodeMismatch = 89, // activation code for two-factor didn't match
|
|
||||||
k_EResultAccountAssociatedToMultiplePartners = 90, // account has been associated with multiple partners
|
|
||||||
k_EResultNotModified = 91, // data not modified
|
|
||||||
k_EResultNoMobileDevice = 92, // the account does not have a mobile device associated with it
|
|
||||||
k_EResultTimeNotSynced = 93, // the time presented is out of range or tolerance
|
|
||||||
k_EResultSmsCodeFailed = 94, // SMS code failure (no match, none pending, etc.)
|
|
||||||
k_EResultAccountLimitExceeded = 95, // Too many accounts access this resource
|
|
||||||
k_EResultAccountActivityLimitExceeded = 96, // Too many changes to this account
|
|
||||||
k_EResultPhoneActivityLimitExceeded = 97, // Too many changes to this phone
|
|
||||||
k_EResultRefundToWallet = 98, // Cannot refund to payment method, must use wallet
|
|
||||||
k_EResultEmailSendFailure = 99, // Cannot send an email
|
|
||||||
k_EResultNotSettled = 100, // Can't perform operation till payment has settled
|
|
||||||
k_EResultNeedCaptcha = 101, // Needs to provide a valid captcha
|
|
||||||
k_EResultGSLTDenied = 102, // a game server login token owned by this token's owner has been banned
|
|
||||||
k_EResultGSOwnerDenied = 103, // game server owner is denied for other reason (account lock, community ban, vac ban, missing phone)
|
|
||||||
k_EResultInvalidItemType = 104, // the type of thing we were requested to act on is invalid
|
|
||||||
k_EResultIPBanned = 105, // the ip address has been banned from taking this action
|
|
||||||
k_EResultGSLTExpired = 106, // this token has expired from disuse; can be reset for use
|
|
||||||
k_EResultInsufficientFunds = 107, // user doesn't have enough wallet funds to complete the action
|
|
||||||
k_EResultTooManyPending = 108, // There are too many of this thing pending already
|
|
||||||
k_EResultNoSiteLicensesFound = 109, // No site licenses found
|
|
||||||
k_EResultWGNetworkSendExceeded = 110, // the WG couldn't send a response because we exceeded max network send size
|
|
||||||
k_EResultAccountNotFriends = 111, // the user is not mutually friends
|
|
||||||
k_EResultLimitedUserAccount = 112, // the user is limited
|
|
||||||
k_EResultCantRemoveItem = 113, // item can't be removed
|
|
||||||
k_EResultAccountDeleted = 114, // account has been deleted
|
|
||||||
k_EResultExistingUserCancelledLicense = 115, // A license for this already exists, but cancelled
|
|
||||||
k_EResultCommunityCooldown = 116, // access is denied because of a community cooldown (probably from support profile data resets)
|
|
||||||
k_EResultNoLauncherSpecified = 117, // No launcher was specified, but a launcher was needed to choose correct realm for operation.
|
|
||||||
k_EResultMustAgreeToSSA = 118, // User must agree to china SSA or global SSA before login
|
|
||||||
k_EResultLauncherMigrated = 119, // The specified launcher type is no longer supported; the user should be directed elsewhere
|
|
||||||
k_EResultSteamRealmMismatch = 120, // The user's realm does not match the realm of the requested resource
|
|
||||||
k_EResultInvalidSignature = 121, // signature check did not match
|
|
||||||
k_EResultParseFailure = 122, // Failed to parse input
|
|
||||||
k_EResultNoVerifiedPhone = 123, // account does not have a verified phone number
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_functions/steam_functions.hpp>
|
|
||||||
#include <steam_impl/steam_impl.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
DLL_EXPORT(void*) CreateInterface(const char* interface_string, int* out_result) {
|
|
||||||
return steam_client::GetGenericInterface(__func__, interface_string, [&]() {
|
|
||||||
GET_ORIGINAL_FUNCTION(CreateInterface)
|
|
||||||
|
|
||||||
return CreateInterface_o(interface_string, out_result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_impl/steam_impl.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(
|
|
||||||
PARAMS( // NOLINT(misc-unused-parameters)
|
|
||||||
AppId_t app_id,
|
|
||||||
AppId_t dlc_id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return steam_apps::IsDlcUnlocked(__func__, app_id, dlc_id);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_impl/steam_impl.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t appId)) {
|
|
||||||
return steam_apps::GetDLCCount(__func__, appId, [&]() {
|
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientApps_GetDLCCount)
|
|
||||||
|
|
||||||
return IClientApps_GetDLCCount_o(ARGS(appId));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL(bool) IClientApps_BGetDLCDataByIndex(
|
|
||||||
PARAMS(
|
|
||||||
AppId_t appID,
|
|
||||||
int iDLC,
|
|
||||||
AppId_t* pDlcID,
|
|
||||||
bool* pbAvailable,
|
|
||||||
char* pchName,
|
|
||||||
int cchNameBufferSize
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return steam_apps::GetDLCDataByIndex(__func__, appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize, [&]() {
|
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientApps_BGetDLCDataByIndex)
|
|
||||||
|
|
||||||
return IClientApps_BGetDLCDataByIndex_o(
|
|
||||||
ARGS(appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
|
||||||
#include <steam_impl/steam_impl.hpp>
|
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
VIRTUAL(bool) IClientUser_IsSubscribedApp(PARAMS(AppId_t app_id)) { // NOLINT(misc-unused-parameters)
|
|
||||||
return steam_apps::IsDlcUnlocked(__func__, 0, app_id);
|
|
||||||
}
|
|
||||||
12
src/store_mode/steamclient/client_app_manager.cpp
Normal file
12
src/store_mode/steamclient/client_app_manager.cpp
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#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)) {
|
||||||
|
return steam_apps::IsDlcUnlocked(
|
||||||
|
__func__, app_id, dlc_id, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientAppManager_IsAppDlcInstalled)
|
||||||
|
|
||||||
|
return IClientAppManager_IsAppDlcInstalled_o(ARGS(app_id, dlc_id));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
44
src/store_mode/steamclient/client_apps.cpp
Normal file
44
src/store_mode/steamclient/client_apps.cpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#include <store_mode/steamclient/steamclient.hpp>
|
||||||
|
#include <steam_impl/steam_apps.hpp>
|
||||||
|
|
||||||
|
VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t appId)) {
|
||||||
|
return steam_apps::GetDLCCount(
|
||||||
|
__func__, appId, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_GetDLCCount)
|
||||||
|
|
||||||
|
return IClientApps_GetDLCCount_o(ARGS(appId));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VIRTUAL(bool) IClientApps_BGetDLCDataByIndex(
|
||||||
|
PARAMS(
|
||||||
|
AppId_t appID,
|
||||||
|
int iDLC,
|
||||||
|
AppId_t* pDlcID,
|
||||||
|
bool* pbAvailable,
|
||||||
|
char* pchName,
|
||||||
|
int cchNameBufferSize
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return steam_apps::GetDLCDataByIndex(
|
||||||
|
__func__, appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize,
|
||||||
|
[&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_BGetDLCDataByIndex)
|
||||||
|
|
||||||
|
return IClientApps_BGetDLCDataByIndex_o(
|
||||||
|
ARGS(appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[&](AppId_t dlc_id) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Would never happen in practice, as the interfaces would be instantiated almost simultaneously
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#include <smoke_api/smoke_api.hpp>
|
#include <store_mode/steamclient/steamclient.hpp>
|
||||||
#include <steam_impl/steam_impl.hpp>
|
#include <steam_impl/steam_inventory.hpp>
|
||||||
|
|
||||||
using namespace smoke_api;
|
|
||||||
|
|
||||||
VIRTUAL(EResult) IClientInventory_GetResultStatus(PARAMS(SteamInventoryResult_t resultHandle)) {
|
VIRTUAL(EResult) IClientInventory_GetResultStatus(PARAMS(SteamInventoryResult_t resultHandle)) {
|
||||||
return steam_inventory::GetResultStatus(__func__, resultHandle, [&]() {
|
return steam_inventory::GetResultStatus(
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientInventory_GetResultStatus)
|
__func__, resultHandle, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_GetResultStatus)
|
||||||
|
|
||||||
return IClientInventory_GetResultStatus_o(ARGS(resultHandle));
|
return IClientInventory_GetResultStatus_o(ARGS(resultHandle));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
VIRTUAL(bool) IClientInventory_GetResultItems(
|
VIRTUAL(bool) IClientInventory_GetResultItems(
|
||||||
@@ -19,11 +19,10 @@ VIRTUAL(bool) IClientInventory_GetResultItems(
|
|||||||
uint32_t * punOutItemsArraySize // 1st pass: ptr to out array size, 2nd pass: ptr to 0
|
uint32_t * punOutItemsArraySize // 1st pass: ptr to out array size, 2nd pass: ptr to 0
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return steam_inventory::GetResultItems(
|
return steam_inventory::GetResultItems(
|
||||||
__func__, resultHandle, pOutItemsArray, punOutItemsArraySize,
|
__func__, resultHandle, pOutItemsArray, punOutItemsArraySize,
|
||||||
[&]() {
|
[&]() {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientInventory_GetResultItems)
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_GetResultItems)
|
||||||
|
|
||||||
*punOutItemsArraySize = item_count;
|
*punOutItemsArraySize = item_count;
|
||||||
return IClientInventory_GetResultItems_o(
|
return IClientInventory_GetResultItems_o(
|
||||||
@@ -31,7 +30,7 @@ VIRTUAL(bool) IClientInventory_GetResultItems(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
[&](SteamItemDef_t* pItemDefIDs, uint32_t* punItemDefIDsArraySize) {
|
[&](SteamItemDef_t* pItemDefIDs, uint32_t* punItemDefIDsArraySize) {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientInventory_GetItemDefinitionIDs)
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_GetItemDefinitionIDs)
|
||||||
|
|
||||||
return IClientInventory_GetItemDefinitionIDs_o(
|
return IClientInventory_GetItemDefinitionIDs_o(
|
||||||
ARGS(pItemDefIDs, *punItemDefIDsArraySize, punItemDefIDsArraySize)
|
ARGS(pItemDefIDs, *punItemDefIDsArraySize, punItemDefIDsArraySize)
|
||||||
@@ -40,7 +39,6 @@ VIRTUAL(bool) IClientInventory_GetResultItems(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify this function [ ] signature, in-game [ ]
|
|
||||||
VIRTUAL(bool) IClientInventory_GetResultItemProperty(
|
VIRTUAL(bool) IClientInventory_GetResultItemProperty(
|
||||||
PARAMS(
|
PARAMS(
|
||||||
SteamInventoryResult_t resultHandle,
|
SteamInventoryResult_t resultHandle,
|
||||||
@@ -53,7 +51,7 @@ VIRTUAL(bool) IClientInventory_GetResultItemProperty(
|
|||||||
) {
|
) {
|
||||||
return steam_inventory::GetResultItemProperty(
|
return steam_inventory::GetResultItemProperty(
|
||||||
__func__, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut, [&]() {
|
__func__, resultHandle, unItemIndex, pchPropertyName, pchValueBuffer, punValueBufferSizeOut, [&]() {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientInventory_GetResultItemProperty)
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_GetResultItemProperty)
|
||||||
|
|
||||||
*punValueBufferSizeOut = item_count;
|
*punValueBufferSizeOut = item_count;
|
||||||
return IClientInventory_GetResultItemProperty_o(
|
return IClientInventory_GetResultItemProperty_o(
|
||||||
@@ -63,7 +61,6 @@ VIRTUAL(bool) IClientInventory_GetResultItemProperty(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify this function [x] signature, in-game [ ]
|
|
||||||
VIRTUAL(bool) IClientInventory_CheckResultSteamID(
|
VIRTUAL(bool) IClientInventory_CheckResultSteamID(
|
||||||
PARAMS(
|
PARAMS(
|
||||||
SteamInventoryResult_t resultHandle,
|
SteamInventoryResult_t resultHandle,
|
||||||
@@ -71,23 +68,20 @@ VIRTUAL(bool) IClientInventory_CheckResultSteamID(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return steam_inventory::CheckResultSteamID(__func__, resultHandle, steamIDExpected, [&]() {
|
return steam_inventory::CheckResultSteamID(__func__, resultHandle, steamIDExpected, [&]() {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientInventory_CheckResultSteamID)
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_CheckResultSteamID)
|
||||||
|
|
||||||
return IClientInventory_CheckResultSteamID_o(ARGS(resultHandle, steamIDExpected));
|
return IClientInventory_CheckResultSteamID_o(ARGS(resultHandle, steamIDExpected));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Verify this function [x] signature, in-game [ ]
|
|
||||||
VIRTUAL(bool) IClientInventory_GetAllItems(PARAMS(SteamInventoryResult_t* pResultHandle)) {
|
VIRTUAL(bool) IClientInventory_GetAllItems(PARAMS(SteamInventoryResult_t* pResultHandle)) {
|
||||||
return steam_inventory::GetAllItems(__func__, pResultHandle, [&]() {
|
return steam_inventory::GetAllItems(__func__, pResultHandle, [&]() {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientInventory_GetAllItems)
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_GetAllItems)
|
||||||
|
|
||||||
return IClientInventory_GetAllItems_o(ARGS(pResultHandle));
|
return IClientInventory_GetAllItems_o(ARGS(pResultHandle));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify this function [x] signature, in-game [ ]
|
|
||||||
VIRTUAL(bool) IClientInventory_GetItemsByID(
|
VIRTUAL(bool) IClientInventory_GetItemsByID(
|
||||||
PARAMS(
|
PARAMS(
|
||||||
SteamInventoryResult_t* pResultHandle,
|
SteamInventoryResult_t* pResultHandle,
|
||||||
@@ -96,13 +90,12 @@ VIRTUAL(bool) IClientInventory_GetItemsByID(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return steam_inventory::GetItemsByID(__func__, pResultHandle, pInstanceIDs, unCountInstanceIDs, [&]() {
|
return steam_inventory::GetItemsByID(__func__, pResultHandle, pInstanceIDs, unCountInstanceIDs, [&]() {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientInventory_GetItemsByID)
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_GetItemsByID)
|
||||||
|
|
||||||
return IClientInventory_GetItemsByID_o(ARGS(pResultHandle, pInstanceIDs, unCountInstanceIDs));
|
return IClientInventory_GetItemsByID_o(ARGS(pResultHandle, pInstanceIDs, unCountInstanceIDs));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify this function [x] signature, in-game [ ]
|
|
||||||
VIRTUAL(bool) IClientInventory_SerializeResult(
|
VIRTUAL(bool) IClientInventory_SerializeResult(
|
||||||
PARAMS(
|
PARAMS(
|
||||||
SteamInventoryResult_t resultHandle,
|
SteamInventoryResult_t resultHandle,
|
||||||
@@ -112,7 +105,7 @@ VIRTUAL(bool) IClientInventory_SerializeResult(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return steam_inventory::SerializeResult(__func__, resultHandle, pOutBuffer, punOutBufferSize, [&]() {
|
return steam_inventory::SerializeResult(__func__, resultHandle, pOutBuffer, punOutBufferSize, [&]() {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientInventory_SerializeResult)
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_SerializeResult)
|
||||||
|
|
||||||
*punOutBufferSize = buffer_size;
|
*punOutBufferSize = buffer_size;
|
||||||
return IClientInventory_SerializeResult_o(ARGS(resultHandle, pOutBuffer, buffer_size, punOutBufferSize));
|
return IClientInventory_SerializeResult_o(ARGS(resultHandle, pOutBuffer, buffer_size, punOutBufferSize));
|
||||||
@@ -127,7 +120,7 @@ VIRTUAL(bool) IClientInventory_GetItemDefinitionIDs(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return steam_inventory::GetItemDefinitionIDs(__func__, pItemDefIDs, p_array_size, [&]() {
|
return steam_inventory::GetItemDefinitionIDs(__func__, pItemDefIDs, p_array_size, [&]() {
|
||||||
GET_ORIGINAL_VIRTUAL_FUNCTION(IClientInventory_GetItemDefinitionIDs)
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientInventory_GetItemDefinitionIDs)
|
||||||
|
|
||||||
*p_array_size = item_count;
|
*p_array_size = item_count;
|
||||||
return IClientInventory_GetItemDefinitionIDs_o(ARGS(pItemDefIDs, item_count, p_array_size));
|
return IClientInventory_GetItemDefinitionIDs_o(ARGS(pItemDefIDs, item_count, p_array_size));
|
||||||
14
src/store_mode/steamclient/client_user.cpp
Normal file
14
src/store_mode/steamclient/client_user.cpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#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 = store::steamclient::interface_name_to_address_map["IClientUtils"];
|
||||||
|
|
||||||
|
const auto app_id = utils_interface ? IClientUtils_GetAppID(utils_interface, EDX) : 0;
|
||||||
|
|
||||||
|
return steam_apps::IsDlcUnlocked(__func__, app_id, dlc_id, [&]() {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientUser_BIsSubscribedApp)
|
||||||
|
|
||||||
|
return IClientUser_BIsSubscribedApp_o(ARGS(dlc_id));
|
||||||
|
});
|
||||||
|
}
|
||||||
7
src/store_mode/steamclient/client_utils.cpp
Normal file
7
src/store_mode/steamclient/client_utils.cpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include <store_mode/steamclient/steamclient.hpp>
|
||||||
|
|
||||||
|
VIRTUAL(AppId_t) IClientUtils_GetAppID(PARAMS()) {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(IClientUtils_GetAppID)
|
||||||
|
|
||||||
|
return IClientUtils_GetAppID_o(ARGS());
|
||||||
|
}
|
||||||
456
src/store_mode/steamclient/steamclient.cpp
Normal file
456
src/store_mode/steamclient/steamclient.cpp
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
#include <store_mode/steamclient/steamclient.hpp>
|
||||||
|
#include <store_mode/store.hpp>
|
||||||
|
#include <koalabox/hook.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
|
#include <koalabox/util.hpp>
|
||||||
|
|
||||||
|
#include <Zydis/Zydis.h>
|
||||||
|
#include <Zydis/DecoderTypes.h>
|
||||||
|
|
||||||
|
namespace store::steamclient {
|
||||||
|
using namespace koalabox;
|
||||||
|
|
||||||
|
Map<String, void*> interface_name_to_address_map; // NOLINT(cert-err58-cpp)
|
||||||
|
|
||||||
|
struct InstructionContext {
|
||||||
|
std::optional<ZydisRegister> base_register;
|
||||||
|
std::optional<String> function_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
// map<interface name, map<function name, function ordinal>>
|
||||||
|
Map<String, Map<String, uint32_t>> ordinal_map; // NOLINT(cert-err58-cpp)
|
||||||
|
|
||||||
|
const auto MAX_INSTRUCTION_SIZE = 15;
|
||||||
|
|
||||||
|
ZydisDecoder decoder = {};
|
||||||
|
ZydisFormatter formatter = {};
|
||||||
|
|
||||||
|
void construct_ordinal_map( // NOLINT(misc-no-recursion)
|
||||||
|
const String& target_interface,
|
||||||
|
Map<String, uint32_t>& function_name_to_ordinal_map,
|
||||||
|
uintptr_t start_address
|
||||||
|
);
|
||||||
|
|
||||||
|
#define CONSTRUCT_ORDINAL_MAP(INTERFACE) \
|
||||||
|
construct_ordinal_map(#INTERFACE, ordinal_map[#INTERFACE], function_selector_address);
|
||||||
|
|
||||||
|
#define HOOK_FUNCTION(INTERFACE, FUNC) hook::swap_virtual_func_or_throw( \
|
||||||
|
globals::address_map, \
|
||||||
|
interface, \
|
||||||
|
#INTERFACE"_"#FUNC, \
|
||||||
|
ordinal_map[#INTERFACE][#FUNC], \
|
||||||
|
reinterpret_cast<uintptr_t>(INTERFACE##_##FUNC) \
|
||||||
|
);
|
||||||
|
|
||||||
|
#define SELECTOR_IMPLEMENTATION(INTERFACE, FUNC_BODY) \
|
||||||
|
DLL_EXPORT(void) INTERFACE##_Selector( \
|
||||||
|
void* interface, \
|
||||||
|
void* arg2, \
|
||||||
|
void* arg3, \
|
||||||
|
void* arg4 \
|
||||||
|
) { \
|
||||||
|
CALL_ONCE({ \
|
||||||
|
interface_name_to_address_map[#INTERFACE] = interface; \
|
||||||
|
[&]()FUNC_BODY(); \
|
||||||
|
}) \
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(INTERFACE##_Selector) \
|
||||||
|
INTERFACE##_Selector_o(interface, arg2, arg3, arg4); \
|
||||||
|
}
|
||||||
|
|
||||||
|
SELECTOR_IMPLEMENTATION(IClientAppManager, {
|
||||||
|
HOOK_FUNCTION(IClientAppManager, IsAppDlcInstalled)
|
||||||
|
})
|
||||||
|
|
||||||
|
SELECTOR_IMPLEMENTATION(IClientApps, {
|
||||||
|
HOOK_FUNCTION(IClientApps, GetDLCCount)
|
||||||
|
HOOK_FUNCTION(IClientApps, BGetDLCDataByIndex)
|
||||||
|
})
|
||||||
|
|
||||||
|
SELECTOR_IMPLEMENTATION(IClientInventory, {
|
||||||
|
HOOK_FUNCTION(IClientInventory, GetResultStatus)
|
||||||
|
HOOK_FUNCTION(IClientInventory, GetResultItems)
|
||||||
|
HOOK_FUNCTION(IClientInventory, GetResultItemProperty)
|
||||||
|
HOOK_FUNCTION(IClientInventory, CheckResultSteamID)
|
||||||
|
HOOK_FUNCTION(IClientInventory, GetAllItems)
|
||||||
|
HOOK_FUNCTION(IClientInventory, GetItemsByID)
|
||||||
|
HOOK_FUNCTION(IClientInventory, SerializeResult)
|
||||||
|
HOOK_FUNCTION(IClientInventory, GetItemDefinitionIDs)
|
||||||
|
})
|
||||||
|
|
||||||
|
SELECTOR_IMPLEMENTATION(IClientUser, {
|
||||||
|
HOOK_FUNCTION(IClientUser, BIsSubscribedApp)
|
||||||
|
})
|
||||||
|
|
||||||
|
SELECTOR_IMPLEMENTATION(IClientUtils, {
|
||||||
|
HOOK_FUNCTION(IClientUtils, GetAppID)
|
||||||
|
})
|
||||||
|
|
||||||
|
#define DETOUR_SELECTOR(INTERFACE) \
|
||||||
|
if(interface_name == #INTERFACE){ \
|
||||||
|
CONSTRUCT_ORDINAL_MAP(INTERFACE) \
|
||||||
|
DETOUR_ADDRESS(INTERFACE##_Selector, function_selector_address) \
|
||||||
|
}
|
||||||
|
|
||||||
|
void detour_interface_selector(const String& interface_name, uintptr_t function_selector_address) {
|
||||||
|
LOG_DEBUG("Detected interface: '{}'", interface_name)
|
||||||
|
|
||||||
|
DETOUR_SELECTOR(IClientAppManager)
|
||||||
|
DETOUR_SELECTOR(IClientApps)
|
||||||
|
DETOUR_SELECTOR(IClientInventory)
|
||||||
|
DETOUR_SELECTOR(IClientUser)
|
||||||
|
DETOUR_SELECTOR(IClientUtils)
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t get_absolute_address(ZydisDecodedInstruction instruction, uintptr_t address) {
|
||||||
|
const auto operand = instruction.operands[0];
|
||||||
|
|
||||||
|
if (operand.imm.is_relative) {
|
||||||
|
ZyanU64 absolute_address;
|
||||||
|
ZydisCalcAbsoluteAddress(&instruction, &operand, address, &absolute_address);
|
||||||
|
|
||||||
|
return absolute_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uintptr_t) operand.imm.value.u;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_push_immediate(const ZydisDecodedInstruction& instruction) {
|
||||||
|
const auto& operand = instruction.operands[0];
|
||||||
|
|
||||||
|
return instruction.mnemonic == ZYDIS_MNEMONIC_PUSH &&
|
||||||
|
operand.type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
||||||
|
operand.visibility == ZYDIS_OPERAND_VISIBILITY_EXPLICIT &&
|
||||||
|
operand.encoding == ZYDIS_OPERAND_ENCODING_SIMM16_32_32;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<String> get_string_argument(const ZydisDecodedInstruction& instruction) {
|
||||||
|
const auto* name_address = reinterpret_cast<char*>(instruction.operands[0].imm.value.u);
|
||||||
|
if (util::is_valid_pointer(name_address)) {
|
||||||
|
return name_address;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<String> get_instruction_string(
|
||||||
|
const ZydisDecodedInstruction& instruction,
|
||||||
|
const uintptr_t address
|
||||||
|
) {
|
||||||
|
const auto buffer_size = 64;
|
||||||
|
char buffer[buffer_size] = {};
|
||||||
|
|
||||||
|
if (ZYAN_SUCCESS(
|
||||||
|
ZydisFormatterFormatInstruction(
|
||||||
|
&formatter,
|
||||||
|
&instruction,
|
||||||
|
buffer,
|
||||||
|
buffer_size,
|
||||||
|
address
|
||||||
|
)
|
||||||
|
)) {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<String> find_interface_name(uintptr_t selector_address) {
|
||||||
|
auto current_address = selector_address;
|
||||||
|
ZydisDecodedInstruction instruction{};
|
||||||
|
while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(
|
||||||
|
&decoder,
|
||||||
|
(void*) current_address,
|
||||||
|
MAX_INSTRUCTION_SIZE,
|
||||||
|
&instruction
|
||||||
|
))) {
|
||||||
|
const auto debug_str = get_instruction_string(instruction, current_address);
|
||||||
|
|
||||||
|
if (is_push_immediate(instruction)) {
|
||||||
|
auto string_opt = get_string_argument(instruction);
|
||||||
|
|
||||||
|
if (string_opt && string_opt->starts_with("IClient")) {
|
||||||
|
return string_opt;
|
||||||
|
}
|
||||||
|
} else if (instruction.mnemonic == ZYDIS_MNEMONIC_RET) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_address += instruction.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOG_WARN("Failed to find any interface names at {}", (void*) selector_address);
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively walks through the code, until a return instruction is reached.
|
||||||
|
* Recursion occurs whenever a jump/branch is encountered.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
void visit_code( // NOLINT(misc-no-recursion)
|
||||||
|
Set<uintptr_t>& visited_addresses,
|
||||||
|
uintptr_t start_address,
|
||||||
|
T context,
|
||||||
|
const Function<bool(
|
||||||
|
const ZydisDecodedInstruction& instruction,
|
||||||
|
const ZydisDecodedOperand& operand,
|
||||||
|
const uintptr_t& current_address,
|
||||||
|
T& context,
|
||||||
|
const std::list<ZydisDecodedInstruction>& instruction_list
|
||||||
|
)>& callback
|
||||||
|
) {
|
||||||
|
LOG_TRACE("{} -> start_address: {}", __func__, (void*) start_address)
|
||||||
|
|
||||||
|
if (visited_addresses.contains(start_address)) {
|
||||||
|
LOG_TRACE("Breaking recursion due to visited address")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto current_address = start_address;
|
||||||
|
std::list instruction_list{ZydisDecodedInstruction{}};
|
||||||
|
|
||||||
|
ZydisDecodedInstruction instruction{};
|
||||||
|
while (
|
||||||
|
ZYAN_SUCCESS(
|
||||||
|
ZydisDecoderDecodeBuffer(
|
||||||
|
&decoder,
|
||||||
|
(void*) current_address,
|
||||||
|
MAX_INSTRUCTION_SIZE,
|
||||||
|
&instruction
|
||||||
|
)
|
||||||
|
)) {
|
||||||
|
visited_addresses.insert(current_address);
|
||||||
|
LOG_TRACE(
|
||||||
|
"{} -> visiting {} │ {}",
|
||||||
|
__func__, (void*) current_address, *get_instruction_string(instruction, current_address)
|
||||||
|
)
|
||||||
|
|
||||||
|
const auto operand = instruction.operands[0];
|
||||||
|
|
||||||
|
const auto should_return = callback(instruction, operand, current_address, context, instruction_list);
|
||||||
|
|
||||||
|
if (should_return) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instruction.meta.category == ZYDIS_CATEGORY_COND_BR) {
|
||||||
|
const auto jump_taken_destination = get_absolute_address(instruction, current_address);
|
||||||
|
const auto jump_not_taken_destination = current_address + instruction.length;
|
||||||
|
|
||||||
|
visit_code(visited_addresses, jump_taken_destination, context, callback);
|
||||||
|
visit_code(visited_addresses, jump_not_taken_destination, context, callback);
|
||||||
|
|
||||||
|
LOG_TRACE("{} -> Breaking recursion due to a conditional branch", __func__)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instruction.mnemonic == ZYDIS_MNEMONIC_JMP && operand.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
|
||||||
|
const auto jump_destination = get_absolute_address(instruction, current_address);
|
||||||
|
|
||||||
|
visit_code(visited_addresses, jump_destination, context, callback);
|
||||||
|
|
||||||
|
LOG_TRACE("{} -> Breaking recursion due to an unconditional jump", __func__)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instruction.mnemonic == ZYDIS_MNEMONIC_JMP &&
|
||||||
|
operand.type == ZYDIS_OPERAND_TYPE_MEMORY &&
|
||||||
|
operand.mem.scale == sizeof(uintptr_t) &&
|
||||||
|
operand.mem.disp.has_displacement) {
|
||||||
|
// Special handling for jump tables. Guaranteed to be present in the interface selector.
|
||||||
|
const auto* table = (uintptr_t*) operand.mem.disp.value;
|
||||||
|
|
||||||
|
const auto* table_entry = table;
|
||||||
|
while (util::is_valid_pointer((void*) *table_entry)) {
|
||||||
|
visit_code(visited_addresses, *table_entry, context, callback);
|
||||||
|
|
||||||
|
table_entry++;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_TRACE("{} -> Breaking recursion due to a jump table", __func__)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instruction.mnemonic == ZYDIS_MNEMONIC_RET) {
|
||||||
|
LOG_TRACE("{} -> Breaking recursion due to return instruction", __func__)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// We push items to the front so that it becomes easy to iterate over instructions
|
||||||
|
// in reverse order of addition.
|
||||||
|
instruction_list.push_front(instruction);
|
||||||
|
current_address += instruction.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void construct_ordinal_map( // NOLINT(misc-no-recursion)
|
||||||
|
const String& target_interface,
|
||||||
|
Map<String, uint32_t>& function_name_to_ordinal_map,
|
||||||
|
uintptr_t start_address
|
||||||
|
) {
|
||||||
|
Set<uintptr_t> visited_addresses;
|
||||||
|
visit_code<InstructionContext>(visited_addresses, start_address, {}, [&](
|
||||||
|
const ZydisDecodedInstruction& instruction,
|
||||||
|
const ZydisDecodedOperand& operand,
|
||||||
|
const auto&,
|
||||||
|
InstructionContext& context,
|
||||||
|
const std::list<ZydisDecodedInstruction>& instruction_list
|
||||||
|
) {
|
||||||
|
if (context.function_name && function_name_to_ordinal_map.contains(*context.function_name)) {
|
||||||
|
// Avoid duplicate work
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& last_instruction = instruction_list.front();
|
||||||
|
|
||||||
|
const auto is_mov_base_esp = instruction.mnemonic == ZYDIS_MNEMONIC_MOV &&
|
||||||
|
instruction.operand_count == 2 &&
|
||||||
|
instruction.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
instruction.operands[1].reg.value == ZYDIS_REGISTER_ESP;
|
||||||
|
|
||||||
|
if (!context.base_register && is_mov_base_esp) {
|
||||||
|
// Save base register
|
||||||
|
context.base_register = instruction.operands[0].reg.value;
|
||||||
|
} else if (is_push_immediate(last_instruction) &&
|
||||||
|
is_push_immediate(instruction) &&
|
||||||
|
!context.function_name) {
|
||||||
|
// The very first 2 consecutive pushes indicate interface and function names.
|
||||||
|
// However, subsequent pushes may contain irrelevant strings.
|
||||||
|
const auto push_string_1 = get_string_argument(last_instruction);
|
||||||
|
const auto push_string_2 = get_string_argument(instruction);
|
||||||
|
|
||||||
|
if (push_string_1 && push_string_2) {
|
||||||
|
if (*push_string_1 == target_interface) {
|
||||||
|
context.function_name = push_string_2;
|
||||||
|
} else if (*push_string_2 == target_interface) {
|
||||||
|
context.function_name = push_string_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.function_name && function_name_to_ordinal_map.contains(*context.function_name)) {
|
||||||
|
// Bail early to avoid duplicate work
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (instruction.mnemonic == ZYDIS_MNEMONIC_CALL) {
|
||||||
|
// On call instructions we should extract the ordinal
|
||||||
|
|
||||||
|
if (context.base_register && context.function_name) {
|
||||||
|
const auto& base_register = *(context.base_register);
|
||||||
|
const auto& function_name = *(context.function_name);
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<uint32_t> offset;
|
||||||
|
|
||||||
|
auto last_destination_reg = ZYDIS_REGISTER_NONE;
|
||||||
|
bool is_derived_from_base_reg = false;
|
||||||
|
|
||||||
|
// Sometimes the offset is present in the call instruction itself,
|
||||||
|
// hence we can immediately obtain it.
|
||||||
|
if (operand.type == ZYDIS_OPERAND_TYPE_MEMORY && operand.mem.base != ZYDIS_REGISTER_NONE) {
|
||||||
|
offset = static_cast<uint32_t>(operand.mem.disp.value);
|
||||||
|
last_destination_reg = operand.mem.base;
|
||||||
|
} else if (operand.type == ZYDIS_OPERAND_TYPE_REGISTER) {
|
||||||
|
last_destination_reg = operand.reg.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& previous_instruction: instruction_list) {
|
||||||
|
const auto& destination_operand = previous_instruction.operands[0];
|
||||||
|
const auto& source_operand = previous_instruction.operands[1];
|
||||||
|
|
||||||
|
// Extract offset if necessary
|
||||||
|
if (previous_instruction.mnemonic == ZYDIS_MNEMONIC_MOV &&
|
||||||
|
previous_instruction.operand_count == 2 &&
|
||||||
|
destination_operand.reg.value == last_destination_reg &&
|
||||||
|
source_operand.type == ZYDIS_OPERAND_TYPE_MEMORY) {
|
||||||
|
|
||||||
|
const auto source_mem = source_operand.mem;
|
||||||
|
if (source_mem.base == base_register &&
|
||||||
|
source_mem.disp.has_displacement &&
|
||||||
|
source_mem.disp.value == 8) {
|
||||||
|
// We have verified that the chain eventually leads up to the base register.
|
||||||
|
// Hence, we can conclude that the offset is valid.
|
||||||
|
is_derived_from_base_reg = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, keep going through the chain
|
||||||
|
last_destination_reg = source_mem.base;
|
||||||
|
|
||||||
|
if (!offset) {
|
||||||
|
offset = static_cast<uint32_t>(source_mem.disp.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset && is_derived_from_base_reg) {
|
||||||
|
const auto ordinal = *offset / sizeof(uintptr_t);
|
||||||
|
|
||||||
|
LOG_DEBUG("Found function ordinal {}::{}@{}", target_interface, function_name, ordinal)
|
||||||
|
|
||||||
|
function_name_to_ordinal_map[function_name] = ordinal;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_interface_selector( // NOLINT(misc-no-recursion)
|
||||||
|
const uintptr_t start_address,
|
||||||
|
Set<uintptr_t>& visited_addresses
|
||||||
|
) {
|
||||||
|
visit_code<nullptr_t>(visited_addresses, start_address, nullptr, [](
|
||||||
|
const ZydisDecodedInstruction& instruction,
|
||||||
|
const ZydisDecodedOperand& operand,
|
||||||
|
const auto& current_address,
|
||||||
|
auto,
|
||||||
|
const auto&
|
||||||
|
) {
|
||||||
|
if (instruction.mnemonic == ZYDIS_MNEMONIC_CALL && operand.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) {
|
||||||
|
LOG_TRACE("Found call instruction at {}", (void*) current_address)
|
||||||
|
|
||||||
|
const auto function_selector_address = get_absolute_address(instruction, current_address);
|
||||||
|
|
||||||
|
const auto interface_name_opt = find_interface_name(function_selector_address);
|
||||||
|
|
||||||
|
if (interface_name_opt) {
|
||||||
|
const auto& interface_name = *interface_name_opt;
|
||||||
|
|
||||||
|
detour_interface_selector(interface_name, function_selector_address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_client_engine(uintptr_t interface) {
|
||||||
|
const auto* steam_client_internal = ((uintptr_t***) interface)[
|
||||||
|
store::config.client_engine_steam_client_internal_ordinal
|
||||||
|
];
|
||||||
|
const auto interface_selector_address = (*steam_client_internal)[
|
||||||
|
store::config.steam_client_internal_interface_selector_ordinal
|
||||||
|
];
|
||||||
|
|
||||||
|
LOG_DEBUG("Found interface selector at: {}", (void*) interface_selector_address)
|
||||||
|
|
||||||
|
if (ZYAN_FAILED(ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LEGACY_32, ZYDIS_ADDRESS_WIDTH_32))) {
|
||||||
|
LOG_ERROR("Failed to initialize zydis decoder")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ZYAN_FAILED(ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL))) {
|
||||||
|
LOG_ERROR("Failed to initialize zydis formatter")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<uintptr_t> visited_addresses;
|
||||||
|
process_interface_selector(interface_selector_address, visited_addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
39
src/store_mode/steamclient/steamclient.hpp
Normal file
39
src/store_mode/steamclient/steamclient.hpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
// IClientAppManager
|
||||||
|
VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(PARAMS(AppId_t, AppId_t));
|
||||||
|
|
||||||
|
// IClientApps
|
||||||
|
VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t));
|
||||||
|
VIRTUAL(bool) IClientApps_BGetDLCDataByIndex(PARAMS(AppId_t, int, AppId_t*, bool*, char*, int));
|
||||||
|
|
||||||
|
// IClientInventory
|
||||||
|
VIRTUAL(EResult) IClientInventory_GetResultStatus(PARAMS(SteamInventoryResult_t));
|
||||||
|
VIRTUAL(bool) IClientInventory_GetResultItems(
|
||||||
|
PARAMS(SteamInventoryResult_t, SteamItemDetails_t*, uint32_t, uint32_t *)
|
||||||
|
);
|
||||||
|
VIRTUAL(bool) IClientInventory_GetResultItemProperty(
|
||||||
|
PARAMS(SteamInventoryResult_t, uint32_t, const char*, char*, uint32_t, uint32_t*)
|
||||||
|
);
|
||||||
|
VIRTUAL(bool) IClientInventory_CheckResultSteamID(PARAMS(SteamInventoryResult_t, CSteamID));
|
||||||
|
VIRTUAL(bool) IClientInventory_GetAllItems(PARAMS(SteamInventoryResult_t*));
|
||||||
|
VIRTUAL(bool) IClientInventory_GetItemsByID(PARAMS(SteamInventoryResult_t*, const SteamItemInstanceID_t*, uint32_t));
|
||||||
|
VIRTUAL(bool) IClientInventory_SerializeResult(PARAMS(SteamInventoryResult_t, void*, uint32_t, uint32_t *));
|
||||||
|
VIRTUAL(bool) IClientInventory_GetItemDefinitionIDs(PARAMS(SteamItemDef_t*, uint32_t, uint32_t *));
|
||||||
|
|
||||||
|
// IClientUser
|
||||||
|
VIRTUAL(bool) IClientUser_BIsSubscribedApp(PARAMS(AppId_t));
|
||||||
|
|
||||||
|
// IClientUtils
|
||||||
|
VIRTUAL(AppId_t) IClientUtils_GetAppID(PARAMS());
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
void process_client_engine(uintptr_t interface);
|
||||||
|
|
||||||
|
}
|
||||||
137
src/store_mode/store.cpp
Normal file
137
src/store_mode/store.cpp
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#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 <build_config.h>
|
||||||
|
#include <koalabox/dll_monitor.hpp>
|
||||||
|
#include <koalabox/logger.hpp>
|
||||||
|
#include <koalabox/ipc.hpp>
|
||||||
|
#include <common/steamclient_exports.hpp>
|
||||||
|
|
||||||
|
namespace store {
|
||||||
|
|
||||||
|
StoreConfig config; // NOLINT(cert-err58-cpp)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A string representing the source of the config.
|
||||||
|
*/
|
||||||
|
void init_store_config() {
|
||||||
|
const auto print_source = [](const String& 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.store_config;
|
||||||
|
if (!kg_config.is_null()) {
|
||||||
|
try {
|
||||||
|
config = kg_config.get<decltype(config)>();
|
||||||
|
|
||||||
|
print_source("local config override");
|
||||||
|
return;
|
||||||
|
} catch (const Exception& ex) {
|
||||||
|
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 = store_cache::get_store_config().value();
|
||||||
|
|
||||||
|
print_source("disk cache");
|
||||||
|
} catch (const Exception& ex) {
|
||||||
|
LOG_ERROR("Failed to get cached store_mode config: {}", ex.what())
|
||||||
|
|
||||||
|
print_source("default config bundled in the binary");
|
||||||
|
|
||||||
|
// Fallback on the default config, to continue execution immediately.
|
||||||
|
config = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, fetch the remote config from GitHub, and inform user about the need to restart Steam,
|
||||||
|
// if a new config has been fetched
|
||||||
|
NEW_THREAD({
|
||||||
|
try {
|
||||||
|
const auto github_config_opt = api::fetch_store_config();
|
||||||
|
if (!github_config_opt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto github_config = *github_config_opt;
|
||||||
|
|
||||||
|
store_cache::save_store_config(github_config);
|
||||||
|
|
||||||
|
if (github_config == config) {
|
||||||
|
LOG_DEBUG("Fetched Store config is equal to existing config")
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("Fetched a new Store config")
|
||||||
|
|
||||||
|
::MessageBox(
|
||||||
|
nullptr,
|
||||||
|
TEXT(
|
||||||
|
"SmokeAPI has downloaded an updated config for Store mode. "
|
||||||
|
"Please restart Steam in order to apply the new Store config. "
|
||||||
|
),
|
||||||
|
TEXT("SmokeAPI - Store"),
|
||||||
|
MB_SETFOREGROUND | MB_ICONINFORMATION | MB_OK
|
||||||
|
);
|
||||||
|
} catch (const Exception& ex) {
|
||||||
|
LOG_ERROR("Failed to get remote store_mode config: {}", ex.what())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_store_mode() {
|
||||||
|
init_store_config();
|
||||||
|
|
||||||
|
koalabox::dll_monitor::init_listener(
|
||||||
|
{VSTDLIB_DLL, STEAMCLIENT_DLL}, [](const HMODULE& module_handle, const String& name) {
|
||||||
|
try {
|
||||||
|
if (name < equals > VSTDLIB_DLL) {
|
||||||
|
// VStdLib DLL handles Family Sharing functions
|
||||||
|
|
||||||
|
globals::vstdlib_module = module_handle;
|
||||||
|
|
||||||
|
if (smoke_api::config::instance.unlock_family_sharing) {
|
||||||
|
DETOUR_VSTDLIB(Coroutine_Create)
|
||||||
|
}
|
||||||
|
} else if (name < equals > STEAMCLIENT_DLL) {
|
||||||
|
// SteamClient DLL handles unlocking functions
|
||||||
|
|
||||||
|
globals::steamclient_module = module_handle;
|
||||||
|
|
||||||
|
DETOUR_STEAMCLIENT(CreateInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globals::vstdlib_module != nullptr && globals::steamclient_module != nullptr) {
|
||||||
|
koalabox::dll_monitor::shutdown_listener();
|
||||||
|
}
|
||||||
|
} catch (const Exception& ex) {
|
||||||
|
LOG_ERROR(
|
||||||
|
"Error listening to DLL load events. Module: '{}', Message: {}",
|
||||||
|
name, ex.what()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NEW_THREAD({
|
||||||
|
koalabox::ipc::init_pipe_server("smokeapi.store.steam", [](const koalabox::ipc::Request& request) {
|
||||||
|
koalabox::ipc::Response response;
|
||||||
|
|
||||||
|
if (request.name < equals > "config::reload") {
|
||||||
|
smoke_api::config::ReloadConfig();
|
||||||
|
response.success = true;
|
||||||
|
} else {
|
||||||
|
response.success = false;
|
||||||
|
response.data["error_message"] = "Invalid request name: " + request.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/store_mode/store.hpp
Normal file
36
src/store_mode/store.hpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <core/types.hpp>
|
||||||
|
|
||||||
|
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 StoreConfig {
|
||||||
|
public:
|
||||||
|
uint32_t client_engine_steam_client_internal_ordinal = 12;
|
||||||
|
uint32_t steam_client_internal_interface_selector_ordinal = 18;
|
||||||
|
uint32_t vstdlib_callback_address_offset = 20;
|
||||||
|
uint32_t vstdlib_callback_data_offset = 0;
|
||||||
|
uint32_t vstdlib_callback_interceptor_address_offset = 1;
|
||||||
|
uint32_t vstdlib_callback_name_offset = 4;
|
||||||
|
|
||||||
|
// We do not use *_WITH_DEFAULT macro to ensure that overriding
|
||||||
|
// the store_mode config requires definition of all keys
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(
|
||||||
|
StoreConfig, // NOLINT(misc-const-correctness)
|
||||||
|
client_engine_steam_client_internal_ordinal,
|
||||||
|
steam_client_internal_interface_selector_ordinal,
|
||||||
|
vstdlib_callback_address_offset,
|
||||||
|
vstdlib_callback_data_offset,
|
||||||
|
vstdlib_callback_interceptor_address_offset,
|
||||||
|
vstdlib_callback_name_offset
|
||||||
|
)
|
||||||
|
|
||||||
|
bool operator==(const StoreConfig& other) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern StoreConfig config;
|
||||||
|
|
||||||
|
void init_store_mode();
|
||||||
|
|
||||||
|
}
|
||||||
19
src/store_mode/store_api.cpp
Normal file
19
src/store_mode/store_api.cpp
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
src/store_mode/store_api.hpp
Normal file
10
src/store_mode/store_api.hpp
Normal file
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
32
src/store_mode/store_cache.cpp
Normal file
32
src/store_mode/store_cache.cpp
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
src/store_mode/store_cache.hpp
Normal file
11
src/store_mode/store_cache.hpp
Normal file
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
64
src/store_mode/vstdlib/vstdlib.cpp
Normal file
64
src/store_mode/vstdlib/vstdlib.cpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include <store_mode/vstdlib/vstdlib.hpp>
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
LOG_DEBUG("{}(this={}, arg={})", __func__, THIS, arg)
|
||||||
|
ARGS();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* const data = (CoroutineData*) THIS;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
DETOUR_ADDRESS(SharedLicensesLockStatus, data->get_callback_data()->get_callback_address())
|
||||||
|
lock_status_hooked = true;
|
||||||
|
} else if (name == "SharedLibraryStopPlaying" && !stop_playing_hooked) {
|
||||||
|
DETOUR_ADDRESS(SharedLibraryStopPlaying, data->get_callback_data()->get_callback_address())
|
||||||
|
stop_playing_hooked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initially, callback data passed into this function is not complete,
|
||||||
|
* hence we must hook an interface method that sets the callback name.
|
||||||
|
*/
|
||||||
|
DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, CoroutineData* data) {
|
||||||
|
GET_ORIGINAL_HOOKED_FUNCTION(Coroutine_Create)
|
||||||
|
|
||||||
|
const auto result = Coroutine_Create_o(callback_address, data);
|
||||||
|
|
||||||
|
// Coroutine callback appears to be always the same
|
||||||
|
CALL_ONCE({
|
||||||
|
LOG_DEBUG("Coroutine_Create -> callback: {}, data: {}", callback_address, fmt::ptr(data));
|
||||||
|
|
||||||
|
DETOUR_ADDRESS(
|
||||||
|
VStdLib_Callback_Interceptor,
|
||||||
|
data->get_callback_data()->get_callback_intercept_address()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
27
src/store_mode/vstdlib/vstdlib.hpp
Normal file
27
src/store_mode/vstdlib/vstdlib.hpp
Normal file
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user