diff --git a/src/spice2x/README.md b/src/spice2x/README.md index 4425487..342ea7e 100644 --- a/src/spice2x/README.md +++ b/src/spice2x/README.md @@ -144,6 +144,13 @@ restriction is that the ID has to be a valid 64-bit unsigned integer. `busy` #### Card +- get_cards() + - returns the current card ID and source for each active card reader + - each entry contains `index`, `card_id`, and `source` + - `source` is `file` or `override` + - file entries also contain `file_name`, without the full configured path + - `-card0` and `-card1` overrides do not contain `file_name` + - this function only works when an API password is configured - insert(index: uint, card_id: hex) - inserts a card which gets read by the emulated card readers for the game - index has to be either 0 (for P1) or 1 (for P2) diff --git a/src/spice2x/api/controller.cpp b/src/spice2x/api/controller.cpp index 1fd41d5..1a6c4dd 100644 --- a/src/spice2x/api/controller.cpp +++ b/src/spice2x/api/controller.cpp @@ -337,9 +337,11 @@ bool Controller::process_request(ClientState *state, const char *in, size_t in_s if (module->name == request.module) { module_found = true; - // check password force - if (module->password_force && this->password.empty() && request.function != "session_refresh") { - Value err("Module requires the password to be set."); + // check password requirement + if (module->requires_password(request.function) + && this->password.empty() + && request.function != "session_refresh") { + Value err("Function requires the password to be set."); response.add_error(err); break; } diff --git a/src/spice2x/api/module.cpp b/src/spice2x/api/module.cpp index 700a562..af2a0e8 100644 --- a/src/spice2x/api/module.cpp +++ b/src/spice2x/api/module.cpp @@ -16,6 +16,14 @@ namespace api { this->password_force = password_force; } + void Module::require_password(const std::string &function) { + this->password_force_functions.emplace(function); + } + + bool Module::requires_password(const std::string &function) const { + return this->password_force || this->password_force_functions.contains(function); + } + void Module::handle(Request &req, Response &res) { // log module access diff --git a/src/spice2x/api/module.h b/src/spice2x/api/module.h index d41b787..262a893 100644 --- a/src/spice2x/api/module.h +++ b/src/spice2x/api/module.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "response.h" @@ -26,6 +27,13 @@ namespace api { // default constructor explicit Module(std::string name, bool password_force=false); + void require_password(const std::string &function); + + private: + + // functions which expose sensitive data or actions + std::unordered_set password_force_functions; + public: // virtual deconstructor @@ -35,6 +43,8 @@ namespace api { std::string name; bool password_force; + bool requires_password(const std::string &function) const; + // the magic void handle(Request &req, Response &res); diff --git a/src/spice2x/api/modules/card.cpp b/src/spice2x/api/modules/card.cpp index 5eb262a..ac65560 100644 --- a/src/spice2x/api/modules/card.cpp +++ b/src/spice2x/api/modules/card.cpp @@ -1,4 +1,5 @@ #include "card.h" +#include #include #include "external/rapidjson/document.h" #include "util/logging.h" @@ -11,8 +12,71 @@ using namespace rapidjson; namespace api::modules { + static bool normalize_card_id(const std::string &value, std::string &card_id) { + if (value.size() != 16) { + return false; + } + + uint8_t card_bin[8] {}; + if (!hex2bin(value.c_str(), card_bin)) { + return false; + } + + card_id = bin2hex(card_bin, std::size(card_bin)); + return true; + } + + static bool read_card_id(const std::filesystem::path &path, std::string &card_id) { + std::ifstream file(path); + char buffer[16] {}; + if (!file.read(buffer, std::size(buffer))) { + return false; + } + + return normalize_card_id(std::string(buffer, std::size(buffer)), card_id); + } + Card::Card() : Module("card") { + functions["get_cards"] = std::bind(&Card::get_cards, this, _1, _2); functions["insert"] = std::bind(&Card::insert, this, _1, _2); + require_password("get_cards"); + } + + /** + * get_cards() + */ + void Card::get_cards(Request &req, Response &res) { + auto &alloc = res.doc()->GetAllocator(); + + for (int index = 0; index < eamuse_get_game_keypads(); index++) { + std::string card_id; + std::string filename; + const auto card_override = eamuse_get_card_override(index); + const bool has_override = !card_override.empty(); + + if (has_override) { + if (!normalize_card_id(card_override, card_id)) { + continue; + } + } else { + const auto path = eamuse_get_card_path(index); + if (!read_card_id(path, card_id)) { + continue; + } + + const auto filename_u8 = path.filename().u8string(); + filename.assign(filename_u8.begin(), filename_u8.end()); + } + + Value card(kObjectType); + card.AddMember("index", index, alloc); + card.AddMember("card_id", Value(card_id.c_str(), alloc), alloc); + card.AddMember("source", Value(has_override ? "override" : "file", alloc), alloc); + if (!has_override) { + card.AddMember("file_name", Value(filename.c_str(), alloc), alloc); + } + res.add_data(card); + } } /** diff --git a/src/spice2x/api/modules/card.h b/src/spice2x/api/modules/card.h index 6012176..e90fbe3 100644 --- a/src/spice2x/api/modules/card.h +++ b/src/spice2x/api/modules/card.h @@ -12,6 +12,7 @@ namespace api::modules { private: // function definitions + void get_cards(Request &req, Response &res); void insert(Request &req, Response &res); }; } diff --git a/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/card.dart b/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/card.dart index 2a42d63..1bd487d 100644 --- a/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/card.dart +++ b/src/spice2x/api/resources/dart/spiceapi-websocket/src/wrappers/card.dart @@ -1,5 +1,32 @@ part of spiceapi; +class CardInfo { + final int index; + final String cardID; + final String source; + final String? fileName; + + CardInfo(this.index, this.cardID, this.source, this.fileName); +} + +Future> cardGetCards(Connection con) { + var req = Request("card", "get_cards"); + return con.request(req).then((res) { + List cards = []; + for (var value in res.getData()) { + cards.add( + CardInfo( + value["index"], + value["card_id"], + value["source"], + value["file_name"], + ), + ); + } + return cards; + }); +} + Future cardInsert(Connection con, int unit, String cardID) { var req = Request("card", "insert"); req.addParam(unit); diff --git a/src/spice2x/api/resources/dart/spiceapi/src/wrappers/card.dart b/src/spice2x/api/resources/dart/spiceapi/src/wrappers/card.dart index 2a42d63..1bd487d 100644 --- a/src/spice2x/api/resources/dart/spiceapi/src/wrappers/card.dart +++ b/src/spice2x/api/resources/dart/spiceapi/src/wrappers/card.dart @@ -1,5 +1,32 @@ part of spiceapi; +class CardInfo { + final int index; + final String cardID; + final String source; + final String? fileName; + + CardInfo(this.index, this.cardID, this.source, this.fileName); +} + +Future> cardGetCards(Connection con) { + var req = Request("card", "get_cards"); + return con.request(req).then((res) { + List cards = []; + for (var value in res.getData()) { + cards.add( + CardInfo( + value["index"], + value["card_id"], + value["source"], + value["file_name"], + ), + ); + } + return cards; + }); +} + Future cardInsert(Connection con, int unit, String cardID) { var req = Request("card", "insert"); req.addParam(unit); diff --git a/src/spice2x/api/resources/python/spiceapi/card.py b/src/spice2x/api/resources/python/spiceapi/card.py index d579ed8..3eff0d2 100644 --- a/src/spice2x/api/resources/python/spiceapi/card.py +++ b/src/spice2x/api/resources/python/spiceapi/card.py @@ -2,6 +2,10 @@ from .connection import Connection from .request import Request +def card_get_cards(con: Connection): + return con.request(Request("card", "get_cards")).get_data() + + def card_insert(con: Connection, unit: int, card_id: str): req = Request("card", "insert") req.add_param(unit) diff --git a/src/spice2x/misc/eamuse.cpp b/src/spice2x/misc/eamuse.cpp index 7e054f5..8968a00 100644 --- a/src/spice2x/misc/eamuse.cpp +++ b/src/spice2x/misc/eamuse.cpp @@ -34,6 +34,7 @@ static uint16_t KEYPAD_STATE_OVERRIDES_READER[] = {0, 0}; static uint16_t KEYPAD_STATE_OVERRIDES_OVERLAY[] = {0, 0}; static std::string EAMUSE_GAME_NAME; static ConfigKeypadBindings KEYPAD_BINDINGS {}; +static std::mutex KEYPAD_BINDINGS_LOCK; // auto card bool AUTO_INSERT_CARD[2] = {false, false}; @@ -87,23 +88,23 @@ bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) { return true; } - // get file path - std::filesystem::path path; - if (!KEYPAD_BINDINGS.card_paths[index].empty()) { - path = KEYPAD_BINDINGS.card_paths[index]; - } else { - path = index > 0 ? "card1.txt" : "card0.txt"; - } - // call the next function - return eamuse_get_card(path, card, index); + return eamuse_get_card(eamuse_get_card_path(index), card, index); +} + +std::filesystem::path eamuse_get_card_path(size_t index) { + std::lock_guard lock(KEYPAD_BINDINGS_LOCK); + if (index >= std::size(KEYPAD_BINDINGS.card_paths)) { + return {}; + } + if (!KEYPAD_BINDINGS.card_paths[index].empty()) { + return KEYPAD_BINDINGS.card_paths[index]; + } + return index > 0 ? "card1.txt" : "card0.txt"; } bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int index) { - // do a quick copy under lock - std::unique_lock lock(CARD_OVERRIDES_LOCK); - const auto card_override = CARD_OVERRIDES[index]; - lock.unlock(); + const auto card_override = eamuse_get_card_override(index); // Check if card overrides are present if (!card_override.empty()) { @@ -149,6 +150,14 @@ bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int index return eamuse_get_card_from_file(path, card, index); } +std::string eamuse_get_card_override(size_t index) { + std::lock_guard lock(CARD_OVERRIDES_LOCK); + if (index >= std::size(CARD_OVERRIDES)) { + return {}; + } + return CARD_OVERRIDES[index]; +} + bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card, int index) { // open file @@ -584,6 +593,7 @@ std::string eamuse_get_keypad_state_str(size_t unit) { } bool eamuse_keypad_state_naive() { + std::lock_guard lock(KEYPAD_BINDINGS_LOCK); return KEYPAD_BINDINGS.keypads[0].empty() && KEYPAD_BINDINGS.keypads[1].empty(); } @@ -595,7 +605,9 @@ void eamuse_set_game(std::string game) { } void eamuse_update_keypad_bindings() { - KEYPAD_BINDINGS = Config::getInstance().getKeypadBindings(EAMUSE_GAME_NAME); + auto bindings = Config::getInstance().getKeypadBindings(EAMUSE_GAME_NAME); + std::lock_guard lock(KEYPAD_BINDINGS_LOCK); + KEYPAD_BINDINGS = std::move(bindings); } const std::string &eamuse_get_game() { diff --git a/src/spice2x/misc/eamuse.h b/src/spice2x/misc/eamuse.h index d772bb5..366ba10 100644 --- a/src/spice2x/misc/eamuse.h +++ b/src/spice2x/misc/eamuse.h @@ -43,6 +43,8 @@ extern std::string AUTO_PIN_MACRO_TRIGGER[2]; bool eamuse_get_card(int active_count, int unit_id, uint8_t *card); bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int unit_id); bool eamuse_get_card_from_file(const std::filesystem::path &path, uint8_t *card, int index); +std::filesystem::path eamuse_get_card_path(size_t index); +std::string eamuse_get_card_override(size_t index); void eamuse_card_insert(int unit); void eamuse_card_insert(int unit, const uint8_t *card);