Initial re-upload of spice2x-24-08-24

This commit is contained in:
2024-08-28 11:10:34 -04:00
commit caa9e02285
1181 changed files with 380065 additions and 0 deletions

18
cfg/Win32D.rc Normal file
View File

@@ -0,0 +1,18 @@
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
#define APSTUDIO_HIDDEN_SYMBOLS
#include "windows.h"
#undef APSTUDIO_HIDDEN_SYMBOLS
#undef APSTUDIO_READONLY_SYMBOLS
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
IDR_CHANGELOG RCDATA "../changelog.txt"
IDR_LICENSES RCDATA "../licenses.txt"
IDR_PATCHES RCDATA "../build/patches.json"
IDR_README RCDATA "../readme.txt"
IDR_DSEGFONT RCDATA "../external/dseg/DSEG14Classic-Italic.ttf"
#endif

255
cfg/analog.cpp Normal file
View File

@@ -0,0 +1,255 @@
#include "analog.h"
#include <numeric>
#include <math.h>
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/time.h"
#include "util/utils.h"
std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
// device must be existing
if (this->device_identifier.empty()) {
return "";
}
// get index string
auto index = this->getIndex();
std::string indexString = fmt::format("{:#x}", index);
// get device
auto device = manager->devices_get(this->device_identifier);
if (!device) {
return "Device missing (" + indexString + ")";
}
// return string based on device type
switch (device->type) {
case rawinput::MOUSE: {
const char *name;
switch (index) {
case rawinput::MOUSEPOS_X:
name = "X";
break;
case rawinput::MOUSEPOS_Y:
name = "Y";
break;
case rawinput::MOUSEPOS_WHEEL:
name = "Scroll Wheel";
break;
default:
name = "?";
break;
}
return fmt::format("{} ({})", name, device->desc);
}
case rawinput::HID: {
auto hid = device->hidInfo;
if (index < hid->value_caps_names.size()) {
return hid->value_caps_names[index] + " (" + device->desc + ")";
}
return "Invalid Axis (" + indexString + ")";
}
case rawinput::MIDI: {
auto midi = device->midiInfo;
if (index < midi->controls_precision.size()) {
return "MIDI PREC " + indexString + " (" + device->desc + ")";
} else if (index < midi->controls_precision.size() + midi->controls_single.size()) {
return "MIDI CTRL " + indexString + " (" + device->desc + ")";
} else if (index < midi->controls_precision.size() + midi->controls_single.size()
+ midi->controls_onoff.size())
{
return "MIDI ONOFF " + indexString + " (" + device->desc + ")";
} else if (index == midi->controls_precision.size() + midi->controls_single.size()
+ midi->controls_onoff.size())
{
return "MIDI Pitch Bend (" + device->desc + ")";
} else {
return "MIDI Unknown " + indexString + " (" + device->desc + ")";
}
}
case rawinput::DESTROYED:
return "Device unplugged (" + indexString + ")";
default:
return "Unknown Axis (" + indexString + ")";
}
}
float Analog::getSmoothedValue(float raw_rads) {
auto now = get_performance_milliseconds();
// prevent extremely frequent polling
if ((now - vector_history.at(vector_history_index).time_in_ms) < 0.9) {
return smoothed_last_state;
}
// calculate derived values for the newly-read analog value
vector_history_index = (vector_history_index + 1) % vector_history.size();
auto &current = vector_history.at(vector_history_index);
current.time_in_ms = now;
current.sine = sin(raw_rads);
current.cosine = cos(raw_rads);
// calculated the weighted sum of sines and cosines
auto sines = 0.f;
auto cosines = 0.f;
for (auto &vector : vector_history) {
auto time_diff = now - vector.time_in_ms;
// time from QPC should never roll backwards, but just in case
if (time_diff < 0.f) {
time_diff = 0.f;
}
// the weight falls of linearly; value from 24ms ago counts as half, 48ms ago counts as 0
double weight = (-time_diff / 48.f) + 1.f;
if (weight > 0.f) {
sines += weight * vector.sine;
cosines += weight * vector.cosine;
}
}
// add a tiny bit so that cosine is never 0.0f when fed to atan2
if (cosines == 0.f) {
cosines = std::nextafter(0.f, 1.f);
}
// average for angles:
// arctan[(sum of sines of all angles) / (sum of cosines of all angles)]
// atan2 will give [-pi, +pi], so normalize to make [0, 2pi]
smoothed_last_state = normalizeAngle(atan2(sines, cosines));
return smoothed_last_state;
}
float Analog::calculateAngularDifference(float old_rads, float new_rads) {
float delta = new_rads - old_rads;
// assumes value doesn't change more than PI (180 deg) compared to last poll
if (std::abs(delta) < M_PI) {
return delta;
} else {
// use the coterminal angle instead
if (delta < 0.f) {
return M_TAU + delta;
} else {
return -(M_TAU - delta);
}
}
}
float Analog::applyAngularSensitivity(float raw_rads) {
float delta = calculateAngularDifference(previous_raw_rads, raw_rads);
previous_raw_rads = raw_rads;
adjusted_rads = normalizeAngle(adjusted_rads + (delta * sensitivity));
return adjusted_rads;
}
float Analog::normalizeAngle(float rads) {
// normalizes radian value into [0, 2pi] range.
// for small angles, this is MUCH faster than fmodf.
float angle = rads;
while (angle > M_TAU) {
angle -= M_TAU;
}
while (angle < 0.f) {
angle += M_TAU;
}
return angle;
}
float Analog::applyMultiplier(float value) {
if (1 < this->multiplier) {
// multiplier - just multiply the value and take the decimal part
return normalizeAnalogValue(value * this->multiplier);
} else if (this->multiplier < -1) {
const unsigned short number_of_divisions = -this->multiplier;
// divisor - need to take care of over/underflow
if (0.75f < this->divisor_previous_value && value < 0.25f) {
this->divisor_region = (this->divisor_region + 1) % number_of_divisions;
} else if (this->divisor_previous_value < 0.25f && 0.75f < value) {
if (1 <= this->divisor_region) {
this->divisor_region -= 1;
} else {
this->divisor_region = number_of_divisions - 1;
}
}
this->divisor_previous_value = value;
return ((float)this->divisor_region + value) / (float)number_of_divisions;
} else {
// multiplier in [-1, 1] range is just treated as 1
return value;
}
}
float Analog::normalizeAnalogValue(float value) {
// effectively the same as fmodf(value, 1.f)
// for small values, this is MUCH faster than fmodf.
float new_value = value;
while (new_value > 1.f) {
new_value -= 1.f;
}
while (new_value < 0.f) {
new_value += 1.f;
}
return new_value;
}
float Analog::applyDeadzone(float raw_value) {
float value = raw_value;
const auto deadzone = this->getDeadzone();
if (deadzone > 0) {
// calculate values
const auto delta = value - 0.5f;
const auto dtlen = 1.f - deadzone;
// check mirror
if (this->getDeadzoneMirror()) {
// deadzone on the edges
if (dtlen != 0.f) {
value = std::max(0.f, std::min(1.f, 0.5f + (delta / dtlen)));
} else {
value = 0.5f;
}
} else {
// deadzone around the middle
const auto limit = deadzone * 0.5f;
if (dtlen != 0.f) {
if (delta > limit) {
value = std::min(1.f, 0.5f + std::max(0.f, (delta - limit) / dtlen));
} else if (delta < -limit) {
value = std::max(0.f, 0.5f + std::min(0.f, (delta + limit) / dtlen));
} else {
value = 0.5f;
}
} else {
value = 0.5f;
}
}
} else if (deadzone < 0) {
// invert for mirror
if (this->getDeadzoneMirror()) {
value = 1.f - value;
}
// deadzone from minimum value
if (deadzone > -1 && value > -deadzone) {
value = std::min(1.f, (value + deadzone) / (1.f + deadzone));
} else {
value = 0.f;
}
// revert value for mirror
if (this->getDeadzoneMirror()) {
value = 1.f - value;
}
}
return value;
}

211
cfg/analog.h Normal file
View File

