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/structuredmessage.cpp
reader/crypt.cpp reader/crypt.cpp
# sdk
sdk/sdk.cpp
# stubs # stubs
stubs/stubs.cpp stubs/stubs.cpp
@@ -860,6 +863,36 @@ if(NOT MSVC)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif() 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 # output directories
#################### ####################
@@ -869,14 +902,14 @@ set_target_properties(spicetools_cfg spicetools_cfg_linux
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools") RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
# output 32bit # 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 PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32") RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
# output 64bit # 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 PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64" 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 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. 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 ## License
Unless otherwise noted, all files are licensed under the GPLv3. Unless otherwise noted, all files are licensed under the GPLv3.
See the LICENSE file for the full license text. 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="spice2x-$(date +%y)-$(date +%m)-$(date +%d).zip"
DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.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.' 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_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" 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_XP32="spicetools_cfg spicetools_spice"
TARGETS_XP64="spicetools_spice64" TARGETS_XP64="spicetools_spice64"
@@ -269,6 +269,8 @@ rm -rf ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS} mkdir -p ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
mkdir -p ${OUTDIR_EXTRAS}/linux mkdir -p ${OUTDIR_EXTRAS}/linux
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/32
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/64
if ((BUILD_XP > 0)) if ((BUILD_XP > 0))
then then
mkdir -p ${OUTDIR_EXTRAS}/winxp 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/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.exe ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.pdb ${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.exe ${OUTDIR} 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.pdb ${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 else
# release files # release files
cp ${BUILDDIR_32}/spicetools/spicecfg.exe ${OUTDIR} 2>/dev/null 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.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_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/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.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/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/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/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.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/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)) if ((BUILD_XP > 0))
then then
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null 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?" echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
fi fi
# sdk headers
cp -r ./sdk/include ${OUTDIR_EXTRAS}/sdk
# copy resources # copy resources
rm -rf ${OUTDIR_EXTRAS}/api rm -rf ${OUTDIR_EXTRAS}/api
mkdir ${OUTDIR_EXTRAS}/api mkdir ${OUTDIR_EXTRAS}/api
+7
View File
@@ -98,6 +98,7 @@
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "rawinput/touch.h" #include "rawinput/touch.h"
#include "reader/reader.h" #include "reader/reader.h"
#include "sdk/sdk.h"
#include "stubs/stubs.h" #include "stubs/stubs.h"
#include "touch/touch.h" #include "touch/touch.h"
#include "util/cpuutils.h" #include "util/cpuutils.h"
@@ -2358,6 +2359,7 @@ int main_implementation(int argc, char *argv[]) {
}); });
} else { } else {
bt5api_hook(module); bt5api_hook(module);
sdk::register_sdk_hooks(hook, module);
} }
} }
@@ -2392,6 +2394,9 @@ int main_implementation(int argc, char *argv[]) {
bt5api_init(); bt5api_init();
} }
// init SDK
sdk::init_sdk_modules();
// API // API
if (api_enable || std::min(api_serial_port.size(), api_serial_baud.size()) > 0) { 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); 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(); bt5api_dispose();
} }
sdk::fini_sdk_modules();
// stop raw input // stop raw input
RI_MGR.reset(); RI_MGR.reset();
+3
View File
@@ -14,6 +14,7 @@
#include "launcher.h" #include "launcher.h"
#include "logger.h" #include "logger.h"
#include "nvapi/nvapi.h" #include "nvapi/nvapi.h"
#include "sdk/sdk.h"
namespace launcher { namespace launcher {
@@ -23,6 +24,8 @@ namespace launcher {
// therefore, subsystems need to be guarded against multiple unload attempts // therefore, subsystems need to be guarded against multiple unload attempts
log_info("launcher", "stopping subsystems"); log_info("launcher", "stopping subsystems");
sdk::fini_sdk_modules();
// reset monitor settings // reset monitor settings
reset_monitor_on_exit(); 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();
}