From 7c15452c1eb901d2f129c8b3d0ec35b6700df9b0 Mon Sep 17 00:00:00 2001 From: ASleepyCat Date: Tue, 13 May 2025 12:27:20 +0800 Subject: [PATCH] Add support for outputting IIDX tape LED data via spiceapi (#315) ## Link to GitHub Issue, if one exists #41 (partially) ## Description of change This PR adds support for outputting IIDX tape LED data via spiceapi (`iidx_tapeled_get()`). It returns the data as a `dict`, with the key being the LED name and the value being the list of RGB values. You can optionally pass in a variadic list of names to filter the returned data. The new API function is only implemented in Python since I'm not able to test the wrappers in other languages. An example script using the new API is available in this Beef Board PR: https://github.com/HWXLR8/beef-board/pull/133. Demo use case: https://streamable.com/pq5e1e ## Testing Tested in IIDX 32 with custom Beef Board firmware along with a Python script to read and send the tape LED data over USB. --- src/spice2x/README.md | 19 +++++++ src/spice2x/api/modules/iidx.cpp | 56 +++++++++++++++++-- src/spice2x/api/modules/iidx.h | 7 +++ .../api/resources/python/spiceapi/iidx.py | 10 ++++ src/spice2x/games/iidx/bi2x_hook.cpp | 43 ++++---------- src/spice2x/games/iidx/iidx.cpp | 22 ++++++++ src/spice2x/games/iidx/iidx.h | 8 +++ src/spice2x/util/tapeled.h | 11 ++++ 8 files changed, 139 insertions(+), 37 deletions(-) diff --git a/src/spice2x/README.md b/src/spice2x/README.md index e7c754d..f224037 100644 --- a/src/spice2x/README.md +++ b/src/spice2x/README.md @@ -247,6 +247,25 @@ which also means that your hex edits are applicable directly. - sets the contents of the 16 segment display and disables writes from game - ticker_reset() - re-enables writes from game +- tapeled_get(name: str, ...) + - returns a list containing a dict of the current tape LED states. The dict keys are: + - `Stage Left` + - `Stage Right` + - `Cabinet Left` + - `Cabinet Right` + - `Control Panel Under` + - `Ceiling Left` + - `Title Left` + - `Title Right` + - `Ceiling Right` + - `Touch Panel Left` + - `Touch Panel Right` + - `Side Panel Left Inner` + - `Side Panel Left Outer` + - `Side Panel Left` + - `Side Panel Right Outer` + - `Side Panel Right Inner` + - `Side Panel Right` #### LCD - info() diff --git a/src/spice2x/api/modules/iidx.cpp b/src/spice2x/api/modules/iidx.cpp index 43860b2..b65423f 100644 --- a/src/spice2x/api/modules/iidx.cpp +++ b/src/spice2x/api/modules/iidx.cpp @@ -1,13 +1,9 @@ #include "iidx.h" #include -#include -#include "games/iidx/iidx.h" -#include "external/rapidjson/document.h" using namespace std::placeholders; using namespace rapidjson; - namespace api::modules { // settings @@ -17,6 +13,11 @@ namespace api::modules { functions["ticker_get"] = std::bind(&IIDX::ticker_get, this, _1, _2); functions["ticker_set"] = std::bind(&IIDX::ticker_set, this, _1, _2); functions["ticker_reset"] = std::bind(&IIDX::ticker_reset, this, _1, _2); + functions["tapeled_get"] = std::bind(&IIDX::tapeled_get, this, _1, _2); + + for (auto &light : games::iidx::TAPELED_MAPPING) { + this->lights_by_names.emplace(light.lightName, light); + } } /** @@ -69,4 +70,51 @@ namespace api::modules { // disable read only games::iidx::IIDXIO_LED_TICKER_READONLY = false; } + + /** + * tapeled_get() + * tapeled_get(name: str, ...) + */ + void IIDX::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::iidx::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()) { + const auto mapping = it->second.get(); + copy_tapeled_data(res, response_object, mapping); + } + } + } + + res.add_data(response_object); + } + + 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()); + 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/iidx.h b/src/spice2x/api/modules/iidx.h index 50fea1e..41ae20e 100644 --- a/src/spice2x/api/modules/iidx.h +++ b/src/spice2x/api/modules/iidx.h @@ -2,6 +2,7 @@ #include "api/module.h" #include "api/request.h" +#include "games/iidx/iidx.h" namespace api::modules { @@ -10,10 +11,16 @@ namespace api::modules { IIDX(); private: + // state + robin_hood::unordered_map> lights_by_names; // function definitions void ticker_get(Request &req, Response &res); void ticker_set(Request &req, Response &res); void ticker_reset(Request &req, Response &res); + void tapeled_get(Request &req, Response &res); + + // helper + void copy_tapeled_data(Response &res, rapidjson::Value &response_object, const tapeledutils::tape_led &mapping); }; } diff --git a/src/spice2x/api/resources/python/spiceapi/iidx.py b/src/spice2x/api/resources/python/spiceapi/iidx.py index b2736e1..79050a5 100644 --- a/src/spice2x/api/resources/python/spiceapi/iidx.py +++ b/src/spice2x/api/resources/python/spiceapi/iidx.py @@ -16,3 +16,13 @@ def iidx_ticker_set(con: Connection, text: str): def iidx_ticker_reset(con: Connection): req = Request("iidx", "ticker_reset") con.request(req) + + +def iidx_tapeled_get(con: Connection, *light_names): + req = Request("iidx", "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 4cbd577..3fdae2e 100644 --- a/src/spice2x/games/iidx/bi2x_hook.cpp +++ b/src/spice2x/games/iidx/bi2x_hook.cpp @@ -409,50 +409,27 @@ namespace games::iidx { * 16 - side panel right - 183 bytes - 61 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[] = { - { 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB }, - { 19, Lights::StageRightAvgR, Lights::StageRightAvgG, Lights::StageRightAvgB }, - { 45, Lights::CabinetLeftAvgR, Lights::CabinetLeftAvgG, Lights::CabinetLeftAvgB }, - { 45, Lights::CabinetRightAvgR, Lights::CabinetRightAvgG, Lights::CabinetRightAvgB }, - { 21, Lights::ControlPanelUnderAvgR, Lights::ControlPanelUnderAvgG, Lights::ControlPanelUnderAvgB }, - { 54, Lights::CeilingLeftAvgR, Lights::CeilingLeftAvgG, Lights::CeilingLeftAvgB }, - { 11, Lights::TitleLeftAvgR, Lights::TitleLeftAvgG, Lights::TitleLeftAvgB }, - { 11, Lights::TitleRightAvgR, Lights::TitleRightAvgG, Lights::TitleRightAvgB }, - { 54, Lights::CeilingRightAvgR, Lights::CeilingRightAvgG, Lights::CeilingRightAvgB }, - { 17, Lights::TouchPanelLeftAvgR, Lights::TouchPanelLeftAvgG, Lights::TouchPanelLeftAvgB }, - { 17, Lights::TouchPanelRightAvgR, Lights::TouchPanelRightAvgG, Lights::TouchPanelRightAvgB }, - { 68, Lights::SidePanelLeftInnerAvgR, Lights::SidePanelLeftInnerAvgG, Lights::SidePanelLeftInnerAvgB }, - { 68, Lights::SidePanelLeftOuterAvgR, Lights::SidePanelLeftOuterAvgG, Lights::SidePanelLeftOuterAvgB }, - { 61, Lights::SidePanelLeftAvgR, Lights::SidePanelLeftAvgG, Lights::SidePanelLeftAvgB }, - { 68, Lights::SidePanelRightOuterAvgR, Lights::SidePanelRightOuterAvgG, Lights::SidePanelRightOuterAvgB }, - { 68, Lights::SidePanelRightInnerAvgR, Lights::SidePanelRightInnerAvgG, Lights::SidePanelRightInnerAvgB }, - { 61, Lights::SidePanelRightAvgR, Lights::SidePanelRightAvgG, Lights::SidePanelRightAvgB }, - }; - // 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.capacity(); // 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); + + 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 (This != custom_node) { diff --git a/src/spice2x/games/iidx/iidx.cpp b/src/spice2x/games/iidx/iidx.cpp index 32cc1f5..e91f942 100644 --- a/src/spice2x/games/iidx/iidx.cpp +++ b/src/spice2x/games/iidx/iidx.cpp @@ -28,6 +28,8 @@ #include "util/sigscan.h" #include "util/utils.h" +#include "external/robin_hood.h" + #include "bi2a.h" #include "bi2x_hook.h" #include "ezusb.h" @@ -74,6 +76,26 @@ namespace games::iidx { bool IIDXIO_LED_TICKER_READONLY = false; std::mutex IIDX_LED_TICKER_LOCK; + tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL] = { + { 19, Lights::StageLeftAvgR, Lights::StageLeftAvgG, Lights::StageLeftAvgB, "Stage Left" }, + { 19, Lights::StageRightAvgR, Lights::StageRightAvgG, Lights::StageRightAvgB, "Stage Right" }, + { 45, Lights::CabinetLeftAvgR, Lights::CabinetLeftAvgG, Lights::CabinetLeftAvgB, "Cabinet Left" }, + { 45, Lights::CabinetRightAvgR, Lights::CabinetRightAvgG, Lights::CabinetRightAvgB, "Cabinet Right" }, + { 21, Lights::ControlPanelUnderAvgR, Lights::ControlPanelUnderAvgG, Lights::ControlPanelUnderAvgB, "Control Panel Under" }, + { 54, Lights::CeilingLeftAvgR, Lights::CeilingLeftAvgG, Lights::CeilingLeftAvgB, "Ceiling Left" }, + { 11, Lights::TitleLeftAvgR, Lights::TitleLeftAvgG, Lights::TitleLeftAvgB, "Title Left" }, + { 11, Lights::TitleRightAvgR, Lights::TitleRightAvgG, Lights::TitleRightAvgB, "Title Right" }, + { 54, Lights::CeilingRightAvgR, Lights::CeilingRightAvgG, Lights::CeilingRightAvgB, "Ceiling Right" }, + { 17, Lights::TouchPanelLeftAvgR, Lights::TouchPanelLeftAvgG, Lights::TouchPanelLeftAvgB, "Touch Panel Left" }, + { 17, Lights::TouchPanelRightAvgR, Lights::TouchPanelRightAvgG, Lights::TouchPanelRightAvgB, "Touch Panel Right" }, + { 68, Lights::SidePanelLeftInnerAvgR, Lights::SidePanelLeftInnerAvgG, Lights::SidePanelLeftInnerAvgB, "Side Panel Left Inner" }, + { 68, Lights::SidePanelLeftOuterAvgR, Lights::SidePanelLeftOuterAvgG, Lights::SidePanelLeftOuterAvgB, "Side Panel Left Outer" }, + { 61, Lights::SidePanelLeftAvgR, Lights::SidePanelLeftAvgG, Lights::SidePanelLeftAvgB, "Side Panel Left" }, + { 68, Lights::SidePanelRightOuterAvgR, Lights::SidePanelRightOuterAvgG, Lights::SidePanelRightOuterAvgB, "Side Panel Right Outer" }, + { 68, Lights::SidePanelRightInnerAvgR, Lights::SidePanelRightInnerAvgG, Lights::SidePanelRightInnerAvgB, "Side Panel Right Inner" }, + { 61, Lights::SidePanelRightAvgR, Lights::SidePanelRightAvgG, Lights::SidePanelRightAvgB, "Side Panel Right" }, + }; + static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) { if (lpSubKey != nullptr && phkResult != nullptr) { if (hKey == HKEY_LOCAL_MACHINE && diff --git a/src/spice2x/games/iidx/iidx.h b/src/spice2x/games/iidx/iidx.h index f1b10b1..0e92368 100644 --- a/src/spice2x/games/iidx/iidx.h +++ b/src/spice2x/games/iidx/iidx.h @@ -2,9 +2,13 @@ #include #include +#include #include "games/game.h" +#include "external/robin_hood.h" +#include "util/tapeled.h" + namespace games::iidx { // settings @@ -30,6 +34,10 @@ namespace games::iidx { extern bool IIDXIO_LED_TICKER_READONLY; extern std::mutex IIDX_LED_TICKER_LOCK; + constexpr int IIDX_TAPELED_TOTAL = 17; + // data mapping + extern tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL]; + class IIDXGame : public games::Game { public: IIDXGame(); diff --git a/src/spice2x/util/tapeled.h b/src/spice2x/util/tapeled.h index be18eca..6687bca 100644 --- a/src/spice2x/util/tapeled.h +++ b/src/spice2x/util/tapeled.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace tapeledutils { @@ -20,6 +22,15 @@ namespace tapeledutils { float b; } rgb_float3_t; + struct tape_led { + std::vector data; + int index_r, index_g, index_b; // Averaged RGB light output indexes + std::string lightName; + + tape_led(size_t data_size, int index_r, int index_g, int index_b, std::string lightName) + : data(std::vector(data_size)), index_r(index_r), index_g(index_g), index_b(index_b), lightName(std::move(lightName)) {} + }; + bool is_enabled(); rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size); size_t get_led_index_using_avg_algo(size_t data_size);