@@ -0,0 +1,211 @@
#pragma once
#include <array>
#include <string>
#include <cmath>
#include <queue>
#define ANALOG_HISTORY_CNT 10
#define M_TAU (2 * M_PI)
#define M_1_TAU (0.5 * M_1_PI)
namespace rawinput {
class RawInputManager;
}
struct AnalogMovingAverage {
double time_in_ms;
float sine;
float cosine;
};
class Analog {
private:
std::string name;
std::string device_identifier = "";
unsigned short index = 0xFF;
float sensitivity = 1.f;
float deadzone = 0.f;
bool deadzone_mirror = false;
bool invert = false;
float last_state = 0.5f;
bool sensitivity_set = false;
bool deadzone_set = false;
// smoothing function
bool smoothing = false;
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history;
int vector_history_index = 0;
float smoothed_last_state = 0.f;
// angular scaling for sensitivity
float previous_raw_rads = 0.f;
float adjusted_rads = 0.f;
// multiplier/divisor
int multiplier = 1;
float divisor_previous_value = 0.5f;
unsigned short divisor_region = 0;
// relative input mode
float absolute_value_for_rel_mode = 0.5f;
bool relative_mode = false;
// circular buffer (delayed input)
int delay_buffer_depth = 0;
std::queue<float> delay_buffer;
float calculateAngularDifference(float old_rads, float new_rads);
float normalizeAngle(float rads);
float normalizeAnalogValue(float value);
public:
// overrides
bool override_enabled = false;
float override_state = 0.5f;
explicit Analog(std::string name) : name(std::move(name)) {
vector_history.fill({0.0, 0.f, 0.f});
};
std::string getDisplayString(rawinput::RawInputManager* manager);
float getSmoothedValue(float raw_rads);
float applyAngularSensitivity(float raw_rads);
float applyMultiplier(float raw_value);
float applyDeadzone(float raw_value);
inline bool isSet() {
if (this->override_enabled) {
return true;
}
return this->index != 0xFF;
}
inline void clearBindings() {
device_identifier = "";
index = 0xFF;
setSensitivity(1.f);
setDeadzone(0.f);
invert = false;
smoothing = false;
setMultiplier(1);
setRelativeMode(false);
setDelayBufferDepth(0);
}
inline const std::string &getName() const {
return this->name;
}
inline const std::string &getDeviceIdentifier() const {
return this->device_identifier;
}
inline void setDeviceIdentifier(std::string device_identifier) {
this->device_identifier = std::move(device_identifier);
}
inline unsigned short getIndex() const {
return this->index;
}
inline void setIndex(unsigned short index) {
this->index = index;
}
inline float getSensitivity() const {
return this->sensitivity;
}
inline float getDeadzone() const {
return this->deadzone;
}
inline void setSensitivity(float sensitivity) {
this->sensitivity = sensitivity;
this->sensitivity_set = (sensitivity < 0.99f || 1.01f < sensitivity);
}
inline void setDeadzone(float deadzone) {
this->deadzone = std::max(-1.f, std::min(1.f, deadzone));
this->deadzone_set = fabsf(deadzone) > 0.01f;
}
inline bool isSensitivitySet() {
return this->sensitivity_set;
}
inline bool isDeadzoneSet() {
return this->deadzone_set;
}
inline bool getDeadzoneMirror() const {
return this->deadzone_mirror;
}
inline void setDeadzoneMirror(bool deadzone_mirror) {
this->deadzone_mirror = deadzone_mirror;
}
inline bool getInvert() const {
return this->invert;
}
inline void setInvert(bool invert) {
this->invert = invert;
}
inline bool getSmoothing() const {
return this->smoothing;
}
inline void setSmoothing(bool smoothing) {
this->smoothing = smoothing;
}
inline int getMultiplier() const {
return this->multiplier;
}
inline void setMultiplier(int multiplier) {
this->multiplier = multiplier;
this->divisor_region = 0;
this->divisor_previous_value = 0.5f;
}
inline float getLastState() const {
return this->last_state;
}
inline void setLastState(float last_state) {
this->last_state = last_state;
}
inline bool isRelativeMode() const {
return this->relative_mode;
}
inline void setRelativeMode(bool relative_mode) {
this->relative_mode = relative_mode;
this->absolute_value_for_rel_mode = 0.5f;
}
inline float getAbsoluteValue(float relative_delta) {
this->absolute_value_for_rel_mode =
normalizeAnalogValue(this->absolute_value_for_rel_mode + relative_delta);
return this->absolute_value_for_rel_mode;
}
inline int getDelayBufferDepth() const {
return this->delay_buffer_depth;
}
inline void setDelayBufferDepth(int depth) {
this->delay_buffer_depth = depth;
}
inline std::queue<float> &getDelayBuffer() {
return this->delay_buffer;
}
};

926
cfg/api.cpp Normal file
View File

