Add more legacy tools that I found while cleaning up my server

This commit is contained in:
icex2
2025-06-21 18:01:55 +02:00
parent 19a31fe2e9
commit 784a06ec9e
2532 changed files with 231258 additions and 0 deletions
Binary file not shown.
Binary file not shown.
+59
View File
@@ -0,0 +1,59 @@
BETA1 -> BETA2
Added .2dx support
Fixed loading .ifs that contain solely audio
BETA2 -> BETA3
Added a seperate load thread, UI now no longer freezes while loading
Added a progress bar for all of you who like knowing how far it is with loading
Possible compatibility improvements
Temporary dirty fix for extraction failing when cache had just been cleared
BETA3 -> BETA4
Added .sd9 support (right click icon/open button/treeview)
Minor improvements
BETA4 -> BETA4FIX
Fixed .2dx files stucking after creating cache
BETA4 -> BETA5
Performance improvements
Changed lame progressbar to a circular one
Fixed failing to open an archive after the cache was cleared
Binary files are now included in the executable
TargaImage is now included in the executable
Changed the horribly misspelled "dependancies" to "runtimes" (in the packed version)
??? =)
BETA5 -> BETA5 R1
Fixed application occasionally failing to load one archive after the other with an image selected
BETA5 R1 -> BETA5 R2
Added a notification for available updates (only visible when running out-of-date version >BETA5 R2)
BETA5 R2 -> BETA5 R3
Added a bug-report generation tool
BETA5 R3 -> BETA5 R4
Added a debug console for troubleshooting
BETA5 R4 -> BETA R5
Cache now relies on md5 checksum instead of file name
Added pirate-chan ^w^
BETA5 R5 -> BETA5 R6
Fixed the problem occuring with read-only .ifs / .2dx files
BETA5 R6 -> BETA5 R7
Fixed arrow-navigation in the file treeview
+2
View File
@@ -0,0 +1,2 @@
Anyone here speaking (near-)fluent English and Japanese?
I could use some help correcting the Google Translate derps.
+14
View File
@@ -0,0 +1,14 @@
This software is licensed under:
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 Intl.
BY-NC-SA
http://creativecommons.org/licenses/by-nc-sa/4.0/
TargaImage.dll is licensed under CPOL:
http://www.codeproject.com/info/cpol10.aspx
(For developers)
Yes, you are allowed to decompile the program and use bits of the
code in one (or multiple) of your projects, but abide by the license and give
proper credit where necessary.
+6
View File
@@ -0,0 +1,6 @@
TargaImage.dll by David Polomis modified by Arena for transperancy fix
TargaImage.dll is licensed under CPOL:
http://www.codeproject.com/info/cpol10.aspx
TargaImage.dll is now included in the program
Binaries by Mutiny
@@ -0,0 +1,22 @@
Please use the following steps to report bugs:
1) Open BemaniExplorer
2) Right-click the titlebar
3) Click BUG REPORT
4) Fill out the form
5) Generate the report
6) Optional, but please do: Upload the report (for ex. Dropbox or Pastebin)
7) PM the report to Arena
NOTE: You do not have to include the log files in your upload
they are automatically included in the bug report file
If you have a very specific bug (e.g. the program not outputting any errors)
please debug before you open the archive/file(s) and attach the debug log.
This can be done by:
1) Right-click the titlebar
2) Open the DEBUG CONSOLE
3) Open your archive/file(s)
4) Right click the debug console and click EXPORT
+30
View File
@@ -0,0 +1,30 @@
1) Open BemaniExplorer.exe as Administrator
2) Open .ifs or .2dx file
3) You can export all by right-clicking the B icon/open button/treeview
4) You can export single by right-clicking the image
5) You can convert .sd9 files by right-clicking the B icon/open button/treeview
> Please report bugs to Arena (PM)
> Requirements:
Windows XP or higher
.NET Framework 3.0
VCRedist_x86 2013
2GB of usable system RAM (x86 4GB> preferred, x64 6GB> preferred)
any CPU
> Notes:
Image parsing is done with the CPU, loading times may vary depending
on your CPU.
If you report errors, please include all the available logs.
The usage of the application beyond this point is the full responsibility
of whoever uses it.
If in any case file damage or physical damage occurs with this program as
a cause (I'm pretty certain it wont), the end user is responsible.
(For developers) the cache folder is now hidden to prevent unexperienced people
from faffing about and breaking the program, you can get to it by adding "\cache" to
the directory where the .exe is located in
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
# SpiceAPI C++ Library
This library is still a bit experimental and might contain bugs.
To include it into your project, it's recommended to just copy the
files into your source directory.
To use the wrappers, RapidJSON is required and you might need to
adjust the include paths for your project's build.
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
@@ -0,0 +1,174 @@
#include <iostream>
#include <ws2tcpip.h>
#include "connection.h"
namespace spiceapi {
// settings
static const size_t RECEIVE_BUFFER_SIZE = 64 * 1024;
static const int RECEIVE_TIMEOUT = 1000;
}
spiceapi::Connection::Connection(std::string host, uint16_t port, std::string password) {
this->host = host;
this->port = port;
this->password = password;
this->socket = INVALID_SOCKET;
this->cipher = nullptr;
// WSA startup
WSADATA wsa_data;
int error = WSAStartup(MAKEWORD(2, 2), &wsa_data);
if (error) {
std::cerr << "Failed to start WSA: " << error << std::endl;
exit(1);
}
}
spiceapi::Connection::~Connection() {
// clean up
if (this->cipher != nullptr)
delete this->cipher;
// cleanup WSA
WSACleanup();
}
void spiceapi::Connection::cipher_alloc() {
// delete old cipher
if (this->cipher != nullptr) {
delete this->cipher;
this->cipher = nullptr;
}
// create new cipher if password is set
if (this->password.length() > 0) {
this->cipher = new RC4(
(uint8_t *) this->password.c_str(),
strlen(this->password.c_str()));
}
}
bool spiceapi::Connection::check() {
int result = 0;
// check if socket is invalid
if (this->socket == INVALID_SOCKET) {
// get all addresses
addrinfo *addr_list;
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
if ((result = getaddrinfo(
this->host.c_str(),
std::to_string(this->port).c_str(),
&hints,
&addr_list))) {
std::cerr << "getaddrinfo failed: " << result << std::endl;
return false;
}
// check all addresses
for (addrinfo *addr = addr_list; addr != NULL; addr = addr->ai_next) {
// try open socket
this->socket = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (this->socket == INVALID_SOCKET) {
std::cerr << "socket failed: " << WSAGetLastError() << std::endl;
freeaddrinfo(addr_list);
return false;
}
// try connect
result = connect(this->socket, addr->ai_addr, (int) addr->ai_addrlen);
if (result == SOCKET_ERROR) {
closesocket(this->socket);
this->socket = INVALID_SOCKET;
continue;
}
// configure socket
int opt_val;
opt_val = 1;
setsockopt(this->socket, IPPROTO_TCP, TCP_NODELAY, (const char*) &opt_val, sizeof(opt_val));
opt_val = RECEIVE_TIMEOUT;
setsockopt(this->socket, SOL_SOCKET, SO_RCVTIMEO, (const char*) &opt_val, sizeof(opt_val));
// connection successful
this->cipher_alloc();
break;
}
// check if successful
freeaddrinfo(addr_list);
if (this->socket == INVALID_SOCKET) {
return false;
}
}
// socket probably still valid
return true;
}
void spiceapi::Connection::change_pass(std::string password) {
this->password = password;
this->cipher_alloc();
}
std::string spiceapi::Connection::request(std::string json) {
// check connection
if (!this->check())
return "";
// crypt
auto json_len = strlen(json.c_str()) + 1;
uint8_t* json_data = new uint8_t[json_len];
memcpy(json_data, json.c_str(), json_len);
if (this->cipher != nullptr)
this->cipher->crypt(json_data, json_len);
// send
auto send_result = send(this->socket, (const char*) json_data, (int) json_len, 0);
delete[] json_data;
if (send_result == SOCKET_ERROR || send_result < (int) json_len) {
closesocket(this->socket);
this->socket = INVALID_SOCKET;
return "";
}
// receive
uint8_t receive_data[RECEIVE_BUFFER_SIZE];
size_t receive_data_len = 0;
int receive_result;
while ((receive_result = recv(
this->socket,
(char*) &receive_data[receive_data_len],
sizeof(receive_data), 0)) > 0) {
// check for buffer overflow
if (receive_data_len + receive_result >= sizeof(receive_data)) {
closesocket(this->socket);
this->socket = INVALID_SOCKET;
return "";
}
// crypt
if (this->cipher != nullptr)
this->cipher->crypt(&receive_data[receive_data_len], (size_t) receive_result);
// increase received data length
receive_data_len += receive_result;
// check for message end
if (receive_data[receive_data_len - 1] == 0)
break;
}
// return resulting json
return std::string((const char*) &receive_data[0], receive_data_len - 1);
}
@@ -0,0 +1,31 @@
#ifndef SPICEAPI_CONNECTION_H
#define SPICEAPI_CONNECTION_H
#include <string>
#include <winsock2.h>
#include "rc4.h"
namespace spiceapi {
class Connection {
private:
std::string host;
uint16_t port;
std::string password;
SOCKET socket;
RC4* cipher;
void cipher_alloc();
public:
Connection(std::string host, uint16_t port, std::string password = "");
~Connection();
bool check();
void change_pass(std::string password);
std::string request(std::string json);
};
}
#endif //SPICEAPI_CONNECTION_H
@@ -0,0 +1,45 @@
#include "rc4.h"
#include <iterator>
spiceapi::RC4::RC4(uint8_t *key, size_t key_size) {
// initialize S-BOX
for (size_t i = 0; i < std::size(s_box); i++)
s_box[i] = (uint8_t) i;
// check key size
if (!key_size)
return;
// KSA
size_t j = 0;
for (size_t i = 0; i < std::size(s_box); i++) {
// update
j = (j + s_box[i] + key[i % key_size]) % std::size(s_box);
// swap
auto tmp = s_box[i];
s_box[i] = s_box[j];
s_box[j] = tmp;
}
}
void spiceapi::RC4::crypt(uint8_t *data, size_t size) {
// iterate all bytes
for (size_t pos = 0; pos < size; pos++) {
// update
a = (a + 1) % std::size(s_box);
b = (b + s_box[a]) % std::size(s_box);
// swap
auto tmp = s_box[a];
s_box[a] = s_box[b];
s_box[b] = tmp;
// crypt
data[pos] ^= s_box[(s_box[a] + s_box[b]) % std::size(s_box)];
}
}
@@ -0,0 +1,21 @@
#ifndef SPICEAPI_RC4_H
#define SPICEAPI_RC4_H
#include <cstdint>
namespace spiceapi {
class RC4 {
private:
uint8_t s_box[256];
size_t a = 0, b = 0;
public:
RC4(uint8_t *key, size_t key_size);
void crypt(uint8_t *data, size_t size);
};
}
#endif //SPICEAPI_RC4_H
@@ -0,0 +1,570 @@
#include "wrappers.h"
#include <random>
#include <string>
/*
* RapidJSON dependency
* You might need to adjust the paths when importing into your own project.
*/
#include "external/rapidjson/document.h"
#include "external/rapidjson/writer.h"
using namespace rapidjson;
namespace spiceapi {
static inline std::string doc2str(Document &doc) {
StringBuffer sb;
rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
doc.Accept(writer);
return sb.GetString();
}
static inline Document request_gen(const char *module, const char *function) {
// create document
Document doc;
doc.SetObject();
// add attributes
auto &alloc = doc.GetAllocator();
doc.AddMember("id", msg_gen_id(), alloc);
doc.AddMember("module", StringRef(module), alloc);
doc.AddMember("function", StringRef(function), alloc);
// add params
Value noparam(kArrayType);
doc.AddMember("params", noparam, alloc);
// return document
return doc;
}
static inline Document *response_get(std::string json) {
// parse document
Document *doc = new Document();
doc->Parse(json.c_str());
// check for parse error
if (doc->HasParseError()) {
delete doc;
return nullptr;
}
// check id
auto it_id = doc->FindMember("id");
if (it_id == doc->MemberEnd() || !(*it_id).value.IsUint64()) {
delete doc;
return nullptr;
}
// check errors
auto it_errors = doc->FindMember("errors");
if (it_errors == doc->MemberEnd() || !(*it_errors).value.IsArray()) {
delete doc;
return nullptr;
}
// check error count
if ((*it_errors).value.Size() > 0) {
delete doc;
return nullptr;
}
// check data
auto it_data = doc->FindMember("data");
if (it_data == doc->MemberEnd() || !(*it_data).value.IsArray()) {
delete doc;
return nullptr;
}
// return document
return doc;
}
}
uint64_t spiceapi::msg_gen_id() {
static uint64_t id_global = 0;
// check if global ID was initialized
if (id_global == 0) {
// generate a new ID
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<uint64_t> dist(1, (uint64_t) std::llround(std::pow(2, 63)));
id_global = dist(gen);
} else {
// increase by one
id_global++;
}
// return global ID
return id_global;
}
bool spiceapi::analogs_read(spiceapi::Connection &con, std::vector<spiceapi::AnalogState> &states) {
auto req = request_gen("analogs", "read");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray()) {
AnalogState state;
state.name = val[0].GetString();
state.value = val[1].GetFloat();
states.push_back(state);
}
delete res;
return true;
}
bool spiceapi::analogs_write(spiceapi::Connection &con, std::vector<spiceapi::AnalogState> &states) {
auto req = request_gen("analogs", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
state_val.PushBack(state.value, alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::analogs_write_reset(spiceapi::Connection &con, std::vector<spiceapi::AnalogState> &states) {
auto req = request_gen("analogs", "write_reset");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::buttons_read(spiceapi::Connection &con, std::vector<spiceapi::ButtonState> &states) {
auto req = request_gen("buttons", "read");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray()) {
ButtonState state;
state.name = val[0].GetString();
state.value = val[1].GetFloat();
states.push_back(state);
}
delete res;
return true;
}
bool spiceapi::buttons_write(spiceapi::Connection &con, std::vector<spiceapi::ButtonState> &states) {
auto req = request_gen("buttons", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
state_val.PushBack(state.value, alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::buttons_write_reset(spiceapi::Connection &con, std::vector<spiceapi::ButtonState> &states) {
auto req = request_gen("buttons", "write_reset");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::card_insert(spiceapi::Connection &con, size_t index, const char *card_id) {
auto req = request_gen("card", "insert");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(index, alloc);
params.PushBack(StringRef(card_id), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::coin_get(Connection &con, int &coins) {
auto req = request_gen("coin", "insert");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
coins = (*res)["data"][0].GetInt();
delete res;
return true;
}
bool spiceapi::coin_set(Connection &con, int coins) {
auto req = request_gen("coin", "set");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(coins, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::coin_insert(Connection &con, int coins) {
auto req = request_gen("coin", "insert");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(coins, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::control_raise(spiceapi::Connection &con, const char *signal) {
auto req = request_gen("control", "raise");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(signal), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::control_exit(spiceapi::Connection &con) {
auto req = request_gen("control", "exit");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::control_session_refresh(spiceapi::Connection &con) {
auto req = request_gen("control", "session_refresh");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto key = (*res)["data"][0].GetString();
con.change_pass(key);
delete res;
return true;
}
bool spiceapi::iidx_ticker_get(spiceapi::Connection &con, char *ticker) {
auto req = request_gen("iidx", "ticker_get");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto data = (*res)["data"][0].GetString();
strncpy(ticker, data, 9);
ticker[9] = 0x00;
delete res;
return true;
}
bool spiceapi::iidx_ticker_set(spiceapi::Connection &con, const char *ticker) {
auto req = request_gen("iidx", "ticker_set");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(ticker), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::iidx_ticker_reset(spiceapi::Connection &con) {
auto req = request_gen("iidx", "ticker_reset");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::info_avs(spiceapi::Connection &con, spiceapi::InfoAvs &info) {
auto req = request_gen("info", "avs");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"][0];
info.model = data["model"].GetString();
info.dest = data["dest"].GetString();
info.spec = data["spec"].GetString();
info.rev = data["rev"].GetString();
info.ext = data["ext"].GetString();
delete res;
return true;
}
bool spiceapi::info_launcher(spiceapi::Connection &con, spiceapi::InfoLauncher &info) {
auto req = request_gen("info", "launcher");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"][0];
info.version = data["version"].GetString();
info.compile_date = data["compile_date"].GetString();
info.compile_time = data["compile_time"].GetString();
info.system_time = data["system_time"].GetString();
for (auto &arg : data["args"].GetArray())
info.args.push_back(arg.GetString());
delete res;
return true;
}
bool spiceapi::info_memory(spiceapi::Connection &con, spiceapi::InfoMemory &info) {
auto req = request_gen("info", "memory");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"][0];
info.mem_total = data["mem_total"].GetUint64();
info.mem_total_used = data["mem_total_used"].GetUint64();
info.mem_used = data["mem_used"].GetUint64();
info.vmem_total = data["vmem_total"].GetUint64();
info.vmem_total_used = data["vmem_total_used"].GetUint64();
info.vmem_used = data["vmem_used"].GetUint64();
delete res;
return true;
}
bool spiceapi::keypads_write(spiceapi::Connection &con, unsigned int keypad, const char *input) {
auto req = request_gen("keypads", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(keypad, alloc);
params.PushBack(StringRef(input), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::keypads_set(spiceapi::Connection &con, unsigned int keypad, std::vector<char> &keys) {
auto req = request_gen("keypads", "set");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(keypad, alloc);
for (auto &key : keys)
params.PushBack(StringRef(&key, 1), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::keypads_get(spiceapi::Connection &con, unsigned int keypad, std::vector<char> &keys) {
auto req = request_gen("keypads", "get");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(keypad, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray())
keys.push_back(val.GetString()[0]);
delete res;
return true;
}
bool spiceapi::lights_read(spiceapi::Connection &con, std::vector<spiceapi::LightState> &states) {
auto req = request_gen("buttons", "read");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray()) {
LightState state;
state.name = val[0].GetString();
state.value = val[1].GetFloat();
states.push_back(state);
}
delete res;
return true;
}
bool spiceapi::lights_write(spiceapi::Connection &con, std::vector<spiceapi::LightState> &states) {
auto req = request_gen("buttons", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
state_val.PushBack(state.value, alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::lights_write_reset(spiceapi::Connection &con, std::vector<spiceapi::LightState> &states) {
auto req = request_gen("buttons", "write_reset");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::memory_write(spiceapi::Connection &con, const char *dll_name, const char *hex, uint32_t offset) {
auto req = request_gen("memory", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(dll_name), alloc);
params.PushBack(StringRef(hex), alloc);
params.PushBack(offset, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::memory_read(spiceapi::Connection &con, const char *dll_name, uint32_t offset, uint32_t size,
std::string &hex) {
auto req = request_gen("memory", "read");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(dll_name), alloc);
params.PushBack(offset, alloc);
params.PushBack(size, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
hex = (*res)["data"][0].GetString();
delete res;
return true;
}
bool spiceapi::memory_signature(spiceapi::Connection &con, const char *dll_name, const char *signature,
const char *replacement, uint32_t offset, uint32_t usage, uint32_t &file_offset) {
auto req = request_gen("memory", "signature");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(dll_name), alloc);
params.PushBack(StringRef(signature), alloc);
params.PushBack(StringRef(replacement), alloc);
params.PushBack(offset, alloc);
params.PushBack(usage, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
file_offset = (*res)["data"][0].GetUint();
delete res;
return true;
}
bool spiceapi::touch_read(spiceapi::Connection &con, std::vector<spiceapi::TouchState> &states) {
auto req = request_gen("touch", "read");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray()) {
TouchState state;
state.id = val[0].GetUint64();
state.x = val[1].GetInt64();
state.y = val[2].GetInt64();
states.push_back(state);
}
delete res;
return true;
}
bool spiceapi::touch_write(spiceapi::Connection &con, std::vector<spiceapi::TouchState> &states) {
auto req = request_gen("touch", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(state.id, alloc);
state_val.PushBack(state.x, alloc);
state_val.PushBack(state.y, alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::touch_write_reset(spiceapi::Connection &con, std::vector<spiceapi::TouchState> &states) {
auto req = request_gen("touch", "write_reset");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states)
params.PushBack(state.id, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
@@ -0,0 +1,91 @@
#ifndef SPICEAPI_WRAPPERS_H
#define SPICEAPI_WRAPPERS_H
#include <vector>
#include <string>
#include "connection.h"
namespace spiceapi {
struct AnalogState {
std::string name;
float value;
};
struct ButtonState {
std::string name;
float value;
};
struct LightState {
std::string name;
float value;
};
struct InfoAvs {
std::string model, dest, spec, rev, ext;
};
struct InfoLauncher {
std::string version;
std::string compile_date, compile_time, system_time;
std::vector<std::string> args;
};
struct InfoMemory {
uint64_t mem_total, mem_total_used, mem_used;
uint64_t vmem_total, vmem_total_used, vmem_used;
};
struct TouchState {
uint64_t id;
int64_t x, y;
};
uint64_t msg_gen_id();
bool analogs_read(Connection &con, std::vector<AnalogState> &states);
bool analogs_write(Connection &con, std::vector<AnalogState> &states);
bool analogs_write_reset(Connection &con, std::vector<AnalogState> &states);
bool buttons_read(Connection &con, std::vector<ButtonState> &states);
bool buttons_write(Connection &con, std::vector<ButtonState> &states);
bool buttons_write_reset(Connection &con, std::vector<ButtonState> &states);
bool card_insert(Connection &con, size_t index, const char *card_id);
bool coin_get(Connection &con, int &coins);
bool coin_set(Connection &con, int coins);
bool coin_insert(Connection &con, int coins=1);
bool control_raise(Connection &con, const char *signal);
bool control_exit(Connection &con);
bool control_session_refresh(Connection &con);
bool iidx_ticker_get(Connection &con, char *ticker);
bool iidx_ticker_set(Connection &con, const char *ticker);
bool iidx_ticker_reset(Connection &con);
bool info_avs(Connection &con, InfoAvs &info);
bool info_launcher(Connection &con, InfoLauncher &info);
bool info_memory(Connection &con, InfoMemory &info);
bool keypads_write(Connection &con, unsigned int keypad, const char *input);
bool keypads_set(Connection &con, unsigned int keypad, std::vector<char> &keys);
bool keypads_get(Connection &con, unsigned int keypad, std::vector<char> &keys);
bool lights_read(Connection &con, std::vector<LightState> &states);
bool lights_write(Connection &con, std::vector<LightState> &states);
bool lights_write_reset(Connection &con, std::vector<LightState> &states);
bool memory_write(Connection &con, const char *dll_name, const char *hex, uint32_t offset);
bool memory_read(Connection &con, const char *dll_name, uint32_t offset, uint32_t size, std::string &hex);
bool memory_signature(Connection &con, const char *dll_name, const char *signature, const char *replacement,
uint32_t offset, uint32_t usage, uint32_t &file_offset);
bool touch_read(Connection &con, std::vector<TouchState> &states);
bool touch_write(Connection &con, std::vector<TouchState> &states);
bool touch_write_reset(Connection &con, std::vector<TouchState> &states);
}
#endif //SPICEAPI_WRAPPERS_H
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
@@ -0,0 +1,22 @@
library spiceapi;
import 'dart:io';
import 'dart:convert';
import 'dart:async';
import 'dart:math';
part "src/connection.dart";
part "src/request.dart";
part "src/response.dart";
part "src/exceptions.dart";
part "src/rc4.dart";
part "src/wrappers/analogs.dart";
part "src/wrappers/buttons.dart";
part "src/wrappers/card.dart";
part "src/wrappers/coin.dart";
part "src/wrappers/control.dart";
part "src/wrappers/info.dart";
part "src/wrappers/keypads.dart";
part "src/wrappers/lights.dart";
part "src/wrappers/memory.dart";
part "src/wrappers/touch.dart";
part "src/wrappers/iidx.dart";
part "src/wrappers/touch.dart";
@@ -0,0 +1,192 @@
part of spiceapi;
class Connection {
// settings
static const _TIMEOUT = Duration(seconds: 2);
static const _BUFFER_SIZE = 1024 * 8;
// state
final String host, pass;
final int port;
var resource;
List<int> _dataBuffer;
StreamController<Response> _responses;
StreamController<Connection> _connections;
Socket _socket;
RC4 _cipher;
bool _disposed = false;
Connection(this.host, this.port, this.pass,
{this.resource, bool refreshSession=true}) {
// initialize
_dataBuffer = List<int>();
_responses = StreamController<Response>.broadcast();
_connections = StreamController<Connection>.broadcast();
if (pass.length > 0)
_cipher = RC4(utf8.encode(pass));
// connect
Socket.connect(host, port, timeout: _TIMEOUT).then((socket) async {
// remember socket
this._socket = socket;
// listen to data
socket.listen((data) {
// cipher
if (_cipher != null)
_cipher.crypt(data);
// add data to buffer
_dataBuffer.addAll(data);
// check buffer size
if (_dataBuffer.length > _BUFFER_SIZE) {
socket.destroy();
return;
}
// check for completed message
for (int i = 0; i < _dataBuffer.length; i++) {
if (_dataBuffer[i] == 0) {
// get message data and remove from buffer
var msgData = List<int>.from(_dataBuffer.getRange(0, i));
_dataBuffer.removeRange(0, i + 1);
// check data length
if (msgData.length > 0) {
// convert to JSON
var msgStr = utf8.decode(msgData, allowMalformed: false);
// build response
var res = Response.fromJson(msgStr);
this._responses.add(res);
}
}
}
}, onError: (e) {
// dispose on listen error
this.dispose();
}, onDone: () {
// dispose on listen done
this.dispose();
});
// refresh session
bool error = false;
if (refreshSession) {
try {
await controlRefreshSession(this);
} on Error {
error = true;
} on TimeoutException {
error = true;
}
}
// mark as connected
if (!this._connections.isClosed)
this._connections.add(this);
if (error)
this.dispose();
}, onError: (e) {
// dispose on connection error
this.dispose();
});
}
void changePass(String pass) {
if (pass.length > 0)
_cipher = RC4(utf8.encode(pass));
else
_cipher = null;
}
void dispose() {
if (_socket != null)
_socket.destroy();
if (_responses != null)
_responses.close();
if (_connections != null)
_connections.close();
this._disposed = true;
this.free();
}
bool isDisposed() {
return this._disposed;
}
void free() {
// release optional resource
if (this.resource != null) {
this.resource.release();
this.resource = null;
}
}
bool isFree() {
return this.resource == null;
}
Future<Connection> onConnect() {
return _connections.stream.first;
}
bool isValid() {
return this._socket != null && !this._disposed;
}
Future<Response> request(Request req) {
// add response listener
var res = _awaitResponse(req._id);
// write request
_writeRequest(req);
// return future response
return res.then((res) {
// validate first
res.validate();
// return it
return res;
});
}
void _writeRequest(Request req) async {
// convert to JSON
var json = req.toJson() + "\x00";
var jsonEncoded = utf8.encode(json);
// cipher
if (_cipher != null)
_cipher.crypt(jsonEncoded);
// write to socket
this._socket.add(jsonEncoded);
}
Future<Response> _awaitResponse(int id) {
return _responses.stream.timeout(_TIMEOUT).firstWhere(
(res) => res._id == id, orElse: null);
}
}
@@ -0,0 +1,11 @@
part of spiceapi;
class APIError implements Exception {
String cause;
APIError(this.cause);
@override
String toString() {
return this.cause;
}
}
@@ -0,0 +1,48 @@
part of spiceapi;
class RC4 {
// state
int _a = 0;
int _b = 0;
List<int> _sBox = List<int>(256);
RC4(List<int> key) {
// init sBox
for (int i = 0; i < 256; i++) {
_sBox[i] = i;
}
// process key
int j = 0;
for (int i = 0; i < 256; i++) {
// update
j = (j + _sBox[i] + key[i % key.length]) % 256;
// swap
var tmp = _sBox[i];
_sBox[i] = _sBox[j];
_sBox[j] = tmp;
}
}
void crypt(List<int> inData) {
for (int i = 0; i < inData.length; i++) {
// update
_a = (_a + 1) % 256;
_b = (_b + _sBox[_a]) % 256;
// swap
var tmp = _sBox[_a];
_sBox[_a] = _sBox[_b];
_sBox[_b] = tmp;
// crypt
inData[i] ^= _sBox[(_sBox[_a] + _sBox[_b]) % 256];
}
}
}
@@ -0,0 +1,45 @@
part of spiceapi;
class Request {
static int _lastID = 0;
// contents
int _id;
String _module;
String _function;
List _params;
Request(String module, String function, {id}) {
// automatic ID iteration
if (id == null) {
if (++_lastID >= pow(2, 32))
_lastID = 1;
id = _lastID;
} else
_lastID = id;
// build contents
this._id = id;
this._module = module;
this._function = function;
this._params = List();
}
String toJson() {
return jsonEncode(
{
"id": this._id,
"module": this._module,
"function": this._function,
"params": this._params,
}
);
}
void addParam(param) {
this._params.add(param);
}
}
@@ -0,0 +1,35 @@
part of spiceapi;
class Response {
String _json;
int _id;
List _errors;
List _data;
Response.fromJson(String json) {
this._json = json;
var obj = jsonDecode(json);
this._id = obj["id"];
this._errors = obj["errors"];
this._data = obj["data"];
}
void validate() {
// check for errors
if (_errors.length > 0) {
// TODO: add all errors
throw APIError(_errors[0].toString());
}
}
List getData() {
return _data;
}
String toJson() {
return _json;
}
}
@@ -0,0 +1,54 @@
part of spiceapi;
class AnalogState {
String name;
double state;
bool active;
AnalogState(this.name, this.state);
AnalogState._fromRead(this.name, this.state, this.active);
}
Future<List<AnalogState>> analogsRead(Connection con) {
var req = Request("analogs", "read");
return con.request(req).then((res) {
// build states list
List<AnalogState> states = [];
for (List state in res.getData()) {
states.add(AnalogState._fromRead(
state[0],
state[1],
state[2],
));
}
// return it
return states;
});
}
Future<void> analogsWrite(Connection con, List<AnalogState> states) {
var req = Request("analogs", "write");
// add params
for (var state in states) {
var obj = [
state.name,
state.state
];
req.addParam(obj);
}
return con.request(req);
}
Future<void> analogsWriteReset(Connection con, List<String> names) {
var req = Request("analogs", "write_reset");
// add params
for (var name in names)
req.addParam(name);
return con.request(req);
}
@@ -0,0 +1,54 @@
part of spiceapi;
class ButtonState {
String name;
double state;
bool active;
ButtonState(this.name, this.state);
ButtonState._fromRead(this.name, this.state, this.active);
}
Future<List<ButtonState>> buttonsRead(Connection con) {
var req = Request("buttons", "read");
return con.request(req).then((res) {
// build states list
List<ButtonState> states = [];
for (List state in res.getData()) {
states.add(ButtonState._fromRead(
state[0],
state[1],
state[2],
));
}
// return it
return states;
});
}
Future<void> buttonsWrite(Connection con, List<ButtonState> states) {
var req = Request("buttons", "write");
// add params
for (var state in states) {
var obj = [
state.name,
state.state
];
req.addParam(obj);
}
return con.request(req);
}
Future<void> buttonsWriteReset(Connection con, List<String> names) {
var req = Request("buttons", "write_reset");
// add params
for (var name in names)
req.addParam(name);
return con.request(req);
}
@@ -0,0 +1,8 @@
part of spiceapi;
Future<void> cardInsert(Connection con, int unit, String cardID) {
var req = Request("card", "insert");
req.addParam(unit);
req.addParam(cardID);
return con.request(req);
}
@@ -0,0 +1,21 @@
part of spiceapi;
Future<int> coinGet(Connection con) {
var req = Request("coin", "get");
return con.request(req).then((res) {
return res.getData()[0];
});
}
Future<void> coinSet(Connection con, int amount) {
var req = Request("coin", "set");
req.addParam(amount);
return con.request(req);
}
Future<void> coinInsert(Connection con, [int amount=1]) {
var req = Request("coin", "insert");
if (amount != 1)
req.addParam(amount);
return con.request(req);
}
@@ -0,0 +1,21 @@
part of spiceapi;
Future<void> controlRaise(Connection con, String signal) {
var req = Request("control", "raise");
req.addParam(signal);
return con.request(req);
}
Future<void> controlExit(Connection con, int code) {
var req = Request("control", "exit");
req.addParam(code);
return con.request(req);
}
Future<void> controlRefreshSession(Connection con) {
var rnd = new Random();
var req = Request("control", "session_refresh", id: rnd.nextInt(pow(2, 32)));
return con.request(req).then((res) {
con.changePass(res.getData()[0]);
});
}
@@ -0,0 +1,19 @@
part of spiceapi;
Future<String> iidxTickerGet(Connection con) {
var req = Request("iidx", "ticker_get");
return con.request(req).then((res) {
return res.getData()[0];
});
}
Future<void> iidxTickerSet(Connection con, String text) {
var req = Request("iidx", "ticker_set");
req.addParam(text);
return con.request(req);
}
Future<void> iidxTickerReset(Connection con) {
var req = Request("iidx", "ticker_reset");
return con.request(req);
}
@@ -0,0 +1,22 @@
part of spiceapi;
Future<Map> infoAVS(Connection con) {
var req = Request("info", "avs");
return con.request(req).then((res) {
return res.getData()[0];
});
}
Future<Map> infoLauncher(Connection con) {
var req = Request("info", "launcher");
return con.request(req).then((res) {
return res.getData()[0];
});
}
Future<Map> infoMemory(Connection con) {
var req = Request("info", "memory");
return con.request(req).then((res) {
return res.getData()[0];
});
}
@@ -0,0 +1,28 @@
part of spiceapi;
Future<void> keypadsWrite(Connection con, int unit, String input) {
var req = Request("keypads", "write");
req.addParam(unit);
req.addParam(input);
return con.request(req);
}
Future<void> keypadsSet(Connection con, int unit, String buttons) {
var req = Request("keypads", "set");
req.addParam(unit);
for (int i = 0; i < buttons.length; i++)
req.addParam(buttons[i]);
return con.request(req);
}
Future<String> keypadsGet(Connection con, int unit) {
var req = Request("keypads", "get");
req.addParam(unit);
return con.request(req).then((res) {
String buttons = "";
for (var obj in res.getData()) {
buttons += obj;
}
return buttons;
});
}
@@ -0,0 +1,54 @@
part of spiceapi;
class LightState {
String name;
double state;
bool active;
LightState(this.name, this.state);
LightState._fromRead(this.name, this.state, this.active);
}
Future<List<LightState>> lightsRead(Connection con) {
var req = Request("lights", "read");
return con.request(req).then((res) {
// build states list
List<LightState> states = [];
for (List state in res.getData()) {
states.add(LightState._fromRead(
state[0],
state[1],
state[2],
));
}
// return it
return states;
});
}
Future<void> lightsWrite(Connection con, List<LightState> states) {
var req = Request("lights", "write");
// add params
for (var state in states) {
var obj = [
state.name,
state.state
];
req.addParam(obj);
}
return con.request(req);
}
Future<void> lightsWriteReset(Connection con, List<String> names) {
var req = Request("lights", "write_reset");
// add params
for (var name in names)
req.addParam(name);
return con.request(req);
}
@@ -0,0 +1,35 @@
part of spiceapi;
Future<void> memoryWrite(Connection con,
String dllName, String data, int offset) {
var req = Request("memory", "write");
req.addParam(dllName);
req.addParam(data);
req.addParam(offset);
return con.request(req);
}
Future<String> memoryRead(Connection con,
String dllName, int offset, int size) {
var req = Request("memory", "read");
req.addParam(dllName);
req.addParam(offset);
req.addParam(size);
return con.request(req).then((res) {
return res.getData()[0];
});
}
Future<int> memorySignature(Connection con,
String dllName, String signature, String replacement,
int offset, int usage) {
var req = Request("memory", "signature");
req.addParam(dllName);
req.addParam(signature);
req.addParam(replacement);
req.addParam(offset);
req.addParam(usage);
return con.request(req).then((res) {
return res.getData()[0];
});
}
@@ -0,0 +1,53 @@
part of spiceapi;
class TouchState {
int id;
int x, y;
TouchState(this.id, this.x, this.y);
}
Future<List<TouchState>> touchRead(Connection con) {
var req = Request("touch", "read");
return con.request(req).then((res) {
// build states list
List<TouchState> states = [];
for (List state in res.getData()) {
states.add(TouchState(
state[0],
state[1],
state[2],
));
}
// return it
return states;
});
}
Future<void> touchWrite(Connection con, List<TouchState> states) {
var req = Request("touch", "write");
// add params
for (var state in states) {
var obj = [
state.id,
state.x,
state.y
];
req.addParam(obj);
}
return con.request(req);
}
Future<void> touchWriteReset(Connection con, List<int> touchIDs) {
var req = Request("touch", "write_reset");
// add params
for (var id in touchIDs)
req.addParam(id);
return con.request(req);
}
@@ -0,0 +1,116 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
@@ -0,0 +1,14 @@
from .connection import Connection
from .request import Request
from .analogs import *
from .buttons import *
from .card import *
from .coin import *
from .control import *
from .exceptions import *
from .iidx import *
from .info import *
from .keypads import *
from .lights import *
from .memory import *
from .touch import *
@@ -0,0 +1,28 @@
from .connection import Connection
from .request import Request
def analogs_read(con: Connection):
res = con.request(Request("analogs", "read"))
return res.get_data()
def analogs_write(con: Connection, analog_state_list):
req = Request("analogs", "write")
for state in analog_state_list:
req.add_param(state)
con.request(req)
def analogs_write_reset(con: Connection, analog_names=None):
req = Request("analogs", "write_reset")
# reset all analogs
if not analog_names:
con.request(req)
return
# reset specified analogs
for analog_name in analog_names:
req.add_param(analog_name)
con.request(req)
@@ -0,0 +1,28 @@
from .connection import Connection
from .request import Request
def buttons_read(con: Connection):
res = con.request(Request("buttons", "read"))
return res.get_data()
def buttons_write(con: Connection, button_state_list):
req = Request("buttons", "write")
for state in button_state_list:
req.add_param(state)
con.request(req)
def buttons_write_reset(con: Connection, button_names=None):
req = Request("buttons", "write_reset")
# reset all buttons
if not button_names:
con.request(req)
return
# reset specified buttons
for button_name in button_names:
req.add_param(button_name)
con.request(req)
@@ -0,0 +1,9 @@
from .connection import Connection
from .request import Request
def card_insert(con: Connection, unit: int, card_id: str):
req = Request("card", "insert")
req.add_param(unit)
req.add_param(card_id)
con.request(req)
@@ -0,0 +1,20 @@
from .connection import Connection
from .request import Request
def coin_get(con: Connection):
res = con.request(Request("coin", "get"))
return res.get_data()[0]
def coin_set(con: Connection, amount: int):
req = Request("coin", "set")
req.add_param(amount)
con.request(req)
def coin_insert(con: Connection, amount=1):
req = Request("coin", "insert")
if amount != 1:
req.add_param(amount)
con.request(req)
@@ -0,0 +1,139 @@
import os
import socket
from .request import Request
from .response import Response
from .rc4 import rc4
from .exceptions import MalformedRequestException, APIError
class Connection:
""" Container for managing a single connection to the API server.
"""
def __init__(self, host: str, port: int, password: str):
"""Default constructor.
:param host: the host string to connect to
:param port: the port of the host
:param password: the connection password string
"""
self.host = host
self.port = port
self.password = password
self.socket = None
self.cipher = None
self.reconnect()
def reconnect(self, refresh_session=True):
"""Reconnect to the server.
This opens a new connection and closes the previous one, if existing.
"""
# close old socket
self.close()
# create new socket
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.settimeout(3)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
self.socket.connect((self.host, self.port))
# cipher
self.change_password(self.password)
# refresh session
if refresh_session:
from .control import control_session_refresh
control_session_refresh(self)
def change_password(self, password):
"""Allows to change the password on the fly.
The cipher will be rebuilt.
"""
if len(self.password) > 0:
self.cipher = rc4(password.encode("UTF-8"))
else:
self.cipher = None
def close(self):
"""Close the active connection, if existing."""
# check if socket is existing
if self.socket:
# close and delete socket
self.socket.close()
self.socket = None
def request(self, request: Request):
"""Send a request to the server and receive the answer.
:param request: request object
:return: response object
"""
# check if disconnected
if not self.socket:
raise RuntimeError("No active connection.")
# build data
data = request.to_json().encode("UTF-8") + b"\x00"
if self.cipher:
data_list = list(data)
data_cipher = []
for b in data_list:
data_cipher.append(b ^ next(self.cipher))
data = bytes(data_cipher)
# send request
if os.name != 'nt':
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1)
self.socket.send(data)
# get answer
answer_data = []
while not len(answer_data) or answer_data[-1] != 0:
# receive data
if os.name != 'nt':
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1)
receive_data = self.socket.recv(4096)
# check length
if len(receive_data):
# check cipher
if self.cipher:
# add decrypted data
for b in receive_data:
answer_data.append(int(b ^ next(self.cipher)))
else:
# add plaintext
for b in receive_data:
answer_data.append(int(b))
else:
raise RuntimeError("Connection was closed.")
# check for empty response
if len(answer_data) <= 1:
# empty response means the JSON couldn't be parsed
raise MalformedRequestException()
# build response
response = Response(bytes(answer_data[:-1]).decode("UTF-8"))
if len(response.get_errors()):
raise APIError(response.get_errors())
# check ID
req_id = request.get_id()
res_id = response.get_id()
if req_id != res_id:
raise RuntimeError(f"Unexpected response ID: {res_id} (expected {req_id})")
# return response object
return response
@@ -0,0 +1,27 @@
import random
from .connection import Connection
from .request import Request
def control_raise(con: Connection, signal: str):
req = Request("control", "raise")
req.add_param(signal)
con.request(req)
def control_exit(con: Connection, code=None):
req = Request("control", "exit")
if code:
req.add_param(code)
try:
con.request(req)
except RuntimeError:
pass # we expect the connection to get killed
def control_session_refresh(con: Connection):
res = con.request(Request("control", "session_refresh", req_id=random.randint(1, 2**64)))
# apply new password
password = res.get_data()[0]
con.change_password(password)
@@ -0,0 +1,10 @@
class APIError(Exception):
def __init__(self, errors):
super().__init__("\r\n".join(errors))
class MalformedRequestException(Exception):
pass
@@ -0,0 +1,18 @@
from .connection import Connection
from .request import Request
def iidx_ticker_get(con: Connection):
res = con.request(Request("iidx", "ticker_get"))
return res.get_data()
def iidx_ticker_set(con: Connection, text: str):
req = Request("iidx", "ticker_set")
req.add_param(text)
con.request(req)
def iidx_ticker_reset(con: Connection):
req = Request("iidx", "ticker_reset")
con.request(req)
@@ -0,0 +1,17 @@
from .connection import Connection
from .request import Request
def info_avs(con: Connection):
res = con.request(Request("info", "avs"))
return res.get_data()[0]
def info_launcher(con: Connection):
res = con.request(Request("info", "launcher"))
return res.get_data()[0]
def info_memory(con: Connection):
res = con.request(Request("info", "memory"))
return res.get_data()[0]
@@ -0,0 +1,24 @@
from .connection import Connection
from .request import Request
def keypads_write(con: Connection, keypad: int, input_values: str):
req = Request("keypads", "write")
req.add_param(keypad)
req.add_param(input_values)
con.request(req)
def keypads_set(con: Connection, keypad: int, input_values: str):
req = Request("keypads", "set")
req.add_param(keypad)
for value in input_values:
req.add_param(value)
con.request(req)
def keypads_get(con: Connection, keypad: int):
req = Request("keypads", "get")
req.add_param(keypad)
res = con.request(req)
return res.get_data()
@@ -0,0 +1,28 @@
from .connection import Connection
from .request import Request
def lights_read(con: Connection):
res = con.request(Request("lights", "read"))
return res.get_data()
def lights_write(con: Connection, light_state_list):
req = Request("lights", "write")
for state in light_state_list:
req.add_param(state)
con.request(req)
def lights_write_reset(con: Connection, light_names=None):
req = Request("lights", "write_reset")
# reset all lights
if not light_names:
con.request(req)
return
# reset specified lights
for light_name in light_names:
req.add_param(light_name)
con.request(req)
@@ -0,0 +1,31 @@
from .connection import Connection
from .request import Request
def memory_write(con: Connection, dll_name: str, data: str, offset: int):
req = Request("memory", "write")
req.add_param(dll_name)
req.add_param(data)
req.add_param(offset)
con.request(req)
def memory_read(con: Connection, dll_name: str, offset: int, size: int):
req = Request("memory", "read")
req.add_param(dll_name)
req.add_param(offset)
req.add_param(size)
res = con.request(req)
return res.get_data()[0]
def memory_signature(con: Connection, dll_name: str, signature: str,
replacement: str, offset: int, usage: int):
req = Request("memory", "signature")
req.add_param(dll_name)
req.add_param(signature)
req.add_param(replacement)
req.add_param(offset)
req.add_param(usage)
res = con.request(req)
return res.get_data()[0]
@@ -0,0 +1,24 @@
def rc4_ksa(key):
n = len(key)
j = 0
s_box = list(range(256))
for i in range(256):
j = (j + s_box[i] + key[i % n]) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
return s_box
def rc4_prga(s_box):
i = 0
j = 0
while True:
i = (i + 1) % 256
j = (j + s_box[i]) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
yield s_box[(s_box[i] + s_box[j]) % 256]
def rc4(key):
return rc4_prga(rc4_ksa(key))
@@ -0,0 +1,63 @@
import json
from threading import Lock
class Request:
# global ID pool
GLOBAL_ID = 1
GLOBAL_ID_LOCK = Lock()
def __init__(self, module: str, function: str, req_id=None):
# use global ID
with Request.GLOBAL_ID_LOCK:
if req_id is None:
# reset at max value
Request.GLOBAL_ID += 1
if Request.GLOBAL_ID >= 2 ** 64:
Request.GLOBAL_ID = 1
# get ID and increase by one
req_id = Request.GLOBAL_ID
else:
# carry over ID
Request.GLOBAL_ID = req_id
# remember ID
self._id = req_id
# build data dict
self.data = {
"id": req_id,
"module": module,
"function": function,
"params": []
}
@staticmethod
def from_json(request_json: str):
req = Request("", "", 0)
req.data = json.loads(request_json)
req._id = req.data["id"]
return req
def get_id(self):
return self._id
def to_json(self):
return json.dumps(
self.data,
ensure_ascii=False,
check_circular=False,
allow_nan=False,
indent=None,
separators=(",", ":"),
sort_keys=False
)
def add_param(self, param):
self.data["params"].append(param)
@@ -0,0 +1,30 @@
import json
class Response:
def __init__(self, response_json: str):
self._res = json.loads(response_json)
self._id = self._res["id"]
self._errors = self._res["errors"]
self._data = self._res["data"]
def to_json(self):
return json.dumps(
self._res,
ensure_ascii=True,
check_circular=False,
allow_nan=False,
indent=2,
separators=(",", ": "),
sort_keys=False
)
def get_id(self):
return self._id
def get_errors(self):
return self._errors
def get_data(self):
return self._data
@@ -0,0 +1,21 @@
from .connection import Connection
from .request import Request
def touch_read(con: Connection):
res = con.request(Request("touch", "read"))
return res.get_data()
def touch_write(con: Connection, touch_points):
req = Request("touch", "write")
for state in touch_points:
req.add_param(state)
con.request(req)
def touch_write_reset(con: Connection, touch_ids):
req = Request("touch", "write_reset")
for touch_id in touch_ids:
req.add_param(touch_id)
con.request(req)
@@ -0,0 +1,554 @@
from datetime import datetime
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.messagebox
try:
import spiceapi
except ModuleNotFoundError:
raise RuntimeError("spiceapi module not installed")
NSEW = tk.N+tk.S+tk.E+tk.W
def api_action(func):
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except spiceapi.APIError as e:
tk.messagebox.showerror(title="API Error", message=str(e))
except ValueError as e:
tk.messagebox.showerror(title="Input Error", message=str(e))
except (ConnectionResetError, BrokenPipeError, RuntimeError) as e:
tk.messagebox.showerror(title="Connection Error", message=str(e))
except AttributeError:
tk.messagebox.showerror(title="Error", message="No active connection.")
except BaseException as e:
tk.messagebox.showerror(title="Exception", message=str(e))
return wrapper
class TextField(ttk.Frame):
"""Simple text field with a scroll bar to the right."""
def __init__(self, parent, read_only=False, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.parent = parent
self.read_only = read_only
# create scroll bar
self.scroll = ttk.Scrollbar(self)
self.scroll.pack(side=tk.RIGHT, fill=tk.Y)
# create text field
self.contents = tk.Text(self, height=1, width=50,
foreground="black", background="#E8E6E0",
insertbackground="black")
self.contents.pack(side=tk.LEFT, expand=True, fill=tk.BOTH)
# link scroll bar with text field
self.scroll.config(command=self.contents.yview)
self.contents.config(yscrollcommand=self.scroll.set)
# read only setting
if self.read_only:
self.contents.config(state=tk.DISABLED)
def get_text(self):
"""Get the current text content as string.
:return: text content
"""
return self.contents.get("1.0", tk.END+"-1c")
def set_text(self, text):
"""Set the current text content.
:param text: text content
:return: None
"""
# enable if read only
if self.read_only:
self.contents.config(state=tk.NORMAL)
# delete old content and insert replacement text
self.contents.delete("1.0", tk.END)
self.contents.insert(tk.END, text)
# disable if read only
if self.read_only:
self.contents.config(state=tk.DISABLED)
class ManualTab(ttk.Frame):
"""Manual JSON request/response functinality."""
def __init__(self, app, parent, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.app = app
self.parent = parent
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
self.columnconfigure(0, weight=1)
# request text field
self.txt_request = TextField(self, read_only=False)
self.txt_request.grid(row=0, column=0, sticky=NSEW, padx=2, pady=2)
self.txt_request.set_text(
'{\n'
' "id": 1,\n'
' "module": "coin",\n'
' "function": "insert",\n'
' "params": []\n'
'}\n'
)
# response text field
self.txt_response = TextField(self, read_only=True)
self.txt_response.grid(row=1, column=0, sticky=NSEW, padx=2, pady=2)
self.txt_response.contents.config(state=tk.DISABLED)
# send button
self.btn_send = ttk.Button(self, text="Send", command=self.action_send)
self.btn_send.grid(row=2, column=0, sticky=tk.W+tk.E, padx=2, pady=2)
def action_send(self):
"""Gets called when the send button is pressed."""
# check connection
if self.app.connection:
# send request and get response
self.txt_response.set_text("Sending...")
try:
# build request
request = spiceapi.Request.from_json(self.txt_request.get_text())
# send request and get response, measure time
t1 = datetime.now()
response = self.app.connection.request(request)
t2 = datetime.now()
# set response text
self.txt_response.set_text("{}\n\nElapsed time: {} seconds".format(
response.to_json(),
(t2 - t1).total_seconds())
)
except spiceapi.APIError as e:
self.txt_response.set_text(f"Server returned error:\n{e}")
except spiceapi.MalformedRequestException:
self.txt_response.set_text("Malformed request detected.")
except (ConnectionResetError, BrokenPipeError, RuntimeError) as e:
self.txt_response.set_text("Error sending request: " + str(e))
except BaseException as e:
self.txt_response.set_text("General Exception: " + str(e))
else:
# print error
self.txt_response.set_text("No active connection.")
class ControlTab(ttk.Frame):
"""Main control tab."""
def __init__(self, app, parent, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.app = app
self.parent = parent
# scale grid
self.columnconfigure(0, weight=1)
# card
self.card = ttk.Frame(self, padding=(8, 8, 8, 8))
self.card.grid(row=0, column=0, sticky=tk.E+tk.W)
self.card.columnconfigure(0, weight=1)
self.card.columnconfigure(1, weight=1)
self.card_lbl = ttk.Label(self.card, text="Card")
self.card_lbl.grid(row=0, columnspan=2)
self.card_entry = ttk.Entry(self.card)
self.card_entry.insert(tk.END, "E004000000000000")
self.card_entry.grid(row=1, columnspan=2, sticky=NSEW, padx=2, pady=2)
self.card_insert_p1 = ttk.Button(self.card, text="Insert P1", command=self.action_insert_p1)
self.card_insert_p1.grid(row=2, column=0, sticky=NSEW, padx=2, pady=2)
self.card_insert_p2 = ttk.Button(self.card, text="Insert P2", command=self.action_insert_p2)
self.card_insert_p2.grid(row=2, column=1, sticky=NSEW, padx=2, pady=2)
# coin
self.coin = ttk.Frame(self, padding=(8, 8, 8, 8))
self.coin.grid(row=1, column=0, sticky=tk.E+tk.W)
self.coin.columnconfigure(0, weight=1)
self.coin.columnconfigure(1, weight=1)
self.coin.columnconfigure(2, weight=1)
self.coin_lbl = ttk.Label(self.coin, text="Coins")
self.coin_lbl.grid(row=0, columnspan=3)
self.coin_entry = ttk.Entry(self.coin)
self.coin_entry.insert(tk.END, "1")
self.coin_entry.grid(row=1, columnspan=3, sticky=NSEW, padx=2, pady=2)
self.coin_set = ttk.Button(self.coin, text="Set to Amount", command=self.action_coin_set)
self.coin_set.grid(row=2, column=0, sticky=NSEW, padx=2, pady=2)
self.coin_insert = ttk.Button(self.coin, text="Insert Amount", command=self.action_coin_insert)
self.coin_insert.grid(row=2, column=1, sticky=NSEW, padx=2, pady=2)
self.coin_insert = ttk.Button(self.coin, text="Insert Single", command=self.action_coin_insert_single)
self.coin_insert.grid(row=2, column=2, sticky=NSEW, padx=2, pady=2)
@api_action
def action_insert(self, unit: int):
spiceapi.card_insert(self.app.connection, unit, self.card_entry.get())
@api_action
def action_insert_p1(self):
return self.action_insert(0)
@api_action
def action_insert_p2(self):
return self.action_insert(1)
@api_action
def action_coin_set(self):
spiceapi.coin_set(self.app.connection, int(self.coin_entry.get()))
@api_action
def action_coin_insert(self):
spiceapi.coin_insert(self.app.connection, int(self.coin_entry.get()))
@api_action
def action_coin_insert_single(self):
spiceapi.coin_insert(self.app.connection)
class InfoTab(ttk.Frame):
"""The info tab."""
def __init__(self, app, parent, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.app = app
self.parent = parent
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
# info text field
self.txt_info = TextField(self, read_only=True)
self.txt_info.grid(row=0, column=0, sticky=NSEW, padx=2, pady=2)
# refresh button
self.btn_refresh = ttk.Button(self, text="Refresh", command=self.action_refresh)
self.btn_refresh.grid(row=1, column=0, sticky=tk.W+tk.E, padx=2, pady=2)
@api_action
def action_refresh(self):
# get information
avs = spiceapi.info_avs(self.app.connection)
launcher = spiceapi.info_launcher(self.app.connection)
memory = spiceapi.info_memory(self.app.connection)
# build text
avs_text = ""
for k, v in avs.items():
avs_text += f"{k}: {v}\n"
launcher_text = ""
for k, v in launcher.items():
if isinstance(v, list):
launcher_text += f"{k}:\n"
for i in v:
launcher_text += f" {i}\n"
else:
launcher_text += f"{k}: {v}\n"
memory_text = ""
for k, v in memory.items():
memory_text += f"{k}: {v}\n"
# set text
self.txt_info.set_text(
f"AVS:\n{avs_text}\n"
f"Launcher:\n{launcher_text}\n"
f"Memory:\n{memory_text}"
)
class ButtonsTab(ttk.Frame):
"""The buttons tab."""
def __init__(self, app, parent, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.app = app
self.parent = parent
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
# button text field
self.txt_buttons = TextField(self, read_only=True)
self.txt_buttons.grid(row=0, column=0, sticky=NSEW, padx=2, pady=2)
# refresh button
self.btn_refresh = ttk.Button(self, text="Refresh", command=self.action_refresh)
self.btn_refresh.grid(row=1, column=0, sticky=tk.W+tk.E, padx=2, pady=2)
@api_action
def action_refresh(self):
# get states
states = spiceapi.buttons_read(self.app.connection)
# build text
txt = ""
for name, velocity, active in states:
state = "on" if velocity > 0 else "off"
active_txt = "" if active else " (inactive)"
txt += f"{name}: {state}{active_txt}\n"
if len(states) == 0:
txt = "No buttons available."
# set text
self.txt_buttons.set_text(txt)
class AnalogsTab(ttk.Frame):
"""The analogs tab."""
def __init__(self, app, parent, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.app = app
self.parent = parent
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
# button text field
self.txt_analogs = TextField(self, read_only=True)
self.txt_analogs.grid(row=0, column=0, sticky=NSEW, padx=2, pady=2)
# refresh button
self.btn_refresh = ttk.Button(self, text="Refresh", command=self.action_refresh)
self.btn_refresh.grid(row=1, column=0, sticky=tk.W+tk.E, padx=2, pady=2)
@api_action
def action_refresh(self):
# get states
states = spiceapi.analogs_read(self.app.connection)
# build text
txt = ""
for name, value, active in states:
value_txt = round(value, 2)
active_txt = "" if active else " (inactive)"
txt += f"{name}: {value_txt}{active_txt}\n"
if len(states) == 0:
txt = "No analogs available."
# set text
self.txt_analogs.set_text(txt)
class LightsTab(ttk.Frame):
"""The lights tab."""
def __init__(self, app, parent, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.app = app
self.parent = parent
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
# light text field
self.txt_lights = TextField(self, read_only=True)
self.txt_lights.grid(row=0, column=0, sticky=NSEW, padx=2, pady=2)
# refresh button
self.btn_refresh = ttk.Button(self, text="Refresh", command=self.action_refresh)
self.btn_refresh.grid(row=1, column=0, sticky=tk.W+tk.E, padx=2, pady=2)
@api_action
def action_refresh(self):
# get states
states = spiceapi.lights_read(self.app.connection)
# build text
txt = ""
for name, value, active in states:
value_txt = round(value, 2)
active_txt = "" if active else " (inactive)"
txt += f"{name}: {value_txt}{active_txt}\n"
if len(states) == 0:
txt = "No lights available."
# set text
self.txt_lights.set_text(txt)
class MainApp(ttk.Frame):
"""The main application frame."""
def __init__(self, parent, **kwargs):
# init frame
ttk.Frame.__init__(self, parent, **kwargs)
self.parent = parent
self.connection = None
self.tabs = ttk.Notebook(self)
self.tab_control = ControlTab(self, self.tabs)
self.tabs.add(self.tab_control, text="Control")
self.tab_info = InfoTab(self, self.tabs)
self.tabs.add(self.tab_info, text="Info")
self.tab_buttons = ButtonsTab(self, self.tabs)
self.tabs.add(self.tab_buttons, text="Buttons")
self.tab_analogs = AnalogsTab(self, self.tabs)
self.tabs.add(self.tab_analogs, text="Analogs")
self.tab_lights = LightsTab(self, self.tabs)
self.tabs.add(self.tab_lights, text="Lights")
self.tab_manual = ManualTab(self, self.tabs)
self.tabs.add(self.tab_manual, text="Manual")
self.tabs.pack(expand=True, fill=tk.BOTH)
# connection panel
self.frm_connection = ttk.Frame(self)
# host: [field]
self.lbl_host = ttk.Label(self.frm_connection, text="Host:")
self.txt_host = ttk.Entry(self.frm_connection, width=15)
self.lbl_host.columnconfigure(0, weight=1)
self.txt_host.columnconfigure(1, weight=1)
self.txt_host.insert(tk.END, "localhost")
# port: [field]
self.lbl_port = ttk.Label(self.frm_connection, text="Port:")
self.txt_port = ttk.Entry(self.frm_connection, width=5)
self.lbl_port.columnconfigure(2, weight=1)
self.txt_port.columnconfigure(3, weight=1)
self.txt_port.insert(tk.END, "1337")
# pass: [field]
self.lbl_pw = ttk.Label(self.frm_connection, text="Pass:")
self.txt_pw = ttk.Entry(self.frm_connection, width=10)
self.lbl_pw.columnconfigure(4, weight=1)
self.txt_pw.columnconfigure(5, weight=1)
self.txt_pw.insert(tk.END, "debug")
# grid setup
self.lbl_host.grid(row=0, column=0, sticky=tk.W+tk.E, padx=2)
self.txt_host.grid(row=0, column=1, sticky=tk.W+tk.E, padx=2)
self.lbl_port.grid(row=0, column=2, sticky=tk.W+tk.E, padx=2)
self.txt_port.grid(row=0, column=3, sticky=tk.W+tk.E, padx=2)
self.lbl_pw.grid(row=0, column=4, sticky=tk.W+tk.E, padx=2)
self.txt_pw.grid(row=0, column=5, sticky=tk.W+tk.E, padx=2)
self.frm_connection.pack(fill=tk.NONE, pady=2)
# send/connect/disconnect buttons panel
self.frm_connect = ttk.Frame(self)
# connect button
self.btn_connect = ttk.Button(
self.frm_connect,
text="Connect",
command=self.action_connect,
width=16)
# disconnect button
self.btn_disconnect = ttk.Button(
self.frm_connect,
text="Disconnect",
command=self.action_disconnect,
width=16)
# kill button
self.btn_kill = ttk.Button(
self.frm_connect,
text="Kill",
command=self.action_kill,
width=16)
# grid setup
self.btn_connect.grid(row=0, column=1, sticky=tk.W+tk.E, padx=2)
self.btn_disconnect.grid(row=0, column=2, sticky=tk.W+tk.E, padx=2)
self.btn_kill.grid(row=0, column=3, sticky=tk.W+tk.E, padx=2)
self.frm_connect.pack(fill=tk.NONE, pady=2)
def action_connect(self):
"""Gets called when the connect button is pressed."""
# retrieve connection info
host = self.txt_host.get()
port = self.txt_port.get()
password = self.txt_pw.get()
# check input
if not port.isdigit():
tk.messagebox.showerror("Connection Error", f"Port '{port}' is not a valid number.")
return
# create a new connection
try:
self.connection = spiceapi.Connection(host=host, port=int(port), password=password)
except OSError as e:
tk.messagebox.showerror("Connection Error", "Failed to connect: " + str(e))
return
# print success
tk.messagebox.showinfo("Success", "Connected.")
def action_disconnect(self):
"""Gets called when the disconnect button is pressed."""
# check connection
if self.connection:
# close connection
self.connection.close()
self.connection = None
tk.messagebox.showinfo("Success", "Closed connection.")
else:
# print error
tk.messagebox.showinfo("Error", "No active connection.")
def action_kill(self):
"""Gets called when the kill button is pressed."""
# check connection
if self.connection:
spiceapi.control_exit(self.connection)
self.connection = None
else:
tk.messagebox.showinfo("Error", "No active connection.")
if __name__ == "__main__":
# create root
root = tk.Tk()
root.title("SpiceRemote")
root.geometry("500x300")
# set theme
preferred_theme = "clam"
s = ttk.Style(root)
if preferred_theme in s.theme_names():
s.theme_use(preferred_theme)
# add application
MainApp(root).pack(side=tk.TOP, fill=tk.BOTH, expand=True)
# run root
root.mainloop()
+267
View File
@@ -0,0 +1,267 @@
SpiceTools
Game Support
Sound Voltex
Beatmania IIDX
Jubeat
Reflec Beat
GitaDora
Nostalgia
Coins
Readers (Experimental)
HID Smartcard Readers
SpiceTools API
Bemanitools 5 API Compatibility
Keybindings/Analogs
Keypad Bindings
Command Line Options
Examples
Download
Installation
Usage
Contributing
Changelog
SpiceTools
Ainsley's preferred toolkit.
https://i.imgur.com/TNFaHSf.png
This tool allows you to run several Konami games on your PC.
It's an earliest version, so you may experience some crashes caused from weird HID devices. If you experience this, please unplug unnecessary USB devices and try again.
SpiceTools currently consists just of spice(64).exe, so you can keep your game data original and clean.
Many game dumps have kbt.dll/kld.dll obfuscated or missing, in this case you can use the replacement DLLs below.
Game Support
Sound Voltex
Beatmania IIDX (Tricoro or newer)
Jubeat
Reflec Beat
Dance Evolution
Dance Dance Revolution
Beatstream
Road Fighters 3D
Pop'n Music
Museca
Metal Gear Arcade
GitaDora
Nostalgia
Bishi Bashi Channel
Quiz Magic Academy
More to come...
Comes in 32bit and 64bit builds.
Sound Voltex
Full feature support.
Printer fully supported as of 03/13/2018, can save prints in PNG/BMP/TGA to a customizable output path.
Optionally able to clean up saved images on game boot time.
Keyboard play should be perfectly working. If you dont want to use the mouse for the knobs, you can make use of native knob emulation, which works just as it works in Keyshoot-Mania.
Beatmania IIDX
Works as expected.
For using your own cameras in IIDX 25 or newer, just plug them in (and hopefully your camera isn't incompatible with the game) and they shall work.
It's also possible to use one camera only instead of the two the game wants, just dismiss the camera error on game boot as you would with no cameras connected, and your camera should be registered in-game as CAMERA A.
If you're not happy with the order of the automatic camera detection, just use -iidxflipcams and CAMERA B will be CAMERA A, etc. Shoutouts to Xyen for assistance.
You can bind the effectors as analog inputs if you want, this is especially interesting for IIDX 25 and newer, where the effects are software based and not tied to specific sound cards anymore. When not bound, they will stay on the maximum value.
Jubeat
This makes use of the SpiceTools Touch™ module, which makes you able to play any version of the game just fine with both mouse and HID compliant touch devices on Windows 7 or newer.
The Dell ST2240T touch screen seems to work well, especially the new 2017 revision.
Card insert button available and enabled by default.
Touch the area to insert a card in attract mode.
Reflec Beat
Including emulation of the real IR touch screen device so the game should just act like the real one.
This makes use of the SpiceTools Touch™ module, which makes you able to play any version of the game just fine with both mouse and HID compliant touch devices on Windows 7 or newer.
The Dell ST2240T touch screen seems to work well, especially the new 2017 revision.
Card insert button available and enabled by default.
For this game it's not visible in fullscreen but still works just as fine.
Touch the area to insert a card in attract mode.
http://i.imgur.com/MK2bQcX.png
GitaDora
To switch between Guitar/Drum you need to specify the specific eamuse config to load. By default, SpiceTools first tries to load ea3-config.xml, then eamuse-config.xml.
Since the latter one is the default for the drums, for the guitar you have to override the path setting:
spice64.exe -e prop\eamuse-config2.xml
Additionally, since GitaDora uses 4 channels by default, you can specify -2ch to run it with 2 channel audio instead. If you don't and have a normal 2 channel setup, the game will refuse to play sound/tracks.
Nostalgia
Spicetools will auto-detect whether or not a compatible touch device is present. If a touch device is detected, the mouse cursor will disappear. If no touch device is detected, the mouse cursor will be visible in-game. During boot-up process, do not click or else the game may pre-maturely crash. You may safely click after the monitor check is completed. It's been found that Virtual HID Touch devices commonly found with remote access software may cause detection issues, take note of that.
Coins
There's a coin emulation working for most games.
Press F1 to insert a single coin.
Some games have an additional/different coin key.
Readers (Experimental)
If you have an original Wavepass reader and have it connected to a COM port on your computer, SpiceTools should be able to use it. It was being created and tested with the newer readers also used in the newer Pop'n Music games.
Example for one reader on COM3: -reader COM3
Example for two readers for P1/2 on COM3 and COM4: -reader COM3 -reader COM4
Example for one reader with P1/2 toggle on NumLock: -togglereader COM3
HID Smartcard Readers
To activate HID smartcard reader functionality, use "-scard". It will use the first reader it detects for P1 and the second one for P2. To flip the order of detection, use "-scardflip".
It will attempt to read the UID of all and every card swiped and feed it to the game.
Thanks to refutatio for his contribution to this.
SpiceTools API
SpiceTools has its own API with a custom protocol. The main goals were simplicity, portability and extensibility, so TCP/JSON was chosen, allowing fast access over network.
It optionally supports passwords/encryption and its response time on LAN was tested to be below 1ms (as long as extended API logging isn't enabled manually).
For details and how to use it, check out README.md in the included source. Additionally, python and dart libraries are provided for easy communication.
Key features are card/coin insertions, read/write access for keypads/analogs/buttons/lights, status information and memory read/write via raw addresses and signatures.
We're currently working on an Android/iOS companion app using this API, stay tuned!
Bemanitools 5 API Compatibility
On request, I've added a basic BT5 API integration as of 03/13/2018.
This feature can be enabled with `-bt5api` and currently makes use of eamio.dll only.
Currently, this can be used for applications such as the popular NFCeamuse app which can be found elsewhere.
If I see interest in making this feature more complete, such as loading the other parts of the API (geninput.dll and whatnot), this feature might be extended in the future.
Keybindings/Analogs
As of beta, Buttons can be bound via either RawInput (the default) or "Naive" (GetAsyncKeyState).
RawInput supports all kinds of HID controllers (your FP7 is one, your XBOX360 Controller as well), however it's bound to a device.
If you plug your controller into another USB slot, SpiceTools does not have a way to detect it and will report the device as "missing", until you plug it back into where you originally configured it.
Using the "naive" approach, only keyboard buttons can be detected, however this will then work with any keyboard attached, it's not bound to a device.
Since May 2018 you can also bind MIDI devices for "one-shot" types like drums and hold types like piano keys and launchpads.
Keypad Bindings
As of beta, you can bind your P1/P2 keypads to specific devices.
The number buttons will be, you guessed it, the numbers, and the Plus (+) key will be the card insert trigger.
The card files can be named card0.txt for P1 and card1.txt for P2, placed in your game root, they will work automatically.
Alternatively you can adjust the path of your card file in the configuration utility.
If you don't specify any keypad, SpiceTools will use any keypad available, use the top-row number keys and have a card trigger on backspace as well. Toggling players via NumLock may or may not work, windows is dumb.
Command Line Options
These are for advanced usage. Don't be scared if you don't understand what most of them do.
=> Moved to config so that I don't have to maintain two lists seperately.
Check the "About" tab in the latest release.
Examples
Run SDVX windowed: spice -w
Run SDVX windowed (long version): spice soundvoltex.dll -io -sdvx -w
Run Reflec Beat with some hook: spice -k hook.dll
Run Reflec Beat with some hook (long version): spice reflecbeat.dll -io -rb -k hook.dll
The short versions are using game auto-detection. This makes it possible for you to just double click spice.exe to run the game, except if you want to use some special options.
It is planned, that once the configuration is done, you can specify your options there, so you don't have to deal with custom batch scripts all the time.
Download
Currently no version numbers, so for now you have to check the changelog.
Bugs may be present, so please report any issues so they can be fixed.
Download the latest version for the newest features, but be aware that it might have bugs.
Just as a little reminder, the whole tool is still very new and might not be stable for you.
If you want your issues to get fixed, then you will have to give me feedback.
Latest Download (incl. source code) (04/14/2019): Direct
Previous Download (03/14/2019): Direct
Alpha Download (01/23/2018): Direct
Legacy Download (07/28/2017): Direct
No it doesn't contain malware, your anti-virus solution is unreliable and retarded.
I don't think I will ever get around false positives anymore. If you don't trust it, don't run it, just like with other software you download from the internet.
Installation
1. Extract the contents of either the 32 or 64 folder into your game folder depending on your game.
2. ??
3. Profit
Usage
Run configuration window with the standalone spicecfg.exe (or spice.exe -cfg) to configurate key bindings.
Double click spice(64).exe to run in simple mode.
For advanced settings create a batch file to launch spice.exe with custom options described above.
Contributing
If you want to help fix bugs and adding features, just contact me via DM on OOC or ask Kon on Discord. We can add you to our Git repository, so you can directly submit patches.
Contributors:
kon
cube
felix
dinsfire
nolm
Changelog
Not including hotfixes.
04/13/2019 - DDR SD Mode lights
Sextet (LIT Board) support
PIUIO hardware support
Windowed mode improvements
03/14/2019 - IIDX API module
EA server maintenance toggle
SDVX BI2A support
02/19/2019 - Network API server
Run outside of modules dir
Misc bug fixes
01/27/2019 - Jubeat Knit support
Quiz Magic Academy support
Museca/Jubeat lights
Smartcard NumLock toggle
01/11/2019 - Open Source!
Jubeat Copious support
Bishi Bashi Channel support
PacDrive/PocketVoltex support
KBT/KLD now are optional
Stacktraces, quiet crash
Huge code refactoring
Stability improvements/fixes
11/03/2018 - IIDX 25 (io, cams, light)
IIDX effector bindings
HID lighting output support
Button Multibinding
HID readers e.g. zyp's cardio
Bug fixes as usual
10/04/2018 - Bishi Bashi Channel support, bugfixes
08/17/2018 - DDR X3 and Nostalgia support, improved network code
06/25/2018 - HID smartcard readers
06/08/2018 - DDR support
06/07/2018 - GitaDora/HID/EA bug fixes
05/18/2018 - GitaDora and MIDI devices
04/21/2018 - Card path configuration
04/09/2018 - HID/Analog/RawInput support - entering BETA state
03/13/2018 - SDVX printer emulation, felica, partial BT5 API support
01/23/2018 - MUSECA analog slowdown, fixes
01/21/2018 - many fixed bugs/crashes
11/15/2017 - Fixed the Museca sound issue
11/14/2017 - Added Museca, Pop'n Music, wavepass reader support
09/11/2017 - Added Road Fighters 3D
08/29/2017 - Custom modules path, UAC Admin Manifest, Netfix module
08/28/2017 - Bug Fixes
08/21/2017 - 64bit support, Beatstream support, more command line options
07/28/2017 - Fixed I/O for IIDX and SDVX
07/26/2017 - configuration tool, jubeat, touch module, more options, bug fixes
06/01/2017 - added DEA support
05/31/2017 - public release
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
# SpiceAPI Arduino Library
This library is still a bit experimental and might contain bugs.
To use this library, it's recommended to just copy the Arduino project and start from that.
@@ -0,0 +1,169 @@
#ifndef SPICEAPI_CONNECTION_H
#define SPICEAPI_CONNECTION_H
#include <stdint.h>
#include "rc4.h"
#ifndef SPICEAPI_INTERFACE
#define SPICEAPI_INTERFACE Serial
#endif
namespace spiceapi {
class Connection {
private:
uint8_t* receive_buffer;
size_t receive_buffer_size;
const char* password;
RC4* cipher;
public:
Connection(size_t receive_buffer_size, const char* password = "");
~Connection();
void reset();
bool check();
void cipher_alloc(const char *session_key = nullptr);
void change_pass(const char* password, bool session = false);
const char* request(const char* json, size_t timeout = 1000);
const char* request(char* json, size_t timeout = 1000);
};
}
spiceapi::Connection::Connection(size_t receive_buffer_size, const char* password) {
this->receive_buffer = new uint8_t[receive_buffer_size];
this->receive_buffer_size = receive_buffer_size;
this->password = password;
this->cipher = nullptr;
this->reset();
}
spiceapi::Connection::~Connection() {
// clean up
if (this->cipher != nullptr)
delete this->cipher;
}
void spiceapi::Connection::reset() {
// drop all input
while (SPICEAPI_INTERFACE.available()) {
SPICEAPI_INTERFACE.read();
}
#ifdef SPICEAPI_INTERFACE_WIFICLIENT
// reconnect TCP client
SPICEAPI_INTERFACE.stop();
this->check();
#else
// 8 zeroes reset the password/session on serial
for (size_t i = 0; i < 8; i++) {
SPICEAPI_INTERFACE.write(0);
}
#endif
// reset password
this->cipher_alloc();
}
void spiceapi::Connection::cipher_alloc(const char *session_key) {
// delete old cipher
if (this->cipher != nullptr) {
delete this->cipher;
this->cipher = nullptr;
}
// create new cipher if password is set
session_key = session_key ? session_key : this->password;
if (strlen(session_key) > 0) {
this->cipher = new RC4(
(uint8_t *) session_key,
strlen(session_key));
}
}
bool spiceapi::Connection::check() {
#ifdef SPICEAPI_INTERFACE_WIFICLIENT
if (!SPICEAPI_INTERFACE.connected()) {
return SPICEAPI_INTERFACE.connect(
SPICEAPI_INTERFACE_WIFICLIENT_HOST,
SPICEAPI_INTERFACE_WIFICLIENT_PORT);
} else {
return true;
}
#else
// serial is always valid
return true;
#endif
}
void spiceapi::Connection::change_pass(const char* password, bool session) {
if (!session) {
this->password = password;
}
this->cipher_alloc(password);
}
const char* spiceapi::Connection::request(const char* json, size_t timeout) {
auto json_len = strlen(json);
strncpy((char*) receive_buffer, json, receive_buffer_size);
return request((char*) receive_buffer, timeout);
}
const char* spiceapi::Connection::request(char* json_data, size_t timeout) {
// check connection
if (!this->check())
return "";
// crypt
auto json_len = strlen(json_data) + 1;
if (this->cipher != nullptr)
this->cipher->crypt((uint8_t*) json_data, json_len);
// send
auto send_result = SPICEAPI_INTERFACE.write((const char*) json_data, (int) json_len);
SPICEAPI_INTERFACE.flush();
if (send_result < (int) json_len) {
return "";
}
// receive
size_t receive_data_len = 0;
auto t_start = millis();
while (SPICEAPI_INTERFACE) {
// check for timeout
if (millis() - t_start > timeout) {
this->reset();
return "";
}
// read single byte
auto b = SPICEAPI_INTERFACE.read();
if (b < 0) continue;
receive_buffer[receive_data_len++] = b;
// check for buffer overflow
if (receive_data_len >= receive_buffer_size) {
this->reset();
return "";
}
// crypt
if (this->cipher != nullptr)
this->cipher->crypt(&receive_buffer[receive_data_len - 1], 1);
// check for message end
if (receive_buffer[receive_data_len - 1] == 0)
break;
}
// return resulting json
return (const char*) &receive_buffer[0];
}
#endif //SPICEAPI_CONNECTION_H
@@ -0,0 +1,65 @@
#ifndef SPICEAPI_RC4_H
#define SPICEAPI_RC4_H
#include <stdint.h>
#include <stddef.h>
namespace spiceapi {
class RC4 {
private:
uint8_t s_box[256];
size_t a = 0, b = 0;
public:
RC4(uint8_t *key, size_t key_size);
void crypt(uint8_t *data, size_t size);
};
}
spiceapi::RC4::RC4(uint8_t *key, size_t key_size) {
// initialize S-BOX
for (size_t i = 0; i < sizeof(s_box); i++)
s_box[i] = (uint8_t) i;
// check key size
if (!key_size)
return;
// KSA
size_t j = 0;
for (size_t i = 0; i < sizeof(s_box); i++) {
// update
j = (j + s_box[i] + key[i % key_size]) % sizeof(s_box);
// swap
auto tmp = s_box[i];
s_box[i] = s_box[j];
s_box[j] = tmp;
}
}
void spiceapi::RC4::crypt(uint8_t *data, size_t size) {
// iterate all bytes
for (size_t pos = 0; pos < size; pos++) {
// update
a = (a + 1) % sizeof(s_box);
b = (b + s_box[a]) % sizeof(s_box);
// swap
auto tmp = s_box[a];
s_box[a] = s_box[b];
s_box[b] = tmp;
// crypt
data[pos] ^= s_box[(s_box[a] + s_box[b]) % sizeof(s_box)];
}
}
#endif //SPICEAPI_RC4_H
@@ -0,0 +1,172 @@
/*
* SpiceAPI Arduino Example Project
*
* To enable it in SpiceTools, use "-api 1337 -apipass changeme -apiserial COM1" or similar.
*/
/*
* SpiceAPI Wrapper Buffer Sizes
*
* They should be as big as possible to be able to create/parse
* some of the bigger requests/responses. Due to dynamic memory
* limitations of some weaker devices, if you set them too high
* you will probably experience crashes/bugs/problems, one
* example would be "Request ID is invalid" in the log.
*/
#define SPICEAPI_WRAPPER_BUFFER_SIZE 256
#define SPICEAPI_WRAPPER_BUFFER_SIZE_STR 256
/*
* WiFi Support
* Uncomment to enable the wireless API interface.
*/
//#define ENABLE_WIFI
/*
* WiFi Settings
* You can ignore these if you don't plan on using WiFi
*/
#ifdef ENABLE_WIFI
#include <ESP8266WiFi.h>
WiFiClient client;
#define SPICEAPI_INTERFACE client
#define SPICEAPI_INTERFACE_WIFICLIENT
#define SPICEAPI_INTERFACE_WIFICLIENT_HOST "192.168.178.143"
#define SPICEAPI_INTERFACE_WIFICLIENT_PORT 1337
#define WIFI_SSID "MySSID"
#define WIFI_PASS "MyWifiPassword"
#endif
/*
* This is the interface a serial connection will use.
* You can change this to another Serial port, e.g. with an
* Arduino Mega you can use Serial1/Serial2/Serial3.
*/
#ifndef ENABLE_WIFI
#define SPICEAPI_INTERFACE Serial
#endif
/*
* SpiceAPI Includes
*
* If you have the JSON strings beforehands or want to craft them
* manually, you don't have to import the wrappers at all and can
* use Connection::request to send and receive raw JSON strings.
*/
#include "connection.h"
#include "wrappers.h"
/*
* This global object represents the API connection.
* The first parameter is the buffer size of the JSON string
* we're receiving. So a size of 512 will only be able to
* hold a JSON of 512 characters maximum.
*
* An empty password string means no password is being used.
* This is the recommended when using Serial only.
*/
spiceapi::Connection CON(512, "changeme");
void setup() {
#ifdef ENABLE_WIFI
/*
* When using WiFi, we can use the Serial interface for debugging.
* You can open Serial Monitor and see what IP it gets assigned to.
*/
Serial.begin(57600);
// set WiFi mode to station (disables integrated AP)
WiFi.mode(WIFI_STA);
// now try connecting to our Router/AP
Serial.print("Connecting");
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// print debug info over serial
Serial.print("\nLocal IP: ");
Serial.println(WiFi.localIP());
#else
/*
* Since the API makes use of the Serial module, we need to
* set it up using our preferred baud rate manually.
*/
SPICEAPI_INTERFACE.begin(57600);
while (!SPICEAPI_INTERFACE);
#endif
}
void loop() {
/*
* Here's a few tests/examples on how to make use of the wrappers.
*/
// insert cards for P1/P2
spiceapi::card_insert(CON, 0, "E004012345678901");
spiceapi::card_insert(CON, 1, "E004012345678902");
// insert a single coin / multiple coins
spiceapi::coin_insert(CON);
spiceapi::coin_insert(CON, 3);
// get the IIDX led ticker text
char ticker[9];
if (spiceapi::iidx_ticker_get(CON, ticker)) {
// if a function returns true, that means success
// now we can do something with the ticker as if it was a string
//Serial1.println(ticker);
}
// get AVS info
spiceapi::InfoAvs avs_info {};
if (spiceapi::info_avs(CON, avs_info)) {
//Serial1.println(avs_info.model);
}
// enter some keys on P1 keypad (blocks until sequence is entered fully)
spiceapi::keypads_write(CON, 0, "1234");
// get light states
spiceapi::LightState lights[8];
size_t lights_size = spiceapi::lights_read(CON, lights, 8);
for (size_t i = 0; i < lights_size; i++) {
auto &light = lights[i];
//Serial1.println(light.name);
//Serial1.println(light.value);
// modify value to full bright
light.value = 1.f;
}
// send back modified light states
spiceapi::lights_write(CON, lights, lights_size);
// refresh session (generates new crypt key, not that important for serial)
spiceapi::control_session_refresh(CON);
// you can also manually send requests without the wrappers
// this avoids json generation, but you still need to parse it in some way
const char *answer_json = CON.request(
"{"
"\"id\": 0,"
"\"module\":\"coin\","
"\"function\":\"insert\","
"\"params\":[]"
"}"
);
/*
* For more functions/information, just check out wrappers.h yourself.
* Have fun :)
*/
delay(5000);
}
@@ -0,0 +1,716 @@
#ifndef SPICEAPI_WRAPPERS_H
#define SPICEAPI_WRAPPERS_H
#define ARDUINOJSON_USE_LONG_LONG 1
#include "ArduinoJson.h"
#include <Arduino.h>
#include "connection.h"
// default buffer sizes
#ifndef SPICEAPI_WRAPPER_BUFFER_SIZE
#define SPICEAPI_WRAPPER_BUFFER_SIZE 256
#endif
#ifndef SPICEAPI_WRAPPER_BUFFER_SIZE_STR
#define SPICEAPI_WRAPPER_BUFFER_SIZE_STR 256
#endif
namespace spiceapi {
/*
* Structs
*/
struct AnalogState {
String name = "";
float value = 0.f;
bool enabled = false;
};
struct ButtonState {
String name = "";
float value = 0.f;
bool enabled = false;
};
struct LightState {
String name = "";
float value = 0.f;
bool enabled = false;
};
struct InfoAvs {
String model, dest, spec, rev, ext;
};
struct InfoLauncher {
String version;
String compile_date, compile_time, system_time;
String args;
};
struct InfoMemory {
uint64_t mem_total, mem_total_used, mem_used;
uint64_t vmem_total, vmem_total_used, vmem_used;
};
struct TouchState {
uint64_t id;
int64_t x, y;
};
// static storage
char JSON_BUFFER_STR[SPICEAPI_WRAPPER_BUFFER_SIZE_STR];
/*
* Helpers
*/
uint64_t msg_gen_id() {
static uint64_t id_global = 0;
return ++id_global;
}
char *doc2str(DynamicJsonDocument *doc) {
char *buf = JSON_BUFFER_STR;
serializeJson(*doc, buf, SPICEAPI_WRAPPER_BUFFER_SIZE_STR);
return buf;
}
DynamicJsonDocument *request_gen(const char *module, const char *function) {
// create document
auto doc = new DynamicJsonDocument(SPICEAPI_WRAPPER_BUFFER_SIZE);
// add attributes
(*doc)["id"] = msg_gen_id();
(*doc)["module"] = module;
(*doc)["function"] = function;
// add params
(*doc).createNestedArray("params");
// return document
return doc;
}
DynamicJsonDocument *response_get(Connection &con, const char *json) {
// parse document
DynamicJsonDocument *doc = new DynamicJsonDocument(SPICEAPI_WRAPPER_BUFFER_SIZE);
auto err = deserializeJson(*doc, (char *) json);
// check for parse error
if (err) {
// reset cipher
con.cipher_alloc();
delete doc;
return nullptr;
}
// check id
if (!(*doc)["id"].is<int64_t>()) {
delete doc;
return nullptr;
}
// check errors
auto errors = (*doc)["errors"];
if (!errors.is<JsonArray>()) {
delete doc;
return nullptr;
}
// check error count
if (errors.as<JsonArray>().size() > 0) {
delete doc;
return nullptr;
}
// check data
if (!(*doc)["data"].is<JsonArray>()) {
delete doc;
return nullptr;
}
// return document
return doc;
}
/*
* Wrappers
*/
size_t analogs_read(Connection &con, AnalogState *buffer, size_t buffer_elements) {
auto req = request_gen("analogs", "read");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return 0;
auto data = (*res)["data"].as<JsonArray>();
size_t buffer_count = 0;
for (auto val : data) {
if (buffer_count >= buffer_elements) {
delete res;
return buffer_count;
}
buffer[buffer_count].name = (const char*) val[0];
buffer[buffer_count].value = val[1];
buffer[buffer_count].enabled = val[2];
buffer_count++;
}
delete res;
return buffer_count;
}
bool analogs_write(Connection &con, AnalogState *buffer, size_t buffer_elements) {
auto req = request_gen("analogs", "write");
auto params = (*req)["params"].as<JsonArray>();
for (size_t i = 0; i < buffer_elements; i++) {
auto &state = buffer[i];
auto data = params.createNestedArray();
data.add(state.name);
data.add(state.value);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool analogs_write_reset(Connection &con, AnalogState *buffer, size_t buffer_elements) {
auto req = request_gen("analogs", "write_reset");
auto params = (*req)["params"].as<JsonArray>();
for (size_t i = 0; i < buffer_elements; i++) {
auto &state = buffer[i];
auto data = params.createNestedArray();
data.add(state.name);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
size_t buttons_read(Connection &con, ButtonState *buffer, size_t buffer_elements) {
auto req = request_gen("buttons", "read");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return 0;
auto data = (*res)["data"].as<JsonArray>();
size_t buffer_count = 0;
for (auto val : data) {
if (buffer_count >= buffer_elements) {
delete res;
return buffer_count;
}
buffer[buffer_count].name = (const char*) val[0];
buffer[buffer_count].value = val[1];
buffer[buffer_count].enabled = val[2];
buffer_count++;
}
delete res;
return buffer_count;
}
bool buttons_write(Connection &con, ButtonState *buffer, size_t buffer_elements) {
auto req = request_gen("buttons", "write");
auto params = (*req)["params"].as<JsonArray>();
for (size_t i = 0; i < buffer_elements; i++) {
auto &state = buffer[i];
auto data = params.createNestedArray();
data.add(state.name);
data.add(state.value);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool buttons_write_reset(Connection &con, ButtonState *buffer, size_t buffer_elements) {
auto req = request_gen("buttons", "write_reset");
auto params = (*req)["params"].as<JsonArray>();
for (size_t i = 0; i < buffer_elements; i++) {
auto &state = buffer[i];
auto data = params.createNestedArray();
data.add(state.name);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool card_insert(Connection &con, size_t index, const char *card_id) {
auto req = request_gen("card", "insert");
auto params = (*req)["params"].as<JsonArray>();
params.add(index);
params.add(card_id);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool coin_get(Connection &con, int &coins) {
auto req = request_gen("coin", "insert");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
coins = (*res)["data"][0];
delete res;
return true;
}
bool coin_set(Connection &con, int coins) {
auto req = request_gen("coin", "set");
auto params = (*req)["params"].as<JsonArray>();
params.add(coins);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool coin_insert(Connection &con, int coins=1) {
auto req = request_gen("coin", "insert");
if (coins != 1) {
auto params = (*req)["params"].as<JsonArray>();
params.add(coins);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool control_raise(Connection &con, const char *signal) {
auto req = request_gen("control", "raise");
auto params = (*req)["params"].as<JsonArray>();
params.add(signal);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool control_exit(Connection &con) {
auto req = request_gen("control", "exit");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool control_exit(Connection &con, int exit_code) {
auto req = request_gen("control", "exit");
auto params = (*req)["params"].as<JsonArray>();
params.add(exit_code);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool control_restart(Connection &con) {
auto req = request_gen("control", "restart");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool control_session_refresh(Connection &con) {
auto req = request_gen("control", "session_refresh");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
const char *key = (*res)["data"][0];
con.change_pass(key, true);
delete res;
return true;
}
bool control_shutdown(Connection &con) {
auto req = request_gen("control", "shutdown");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool control_reboot(Connection &con) {
auto req = request_gen("control", "reboot");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool iidx_ticker_get(Connection &con, char *ticker) {
auto req = request_gen("iidx", "ticker_get");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
const char *data = (*res)["data"][0];
strncpy(ticker, data, 9);
ticker[9] = 0x00;
delete res;
return true;
}
bool iidx_ticker_set(Connection &con, const char *ticker) {
auto req = request_gen("iidx", "ticker_set");
auto params = (*req)["params"].as<JsonArray>();
params.add(ticker);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool iidx_ticker_reset(Connection &con) {
auto req = request_gen("iidx", "ticker_reset");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool info_avs(Connection &con, InfoAvs &info) {
auto req = request_gen("info", "avs");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
auto data = (*res)["data"][0];
info.model = (const char*) data["model"];
info.dest = (const char*) data["dest"];
info.spec = (const char*) data["spec"];
info.rev = (const char*) data["rev"];
info.ext = (const char*) data["ext"];
delete res;
return true;
}
bool info_launcher(Connection &con, InfoLauncher &info) {
auto req = request_gen("info", "launcher");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
auto data = (*res)["data"][0];
info.version = (const char*) data["version"];
info.compile_date = (const char*) data["compile_date"];
info.compile_time = (const char*) data["compile_time"];
info.system_time = (const char*) data["system_time"];
for (auto arg : data["args"].as<JsonArray>()) {
info.args += (const char*) arg;
info.args += " ";
// TODO: remove last space
}
delete res;
return true;
}
bool info_memory(Connection &con, InfoMemory &info) {
auto req = request_gen("info", "memory");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
auto data = (*res)["data"][0];
info.mem_total = data["mem_total"];
info.mem_total_used = data["mem_total_used"];
info.mem_used = data["mem_used"];
info.vmem_total = data["vmem_total"];
info.vmem_total_used = data["vmem_total_used"];
info.vmem_used = data["vmem_used"];
delete res;
return true;
}
bool keypads_write(Connection &con, unsigned int keypad, const char *input) {
auto req = request_gen("keypads", "write");
auto params = (*req)["params"].as<JsonArray>();
params.add(keypad);
params.add(input);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str, 1000 + strlen(input) * 300));
if (!res)
return false;
delete res;
return true;
}
bool keypads_set(Connection &con, unsigned int keypad, const char *keys) {
auto keys_len = strlen(keys);
auto req = request_gen("keypads", "set");
auto params = (*req)["params"].as<JsonArray>();
params.add(keypad);
for (size_t i = 0; i < keys_len; i++) {
char buf[] = {keys[i], 0x00};
params.add(buf);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool keypads_get(Connection &con, unsigned int keypad, char *keys, size_t keys_len) {
auto req = request_gen("keypads", "get");
auto params = (*req)["params"].as<JsonArray>();
params.add(keypad);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
for (auto key : (*res)["data"].as<JsonArray>()) {
const char *key_str = key;
if (key_str != nullptr && keys_len > 0) {
*(keys++) = key_str[0];
keys_len--;
}
}
delete res;
return true;
}
size_t lights_read(Connection &con, LightState *buffer, size_t buffer_elements) {
auto req = request_gen("lights", "read");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return 0;
auto data = (*res)["data"].as<JsonArray>();
size_t buffer_count = 0;
for (auto val : data) {
if (buffer_count >= buffer_elements) {
delete res;
return buffer_count;
}
buffer[buffer_count].name = (const char*) val[0];
buffer[buffer_count].value = val[1];
buffer[buffer_count].enabled = val[2];
buffer_count++;
}
delete res;
return buffer_count;
}
bool lights_write(Connection &con, LightState *buffer, size_t buffer_elements) {
auto req = request_gen("lights", "write");
auto params = (*req)["params"].as<JsonArray>();
for (size_t i = 0; i < buffer_elements; i++) {
auto &state = buffer[i];
auto data = params.createNestedArray();
data.add(state.name);
data.add(state.value);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool lights_write_reset(Connection &con, LightState *buffer, size_t buffer_elements) {
auto req = request_gen("lights", "write_reset");
auto params = (*req)["params"].as<JsonArray>();
for (size_t i = 0; i < buffer_elements; i++) {
auto &state = buffer[i];
auto data = params.createNestedArray();
data.add(state.name);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool memory_write(Connection &con, const char *dll_name, const char *hex, uint32_t offset) {
auto req = request_gen("memory", "write");
auto params = (*req)["params"].as<JsonArray>();
params.add(dll_name);
params.add(hex);
params.add(offset);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool memory_read(Connection &con, const char *dll_name, uint32_t offset, uint32_t size, String &hex) {
auto req = request_gen("memory", "read");
auto params = (*req)["params"].as<JsonArray>();
params.add(dll_name);
params.add(offset);
params.add(size);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
hex = (const char*) (*res)["data"][0];
delete res;
return true;
}
bool memory_signature(Connection &con, const char *dll_name, const char *signature,
const char *replacement, uint32_t offset, uint32_t usage, uint32_t &file_offset) {
auto req = request_gen("memory", "signature");
auto params = (*req)["params"].as<JsonArray>();
params.add(dll_name);
params.add(signature);
params.add(replacement);
params.add(offset);
params.add(usage);
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
file_offset = (*res)["data"][0];
delete res;
return true;
}
size_t touch_read(Connection &con, TouchState *buffer, size_t buffer_elements) {
auto req = request_gen("touch", "read");
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return 0;
auto data = (*res)["data"].as<JsonArray>();
size_t buffer_count = 0;
for (auto val : data) {
if (buffer_count >= buffer_elements) {
delete res;
return buffer_count;
}
buffer[buffer_count].id = val[0];
buffer[buffer_count].x = val[1];
buffer[buffer_count].y = val[2];
buffer_count++;
}
delete res;
return buffer_count;
}
bool touch_write(Connection &con, TouchState *buffer, size_t buffer_elements) {
auto req = request_gen("touch", "write");
auto params = (*req)["params"].as<JsonArray>();
for (size_t i = 0; i < buffer_elements; i++) {
auto &state = buffer[i];
auto data = params.createNestedArray();
data.add(state.id);
data.add(state.x);
data.add(state.y);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
bool touch_write_reset(Connection &con, TouchState *buffer, size_t buffer_elements) {
auto req = request_gen("touch", "write_reset");
auto params = (*req)["params"].as<JsonArray>();
for (size_t i = 0; i < buffer_elements; i++) {
auto &state = buffer[i];
auto data = params.createNestedArray();
data.add(state.id);
}
auto req_str = doc2str(req);
delete req;
auto res = response_get(con, con.request(req_str));
if (!res)
return false;
delete res;
return true;
}
}
#endif //SPICEAPI_WRAPPERS_H
@@ -0,0 +1,8 @@
# SpiceAPI C++ Library
This library is still a bit experimental and might contain bugs.
To include it into your project, it's recommended to just copy the
files into your source directory.
To use the wrappers, RapidJSON is required and you might need to
adjust the include paths for your project's build.
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
@@ -0,0 +1,181 @@
#include <iostream>
#include <ws2tcpip.h>
#include "connection.h"
namespace spiceapi {
// settings
static const size_t RECEIVE_BUFFER_SIZE = 64 * 1024;
static const int RECEIVE_TIMEOUT = 1000;
}
spiceapi::Connection::Connection(std::string host, uint16_t port, std::string password) {
this->host = host;
this->port = port;
this->password = password;
this->socket = INVALID_SOCKET;
this->cipher = nullptr;
// WSA startup
WSADATA wsa_data;
int error = WSAStartup(MAKEWORD(2, 2), &wsa_data);
if (error) {
std::cerr << "Failed to start WSA: " << error << std::endl;
exit(1);
}
}
spiceapi::Connection::~Connection() {
// clean up
if (this->cipher != nullptr)
delete this->cipher;
// cleanup WSA
WSACleanup();
}
void spiceapi::Connection::cipher_alloc() {
// delete old cipher
if (this->cipher != nullptr) {
delete this->cipher;
this->cipher = nullptr;
}
// create new cipher if password is set
if (this->password.length() > 0) {
this->cipher = new RC4(
(uint8_t *) this->password.c_str(),
strlen(this->password.c_str()));
}
}
bool spiceapi::Connection::check() {
int result = 0;
// check if socket is invalid
if (this->socket == INVALID_SOCKET) {
// get all addresses
addrinfo *addr_list;
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
if ((result = getaddrinfo(
this->host.c_str(),
std::to_string(this->port).c_str(),
&hints,
&addr_list))) {
std::cerr << "getaddrinfo failed: " << result << std::endl;
return false;
}
// check all addresses
for (addrinfo *addr = addr_list; addr != NULL; addr = addr->ai_next) {
// try open socket
this->socket = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (this->socket == INVALID_SOCKET) {
std::cerr << "socket failed: " << WSAGetLastError() << std::endl;
freeaddrinfo(addr_list);
return false;
}
// try connect
result = connect(this->socket, addr->ai_addr, (int) addr->ai_addrlen);
if (result == SOCKET_ERROR) {
closesocket(this->socket);
this->socket = INVALID_SOCKET;
continue;
}
// configure socket
int opt_val;
opt_val = 1;
setsockopt(this->socket, IPPROTO_TCP, TCP_NODELAY, (const char*) &opt_val, sizeof(opt_val));
opt_val = RECEIVE_TIMEOUT;
setsockopt(this->socket, SOL_SOCKET, SO_RCVTIMEO, (const char*) &opt_val, sizeof(opt_val));
// connection successful
this->cipher_alloc();
break;
}
// check if successful
freeaddrinfo(addr_list);
if (this->socket == INVALID_SOCKET) {
return false;
}
}
// socket probably still valid
return true;
}
void spiceapi::Connection::change_pass(std::string password) {
this->password = password;
this->cipher_alloc();
}
std::string spiceapi::Connection::request(std::string json) {
// check connection
if (!this->check())
return "";
// crypt
auto json_len = strlen(json.c_str()) + 1;
uint8_t* json_data = new uint8_t[json_len];
memcpy(json_data, json.c_str(), json_len);
if (this->cipher != nullptr)
this->cipher->crypt(json_data, json_len);
// send
auto send_result = send(this->socket, (const char*) json_data, (int) json_len, 0);
delete[] json_data;
if (send_result == SOCKET_ERROR || send_result < (int) json_len) {
closesocket(this->socket);
this->socket = INVALID_SOCKET;
return "";
}
// receive
uint8_t receive_data[RECEIVE_BUFFER_SIZE];
size_t receive_data_len = 0;
int receive_result;
while ((receive_result = recv(
this->socket,
(char*) &receive_data[receive_data_len],
sizeof(receive_data) - receive_data_len, 0)) > 0) {
// check for buffer overflow
if (receive_data_len + receive_result >= sizeof(receive_data)) {
closesocket(this->socket);
this->socket = INVALID_SOCKET;
return "";
}
// crypt
if (this->cipher != nullptr)
this->cipher->crypt(&receive_data[receive_data_len], (size_t) receive_result);
// increase received data length
receive_data_len += receive_result;
// check for message end
if (receive_data[receive_data_len - 1] == 0)
break;
}
// return resulting json
if (receive_data_len > 0) {
return std::string((const char *) &receive_data[0], receive_data_len - 1);
} else {
// receive error
this->socket = INVALID_SOCKET;
return "";
}
}
@@ -0,0 +1,31 @@
#ifndef SPICEAPI_CONNECTION_H
#define SPICEAPI_CONNECTION_H
#include <string>
#include <winsock2.h>
#include "rc4.h"
namespace spiceapi {
class Connection {
private:
std::string host;
uint16_t port;
std::string password;
SOCKET socket;
RC4* cipher;
void cipher_alloc();
public:
Connection(std::string host, uint16_t port, std::string password = "");
~Connection();
bool check();
void change_pass(std::string password);
std::string request(std::string json);
};
}
#endif //SPICEAPI_CONNECTION_H
@@ -0,0 +1,45 @@
#include "rc4.h"
#include <iterator>
spiceapi::RC4::RC4(uint8_t *key, size_t key_size) {
// initialize S-BOX
for (size_t i = 0; i < std::size(s_box); i++)
s_box[i] = (uint8_t) i;
// check key size
if (!key_size)
return;
// KSA
size_t j = 0;
for (size_t i = 0; i < std::size(s_box); i++) {
// update
j = (j + s_box[i] + key[i % key_size]) % std::size(s_box);
// swap
auto tmp = s_box[i];
s_box[i] = s_box[j];
s_box[j] = tmp;
}
}
void spiceapi::RC4::crypt(uint8_t *data, size_t size) {
// iterate all bytes
for (size_t pos = 0; pos < size; pos++) {
// update
a = (a + 1) % std::size(s_box);
b = (b + s_box[a]) % std::size(s_box);
// swap
auto tmp = s_box[a];
s_box[a] = s_box[b];
s_box[b] = tmp;
// crypt
data[pos] ^= s_box[(s_box[a] + s_box[b]) % std::size(s_box)];
}
}
@@ -0,0 +1,21 @@
#ifndef SPICEAPI_RC4_H
#define SPICEAPI_RC4_H
#include <cstdint>
namespace spiceapi {
class RC4 {
private:
uint8_t s_box[256];
size_t a = 0, b = 0;
public:
RC4(uint8_t *key, size_t key_size);
void crypt(uint8_t *data, size_t size);
};
}
#endif //SPICEAPI_RC4_H
@@ -0,0 +1,638 @@
#include "wrappers.h"
#include <random>
#include <string>
/*
* RapidJSON dependency
* You might need to adjust the paths when importing into your own project.
*/
#include "external/rapidjson/document.h"
#include "external/rapidjson/writer.h"
using namespace rapidjson;
namespace spiceapi {
static inline std::string doc2str(Document &doc) {
StringBuffer sb;
rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
doc.Accept(writer);
return sb.GetString();
}
static inline Document request_gen(const char *module, const char *function) {
// create document
Document doc;
doc.SetObject();
// add attributes
auto &alloc = doc.GetAllocator();
doc.AddMember("id", msg_gen_id(), alloc);
doc.AddMember("module", StringRef(module), alloc);
doc.AddMember("function", StringRef(function), alloc);
// add params
Value noparam(kArrayType);
doc.AddMember("params", noparam, alloc);
// return document
return doc;
}
static inline Document *response_get(std::string json) {
// parse document
Document *doc = new Document();
doc->Parse(json.c_str());
// check for parse error
if (doc->HasParseError()) {
delete doc;
return nullptr;
}
// check id
auto it_id = doc->FindMember("id");
if (it_id == doc->MemberEnd() || !(*it_id).value.IsUint64()) {
delete doc;
return nullptr;
}
// check errors
auto it_errors = doc->FindMember("errors");
if (it_errors == doc->MemberEnd() || !(*it_errors).value.IsArray()) {
delete doc;
return nullptr;
}
// check error count
if ((*it_errors).value.Size() > 0) {
delete doc;
return nullptr;
}
// check data
auto it_data = doc->FindMember("data");
if (it_data == doc->MemberEnd() || !(*it_data).value.IsArray()) {
delete doc;
return nullptr;
}
// return document
return doc;
}
}
uint64_t spiceapi::msg_gen_id() {
static uint64_t id_global = 0;
// check if global ID was initialized
if (id_global == 0) {
// generate a new ID
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<uint64_t> dist(1, (uint64_t) std::llround(std::pow(2, 63)));
id_global = dist(gen);
} else {
// increase by one
id_global++;
}
// return global ID
return id_global;
}
bool spiceapi::analogs_read(spiceapi::Connection &con, std::vector<spiceapi::AnalogState> &states) {
auto req = request_gen("analogs", "read");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray()) {
AnalogState state;
state.name = val[0].GetString();
state.value = val[1].GetFloat();
states.push_back(state);
}
delete res;
return true;
}
bool spiceapi::analogs_write(spiceapi::Connection &con, std::vector<spiceapi::AnalogState> &states) {
auto req = request_gen("analogs", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
state_val.PushBack(state.value, alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::analogs_write_reset(spiceapi::Connection &con, std::vector<spiceapi::AnalogState> &states) {
auto req = request_gen("analogs", "write_reset");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::buttons_read(spiceapi::Connection &con, std::vector<spiceapi::ButtonState> &states) {
auto req = request_gen("buttons", "read");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray()) {
ButtonState state;
state.name = val[0].GetString();
state.value = val[1].GetFloat();
states.push_back(state);
}
delete res;
return true;
}
bool spiceapi::buttons_write(spiceapi::Connection &con, std::vector<spiceapi::ButtonState> &states) {
auto req = request_gen("buttons", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
state_val.PushBack(state.value, alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::buttons_write_reset(spiceapi::Connection &con, std::vector<spiceapi::ButtonState> &states) {
auto req = request_gen("buttons", "write_reset");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::card_insert(spiceapi::Connection &con, size_t index, const char *card_id) {
auto req = request_gen("card", "insert");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(index, alloc);
params.PushBack(StringRef(card_id), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::coin_get(Connection &con, int &coins) {
auto req = request_gen("coin", "get");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
coins = (*res)["data"][0].GetInt();
delete res;
return true;
}
bool spiceapi::coin_set(Connection &con, int coins) {
auto req = request_gen("coin", "set");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(coins, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::coin_insert(Connection &con, int coins) {
auto req = request_gen("coin", "insert");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(coins, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::coin_blocker_get(Connection &con, bool &closed) {
auto req = request_gen("coin", "blocker_get");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
closed = (*res)["data"][0].GetBool();
delete res;
return true;
}
bool spiceapi::control_raise(spiceapi::Connection &con, const char *signal) {
auto req = request_gen("control", "raise");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(signal), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::control_exit(spiceapi::Connection &con) {
auto req = request_gen("control", "exit");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::control_exit(spiceapi::Connection &con, int exit_code) {
auto req = request_gen("control", "exit");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(exit_code, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::control_restart(spiceapi::Connection &con) {
auto req = request_gen("control", "restart");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::control_session_refresh(spiceapi::Connection &con) {
auto req = request_gen("control", "session_refresh");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto key = (*res)["data"][0].GetString();
con.change_pass(key);
delete res;
return true;
}
bool spiceapi::control_shutdown(spiceapi::Connection &con) {
auto req = request_gen("control", "shutdown");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::control_reboot(spiceapi::Connection &con) {
auto req = request_gen("control", "reboot");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::iidx_ticker_get(spiceapi::Connection &con, char *ticker) {
auto req = request_gen("iidx", "ticker_get");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto data = (*res)["data"][0].GetString();
strncpy(ticker, data, 9);
ticker[9] = 0x00;
delete res;
return true;
}
bool spiceapi::iidx_ticker_set(spiceapi::Connection &con, const char *ticker) {
auto req = request_gen("iidx", "ticker_set");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(ticker), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::iidx_ticker_reset(spiceapi::Connection &con) {
auto req = request_gen("iidx", "ticker_reset");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::info_avs(spiceapi::Connection &con, spiceapi::InfoAvs &info) {
auto req = request_gen("info", "avs");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"][0];
info.model = data["model"].GetString();
info.dest = data["dest"].GetString();
info.spec = data["spec"].GetString();
info.rev = data["rev"].GetString();
info.ext = data["ext"].GetString();
delete res;
return true;
}
bool spiceapi::info_launcher(spiceapi::Connection &con, spiceapi::InfoLauncher &info) {
auto req = request_gen("info", "launcher");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"][0];
info.version = data["version"].GetString();
info.compile_date = data["compile_date"].GetString();
info.compile_time = data["compile_time"].GetString();
info.system_time = data["system_time"].GetString();
for (auto &arg : data["args"].GetArray())
info.args.push_back(arg.GetString());
delete res;
return true;
}
bool spiceapi::info_memory(spiceapi::Connection &con, spiceapi::InfoMemory &info) {
auto req = request_gen("info", "memory");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"][0];
info.mem_total = data["mem_total"].GetUint64();
info.mem_total_used = data["mem_total_used"].GetUint64();
info.mem_used = data["mem_used"].GetUint64();
info.vmem_total = data["vmem_total"].GetUint64();
info.vmem_total_used = data["vmem_total_used"].GetUint64();
info.vmem_used = data["vmem_used"].GetUint64();
delete res;
return true;
}
bool spiceapi::keypads_write(spiceapi::Connection &con, unsigned int keypad, const char *input) {
auto req = request_gen("keypads", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(keypad, alloc);
params.PushBack(StringRef(input), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::keypads_set(spiceapi::Connection &con, unsigned int keypad, std::vector<char> &keys) {
auto req = request_gen("keypads", "set");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(keypad, alloc);
for (auto &key : keys)
params.PushBack(StringRef(&key, 1), alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::keypads_get(spiceapi::Connection &con, unsigned int keypad, std::vector<char> &keys) {
auto req = request_gen("keypads", "get");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(keypad, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray())
keys.push_back(val.GetString()[0]);
delete res;
return true;
}
bool spiceapi::lights_read(spiceapi::Connection &con, std::vector<spiceapi::LightState> &states) {
auto req = request_gen("lights", "read");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray()) {
LightState state;
state.name = val[0].GetString();
state.value = val[1].GetFloat();
states.push_back(state);
}
delete res;
return true;
}
bool spiceapi::lights_write(spiceapi::Connection &con, std::vector<spiceapi::LightState> &states) {
auto req = request_gen("lights", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
state_val.PushBack(state.value, alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::lights_write_reset(spiceapi::Connection &con, std::vector<spiceapi::LightState> &states) {
auto req = request_gen("lights", "write_reset");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(StringRef(state.name.c_str()), alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::memory_write(spiceapi::Connection &con, const char *dll_name, const char *hex, uint32_t offset) {
auto req = request_gen("memory", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(dll_name), alloc);
params.PushBack(StringRef(hex), alloc);
params.PushBack(offset, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::memory_read(spiceapi::Connection &con, const char *dll_name, uint32_t offset, uint32_t size,
std::string &hex) {
auto req = request_gen("memory", "read");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(dll_name), alloc);
params.PushBack(offset, alloc);
params.PushBack(size, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
hex = (*res)["data"][0].GetString();
delete res;
return true;
}
bool spiceapi::memory_signature(spiceapi::Connection &con, const char *dll_name, const char *signature,
const char *replacement, uint32_t offset, uint32_t usage, uint32_t &file_offset) {
auto req = request_gen("memory", "signature");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
params.PushBack(StringRef(dll_name), alloc);
params.PushBack(StringRef(signature), alloc);
params.PushBack(StringRef(replacement), alloc);
params.PushBack(offset, alloc);
params.PushBack(usage, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
file_offset = (*res)["data"][0].GetUint();
delete res;
return true;
}
bool spiceapi::touch_read(spiceapi::Connection &con, std::vector<spiceapi::TouchState> &states) {
auto req = request_gen("touch", "read");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"];
for (auto &val : data.GetArray()) {
TouchState state;
state.id = val[0].GetUint64();
state.x = val[1].GetInt64();
state.y = val[2].GetInt64();
states.push_back(state);
}
delete res;
return true;
}
bool spiceapi::touch_write(spiceapi::Connection &con, std::vector<spiceapi::TouchState> &states) {
auto req = request_gen("touch", "write");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states) {
Value state_val(kArrayType);
state_val.PushBack(state.id, alloc);
state_val.PushBack(state.x, alloc);
state_val.PushBack(state.y, alloc);
params.PushBack(state_val, alloc);
}
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::touch_write_reset(spiceapi::Connection &con, std::vector<spiceapi::TouchState> &states) {
auto req = request_gen("touch", "write_reset");
auto &alloc = req.GetAllocator();
Value params(kArrayType);
for (auto &state : states)
params.PushBack(state.id, alloc);
req["params"] = params;
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
delete res;
return true;
}
bool spiceapi::lcd_info(spiceapi::Connection &con, spiceapi::LCDInfo &info) {
auto req = request_gen("lcd", "info");
auto res = response_get(con.request(doc2str(req)));
if (!res)
return false;
auto &data = (*res)["data"][0];
info.enabled = data["enabled"].GetBool();
info.csm = data["csm"].GetString();
info.bri = data["bri"].GetInt();
info.con = data["con"].GetInt();
info.bl = data["bl"].GetInt();
info.red = data["red"].GetInt();
info.green = data["green"].GetInt();
info.blue = data["blue"].GetInt();
delete res;
return true;
}
@@ -0,0 +1,104 @@
#ifndef SPICEAPI_WRAPPERS_H
#define SPICEAPI_WRAPPERS_H
#include <vector>
#include <string>
#include "connection.h"
namespace spiceapi {
struct AnalogState {
std::string name;
float value;
};
struct ButtonState {
std::string name;
float value;
};
struct LightState {
std::string name;
float value;
};
struct InfoAvs {
std::string model, dest, spec, rev, ext;
};
struct InfoLauncher {
std::string version;
std::string compile_date, compile_time, system_time;
std::vector<std::string> args;
};
struct InfoMemory {
uint64_t mem_total, mem_total_used, mem_used;
uint64_t vmem_total, vmem_total_used, vmem_used;
};
struct TouchState {
uint64_t id;
int64_t x, y;
};
struct LCDInfo {
bool enabled;
std::string csm;
uint8_t bri, con, bl, red, green, blue;
};
uint64_t msg_gen_id();
bool analogs_read(Connection &con, std::vector<AnalogState> &states);
bool analogs_write(Connection &con, std::vector<AnalogState> &states);
bool analogs_write_reset(Connection &con, std::vector<AnalogState> &states);
bool buttons_read(Connection &con, std::vector<ButtonState> &states);
bool buttons_write(Connection &con, std::vector<ButtonState> &states);
bool buttons_write_reset(Connection &con, std::vector<ButtonState> &states);
bool card_insert(Connection &con, size_t index, const char *card_id);
bool coin_get(Connection &con, int &coins);
bool coin_set(Connection &con, int coins);
bool coin_insert(Connection &con, int coins=1);
bool coin_blocker_get(Connection &con, bool &closed);
bool control_raise(Connection &con, const char *signal);
bool control_exit(Connection &con);
bool control_exit(Connection &con, int exit_code);
bool control_restart(Connection &con);
bool control_session_refresh(Connection &con);
bool control_shutdown(Connection &con);
bool control_reboot(Connection &con);
bool iidx_ticker_get(Connection &con, char *ticker);
bool iidx_ticker_set(Connection &con, const char *ticker);
bool iidx_ticker_reset(Connection &con);
bool info_avs(Connection &con, InfoAvs &info);
bool info_launcher(Connection &con, InfoLauncher &info);
bool info_memory(Connection &con, InfoMemory &info);
bool keypads_write(Connection &con, unsigned int keypad, const char *input);
bool keypads_set(Connection &con, unsigned int keypad, std::vector<char> &keys);
bool keypads_get(Connection &con, unsigned int keypad, std::vector<char> &keys);
bool lights_read(Connection &con, std::vector<LightState> &states);
bool lights_write(Connection &con, std::vector<LightState> &states);
bool lights_write_reset(Connection &con, std::vector<LightState> &states);
bool memory_write(Connection &con, const char *dll_name, const char *hex, uint32_t offset);
bool memory_read(Connection &con, const char *dll_name, uint32_t offset, uint32_t size, std::string &hex);
bool memory_signature(Connection &con, const char *dll_name, const char *signature, const char *replacement,
uint32_t offset, uint32_t usage, uint32_t &file_offset);
bool touch_read(Connection &con, std::vector<TouchState> &states);
bool touch_write(Connection &con, std::vector<TouchState> &states);
bool touch_write_reset(Connection &con, std::vector<TouchState> &states);
bool lcd_info(Connection &con, LCDInfo &info);
}
#endif //SPICEAPI_WRAPPERS_H
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
@@ -0,0 +1,22 @@
library spiceapi;
import 'dart:convert';
import 'dart:async';
import 'dart:math';
import 'dart:html';
import 'dart:typed_data';
part "src/connection.dart";
part "src/request.dart";
part "src/response.dart";
part "src/exceptions.dart";
part "src/rc4.dart";
part "src/wrappers/analogs.dart";
part "src/wrappers/buttons.dart";
part "src/wrappers/card.dart";
part "src/wrappers/coin.dart";
part "src/wrappers/control.dart";
part "src/wrappers/info.dart";
part "src/wrappers/keypads.dart";
part "src/wrappers/lights.dart";
part "src/wrappers/memory.dart";
part "src/wrappers/iidx.dart";
part "src/wrappers/touch.dart";
@@ -0,0 +1,193 @@
part of spiceapi;
class Connection {
// settings
static const _TIMEOUT = Duration(seconds: 2);
static const _BUFFER_SIZE = 1024 * 8;
// state
final String host, pass;
final int port;
var resource;
List<int> _dataBuffer;
StreamController<Response> _responses;
StreamController<Connection> _connections;
WebSocket _socket;
RC4 _cipher;
bool _disposed = false;
Connection(this.host, this.port, this.pass,
{this.resource, bool refreshSession=true}) {
// initialize
_dataBuffer = List<int>();
_responses = StreamController<Response>.broadcast();
_connections = StreamController<Connection>.broadcast();
if (pass.length > 0)
_cipher = RC4(utf8.encode(pass));
// initialize socket
this._socket = WebSocket("ws://$host:${port + 1}");
this._socket.binaryType = "arraybuffer";
// listen to events
this._socket.onOpen.listen((e) async {
// refresh session
bool error = false;
if (refreshSession) {
try {
await controlRefreshSession(this);
} on Error {
error = true;
} on TimeoutException {
error = true;
}
}
// mark as connected
if (!this._connections.isClosed)
this._connections.add(this);
if (error)
this.dispose();
});
this._socket.onMessage.listen((e) {
// get data
var data = e.data;
if (data is ByteBuffer)
data = data.asUint8List();
// check type
if (data is List<int>) {
// cipher
if (_cipher != null)
_cipher.crypt(data);
// add data to buffer
_dataBuffer.addAll(data);
// check buffer size
if (_dataBuffer.length > _BUFFER_SIZE) {
this.dispose();
return;
}
// check for completed message
for (int i = 0; i < _dataBuffer.length; i++) {
if (_dataBuffer[i] == 0) {
// get message data and remove from buffer
var msgData = List<int>.from(_dataBuffer.getRange(0, i));
_dataBuffer.removeRange(0, i + 1);
// check data length
if (msgData.length > 0) {
// convert to JSON
var msgStr = utf8.decode(msgData, allowMalformed: false);
// build response
var res = Response.fromJson(msgStr);
this._responses.add(res);
}
}
}
}
});
this._socket.onClose.listen((e) {
this.dispose();
});
this._socket.onError.listen((e) {
this.dispose();
});
}
void changePass(String pass) {
if (pass.length > 0)
_cipher = RC4(utf8.encode(pass));
else
_cipher = null;
}
void dispose() {
if (_socket != null)
_socket.close();
_socket = null;
if (_responses != null)
_responses.close();
if (_connections != null)
_connections.close();
this._disposed = true;
this.free();
}
bool isDisposed() {
return this._disposed;
}
void free() {
// release optional resource
if (this.resource != null) {
this.resource.release();
this.resource = null;
}
}
bool isFree() {
return this.resource == null;
}
Future<Connection> onConnect() {
return _connections.stream.first;
}
bool isValid() {
return this._socket != null && !this._disposed;
}
Future<Response> request(Request req) {
// add response listener
var res = _awaitResponse(req._id);
// write request
_writeRequest(req);
// return future response
return res.then((res) {
// validate first
res.validate();
// return it
return res;
});
}
void _writeRequest(Request req) async {
// convert to JSON
var json = req.toJson() + "\x00";
var jsonEncoded = utf8.encode(json);
// cipher
if (_cipher != null)
_cipher.crypt(jsonEncoded);
// write to socket
this._socket.sendByteBuffer(Int8List.fromList(jsonEncoded).buffer);
}
Future<Response> _awaitResponse(int id) {
return _responses.stream.timeout(_TIMEOUT).firstWhere(
(res) => res._id == id, orElse: null);
}
}
@@ -0,0 +1,11 @@
part of spiceapi;
class APIError implements Exception {
String cause;
APIError(this.cause);
@override
String toString() {
return this.cause;
}
}
@@ -0,0 +1,48 @@
part of spiceapi;
class RC4 {
// state
int _a = 0;
int _b = 0;
List<int> _sBox = List<int>(256);
RC4(List<int> key) {
// init sBox
for (int i = 0; i < 256; i++) {
_sBox[i] = i;
}
// process key
int j = 0;
for (int i = 0; i < 256; i++) {
// update
j = (j + _sBox[i] + key[i % key.length]) % 256;
// swap
var tmp = _sBox[i];
_sBox[i] = _sBox[j];
_sBox[j] = tmp;
}
}
void crypt(List<int> inData) {
for (int i = 0; i < inData.length; i++) {
// update
_a = (_a + 1) % 256;
_b = (_b + _sBox[_a]) % 256;
// swap
var tmp = _sBox[_a];
_sBox[_a] = _sBox[_b];
_sBox[_b] = tmp;
// crypt
inData[i] ^= _sBox[(_sBox[_a] + _sBox[_b]) % 256];
}
}
}
@@ -0,0 +1,45 @@
part of spiceapi;
class Request {
static int _lastID = 0;
// contents
int _id;
String _module;
String _function;
List _params;
Request(String module, String function, {id}) {
// automatic ID iteration
if (id == null) {
if (++_lastID >= pow(2, 32))
_lastID = 1;
id = _lastID;
} else
_lastID = id;
// build contents
this._id = id;
this._module = module;
this._function = function;
this._params = List();
}
String toJson() {
return jsonEncode(
{
"id": this._id,
"module": this._module,
"function": this._function,
"params": this._params,
}
);
}
void addParam(param) {
this._params.add(param);
}
}
@@ -0,0 +1,35 @@
part of spiceapi;
class Response {
String _json;
int _id;
List _errors;
List _data;
Response.fromJson(String json) {
this._json = json;
var obj = jsonDecode(json);
this._id = obj["id"];
this._errors = obj["errors"];
this._data = obj["data"];
}
void validate() {
// check for errors
if (_errors.length > 0) {
// TODO: add all errors
throw APIError(_errors[0].toString());
}
}
List getData() {
return _data;
}
String toJson() {
return _json;
}
}
@@ -0,0 +1,54 @@
part of spiceapi;
class AnalogState {
String name;
double state;
bool active;
AnalogState(this.name, this.state);
AnalogState._fromRead(this.name, this.state, this.active);
}
Future<List<AnalogState>> analogsRead(Connection con) {
var req = Request("analogs", "read");
return con.request(req).then((res) {
// build states list
List<AnalogState> states = [];
for (List state in res.getData()) {
states.add(AnalogState._fromRead(
state[0],
state[1],
state[2],
));
}
// return it
return states;
});
}
Future<void> analogsWrite(Connection con, List<AnalogState> states) {
var req = Request("analogs", "write");
// add params
for (var state in states) {
var obj = [
state.name,
state.state
];
req.addParam(obj);
}
return con.request(req);
}
Future<void> analogsWriteReset(Connection con, List<String> names) {
var req = Request("analogs", "write_reset");
// add params
for (var name in names)
req.addParam(name);
return con.request(req);
}
@@ -0,0 +1,54 @@
part of spiceapi;
class ButtonState {
String name;
double state;
bool active;
ButtonState(this.name, this.state);
ButtonState._fromRead(this.name, this.state, this.active);
}
Future<List<ButtonState>> buttonsRead(Connection con) {
var req = Request("buttons", "read");
return con.request(req).then((res) {
// build states list
List<ButtonState> states = [];
for (List state in res.getData()) {
states.add(ButtonState._fromRead(
state[0],
state[1],
state[2],
));
}
// return it
return states;
});
}
Future<void> buttonsWrite(Connection con, List<ButtonState> states) {
var req = Request("buttons", "write");
// add params
for (var state in states) {
var obj = [
state.name,
state.state
];
req.addParam(obj);
}
return con.request(req);
}
Future<void> buttonsWriteReset(Connection con, List<String> names) {
var req = Request("buttons", "write_reset");
// add params
for (var name in names)
req.addParam(name);
return con.request(req);
}
@@ -0,0 +1,8 @@
part of spiceapi;
Future<void> cardInsert(Connection con, int unit, String cardID) {
var req = Request("card", "insert");
req.addParam(unit);
req.addParam(cardID);
return con.request(req);
}
@@ -0,0 +1,21 @@
part of spiceapi;
Future<int> coinGet(Connection con) {
var req = Request("coin", "get");
return con.request(req).then((res) {
return res.getData()[0];
});
}
Future<void> coinSet(Connection con, int amount) {
var req = Request("coin", "set");
req.addParam(amount);
return con.request(req);
}
Future<void> coinInsert(Connection con, [int amount=1]) {
var req = Request("coin", "insert");
if (amount != 1)
req.addParam(amount);
return con.request(req);
}
@@ -0,0 +1,36 @@
part of spiceapi;
Future<void> controlRaise(Connection con, String signal) {
var req = Request("control", "raise");
req.addParam(signal);
return con.request(req);
}
Future<void> controlExit(Connection con, int code) {
var req = Request("control", "exit");
req.addParam(code);
return con.request(req);
}
Future<void> controlRestart(Connection con) {
var req = Request("control", "restart");
return con.request(req);
}
Future<void> controlRefreshSession(Connection con) {
var rnd = new Random();
var req = Request("control", "session_refresh", id: rnd.nextInt(pow(2, 32)));
return con.request(req).then((res) {
con.changePass(res.getData()[0]);
});
}
Future<void> controlShutdown(Connection con) {
var req = Request("control", "shutdown");
return con.request(req);
}
Future<void> controlReboot(Connection con) {
var req = Request("control", "reboot");
return con.request(req);
}
@@ -0,0 +1,19 @@
part of spiceapi;
Future<String> iidxTickerGet(Connection con) {
var req = Request("iidx", "ticker_get");
return con.request(req).then((res) {
return res.getData()[0];
});
}
Future<void> iidxTickerSet(Connection con, String text) {
var req = Request("iidx", "ticker_set");
req.addParam(text);
return con.request(req);
}
Future<void> iidxTickerReset(Connection con) {
var req = Request("iidx", "ticker_reset");
return con.request(req);
}
@@ -0,0 +1,22 @@
part of spiceapi;
Future<Map> infoAVS(Connection con) {
var req = Request("info", "avs");
return con.request(req).then((res) {
return res.getData()[0];
});
}
Future<Map> infoLauncher(Connection con) {
var req = Request("info", "launcher");
return con.request(req).then((res) {
return res.getData()[0];
});
}
Future<Map> infoMemory(Connection con) {
var req = Request("info", "memory");
return con.request(req).then((res) {
return res.getData()[0];
});
}
@@ -0,0 +1,28 @@
part of spiceapi;
Future<void> keypadsWrite(Connection con, int unit, String input) {
var req = Request("keypads", "write");
req.addParam(unit);
req.addParam(input);
return con.request(req);
}
Future<void> keypadsSet(Connection con, int unit, String buttons) {
var req = Request("keypads", "set");
req.addParam(unit);
for (int i = 0; i < buttons.length; i++)
req.addParam(buttons[i]);
return con.request(req);
}
Future<String> keypadsGet(Connection con, int unit) {
var req = Request("keypads", "get");
req.addParam(unit);
return con.request(req).then((res) {
String buttons = "";
for (var obj in res.getData()) {
buttons += obj;
}
return buttons;
});
}
@@ -0,0 +1,54 @@
part of spiceapi;
class LightState {
String name;
double state;
bool active;
LightState(this.name, this.state);
LightState._fromRead(this.name, this.state, this.active);
}
Future<List<LightState>> lightsRead(Connection con) {
var req = Request("lights", "read");
return con.request(req).then((res) {
// build states list
List<LightState> states = [];
for (List state in res.getData()) {
states.add(LightState._fromRead(
state[0],
state[1],
state[2],
));
}
// return it
return states;
});
}
Future<void> lightsWrite(Connection con, List<LightState> states) {
var req = Request("lights", "write");
// add params
for (var state in states) {
var obj = [
state.name,
state.state
];
req.addParam(obj);
}
return con.request(req);
}
Future<void> lightsWriteReset(Connection con, List<String> names) {
var req = Request("lights", "write_reset");
// add params
for (var name in names)
req.addParam(name);
return con.request(req);
}

Some files were not shown because too many files have changed in this diff Show More