mirror of
https://github.com/djhackersdev/legacy-tools.git
synced 2026-10-06 12:37:56 +03:00
Add more legacy tools that I found while cleaning up my server
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
Anyone here speaking (near-)fluent English and Japanese?
|
||||
I could use some help correcting the Google Translate derps.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
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()
|
||||
@@ -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
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
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;
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -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);
|
||||
}
|
||||
+54
@@ -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);
|
||||
}
|
||||
+21
@@ -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);
|
||||
}
|
||||
+36
@@ -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);
|
||||
}
|
||||
+19
@@ -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);
|
||||
}
|
||||
+22
@@ -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];
|
||||
});
|
||||
}
|
||||
+28
@@ -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;
|
||||
});
|
||||
}
|
||||
+54
@@ -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
Reference in New Issue
Block a user