@@ -0,0 +1,926 @@
#include "api.h"
#include "rawinput/rawinput.h"
#include "rawinput/piuio.h"
#include "util/time.h"
#include "util/utils.h"
#include "config.h"
using namespace GameAPI;
std::vector<Button> GameAPI::Buttons::getButtons(const std::string &game_name) {
return Config::getInstance().getButtons(game_name);
}
std::vector<Button> GameAPI::Buttons::getButtons(Game *game) {
return Config::getInstance().getButtons(game);
}
std::vector<Button> GameAPI::Buttons::sortButtons(
const std::vector<Button> &buttons,
const std::vector<std::string> &button_names,
const std::vector<unsigned short> *vkey_defaults)
{
std::vector<Button> sorted;
bool button_found;
int index = 0;
for (auto &name : button_names) {
button_found = false;
for (auto &bt : buttons) {
if (name == bt.getName()) {
button_found = true;
sorted.push_back(bt);
break;
}
}
if (!button_found) {
auto &button = sorted.emplace_back(name);
if (vkey_defaults) {
button.setVKey(vkey_defaults->at(index));
}
}
++index;
}
return sorted;
}
GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *manager, Button &_button, bool check_alts) {
// check override
if (_button.override_enabled) {
return _button.override_state;
}
// for iterating button alternatives
auto current_button = &_button;
auto alternatives = check_alts ? &current_button->getAlternatives() : nullptr;
unsigned int button_count = 0;
while (true) {
// naive behavior
if (current_button->isNaive()) {
// read
auto vkey = current_button->getVKey();
GameAPI::Buttons::State state;
if (vkey == 0xFF) {
state = BUTTON_NOT_PRESSED;
} else {
state = (GetAsyncKeyState(current_button->getVKey()) & 0x8000) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
}
// invert
if (current_button->getInvert()) {
if (state == BUTTON_PRESSED) {
state = BUTTON_NOT_PRESSED;
} else {
state = BUTTON_PRESSED;
}
}
// return state
if (state != BUTTON_NOT_PRESSED) {
return state;
}
// get next button
button_count++;
if (!alternatives || alternatives->empty() || button_count - 1 >= alternatives->size()) {
return BUTTON_NOT_PRESSED;
} else {
current_button = &alternatives->at(button_count - 1);
continue;
}
}
// get device
auto &devid = current_button->getDeviceIdentifier();
auto device = manager->devices_get(devid, false); // TODO: fix to update only
// get state if device was marked as updated
GameAPI::Buttons::State state = current_button->getLastState();
double *last_up = nullptr;
double *last_down = nullptr;
if (device) {
// lock device
device->mutex->lock();
// get vkey
auto vKey = current_button->getVKey();
// update state based on device type
switch (device->type) {
case rawinput::MOUSE: {
if (vKey < sizeof(device->mouseInfo->key_states)) {
auto mouse = device->mouseInfo;
state = mouse->key_states[vKey] ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
last_up = &mouse->key_up[vKey];
last_down = &mouse->key_down[vKey];
}
break;
}
case rawinput::KEYBOARD: {
if (vKey < sizeof(device->keyboardInfo->key_states)) {
auto kb = device->keyboardInfo;
state = kb->key_states[vKey] ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
last_up = &kb->key_up[vKey];
last_down = &kb->key_down[vKey];
}
break;
}
case rawinput::HID: {
auto hid = device->hidInfo;
auto bat = current_button->getAnalogType();
switch (bat) {
case BAT_NONE: {
auto button_states_it = hid->button_states.begin();
auto button_up_it = hid->button_up.begin();
auto button_down_it = hid->button_down.begin();
while (button_states_it != hid->button_states.end()) {
auto size = button_states_it->size();
if (vKey < size) {
state = (*button_states_it)[vKey] ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
last_up = &(*button_up_it)[vKey];
last_down = &(*button_down_it)[vKey];
break;
} else {
vKey -= size;
++button_states_it;
++button_up_it;
++button_down_it;
}
}
break;
}
case BAT_NEGATIVE:
case BAT_POSITIVE: {
auto value_states = &hid->value_states;
if (vKey < value_states->size()) {
auto value = value_states->at(vKey);
if (current_button->getAnalogType() == BAT_POSITIVE) {
state = value > 0.6f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
} else {
state = value < 0.4f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
}
} else {
state = BUTTON_NOT_PRESSED;
}
break;
}
case BAT_HS_UP:
case BAT_HS_UPRIGHT:
case BAT_HS_RIGHT:
case BAT_HS_DOWNRIGHT:
case BAT_HS_DOWN:
case BAT_HS_DOWNLEFT:
case BAT_HS_LEFT:
case BAT_HS_UPLEFT:
case BAT_HS_NEUTRAL: {
auto &value_states = hid->value_states;
if (vKey < value_states.size()) {
auto value = value_states.at(vKey);
// get hat switch values
ButtonAnalogType buffer[3];
Button::getHatSwitchValues(value, buffer);
// check if one of the values match our analog type
state = BUTTON_NOT_PRESSED;
for (ButtonAnalogType &buffer_bat : buffer) {
if (buffer_bat == bat) {
state = BUTTON_PRESSED;
break;
}
}
} else
state = BUTTON_NOT_PRESSED;
break;
}
default:
state = BUTTON_NOT_PRESSED;
break;
}
break;
}
case rawinput::MIDI: {
auto bat = current_button->getAnalogType();
auto midi = device->midiInfo;
switch (bat) {
case BAT_NONE: {
if (vKey < 16 * 128) {
// check for event
auto midi_event = midi->states_events[vKey];
if (midi_event) {
// choose state based on event
state = (midi_event % 2) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
// update event
if (!midi->states[vKey] || midi_event > 1)
midi->states_events[vKey]--;
} else
state = BUTTON_NOT_PRESSED;
}
break;
}
case BAT_MIDI_CTRL_PRECISION: {
if (vKey < 16 * 32)
state = midi->controls_precision[vKey] > 0 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
else
state = BUTTON_NOT_PRESSED;
break;
}
case BAT_MIDI_CTRL_SINGLE: {
if (vKey < 16 * 44)
state = midi->controls_single[vKey] > 0 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
else
state = BUTTON_NOT_PRESSED;
break;
}
case BAT_MIDI_CTRL_ONOFF: {
if (vKey < 16 * 6)
state = midi->controls_onoff[vKey] ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
else
state = BUTTON_NOT_PRESSED;
break;
}
case BAT_MIDI_PITCH_DOWN:
state = midi->pitch_bend < 0x2000 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
case BAT_MIDI_PITCH_UP:
state = midi->pitch_bend > 0x2000 ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
default: {
state = BUTTON_NOT_PRESSED;
break;
}
}
break;
}
case rawinput::PIUIO_DEVICE: {
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
}
default:
break;
}
// unlock device
device->mutex->unlock();
}
// debounce
if (state == BUTTON_NOT_PRESSED) {
if (last_up) {
auto debounce_up = current_button->getDebounceUp();
if (debounce_up > 0.0 && get_performance_seconds() - *last_up < debounce_up) {
state = BUTTON_PRESSED;
}
}
} else {
if (last_down) {
auto debounce_down = current_button->getDebounceDown();
if (debounce_down > 0.0 && get_performance_seconds() - *last_down < debounce_down) {
state = BUTTON_NOT_PRESSED;
}
}
}
// set last state
current_button->setLastState(state);
// invert
if (current_button->getInvert()) {
if (state == BUTTON_PRESSED) {
state = BUTTON_NOT_PRESSED;
} else {
state = BUTTON_PRESSED;
}
}
// early quit
if (state == BUTTON_PRESSED) {
return state;
}
// get next button
button_count++;
if (!alternatives || alternatives->empty() || button_count - 1 >= alternatives->size()) {
return BUTTON_NOT_PRESSED;
} else {
current_button = &alternatives->at(button_count - 1);
}
}
}
Buttons::State Buttons::getState(std::unique_ptr<rawinput::RawInputManager> &manager, Button &button, bool check_alts) {
if (manager) {
return getState(manager.get(), button, check_alts);
} else {
return button.getLastState();
}
}
static float getVelocityHelper(rawinput::RawInputManager *manager, Button &button) {
// check override
if (button.override_enabled) {
return button.override_velocity;
}
// naive behavior
if (button.isNaive()) {
if (button.getInvert()) {
return (GetAsyncKeyState(button.getVKey()) & 0x8000) ? 0.f : 1.f;
} else {
return (GetAsyncKeyState(button.getVKey()) & 0x8000) ? 1.f : 0.f;
}
}
// get button state
Buttons::State button_state = Buttons::getState(manager, button, false);
// check if button isn't being pressed
if (button_state != Buttons::BUTTON_PRESSED) {
return 0.f;
}
// get device
auto &devid = button.getDeviceIdentifier();
auto device = manager->devices_get(devid, false);
// return last velocity if device wasn't found
if (!device) {
return button.getLastVelocity();
}
// prepare
float velocity = 1.f;
auto vKey = button.getVKey();
// lock device
device->mutex->lock();
// determine velocity based on device type
switch (device->type) {
case rawinput::MIDI: {
// read control
if (vKey < 16 * 128) {
velocity = (float) device->midiInfo->velocity[vKey] / 127.f;
} else {
velocity = 0.f;
}
// invert
if (button.getInvert()) {
velocity = 1.f - velocity;
}
break;
}
default:
break;
}
// unlock device
device->mutex->unlock();
// set last velocity
button.setLastVelocity(velocity);
// return determined velocity
return velocity;
}
float GameAPI::Buttons::getVelocity(rawinput::RawInputManager *manager, Button &button) {
// get button velocity
auto velocity = getVelocityHelper(manager, button);
// check alternatives
for (auto &alternative : button.getAlternatives()) {
auto alt_velocity = getVelocityHelper(manager, alternative);
if (alt_velocity > velocity) {
velocity = alt_velocity;
}
}
// return highest velocity detected
return velocity;
}
float Buttons::getVelocity(std::unique_ptr<rawinput::RawInputManager> &manager, Button &button) {
if (manager) {
return getVelocity(manager.get(), button);
} else {
return button.getLastVelocity();
}
}
float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
float value = 0.5f;
if (!device) {
return value;
}
auto index = analog.getIndex();
auto inverted = analog.getInvert();
device->mutex->lock();
// get value from device
switch (device->type) {
case rawinput::MOUSE: {
// get mouse position
auto mouse = device->mouseInfo;
long pos;
switch (index) {
case rawinput::MOUSEPOS_X:
pos = mouse->pos_x;
break;
case rawinput::MOUSEPOS_Y:
pos = mouse->pos_y;
break;
case rawinput::MOUSEPOS_WHEEL:
pos = mouse->pos_wheel;
break;
default:
pos = 0;
break;
}
// apply sensitivity
auto val = (int) roundf(pos * analog.getSensitivity());
if (val < 0) {
inverted = !inverted;
}
// modulo & normalize to [0.0, 1.0]
if (index != rawinput::MOUSEPOS_WHEEL) {
val = std::abs(val) % 257;
value = val / 256.f;
} else {
val = std::abs(val) % 65;
value = val / 64.f;
}
// invert
if (inverted) {
value = 1.f - value;
}
break;
}
case rawinput::HID: {
// get value
if (inverted) {
value = 1.f - device->hidInfo->value_states[index];
} else {
value = device->hidInfo->value_states[index];
}
// deadzone
if (analog.isDeadzoneSet()) {
value = analog.applyDeadzone(value);
}
if (analog.isRelativeMode()) {
float relative_delta = value - 0.5f;
// built-in scaling to make values reasonable
relative_delta /= 80.f;
// integer multiplier/divisor
const auto mult = analog.getMultiplier();
if (mult < -1) {
relative_delta /= -mult;
} else if (1 < mult) {
relative_delta *= mult;
}
// sensitivity (ranges from 0.0 to 4.0)
if (analog.isSensitivitySet()) {
relative_delta *= analog.getSensitivity();
}
// translate relative movement to absolute value
value = analog.getAbsoluteValue(relative_delta);
} else {
// integer multiplier
value = analog.applyMultiplier(value);
// smoothing/sensitivity
if (analog.getSmoothing() || analog.isSensitivitySet()) {
float rads = value * (float) M_TAU;
// smoothing
if (analog.getSmoothing()) {
// preserve direction
if (rads >= M_TAU) {
rads -= 0.0001f;
}
// calculate angle
rads = analog.getSmoothedValue(rads);
}
// sensitivity
if (analog.isSensitivitySet()) {
rads = analog.applyAngularSensitivity(rads);
}
// apply to value
value = rads * (float) M_1_TAU;
}
}
// delay
if (0 < analog.getDelayBufferDepth()) {
auto& queue = analog.getDelayBuffer();
// ensure the queue isn't too long; drop old values
while (analog.getDelayBufferDepth() <= (int)queue.size()) {
queue.pop();
}
// always push new value
queue.push(value);
// get a new value to return
if ((int)queue.size() < analog.getDelayBufferDepth()) {
// not enough in the queue, stall for now, shouldn't happen often
value = analog.getLastState();
} else {
value = queue.front();
queue.pop();
}
}
break;
}
case rawinput::MIDI: {
// get sizes
auto midi = device->midiInfo;
auto prec_count = (int) midi->controls_precision.size();
auto single_count = (int) midi->controls_single.size();
auto onoff_count = (int) midi->controls_onoff.size();
// decide on value
if (index < prec_count)
value = midi->controls_precision[index] / 16383.f;
else if (index < prec_count + single_count)
value = midi->controls_single[index - prec_count] / 127.f;
else if (index < prec_count + single_count + onoff_count)
value = midi->controls_onoff[index - prec_count - single_count] ? 1.f : 0.f;
else if (index == prec_count + single_count + onoff_count)
value = midi->pitch_bend / 16383.f;
// invert value
if (inverted) {
value = 1.f - value;
}
// deadzone
if (analog.isDeadzoneSet()) {
value = analog.applyDeadzone(value);
}
break;
}
default:
break;
}
device->mutex->unlock();
return value;
}
std::vector<Analog> GameAPI::Analogs::getAnalogs(const std::string &game_name) {
return Config::getInstance().getAnalogs(game_name);
}
std::vector<Analog> GameAPI::Analogs::sortAnalogs(
const std::vector<Analog> &analogs,
const std::vector<std::string> &analog_names)
{
std::vector<Analog> sorted;
bool analog_found;
for (auto &name : analog_names) {
analog_found = false;
for (auto &analog : analogs) {
if (name == analog.getName()) {
analog_found = true;
sorted.push_back(analog);
break;
}
}
if (!analog_found) {
sorted.emplace_back(name);
}
}
return sorted;
}
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
// check override
if (analog.override_enabled) {
return analog.override_state;
}
// get device
auto &devid = analog.getDeviceIdentifier();
auto device = manager->devices_get(devid, false); // TODO: fix to update only
// return last state if device wasn't updated
if (!device) {
return analog.getLastState();
}
float state = getState(device, analog);
analog.setLastState(state);
return state;
}
float Analogs::getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog) {
if (manager) {
return getState(manager.get(), analog);
} else {
return analog.getLastState();
}
}
std::vector<Light> GameAPI::Lights::getLights(const std::string &game_name) {
return Config::getInstance().getLights(game_name);
}
std::vector<Light> GameAPI::Lights::sortLights(
const std::vector<Light> &lights,
const std::vector<std::string> &light_names)
{
std::vector<Light> sorted;
bool light_found;
for (auto &name : light_names) {
light_found = false;
for (auto &light : lights) {
if (name == light.getName()) {
light_found = true;
sorted.push_back(light);
break;
}
}
if (!light_found) {
sorted.emplace_back(name);
}
}
return sorted;
}
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) {
// check device
if (!device) {
return;
}
// clamp to range [0,1]
value = CLAMP(value, 0.f, 1.f);
// lock device
device->mutex->lock();
// enable output
device->output_enabled = true;
// check type
switch (device->type) {
case rawinput::HID: {
auto hid = device->hidInfo;
// find in buttons
bool button_found = false;
for (auto &button_states : hid->button_output_states) {
if ((size_t) index < button_states.size()) {
auto new_state = value > 0.5f;
if (button_states[index] != new_state) {
button_states[index] = new_state;
device->output_pending = true;
}
button_found = true;
break;
} else
index -= button_states.size();
}
// find in values
if (!button_found) {
auto &value_states = hid->value_output_states;
if ((size_t) index < value_states.size()) {
auto cur_state = &value_states[index];
if (*cur_state != value) {
*cur_state = value;
device->output_pending = true;
}
}
}
break;
}
case rawinput::SEXTET_OUTPUT: {
if (index < rawinput::SextetDevice::LIGHT_COUNT) {
device->sextetInfo->light_state[index] = value > 0;
device->sextetInfo->push_light_state();
device->output_pending = true;
} else {
log_warning("api", "invalid sextet light index: {}", index);
}
break;
}
case rawinput::PIUIO_DEVICE: {
if (index < rawinput::PIUIO::PIUIO_MAX_NUM_OF_LIGHTS) {
device->piuioDev->SetLight(index, value > 0);
device->output_pending = true;
} else {
log_warning("api", "invalid piuio light index: {}", index);
}
break;
}
case rawinput::SMX_STAGE: {
if (index < rawinput::SmxStageDevice::TOTAL_LIGHT_COUNT) {
device->smxstageInfo->SetLightByIndex(index, static_cast<uint8_t>(value*255.f));
device->output_pending = true;
} else {
log_warning("api", "invalid smx stage light index: {}", index);
}
break;
}
default:
break;
}
// unlock device
device->mutex->unlock();
}
void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &light, float value) {
// clamp to range [0,1]
value = CLAMP(value, 0.f, 1.f);
// write to last state
light.last_state = value;
// get device
auto &devid = light.getDeviceIdentifier();
auto device = manager->devices_get(devid, false);
// check device
if (device) {
// write state
if (light.override_enabled) {
writeLight(device, light.getIndex(), light.override_state);
} else {
writeLight(device, light.getIndex(), value);
}
}
// alternatives
for (auto &alternative : light.getAlternatives()) {
if (light.override_enabled) {
alternative.override_enabled = true;
alternative.override_state = light.override_state;
writeLight(manager, alternative, light.override_state);
} else {
alternative.override_enabled = false;
writeLight(manager, alternative, value);
}
}
}
void Lights::writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value) {
if (manager) {
writeLight(manager.get(), light, value);
}
}
float GameAPI::Lights::readLight(rawinput::Device *device, int index) {
float ret = 0.f;
// lock device
device->mutex->lock();
// check type
switch (device->type) {
case rawinput::HID: {
auto hid = device->hidInfo;
// find in buttons
bool button_found = false;
for (auto &button_states : hid->button_output_states) {
if ((size_t) index < button_states.size()) {
ret = button_states[index] ? 1.f : 0.f;
button_found = true;
break;
} else
index -= button_states.size();
}
// find in values
if (!button_found) {
auto value_states = &hid->value_output_states;
if ((size_t) index < value_states->size()) {
ret = (*value_states)[index];
}
}
break;
}
default:
break;
}
// unlock device
device->mutex->unlock();
// return result
return ret;
}
float GameAPI::Lights::readLight(rawinput::RawInputManager *manager, Light &light) {
// check override
if (light.override_enabled) {
return light.override_state;
}
// just return last state since that reflects the last value being written
return light.last_state;
}
float Lights::readLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light) {
if (manager) {
return readLight(manager.get(), light);
} else {
// check override
if (light.override_enabled) {
return light.override_state;
}
// just return last state since that reflects the last value being written
return light.last_state;
}
}
std::vector<Option> GameAPI::Options::getOptions(const std::string &gameName) {
return Config::getInstance().getOptions(gameName);
}
void GameAPI::Options::sortOptions(std::vector<Option> &options, const std::vector<OptionDefinition> &definitions) {
std::vector<Option> sorted;
bool option_found;
for (const auto &definition : definitions) {
option_found = false;
for (auto &option : options) {
if (definition.name == option.get_definition().name) {
option_found = true;
auto &new_option = sorted.emplace_back(option);
new_option.set_definition(definition);
break;
}
}
if (!option_found) {
sorted.emplace_back(definition);
}
}
options = std::move(sorted);
}

