mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-09-30 14:04:55 -07:00
sdk: add get_module_info, get_plugin_directory, d3d9 callbacks (#908)
## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change To make plugin development easier, add `get_module_info` and `get_plugin_directory` to deal with common tasks for hooking DLLs and reading INI files. Also, add the ability to register for d3d9 present callbacks via `register_d3d9`, which makes it significantly easier for plugins to draw things on screen. ## Testing See sample code.
This commit is contained in:
@@ -719,6 +719,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
reader/crypt.cpp
|
reader/crypt.cpp
|
||||||
|
|
||||||
# sdk
|
# sdk
|
||||||
|
sdk/d3d9.cpp
|
||||||
|
sdk/modules.cpp
|
||||||
sdk/sdk.cpp
|
sdk/sdk.cpp
|
||||||
|
|
||||||
# stubs
|
# stubs
|
||||||
@@ -1047,8 +1049,16 @@ if(NOT MSVC)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# sdk_sample_v0_cpp.dll (64 bit)
|
# sdk_sample_v0_cpp.dll (64 bit)
|
||||||
set(SOURCE_FILES sdk/sample/v0/cpp/v0_cpp.cpp)
|
set(SOURCE_FILES
|
||||||
|
sdk/sample/v0/cpp/v0_cpp.cpp
|
||||||
|
sdk/sample/v0/cpp/v0_cpp_imgui.cpp
|
||||||
|
external/imgui/imgui.cpp
|
||||||
|
external/imgui/imgui_draw.cpp
|
||||||
|
external/imgui/imgui_tables.cpp
|
||||||
|
external/imgui/imgui_widgets.cpp
|
||||||
|
external/imgui/backends/imgui_impl_dx9.cpp)
|
||||||
add_library(spicetools_sdk_sample_v0_cpp_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/cpp/v0_cpp.def)
|
add_library(spicetools_sdk_sample_v0_cpp_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/cpp/v0_cpp.def)
|
||||||
|
target_link_libraries(spicetools_sdk_sample_v0_cpp_64 PRIVATE imm32)
|
||||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES PREFIX "")
|
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES PREFIX "")
|
||||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_cpp")
|
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_cpp")
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "launcher/shutdown.h"
|
#include "launcher/shutdown.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
|
#include "sdk/d3d9.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/deferlog.h"
|
#include "util/deferlog.h"
|
||||||
#include "util/flags_helper.h"
|
#include "util/flags_helper.h"
|
||||||
@@ -1511,6 +1512,8 @@ void graphics_d3d9_on_present(
|
|||||||
device->EndScene();
|
device->EndScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sdk::d3d9::draw(hFocusWindow, device);
|
||||||
|
|
||||||
// after the overlay render so the screenshot includes toasts / menus
|
// after the overlay render so the screenshot includes toasts / menus
|
||||||
if (GRAPHICS_SCREENSHOT_INCLUDE_OVERLAY) {
|
if (GRAPHICS_SCREENSHOT_INCLUDE_OVERLAY) {
|
||||||
graphics_d3d9_process_screenshot(device, wrapped_device);
|
graphics_d3d9_process_screenshot(device, wrapped_device);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "games/gitadora/gitadora.h"
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
|
#include "sdk/d3d9.h"
|
||||||
#include "util/flags_helper.h"
|
#include "util/flags_helper.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "cfg/screen_resize.h"
|
#include "cfg/screen_resize.h"
|
||||||
@@ -136,6 +137,7 @@ ULONG STDMETHODCALLTYPE WrappedIDirect3DDevice9::Release() {
|
|||||||
|
|
||||||
// release owned objects if there are no more references
|
// release owned objects if there are no more references
|
||||||
if (local_refs == 0) {
|
if (local_refs == 0) {
|
||||||
|
sdk::d3d9::destroy(this->pReal);
|
||||||
if (this->main_swapchain) {
|
if (this->main_swapchain) {
|
||||||
this->main_swapchain->Release();
|
this->main_swapchain->Release();
|
||||||
this->main_swapchain = nullptr;
|
this->main_swapchain = nullptr;
|
||||||
@@ -632,6 +634,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Reset(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sdk::d3d9::invalidate(pReal);
|
||||||
|
|
||||||
// reset overlay
|
// reset overlay
|
||||||
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal)) {
|
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal)) {
|
||||||
overlay::OVERLAY->reset_invalidate();
|
overlay::OVERLAY->reset_invalidate();
|
||||||
@@ -643,6 +647,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Reset(
|
|||||||
d3d9_readback::discard_snapshot_targets(pReal);
|
d3d9_readback::discard_snapshot_targets(pReal);
|
||||||
|
|
||||||
HRESULT res = pReal->Reset(pPresentationParameters);
|
HRESULT res = pReal->Reset(pPresentationParameters);
|
||||||
|
sdk::d3d9::reset_complete(pReal, SUCCEEDED(res));
|
||||||
|
|
||||||
// recreate overlay
|
// recreate overlay
|
||||||
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal) && SUCCEEDED(res)) {
|
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal) && SUCCEEDED(res)) {
|
||||||
@@ -673,7 +678,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Present(
|
|||||||
gfdm_small_head.compose(this);
|
gfdm_small_head.compose(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_RESULT(pReal->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion));
|
const HRESULT result = pReal->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
|
||||||
|
sdk::d3d9::present_complete(pReal, result);
|
||||||
|
CHECK_RESULT(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetBackBuffer(
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetBackBuffer(
|
||||||
@@ -2088,8 +2095,10 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::PresentEx(
|
|||||||
|
|
||||||
graphics_d3d9_on_present(hFocusWindow, pReal, this);
|
graphics_d3d9_on_present(hFocusWindow, pReal, this);
|
||||||
|
|
||||||
CHECK_RESULT(static_cast<IDirect3DDevice9Ex *>(pReal)->PresentEx(
|
const HRESULT result = static_cast<IDirect3DDevice9Ex *>(pReal)->PresentEx(
|
||||||
pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags));
|
pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags);
|
||||||
|
sdk::d3d9::present_complete(pReal, result);
|
||||||
|
CHECK_RESULT(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetGPUThreadPriority(
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetGPUThreadPriority(
|
||||||
@@ -2336,6 +2345,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sdk::d3d9::invalidate(pReal);
|
||||||
|
|
||||||
// reset overlay
|
// reset overlay
|
||||||
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal)) {
|
if (overlay::OVERLAY && overlay::OVERLAY->uses_device(pReal)) {
|
||||||
overlay::OVERLAY->reset_invalidate();
|
overlay::OVERLAY->reset_invalidate();
|
||||||
@@ -2350,6 +2361,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
|
|||||||
gfdm_parameters.presentation_parameters,
|
gfdm_parameters.presentation_parameters,
|
||||||
gfdm_parameters.fullscreen_display_modes);
|
gfdm_parameters.fullscreen_display_modes);
|
||||||
|
|
||||||
|
sdk::d3d9::reset_complete(pReal, SUCCEEDED(res));
|
||||||
|
|
||||||
if (is_gfdm_two_head_exclusive()
|
if (is_gfdm_two_head_exclusive()
|
||||||
&& SUCCEEDED(res)
|
&& SUCCEEDED(res)
|
||||||
&& gfdm_parameters.recovery_candidate)
|
&& gfdm_parameters.recovery_candidate)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "games/gitadora/gitadora.h"
|
#include "games/gitadora/gitadora.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
|
#include "sdk/d3d9.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
|
||||||
#include "d3d9_device.h"
|
#include "d3d9_device.h"
|
||||||
@@ -558,6 +559,7 @@ HRESULT graphics_d3d9_gfdm_recover_two_head_present_mode(
|
|||||||
temporary_parameters[1].FullScreen_RefreshRateInHz = alternate_small.RefreshRate;
|
temporary_parameters[1].FullScreen_RefreshRateInHz = alternate_small.RefreshRate;
|
||||||
temporary_modes[1] = alternate_small;
|
temporary_modes[1] = alternate_small;
|
||||||
|
|
||||||
|
sdk::d3d9::invalidate(device);
|
||||||
HRESULT temporary_result = device->ResetEx(temporary_parameters, temporary_modes);
|
HRESULT temporary_result = device->ResetEx(temporary_parameters, temporary_modes);
|
||||||
const bool temporary_settled =
|
const bool temporary_settled =
|
||||||
temporary_result == D3D_OK
|
temporary_result == D3D_OK
|
||||||
@@ -574,6 +576,8 @@ HRESULT graphics_d3d9_gfdm_recover_two_head_present_mode(
|
|||||||
restore_result == D3D_OK
|
restore_result == D3D_OK
|
||||||
&& gfdm_wait_for_small_mode(desired_parameters[1].hDeviceWindow, desired_modes[1]);
|
&& gfdm_wait_for_small_mode(desired_parameters[1].hDeviceWindow, desired_modes[1]);
|
||||||
|
|
||||||
|
sdk::d3d9::reset_complete(device, SUCCEEDED(restore_result));
|
||||||
|
|
||||||
if (temporary_result != D3D_OK) {
|
if (temporary_result != D3D_OK) {
|
||||||
return temporary_result;
|
return temporary_result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
|
#include "sdk/d3d9.h"
|
||||||
|
|
||||||
#include "d3d9_backend.h"
|
#include "d3d9_backend.h"
|
||||||
#include "d3d9_device.h"
|
#include "d3d9_device.h"
|
||||||
@@ -140,6 +141,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::Present(const RECT *pSourc
|
|||||||
result = recovery_failure;
|
result = recovery_failure;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (should_run_hooks) {
|
||||||
|
sdk::d3d9::present_complete(pDev->pReal, result);
|
||||||
|
}
|
||||||
CHECK_RESULT(result);
|
CHECK_RESULT(result);
|
||||||
}
|
}
|
||||||
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) {
|
HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::GetFrontBufferData(IDirect3DSurface9 *pDestSurface) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@@ -396,7 +397,12 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
|||||||
// terminate
|
// terminate
|
||||||
if (uMsg == WM_CLOSE) {
|
if (uMsg == WM_CLOSE) {
|
||||||
log_info("graphics", "detected WM_CLOSE, terminating...");
|
log_info("graphics", "detected WM_CLOSE, terminating...");
|
||||||
launcher::shutdown(0);
|
static std::once_flag shutdown_requested;
|
||||||
|
std::call_once(shutdown_requested, [] {
|
||||||
|
std::thread([] {
|
||||||
|
launcher::shutdown(0);
|
||||||
|
}).detach();
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2908,7 +2908,7 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
bt5api_dispose();
|
bt5api_dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
sdk::fini_sdk_modules();
|
sdk::fini_sdk_modules(true);
|
||||||
|
|
||||||
// stop raw input
|
// stop raw input
|
||||||
hotkeys::disable_raw_input();
|
hotkeys::disable_raw_input();
|
||||||
|
|||||||
@@ -0,0 +1,403 @@
|
|||||||
|
#include "d3d9.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace sdk::d3d9 {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct Renderer {
|
||||||
|
spice_sdk_d3d9_callback_func *callback;
|
||||||
|
void *userdata;
|
||||||
|
bool ready = false;
|
||||||
|
bool started = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// keep registration separate from serialized device access and callback execution.
|
||||||
|
std::mutex registry_mutex;
|
||||||
|
std::recursive_mutex execution_mutex;
|
||||||
|
std::condition_variable_any shutdown_condition;
|
||||||
|
std::vector<std::shared_ptr<Renderer>> renderers;
|
||||||
|
|
||||||
|
SPICE_SDK_D3D9_FRAME frame{sizeof(SPICE_SDK_D3D9_FRAME), nullptr, nullptr, 0, 0};
|
||||||
|
bool stopped = false;
|
||||||
|
bool resetting = false;
|
||||||
|
bool device_lost = false;
|
||||||
|
bool reset_in_progress = false;
|
||||||
|
bool shutdown_complete = false;
|
||||||
|
DWORD device_thread = 0;
|
||||||
|
|
||||||
|
// reject reentrant SDK graphics calls made by a renderer callback.
|
||||||
|
thread_local bool dispatching = false;
|
||||||
|
struct DispatchScope {
|
||||||
|
DispatchScope() {
|
||||||
|
dispatching = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
~DispatchScope() {
|
||||||
|
dispatching = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Interface>
|
||||||
|
struct ComReference {
|
||||||
|
Interface *value = nullptr;
|
||||||
|
|
||||||
|
~ComReference() {
|
||||||
|
if (value) {
|
||||||
|
value->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// restore game state after each renderer, including early exits during setup.
|
||||||
|
struct GraphicsState {
|
||||||
|
IDirect3DDevice9 *device;
|
||||||
|
ComReference<IDirect3DStateBlock9> block;
|
||||||
|
std::array<ComReference<IDirect3DSurface9>, 4> targets;
|
||||||
|
ComReference<IDirect3DSurface9> depth;
|
||||||
|
D3DVIEWPORT9 viewport{};
|
||||||
|
std::array<D3DMATRIX, 3> transforms{};
|
||||||
|
std::array<bool, 3> have_transform{};
|
||||||
|
DWORD target_count = 0;
|
||||||
|
bool captured = false;
|
||||||
|
|
||||||
|
explicit GraphicsState(IDirect3DDevice9 *device) : device(device) {
|
||||||
|
D3DCAPS9 caps{};
|
||||||
|
if (FAILED(device->GetDeviceCaps(&caps)) ||
|
||||||
|
FAILED(device->CreateStateBlock(D3DSBT_ALL, &block.value)) ||
|
||||||
|
FAILED(block.value->Capture()) || FAILED(device->GetViewport(&viewport))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// state blocks do not capture render targets or the depth surface.
|
||||||
|
target_count = std::min<DWORD>(caps.NumSimultaneousRTs, targets.size());
|
||||||
|
for (DWORD index = 0; index < target_count; ++index) {
|
||||||
|
const auto status = device->GetRenderTarget(index, &targets[index].value);
|
||||||
|
if (FAILED(status) && status != D3DERR_NOTFOUND) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto status = device->GetDepthStencilSurface(&depth.value);
|
||||||
|
if (FAILED(status) && status != D3DERR_NOTFOUND) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::array kinds{D3DTS_WORLD, D3DTS_VIEW, D3DTS_PROJECTION};
|
||||||
|
for (size_t index = 0; index < kinds.size(); ++index) {
|
||||||
|
have_transform[index] = SUCCEEDED(
|
||||||
|
device->GetTransform(kinds[index], &transforms[index]));
|
||||||
|
}
|
||||||
|
captured = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
~GraphicsState() {
|
||||||
|
if (!captured) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unbind auxiliary surfaces before restoring targets of potentially different sizes.
|
||||||
|
device->SetDepthStencilSurface(nullptr);
|
||||||
|
for (DWORD index = 1; index < target_count; ++index) {
|
||||||
|
device->SetRenderTarget(index, nullptr);
|
||||||
|
}
|
||||||
|
device->SetRenderTarget(0, targets[0].value);
|
||||||
|
for (DWORD index = 1; index < target_count; ++index) {
|
||||||
|
device->SetRenderTarget(index, targets[index].value);
|
||||||
|
}
|
||||||
|
device->SetDepthStencilSurface(depth.value);
|
||||||
|
|
||||||
|
block.value->Apply();
|
||||||
|
const std::array kinds{D3DTS_WORLD, D3DTS_VIEW, D3DTS_PROJECTION};
|
||||||
|
for (size_t index = 0; index < kinds.size(); ++index) {
|
||||||
|
if (have_transform[index]) {
|
||||||
|
device->SetTransform(kinds[index], &transforms[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
device->SetViewport(&viewport);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Renderer>> snapshot() {
|
||||||
|
// callbacks run without the registry lock, while their entries remain alive.
|
||||||
|
std::lock_guard lock(registry_mutex);
|
||||||
|
return renderers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_renderers() {
|
||||||
|
for (const auto &renderer : snapshot()) {
|
||||||
|
if (renderer->started) {
|
||||||
|
renderer->callback(SPICE_SDK_D3D9_DESTROY, &frame, renderer->userdata);
|
||||||
|
}
|
||||||
|
renderer->ready = false;
|
||||||
|
renderer->started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = {sizeof(frame), nullptr, nullptr, 0, 0};
|
||||||
|
resetting = false;
|
||||||
|
device_lost = false;
|
||||||
|
reset_in_progress = false;
|
||||||
|
device_thread = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish_shutdown() {
|
||||||
|
if (shutdown_complete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_renderers();
|
||||||
|
{
|
||||||
|
std::lock_guard lock(registry_mutex);
|
||||||
|
renderers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown_complete = true;
|
||||||
|
shutdown_condition.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE register_d3d9(const std::vector<SdkModule> &modules,
|
||||||
|
spice_sdk_d3d9_callback_func *callback, void *userdata) {
|
||||||
|
if (dispatching) {
|
||||||
|
return SPICE_SDK_STATUS_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
if (!callback) {
|
||||||
|
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only registered SDK modules may supply callbacks.
|
||||||
|
HMODULE owner = nullptr;
|
||||||
|
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
||||||
|
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||||
|
reinterpret_cast<LPCWSTR>(callback), &owner) ||
|
||||||
|
std::none_of(modules.begin(), modules.end(), [&](const SdkModule &module) {
|
||||||
|
return module.module == owner;
|
||||||
|
})) {
|
||||||
|
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(registry_mutex);
|
||||||
|
if (stopped) {
|
||||||
|
return SPICE_SDK_STATUS_TOO_LATE;
|
||||||
|
}
|
||||||
|
if (std::any_of(renderers.begin(), renderers.end(), [&](const auto &renderer) {
|
||||||
|
return renderer->callback == callback && renderer->userdata == userdata;
|
||||||
|
})) {
|
||||||
|
return SPICE_SDK_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep callback code loaded for the lifetime of the process.
|
||||||
|
try {
|
||||||
|
auto renderer = std::make_shared<Renderer>(Renderer{callback, userdata});
|
||||||
|
if (!GetModuleHandleExW(
|
||||||
|
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
|
||||||
|
reinterpret_cast<LPCWSTR>(callback), &owner)) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
renderers.push_back(std::move(renderer));
|
||||||
|
} catch (...) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SPICE_SDK_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(HWND window, IDirect3DDevice9 *device) {
|
||||||
|
if (dispatching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(execution_mutex);
|
||||||
|
DispatchScope scope;
|
||||||
|
|
||||||
|
if (frame.device && frame.device != device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
device_thread = GetCurrentThreadId();
|
||||||
|
if (stopped) {
|
||||||
|
if (!reset_in_progress) {
|
||||||
|
finish_shutdown();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// latch the first presented device even before any renderers register.
|
||||||
|
frame.device = device;
|
||||||
|
frame.window = window;
|
||||||
|
|
||||||
|
const auto current = snapshot();
|
||||||
|
if (current.empty() || resetting || device_lost) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComReference<IDirect3DSurface9> backbuffer;
|
||||||
|
D3DSURFACE_DESC description{};
|
||||||
|
if (FAILED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer.value)) ||
|
||||||
|
FAILED(backbuffer.value->GetDesc(&description))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frame = {sizeof(frame), device, window, description.Width, description.Height};
|
||||||
|
|
||||||
|
for (const auto &renderer : current) {
|
||||||
|
// isolate each renderer and give it the full backbuffer in its own scene.
|
||||||
|
GraphicsState state(device);
|
||||||
|
if (!state.captured) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FAILED(device->SetDepthStencilSurface(nullptr))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool target_ready = true;
|
||||||
|
for (DWORD index = 1; index < state.target_count; ++index) {
|
||||||
|
if (FAILED(device->SetRenderTarget(index, nullptr))) {
|
||||||
|
target_ready = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const D3DVIEWPORT9 viewport{0, 0, frame.width, frame.height, 0.0f, 1.0f};
|
||||||
|
if (!target_ready || FAILED(device->SetRenderTarget(0, backbuffer.value)) ||
|
||||||
|
FAILED(device->SetViewport(&viewport)) || FAILED(device->BeginScene())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch READY to also recreate resources released before a successful reset.
|
||||||
|
if (!renderer->ready) {
|
||||||
|
renderer->started = true;
|
||||||
|
renderer->callback(SPICE_SDK_D3D9_READY, &frame, renderer->userdata);
|
||||||
|
renderer->ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer->callback(SPICE_SDK_D3D9_DRAW, &frame, renderer->userdata);
|
||||||
|
device->EndScene();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void present_complete(IDirect3DDevice9 *device, HRESULT result) {
|
||||||
|
if (dispatching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(execution_mutex);
|
||||||
|
DispatchScope scope;
|
||||||
|
|
||||||
|
if (frame.device == device) {
|
||||||
|
device_thread = GetCurrentThreadId();
|
||||||
|
if (stopped && !reset_in_progress) {
|
||||||
|
finish_shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a busy nonblocking present does not change the known device-loss state.
|
||||||
|
if (SUCCEEDED(result)) {
|
||||||
|
device_lost = false;
|
||||||
|
} else if (result != D3DERR_WASSTILLDRAWING) {
|
||||||
|
device_lost = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void invalidate(IDirect3DDevice9 *device) {
|
||||||
|
if (dispatching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(execution_mutex);
|
||||||
|
DispatchScope scope;
|
||||||
|
|
||||||
|
if (frame.device != device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_thread = GetCurrentThreadId();
|
||||||
|
if (stopped) {
|
||||||
|
finish_shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default-pool resources must be released before the native reset starts.
|
||||||
|
reset_in_progress = true;
|
||||||
|
resetting = true;
|
||||||
|
for (const auto &renderer : snapshot()) {
|
||||||
|
if (renderer->ready) {
|
||||||
|
renderer->callback(SPICE_SDK_D3D9_INVALIDATE, &frame, renderer->userdata);
|
||||||
|
}
|
||||||
|
renderer->ready = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_complete(IDirect3DDevice9 *device, bool success) {
|
||||||
|
if (dispatching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(execution_mutex);
|
||||||
|
DispatchScope scope;
|
||||||
|
|
||||||
|
if (frame.device == device) {
|
||||||
|
device_thread = GetCurrentThreadId();
|
||||||
|
reset_in_progress = false;
|
||||||
|
resetting = !success;
|
||||||
|
device_lost = !success;
|
||||||
|
|
||||||
|
if (stopped) {
|
||||||
|
finish_shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy(IDirect3DDevice9 *device) {
|
||||||
|
if (dispatching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(execution_mutex);
|
||||||
|
DispatchScope scope;
|
||||||
|
|
||||||
|
if (frame.device == device) {
|
||||||
|
if (stopped) {
|
||||||
|
finish_shutdown();
|
||||||
|
} else {
|
||||||
|
destroy_renderers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shutdown(bool graphics_stopped) {
|
||||||
|
std::unique_lock lock(execution_mutex);
|
||||||
|
if (shutdown_complete) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close registration before deciding where device resources can be released.
|
||||||
|
{
|
||||||
|
std::lock_guard registry_lock(registry_mutex);
|
||||||
|
stopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto current = snapshot();
|
||||||
|
const bool resources_started = std::any_of(
|
||||||
|
current.begin(), current.end(), [](const auto &renderer) {
|
||||||
|
return renderer->started;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!resources_started || (!reset_in_progress &&
|
||||||
|
(graphics_stopped || device_thread == GetCurrentThreadId()))) {
|
||||||
|
DispatchScope scope;
|
||||||
|
finish_shutdown();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a graphics boundary must drain callbacks when another thread requests shutdown.
|
||||||
|
return shutdown_condition.wait_for(lock, std::chrono::milliseconds(100), [] {
|
||||||
|
return shutdown_complete;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <d3d9.h>
|
||||||
|
#include "modules.h"
|
||||||
|
|
||||||
|
namespace sdk::d3d9 {
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE register_d3d9(const std::vector<SdkModule> &modules,
|
||||||
|
spice_sdk_d3d9_callback_func *callback, void *userdata);
|
||||||
|
void draw(HWND window, IDirect3DDevice9 *device);
|
||||||
|
void present_complete(IDirect3DDevice9 *device, HRESULT result);
|
||||||
|
void invalidate(IDirect3DDevice9 *device);
|
||||||
|
void reset_complete(IDirect3DDevice9 *device, bool success);
|
||||||
|
void destroy(IDirect3DDevice9 *device);
|
||||||
|
bool shutdown(bool graphics_stopped);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
#define SPICE_SDK_ENTRY_POINT extern "C" __declspec(dllexport) int __cdecl
|
#define SPICE_SDK_ENTRY_POINT extern "C" __declspec(dllexport) int __cdecl
|
||||||
@@ -247,6 +248,95 @@ typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_insert_coin_func)(
|
|||||||
uint8_t amount
|
uint8_t amount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
typedef struct SPICE_SDK_MODULE_INFO {
|
||||||
|
uint32_t size; // initialize to sizeof(SPICE_SDK_MODULE_INFO)
|
||||||
|
uintptr_t base; // loaded address, not the preferred PE image base
|
||||||
|
uint32_t image_size;
|
||||||
|
uint32_t timestamp;
|
||||||
|
uint32_t entry_point; // RVA, not an absolute address
|
||||||
|
uint16_t machine; // PE IMAGE_FILE_MACHINE_* value
|
||||||
|
} SPICE_SDK_MODULE_INFO;
|
||||||
|
|
||||||
|
// get_module_info (v0.4 and up)
|
||||||
|
// gets PE info about an already loaded module
|
||||||
|
//
|
||||||
|
// does not load or permanently pin the module; the caller must keep it loaded
|
||||||
|
// while using the returned base address
|
||||||
|
// returns GENERIC_ERROR if not found or unreadable, NOT_SUPPORTED for non-PE32/PE32+
|
||||||
|
//
|
||||||
|
// module_name: null-terminated UTF-16 module name or full path
|
||||||
|
// info: receives module info; initialize size to sizeof(SPICE_SDK_MODULE_INFO)
|
||||||
|
|
||||||
|
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_module_info_func)(
|
||||||
|
const wchar_t *module_name,
|
||||||
|
SPICE_SDK_MODULE_INFO *info
|
||||||
|
);
|
||||||
|
|
||||||
|
// get_plugin_directory (v0.4 and up)
|
||||||
|
// gets the directory containing a registered SDK DLL
|
||||||
|
//
|
||||||
|
// available during entry-point initialization and until destroy callbacks finish
|
||||||
|
// returns INVALID_ARGUMENT_1 for addresses outside registered SDK DLLs
|
||||||
|
//
|
||||||
|
// plugin_address: address of a function or static object in the plugin DLL
|
||||||
|
// used only to identify the DLL; the pointed-to object is not read
|
||||||
|
// address of any global variable in your DLL will work
|
||||||
|
// buffer: caller-owned UTF-16 output, or NULL to query the required size
|
||||||
|
// size: input capacity and output required wchar_t count, including the terminator
|
||||||
|
// NULL buffer or insufficient capacity returns TOO_SMALL and sets the
|
||||||
|
// required size without partial output
|
||||||
|
|
||||||
|
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_plugin_directory_func)(
|
||||||
|
const void *plugin_address,
|
||||||
|
wchar_t *buffer,
|
||||||
|
uint32_t *size
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef enum SPICE_SDK_D3D9_EVENT {
|
||||||
|
SPICE_SDK_D3D9_READY = 0,
|
||||||
|
SPICE_SDK_D3D9_DRAW = 1,
|
||||||
|
SPICE_SDK_D3D9_INVALIDATE = 2,
|
||||||
|
SPICE_SDK_D3D9_DESTROY = 3,
|
||||||
|
} SPICE_SDK_D3D9_EVENT;
|
||||||
|
|
||||||
|
typedef struct SPICE_SDK_D3D9_FRAME {
|
||||||
|
uint32_t size;
|
||||||
|
void *device; // borrowed IDirect3DDevice9*, not Spice's wrapper
|
||||||
|
void *window; // HWND
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
} SPICE_SDK_D3D9_FRAME;
|
||||||
|
|
||||||
|
typedef void (__cdecl spice_sdk_d3d9_callback_func)(
|
||||||
|
SPICE_SDK_D3D9_EVENT event,
|
||||||
|
const SPICE_SDK_D3D9_FRAME *frame,
|
||||||
|
void *userdata
|
||||||
|
);
|
||||||
|
|
||||||
|
// register_d3d9 (v0.4 and up)
|
||||||
|
// registers a process-lifetime renderer for the primary D3D9 presentation target
|
||||||
|
//
|
||||||
|
// READY precedes drawing on a usable device and follows each successful reset
|
||||||
|
// DRAW runs on top of all Spice overlays, even when the overlay is closed; the host
|
||||||
|
// binds the backbuffer, brackets the scene, and restores graphics state
|
||||||
|
// INVALIDATE precedes reset; release default-pool resources, even if reset fails
|
||||||
|
// DESTROY releases all device references before device destruction or SDK shutdown
|
||||||
|
// callbacks are serialized; DRAW/READY run on the presentation thread; teardown
|
||||||
|
// runs at a graphics-thread boundary, or after the game stops rendering
|
||||||
|
// do not throw, block, reset/present the
|
||||||
|
// device, or shut down Spice from a callback; frame is valid only during the call
|
||||||
|
// no callbacks run after the plugin's SDK destroy callback begins
|
||||||
|
// registration from inside a render callback is not supported
|
||||||
|
// input capture and non-D3D9 backends are not provided
|
||||||
|
//
|
||||||
|
// callback: function in a registered plugin DLL; the DLL is retained until exit
|
||||||
|
// userdata: opaque plugin state passed unchanged to each callback
|
||||||
|
|
||||||
|
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_register_d3d9_func)(
|
||||||
|
spice_sdk_d3d9_callback_func *callback,
|
||||||
|
void *userdata
|
||||||
|
);
|
||||||
|
|
||||||
typedef struct SPICE_SDK_V0 {
|
typedef struct SPICE_SDK_V0 {
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
|
|
||||||
@@ -274,6 +364,11 @@ typedef struct SPICE_SDK_V0 {
|
|||||||
|
|
||||||
spice_sdk_insert_coin_func *insert_coin;
|
spice_sdk_insert_coin_func *insert_coin;
|
||||||
|
|
||||||
|
spice_sdk_get_module_info_func *get_module_info;
|
||||||
|
spice_sdk_get_plugin_directory_func *get_plugin_directory;
|
||||||
|
|
||||||
|
spice_sdk_register_d3d9_func *register_d3d9;
|
||||||
|
|
||||||
} SPICE_SDK_V0;
|
} SPICE_SDK_V0;
|
||||||
|
|
||||||
typedef void (__cdecl spice_sdk_destroy_callback_func)(
|
typedef void (__cdecl spice_sdk_destroy_callback_func)(
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
#include "modules.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace sdk::modules {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// RAII wrapper for a module handle
|
||||||
|
struct ModuleReference {
|
||||||
|
HMODULE handle = nullptr;
|
||||||
|
|
||||||
|
ModuleReference() = default;
|
||||||
|
ModuleReference(const ModuleReference &) = delete;
|
||||||
|
ModuleReference &operator=(const ModuleReference &) = delete;
|
||||||
|
~ModuleReference() {
|
||||||
|
if (handle) {
|
||||||
|
FreeLibrary(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Value>
|
||||||
|
bool read_module_value(uintptr_t base, size_t offset, Value &value) {
|
||||||
|
if (offset > std::numeric_limits<uintptr_t>::max() - base ||
|
||||||
|
sizeof(Value) > std::numeric_limits<uintptr_t>::max() - (base + offset)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_T copied = 0;
|
||||||
|
return ReadProcessMemory(GetCurrentProcess(), reinterpret_cast<const void *>(base + offset),
|
||||||
|
&value, sizeof(value), &copied) && copied == sizeof(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE get_module_info(const wchar_t *module_name, SPICE_SDK_MODULE_INFO *info) {
|
||||||
|
if (!module_name || !module_name[0]) {
|
||||||
|
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||||
|
}
|
||||||
|
if (!info) {
|
||||||
|
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
|
||||||
|
}
|
||||||
|
if (info->size < sizeof(SPICE_SDK_MODULE_INFO)) {
|
||||||
|
return SPICE_SDK_STATUS_TOO_SMALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the module loaded while inspecting its headers.
|
||||||
|
ModuleReference module;
|
||||||
|
if (!GetModuleHandleExW(0, module_name, &module.handle)) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locate the PE header through the DOS header.
|
||||||
|
const auto base = reinterpret_cast<uintptr_t>(module.handle);
|
||||||
|
IMAGE_DOS_HEADER dos{};
|
||||||
|
if (!read_module_value(base, 0, dos) || dos.e_magic != IMAGE_DOS_SIGNATURE ||
|
||||||
|
dos.e_lfanew < static_cast<LONG>(sizeof(dos))) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto nt_offset = static_cast<size_t>(dos.e_lfanew);
|
||||||
|
DWORD signature{};
|
||||||
|
IMAGE_FILE_HEADER header{};
|
||||||
|
if (!read_module_value(base, nt_offset, signature) || signature != IMAGE_NT_SIGNATURE ||
|
||||||
|
!read_module_value(base, nt_offset + sizeof(signature), header)) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The optional-header magic selects the PE32 or PE32+ layout.
|
||||||
|
const auto optional_offset = nt_offset + sizeof(signature) + sizeof(header);
|
||||||
|
WORD magic{};
|
||||||
|
if (header.SizeOfOptionalHeader < sizeof(magic) || !read_module_value(base, optional_offset, magic)) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
SPICE_SDK_MODULE_INFO result{};
|
||||||
|
result.size = info->size;
|
||||||
|
result.base = base;
|
||||||
|
result.timestamp = header.TimeDateStamp;
|
||||||
|
result.machine = header.Machine;
|
||||||
|
|
||||||
|
if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
|
||||||
|
IMAGE_OPTIONAL_HEADER64 optional{};
|
||||||
|
if (header.SizeOfOptionalHeader < sizeof(optional) || !read_module_value(base, optional_offset, optional)) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
result.image_size = optional.SizeOfImage;
|
||||||
|
result.entry_point = optional.AddressOfEntryPoint;
|
||||||
|
} else if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||||||
|
IMAGE_OPTIONAL_HEADER32 optional{};
|
||||||
|
if (header.SizeOfOptionalHeader < sizeof(optional) || !read_module_value(base, optional_offset, optional)) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
result.image_size = optional.SizeOfImage;
|
||||||
|
result.entry_point = optional.AddressOfEntryPoint;
|
||||||
|
} else {
|
||||||
|
return SPICE_SDK_STATUS_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject headers or entry points outside the image.
|
||||||
|
if (optional_offset > result.image_size || header.SizeOfOptionalHeader > result.image_size - optional_offset ||
|
||||||
|
result.entry_point >= result.image_size) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leave the caller's output untouched on failure.
|
||||||
|
*info = result;
|
||||||
|
return SPICE_SDK_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE get_plugin_directory(const std::vector<SdkModule> ®istered_modules,
|
||||||
|
const void *plugin_address, wchar_t *buffer, uint32_t *size) {
|
||||||
|
if (!plugin_address) {
|
||||||
|
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||||
|
}
|
||||||
|
if (!size) {
|
||||||
|
return SPICE_SDK_STATUS_INVALID_ARGUMENT_3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the address and require a registered plugin.
|
||||||
|
ModuleReference module;
|
||||||
|
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
||||||
|
reinterpret_cast<LPCWSTR>(plugin_address), &module.handle) ||
|
||||||
|
std::none_of(registered_modules.begin(), registered_modules.end(), [&](const SdkModule &entry) {
|
||||||
|
return entry.module == module.handle;
|
||||||
|
})) {
|
||||||
|
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Reject truncated paths before extracting the directory.
|
||||||
|
std::array<wchar_t, 32768> filename{};
|
||||||
|
const auto length = GetModuleFileNameW(module.handle, filename.data(), static_cast<DWORD>(filename.size()));
|
||||||
|
if (!length || length >= filename.size()) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto path = std::filesystem::path(filename.data()).parent_path();
|
||||||
|
|
||||||
|
// Report the full UTF-16 capacity, including the terminator.
|
||||||
|
const auto &text = path.native();
|
||||||
|
const auto required = static_cast<uint32_t>(text.size() + 1);
|
||||||
|
const auto capacity = *size;
|
||||||
|
*size = required;
|
||||||
|
if (!buffer || capacity < required) {
|
||||||
|
return SPICE_SDK_STATUS_TOO_SMALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy only when the entire path fits.
|
||||||
|
std::copy_n(text.c_str(), required, buffer);
|
||||||
|
return SPICE_SDK_STATUS_SUCCESS;
|
||||||
|
} catch (const std::exception &) {
|
||||||
|
return SPICE_SDK_STATUS_GENERIC_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "sdk/include/spicesdk.h"
|
||||||
|
|
||||||
|
namespace sdk {
|
||||||
|
|
||||||
|
struct SdkModule {
|
||||||
|
std::string dll;
|
||||||
|
HINSTANCE module;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace modules {
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE get_module_info(const wchar_t *module_name, SPICE_SDK_MODULE_INFO *info);
|
||||||
|
SPICE_SDK_STATUS_CODE get_plugin_directory(const std::vector<SdkModule> ®istered_modules,
|
||||||
|
const void *plugin_address, wchar_t *buffer, uint32_t *size);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
#include <format>
|
#include <format>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "v0_cpp_imgui.h"
|
||||||
#include "sdk/include/spicesdk.h"
|
#include "sdk/include/spicesdk.h"
|
||||||
#include "sdk/include/spicesdk_io.h"
|
#include "sdk/include/spicesdk_io.h"
|
||||||
|
|
||||||
@@ -17,6 +21,24 @@ static spice_sdk_destroy_callback_func destroy_callback;
|
|||||||
static std::jthread worker_thread;
|
static std::jthread worker_thread;
|
||||||
static void worker_thread_main(std::stop_token stop_token);
|
static void worker_thread_main(std::stop_token stop_token);
|
||||||
|
|
||||||
|
static std::optional<std::string> utf16_to_utf8(const wchar_t *text) {
|
||||||
|
const auto bytes = WideCharToMultiByte(
|
||||||
|
CP_UTF8, WC_ERR_INVALID_CHARS, text, -1, nullptr, 0, nullptr, nullptr);
|
||||||
|
if (bytes == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result(bytes, '\0');
|
||||||
|
if (WideCharToMultiByte(
|
||||||
|
CP_UTF8, WC_ERR_INVALID_CHARS, text, -1,
|
||||||
|
result.data(), bytes, nullptr, nullptr) != bytes) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.pop_back();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// main entry point into the DLL
|
// main entry point into the DLL
|
||||||
// spice executable will call into this shortly after the game is partially
|
// spice executable will call into this shortly after the game is partially
|
||||||
// initialized (but not quite fully booted just yet)
|
// initialized (but not quite fully booted just yet)
|
||||||
@@ -42,6 +64,37 @@ spice_sdk_entry_point(
|
|||||||
|
|
||||||
LOG_INFO("plugin loaded");
|
LOG_INFO("plugin loaded");
|
||||||
|
|
||||||
|
if (spice.get_plugin_directory) {
|
||||||
|
std::array<wchar_t, 32768> directory{};
|
||||||
|
uint32_t size = static_cast<uint32_t>(directory.size());
|
||||||
|
if (spice.get_plugin_directory(&spice, directory.data(), &size) ==
|
||||||
|
SPICE_SDK_STATUS_SUCCESS) {
|
||||||
|
if (const auto path = utf16_to_utf8(directory.data())) {
|
||||||
|
LOG_INFO(std::format("plugin directory: {}", *path).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spice.get_module_info) {
|
||||||
|
SPICE_SDK_MODULE_INFO info{};
|
||||||
|
info.size = sizeof(info);
|
||||||
|
if (spice.get_module_info(L"kernel32.dll", &info) == SPICE_SDK_STATUS_SUCCESS) {
|
||||||
|
LOG_INFO(std::format(
|
||||||
|
"kernel32 PE: {:x}_{:x}, base={:x}, size={:x}",
|
||||||
|
info.timestamp, info.entry_point, info.base, info.image_size).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spice.register_d3d9) {
|
||||||
|
status = sample_imgui::initialize(spice);
|
||||||
|
if (status != SPICE_SDK_STATUS_SUCCESS) {
|
||||||
|
LOG_INFO(std::format(
|
||||||
|
"D3D9 registration failed: {}", static_cast<int>(status)).c_str());
|
||||||
|
} else {
|
||||||
|
LOG_INFO("D3D9 renderer registered; Ctrl+Enter toggles the sample window");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// spin up a worker thread
|
// spin up a worker thread
|
||||||
worker_thread = std::jthread(worker_thread_main);
|
worker_thread = std::jthread(worker_thread_main);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -77,10 +130,18 @@ static ArrowButton arrow_buttons[] = {
|
|||||||
// worker thread for I/O
|
// worker thread for I/O
|
||||||
static void worker_thread_main(std::stop_token stop_token) {
|
static void worker_thread_main(std::stop_token stop_token) {
|
||||||
bool coin_previous_state[10] = {};
|
bool coin_previous_state[10] = {};
|
||||||
|
bool window_toggle_previous_state = false;
|
||||||
while (!stop_token.stop_requested()) {
|
while (!stop_token.stop_requested()) {
|
||||||
|
|
||||||
// insert coin
|
|
||||||
const bool control_pressed = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
|
const bool control_pressed = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
|
||||||
|
const bool window_toggle_pressed = control_pressed &&
|
||||||
|
((GetAsyncKeyState(VK_RETURN) & 0x8000) != 0);
|
||||||
|
if (window_toggle_pressed && !window_toggle_previous_state) {
|
||||||
|
sample_imgui::toggle();
|
||||||
|
}
|
||||||
|
window_toggle_previous_state = window_toggle_pressed;
|
||||||
|
|
||||||
|
// insert coin
|
||||||
for (uint8_t amount = 0; amount < 10; amount++) {
|
for (uint8_t amount = 0; amount < 10; amount++) {
|
||||||
const bool coin_pressed = control_pressed &&
|
const bool coin_pressed = control_pressed &&
|
||||||
((GetAsyncKeyState('0' + amount) & 0x8000) != 0);
|
((GetAsyncKeyState('0' + amount) & 0x8000) != 0);
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
#include "v0_cpp_imgui.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cfloat>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "backends/imgui_impl_dx9.h"
|
||||||
|
|
||||||
|
namespace sample_imgui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct SampleRenderer {
|
||||||
|
ImGuiContext *context = nullptr;
|
||||||
|
bool backend_initialized = false;
|
||||||
|
bool was_visible = false;
|
||||||
|
bool dummy_enabled = true;
|
||||||
|
float dummy_value = 0.5f;
|
||||||
|
int32_t dummy_mode = 0;
|
||||||
|
uint32_t click_count = 0;
|
||||||
|
std::chrono::steady_clock::time_point previous_frame;
|
||||||
|
};
|
||||||
|
|
||||||
|
SampleRenderer renderer;
|
||||||
|
std::atomic_bool window_visible = false;
|
||||||
|
|
||||||
|
void update_mouse(const SPICE_SDK_D3D9_FRAME &frame) {
|
||||||
|
auto &io = ImGui::GetIO();
|
||||||
|
const auto window = static_cast<HWND>(frame.window);
|
||||||
|
const bool focused = window &&
|
||||||
|
GetAncestor(GetForegroundWindow(), GA_ROOT) == GetAncestor(window, GA_ROOT);
|
||||||
|
io.AddFocusEvent(focused);
|
||||||
|
|
||||||
|
POINT position{};
|
||||||
|
RECT client{};
|
||||||
|
|
||||||
|
// can window receive mouse input?
|
||||||
|
const bool mouse_available =
|
||||||
|
focused &&
|
||||||
|
GetCursorPos(&position) &&
|
||||||
|
ScreenToClient(window, &position) &&
|
||||||
|
GetClientRect(window, &client) &&
|
||||||
|
client.right > 0 &&
|
||||||
|
client.bottom > 0;
|
||||||
|
|
||||||
|
// add mouse position
|
||||||
|
if (mouse_available) {
|
||||||
|
io.AddMousePosEvent(
|
||||||
|
static_cast<float>(position.x) * frame.width / client.right,
|
||||||
|
static_cast<float>(position.y) * frame.height / client.bottom);
|
||||||
|
} else {
|
||||||
|
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add click to imgui
|
||||||
|
const bool swapped = GetSystemMetrics(SM_SWAPBUTTON) != 0;
|
||||||
|
io.AddMouseButtonEvent(0, mouse_available &&
|
||||||
|
((GetAsyncKeyState(swapped ? VK_RBUTTON : VK_LBUTTON) & 0x8000) != 0));
|
||||||
|
io.AddMouseButtonEvent(1, mouse_available &&
|
||||||
|
((GetAsyncKeyState(swapped ? VK_LBUTTON : VK_RBUTTON) & 0x8000) != 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_window(SampleRenderer &state, const SPICE_SDK_D3D9_FRAME &frame) {
|
||||||
|
auto &io = ImGui::GetIO();
|
||||||
|
|
||||||
|
ImGui::SetNextWindowPos(
|
||||||
|
ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f),
|
||||||
|
ImGuiCond_Once, ImVec2(0.5f, 0.5f));
|
||||||
|
ImGui::SetNextWindowBgAlpha(0.8f);
|
||||||
|
|
||||||
|
if (ImGui::Begin("SDK sample", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize |
|
||||||
|
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse)) {
|
||||||
|
|
||||||
|
ImGui::Text("%u x %u", frame.width, frame.height);
|
||||||
|
ImGui::Text("%.1f FPS", io.Framerate);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
ImGui::Checkbox("Enable dummy widgets", &state.dummy_enabled);
|
||||||
|
|
||||||
|
ImGui::BeginDisabled(!state.dummy_enabled);
|
||||||
|
{
|
||||||
|
ImGui::SliderFloat("Value", &state.dummy_value, 0.0f, 1.0f, "%.2f",
|
||||||
|
ImGuiSliderFlags_NoInput);
|
||||||
|
ImGui::Combo("Mode", &state.dummy_mode, "Off\0Low\0High\0");
|
||||||
|
if (ImGui::Button("Increment")) {
|
||||||
|
++state.click_count;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Reset")) {
|
||||||
|
state.click_count = 0;
|
||||||
|
state.dummy_value = 0.5f;
|
||||||
|
state.dummy_mode = 0;
|
||||||
|
}
|
||||||
|
ImGui::Text("Count: %u", state.click_count);
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_frame(SampleRenderer &state, const SPICE_SDK_D3D9_FRAME &frame) {
|
||||||
|
if (!state.backend_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const float elapsed = std::chrono::duration<float>(now - state.previous_frame).count();
|
||||||
|
state.previous_frame = now;
|
||||||
|
const bool visible = window_visible.load(std::memory_order_relaxed);
|
||||||
|
if (!visible && !state.was_visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.was_visible = visible;
|
||||||
|
|
||||||
|
// update imgui state
|
||||||
|
auto &io = ImGui::GetIO();
|
||||||
|
io.DisplaySize = ImVec2(
|
||||||
|
static_cast<float>(frame.width), static_cast<float>(frame.height));
|
||||||
|
io.DeltaTime = elapsed > 0.0f ? elapsed : 1.0f / 60.0f;
|
||||||
|
|
||||||
|
// update mouse i/o
|
||||||
|
if (visible) {
|
||||||
|
update_mouse(frame);
|
||||||
|
} else {
|
||||||
|
io.ClearEventsQueue();
|
||||||
|
io.ClearInputMouse();
|
||||||
|
io.AddFocusEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui_ImplDX9_NewFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
if (!visible) {
|
||||||
|
ImGui::SetWindowFocus(nullptr);
|
||||||
|
ImGui::EndFrame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_window(state, frame);
|
||||||
|
ImGui::Render();
|
||||||
|
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
}
|
||||||
|
|
||||||
|
void __cdecl render_callback(
|
||||||
|
SPICE_SDK_D3D9_EVENT event,
|
||||||
|
const SPICE_SDK_D3D9_FRAME *frame,
|
||||||
|
void *userdata) {
|
||||||
|
|
||||||
|
auto &state = *static_cast<SampleRenderer *>(userdata);
|
||||||
|
auto *previous_context = ImGui::GetCurrentContext();
|
||||||
|
|
||||||
|
// init imgui context
|
||||||
|
if (event == SPICE_SDK_D3D9_READY && !state.context) {
|
||||||
|
ImGui::SetAllocatorFunctions(
|
||||||
|
[](size_t size, void *) -> void * { return std::malloc(size); },
|
||||||
|
[](void *memory, void *) { std::free(memory); });
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
|
state.context = ImGui::CreateContext();
|
||||||
|
ImGui::SetCurrentContext(state.context);
|
||||||
|
auto &io = ImGui::GetIO();
|
||||||
|
io.IniFilename = nullptr;
|
||||||
|
io.LogFilename = nullptr;
|
||||||
|
io.MouseDrawCursor = true;
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
state.backend_initialized = ImGui_ImplDX9_Init(
|
||||||
|
static_cast<IDirect3DDevice9 *>(frame->device));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.context) {
|
||||||
|
ImGui::SetCurrentContext(state.context);
|
||||||
|
switch (event) {
|
||||||
|
case SPICE_SDK_D3D9_READY:
|
||||||
|
state.previous_frame = std::chrono::steady_clock::now();
|
||||||
|
if (state.backend_initialized) {
|
||||||
|
ImGui_ImplDX9_CreateDeviceObjects();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SPICE_SDK_D3D9_DRAW:
|
||||||
|
draw_frame(state, *frame);
|
||||||
|
break;
|
||||||
|
case SPICE_SDK_D3D9_INVALIDATE:
|
||||||
|
if (state.backend_initialized) {
|
||||||
|
ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SPICE_SDK_D3D9_DESTROY:
|
||||||
|
if (state.backend_initialized) {
|
||||||
|
ImGui_ImplDX9_Shutdown();
|
||||||
|
}
|
||||||
|
ImGui::DestroyContext(state.context);
|
||||||
|
state.context = nullptr;
|
||||||
|
state.backend_initialized = false;
|
||||||
|
state.was_visible = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCurrentContext(previous_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE initialize(const SPICE_SDK_V0 &spice) {
|
||||||
|
if (!spice.register_d3d9) {
|
||||||
|
return SPICE_SDK_STATUS_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
return spice.register_d3d9(render_callback, &renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggle() {
|
||||||
|
const bool visible = !window_visible.load(std::memory_order_relaxed);
|
||||||
|
window_visible.store(visible, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sdk/include/spicesdk.h"
|
||||||
|
|
||||||
|
namespace sample_imgui {
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE initialize(const SPICE_SDK_V0 &spice);
|
||||||
|
void toggle();
|
||||||
|
|
||||||
|
}
|
||||||
+58
-7
@@ -1,8 +1,11 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "sdk.h"
|
#include "sdk.h"
|
||||||
|
#include "modules.h"
|
||||||
|
#include "d3d9.h"
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "games/io.h"
|
#include "games/io.h"
|
||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
@@ -32,11 +35,9 @@ static spice_sdk_insert_card_func sdk_insert_card;
|
|||||||
static spice_sdk_set_keypad_func sdk_set_keypad;
|
static spice_sdk_set_keypad_func sdk_set_keypad;
|
||||||
static spice_sdk_add_toast_func sdk_add_toast;
|
static spice_sdk_add_toast_func sdk_add_toast;
|
||||||
static spice_sdk_insert_coin_func sdk_insert_coin;
|
static spice_sdk_insert_coin_func sdk_insert_coin;
|
||||||
|
static spice_sdk_get_module_info_func sdk_get_module_info;
|
||||||
struct SdkModule {
|
static spice_sdk_get_plugin_directory_func sdk_get_plugin_directory;
|
||||||
std::string dll;
|
static spice_sdk_register_d3d9_func sdk_register_d3d9;
|
||||||
HINSTANCE module;
|
|
||||||
};
|
|
||||||
|
|
||||||
// DLLs
|
// DLLs
|
||||||
static int sdk_modules_count = 0;
|
static int sdk_modules_count = 0;
|
||||||
@@ -46,6 +47,7 @@ static std::shared_mutex sdk_global_mutex;
|
|||||||
// internal
|
// internal
|
||||||
static bool sdk_initialized = false;
|
static bool sdk_initialized = false;
|
||||||
static bool sdk_shutting_down = false;
|
static bool sdk_shutting_down = false;
|
||||||
|
static bool sdk_finalizing = false;
|
||||||
static std::vector<Button> *buttons;
|
static std::vector<Button> *buttons;
|
||||||
static std::vector<Analog> *analogs;
|
static std::vector<Analog> *analogs;
|
||||||
static std::vector<Light> *lights;
|
static std::vector<Light> *lights;
|
||||||
@@ -91,14 +93,22 @@ void init_sdk_modules() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fini_sdk_modules() {
|
void fini_sdk_modules(bool graphics_stopped) {
|
||||||
// prevent multiple calls and further calls into sdk_init
|
// prevent multiple calls and further calls into sdk_init
|
||||||
{
|
{
|
||||||
std::unique_lock lock(sdk_global_mutex);
|
std::unique_lock lock(sdk_global_mutex);
|
||||||
if (!sdk_initialized) {
|
if (!sdk_initialized || sdk_finalizing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sdk_shutting_down = true;
|
sdk_shutting_down = true;
|
||||||
|
sdk_finalizing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!d3d9::shutdown(graphics_stopped)) {
|
||||||
|
std::unique_lock lock(sdk_global_mutex);
|
||||||
|
sdk_finalizing = false;
|
||||||
|
log_warning("sdk", "deferring plugin teardown: waiting for D3D9 rendering to stop");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// call into destroy callback of each DLL
|
// call into destroy callback of each DLL
|
||||||
@@ -183,6 +193,17 @@ sdk_init(
|
|||||||
}
|
}
|
||||||
// end of 0.3
|
// end of 0.3
|
||||||
|
|
||||||
|
if (v0->size >= RTL_SIZEOF_THROUGH_FIELD(SPICE_SDK_V0, get_module_info)) {
|
||||||
|
v0->get_module_info = sdk_get_module_info;
|
||||||
|
}
|
||||||
|
if (v0->size >= RTL_SIZEOF_THROUGH_FIELD(SPICE_SDK_V0, get_plugin_directory)) {
|
||||||
|
v0->get_plugin_directory = sdk_get_plugin_directory;
|
||||||
|
}
|
||||||
|
if (v0->size >= RTL_SIZEOF_THROUGH_FIELD(SPICE_SDK_V0, register_d3d9)) {
|
||||||
|
v0->register_d3d9 = sdk_register_d3d9;
|
||||||
|
}
|
||||||
|
// end of 0.4
|
||||||
|
|
||||||
// any newer minor iterations will need to check the size
|
// any newer minor iterations will need to check the size
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -195,6 +216,36 @@ sdk_init(
|
|||||||
return SPICE_SDK_STATUS_SUCCESS;
|
return SPICE_SDK_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE
|
||||||
|
__cdecl
|
||||||
|
sdk_get_module_info(const wchar_t *module_name, SPICE_SDK_MODULE_INFO *info) {
|
||||||
|
std::shared_lock lock(sdk_global_mutex);
|
||||||
|
if (!sdk_initialized) {
|
||||||
|
return SPICE_SDK_STATUS_TOO_LATE;
|
||||||
|
}
|
||||||
|
return modules::get_module_info(module_name, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE
|
||||||
|
__cdecl
|
||||||
|
sdk_get_plugin_directory(const void *plugin_address, wchar_t *buffer, uint32_t *size) {
|
||||||
|
std::shared_lock lock(sdk_global_mutex);
|
||||||
|
if (!sdk_initialized) {
|
||||||
|
return SPICE_SDK_STATUS_TOO_LATE;
|
||||||
|
}
|
||||||
|
return modules::get_plugin_directory(sdk_modules_list, plugin_address, buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
SPICE_SDK_STATUS_CODE
|
||||||
|
__cdecl
|
||||||
|
sdk_register_d3d9(spice_sdk_d3d9_callback_func *callback, void *userdata) {
|
||||||
|
std::shared_lock lock(sdk_global_mutex);
|
||||||
|
if (!sdk_initialized || sdk_shutting_down) {
|
||||||
|
return SPICE_SDK_STATUS_TOO_LATE;
|
||||||
|
}
|
||||||
|
return d3d9::register_d3d9(sdk_modules_list, callback, userdata);
|
||||||
|
}
|
||||||
|
|
||||||
SPICE_SDK_STATUS_CODE
|
SPICE_SDK_STATUS_CODE
|
||||||
__cdecl
|
__cdecl
|
||||||
sdk_log(
|
sdk_log(
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ namespace sdk {
|
|||||||
|
|
||||||
void register_sdk_hooks(std::string dll, HINSTANCE module);
|
void register_sdk_hooks(std::string dll, HINSTANCE module);
|
||||||
void init_sdk_modules();
|
void init_sdk_modules();
|
||||||
void fini_sdk_modules();
|
void fini_sdk_modules(bool graphics_stopped = false);
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user