Initial commit for GitHub migration based on spice2x-25-03-03

This commit is contained in:
sp2xdev
2025-03-10 19:16:49 -07:00
parent 94af8d32c9
commit ed8c72852f
1008 changed files with 296815 additions and 0 deletions
+18
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
+274
View File
@@ -0,0 +1,274 @@
#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;
// update strings in button.cpp as well
if (index < midi->controls_precision.size()) {
const int channel = (index / 32) + 1;
const int cc_index = (index % 32);
return fmt::format("MIDI Prec Ctrl Ch.{} CC#{} ({})", channel, cc_index, device->desc);
} else if (index < midi->controls_precision.size() + midi->controls_single.size()) {
const int index_rel = index - midi->controls_precision.size();
const int channel = (index_rel / 44) + 1;
int cc_index = (index_rel % 44);
if (cc_index < 26) {
cc_index += 0x46; // single byte range
} else {
cc_index = cc_index - 26 + 0x66; // undefined single byte range
}
return fmt::format("MIDI Ctrl Ch.{} CC#{} ({})", channel, cc_index, device->desc);
} else if (index < midi->controls_precision.size() + midi->controls_single.size()
+ midi->controls_onoff.size())
{
const int index_rel = index - midi->controls_precision.size() - midi->controls_single.size();
const int channel = (index_rel / 6) + 1;
const int cc_index = (index_rel % 6) + 0x40;
return fmt::format("MIDI OnOff Ch.{} CC#{} ({})", channel, cc_index, device->desc);
} else if (index <
midi->controls_precision.size() + midi->controls_single.size() + midi->controls_onoff.size() + midi->pitch_bend.size())
{
const int index_rel =
index -
midi->controls_precision.size() -
midi->controls_single.size() -
midi->controls_onoff.size();
return fmt::format("MIDI Pitch Ch.{} ({})", index_rel + 1, device->desc);
} else {
return "MIDI Unknown Index " + 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
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 = USHRT_MAX;
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;
}
};
File diff suppressed because it is too large Load Diff
+158
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"
+578
View File
@@ -0,0 +1,578 @@
#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",
"Any Direction",
};
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::getMidiNoteString() {
static const std::string note_names[] = {"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"};
int channel;
int index;
this->getMidiVKey(channel, index);
return fmt::format("{}{}", note_names[index % 12], ((index / 12) - 1));
}
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:
case BAT_ANY: {
const char *sign;
if (this->analog_type == BAT_NEGATIVE) {
sign = "-";
} else if (this->analog_type == BAT_POSITIVE) {
sign = "+";
} else {
sign = "*";
}
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: {
int channel = 0;
int ctrl = 0;
this->getMidiVKey(channel, ctrl);
switch (this->analog_type) {
// update strings in analog.cpp as well
case BAT_NONE: {
const auto note = this->getMidiNoteString();
return fmt::format("MIDI Note Ch.{} #{} {} ({})", channel, ctrl, note, device->desc);
}
case BAT_MIDI_CTRL_PRECISION: {
return fmt::format("MIDI Prec Ctrl Ch.{} CC#{} ({})", channel, ctrl, device->desc);
}
case BAT_MIDI_CTRL_SINGLE: {
return fmt::format("MIDI Ctrl Ch.{} CC#{} ({})", channel, ctrl, device->desc);
}
case BAT_MIDI_CTRL_ONOFF: {
return fmt::format("MIDI OnOff Ch.{} CC#{} ({})", channel, ctrl, device->desc);
}
case BAT_MIDI_PITCH_DOWN:
return fmt::format("MIDI Pitch Down Ch.{} ({})", channel, device->desc);
case BAT_MIDI_PITCH_UP:
return fmt::format("MIDI Pitch Up Ch.{} ({})", channel, device->desc);
default:
return "MIDI Unknown " + vKeyString + " (" + device->desc + ")";
}
return "";
}
case rawinput::PIUIO_DEVICE:
return "PIUIO " + vKeyString;
case rawinput::DESTROYED:
return "Device unplugged (" + vKeyString + ")";
default:
return "Unknown device type (" + vKeyString + ")";
}
}
}
void Button::getMidiVKey(int& channel, int& index) {
switch (this->analog_type) {
// update strings in analog.cpp as well
case BAT_NONE:
channel = (vKey / 0x80) + 1;
index = vKey & 0x7f;
break;
case BAT_MIDI_CTRL_PRECISION:
channel = (vKey / 32) + 1;
index = (vKey % 32);
break;
case BAT_MIDI_CTRL_SINGLE:
channel = (vKey / 44) + 1;
index = (vKey % 44);
if (index <= 25) {
index += 0x46; // single byte range
} else {
index = index - 26 + 0x66; // undefined single byte range
}
break;
case BAT_MIDI_CTRL_ONOFF:
channel = (vKey / 6) + 1;
index = (vKey % 6) + 0x40;
break;
case BAT_MIDI_PITCH_DOWN:
case BAT_MIDI_PITCH_UP:
channel = vKey + 1;
index = 0;
break;
default:
channel = 0;
index = 0;
break;
}
}
void Button::setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index) {
int vKey = 0;
if (is_note) {
vKey = (channel - 1) * 0x80 + index;
this->setVKey(vKey);
this->setAnalogType(BAT_NONE);
// ensure that velocity threshold is read back from what rawinput has for other bindings
if (manager && !this->device_identifier.empty()) {
auto device = manager->devices_get(this->device_identifier);
if (device &&
device->midiInfo &&
(size_t)vKey < device->midiInfo->v2_velocity_threshold.size()) {
this->setVelocityThreshold(device->midiInfo->v2_velocity_threshold[vKey]);
}
}
return;
}
if (channel < 1 || 16 < channel) {
this->setVKey(0);
this->setAnalogType(BAT_NONE);
return;
}
if (index < 0 || 127 < index) {
this->setVKey(0);
this->setAnalogType(BAT_NONE);
return;
}
// continuous controller MSB
if (0x00 <= index && index <= 0x1F) {
vKey = (channel - 1) * 32 + index;
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_PRECISION);
return;
}
// continuous controller LSB
if (0x20 <= index && index <= 0x3F) {
vKey = (channel - 1) * 32 + index - 0x20;
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_PRECISION);
return;
}
// on/off controls
if (0x40 <= index && index <= 0x45) {
vKey = (channel - 1) * 6 + (index - 0x40);
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_ONOFF);
return;
}
// single byte controllers
if (0x46 <= index && index <= 0x5F) {
vKey = (channel - 1) * 44;
vKey += index - 0x46; // single byte range
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_SINGLE);
return;
}
// undefined single byte controllers
if (0x66 <= index && index <= 0x77) {
vKey = (channel - 1) * 44;
vKey += index - 0x66 + (0x5F - 0x46 + 1) ; // undefined single byte range
this->setVKey(vKey);
this->setAnalogType(BAT_MIDI_CTRL_SINGLE);
return;
}
}
#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;
}
+190
View File
@@ -0,0 +1,190 @@
#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,
BAT_ANY = 17,
};
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;
unsigned short velocity_threshold = 0;
std::string getVKeyString();
std::string getMidiNoteString();
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;
}
inline unsigned short getVelocityThreshold() const {
return this->velocity_threshold;
}
inline void setVelocityThreshold(unsigned short velocity_threshold) {
this->velocity_threshold = velocity_threshold;
}
void getMidiVKey(int& channel, int& index);
void setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index);
/*
* 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);
};
File diff suppressed because it is too large Load Diff
+62
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();
};
+31
View File
@@ -0,0 +1,31 @@
#include "configurator.h"
#include "overlay/overlay.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;
// run window
this->wnd.run();
}
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "configurator_wnd.h"
namespace cfg {
enum class ConfigType {
Config
};
// globals
extern bool CONFIGURATOR_STANDALONE;
extern ConfigType CONFIGURATOR_TYPE;
class Configurator {
private:
ConfiguratorWindow wnd;
public:
Configurator();
~Configurator();
void run();
};
}
+173
View File
@@ -0,0 +1,173 @@
#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;
}
// 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
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
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
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
View File
@@ -0,0 +1,3 @@
#pragma once
#define MAINICON 20
Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

+3
View File
@@ -0,0 +1,3 @@
#include "icon.h"
MAINICON ICON "icon.ico"
+93
View File
@@ -0,0 +1,93 @@
#include "light.h"
#include <utility>
#include "rawinput/piuio.h"
#include "rawinput/rawinput.h"
#include "rawinput/sextet.h"
#include "rawinput/smxdedicab.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::SMX_DEDICAB: {
// get light name of SMX Dedicab device
if (index < rawinput::SmxDedicabDevice::LIGHTS_COUNT) {
return rawinput::SmxDedicabDevice::GetLightNameByIndex(index) + " (" + index_string + ")";
}
return "Invalid SMX Dedicab Light (" + index_string + ")";
}
case rawinput::DESTROYED:
return "Unplugged device (" + index_string + ")";
default:
return "Unknown Light (" + index_string + ")";
}
}
+68
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
View File
@@ -0,0 +1,4 @@
#pragma once
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
#define RT_MANIFEST 24
+29
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
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
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
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
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
+223
View File
@@ -0,0 +1,223 @@
#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() {
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
log_info("ScreenResize", "loading custom config: {}", this->config_path.string());
} else {
log_info("ScreenResize", "loading global config from APPDATA");
}
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
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
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
View File
@@ -0,0 +1,6 @@
#pragma once
#include <vector>
#include <string>
int spicecfg_run(const std::vector<std::string> &sextet_devices);