158
cfg/api.h Normal file
View File

@@ -0,0 +1,158 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <windows.h>
#include "option.h"
namespace rawinput {
class RawInputManager;
struct Device;
}
class Button;
class Analog;
class Light;
class Game;
class Option;
namespace GameAPI {
namespace Buttons {
enum State {
BUTTON_PRESSED = true,
BUTTON_NOT_PRESSED = false
};
/**
* Parses the config and returns the buttons set by the user.
*
* @param a std::string containing the game name OR a pointer to a Game
* @return a vector pointer containing pointers of Button's set by the user
*/
std::vector<Button> getButtons(const std::string &game_name);
std::vector<Button> getButtons(Game *);
/**
* Sorts Buttons by their name
*
* @param a pointer to a vector pointer of Button pointers that will be sorted by the order of a vector pointer of strings
* OR a parameter pack of std::string... can be used.
* @return void
*/
std::vector<Button> sortButtons(
const std::vector<Button> &buttons,
const std::vector<std::string> &button_names,
const std::vector<unsigned short> *vkey_defaults = nullptr);
template<typename T>
void sortButtons(std::vector<Button> *buttons, T t) {
const std::vector<std::string> names { t };
if (buttons) {
*buttons = GameAPI::Buttons::sortButtons(*buttons, names);
}
}
template<typename T, typename... Rest>
void sortButtons(std::vector<Button> *buttons, T t, Rest... rest) {
const std::vector<std::string> names { t, rest... };
if (buttons) {
*buttons = GameAPI::Buttons::sortButtons(*buttons, names);
}
}
/**
* Returns the state of whether a button is pressed or not.
* Highly recommended to use either of these two functions than the other two below them.
*
* @return either a GameAPI::Buttons::State::BUTTON_PRESSED or a Game::API::Buttons::State::BUTTON_NOT_PRESSED
*/
State getState(rawinput::RawInputManager *manager, Button &button, bool check_alts = true);
State getState(std::unique_ptr<rawinput::RawInputManager> &manager, Button &button, bool check_alts = true);
/**
* Returns the current velocity of a button.
* When not pressed, the returned velocity is 0.
* When pressed, the velocity can be anywhere in the range [0, 1]
* @return velocity in the range [0, 1]
*/
float getVelocity(rawinput::RawInputManager *manager, Button &button);
float getVelocity(std::unique_ptr<rawinput::RawInputManager> &manager, Button &button);
}
namespace Analogs {
std::vector<Analog> getAnalogs(const std::string &game_name);
std::vector<Analog> sortAnalogs(
const std::vector<Analog> &analogs,
const std::vector<std::string> &analog_names);
template<typename T>
void sortAnalogs(std::vector<Analog> *analogs, T t) {
const std::vector<std::string> analog_names { t };
if (analogs) {
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names);
}
}
template<typename T, typename... Rest>
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
const std::vector<std::string> analog_names { t, rest... };
if (analogs) {
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names);
}
}
float getState(rawinput::Device *device, Analog &analog);
float getState(rawinput::RawInputManager *manager, Analog &analog);
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
}
namespace Lights {
std::vector<Light> getLights(const std::string &game_name);
std::vector<Light> sortLights(
const std::vector<Light> &lights,
const std::vector<std::string> &light_names);
template<typename T>
void sortLights(std::vector<Light> *lights, T t) {
const std::vector<std::string> light_names { t };
if (lights) {
*lights = GameAPI::Lights::sortLights(*lights, light_names);
}
}
template<typename T, typename... Rest>
void sortLights(std::vector<Light> *lights, T t, Rest... rest) {
const std::vector<std::string> light_names { t, rest... };
if (lights) {
*lights = GameAPI::Lights::sortLights(*lights, light_names);
}
}
void writeLight(rawinput::Device *device, int index, float value);
void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
float readLight(rawinput::Device *device, int index);
float readLight(rawinput::RawInputManager *manager, Light &light);
float readLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light);
}
namespace Options {
std::vector<Option> getOptions(const std::string &game_name);
void sortOptions(std::vector<Option> &, const std::vector<OptionDefinition> &);
}
}
#include "button.h"
#include "analog.h"
#include "light.h"

440
cfg/button.cpp Normal file
View File

