diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index 5711010..8b80adb 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -332,6 +332,7 @@ set(SOURCE_FILES ${SOURCE_FILES} api/modules/control.cpp api/modules/touch.cpp api/modules/iidx.cpp + api/modules/sdvx.cpp api/serial.cpp api/modules/drs.cpp api/modules/lcd.cpp diff --git a/src/spice2x/README.md b/src/spice2x/README.md index 63bfbe6..e6eae30 100644 --- a/src/spice2x/README.md +++ b/src/spice2x/README.md @@ -267,6 +267,20 @@ which also means that your hex edits are applicable directly. - `Side Panel Right Inner` - `Side Panel Right` +#### SDVX +- tapeled_get(name: str, ...) + - returns a list containing a dict of the current tape LED states. The dict keys are: + - `Title` + - `Upper Left Speaker` + - `Upper Right Speaker` + - `Left Wing` + - `Right Wing` + - `Control Panel` + - `Lower Left Speaker` + - `Lower Right Speaker` + - `Woofer` + - `V Unit` + #### LCD - info() - returns information about the serial LCD controller some games use diff --git a/src/spice2x/api/client.h b/src/spice2x/api/client.h new file mode 100644 index 0000000..c73fc6d --- /dev/null +++ b/src/spice2x/api/client.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +namespace api { + + extern std::atomic_uint32_t CLIENT_COUNT; + + inline bool has_clients() { + return CLIENT_COUNT.load(std::memory_order_relaxed) > 0; + } +} diff --git a/src/spice2x/api/controller.cpp b/src/spice2x/api/controller.cpp index 19713fa..1fd41d5 100644 --- a/src/spice2x/api/controller.cpp +++ b/src/spice2x/api/controller.cpp @@ -5,6 +5,7 @@ #include +#include "client.h" #include "cfg/configurator.h" #include "external/rapidjson/document.h" #include "util/crypt.h" @@ -28,6 +29,7 @@ #include "modules/lcd.h" #include "modules/lights.h" #include "modules/memory.h" +#include "modules/sdvx.h" #include "modules/touch.h" #include "modules/resize.h" #include "request.h" @@ -36,6 +38,8 @@ using namespace rapidjson; using namespace api; +std::atomic_uint32_t api::CLIENT_COUNT = 0; + Controller::Controller(unsigned short port, std::string password, bool pretty) : port(port), password(std::move(password)), pretty(pretty) { @@ -411,8 +415,11 @@ void Controller::init_state(api::ClientState *state) { state->modules.push_back(new modules::LCD()); state->modules.push_back(new modules::Lights()); state->modules.push_back(new modules::Memory()); + state->modules.push_back(new modules::SDVX()); state->modules.push_back(new modules::Touch()); state->modules.push_back(new modules::Resize()); + + CLIENT_COUNT.fetch_add(1, std::memory_order_relaxed); } void Controller::free_state(api::ClientState *state) { @@ -424,6 +431,8 @@ void Controller::free_state(api::ClientState *state) { // free cipher delete state->cipher; + + CLIENT_COUNT.fetch_sub(1, std::memory_order_relaxed); } void Controller::free_socket() { diff --git a/src/spice2x/api/modules/iidx.cpp b/src/spice2x/api/modules/iidx.cpp index b65423f..cb47df7 100644 --- a/src/spice2x/api/modules/iidx.cpp +++ b/src/spice2x/api/modules/iidx.cpp @@ -106,14 +106,16 @@ namespace api::modules { void IIDX::copy_tapeled_data(Response &res, Value &response_object, const tapeledutils::tape_led &mapping) { // Create an array for the light state Value light_state(kArrayType); - light_state.Reserve(mapping.data.capacity() * 3, res.doc()->GetAllocator()); + light_state.Reserve( + static_cast(mapping.data.size() * 3), + res.doc()->GetAllocator()); for (const auto [r, g, b] : mapping.data) { light_state.PushBack(r, res.doc()->GetAllocator()); light_state.PushBack(g, res.doc()->GetAllocator()); light_state.PushBack(b, res.doc()->GetAllocator()); } - // Can't use StringRef here, turns some strings partially into null bytes for some reason + // can't use StringRef here, turns some strings partially into null bytes for some reason Value light_name(mapping.lightName.c_str(), res.doc()->GetAllocator()); response_object.AddMember(light_name, light_state, res.doc()->GetAllocator()); } diff --git a/src/spice2x/api/modules/sdvx.cpp b/src/spice2x/api/modules/sdvx.cpp new file mode 100644 index 0000000..b4c059c --- /dev/null +++ b/src/spice2x/api/modules/sdvx.cpp @@ -0,0 +1,67 @@ +#include "sdvx.h" + +#include + +using namespace std::placeholders; +using namespace rapidjson; + +namespace api::modules { + + SDVX::SDVX() : Module("sdvx") { + functions["tapeled_get"] = std::bind(&SDVX::tapeled_get, this, _1, _2); + + for (auto &light : games::sdvx::TAPELED_MAPPING) { + lights_by_names.emplace(light.lightName, light); + } + } + + /** + * tapeled_get() + * tapeled_get(name: str, ...) + */ + void SDVX::tapeled_get(Request &req, Response &res) { + Value response_object(kObjectType); + + // all tape leds + if (req.params.Size() == 0) { + // iterate through each device and dump its lights data into the response + for (const auto &mapping : games::sdvx::TAPELED_MAPPING) { + copy_tapeled_data(res, response_object, mapping); + } + } else { + // specified light names + for (Value ¶m : req.params.GetArray()) { + // check params + if (!param.IsString()) { + error_type(res, "name", "string"); + return; + } + + const auto name = param.GetString(); + if (const auto &it = lights_by_names.find(name); it != lights_by_names.end()) { + copy_tapeled_data(res, response_object, it->second.get()); + } + } + } + + res.add_data(response_object); + } + + void SDVX::copy_tapeled_data(Response &res, Value &response_object, + const tapeledutils::tape_led &mapping) + { + Value light_state(kArrayType); + light_state.Reserve( + static_cast(mapping.data.size() * 3), + res.doc()->GetAllocator()); + for (const auto [r, g, b] : mapping.data) { + light_state.PushBack(r, res.doc()->GetAllocator()); + light_state.PushBack(g, res.doc()->GetAllocator()); + light_state.PushBack(b, res.doc()->GetAllocator()); + } + + // can't use StringRef here, turns some strings partially into null bytes for some reason + Value light_name(mapping.lightName.c_str(), res.doc()->GetAllocator()); + response_object.AddMember(light_name, light_state, res.doc()->GetAllocator()); + } +} diff --git a/src/spice2x/api/modules/sdvx.h b/src/spice2x/api/modules/sdvx.h new file mode 100644 index 0000000..d9519d0 --- /dev/null +++ b/src/spice2x/api/modules/sdvx.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "api/module.h" +#include "api/request.h" +#include "external/robin_hood.h" +#include "games/sdvx/sdvx.h" + +namespace api::modules { + + class SDVX : public Module { + public: + SDVX(); + + private: + robin_hood::unordered_map> lights_by_names; + + void tapeled_get(Request &req, Response &res); + void copy_tapeled_data(Response &res, rapidjson::Value &response_object, + const tapeledutils::tape_led &mapping); + }; +} diff --git a/src/spice2x/api/resources/python/spiceapi/__init__.py b/src/spice2x/api/resources/python/spiceapi/__init__.py index b7fe465..f0da213 100644 --- a/src/spice2x/api/resources/python/spiceapi/__init__.py +++ b/src/spice2x/api/resources/python/spiceapi/__init__.py @@ -7,6 +7,7 @@ from .coin import * from .control import * from .exceptions import * from .iidx import * +from .sdvx import * from .info import * from .keypads import * from .lights import * diff --git a/src/spice2x/api/resources/python/spiceapi/sdvx.py b/src/spice2x/api/resources/python/spiceapi/sdvx.py new file mode 100644 index 0000000..57ebdab --- /dev/null +++ b/src/spice2x/api/resources/python/spiceapi/sdvx.py @@ -0,0 +1,12 @@ +from .connection import Connection +from .request import Request + + +def sdvx_tapeled_get(con: Connection, *light_names): + req = Request("sdvx", "tapeled_get") + + for light_name in light_names: + req.add_param(light_name) + + res = con.request(req) + return res.get_data() diff --git a/src/spice2x/games/iidx/bi2x_hook.cpp b/src/spice2x/games/iidx/bi2x_hook.cpp index 4c9dc81..8e04a27 100644 --- a/src/spice2x/games/iidx/bi2x_hook.cpp +++ b/src/spice2x/games/iidx/bi2x_hook.cpp @@ -3,6 +3,7 @@ #if SPICE64 #include +#include "api/client.h" #include "util/detour.h" #include "util/logging.h" #include "util/utils.h" @@ -443,10 +444,12 @@ namespace games::iidx { GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b); - for (unsigned int i = 0; i < data_size; ++i) { - map.data[i].r = data[i * 3]; - map.data[i].g = data[i * 3 + 1]; - map.data[i].b = data[i * 3 + 2]; + if (api::has_clients()) { + for (size_t i = 0; i < data_size; ++i) { + map.data[i].r = data[i * 3]; + map.data[i].g = data[i * 3 + 1]; + map.data[i].b = data[i * 3 + 2]; + } } } diff --git a/src/spice2x/games/sdvx/bi2x_hook.cpp b/src/spice2x/games/sdvx/bi2x_hook.cpp index 09cb08f..dc53f81 100644 --- a/src/spice2x/games/sdvx/bi2x_hook.cpp +++ b/src/spice2x/games/sdvx/bi2x_hook.cpp @@ -3,6 +3,7 @@ #if SPICE64 #include +#include "api/client.h" #include "util/detour.h" #include "util/logging.h" #include "util/utils.h" @@ -349,43 +350,29 @@ namespace games::sdvx { * 9 - v unit - 258 bytes - 86 colors * * data is stored in RGB order, 3 bytes per color - * - * TODO: expose this data via API */ - // data mapping - static struct TapeLedMapping { - size_t data_size; - int index_r, index_g, index_b; - - TapeLedMapping(size_t data_size, int index_r, int index_g, int index_b) - : data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {} - - } mapping[] = { - { 74, Lights::TITLE_AVG_R, Lights::TITLE_AVG_G, Lights::TITLE_AVG_B }, - { 12, Lights::UPPER_LEFT_SPEAKER_AVG_R, Lights::UPPER_LEFT_SPEAKER_AVG_G, Lights::UPPER_LEFT_SPEAKER_AVG_B }, - { 12, Lights::UPPER_RIGHT_SPEAKER_AVG_R, Lights::UPPER_RIGHT_SPEAKER_AVG_G, Lights::UPPER_RIGHT_SPEAKER_AVG_B }, - { 56, Lights::LEFT_WING_AVG_R, Lights::LEFT_WING_AVG_G, Lights::LEFT_WING_AVG_B }, - { 56, Lights::RIGHT_WING_AVG_R, Lights::RIGHT_WING_AVG_G, Lights::RIGHT_WING_AVG_B }, - { 94, Lights::CONTROL_PANEL_AVG_R, Lights::CONTROL_PANEL_AVG_G, Lights::CONTROL_PANEL_AVG_B }, - { 12, Lights::LOWER_LEFT_SPEAKER_AVG_R, Lights::LOWER_LEFT_SPEAKER_AVG_G, Lights::LOWER_LEFT_SPEAKER_AVG_B }, - { 12, Lights::LOWER_RIGHT_SPEAKER_AVG_R, Lights::LOWER_RIGHT_SPEAKER_AVG_G, Lights::LOWER_RIGHT_SPEAKER_AVG_B }, - { 14, Lights::WOOFER_AVG_R, Lights::WOOFER_AVG_G, Lights::WOOFER_AVG_B }, - { 86, Lights::V_UNIT_AVG_R, Lights::V_UNIT_AVG_G, Lights::V_UNIT_AVG_B }, - }; - // check index bounds - if (tapeledutils::is_enabled() && index < std::size(mapping)) { - auto &map = mapping[index]; + if (tapeledutils::is_enabled() && index < std::size(TAPELED_MAPPING)) { + auto &map = TAPELED_MAPPING[index]; + const auto data_size = map.data.size(); // pick a color to use - const auto rgb = tapeledutils::pick_color_from_led_tape(data, map.data_size); + const auto rgb = tapeledutils::pick_color_from_led_tape(data, data_size); // program the lights into API auto &lights = get_lights(); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b); + + if (api::has_clients()) { + for (size_t i = 0; i < data_size; ++i) { + map.data[i].r = data[i * 3]; + map.data[i].g = data[i * 3 + 1]; + map.data[i].b = data[i * 3 + 2]; + } + } } if (This != custom_node) { diff --git a/src/spice2x/games/sdvx/sdvx.cpp b/src/spice2x/games/sdvx/sdvx.cpp index a263e4a..e20f94b 100644 --- a/src/spice2x/games/sdvx/sdvx.cpp +++ b/src/spice2x/games/sdvx/sdvx.cpp @@ -61,6 +61,19 @@ namespace games::sdvx { static HKEY real_asio_reg_handle = nullptr; static HKEY real_asio_device_reg_handle = nullptr; + tapeledutils::tape_led TAPELED_MAPPING[SDVX_TAPELED_TOTAL] = { + { 74, Lights::TITLE_AVG_R, Lights::TITLE_AVG_G, Lights::TITLE_AVG_B, "Title" }, + { 12, Lights::UPPER_LEFT_SPEAKER_AVG_R, Lights::UPPER_LEFT_SPEAKER_AVG_G, Lights::UPPER_LEFT_SPEAKER_AVG_B, "Upper Left Speaker" }, + { 12, Lights::UPPER_RIGHT_SPEAKER_AVG_R, Lights::UPPER_RIGHT_SPEAKER_AVG_G, Lights::UPPER_RIGHT_SPEAKER_AVG_B, "Upper Right Speaker" }, + { 56, Lights::LEFT_WING_AVG_R, Lights::LEFT_WING_AVG_G, Lights::LEFT_WING_AVG_B, "Left Wing" }, + { 56, Lights::RIGHT_WING_AVG_R, Lights::RIGHT_WING_AVG_G, Lights::RIGHT_WING_AVG_B, "Right Wing" }, + { 94, Lights::CONTROL_PANEL_AVG_R, Lights::CONTROL_PANEL_AVG_G, Lights::CONTROL_PANEL_AVG_B, "Control Panel" }, + { 12, Lights::LOWER_LEFT_SPEAKER_AVG_R, Lights::LOWER_LEFT_SPEAKER_AVG_G, Lights::LOWER_LEFT_SPEAKER_AVG_B, "Lower Left Speaker" }, + { 12, Lights::LOWER_RIGHT_SPEAKER_AVG_R, Lights::LOWER_RIGHT_SPEAKER_AVG_G, Lights::LOWER_RIGHT_SPEAKER_AVG_B, "Lower Right Speaker" }, + { 14, Lights::WOOFER_AVG_R, Lights::WOOFER_AVG_G, Lights::WOOFER_AVG_B, "Woofer" }, + { 86, Lights::V_UNIT_AVG_R, Lights::V_UNIT_AVG_G, Lights::V_UNIT_AVG_B, "V Unit" }, + }; + static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) { if (lpSubKey != nullptr && phkResult != nullptr && diff --git a/src/spice2x/games/sdvx/sdvx.h b/src/spice2x/games/sdvx/sdvx.h index bc868fe..6e54054 100644 --- a/src/spice2x/games/sdvx/sdvx.h +++ b/src/spice2x/games/sdvx/sdvx.h @@ -6,6 +6,7 @@ #include "avs/game.h" #include "games/game.h" +#include "util/tapeled.h" namespace games::sdvx { @@ -25,6 +26,9 @@ namespace games::sdvx { // states extern bool SHOW_VM_MONITOR_WARNING; + constexpr int SDVX_TAPELED_TOTAL = 10; + extern tapeledutils::tape_led TAPELED_MAPPING[SDVX_TAPELED_TOTAL]; + static inline bool is_valkyrie_model() { return ( avs::game::is_model("KFC") &&