mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 14:20:42 -07:00
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:
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user