@@ -0,0 +1,440 @@
#include "button.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/utils.h"
const char *ButtonAnalogTypeStr[] = {
"None",
"Positive",
"Negative",
"Hat Up",
"Hat Upright",
"Hat Right",
"Hat Downright",
"Hat Down",
"Hat Downleft",
"Hat Left",
"Hat Upleft",
"Hat Neutral",
"MIDI Control Precision",
"MIDI Control Single",
"MIDI Control On/Off",
"MIDI Pitch Down",
"MIDI Pitch Up",
};
std::string Button::getVKeyString() {
switch (this->getVKey() % 256) {
case 0x01:
return "Left MB";
case 0x02:
return "Right MB";
case 0x04:
return "Middle MB";
case 0x05:
return "X1 MB";
case 0x06:
return "X2 MB";
case 0x08:
return "Backspace";
case 0x09:
return "Tab";
case 0x0C:
return "Clear";
case 0x0D:
return "Enter";
case 0x10:
return "Shift";
case 0x11:
return "Ctrl";
case 0x12:
if (this->getVKey() > 255)
return "AltGr";
else
return "Alt";
case 0x13:
return "Pause";
case 0x14:
return "Caps Lock";
case 0x1B:
return "Escape";
case 0x20:
return "Space";
case 0x21:
return "Page Up";
case 0x22:
return "Page Down";
case 0x23:
return "End";
case 0x24:
return "Home";
case 0x25:
return "Left";
case 0x26:
return "Up";
case 0x27:
return "Right";
case 0x28:
return "Down";
case 0x2C:
return "Prt Scr";
case 0x2D:
return "Insert";
case 0x2E:
return "Delete";
case 0x30:
return "0";
case 0x31:
return "1";
case 0x32:
return "2";
case 0x33:
return "3";
case 0x34:
return "4";
case 0x35:
return "5";
case 0x36:
return "6";
case 0x37:
return "7";
case 0x38:
return "8";
case 0x39:
return "9";
case 0x41:
return "A";
case 0x42:
return "B";
case 0x43:
return "C";
case 0x44:
return "D";
case 0x45:
return "E";
case 0x46:
return "F";
case 0x47:
return "G";
case 0x48:
return "H";
case 0x49:
return "I";
case 0x4A:
return "J";
case 0x4B:
return "K";
case 0x4C:
return "L";
case 0x4D:
return "M";
case 0x4E:
return "N";
case 0x4F:
return "O";
case 0x50:
return "P";
case 0x51:
return "Q";
case 0x52:
return "R";
case 0x53:
return "S";
case 0x54:
return "T";
case 0x55:
return "U";
case 0x56:
return "V";
case 0x57:
return "W";
case 0x58:
return "X";
case 0x59:
return "Y";
case 0x5A:
return "Z";
case 0x5B:
return "Left Windows";
case 0x5C:
return "Right Windows";
case 0x5D:
return "Apps";
case 0x60:
return "Num 0";
case 0x61:
return "Num 1";
case 0x62:
return "Num 2";
case 0x63:
return "Num 3";
case 0x64:
return "Num 4";
case 0x65:
return "Num 5";
case 0x66:
return "Num 6";
case 0x67:
return "Num 7";
case 0x68:
return "Num 8";
case 0x69:
return "Num 9";
case 0x6A:
return "*";
case 0x6B:
return "+";
case 0x6C:
return "Seperator";
case 0x6D:
return "-";
case 0x6E:
return ".";
case 0x6F:
return "/";
case 0x70:
return "F1";
case 0x71:
return "F2";
case 0x72:
return "F3";
case 0x73:
return "F4";
case 0x74:
return "F5";
case 0x75:
return "F6";
case 0x76:
return "F7";
case 0x77:
return "F8";
case 0x78:
return "F9";
case 0x79:
return "F10";
case 0x7A:
return "F11";
case 0x7B:
return "F12";
case 0x7C:
return "F13";
case 0x7D:
return "F14";
case 0x7E:
return "F15";
case 0x7F:
return "F16";
case 0x80:
return "F17";
case 0x81:
return "F18";
case 0x82:
return "F19";
case 0x83:
return "F20";
case 0x84:
return "F21";
case 0x85:
return "F22";
case 0x86:
return "F23";
case 0x87:
return "F24";
case 0x90:
return "Num Lock";
case 0x91:
return "Scroll Lock";
case 0xA0:
return "Left Shift";
case 0xA1:
return "Right Shift";
case 0xA2:
return "Left Control";
case 0xA3:
return "Right Control";
case 0xA4:
return "Left Menu";
case 0xA5:
return "Right Menu";
default:
// check win API
char keyName[128];
if (GetKeyNameText((LONG) (MapVirtualKey(vKey, MAPVK_VK_TO_VSC) << 16), keyName, 128))
return std::string(keyName);
return "Unknown";
}
}
std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
// get VKey string
auto vKey = (uint16_t) this->getVKey();
std::string vKeyString = fmt::format("{:#x}", vKey);
// device must be existing
if (this->device_identifier.empty() && vKey == 0xFF) {
return "";
}
if (this->isNaive()) {
return this->getVKeyString() + " (Naive, " + vKeyString + ")";
} else {
auto device = manager->devices_get(this->device_identifier);
if (!device) {
return "Device missing (" + vKeyString + ")";
}
std::lock_guard<std::mutex> lock(*device->mutex);
switch (device->type) {
case rawinput::MOUSE: {
const char *btn = "Unknown";
static const char *MOUSE_NAMES[] = {
"Left Mouse",
"Right Mouse",
"Middle Mouse",
"Mouse 1",
"Mouse 2",
"Mouse 3",
"Mouse 4",
"Mouse 5",
};
if (vKey < sizeof(MOUSE_NAMES)) {
btn = MOUSE_NAMES[vKey];
}
return fmt::format("{} ({})", btn, device->desc);
}
case rawinput::KEYBOARD:
return this->getVKeyString() + " (" + device->desc + ")";
case rawinput::HID: {
auto hid = device->hidInfo;
switch (this->analog_type) {
case BAT_NONE:
if (vKey < hid->button_caps_names.size())
return hid->button_caps_names[vKey] + " (" + device->desc + ")";
else
return "Invalid button (" + device->desc + ")";
case BAT_NEGATIVE:
case BAT_POSITIVE: {
const char *sign = this->analog_type == BAT_NEGATIVE ? "-" : "+";
if (vKey < hid->value_caps_names.size()) {
return hid->value_caps_names[vKey] + sign + " (" + device->desc + ")";
} else {
return "Invalid analog (" + device->desc + ")";
}
}
case BAT_HS_UP:
return "Hat Up (" + device->desc + ")";
case BAT_HS_UPRIGHT:
return "Hat UpRight (" + device->desc + ")";
case BAT_HS_RIGHT:
return "Hat Right (" + device->desc + ")";
case BAT_HS_DOWNRIGHT:
return "Hat DownRight (" + device->desc + ")";
case BAT_HS_DOWN:
return "Hat Down (" + device->desc + ")";
case BAT_HS_DOWNLEFT:
return "Hat DownLeft (" + device->desc + ")";
case BAT_HS_LEFT:
return "Hat Left (" + device->desc + ")";
case BAT_HS_UPLEFT:
return "Hat UpLeft (" + device->desc + ")";
case BAT_HS_NEUTRAL:
return "Hat Neutral (" + device->desc + ")";
default:
return "Unknown analog type (" + device->desc + ")";
}
}
case rawinput::MIDI:
switch (this->analog_type) {
case BAT_NONE:
return "MIDI " + vKeyString + " (" + device->desc + ")";
case BAT_MIDI_CTRL_PRECISION:
return "MIDI PREC " + vKeyString + " (" + device->desc + ")";
case BAT_MIDI_CTRL_SINGLE:
return "MIDI CTRL " + vKeyString + " (" + device->desc + ")";
case BAT_MIDI_CTRL_ONOFF:
return "MIDI ONOFF " + vKeyString + " (" + device->desc + ")";
case BAT_MIDI_PITCH_DOWN:
return "MIDI Pitch Down (" + device->desc + ")";
case BAT_MIDI_PITCH_UP:
return "MIDI Pitch Up (" + device->desc + ")";
default:
return "MIDI Unknown " + vKeyString + " (" + device->desc + ")";
}
case rawinput::PIUIO_DEVICE:
return "PIUIO " + vKeyString;
case rawinput::DESTROYED:
return "Device unplugged (" + vKeyString + ")";
default:
return "Unknown device type (" + vKeyString + ")";
}
}
}
#define HAT_SWITCH_INCREMENT (1.f / 7)
void Button::getHatSwitchValues(float analog_state, ButtonAnalogType* buffer) {
// rawinput converts neutral hat switch values to a negative value
if (analog_state < 0.f) {
buffer[0] = BAT_HS_NEUTRAL;
buffer[1] = BAT_NONE;
buffer[2] = BAT_NONE;
return;
}
if (analog_state < 0 * HAT_SWITCH_INCREMENT + 0.001f) {
buffer[0] = BAT_HS_UP;
buffer[1] = BAT_NONE;
buffer[2] = BAT_NONE;
return;
}
if (analog_state < 1 * HAT_SWITCH_INCREMENT + 0.001f) {
buffer[0] = BAT_HS_UPRIGHT;
buffer[1] = BAT_HS_UP;
buffer[2] = BAT_HS_RIGHT;
return;
}
if (analog_state < 2 * HAT_SWITCH_INCREMENT + 0.001f) {
buffer[0] = BAT_HS_RIGHT;
buffer[1] = BAT_NONE;
buffer[2] = BAT_NONE;
return;
}
if (analog_state < 3 * HAT_SWITCH_INCREMENT + 0.001f) {
buffer[0] = BAT_HS_DOWNRIGHT;
buffer[1] = BAT_HS_RIGHT;
buffer[2] = BAT_HS_DOWN;
return;
}
if (analog_state < 4 * HAT_SWITCH_INCREMENT + 0.001f) {
buffer[0] = BAT_HS_DOWN;
buffer[1] = BAT_NONE;
buffer[2] = BAT_NONE;
return;
}
if (analog_state < 5 * HAT_SWITCH_INCREMENT + 0.001f) {
buffer[0] = BAT_HS_DOWNLEFT;
buffer[1] = BAT_HS_DOWN;
buffer[2] = BAT_HS_LEFT;
return;
}
if (analog_state < 6 * HAT_SWITCH_INCREMENT + 0.001f) {
buffer[0] = BAT_HS_LEFT;
buffer[1] = BAT_NONE;
buffer[2] = BAT_NONE;
return;
}
if (analog_state < 7 * HAT_SWITCH_INCREMENT + 0.001f) {
buffer[0] = BAT_HS_UPLEFT;
buffer[1] = BAT_HS_LEFT;
buffer[2] = BAT_HS_UP;
return;
}
buffer[0] = BAT_HS_NEUTRAL;
buffer[1] = BAT_NONE;
buffer[2] = BAT_NONE;
return;
}

176
cfg/button.h Normal file
View File

@@ -0,0 +1,176 @@
#pragma once
#include <string>
#include <utility>
#include <vector>
#include "api.h"
namespace rawinput {
class RawInputManager;
}
enum ButtonAnalogType {
BAT_NONE = 0,
BAT_POSITIVE = 1,
BAT_NEGATIVE = 2,
BAT_HS_UP = 3,
BAT_HS_UPRIGHT = 4,
BAT_HS_RIGHT = 5,
BAT_HS_DOWNRIGHT = 6,
BAT_HS_DOWN = 7,
BAT_HS_DOWNLEFT = 8,
BAT_HS_LEFT = 9,
BAT_HS_UPLEFT = 10,
BAT_HS_NEUTRAL = 11,
BAT_MIDI_CTRL_PRECISION = 12,
BAT_MIDI_CTRL_SINGLE = 13,
BAT_MIDI_CTRL_ONOFF = 14,
BAT_MIDI_PITCH_DOWN = 15,
BAT_MIDI_PITCH_UP = 16,
};
extern const char *ButtonAnalogTypeStr[];
class Button {
private:
std::vector<Button> alternatives;
std::string name;
std::string device_identifier = "";
unsigned short vKey = 0xFF;
ButtonAnalogType analog_type = BAT_NONE;
double debounce_up = 0.0;
double debounce_down = 0.0;
bool invert = false;
GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED;
float last_velocity = 0.f;
std::string getVKeyString();
public:
// overrides
bool override_enabled = false;
GameAPI::Buttons::State override_state = GameAPI::Buttons::BUTTON_NOT_PRESSED;
float override_velocity = 0.f;
explicit Button(std::string name) : name(std::move(name)) {};
inline std::vector<Button> &getAlternatives() {
return this->alternatives;
}
inline bool isSet() {
if (this->override_enabled) {
return true;
}
if (this->vKey != 0xFF) {
return true;
}
for (auto &alternative : this->alternatives) {
if (alternative.vKey != 0xFF) {
return true;
}
}
return false;
}
inline void clearBindings() {
vKey = 0xFF;
alternatives.clear();
device_identifier = "";
analog_type = BAT_NONE;
}
std::string getDisplayString(rawinput::RawInputManager* manager);
inline bool isNaive() const {
return this->device_identifier.empty();
}
inline const std::string &getName() const {
return this->name;
}
inline const std::string &getDeviceIdentifier() const {
return this->device_identifier;
}
inline void setDeviceIdentifier(std::string new_device_identifier) {
this->device_identifier = std::move(new_device_identifier);
}
inline unsigned short getVKey() const {
return this->vKey;
}
inline void setVKey(unsigned short vKey) {
this->vKey = vKey;
}
inline ButtonAnalogType getAnalogType() const {
return this->analog_type;
}
inline void setAnalogType(ButtonAnalogType analog_type) {
this->analog_type = analog_type;
}
inline double getDebounceUp() const {
return this->debounce_up;
}
inline void setDebounceUp(double debounce_time_up) {
this->debounce_up = debounce_time_up;
}
inline double getDebounceDown() const {
return this->debounce_down;
}
inline void setDebounceDown(double debounce_time_down) {
this->debounce_down = debounce_time_down;
}
inline bool getInvert() const {
return this->invert;
}
inline void setInvert(bool invert) {
this->invert = invert;
}
inline GameAPI::Buttons::State getLastState() {
return this->last_state;
}
inline void setLastState(GameAPI::Buttons::State last_state) {
this->last_state = last_state;
}
inline float getLastVelocity() {
return this->last_velocity;
}
inline void setLastVelocity(float last_velocity) {
this->last_velocity = last_velocity;
}
/*
* Map hat switch float value from [0-1] to directions.
* Buffer must be sized 3 or bigger.
* Order of detection is:
* 1. Multi Direction Binding (example: BAT_HS_UPRIGHT)
* 2. Lower number binding (in order up, right, down, left)
* 3. Higher number binding
* Empty fields will be 0 (BAT_NONE)
*
* Example:
* Xbox360 Controller reports value 2 => mapped to float [0-1] it's 0.25f
* Resulting buffer is: {BAT_HS_UPRIGHT, BAT_HS_UP, BAT_HS_RIGHT}
*/
static void getHatSwitchValues(float analog_state, ButtonAnalogType* buffer);
};

