sdk: introduce Spice SDK (#676)

## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Adds a flat-C, headers-only library for writing DLL plugins with spice.

This is meant to be an alternative to writing applications over spice
API which doesn't suit some scenarios like writing custom
high-performance I/O modules.

All you have to do is:

1. Create a 32-bit or 64-bit DLL with `spice_sdk_entry_point` as a
DLLExport
1. Include `spicesdk.h`, and optionally `spicesdk_io.h` (see
`extras\sdk\include`)
1. `spice_sdk_entry_point` will be called when hooks get initialized.
Call `init` with valid parameters.
1. When `init` returns, `SPICE_SDK_V0` will be filled out with a series
of function pointers; your DLL can now start calling into it.
1. Compile your DLL and add it as a DLL hook (`-k`) in spice when
launching the game.

See `v0_cpp.cpp` for an example.

V0 implements the following functions:

```
    log;

    get_game_info;
    get_avs_info;

    get_button;
    set_button;

    get_analog;
    set_analog;

    get_light;
    set_light;

    set_touch;
    clear_touch;

    insert_card;
    set_keypad;
```

V0 is focused on providing common utility functions needed in a lot of
DLL hooks (get AVS info, log messages to file), and implementing I/O.

In the future, we could have more interesting things like:

* scanning and modifying memory 
* installing DLL hooks
* having direct hooks into I/O emulation modules
* getting full tape LEDs
* having a callback into DX9 `Present` call
* drawing an ImGui window

More detailed documentation is coming soon in form of a wiki page.

## Testing
See included sample DLL.
This commit is contained in:
bicarus
2026-05-06 23:29:52 -07:00
committed by GitHub
parent 1f23d88c7a
commit cd0ba51b5a
13 changed files with 3233 additions and 16 deletions
+35 -2
View File
@@ -607,6 +607,9 @@ set(SOURCE_FILES ${SOURCE_FILES}
reader/structuredmessage.cpp
reader/crypt.cpp
# sdk
sdk/sdk.cpp
# stubs
stubs/stubs.cpp
@@ -860,6 +863,36 @@ if(NOT MSVC)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# sdk_sample_v0_flat_c.dll (32 bit)
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
add_library(spicetools_sdk_sample_v0_flat_c_32 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES PREFIX "")
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
if(NOT MSVC)
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# sdk_sample_v0_flat_c.dll (64 bit)
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
add_library(spicetools_sdk_sample_v0_flat_c_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES PREFIX "")
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
if(NOT MSVC)
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# sdk_sample_v0_cpp.dll (64 bit)
set(SOURCE_FILES sdk/sample/v0/cpp/v0_cpp.cpp)
add_library(spicetools_sdk_sample_v0_cpp_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/cpp/v0_cpp.def)
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")
if(NOT MSVC)
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# output directories
####################
@@ -869,14 +902,14 @@ set_target_properties(spicetools_cfg spicetools_cfg_linux
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
# output 32bit
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm spicetools_sdk_sample_v0_flat_c_32
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
# output 64bit
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64 spicetools_sdk_sample_v0_flat_c_64 spicetools_sdk_sample_v0_cpp_64
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64"
+10
View File
@@ -283,6 +283,16 @@ Python is the only one that is fully spec compliant.
Other libraries may be missing features and contain bugs; please feel free to
contribute code to fill the gaps if you work on a project using these libraries.
## Spice SDK (for DLL hooks / plugins)
As an alternative to Spice API, Spice SDK is available.
This is a flat C, header-only library that can be included in your DLL. Once
initialized, you can call directly into helper routines provided by Spicetools
executable, making low-latency and high-throughput interactions possible.
Check the documentation on the wiki for more details.
## License
Unless otherwise noted, all files are licensed under the GPLv3.
See the LICENSE file for the full license text.
+10 -14
View File
@@ -68,8 +68,8 @@ DIST_FOLDER="./dist"
DIST_NAME="spice2x-$(date +%y)-$(date +%m)-$(date +%d).zip"
DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm spicetools_sdk_sample_v0_flat_c_32"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux spicetools_sdk_sample_v0_flat_c_64 spicetools_sdk_sample_v0_cpp_64"
TARGETS_XP32="spicetools_cfg spicetools_spice"
TARGETS_XP64="spicetools_spice64"
@@ -269,6 +269,8 @@ rm -rf ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
mkdir -p ${OUTDIR_EXTRAS}/linux
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/32
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/64
if ((BUILD_XP > 0))
then
mkdir -p ${OUTDIR_EXTRAS}/winxp
@@ -281,16 +283,8 @@ then
cp ${BUILDDIR_32}/spicetools/spicecfg-pdb.pdb ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.exe ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.pdb ${OUTDIR} 2>/dev/null
#cp ${BUILDDIR_32}/spicetools/32/kbt.dll ${OUTDIR}/stubs/32 2>/dev/null
#cp ${BUILDDIR_32}/spicetools/32/kld.dll ${OUTDIR}/stubs/32 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.exe ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.pdb ${OUTDIR} 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/kbt.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/kld.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
else
# release files
cp ${BUILDDIR_32}/spicetools/spicecfg.exe ${OUTDIR} 2>/dev/null
@@ -298,16 +292,15 @@ else
cp ${BUILDDIR_32}/spicetools/32/spice.exe ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/spice_laa.exe ${OUTDIR_EXTRAS}/largeaddressaware/spice.exe 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/spice_linux.exe ${OUTDIR_EXTRAS}/linux/spice.exe 2>/dev/null
#cp ${BUILDDIR_32}/spicetools/32/kbt.dll ${OUTDIR}/stubs/32 2>/dev/null
#cp ${BUILDDIR_32}/spicetools/32/kld.dll ${OUTDIR}/stubs/32 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/spice64.exe ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/spice64_linux.exe ${OUTDIR_EXTRAS}/linux/spice64.exe 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/kbt.dll ${OUTDIR}/stubs/64 2>/dev/null
#cp ${BUILDDIR_64}/spicetools/64/kld.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/sdk_sample_v0_flat_c.dll ${OUTDIR_EXTRAS}/sdk/samples/32/v0_flat_c.dll 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/sdk_sample_v0_flat_c.dll ${OUTDIR_EXTRAS}/sdk/samples/64/v0_flat_c.dll 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/sdk_sample_v0_cpp.dll ${OUTDIR_EXTRAS}/sdk/samples/64/v0_cpp.dll 2>/dev/null
if ((BUILD_XP > 0))
then
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
@@ -326,6 +319,9 @@ then
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
fi
# sdk headers
cp -r ./sdk/include ${OUTDIR_EXTRAS}/sdk
# copy resources
rm -rf ${OUTDIR_EXTRAS}/api
mkdir ${OUTDIR_EXTRAS}/api
+7
View File
@@ -98,6 +98,7 @@
#include "rawinput/rawinput.h"
#include "rawinput/touch.h"
#include "reader/reader.h"
#include "sdk/sdk.h"
#include "stubs/stubs.h"
#include "touch/touch.h"
#include "util/cpuutils.h"
@@ -2358,6 +2359,7 @@ int main_implementation(int argc, char *argv[]) {
});
} else {
bt5api_hook(module);
sdk::register_sdk_hooks(hook, module);
}
}
@@ -2392,6 +2394,9 @@ int main_implementation(int argc, char *argv[]) {
bt5api_init();
}
// init SDK
sdk::init_sdk_modules();
// API
if (api_enable || std::min(api_serial_port.size(), api_serial_baud.size()) > 0) {
API_CONTROLLER = std::make_unique<api::Controller>(api_port, api_pass, api_pretty);
@@ -2499,6 +2504,8 @@ int main_implementation(int argc, char *argv[]) {
bt5api_dispose();
}
sdk::fini_sdk_modules();
// stop raw input
RI_MGR.reset();
+3
View File
@@ -14,6 +14,7 @@
#include "launcher.h"
#include "logger.h"
#include "nvapi/nvapi.h"
#include "sdk/sdk.h"
namespace launcher {
@@ -23,6 +24,8 @@ namespace launcher {
// therefore, subsystems need to be guarded against multiple unload attempts
log_info("launcher", "stopping subsystems");
sdk::fini_sdk_modules();
// reset monitor settings
reset_monitor_on_exit();
+273
View File
@@ -0,0 +1,273 @@
#pragma once
#ifndef SPICE_SDK_H
#define SPICE_SDK_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
#define SPICE_SDK_ENTRY_POINT extern "C" __declspec(dllexport) int __cdecl
#else
#define SPICE_SDK_ENTRY_POINT __declspec(dllexport) int __cdecl
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef enum SPICE_SDK_STATUS_CODE {
SPICE_SDK_STATUS_SUCCESS = 0,
// 000: generic
SPICE_SDK_STATUS_GENERIC_ERROR = 1,
SPICE_SDK_STATUS_NOT_INITIALIZED = 2,
SPICE_SDK_STATUS_NOT_SUPPORTED = 3,
SPICE_SDK_STATUS_TOO_SMALL = 4,
SPICE_SDK_STATUS_TOO_LATE = 5,
// 1000: invalid args
SPICE_SDK_STATUS_INVALID_ARGUMENT_1 = 1001,
SPICE_SDK_STATUS_INVALID_ARGUMENT_2 = 1002,
SPICE_SDK_STATUS_INVALID_ARGUMENT_3 = 1003,
SPICE_SDK_STATUS_INVALID_ARGUMENT_4 = 1004,
SPICE_SDK_STATUS_INVALID_ARGUMENT_5 = 1005,
} SPICE_SDK_STATUS_CODE;
typedef enum SPICE_SDK_LOG_LEVEL {
SPICE_SDK_LOG_LEVEL_MISC = 0,
SPICE_SDK_LOG_LEVEL_INFO = 1,
SPICE_SDK_LOG_LEVEL_WARNING = 2,
SPICE_SDK_LOG_LEVEL_FATAL = 3,
} SPICE_SDK_LOG_LEVEL;
typedef struct SPICE_SDK_TOUCH_POINT {
uint32_t id;
int x;
int y;
} SPICE_SDK_TOUCH_POINT;
typedef struct SPICE_SDK_GAME_INFO {
char name[64]; // null-terminated
} SPICE_SDK_GAME_INFO;
typedef struct SPICE_SDK_AVS_INFO {
char model[4]; // "MDX", null-terminated
char dest; // J
char spec; // A
char rev; // A
char ext[11]; // "2025061002", null-terminated
} SPICE_SDK_AVS_INFO;
// get_game_info (v0.1 and up)
//
// get info about the currently running game
//
// info: receives game info
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_game_info_func)(
SPICE_SDK_GAME_INFO *info
);
// get_avs_info (v0.1 and up)
//
// get AVS info (model, dest, spec, rev, ext)
//
// info: receives AVS info
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_avs_info_func)(
SPICE_SDK_AVS_INFO *info
);
// log (v0.1 and up)
// logs a message to the log
// writing a FATAL message will terminate spice, only use in catastrophic failure
//
// level: see log level enum
// module: short string that identifies the facility / module / submodule
// message: the message to log
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_log_func)(
SPICE_SDK_LOG_LEVEL level,
const char *module,
const char *message
);
// get_button (v0.1 and up)
// gets the button state
//
// button_id: ID of the button; see spicesdk_io.h for named values
// pressed: (optional) is the button pressed?
// velocity: (optional) MIDI velocity of the button
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_button_func)(
uint32_t button_id,
bool *pressed,
float *velocity
);
// set_button (v0.1 and up)
// sets or clears the button override
//
// make sure to hold the button long enough for the game's I/O engine to pick up
// usually, one or two frames
//
// button_id: ID of the button; see spicesdk_io.h for named values
// pressed: true to set the button override (permanently set the button to be ON until cleared),
// false to clear the override (allow user's controller to provide input again)
// velocity: MIDI velocity of the button; only valid when pressed is true
// can be between 0.0 and 1.0, inclusive
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_button_func)(
uint32_t button_id,
bool pressed,
float velocity
);
// get_analog (v0.1 and up)
// gets the analog state
//
// button_id: ID of the button; see spicesdk_io.h for named values
// value: receives the state of the analog
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_analog_func)(
uint32_t analog_id,
float *value
);
// set_analog (v0.1 and up)
// sets or clears the analog override
//
// analog_id: ID of the analog; see spicesdk_io.h for named values
// override_active: true to set override (gain exclusive control)
// false to clear it (allow user's controller provide input again)
// value: value of the analog; only valid when override_active is true
// can be between 0.0 and 1.0, inclusive
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_analog_func)(
uint32_t analog_id,
bool override_active,
float value
);
// get_light (v0.1 and up)
// gets the last observed value of a light
//
// light_id: ID of the light; see spicesdk_io.h for named values
// value: output parameter for the light value; 0.0 to 1.0
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_get_light_func)(
uint32_t light_id,
float *value
);
// set_light (v0.1 and up)
// sets or clears the light override
//
// light_id: ID of the light; see spicesdk_io.h for named values
// light_value: output parameter for the light value; 0.0 to 1.0
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_light_func)(
uint32_t light_id,
bool override_active,
float light_value
);
// set_touch (v0.1 and up)
// adds or updates touch points
//
// points: array of touch points to add or update
// count: number of touch points in the array
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_touch_func)(
const SPICE_SDK_TOUCH_POINT *points,
uint32_t count
);
// clear_touch (v0.1 and up)
// clears touch points (i.e., no longer being touched)
//
// ids: array of touch point IDs to clear
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_clear_touch_func)(
const uint32_t *ids,
uint32_t count
);
// insert_card (v0.1 and up)
// simulates inserting an e-amuse card with the given ID
//
// unit: 0 for player 1, 1 for player 2
// card_id: null-terminated string of the card ID
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_insert_card_func)(
uint8_t unit,
const char *card_id
);
// set_keypad (v0.1 and up)
// sets keypad state
//
// make sure to hold the button long enough for the game to pick up
// 70ms is usually sufficient, except for DDR which needs 150ms
//
// unit: 0 for player 1, 1 for player 2
// key: '0' to '9' for numbers, 'A' for 00, 'D' for decimal point, 0 or '\0' to release all keys
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_set_keypad_func)(
uint8_t unit,
char key
);
typedef struct SPICE_SDK_V0 {
uint32_t size;
spice_sdk_log_func *log;
spice_sdk_get_game_info_func *get_game_info;
spice_sdk_get_avs_info_func *get_avs_info;
spice_sdk_get_button_func *get_button;
spice_sdk_set_button_func *set_button;
spice_sdk_get_analog_func *get_analog;
spice_sdk_set_analog_func *set_analog;
spice_sdk_get_light_func *get_light;
spice_sdk_set_light_func *set_light;
spice_sdk_set_touch_func *set_touch;
spice_sdk_clear_touch_func *clear_touch;
spice_sdk_insert_card_func *insert_card;
spice_sdk_set_keypad_func *set_keypad;
} SPICE_SDK_V0;
typedef void (__cdecl spice_sdk_destroy_callback_func)(
void
);
// init (v0.1 and up)
//
// version: supply 0
// destroy_callback: supply a function pointer that will be called when spice
// is shutting down
// sdk_functions: supply a pointer to SPICE_SDK_V0; ensure size field is initialized
// to sizeof(SPICE_SDK_V0) before calling this function
typedef SPICE_SDK_STATUS_CODE (__cdecl spice_sdk_init_func)(
uint32_t version,
spice_sdk_destroy_callback_func *destroy_callback,
void *sdk_functions
);
typedef int (__cdecl spice_sdk_entry_point_func)(
spice_sdk_init_func *init
);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // SPICE_SDK_H
File diff suppressed because it is too large Load Diff
+57
View File
@@ -0,0 +1,57 @@
#include <chrono>
#include <thread>
#include "sdk/include/spicesdk.h"
#include "sdk/include/spicesdk_io.h"
#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) {
while (!stop_token.stop_requested()) {
static bool state = false;
bool new_state;
spice.get_button(IIDX_Button_P1_Start, &new_state, nullptr);
if (!state && new_state) {
LOG_INFO("user pressed P1 Start button!");
}
state = new_state;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
SPICE_SDK_ENTRY_POINT
spice_sdk_entry_point(
spice_sdk_init_func *init
)
{
SPICE_SDK_STATUS_CODE status;
spice.size = sizeof(spice);
status = init(0, destroy_callback, &spice);
if (status != SPICE_SDK_STATUS_SUCCESS) {
return 0;
}
LOG_INFO("plugin loaded");
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();
}
}
+4
View File
@@ -0,0 +1,4 @@
LIBRARY sdk_sample_v0_cpp
EXPORTS
spice_sdk_entry_point
@@ -0,0 +1,318 @@
#include <string.h>
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include "sdk/include/spicesdk.h"
#include "sdk/include/spicesdk_io.h"
static SPICE_SDK_V0 spice;
static spice_sdk_destroy_callback_func destroy_callback;
static void test_logging();
static void test_game_info();
static void test_avs_info();
static void get_buttons();
static void set_buttons();
static void clear_buttons();
static void get_analogs();
static void set_analogs();
static void clear_analogs();
static void get_lights();
static void set_lights();
static void clear_lights();
static void set_touch();
static void clear_touch();
static void insert_card();
static void set_keypad();
static void clear_keypad();
static HANDLE worker_stop_event;
static HANDLE worker_handle;
static unsigned __stdcall worker_thread(void *arg);
// this sample assumes that the game is IIDX, but it doesn't check for it.
SPICE_SDK_ENTRY_POINT
spice_sdk_entry_point(
spice_sdk_init_func *init
)
{
SPICE_SDK_STATUS_CODE status;
memset(&spice, 0, sizeof(spice));
spice.size = sizeof(spice);
status = init(0, destroy_callback, &spice);
if (status != SPICE_SDK_STATUS_SUCCESS) {
return 0;
}
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "plugin loaded");
test_logging();
test_game_info();
test_avs_info();
worker_stop_event = CreateEventA(NULL, TRUE, FALSE, NULL);
if (!worker_stop_event) {
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "failed to create worker stop event");
return 0;
}
worker_handle = (HANDLE)_beginthreadex(
NULL, // security
0, // stack size
worker_thread, // function
NULL, // argument
0, // flags
NULL // thread id
);
if (!worker_handle) {
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "failed to create worker thread");
CloseHandle(worker_stop_event);
worker_stop_event = NULL;
return 0;
}
return 1;
}
void
__cdecl
destroy_callback(
void
)
{
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "plugin unloaded");
if (worker_stop_event) {
SetEvent(worker_stop_event);
}
if (worker_handle) {
WaitForSingleObject(worker_handle, INFINITE);
CloseHandle(worker_handle);
worker_handle = NULL;
}
if (worker_stop_event) {
CloseHandle(worker_stop_event);
worker_stop_event = NULL;
}
}
static unsigned __stdcall worker_thread(void *arg) {
int phase = 0;
while (WaitForSingleObject(worker_stop_event, 0) == WAIT_TIMEOUT) {
phase += 1;
switch (phase) {
case 1:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "get buttons...");
get_buttons();
break;
case 2:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set buttons...");
set_buttons();
break;
case 3:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear buttons...");
clear_buttons();
break;
case 4:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "get analogs...");
get_analogs();
break;
case 5:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set analogs...");
set_analogs();
break;
case 6:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear analogs...");
clear_analogs();
break;
case 7:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "get lights...");
get_lights();
break;
case 8:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set lights...");
set_lights();
break;
case 9:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear lights...");
clear_lights();
break;
case 10:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set touch...");
set_touch();
break;
case 11:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear touch...");
clear_touch();
break;
case 12:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "insert card...");
insert_card();
break;
case 13:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "set keypad...");
set_keypad();
break;
case 14:
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "clear keypad...");
clear_keypad();
break;
default:
phase = 0;
break;
}
if (phase != 0) {
WaitForSingleObject(worker_stop_event, 3000);
}
}
return 0;
}
static void test_logging() {
spice.log(SPICE_SDK_LOG_LEVEL_MISC, "sample_v0", "this is a misc message");
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", "this is an info message");
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "this is a warning message");
}
static void test_game_info() {
SPICE_SDK_GAME_INFO info;
SPICE_SDK_STATUS_CODE status;
status = spice.get_game_info(&info);
if (status != SPICE_SDK_STATUS_SUCCESS) {
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "get_game_info failed");
return;
}
char log_message[128];
snprintf(log_message, sizeof(log_message), "game info - name: %s", info.name);
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
}
static void test_avs_info() {
SPICE_SDK_AVS_INFO info;
SPICE_SDK_STATUS_CODE status;
status = spice.get_avs_info(&info);
if (status != SPICE_SDK_STATUS_SUCCESS) {
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "get_avs_info failed");
return;
}
char log_message[128];
snprintf(
log_message,
sizeof(log_message),
"avs - model: %s, dest: %c, spec: %c, rev: %c, ext: %s",
info.model, info.dest, info.spec, info.rev, info.ext);
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
}
static void get_buttons() {
bool pressed;
float velocity;
spice.get_button(IIDX_Button_P1_Headphone, &pressed, &velocity);
char log_message[128];
snprintf(
log_message,
sizeof(log_message),
"button P1_Headphone pressed: %s, velocity: %.2f",
pressed ? "ON" : "off", velocity);
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
}
static void set_buttons() {
spice.set_button(IIDX_Button_P1_1, true, 0.3f);
spice.set_button(IIDX_Button_P1_3, true, 0.5f);
spice.set_button(IIDX_Button_P1_5, true, 0.7f);
}
static void clear_buttons() {
spice.set_button(IIDX_Button_P1_1, false, 0.f);
spice.set_button(IIDX_Button_P1_3, false, 0.f);
spice.set_button(IIDX_Button_P1_5, false, 0.f);
}
static void get_analogs() {
float value;
spice.get_analog(IIDX_Analog_TT_P1, &value);
char log_message[128];
snprintf(
log_message,
sizeof(log_message),
"analog TT_P1: %.2f",
value);
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
}
static void set_analogs() {
spice.set_analog(IIDX_Analog_TT_P1, true, 0.25f);
spice.set_analog(IIDX_Analog_TT_P2, true, 0.75f);
}
static void clear_analogs() {
spice.set_analog(IIDX_Analog_TT_P1, false, 0.f);
spice.set_analog(IIDX_Analog_TT_P2, false, 0.f);
}
static void get_lights() {
float value;
SPICE_SDK_STATUS_CODE status;
status = spice.get_light(IIDX_Light_TT_P1_Resistance, &value);
if (status == SPICE_SDK_STATUS_SUCCESS) {
char log_message[64];
snprintf(log_message, sizeof(log_message), "P1 TT resistance value: %.2f", value);
spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0", log_message);
} else {
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", "get_light failed");
}
}
static void set_lights() {
spice.set_light(IIDX_Light_P1_Start, true, 1.f);
}
static void clear_lights() {
spice.set_light(IIDX_Light_P1_Start, false, 0.f);
}
static void set_touch() {
SPICE_SDK_TOUCH_POINT points[2] = {
{ .id = 1, .x = 100, .y = 200 },
{ .id = 2, .x = 300, .y = 400 },
};
spice.set_touch(points, 2);
}
static void clear_touch() {
uint32_t ids[2] = { 1, 2 };
spice.clear_touch(ids, 2);
}
static void insert_card() {
spice.insert_card(0, "E004010000001234");
}
static void set_keypad() {
SPICE_SDK_STATUS_CODE ret = spice.set_keypad(0, '3');
if (ret != SPICE_SDK_STATUS_SUCCESS) {
char log_message[64];
snprintf(log_message, sizeof(log_message), "set_keypad failed: %d", ret);
spice.log(SPICE_SDK_LOG_LEVEL_WARNING, "sample_v0", log_message);
}
}
static void clear_keypad() {
spice.set_keypad(0, 0);
}
@@ -0,0 +1,4 @@
LIBRARY sdk_sample_v0_flat_c
EXPORTS
spice_sdk_entry_point
+595
View File
@@ -0,0 +1,595 @@
#include <vector>
#include <mutex>
#include <shared_mutex>
#include "sdk.h"
#include "avs/game.h"
#include "games/io.h"
#include "launcher/launcher.h"
#include "misc/eamuse.h"
#include "sdk/include/spicesdk.h"
#include "touch/touch.h"
#include "util/logging.h"
#include "util/utils.h"
namespace sdk {
static spice_sdk_init_func sdk_init;
static spice_sdk_log_func sdk_log;
static spice_sdk_get_avs_info_func sdk_get_avs_info;
static spice_sdk_get_game_info_func sdk_get_game_info;
static spice_sdk_get_button_func sdk_get_button;
static spice_sdk_set_button_func sdk_set_button;
static spice_sdk_get_analog_func sdk_get_analog;
static spice_sdk_set_analog_func sdk_set_analog;
static spice_sdk_get_light_func sdk_get_light;
static spice_sdk_set_light_func sdk_set_light;
static spice_sdk_set_touch_func sdk_set_touch;
static spice_sdk_clear_touch_func sdk_clear_touch;
static spice_sdk_insert_card_func sdk_insert_card;
static spice_sdk_set_keypad_func sdk_set_keypad;
struct SdkModule {
std::string dll;
HINSTANCE module;
};
// DLLs
static int sdk_modules_count = 0;
static std::vector<SdkModule> sdk_modules_list;
static std::shared_mutex sdk_global_mutex;
// internal
static bool sdk_initialized = false;
static bool sdk_shutting_down = false;
static std::vector<Button> *buttons;
static std::vector<Analog> *analogs;
static std::vector<Light> *lights;
// callbacks
static std::mutex sdk_callback_registration_mutex;
static std::vector<spice_sdk_destroy_callback_func *> callbacks_destroy;
void register_sdk_hooks(std::string dll, HINSTANCE module) {
sdk_modules_list.emplace_back(std::move(dll), module);
sdk_modules_count += 1;
}
void init_sdk_modules() {
if (sdk_modules_count == 0) {
return;
}
// prepare
buttons = games::get_buttons(eamuse_get_game());
analogs = games::get_analogs(eamuse_get_game());
lights = games::get_lights(eamuse_get_game());
// under exclusive lock, mark the SDK helpers as being available
{
std::unique_lock lock(sdk_global_mutex);
sdk_initialized = true;
}
// without any locks, call into each DLL; this may call back into SDK functions
for (auto &module : sdk_modules_list) {
// get a pointer to dll's spice_sdk_entry_point
const auto entry_point = reinterpret_cast<spice_sdk_entry_point_func *>(
GetProcAddress(module.module, "spice_sdk_entry_point"));
if (!entry_point) {
continue;
}
// call into it on this thread
log_info("sdk", "registering SDK hooks for {}", module.dll);
const auto ret = entry_point(sdk_init);
log_info("sdk", "spice_sdk_entry_point returned {}", ret);
}
}
void fini_sdk_modules() {
// prevent multiple calls and further calls into sdk_init
{
std::unique_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return;
}
sdk_shutting_down = true;
}
// call into destroy callback of each DLL
// this may call back into SDK functions (e.g., for logging)
// so we leave sdk_initialized as-is
{
log_info("sdk", "calling destroy callbacks...");
std::lock_guard lock(sdk_callback_registration_mutex);
for (const auto& destroy : callbacks_destroy) {
destroy();
}
}
// under exclusive lock, mark the SDK functions as no longer available
{
std::unique_lock lock(sdk_global_mutex);
sdk_initialized = false;
}
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_init(
uint32_t version,
spice_sdk_destroy_callback_func *destroy_callback,
void *sdk_functions
)
{
std::shared_lock lock(sdk_global_mutex);
if (sdk_shutting_down || !sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
uint32_t size;
if (version != 0) {
log_warning("sdk", "sdk_init returning NOT_SUPPORTED due to invalid version: {}", version);
return SPICE_SDK_STATUS_NOT_SUPPORTED;
}
if (!destroy_callback) {
log_warning("sdk", "sdk_init returning INVALID_ARGUMENT_2 due to invalid destroy_callback pointer");
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
}
if (!sdk_functions) {
log_warning("sdk", "sdk_init returning INVALID_ARGUMENT_3 due to invalid sdk_functions pointer");
return SPICE_SDK_STATUS_INVALID_ARGUMENT_3;
}
auto *v0 = reinterpret_cast<SPICE_SDK_V0 *>(sdk_functions);
if (v0->size < RTL_SIZEOF_THROUGH_FIELD(SPICE_SDK_V0, set_keypad)) {
log_warning("sdk", "sdk_init returning TOO_SMALL due to size field of SPICE_SDK_V0 not being set");
return SPICE_SDK_STATUS_TOO_SMALL;
}
// we are trusting the size passed in by the caller
size = v0->size;
memset(v0, 0, size);
v0->size = size;
v0->log = sdk_log;
v0->get_game_info = sdk_get_game_info;
v0->get_avs_info = sdk_get_avs_info;
v0->get_button = sdk_get_button;
v0->set_button = sdk_set_button;
v0->get_analog = sdk_get_analog;
v0->set_analog = sdk_set_analog;
v0->get_light = sdk_get_light;
v0->set_light = sdk_set_light;
v0->set_touch = sdk_set_touch;
v0->clear_touch = sdk_clear_touch;
v0->insert_card = sdk_insert_card;
v0->set_keypad = sdk_set_keypad;
// end of 0.1
// any newer minor iterations will need to check the size
{
// destroy callbacks are only called upon successful return from this routine
std::lock_guard lock(sdk_callback_registration_mutex);
callbacks_destroy.push_back(destroy_callback);
}
log_info("sdk", "sdk_init returning SUCCESS");
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_log(
SPICE_SDK_LOG_LEVEL level,
const char *facility,
const char *message
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (facility == nullptr) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
}
const std::string facility_str = fmt::format("sdk::{}", facility);
switch (level) {
case SPICE_SDK_LOG_LEVEL_MISC:
log_misc(facility_str.c_str(), "{}", message);
break;
case SPICE_SDK_LOG_LEVEL_INFO:
log_info(facility_str.c_str(), "{}", message);
break;
case SPICE_SDK_LOG_LEVEL_WARNING:
log_warning(facility_str.c_str(), "{}", message);
break;
case SPICE_SDK_LOG_LEVEL_FATAL:
log_fatal(facility_str.c_str(), "{}", message);
break;
default:
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_get_avs_info(
SPICE_SDK_AVS_INFO *info
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (!info) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
// MDXJAA2025061002
strncpy(info->model, avs::game::MODEL, sizeof(info->model));
info->dest = avs::game::DEST[0];
info->spec = avs::game::SPEC[0];
info->rev = avs::game::REV[0];
strncpy(info->ext, avs::game::EXT, sizeof(info->ext));
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_get_game_info(
SPICE_SDK_GAME_INFO *info
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (!info) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
*info = {};
const std::string game_name = eamuse_get_game();
strncpy(info->name, game_name.c_str(), sizeof(info->name));
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_get_button (
uint32_t button_id,
bool *pressed,
float *velocity
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (!buttons) {
return SPICE_SDK_STATUS_NOT_INITIALIZED;
}
if (button_id >= buttons->size()) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
Button &button = (*buttons)[button_id];
if (pressed) {
*pressed = (GameAPI::Buttons::getState(RI_MGR, button) == GameAPI::Buttons::BUTTON_PRESSED);
}
if (velocity) {
*velocity = GameAPI::Buttons::getVelocity(RI_MGR, button);
}
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_set_button (
uint32_t button_id,
bool pressed,
float velocity
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (!buttons) {
return SPICE_SDK_STATUS_NOT_INITIALIZED;
}
if (button_id >= buttons->size()) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
if (pressed) {
velocity = std::clamp(velocity, 0.f, 1.f);
}
Button &button = (*buttons)[button_id];
if (pressed) {
button.override_state = pressed ? GameAPI::Buttons::BUTTON_PRESSED : GameAPI::Buttons::BUTTON_NOT_PRESSED;
button.override_velocity = velocity;
}
button.override_enabled = pressed;
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_get_analog (
uint32_t analog_id,
float *value
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (!analogs) {
return SPICE_SDK_STATUS_NOT_INITIALIZED;
}
if (analog_id >= analogs->size()) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
if (!value) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
}
Analog &analog = (*analogs)[analog_id];
*value = GameAPI::Analogs::getState(RI_MGR, analog);
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_set_analog (
uint32_t analog_id,
bool override_active,
float value
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (!analogs) {
return SPICE_SDK_STATUS_NOT_INITIALIZED;
}
if (analog_id >= analogs->size()) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
if (override_active) {
value = std::clamp(value, 0.f, 1.f);
}
Analog &analog = (*analogs)[analog_id];
if (override_active) {
analog.override_state = value;
}
analog.override_enabled = override_active;
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_get_light(
uint32_t light_id,
float *value
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (!lights) {
return SPICE_SDK_STATUS_NOT_INITIALIZED;
}
if (light_id >= lights->size()) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
if (!value) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
}
Light &light = (*lights)[light_id];
*value = GameAPI::Lights::readLight(RI_MGR, light);
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_set_light(
uint32_t light_id,
bool override_active,
float value
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (!lights) {
return SPICE_SDK_STATUS_NOT_INITIALIZED;
}
if (light_id >= lights->size()) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
if (override_active) {
value = std::clamp(value, 0.f, 1.f);
}
Light &light = (*lights)[light_id];
if (override_active) {
light.override_state = value;
}
light.override_enabled = override_active;
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_set_touch(
const SPICE_SDK_TOUCH_POINT *points,
uint32_t count
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (count == 0 || !points) {
return SPICE_SDK_STATUS_TOO_SMALL;
}
std::vector<TouchPoint> touch_points;
for (uint32_t i = 0; i < count; i++) {
const SPICE_SDK_TOUCH_POINT &point = points[i];
touch_points.emplace_back(TouchPoint {
.id = point.id,
.x = point.x,
.y = point.y,
.mouse = false,
});
}
touch_write_points(&touch_points);
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_clear_touch(
const uint32_t *ids,
uint32_t count
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (count == 0 || !ids) {
return SPICE_SDK_STATUS_TOO_SMALL;
}
std::vector<DWORD> touch_point_ids;
for (uint32_t i = 0; i < count; i++) {
touch_point_ids.push_back(ids[i]);
}
touch_remove_points(&touch_point_ids);
return SPICE_SDK_STATUS_SUCCESS;
}
SPICE_SDK_STATUS_CODE
__cdecl
sdk_insert_card(
uint8_t unit,
const char *card_id
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (unit >= 2) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
if (!card_id) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
}
if (strlen(card_id) != 16) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
}
// convert to binary
uint8_t card_bin[8] {};
if (!hex2bin(card_id, card_bin)) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
}
// insert
eamuse_card_insert(unit & 1, card_bin);
return SPICE_SDK_STATUS_SUCCESS;
}
struct KeypadMapping {
char character;
uint16_t state;
};
static KeypadMapping KEYPAD_MAPPINGS[] = {
{ '0', 1 << EAM_IO_KEYPAD_0 },
{ '1', 1 << EAM_IO_KEYPAD_1 },
{ '2', 1 << EAM_IO_KEYPAD_2 },
{ '3', 1 << EAM_IO_KEYPAD_3 },
{ '4', 1 << EAM_IO_KEYPAD_4 },
{ '5', 1 << EAM_IO_KEYPAD_5 },
{ '6', 1 << EAM_IO_KEYPAD_6 },
{ '7', 1 << EAM_IO_KEYPAD_7 },
{ '8', 1 << EAM_IO_KEYPAD_8 },
{ '9', 1 << EAM_IO_KEYPAD_9 },
{ 'A', 1 << EAM_IO_KEYPAD_00 },
{ 'D', 1 << EAM_IO_KEYPAD_DECIMAL },
};
SPICE_SDK_STATUS_CODE
__cdecl
sdk_set_keypad(
uint8_t unit,
char key
)
{
std::shared_lock lock(sdk_global_mutex);
if (!sdk_initialized) {
return SPICE_SDK_STATUS_TOO_LATE;
}
if (unit >= 2) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_1;
}
if (key == 0) {
eamuse_set_keypad_overrides(unit, 0);
return SPICE_SDK_STATUS_SUCCESS;
}
// find mapping
uint16_t state = 0;
bool mapping_found = false;
for (auto &mapping : KEYPAD_MAPPINGS) {
if (mapping.character == key) {
state |= mapping.state;
mapping_found = true;
break;
}
}
// check for error
if (!mapping_found) {
return SPICE_SDK_STATUS_INVALID_ARGUMENT_2;
}
// set
eamuse_set_keypad_overrides(unit, state);
return SPICE_SDK_STATUS_SUCCESS;
}
} // namespace sdk
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <windows.h>
namespace sdk {
void register_sdk_hooks(std::string dll, HINSTANCE module);
void init_sdk_modules();
void fini_sdk_modules();
}