mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-09-29 02:08:03 +03:00
## 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.
194 lines
6.3 KiB
C++
194 lines
6.3 KiB
C++
#include <format>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <array>
|
|
#include <string>
|
|
#include <optional>
|
|
#include <windows.h>
|
|
|
|
#include "v0_cpp_imgui.h"
|
|
#include "sdk/include/spicesdk.h"
|
|
#include "sdk/include/spicesdk_io.h"
|
|
|
|
// this sample assumes that the game is DDR
|
|
|
|
// convenience wrapper for logging; feel free to add other levels
|
|
#define LOG_INFO(message) spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0_cpp", message)
|
|
|
|
static SPICE_SDK_V0 spice = {};
|
|
|
|
static spice_sdk_destroy_callback_func destroy_callback;
|
|
static std::jthread worker_thread;
|
|
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
|
|
// spice executable will call into this shortly after the game is partially
|
|
// initialized (but not quite fully booted just yet)
|
|
SPICE_SDK_ENTRY_POINT
|
|
spice_sdk_entry_point(
|
|
spice_sdk_init_func *init
|
|
)
|
|
{
|
|
SPICE_SDK_STATUS_CODE status;
|
|
|
|
// SPICE_SDK_V0 must be zeroed AND the size field must be set
|
|
spice.size = sizeof(spice);
|
|
|
|
// initialize the SDK - can be called any time, but we'll do it here
|
|
status = init(0, destroy_callback, &spice);
|
|
|
|
// always check for return value; failure could be usage error,
|
|
// or the spice executable doesn't support this SDK version
|
|
// check the log messages from sdk:
|
|
if (status != SPICE_SDK_STATUS_SUCCESS) {
|
|
return 0;
|
|
}
|
|
|
|
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
|
|
worker_thread = std::jthread(worker_thread_main);
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
__cdecl
|
|
destroy_callback(
|
|
void
|
|
)
|
|
{
|
|
LOG_INFO("plugin unloading");
|
|
if (worker_thread.joinable()) {
|
|
worker_thread.request_stop();
|
|
worker_thread.join();
|
|
}
|
|
}
|
|
|
|
struct ArrowButton {
|
|
int key;
|
|
SPICE_SDK_DDR_BUTTONS button;
|
|
const char* name;
|
|
bool previous_state;
|
|
};
|
|
|
|
static ArrowButton arrow_buttons[] = {
|
|
{ VK_UP, DDR_Button_P1_PANEL_UP, "UP", false },
|
|
{ VK_DOWN, DDR_Button_P1_PANEL_DOWN, "DOWN", false },
|
|
{ VK_LEFT, DDR_Button_P1_PANEL_LEFT, "LEFT", false },
|
|
{ VK_RIGHT, DDR_Button_P1_PANEL_RIGHT, "RIGHT", false },
|
|
};
|
|
|
|
// worker thread for I/O
|
|
static void worker_thread_main(std::stop_token stop_token) {
|
|
bool coin_previous_state[10] = {};
|
|
bool window_toggle_previous_state = false;
|
|
while (!stop_token.stop_requested()) {
|
|
|
|
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++) {
|
|
const bool coin_pressed = control_pressed &&
|
|
((GetAsyncKeyState('0' + amount) & 0x8000) != 0);
|
|
|
|
if (coin_pressed && !coin_previous_state[amount] && spice.insert_coin) {
|
|
const auto status = spice.insert_coin(amount);
|
|
if (status != SPICE_SDK_STATUS_SUCCESS) {
|
|
LOG_INFO(std::format(
|
|
"coin insertion failed: {}",
|
|
static_cast<int>(status)).c_str());
|
|
} else {
|
|
const auto message = std::format(
|
|
"v0_cpp: inserted {} coin{}", amount, amount == 1 ? "" : "s");
|
|
LOG_INFO(message.c_str());
|
|
if (spice.add_toast) {
|
|
spice.add_toast(SPICE_SDK_TOAST_LEVEL_SUCCESS, message.c_str());
|
|
}
|
|
}
|
|
}
|
|
coin_previous_state[amount] = coin_pressed;
|
|
}
|
|
|
|
// check for ctrl + arrow keys and trigger p1 pad arrows
|
|
for (auto& arrow : arrow_buttons) {
|
|
if (((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) &&
|
|
((GetAsyncKeyState(arrow.key) & 0x8000) != 0)) {
|
|
spice.set_button(arrow.button, true, 1.f);
|
|
|
|
if (!arrow.previous_state) {
|
|
|
|
if (spice.add_toast) {
|
|
spice.add_toast(
|
|
SPICE_SDK_TOAST_LEVEL_INFO,
|
|
std::format("v0_cpp: let me hear you say: {}", arrow.name).c_str());
|
|
} else {
|
|
LOG_INFO(std::format("let me hear you say: {}", arrow.name).c_str());
|
|
}
|
|
|
|
arrow.previous_state = true;
|
|
}
|
|
} else {
|
|
spice.set_button(arrow.button, false, 0.f);
|
|
arrow.previous_state = false;
|
|
}
|
|
}
|
|
|
|
Sleep(1);
|
|
}
|
|
}
|