1215
cfg/config.cpp Normal file

File diff suppressed because it is too large Load Diff

62
cfg/config.h Normal file
View File

@@ -0,0 +1,62 @@
#pragma once
#include <filesystem>
#include <fstream>
#include <iostream>
#include "external/tinyxml2/tinyxml2.h"
#include "game.h"
// settings
extern std::string CONFIG_PATH_OVERRIDE;
struct ConfigKeypadBindings {
std::string keypads[2];
std::filesystem::path card_paths[2];
};
class Config {
public:
static Config &getInstance();
bool getStatus();
bool createConfigFile();
bool addGame(Game &game);
bool updateBinding(const Game &game, const Button &button, int alternative);
bool updateBinding(const Game &game, const Analog &analog);
bool updateBinding(const Game &game, ConfigKeypadBindings &keypads);
bool updateBinding(const Game &game, const Light &light, int alternative);
bool updateBinding(const Game &game, const Option &option);
std::vector<Button> getButtons(const std::string &game);
std::vector<Button> getButtons(Game *);
std::vector<Analog> getAnalogs(const std::string &game);
std::vector<Analog> getAnalogs(Game *);
ConfigKeypadBindings getKeypadBindings(const std::string &game);
ConfigKeypadBindings getKeypadBindings(Game *);
std::vector<Light> getLights(const std::string &game);
std::vector<Light> getLights(Game *);
std::vector<Option> getOptions(const std::string &game);
std::vector<Option> getOptions(Game *);
private:
Config();
Config(const Config &);
~Config() = default;
const Config &operator=(const Config &);
tinyxml2::XMLDocument configFile;
bool status;
std::filesystem::path configLocation;
std::filesystem::path configLocationTemp;
bool firstFillConfigFile();
void saveConfigFile();
};

39
cfg/configurator.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "configurator.h"
#include "overlay/overlay.h"
#include "script/manager.h"
namespace cfg {
// globals
bool CONFIGURATOR_STANDALONE = false;
ConfigType CONFIGURATOR_TYPE = ConfigType::Config;
Configurator::Configurator() {
CONFIGURATOR_STANDALONE = true;
}
Configurator::~Configurator() {
CONFIGURATOR_STANDALONE = false;
}
void Configurator::run() {
// create instance
overlay::ENABLED = true;
overlay::create_software(this->wnd.hWnd);
overlay::OVERLAY->set_active(true);
overlay::OVERLAY->hotkeys_enable = false;
ImGui::GetIO().MouseDrawCursor = false;
// scripts
script::manager_scan();
script::manager_config();
// run window
this->wnd.run();
// clean up
script::manager_shutdown();
}
}

26
cfg/configurator.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include "configurator_wnd.h"
namespace cfg {
enum class ConfigType {
Config,
KFControl,
};
// globals
extern bool CONFIGURATOR_STANDALONE;
extern ConfigType CONFIGURATOR_TYPE;
class Configurator {
private:
ConfiguratorWindow wnd;
public:
Configurator();
~Configurator();
void run();
};
}

177
cfg/configurator_wnd.cpp Normal file
View File

@@ -0,0 +1,177 @@
#include "configurator_wnd.h"
#include <windows.h>
#include "build/defs.h"
#include "launcher/shutdown.h"
#include "overlay/overlay.h"
#include "util/logging.h"
#include "cfg/configurator.h"
#include "icon.h"
static const char *CLASS_NAME = "ConfiguratorWindow";
static std::string WINDOW_TITLE;
static int WINDOW_SIZE_X = 800;
static int WINDOW_SIZE_Y = 600;
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
cfg::ConfiguratorWindow::ConfiguratorWindow() {
// register the window class
WNDCLASS wc {};
wc.lpfnWndProc = ConfiguratorWindow::window_proc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = NULL;
wc.hIcon = WINDOW_ICON;
RegisterClass(&wc);
// determine window title
if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
WINDOW_TITLE = "spice2x config (" + to_string(VERSION_STRING_CFG) + ")";
WINDOW_SIZE_X = 800;
WINDOW_SIZE_Y = 600;
} else if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::KFControl) {
WINDOW_TITLE = "KFControl (" + to_string(VERSION_STRING_CFG) + ")";
WINDOW_SIZE_X = 400;
WINDOW_SIZE_Y = 316;
}
// open window
this->hWnd = CreateWindowEx(
0,
CLASS_NAME,
WINDOW_TITLE.c_str(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
GetModuleHandle(NULL),
(LPVOID) this);
if (this->hWnd) {
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
}
}
cfg::ConfiguratorWindow::~ConfiguratorWindow() {
// close window
DestroyWindow(this->hWnd);
// unregister class
UnregisterClass(CLASS_NAME, GetModuleHandle(NULL));
}
void cfg::ConfiguratorWindow::run() {
// show window
SetWindowPos(this->hWnd, HWND_TOP, 0, 0, WINDOW_SIZE_X, WINDOW_SIZE_Y, 0);
ShowWindow(this->hWnd, SW_SHOWNORMAL);
UpdateWindow(this->hWnd);
// draw overlay in 60 FPS
SetTimer(this->hWnd, 1, 1000 / 60, nullptr);
// window loop
BOOL ret;
MSG msg;
while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0) {
if (ret == -1) {
break;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
LRESULT CALLBACK cfg::ConfiguratorWindow::window_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CHAR: {
// input characters if overlay is active
if (overlay::OVERLAY && overlay::OVERLAY->has_focus()) {
overlay::OVERLAY->input_char((unsigned int) wParam);
return true;
}
break;
}
case WM_CREATE: {
// set user data of window to class pointer
auto create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam);
SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(create_struct->lpCreateParams));
break;
}
case WM_CLOSE:
case WM_DESTROY: {
// exit process
launcher::shutdown();
break;
}
case WM_TIMER: {
// update overlay
if (overlay::OVERLAY) {
overlay::OVERLAY->update();
overlay::OVERLAY->set_active(true);
overlay::OVERLAY->new_frame();
overlay::OVERLAY->render();
}
// repaint window
InvalidateRect(hWnd, nullptr, TRUE);
break;
}
case WM_ERASEBKGND: {
return 1;
}
case WM_PAINT: {
// render overlay
if (overlay::OVERLAY) {
// get pixel data
int width, height;
uint32_t *pixel_data = overlay::OVERLAY->sw_get_pixel_data(&width, &height);
if (width > 0 && height > 0) {
// create bitmap
HBITMAP bitmap = CreateBitmap(width, height, 1, 8 * sizeof(uint32_t), pixel_data);
// prepare paint
PAINTSTRUCT paint{};
HDC hdc = BeginPaint(hWnd, &paint);
HDC hdcMem = CreateCompatibleDC(hdc);
SetBkMode(hdc, TRANSPARENT);
// draw bitmap
SelectObject(hdcMem, bitmap);
BitBlt(hdc, paint.rcPaint.left, paint.rcPaint.top,
paint.rcPaint.right - paint.rcPaint.left,
paint.rcPaint.bottom - paint.rcPaint.top,
hdcMem, paint.rcPaint.left, paint.rcPaint.top, SRCCOPY);
// delete bitmap
DeleteObject(bitmap);
// clean up
DeleteDC(hdcMem);
EndPaint(hWnd, &paint);
} else {
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
} else {
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
break;
}
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}

19
cfg/configurator_wnd.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <windows.h>
namespace cfg {
class ConfiguratorWindow {
public:
HWND hWnd;
ConfiguratorWindow();
~ConfiguratorWindow();
void run();
static LRESULT CALLBACK window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
};
}

25
cfg/game.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include "game.h"
/////////////////////////
/// Private Functions ///
/////////////////////////
// add button items
void Game::addItem(Button button) {
this->buttons.push_back(std::move(button));
}
// add analog items
void Game::addItem(Analog analog) {
this->analogs.push_back(std::move(analog));
}
// add light items
void Game::addItem(Light light) {
this->lights.push_back(std::move(light));
}
// add option items
void Game::addItem(Option option) {
this->options.push_back(std::move(option));
}

84
cfg/game.h Normal file
View File

@@ -0,0 +1,84 @@
#pragma once
#include <vector>
#include <typeinfo>
#include <iostream>
#include "button.h"
#include "analog.h"
#include "light.h"
#include "option.h"
#include "external/tinyxml2/tinyxml2.h"
class Game {
public:
explicit Game(std::string game_name) : game_name(std::move(game_name)) {};
/*
template<typename T, typename... Rest>
Game(std::string gameName) : gameName(std::move(gameName)) {};
*/
~Game() = default;
inline const std::string &getGameName() const {
return this->game_name;
};
inline std::vector<std::string> &getDLLNames() {
return this->dll_names;
}
inline void addDLLName(std::string dll_name) {
this->dll_names.push_back(std::move(dll_name));
}
template<typename T>
void addItems(T t) {
this->addItem(t);
}
template<typename T, typename... Rest>
void addItems(T t, Rest... rest) {
this->addItem(t);
this->addItems(rest...);
}
inline std::vector<Button> &getButtons() {
return this->buttons;
}
inline const std::vector<Button> &getButtons() const {
return this->buttons;
}
inline std::vector<Analog> &getAnalogs() {
return this->analogs;
}
inline const std::vector<Analog> &getAnalogs() const {
return this->analogs;
}
inline std::vector<Light> &getLights() {
return this->lights;
}
inline const std::vector<Light> &getLights() const {
return this->lights;
}
inline std::vector<Option> &getOptions() {
return this->options;
}
inline const std::vector<Option> &getOptions() const {
return this->options;
}
private:
std::string game_name;
std::vector<std::string> dll_names;
std::vector<Button> buttons;
std::vector<Analog> analogs;
std::vector<Light> lights;
std::vector<Option> options;
void addItem(Button button);
void addItem(Analog analog);
void addItem(Light light);
void addItem(Option option);
};

3
cfg/icon.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
#define MAINICON 20

BIN
cfg/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

3
cfg/icon.rc Normal file
View File

@@ -0,0 +1,3 @@
#include "icon.h"
MAINICON ICON "icon.ico"

83
cfg/light.cpp Normal file
View File

@@ -0,0 +1,83 @@
#include "light.h"
#include <utility>
#include "rawinput/piuio.h"
#include "rawinput/rawinput.h"
#include "rawinput/sextet.h"
#include "rawinput/smxstage.h"
#include "util/logging.h"
std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
// get index string
std::string index_string = fmt::format("{:#x}", index);
// device must be existing
if (this->deviceIdentifier.empty()) {
return "";
}
// get device
auto device = manager->devices_get(this->deviceIdentifier);
if (!device) {
return "Device missing (" + index_string + ")";
}
// return string based on device type
switch (device->type) {
case rawinput::HID: {
auto hid = device->hidInfo;
unsigned int hid_index = index;
// check button output caps
if (hid_index < hid->button_output_caps_names.size()) {
return hid->button_output_caps_names[hid_index] +
" (" + index_string + " - " + device->desc + ")";
} else {
hid_index -= hid->button_output_caps_names.size();
}
// check value output caps
if (hid_index < hid->value_output_caps_names.size()) {
return hid->value_output_caps_names[hid_index] +
" (" + index_string + " - " + device->desc + ")";
}
// not found
return "Invalid Light (" + index_string + ")";
}
case rawinput::SEXTET_OUTPUT: {
// get light name of sextet device
if (index < rawinput::SextetDevice::LIGHT_COUNT) {
return rawinput::SextetDevice::LIGHT_NAMES[index] + " (" + index_string + ")";
}
// not found
return "Invalid Sextet Light (" + index_string + ")";
}
case rawinput::PIUIO_DEVICE: {
// get light name of PIUIO device
if (index < rawinput::PIUIO::PIUIO_MAX_NUM_OF_LIGHTS) {
return rawinput::PIUIO::LIGHT_NAMES[index] + " (" + index_string + ")";
}
return "Invalid PIUIO Light (" + index_string + ")";
}
case rawinput::SMX_STAGE: {
// get light name of SMX Stage device
if (index < rawinput::SmxStageDevice::TOTAL_LIGHT_COUNT) {
return rawinput::SmxStageDevice::GetLightNameByIndex(index) + " (" + index_string + ")";
}
return "Invalid SMX Stage Light (" + index_string + ")";
}
case rawinput::DESTROYED:
return "Unplugged device (" + index_string + ")";
default:
return "Unknown Light (" + index_string + ")";
}
}

