rawaccel/converter/converter.cpp
a1xd adb528c121 improve converter + docs
remove note about not supporting prescale
use relative links

on conversion
  - flip rotation sign
  - set angle snapping
  - set domain weights to prescale
2021-09-22 18:25:27 -04:00

283 lines
8.1 KiB
C++

#include <rawaccel-base.hpp>
#include <array>
#include <charconv>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>
#include <sstream>
#include <string>
using namespace System;
using namespace System::IO;
using namespace System::Runtime::InteropServices;
using namespace Newtonsoft::Json;
namespace ra = rawaccel;
namespace fs = std::filesystem;
const wchar_t* IA_SETTINGS_NAME = L"settings.txt";
const wchar_t* IA_PROFILE_EXT = L".profile";
enum IA_MODES_ENUM { IA_QL, IA_NAT, IA_LOG };
constexpr std::array<std::string_view, 3> IA_MODES = {
"QuakeLive", "Natural", "Logarithmic"
};
// trim from start (in place)
static inline void ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static inline void rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
}
// trim from both ends (in place)
static inline void trim(std::string& s) {
ltrim(s);
rtrim(s);
}
bool ask(std::string_view question) {
std::cout << question << " (Y/N)" << std::endl;
wchar_t ch;
bool yes;
do
{
ch = towupper(_getwch());
yes = ch == 'Y';
} while (ch != 'N' && !yes);
return yes;
}
using ia_settings_t = std::vector<std::pair<std::string, double>>;
ia_settings_t parse_ia_settings(const fs::path fp) {
ia_settings_t kv_pairs;
std::ifstream ifs(fp);
std::string line;
while (std::getline(ifs, line)) {
if (line.empty()) continue;
auto delim_pos = line.find('=');
if (delim_pos == std::string::npos) continue;
std::string key(line.substr(0, delim_pos));
trim(key);
auto val_pos = line.find_first_not_of(" \t", delim_pos + 1);
if (val_pos == std::string::npos) continue;
double val = 0;
auto [p, ec] = std::from_chars(&line[val_pos], &line[0] + line.size(), val);
if (ec == std::errc()) {
kv_pairs.emplace_back(key, val);
}
else if (key == "AccelMode") {
std::string mode_val = line.substr(val_pos);
rtrim(mode_val);
auto it = std::find(IA_MODES.begin(), IA_MODES.end(), mode_val);
if (it != IA_MODES.end()) {
val = static_cast<double>(std::distance(IA_MODES.begin(), it));
kv_pairs.emplace_back(key, val);
}
}
}
return kv_pairs;
}
auto make_extractor(const ia_settings_t& ia_settings) {
return [&](auto... keys) -> std::optional<double> {
auto it = std::find_if(ia_settings.begin(), ia_settings.end(), [=](auto&& p) {
return ((p.first == keys) || ...);
});
if (it == ia_settings.end()) return std::nullopt;
return it->second;
};
}
ra::accel_args convert_natural(const ia_settings_t& ia_settings, bool legacy) {
auto get = make_extractor(ia_settings);
double accel = get("Acceleration").value_or(0);
double cap = get("SensitivityCap").value_or(0);
double sens = get("Sensitivity").value_or(1);
ra::accel_args args;
args.limit = 1 + std::abs(cap - sens) / sens;
args.decay_rate = accel / sens;
args.offset = get("Offset").value_or(0);
args.mode = ra::accel_mode::natural;
args.legacy = legacy;
return args;
}
ra::accel_args convert_quake(const ia_settings_t& ia_settings, bool legacy) {
auto get = make_extractor(ia_settings);
double power = get("Power").value_or(2);
double accel = get("Acceleration").value_or(0);
double cap = get("SensitivityCap").value_or(0);
double sens = get("Sensitivity").value_or(1);
ra::accel_args args;
double accel_b = std::pow(accel, power - 1) / sens;
args.accel_classic = std::pow(accel_b, 1 / (power - 1));
args.cap = cap / sens;
args.power = power;
args.offset = get("Offset").value_or(0);
args.mode = ra::accel_mode::classic;
args.legacy = legacy;
return args;
}
bool try_convert(const ia_settings_t& ia_settings) {
auto get = make_extractor(ia_settings);
ra::settings& ra_settings = *(new ra::settings());
vec2d prescale = { get("Pre-ScaleX").value_or(1), get("Pre-ScaleY").value_or(1) };
ra_settings.dom_args.domain_weights = prescale;
ra_settings.degrees_rotation = -1 * get("Angle", "AngleAdjustment").value_or(0);
ra_settings.degrees_snap = get("AngleSnapping").value_or(0);
ra_settings.sens = {
get("Post-ScaleX").value_or(1) * prescale.x,
get("Post-ScaleY").value_or(1) * prescale.y
};
double mode = get("AccelMode").value_or(IA_QL);
switch (static_cast<IA_MODES_ENUM>(mode)) {
case IA_QL: {
ra_settings.argsv.x = convert_quake(ia_settings, 1);
break;
}
case IA_NAT: {
ra_settings.argsv.x = convert_natural(ia_settings, 1);
break;
}
case IA_LOG: {
std::cout << "Logarithmic accel mode is not supported.\n";
return true;
}
default: return false;
}
DriverSettings^ new_settings = Marshal::PtrToStructure<DriverSettings^>((IntPtr)&ra_settings);
SettingsErrors^ errors = gcnew SettingsErrors(new_settings);
if (!errors->Empty()) {
Console::WriteLine("Bad settings: {0}", errors);
return false;
}
bool nat = ra_settings.argsv.x.mode == ra::accel_mode::natural;
bool nat_or_capped = nat || ra_settings.argsv.x.cap > 0;
if (nat_or_capped) {
Console::WriteLine("NOTE:\n"
" Raw Accel features a new cap style that is preferred by many users.\n"
" To test it out, run rawaccel.exe, check the 'Gain' option, and click 'Apply'.\n");
}
if (ra_settings.argsv.x.offset > 0) {
Console::WriteLine("NOTE:\n"
" Offsets in Raw Accel work a bit differently compared to InterAccel,\n"
" the '{0}' parameter may need adjustment to compensate.\n",
nat ? "decay rate" : "acceleration");
}
Console::Write("Sending to driver... ");
(gcnew ManagedAccel(gcnew ExtendedSettings(new_settings)))->Activate();
Console::WriteLine("done");
Console::Write("Generating settings.json... ");
File::WriteAllText("settings.json", RaConvert::Settings(new_settings));
Console::WriteLine("done");
return true;
}
int main(int argc, char** argv)
{
auto close_prompt = [] {
std::cout << "Press any key to close this window . . ." << std::endl;
_getwch();
std::exit(0);
};
auto convert_or_print_error = [](auto&& path) {
try {
if (!try_convert(parse_ia_settings(path)))
std::cout << "Unable to convert settings.\n";
}
catch (Exception^ e) {
Console::WriteLine("\nError: {0}", e);
}
catch (const std::exception& e) {
std::cout << "Error: " << e.what() << '\n';
}
};
try {
VersionHelper::ValidOrThrow();
}
catch (InteropException^ ex) {
Console::WriteLine(ex->Message);
close_prompt();
}
if (argc == 2 && fs::exists(argv[1])) {
convert_or_print_error(argv[1]);
}
else {
std::optional<fs::path> opt_path;
if (fs::exists(IA_SETTINGS_NAME)) {
opt_path = IA_SETTINGS_NAME;
}
else {
for (auto&& entry : fs::directory_iterator(".")) {
if (fs::is_regular_file(entry) &&
entry.path().extension() == IA_PROFILE_EXT) {
opt_path = entry;
break;
}
}
}
if (opt_path) {
std::string path = opt_path->filename().generic_string();
std::stringstream ss;
ss << "Found " << path <<
"\n\nConvert and send settings generated from " << path << '?';
if (ask(ss.str())) {
convert_or_print_error(opt_path.value());
}
}
else {
std::cout << "Drop your InterAccel settings/profile into this directory.\n"
"Then run this program to generate the equivalent Raw Accel settings.\n";
}
}
close_prompt();
}