68
cfg/light.h Normal file
View File

@@ -0,0 +1,68 @@
#pragma once
#include <string>
#include <vector>
namespace rawinput {
class RawInputManager;
}
class Light {
private:
std::vector<Light> alternatives;
std::string lightName;
std::string deviceIdentifier = "";
unsigned int index = 0;
public:
float last_state = 0.f;
// overrides
bool override_enabled = false;
float override_state = 0.f;
explicit Light(std::string lightName) : lightName(std::move(lightName)) {};
std::string getDisplayString(rawinput::RawInputManager* manager);
inline std::vector<Light> &getAlternatives() {
return this->alternatives;
}
inline bool isSet() const {
if (this->override_enabled) {
return true;
}
if (!this->deviceIdentifier.empty()) {
return true;
}
for (auto &alternative : this->alternatives) {
if (!alternative.deviceIdentifier.empty()) {
return true;
}
}
return false;
}
inline const std::string &getName() const {
return this->lightName;
}
inline const std::string &getDeviceIdentifier() const {
return this->deviceIdentifier;
}
inline void setDeviceIdentifier(std::string deviceIdentifier) {
this->deviceIdentifier = std::move(deviceIdentifier);
}
inline unsigned int getIndex() const {
return this->index;
}
inline void setIndex(unsigned int index) {
this->index = index;
}
};

4
cfg/manifest.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
#define RT_MANIFEST 24

29
cfg/manifest.manifest Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="spicecfg.exe"
type="win32"
/>
<description>spice2x config</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

30
cfg/manifest.rc Normal file
View File

@@ -0,0 +1,30 @@
#include "manifest.h"
#include "winuser.h"
#ifdef __GNUC__
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "manifest.manifest"
#endif
1 VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "080904E4"
BEGIN
VALUE "CompanyName", ""
VALUE "FileDescription", "SpiceTools Configuration Utility"
VALUE "FileVersion", "1.0.0.0"
VALUE "InternalName", "spicecfg"
VALUE "LegalCopyright", ""
VALUE "OriginalFilename", "spicecfg.exe"
VALUE "ProductName", "SpiceTools Configuration"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x809, 1252
END
END

112
cfg/option.cpp Normal file
View File

@@ -0,0 +1,112 @@
#include "option.h"
#include "util/logging.h"
#include "util/utils.h"
void Option::value_add(std::string new_value) {
// put in primary slot if possible
if (this->value.empty()) {
this->value = std::move(new_value);
return;
}
// add new alternative
this->alternatives.emplace_back(this->definition, std::move(new_value));
}
bool Option::has_alternatives() const {
return !this->alternatives.empty();
}
bool Option::value_bool() const {
if (this->definition.type != OptionType::Bool) {
log_fatal("option", "value_bool() called on {}/{}", this->definition.title, this->definition.type);
}
return !this->value.empty();
}
const std::string &Option::value_text() const {
if (this->definition.type != OptionType::Text && this->definition.type != OptionType::Enum) {
log_fatal("option", "value_text() called on {}/{}", this->definition.title, this->definition.type);
}
return this->value;
}
std::vector<std::string> Option::values() const {
std::vector<std::string> values;
if (!this->is_active()) {
return values;
}
values.push_back(this->value);
for (auto &alt : this->alternatives) {
if (alt.is_active()) {
values.push_back(alt.value);
}
}
return values;
}
std::vector<std::string> Option::values_text() const {
std::vector<std::string> values;
if (!this->is_active()) {
return values;
}
values.push_back(this->value_text());
for (auto &alt : this->alternatives) {
if (alt.is_active()) {
values.push_back(alt.value_text());
}
}
return values;
}
uint32_t Option::value_uint32() const {
if (this->definition.type != OptionType::Integer && this->definition.type != OptionType::Enum) {
log_fatal("option", "invalid call: value_uint32() called on {}/{}", this->definition.title, this->definition.type);
return 0;
}
char *p;
auto res = strtol(this->value.c_str(), &p, 10);
if (*p) {
log_fatal("option", "failed to convert {} to unsigned integer (option: {})", this->value, this->definition.title);
return 0;
} else {
return res;
}
}
uint64_t Option::value_hex64() const {
if (this->definition.type != OptionType::Hex) {
log_fatal("option", "invalid call: value_hex() called on {}/{}", this->definition.title, this->definition.type);
return 0;
}
uint64_t affinity = 0;
try {
affinity = std::stoull(this->value.c_str(), nullptr, 16);
} catch (const std::exception &ex) {
log_fatal("option", "failed to parse {} as hexadecimal (option: {})", this->value, this->definition.title);
}
return affinity;
}
bool Option::search_match(const std::string &query_in_lower_case) {
if (this->search_string.empty()) {
const auto &param =
this->definition.display_name.empty() ?
this->definition.name : this->definition.display_name;
const auto s = this->definition.title + " -" + param;
this->search_string = strtolower(s);
}
return this->search_string.find(query_in_lower_case) != std::string::npos;
}

72
cfg/option.h Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#include <string>
#include <utility>
#include <vector>
#include <cstdint>
enum class OptionType {
Bool,
Text,
Integer,
Enum,
Hex,
};
struct OptionDefinition {
std::string title;
// unique identifier used for flag matching but also stored in config files
// (should not be changed once published for compat)
std::string name;
// what's displayed in the UI/logs as the flag name
std::string display_name = "";
// slash-delimited list of strings that also work as flag
std::string aliases = "";
// what's displayed in the UI/logs as the tooltip
std::string desc;
OptionType type;
bool hidden = false;
std::string setting_name = "";
std::string game_name = "";
std::string category = "Development";
bool sensitive = false;
std::vector<std::pair<std::string, std::string>> elements = {};
bool disabled = false;
};
class Option {
private:
OptionDefinition definition;
std::string search_string;
public:
std::string value;
std::vector<Option> alternatives;
bool disabled = false;
explicit Option(OptionDefinition definition, std::string value = "") :
definition(std::move(definition)), value(std::move(value)) {
};
inline const OptionDefinition &get_definition() const {
return this->definition;
}
inline void set_definition(OptionDefinition definition) {
this->definition = std::move(definition);
}
inline bool is_active() const {
return !this->value.empty();
}
void value_add(std::string new_value);
bool has_alternatives() const;
bool value_bool() const;
const std::string &value_text() const;
std::vector<std::string> values() const;
std::vector<std::string> values_text() const;
uint32_t value_uint32() const;
uint64_t value_hex64() const;
bool search_match(const std::string &query_in_lower_case);
};

7
cfg/resource.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
#define IDR_CHANGELOG 130
#define IDR_LICENSES 131
#define IDR_PATCHES 132
#define IDR_README 133
#define IDR_DSEGFONT 134

219
cfg/screen_resize.cpp Normal file
View File

@@ -0,0 +1,219 @@
#include "screen_resize.h"
#include "external/rapidjson/document.h"
#include "external/rapidjson/pointer.h"
#include "external/rapidjson/prettywriter.h"
#include "misc/eamuse.h"
#include "util/utils.h"
#include "util/fileutils.h"
#include "hooks/graphics/graphics.h"
namespace cfg {
// globals
std::unique_ptr<cfg::ScreenResize> SCREENRESIZE;
std::optional<std::string> SCREEN_RESIZE_CFG_PATH_OVERRIDE;
ScreenResize::ScreenResize() {
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
} else {
this->config_path = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools_screen_resize.json";
}
if (fileutils::file_exists(this->config_path)) {
this->config_load();
}
}
ScreenResize::~ScreenResize() {
}
void ScreenResize::config_load() {
log_info("ScreenResize", "loading config: {}", this->config_path.string());
std::string config = fileutils::text_read(this->config_path);
if (config.empty()) {
log_info("ScreenResize", "config is empty");
return;
}
// parse document
rapidjson::Document doc;
doc.Parse(config.c_str());
// check parse error
auto error = doc.GetParseError();
if (error) {
log_warning("ScreenResize", "config parse error: {}", error);
return;
}
// verify root is a dict
if (!doc.IsObject()) {
log_warning("ScreenResize", "config not found");
return;
}
bool use_game_setting = false;
std::string root("/");
// try to find game-specific setting, if one exists
{
const auto game = rapidjson::Pointer("/sp2x_games/" + eamuse_get_game()).Get(doc);
if (game && game->IsObject()) {
use_game_setting = true;
root = "/sp2x_games/" + eamuse_get_game() + "/";
}
}
log_misc(
"ScreenResize",
"Loading fullscreen image settings. Game = {}, is_global = {}, JSON path: {}",
eamuse_get_game(),
use_game_setting,
root);
load_int_value(doc, root + "offset_x", this->offset_x);
load_int_value(doc, root + "offset_y", this->offset_y);
load_float_value(doc, root + "scale_x", this->scale_x);
load_float_value(doc, root + "scale_y", this->scale_y);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
load_bool_value(doc, root + "keep_aspect_ratio", this->keep_aspect_ratio);
load_bool_value(doc, root + "centered", this->centered);
// windowed settings are always under game settings
root = "/sp2x_games/" + eamuse_get_game() + "/";
log_misc(
"ScreenResize",
"Loading window settings. Game = {}, JSON path: {}",
eamuse_get_game(),
root);
load_bool_value(doc, root + "w_always_on_top", this->window_always_on_top);
load_bool_value(doc, root + "w_enable_resize", this->enable_window_resize);
load_bool_value(doc, root + "w_keep_aspect_ratio", this->client_keep_aspect_ratio);
load_int_value(doc, root + "w_border_type", this->window_decoration);
load_uint32_value(doc, root + "w_width", this->client_width);
load_uint32_value(doc, root + "w_height", this->client_height);
load_int_value(doc, root + "w_offset_x", this->window_offset_x);
load_int_value(doc, root + "w_offset_y", this->window_offset_y);
}
bool ScreenResize::load_bool_value(rapidjson::Document& doc, std::string path, bool& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_misc("ScreenResize", "{} not found", path);
return false;
}
if (!v->IsBool()) {
log_warning("ScreenResize", "{} is invalid type", path);
return false;
}
value = v->GetBool();
return true;
}
bool ScreenResize::load_int_value(rapidjson::Document& doc, std::string path, int& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_misc("ScreenResize", "{} not found", path);
return false;
}
if (!v->IsInt()) {
log_warning("ScreenResize", "{} is invalid type", path);
return false;
}
value = v->GetInt();
return true;
}
bool ScreenResize::load_uint32_value(rapidjson::Document& doc, std::string path, uint32_t& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_misc("ScreenResize", "{} not found", path);
return false;
}
if (!v->IsUint()) {
log_warning("ScreenResize", "{} is invalid type", path);
return false;
}
value = v->GetUint();
return true;
}
bool ScreenResize::load_float_value(rapidjson::Document& doc, std::string path, float& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_misc("ScreenResize", "{} not found", path);
return false;
}
if (v->IsInt()) {
value = v->GetInt();
return true;
}
if (v->IsDouble()) {
value = v->GetDouble();
return true;
}
if (v->IsFloat()) {
value = v->GetFloat();
return true;
}
return false;
}
void ScreenResize::config_save() {
log_info("ScreenResize", "saving config: {}", this->config_path.string());
rapidjson::Document doc;
std::string config = fileutils::text_read(this->config_path);
if (!config.empty()) {
doc.Parse(config.c_str());
log_misc("ScreenResize", "existing config file found");
}
if (!doc.IsObject()) {
log_misc("ScreenResize", "clearing out config file");
doc.SetObject();
}
// always save under per-game settings
std::string root("/sp2x_games/" + eamuse_get_game() + "/");
log_misc(
"ScreenResize",
"Game = {}, JSON path = {}",
eamuse_get_game(),
root);
// full screen image settings
rapidjson::Pointer(root + "offset_x").Set(doc, this->offset_x);
rapidjson::Pointer(root + "offset_y").Set(doc, this->offset_y);
rapidjson::Pointer(root + "scale_x").Set(doc, this->scale_x);
rapidjson::Pointer(root + "scale_y").Set(doc, this->scale_y);
rapidjson::Pointer(root + "enable_screen_resize").Set(doc, this->enable_screen_resize);
rapidjson::Pointer(root + "enable_linear_filter").Set(doc, this->enable_linear_filter);
rapidjson::Pointer(root + "keep_aspect_ratio").Set(doc, this->keep_aspect_ratio);
rapidjson::Pointer(root + "centered").Set(doc, this->centered);
// windowed mode settings
rapidjson::Pointer(root + "w_always_on_top").Set(doc, this->window_always_on_top);
rapidjson::Pointer(root + "w_enable_resize").Set(doc, this->enable_window_resize);
rapidjson::Pointer(root + "w_keep_aspect_ratio").Set(doc, this->client_keep_aspect_ratio);
rapidjson::Pointer(root + "w_border_type").Set(doc, this->window_decoration);
rapidjson::Pointer(root + "w_width").Set(doc, this->client_width);
rapidjson::Pointer(root + "w_height").Set(doc, this->client_height);
rapidjson::Pointer(root + "w_offset_x").Set(doc, this->window_offset_x);
rapidjson::Pointer(root + "w_offset_y").Set(doc, this->window_offset_y);
// build JSON
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
doc.Accept(writer);
// save to file
if (fileutils::text_write(this->config_path, buffer.GetString())) {
// this->config_dirty = false;
} else {
log_warning("ScreenResize", "unable to save config file to {}", this->config_path.string());
}
}
}

70
cfg/screen_resize.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include <memory>
#include <string>
#include <optional>
#include <filesystem>
#include "external/rapidjson/document.h"
namespace cfg {
enum WindowDecorationMode {
Default = 0,
Borderless = 1,
ResizableFrame = 2
};
extern std::optional<std::string> SCREEN_RESIZE_CFG_PATH_OVERRIDE;
class ScreenResize {
private:
std::filesystem::path config_path;
// bool config_dirty = false;
bool load_bool_value(rapidjson::Document& doc, std::string path, bool& value);
bool load_int_value(rapidjson::Document& doc, std::string path, int& value);
bool load_uint32_value(rapidjson::Document& doc, std::string path, uint32_t& value);
bool load_float_value(rapidjson::Document& doc, std::string path, float& value);
public:
ScreenResize();
~ScreenResize();
// full screen (directx) image settings
int offset_x = 0;
int offset_y = 0;
float scale_x = 1.0;
float scale_y = 1.0;
bool enable_screen_resize = false;
bool enable_linear_filter = true;
bool keep_aspect_ratio = true;
bool centered = true;
// windowed mode sizing
// Windows terminology:
// window = rectangle including the frame
// client = just the content area without frames.
bool window_always_on_top = false;
bool client_keep_aspect_ratio = true;
bool enable_window_resize = false;
int window_decoration = 0; // enum type WindowDecorationMode
uint32_t client_width = 0;
uint32_t client_height = 0;
int32_t window_offset_x = 0;
int32_t window_offset_y = 0;
// these are not saved by config, but used by window management
uint32_t init_client_width = 0;
uint32_t init_client_height = 0;
float init_client_aspect_ratio = 1.f;
uint32_t init_window_style = 0;
uint32_t window_deco_width = 0;
uint32_t window_deco_height = 0;
void config_load();
void config_save();
};
// globals
extern std::unique_ptr<cfg::ScreenResize> SCREENRESIZE;
}

70
cfg/spicecfg.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include "spicecfg.h"
#include <memory>
#include <windows.h>
#include <shlwapi.h>
#include "launcher/launcher.h"
#include "launcher/logger.h"
#include "launcher/signal.h"
#include "launcher/options.h"
#include "util/crypt.h"
#include "util/libutils.h"
#include "rawinput/rawinput.h"
#include "config.h"
#include "configurator.h"
// for debugging, set to 1 to allocate console
#define ALLOC_CONSOLE 0
int spicecfg_run(const std::vector<std::string> &sextet_devices) {
// initialize config
auto &config = Config::getInstance();
if (!config.getStatus()) {
return EXIT_FAILURE;
}
// initialize input
RI_MGR = std::make_unique<rawinput::RawInputManager>();
RI_MGR->devices_print();
for (const auto &device : sextet_devices) {
RI_MGR->sextet_register(device);
}
// run configurator
cfg::CONFIGURATOR_STANDALONE = true;
cfg::Configurator configurator;
configurator.run();
// success
return 0;
}
#ifdef SPICETOOLS_SPICECFG_STANDALONE
#ifdef _MSC_VER
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) {
#else
int main(int argc, char *argv[]) {
#endif
// initialize console
if (ALLOC_CONSOLE) {
AllocConsole();
freopen("conin$", "r", stdin);
freopen("conout$", "w", stdout);
freopen("conout$", "w", stderr);
}
// run launcher with configurator option
cfg::CONFIGURATOR_STANDALONE = true;
#ifdef _MSC_VER
return main_implementation(__argc, __argv);
#else
return main_implementation(argc, argv);
#endif
}
#endif

6
cfg/spicecfg.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include <vector>
#include <string>
int spicecfg_run(const std::vector<std::string> &sextet_devices);