mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e6b4c16cc | |||
| d6eb8f7125 | |||
| dc6850e479 | |||
| 2d623e179b | |||
| 884d665c1c | |||
| 2842fe7ec6 | |||
| 954e6022d9 | |||
| 955c50a9f3 |
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
> Feel free to remove this section after you have read it.
|
> Feel free to remove this section after you have read it.
|
||||||
|
|
||||||
## Link to GitHub Issue, if one exists
|
## Link to GitHub Issue or related Pull Request, if one exists
|
||||||
#0
|
#0
|
||||||
|
|
||||||
## Description of change
|
## Description of change
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace api::modules {
|
|||||||
void DRS::touch_set(Request &req, Response &res) {
|
void DRS::touch_set(Request &req, Response &res) {
|
||||||
|
|
||||||
// get all touch points
|
// get all touch points
|
||||||
games::drs::drs_touch_t touches[16];
|
auto touches = std::make_unique<games::drs::drs_touch_t[]>(req.params.Size());
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (Value ¶m : req.params.GetArray()) {
|
for (Value ¶m : req.params.GetArray()) {
|
||||||
|
|
||||||
@@ -86,6 +86,6 @@ namespace api::modules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// apply touch points
|
// apply touch points
|
||||||
games::drs::fire_touches(touches, i);
|
games::drs::fire_touches(touches.get(), i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ enum class OptionType {
|
|||||||
Hex,
|
Hex,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class OptionPickerType {
|
||||||
|
None,
|
||||||
|
AsioDriver,
|
||||||
|
EACard,
|
||||||
|
CpuAffinity,
|
||||||
|
FilePath,
|
||||||
|
DirectoryPath,
|
||||||
|
};
|
||||||
|
|
||||||
struct OptionDefinition {
|
struct OptionDefinition {
|
||||||
std::string title;
|
std::string title;
|
||||||
// unique identifier used for flag matching but also stored in config files
|
// unique identifier used for flag matching but also stored in config files
|
||||||
@@ -32,6 +41,10 @@ struct OptionDefinition {
|
|||||||
bool sensitive = false;
|
bool sensitive = false;
|
||||||
std::vector<std::pair<std::string, std::string>> elements = {};
|
std::vector<std::pair<std::string, std::string>> elements = {};
|
||||||
bool disabled = false;
|
bool disabled = false;
|
||||||
|
OptionPickerType picker = OptionPickerType::None;
|
||||||
|
|
||||||
|
// for OptionPickerType::FilePath
|
||||||
|
std::string file_extension = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
class Option {
|
class Option {
|
||||||
|
|||||||
@@ -21,8 +21,9 @@
|
|||||||
#include "util/socd_cleaner.h"
|
#include "util/socd_cleaner.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "misc/wintouchemu.h"
|
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
|
#include "misc/nativetouchhook.h"
|
||||||
|
#include "misc/wintouchemu.h"
|
||||||
#include "bi2x_hook.h"
|
#include "bi2x_hook.h"
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
@@ -416,9 +417,11 @@ namespace games::sdvx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_valkyrie_model()) {
|
if (is_valkyrie_model()) {
|
||||||
// hook touch window
|
if (NATIVETOUCH) {
|
||||||
// in windowed mode, game can accept mouse input on the second screen
|
nativetouchhook::hook(avs::game::DLL_INSTANCE);
|
||||||
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
} else if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
||||||
|
// hook touch window
|
||||||
|
// in windowed mode, game can accept mouse input on the second screen
|
||||||
wintouchemu::FORCE = true;
|
wintouchemu::FORCE = true;
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
wintouchemu::hook_title_ends(
|
wintouchemu::hook_title_ends(
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ namespace hooks::audio {
|
|||||||
bool USE_DUMMY = false;
|
bool USE_DUMMY = false;
|
||||||
WAVEFORMATEXTENSIBLE FORMAT {};
|
WAVEFORMATEXTENSIBLE FORMAT {};
|
||||||
std::optional<Backend> BACKEND = std::nullopt;
|
std::optional<Backend> BACKEND = std::nullopt;
|
||||||
size_t ASIO_DRIVER_ID = 0;
|
std::optional<size_t> ASIO_DRIVER_ID = std::nullopt;
|
||||||
|
std::string ASIO_DRIVER_NAME = "";
|
||||||
bool ASIO_FORCE_UNLOAD_ON_STOP = false;
|
bool ASIO_FORCE_UNLOAD_ON_STOP = false;
|
||||||
|
|
||||||
// private globals
|
// private globals
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <mmreg.h>
|
#include <mmreg.h>
|
||||||
@@ -20,7 +21,8 @@ namespace hooks::audio {
|
|||||||
extern bool USE_DUMMY;
|
extern bool USE_DUMMY;
|
||||||
extern WAVEFORMATEXTENSIBLE FORMAT;
|
extern WAVEFORMATEXTENSIBLE FORMAT;
|
||||||
extern std::optional<Backend> BACKEND;
|
extern std::optional<Backend> BACKEND;
|
||||||
extern size_t ASIO_DRIVER_ID;
|
extern std::optional<size_t> ASIO_DRIVER_ID;
|
||||||
|
extern std::string ASIO_DRIVER_NAME;
|
||||||
extern bool ASIO_FORCE_UNLOAD_ON_STOP;
|
extern bool ASIO_FORCE_UNLOAD_ON_STOP;
|
||||||
extern bool LOW_LATENCY_SHARED_WASAPI;
|
extern bool LOW_LATENCY_SHARED_WASAPI;
|
||||||
|
|
||||||
|
|||||||
@@ -218,13 +218,40 @@ void AsioBackend::set_thread_state(AsioThreadState state) {
|
|||||||
bool AsioBackend::load_driver() {
|
bool AsioBackend::load_driver() {
|
||||||
AsioDriverList asio_driver_list;
|
AsioDriverList asio_driver_list;
|
||||||
|
|
||||||
for (const auto &driver : asio_driver_list.driver_list) {
|
size_t driver_id = 0;
|
||||||
log_info("audio::asio", "Driver {}", driver.id);
|
std::string driver_name = "";
|
||||||
log_info("audio::asio", "... Name : {}", driver.name);
|
if (hooks::audio::ASIO_DRIVER_ID.has_value()) {
|
||||||
log_info("audio::asio", "... Path : {}", driver.dll_path);
|
driver_id = hooks::audio::ASIO_DRIVER_ID.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto driver_id = hooks::audio::ASIO_DRIVER_ID;
|
for (const auto &driver : asio_driver_list.driver_list) {
|
||||||
|
log_info("audio::asio", " Driver ID : {}", driver.id);
|
||||||
|
log_info("audio::asio", " ... Name : {}", driver.name);
|
||||||
|
log_info("audio::asio", " ... Path : {}", driver.dll_path);
|
||||||
|
|
||||||
|
if (!hooks::audio::ASIO_DRIVER_NAME.empty()) {
|
||||||
|
// match on driver name
|
||||||
|
if (driver.name == hooks::audio::ASIO_DRIVER_NAME) {
|
||||||
|
driver_id = driver.id;
|
||||||
|
driver_name = driver.name;
|
||||||
|
}
|
||||||
|
} else if (driver.id == driver_id) {
|
||||||
|
// match on driver index (0 for default or user-specified)
|
||||||
|
driver_name = driver.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driver_name.empty()) {
|
||||||
|
if (!hooks::audio::ASIO_DRIVER_NAME.empty()) {
|
||||||
|
log_warning(
|
||||||
|
"audio::asio",
|
||||||
|
"tried to look for ASIO driver but failed: {}", hooks::audio::ASIO_DRIVER_NAME);
|
||||||
|
}
|
||||||
|
log_fatal(
|
||||||
|
"audio::asio",
|
||||||
|
"could not find ASIO driver to use for conversion, fix your audio options and try again");
|
||||||
|
}
|
||||||
|
log_info("audio::asio", "will attempt to use the following ASIO driver: ID = {}, Name = {}", driver_id, driver_name);
|
||||||
|
|
||||||
IAsio *driver = nullptr;
|
IAsio *driver = nullptr;
|
||||||
auto ret = asio_driver_list.open_driver(driver_id, reinterpret_cast<void **>(&driver));
|
auto ret = asio_driver_list.open_driver(driver_id, reinterpret_cast<void **>(&driver));
|
||||||
|
|||||||
@@ -1011,18 +1011,26 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
if (options[launcher::Options::spice2x_DisableVolumeHook].value_bool()) {
|
if (options[launcher::Options::spice2x_DisableVolumeHook].value_bool()) {
|
||||||
hooks::audio::VOLUME_HOOK_ENABLED = false;
|
hooks::audio::VOLUME_HOOK_ENABLED = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options[launcher::Options::AudioBackend].is_active()) {
|
if (options[launcher::Options::AudioBackend].is_active()) {
|
||||||
auto &name = options[launcher::Options::AudioBackend].value_text();
|
auto &name = options[launcher::Options::AudioBackend].value_text();
|
||||||
auto backend = hooks::audio::name_to_backend(name.c_str());
|
auto backend = hooks::audio::name_to_backend(name.c_str());
|
||||||
if (!backend.has_value() && !cfg::CONFIGURATOR_STANDALONE) {
|
if (!backend.has_value() && !cfg::CONFIGURATOR_STANDALONE) {
|
||||||
log_fatal("launcher", "invalid audio backend: {}", name);
|
log_fatal("launcher", "invalid audio backend: {}", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
hooks::audio::BACKEND = backend;
|
hooks::audio::BACKEND = backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options[launcher::Options::AsioDriverId].is_active()) {
|
if (options[launcher::Options::AsioDriverId].is_active()) {
|
||||||
hooks::audio::ASIO_DRIVER_ID = options[launcher::Options::AsioDriverId].value_uint32();
|
hooks::audio::ASIO_DRIVER_ID = options[launcher::Options::AsioDriverId].value_uint32();
|
||||||
}
|
}
|
||||||
|
if (options[launcher::Options::AsioDriverName].is_active()) {
|
||||||
|
hooks::audio::BACKEND = hooks::audio::name_to_backend("asio");
|
||||||
|
hooks::audio::ASIO_DRIVER_NAME = options[launcher::Options::AsioDriverName].value_text();
|
||||||
|
// this new option overrides the old one
|
||||||
|
hooks::audio::ASIO_DRIVER_ID.reset();
|
||||||
|
}
|
||||||
|
|
||||||
if (options[launcher::Options::AudioDummy].value_bool()) {
|
if (options[launcher::Options::AudioDummy].value_bool()) {
|
||||||
hooks::audio::USE_DUMMY = true;
|
hooks::audio::USE_DUMMY = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.setting_name = "*.dll",
|
.setting_name = "*.dll",
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
// intentionally not setting a file picker here to discourage people setting this without a good reason
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Open Configurator",
|
.title = "Open Configurator",
|
||||||
@@ -121,6 +122,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.setting_name = "E004010000000000",
|
.setting_name = "E004010000000000",
|
||||||
.category = "Network",
|
.category = "Network",
|
||||||
.sensitive = true,
|
.sensitive = true,
|
||||||
|
.picker = OptionPickerType::EACard,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Player 2 Card",
|
.title = "Player 2 Card",
|
||||||
@@ -130,6 +132,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.setting_name = "E004010000000000",
|
.setting_name = "E004010000000000",
|
||||||
.category = "Network",
|
.category = "Network",
|
||||||
.sensitive = true,
|
.sensitive = true,
|
||||||
|
.picker = OptionPickerType::EACard,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Player1PinMacro
|
// Player1PinMacro
|
||||||
@@ -618,6 +621,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.game_name = "Beatmania IIDX",
|
.game_name = "Beatmania IIDX",
|
||||||
.category = "Game Options",
|
.category = "Game Options",
|
||||||
|
.picker = OptionPickerType::AsioDriver,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "IIDX BIO2 Firmware Update",
|
.title = "IIDX BIO2 Firmware Update",
|
||||||
@@ -778,6 +782,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.game_name = "Sound Voltex",
|
.game_name = "Sound Voltex",
|
||||||
.category = "Game Options (Advanced)",
|
.category = "Game Options (Advanced)",
|
||||||
|
.picker = OptionPickerType::DirectoryPath,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "SDVX Printer Output Clear",
|
.title = "SDVX Printer Output Clear",
|
||||||
@@ -884,11 +889,12 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.display_name = "sdvxasio",
|
.display_name = "sdvxasio",
|
||||||
.aliases= "sdvxasio",
|
.aliases= "sdvxasio",
|
||||||
.desc = "ASIO driver name to use, replacing XONAR SOUND CARD(64). "
|
.desc = "ASIO driver name to use, replacing XONAR SOUND CARD(64). "
|
||||||
"String should match registry key under HLKM\\SOFTWARE\\ASIO\\ \n\n"
|
"String should match registry key under HKLM\\SOFTWARE\\ASIO\\ \n\n"
|
||||||
"SDVX is EXTREMELY picky about ASIO devices it can support!",
|
"SDVX is EXTREMELY picky about ASIO devices it can support!",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.game_name = "Sound Voltex",
|
.game_name = "Sound Voltex",
|
||||||
.category = "Game Options",
|
.category = "Game Options",
|
||||||
|
.picker = OptionPickerType::AsioDriver,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// spice2x_SDVXSubPos
|
// spice2x_SDVXSubPos
|
||||||
@@ -1284,6 +1290,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.desc = "Sets a custom path to the modules folder.",
|
.desc = "Sets a custom path to the modules folder.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
// intentionally not setting a folder picker here to discourage people setting this without a good reason
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Screenshot Folder Override",
|
.title = "Screenshot Folder Override",
|
||||||
@@ -1291,6 +1298,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.desc = "Sets a custom path to the screenshots folder.",
|
.desc = "Sets a custom path to the screenshots folder.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
.picker = OptionPickerType::DirectoryPath,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Configuration Path Override",
|
.title = "Configuration Path Override",
|
||||||
@@ -1310,6 +1318,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
"If left empty, %appdata%\\spice2x\\spicetools_screen_resize.json will be used.",
|
"If left empty, %appdata%\\spice2x\\spicetools_screen_resize.json will be used.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
.picker = OptionPickerType::FilePath,
|
||||||
|
.file_extension = "JSON",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// PatchManagerConfigPath
|
// PatchManagerConfigPath
|
||||||
@@ -1319,6 +1329,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
"If left empty, %appdata%\\spice2x\\spicetools_patch_manager.json will be used.",
|
"If left empty, %appdata%\\spice2x\\spicetools_patch_manager.json will be used.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
.picker = OptionPickerType::FilePath,
|
||||||
|
.file_extension = "JSON",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Intel SDE",
|
.title = "Intel SDE",
|
||||||
@@ -1326,6 +1338,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.desc = "Path to Intel SDE kit path for automatic attaching.",
|
.desc = "Path to Intel SDE kit path for automatic attaching.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Development",
|
.category = "Development",
|
||||||
|
.picker = OptionPickerType::DirectoryPath
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "ea3-config.xml Override",
|
.title = "ea3-config.xml Override",
|
||||||
@@ -1333,6 +1346,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.desc = "Sets a custom path to ea3-config.xml.",
|
.desc = "Sets a custom path to ea3-config.xml.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
.picker = OptionPickerType::FilePath,
|
||||||
|
.file_extension = "XML",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "app-config.xml Override",
|
.title = "app-config.xml Override",
|
||||||
@@ -1340,6 +1355,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.desc = "Sets a custom path to app-config.xml.",
|
.desc = "Sets a custom path to app-config.xml.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
.picker = OptionPickerType::FilePath,
|
||||||
|
.file_extension = "XML",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "avs-config.xml Override",
|
.title = "avs-config.xml Override",
|
||||||
@@ -1347,6 +1364,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.desc = "Sets a custom path to avs-config.xml.",
|
.desc = "Sets a custom path to avs-config.xml.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
.picker = OptionPickerType::FilePath,
|
||||||
|
.file_extension = "XML",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "bootstrap.xml Override",
|
.title = "bootstrap.xml Override",
|
||||||
@@ -1354,6 +1373,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.desc = "Sets a custom path to bootstrap.xml.",
|
.desc = "Sets a custom path to bootstrap.xml.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
.picker = OptionPickerType::FilePath,
|
||||||
|
.file_extension = "XML",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "log.txt Override",
|
.title = "log.txt Override",
|
||||||
@@ -1361,6 +1382,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.desc = "Sets a custom path to log.txt.",
|
.desc = "Sets a custom path to log.txt.",
|
||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.category = "Path Overrides",
|
.category = "Path Overrides",
|
||||||
|
.picker = OptionPickerType::FilePath,
|
||||||
|
.file_extension = "TXT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "API TCP Port",
|
.title = "API TCP Port",
|
||||||
@@ -1670,6 +1693,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
"Must provide a hexadecimal mask (e.g., 0x1ff00).",
|
"Must provide a hexadecimal mask (e.g., 0x1ff00).",
|
||||||
.type = OptionType::Hex,
|
.type = OptionType::Hex,
|
||||||
.category = "Performance",
|
.category = "Performance",
|
||||||
|
.picker = OptionPickerType::CpuAffinity,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// spice2x_ProcessorEfficiencyClass
|
// spice2x_ProcessorEfficiencyClass
|
||||||
@@ -1742,11 +1766,13 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.category = "Audio (Hacks)",
|
.category = "Audio (Hacks)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Spice Audio Hook Backend",
|
// AudioBackend
|
||||||
|
.title = "Spice Audio Hook Backend (DEPRECATED - use -asioconvert instead)",
|
||||||
.name = "audiobackend",
|
.name = "audiobackend",
|
||||||
.desc = "Selects the audio backend to use when spice audio hook is enabled, overriding exclusive WASAPI. "
|
.desc = "Selects the audio backend to use when spice audio hook is enabled, overriding exclusive WASAPI. "
|
||||||
"Does nothing for games that do not output to exclusive WASAPI.",
|
"Does nothing for games that do not output to exclusive WASAPI.",
|
||||||
.type = OptionType::Enum,
|
.type = OptionType::Enum,
|
||||||
|
.hidden = true,
|
||||||
.category = "Audio",
|
.category = "Audio",
|
||||||
.elements = {
|
.elements = {
|
||||||
{"asio", "ASIO"},
|
{"asio", "ASIO"},
|
||||||
@@ -1754,12 +1780,26 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "Spice Audio Hook ASIO Driver ID",
|
// AsioDriverId
|
||||||
|
.title = "Spice Audio Hook ASIO Driver ID (DEPRECATED - use -asioconvert instead)",
|
||||||
.name = "asiodriverid",
|
.name = "asiodriverid",
|
||||||
.desc = "Selects the ASIO driver id to use when Spice Audio Backend is set to ASIO.",
|
.desc = "Selects the ASIO driver id to use when Spice Audio Backend is set to ASIO.",
|
||||||
.type = OptionType::Integer,
|
.type = OptionType::Integer,
|
||||||
|
.hidden = true,
|
||||||
.category = "Audio",
|
.category = "Audio",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// AsioDriverName
|
||||||
|
.title = "WASAPI Exclusive to ASIO Conversion",
|
||||||
|
.name = "asioconvert",
|
||||||
|
.desc = "Converts WASAPI Exclusive audio output to ASIO. Value here should match registry key under HKLM\\SOFTWARE\\ASIO\\\n\n"
|
||||||
|
"Use this if the game is configured to use WASAPI Exclusive but you want to use your ASIO driver instead.\n\n"
|
||||||
|
"This should only be used as last resort if your audio device does not support WASAPI Exclusive.\n\n"
|
||||||
|
"Does nothing for games that do not output to exclusive WASAPI.",
|
||||||
|
.type = OptionType::Text,
|
||||||
|
.category = "Audio",
|
||||||
|
.picker = OptionPickerType::AsioDriver,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.title = "WASAPI Dummy Context",
|
.title = "WASAPI Dummy Context",
|
||||||
.name = "audiodummy",
|
.name = "audiodummy",
|
||||||
@@ -2592,6 +2632,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
.type = OptionType::Text,
|
.type = OptionType::Text,
|
||||||
.game_name = "LovePlus",
|
.game_name = "LovePlus",
|
||||||
.category = "Game Options (Advanced)",
|
.category = "Game Options (Advanced)",
|
||||||
|
.picker = OptionPickerType::DirectoryPath,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.title = "LovePlus Printer Output Clear",
|
.title = "LovePlus Printer Output Clear",
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ namespace launcher {
|
|||||||
spice2x_DisableVolumeHook,
|
spice2x_DisableVolumeHook,
|
||||||
AudioBackend,
|
AudioBackend,
|
||||||
AsioDriverId,
|
AsioDriverId,
|
||||||
|
AsioDriverName,
|
||||||
AudioDummy,
|
AudioDummy,
|
||||||
DelayBy5Seconds,
|
DelayBy5Seconds,
|
||||||
spice2x_DelayByNSeconds,
|
spice2x_DelayByNSeconds,
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
// mingw otherwise doesn't load touch stuff
|
// mingw otherwise doesn't load touch stuff
|
||||||
#define _WIN32_WINNT 0x0601
|
#define _WIN32_WINNT 0x0601
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
#include "wintouchemu.h"
|
#include "wintouchemu.h"
|
||||||
|
#include "rawinput/touch.h"
|
||||||
|
#include "hooks/graphics/graphics.h"
|
||||||
|
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
@@ -63,6 +66,11 @@ namespace nativetouchhook {
|
|||||||
point->cyContact = 0;
|
point->cyContact = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void flip_touch_points(PTOUCHINPUT point) {
|
||||||
|
point->x = rawinput::touch::DISPLAY_SIZE_X * 100 - point->x;
|
||||||
|
point->y = rawinput::touch::DISPLAY_SIZE_Y * 100 - point->y;
|
||||||
|
}
|
||||||
|
|
||||||
static BOOL WINAPI GetTouchInputInfoHook(
|
static BOOL WINAPI GetTouchInputInfoHook(
|
||||||
HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
|
HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
|
||||||
|
|
||||||
@@ -72,9 +80,28 @@ namespace nativetouchhook {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool flip_values = false;
|
||||||
|
if (avs::game::is_model("KFC") && rawinput::touch::DISPLAY_INITIALIZED) {
|
||||||
|
log_debug(
|
||||||
|
"touch::native", "DISPLAY_ORIENTATION = {}, DISPLAY_SIZE_X = {}, DISPLAY_SIZE_Y = {}",
|
||||||
|
rawinput::touch::DISPLAY_ORIENTATION,
|
||||||
|
rawinput::touch::DISPLAY_SIZE_X,
|
||||||
|
rawinput::touch::DISPLAY_SIZE_Y);
|
||||||
|
if (rawinput::touch::DISPLAY_ORIENTATION == DMDO_270) {
|
||||||
|
flip_values = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < cInputs; i++) {
|
for (size_t i = 0; i < cInputs; i++) {
|
||||||
PTOUCHINPUT point = &pInputs[i];
|
PTOUCHINPUT point = &pInputs[i];
|
||||||
strip_contact_size(point);
|
|
||||||
|
if (avs::game::is_model("LDJ")) {
|
||||||
|
strip_contact_size(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flip_values) {
|
||||||
|
flip_touch_points(point);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
#include "rawinput/touch.h"
|
||||||
|
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
|
|
||||||
@@ -126,6 +127,11 @@ namespace wintouchemu {
|
|||||||
WINDOW_TITLE_END = window_title_end;
|
WINDOW_TITLE_END = window_title_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void flip_touch_points(PTOUCHINPUT point) {
|
||||||
|
point->x = rawinput::touch::DISPLAY_SIZE_X * 100 - point->x;
|
||||||
|
point->y = rawinput::touch::DISPLAY_SIZE_Y * 100 - point->y;
|
||||||
|
}
|
||||||
|
|
||||||
static BOOL WINAPI GetTouchInputInfoHook(HANDLE hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
|
static BOOL WINAPI GetTouchInputInfoHook(HANDLE hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
|
||||||
|
|
||||||
// check if original should be called
|
// check if original should be called
|
||||||
@@ -220,6 +226,12 @@ namespace wintouchemu {
|
|||||||
touch_input->cxContact = 0;
|
touch_input->cxContact = 0;
|
||||||
touch_input->cyContact = 0;
|
touch_input->cyContact = 0;
|
||||||
|
|
||||||
|
if (avs::game::is_model("KFC") &&
|
||||||
|
rawinput::touch::DISPLAY_INITIALIZED &&
|
||||||
|
rawinput::touch::DISPLAY_ORIENTATION == DMDO_270) {
|
||||||
|
flip_touch_points(touch_input);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (USE_MOUSE && !mouse_used) {
|
} else if (USE_MOUSE && !mouse_used) {
|
||||||
|
|
||||||
// disable further mouse inputs this call
|
// disable further mouse inputs this call
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ namespace ImGui {
|
|||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::HelpTooltip(tooltip.c_str());
|
ImGui::HelpTooltip(tooltip.c_str());
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
@@ -159,7 +158,6 @@ namespace ImGui {
|
|||||||
bool clicked = ImGui::Button("\u00D7"); // multiplication sign (×)
|
bool clicked = ImGui::Button("\u00D7"); // multiplication sign (×)
|
||||||
ImGui::PopStyleColor(4);
|
ImGui::PopStyleColor(4);
|
||||||
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::HelpTooltip(tooltip.c_str());
|
ImGui::HelpTooltip(tooltip.c_str());
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ void ImGui_ImplSpice_NewFrame() {
|
|||||||
memset(io.MouseDown, false, sizeof(io.MouseDown));
|
memset(io.MouseDown, false, sizeof(io.MouseDown));
|
||||||
|
|
||||||
// early quit if window not in focus
|
// early quit if window not in focus
|
||||||
if (!superexit::has_focus()) {
|
if (!superexit::has_focus() || rawinput::OS_WINDOW_ACTIVE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,16 @@
|
|||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
#include <shlobj.h>
|
||||||
#include <commdlg.h>
|
#include <commdlg.h>
|
||||||
|
|
||||||
#include "build/defs.h"
|
#include "build/defs.h"
|
||||||
#include "build/resource.h"
|
#include "build/resource.h"
|
||||||
#include "cfg/config.h"
|
#include "cfg/config.h"
|
||||||
#include "cfg/configurator.h"
|
#include "cfg/configurator.h"
|
||||||
|
#include "external/asio/asiolist.h"
|
||||||
#include "external/imgui/imgui_internal.h"
|
#include "external/imgui/imgui_internal.h"
|
||||||
|
#include "external/imgui/misc/cpp/imgui_stdlib.h"
|
||||||
#include "games/io.h"
|
#include "games/io.h"
|
||||||
#include "avs/core.h"
|
#include "avs/core.h"
|
||||||
#include "avs/ea3.h"
|
#include "avs/ea3.h"
|
||||||
@@ -26,6 +29,7 @@
|
|||||||
#include "util/fileutils.h"
|
#include "util/fileutils.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/resutils.h"
|
#include "util/resutils.h"
|
||||||
|
#include "util/scope_guard.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
@@ -33,6 +37,17 @@
|
|||||||
#undef min
|
#undef min
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static int CALLBACK BrowseCallbackProc(
|
||||||
|
HWND hwnd,
|
||||||
|
UINT uMsg,
|
||||||
|
LPARAM lParam,
|
||||||
|
LPARAM lpData) {
|
||||||
|
if (uMsg == BFFM_INITIALIZED) {
|
||||||
|
SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, lpData);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
namespace overlay::windows {
|
namespace overlay::windows {
|
||||||
|
|
||||||
// same width as dummy marker
|
// same width as dummy marker
|
||||||
@@ -40,6 +55,8 @@ namespace overlay::windows {
|
|||||||
const auto PROJECT_URL = "https://spice2x.github.io";
|
const auto PROJECT_URL = "https://spice2x.github.io";
|
||||||
|
|
||||||
constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f);
|
constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f);
|
||||||
|
|
||||||
|
std::unique_ptr<AsioDriverList> asio_driver_list;
|
||||||
|
|
||||||
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
|
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
|
||||||
this->title = "Configuration";
|
this->title = "Configuration";
|
||||||
@@ -2707,17 +2724,17 @@ namespace overlay::windows {
|
|||||||
|
|
||||||
// open dialog to get path
|
// open dialog to get path
|
||||||
auto ofn_path = std::make_unique<wchar_t[]>(512);
|
auto ofn_path = std::make_unique<wchar_t[]>(512);
|
||||||
|
ofn_path[0] = L'\0';
|
||||||
OPENFILENAMEW ofn {};
|
OPENFILENAMEW ofn {};
|
||||||
memset(&ofn, 0, sizeof(ofn));
|
|
||||||
ofn.lStructSize = sizeof(ofn);
|
ofn.lStructSize = sizeof(ofn);
|
||||||
ofn.hwndOwner = nullptr;
|
|
||||||
ofn.lpstrFilter = L"";
|
|
||||||
ofn.lpstrFile = ofn_path.get();
|
ofn.lpstrFile = ofn_path.get();
|
||||||
ofn.nMaxFile = 512;
|
ofn.nMaxFile = 512;
|
||||||
ofn.Flags = OFN_EXPLORER;
|
ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR;
|
||||||
ofn.lpstrDefExt = L"txt";
|
ofn.lpstrDefExt = L"txt";
|
||||||
|
ofn.lpstrInitialDir = L".";
|
||||||
|
|
||||||
// check for success
|
// check for success
|
||||||
|
auto guard = rawinput::set_os_window_focus_guard();
|
||||||
if (GetSaveFileNameW(&ofn)) {
|
if (GetSaveFileNameW(&ofn)) {
|
||||||
|
|
||||||
// update card path
|
// update card path
|
||||||
@@ -2905,6 +2922,255 @@ namespace overlay::windows {
|
|||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Config::build_option_value_picker_title(const OptionDefinition& definition) {
|
||||||
|
// need to make these all unique since they are also used as ID
|
||||||
|
// if not unique, append ## per ImGui rules to create unique ones
|
||||||
|
switch (definition.picker) {
|
||||||
|
case OptionPickerType::AsioDriver:
|
||||||
|
return "ASIO Driver Picker";
|
||||||
|
case OptionPickerType::EACard:
|
||||||
|
return "EA Card Picker";
|
||||||
|
case OptionPickerType::CpuAffinity:
|
||||||
|
return "CPU Affinity Picker";
|
||||||
|
case OptionPickerType::FilePath:
|
||||||
|
return "File Picker";
|
||||||
|
case OptionPickerType::DirectoryPath:
|
||||||
|
return "Folder Picker";
|
||||||
|
default:
|
||||||
|
return "Unknown Picker";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::build_option_value_picker(Option& option) {
|
||||||
|
auto &definition = option.get_definition();
|
||||||
|
if (definition.picker == OptionPickerType::AsioDriver) {
|
||||||
|
if (asio_driver_list == nullptr) {
|
||||||
|
asio_driver_list = std::make_unique<AsioDriverList>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextUnformatted("If your ASIO driver is not shown here, close this");
|
||||||
|
ImGui::TextUnformatted("popup and enter the driver name manually.");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::HelpMarker(
|
||||||
|
"This list is populated by scanning the registry for ASIO drivers.\n\n"
|
||||||
|
"If your driver is not showing up, it may be because it is not properly registered in the system.\n\n"
|
||||||
|
"For 64-bit games, check in HKLM\\SOFTWARE\\ASIO\\.\n\n"
|
||||||
|
"For 32-bit games on 64-bit Windows, check in HKLM\\SOFTWARE\\WOW6432Node\\ASIO\\.\n\n"
|
||||||
|
"spicecfg runs in 32-bit, so it may not see 64-bit-only drivers.");
|
||||||
|
|
||||||
|
ImGui::TextUnformatted("");
|
||||||
|
if (asio_driver_list->driver_list.empty()) {
|
||||||
|
ImGui::TextUnformatted("No ASIO drivers found.");
|
||||||
|
} else {
|
||||||
|
ImGui::TextUnformatted("Pick from ASIO drivers:");
|
||||||
|
ImGui::SetNextItemWidth(300.f);
|
||||||
|
if (ImGui::BeginListBox("##asiodrivers")) {
|
||||||
|
for (const auto &driver : asio_driver_list->driver_list) {
|
||||||
|
const bool is_selected = option.value == std::string(driver.name);
|
||||||
|
if (ImGui::Selectable(fmt::format("[{}] {}", driver.id, driver.name).c_str(), is_selected)) {
|
||||||
|
option.value = driver.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndListBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (definition.picker == OptionPickerType::EACard) {
|
||||||
|
ImGui::TextUnformatted("Generate a new card number:");
|
||||||
|
if (ImGui::Button("Generate")) {
|
||||||
|
char new_card[17];
|
||||||
|
generate_ea_card(new_card);
|
||||||
|
option.value = new_card;
|
||||||
|
}
|
||||||
|
} else if (definition.picker == OptionPickerType::CpuAffinity) {
|
||||||
|
ImGui::TextUnformatted("Requires restart! Showing all procs in Group 0.");
|
||||||
|
ImGui::TextUnformatted("");
|
||||||
|
ImGui::TextUnformatted("Pick CPU cores to use:");
|
||||||
|
const uint64_t cpu_count = GetActiveProcessorCount(0);
|
||||||
|
uint64_t affinity = 0;
|
||||||
|
if (!option.value.empty()) {
|
||||||
|
try {
|
||||||
|
affinity = std::stoull(option.value, nullptr, 16);
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
option.value = "";
|
||||||
|
affinity = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::BeginChild(
|
||||||
|
"##cpuaffinity",
|
||||||
|
ImVec2(
|
||||||
|
0,
|
||||||
|
ImGui::GetFrameHeightWithSpacing() * std::clamp(cpu_count / 4, 2ull, 4ull))
|
||||||
|
);
|
||||||
|
|
||||||
|
bool selection_changed = false;
|
||||||
|
uint64_t set_bits = 0;
|
||||||
|
for (uint64_t i = 0; i < cpu_count; i++) {
|
||||||
|
if (i % 4 != 0) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool selected = (affinity & (1ULL << i)) != 0;
|
||||||
|
if (ImGui::Checkbox(fmt::format("CPU {}", i).c_str(), &selected)) {
|
||||||
|
selection_changed = true;
|
||||||
|
if (selected) {
|
||||||
|
affinity |= (1ULL << i);
|
||||||
|
} else {
|
||||||
|
affinity &= ~(1ULL << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selected) {
|
||||||
|
set_bits++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selection_changed) {
|
||||||
|
if (set_bits == 0) {
|
||||||
|
option.value = "";
|
||||||
|
} else {
|
||||||
|
option.value = fmt::format("0x{:X}", affinity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
if (option.value.empty()) {
|
||||||
|
ImGui::TextUnformatted("Using all CPUs (option default).");
|
||||||
|
} else {
|
||||||
|
ImGui::TextUnformatted("Using selected CPUs.");
|
||||||
|
}
|
||||||
|
} else if (definition.picker == OptionPickerType::FilePath) {
|
||||||
|
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
ImGui::TextUnformatted("File browser only works in spicecfg.");
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Select File...")) {
|
||||||
|
// run in separate thread otherwise we get a crash
|
||||||
|
if (!file_picker_thread) {
|
||||||
|
file_picker_done = false;
|
||||||
|
file_picker_path = "";
|
||||||
|
file_picker_thread = new std::thread([this, &definition] {
|
||||||
|
|
||||||
|
std::wstring extensions;
|
||||||
|
if (!definition.file_extension.empty()) {
|
||||||
|
const std::wstring ext = s2ws(definition.file_extension);
|
||||||
|
// filter to file extension preferred by the option (e.g., DLL)
|
||||||
|
extensions = ext + L" Files (*." + ext + L")";
|
||||||
|
extensions.push_back(L'\0');
|
||||||
|
extensions += L"*." + ext;
|
||||||
|
extensions.push_back(L'\0');
|
||||||
|
// also add "All files" filter
|
||||||
|
extensions += L"All Files (*.*)";
|
||||||
|
extensions.push_back(L'\0');
|
||||||
|
extensions += L"*.*";
|
||||||
|
extensions.push_back(L'\0');
|
||||||
|
// eol
|
||||||
|
extensions.push_back(L'\0');
|
||||||
|
} else {
|
||||||
|
extensions = L"All Files (*.*)";
|
||||||
|
extensions.push_back(L'\0');
|
||||||
|
extensions += L"*.*";
|
||||||
|
extensions.push_back(L'\0');
|
||||||
|
extensions.push_back(L'\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// open dialog to get path
|
||||||
|
auto ofn_path = std::make_unique<wchar_t[]>(512);
|
||||||
|
ofn_path[0] = L'\0';
|
||||||
|
OPENFILENAMEW ofn{};
|
||||||
|
ofn.lStructSize = sizeof(ofn);
|
||||||
|
ofn.lpstrFilter = extensions.c_str();
|
||||||
|
ofn.lpstrFile = ofn_path.get();
|
||||||
|
ofn.nMaxFile = 512;
|
||||||
|
ofn.nFilterIndex = 1;
|
||||||
|
ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR;
|
||||||
|
ofn.lpstrInitialDir = L".";
|
||||||
|
|
||||||
|
// check for success
|
||||||
|
auto guard = rawinput::set_os_window_focus_guard();
|
||||||
|
if (GetSaveFileNameW(&ofn)) {
|
||||||
|
file_picker_path = std::filesystem::path(ofn_path.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
file_picker_done = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_picker_done) {
|
||||||
|
file_picker_done = false;
|
||||||
|
file_picker_thread->join();
|
||||||
|
delete file_picker_thread;
|
||||||
|
file_picker_thread = nullptr;
|
||||||
|
if (!file_picker_path.empty()) {
|
||||||
|
option.value = file_picker_path.string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
} else if (definition.picker == OptionPickerType::DirectoryPath) {
|
||||||
|
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
ImGui::TextUnformatted("File browser only works in spicecfg.");
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Browse...")) {
|
||||||
|
// run in separate thread otherwise we get a crash
|
||||||
|
if (!file_picker_thread) {
|
||||||
|
file_picker_done = false;
|
||||||
|
file_picker_path = "";
|
||||||
|
file_picker_thread = new std::thread([this] {
|
||||||
|
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||||
|
|
||||||
|
const auto spice_bin_path =
|
||||||
|
libutils::module_file_name(nullptr).parent_path().wstring();
|
||||||
|
|
||||||
|
// SHBrowseForFolderW sucks, but the alternatives are:
|
||||||
|
// 1. Use IFileDialog, which requires pulling in more Windows dependencies
|
||||||
|
// 2. use ImGui::FileBrowser
|
||||||
|
// both are acceptable but sticking to legacy UI for now for simplicity
|
||||||
|
BROWSEINFOW info{};
|
||||||
|
info.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
|
||||||
|
info.lpfn = BrowseCallbackProc;
|
||||||
|
info.lParam = reinterpret_cast<LPARAM>(spice_bin_path.c_str());
|
||||||
|
auto guard = rawinput::set_os_window_focus_guard();
|
||||||
|
auto pidl = SHBrowseForFolderW(&info);
|
||||||
|
if (pidl) {
|
||||||
|
wchar_t path[MAX_PATH];
|
||||||
|
std::filesystem::path result;
|
||||||
|
if (SHGetPathFromIDListW(pidl, path)) {
|
||||||
|
file_picker_path = path;
|
||||||
|
}
|
||||||
|
CoTaskMemFree(pidl);
|
||||||
|
} else {
|
||||||
|
file_picker_path = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
file_picker_done = true;
|
||||||
|
CoUninitialize();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_picker_done) {
|
||||||
|
file_picker_done = false;
|
||||||
|
file_picker_thread->join();
|
||||||
|
delete file_picker_thread;
|
||||||
|
file_picker_thread = nullptr;
|
||||||
|
if (!file_picker_path.empty()) {
|
||||||
|
option.value = file_picker_path.string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ImGui::TextUnformatted("No picker available for this option. How did you get here?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Config::build_options(
|
void Config::build_options(
|
||||||
std::vector<Option> *options, const std::string &category, const std::string *filter) {
|
std::vector<Option> *options, const std::string &category, const std::string *filter) {
|
||||||
int options_count;
|
int options_count;
|
||||||
@@ -3183,12 +3449,24 @@ namespace overlay::windows {
|
|||||||
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
|
||||||
|
const std::string option_popup_id = build_option_value_picker_title(definition);
|
||||||
|
if (definition.picker != OptionPickerType::None && !option.disabled && !definition.disabled) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Pick")) {
|
||||||
|
ImGui::OpenPopup(option_popup_id.c_str());
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
|
||||||
|
ImGui::HelpTooltip(definition.desc.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing
|
||||||
|
|
||||||
// clean up disabled item flags
|
// clean up disabled item flags
|
||||||
if (option.disabled || definition.disabled) {
|
if (option.disabled || definition.disabled) {
|
||||||
ImGui::PopItemFlag();
|
ImGui::PopItemFlag(); // ImGuiItemFlags_Disabled
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar(); // ImGuiStyleVar_Alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
// disabled help
|
// disabled help
|
||||||
@@ -3199,6 +3477,44 @@ namespace overlay::windows {
|
|||||||
"Run spicecfg.exe to configure the options and then run spice(64).exe directly.");
|
"Run spicecfg.exe to configure the options and then run spice(64).exe directly.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// value picker
|
||||||
|
if (definition.picker != OptionPickerType::None &&
|
||||||
|
!option.disabled && !definition.disabled) {
|
||||||
|
if (ImGui::BeginPopupModal(option_popup_id.c_str(),
|
||||||
|
nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
// for min width enforcement
|
||||||
|
ImGui::Dummy(ImVec2(320.f, 0.f));
|
||||||
|
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "%s", definition.title.c_str());
|
||||||
|
|
||||||
|
ImGui::TextUnformatted("");
|
||||||
|
|
||||||
|
ImGui::TextUnformatted("Current value:");
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
// keeping it read only; if you want to make this editable, we need to refactor the
|
||||||
|
// input validation logic above (for int/hex) so people don't end up with invalid
|
||||||
|
// values that can crash spicecfg
|
||||||
|
ImGui::InputText("", &option.value, ImGuiInputTextFlags_ReadOnly);
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::ClearButton("Reset to default")) {
|
||||||
|
option.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TextUnformatted("");
|
||||||
|
|
||||||
|
build_option_value_picker(option);
|
||||||
|
|
||||||
|
ImGui::TextUnformatted("");
|
||||||
|
|
||||||
|
if (ImGui::Button("Save & Close")) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
this->options_dirty = true;
|
||||||
|
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// next item
|
// next item
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ namespace overlay::windows {
|
|||||||
std::string search_filter = "";
|
std::string search_filter = "";
|
||||||
std::string search_filter_in_lower_case = "";
|
std::string search_filter_in_lower_case = "";
|
||||||
|
|
||||||
|
std::filesystem::path file_picker_path;
|
||||||
|
std::thread *file_picker_thread = nullptr;
|
||||||
|
bool file_picker_done = false;
|
||||||
|
|
||||||
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
|
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
|
||||||
void build_button(
|
void build_button(
|
||||||
const std::string &name,
|
const std::string &name,
|
||||||
@@ -104,6 +108,8 @@ namespace overlay::windows {
|
|||||||
void edit_light_popup(Light &primary_light, Light *light, const int alt_index);
|
void edit_light_popup(Light &primary_light, Light *light, const int alt_index);
|
||||||
|
|
||||||
void build_cards();
|
void build_cards();
|
||||||
|
std::string build_option_value_picker_title(const OptionDefinition& option);
|
||||||
|
void build_option_value_picker(Option& option);
|
||||||
void build_options(
|
void build_options(
|
||||||
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
|
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
|
||||||
void build_about();
|
void build_about();
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ namespace rawinput {
|
|||||||
uint32_t MIDI_NOTE_SUSTAIN = 20;
|
uint32_t MIDI_NOTE_SUSTAIN = 20;
|
||||||
|
|
||||||
static MidiNoteAlgorithm MIDI_NOTE_ALGORITHM = MidiNoteAlgorithm::V2;
|
static MidiNoteAlgorithm MIDI_NOTE_ALGORITHM = MidiNoteAlgorithm::V2;
|
||||||
|
|
||||||
|
// the price we pay for making spice overlay consume from raw input
|
||||||
|
// making focus detection a nightmare
|
||||||
|
bool OS_WINDOW_ACTIVE = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rawinput::MidiNoteAlgorithm rawinput::get_midi_algorithm() {
|
rawinput::MidiNoteAlgorithm rawinput::get_midi_algorithm() {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "hotplug.h"
|
#include "hotplug.h"
|
||||||
|
#include "util/scope_guard.h"
|
||||||
|
|
||||||
namespace rawinput {
|
namespace rawinput {
|
||||||
|
|
||||||
@@ -31,6 +32,15 @@ namespace rawinput {
|
|||||||
extern bool NAIVE_REQUIRE_FOCUS;
|
extern bool NAIVE_REQUIRE_FOCUS;
|
||||||
extern bool RAWINPUT_REQUIRE_FOCUS;
|
extern bool RAWINPUT_REQUIRE_FOCUS;
|
||||||
|
|
||||||
|
// while active, prevents overlay from accepting any input
|
||||||
|
// can be used while OS modal dialog is shown on top of overlay/spicecfg
|
||||||
|
// always prefer RAII set_os_window_focus_guard instead of the global bool
|
||||||
|
extern bool OS_WINDOW_ACTIVE;
|
||||||
|
inline scope_guard set_os_window_focus_guard() {
|
||||||
|
rawinput::OS_WINDOW_ACTIVE = true;
|
||||||
|
return scope_guard {[]() { rawinput::OS_WINDOW_ACTIVE = false; }};
|
||||||
|
}
|
||||||
|
|
||||||
struct DeviceCallback {
|
struct DeviceCallback {
|
||||||
void *data;
|
void *data;
|
||||||
std::function<void(void*, Device*)> f;
|
std::function<void(void*, Device*)> f;
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ namespace rawinput::touch {
|
|||||||
bool INVERTED = false;
|
bool INVERTED = false;
|
||||||
|
|
||||||
// state
|
// state
|
||||||
static bool DISPLAY_INITIALIZED = false;
|
DWORD DISPLAY_ORIENTATION = DMDO_DEFAULT;
|
||||||
static DWORD DISPLAY_ORIENTATION = DMDO_DEFAULT;
|
long DISPLAY_SIZE_X = 1920L;
|
||||||
static long DISPLAY_SIZE_X = 1920L;
|
long DISPLAY_SIZE_Y = 1080L;
|
||||||
static long DISPLAY_SIZE_Y = 1080L;
|
bool DISPLAY_INITIALIZED = false;
|
||||||
|
|
||||||
bool is_touchscreen(Device *device) {
|
bool is_touchscreen(Device *device) {
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ namespace rawinput::touch {
|
|||||||
extern bool DISABLED;
|
extern bool DISABLED;
|
||||||
extern bool INVERTED;
|
extern bool INVERTED;
|
||||||
|
|
||||||
|
// global state
|
||||||
|
extern DWORD DISPLAY_ORIENTATION;
|
||||||
|
extern long DISPLAY_SIZE_X;
|
||||||
|
extern long DISPLAY_SIZE_Y;
|
||||||
|
extern bool DISPLAY_INITIALIZED;
|
||||||
|
|
||||||
bool is_touchscreen(Device *device);
|
bool is_touchscreen(Device *device);
|
||||||
void enable(Device *device);
|
void enable(Device *device);
|
||||||
void disable(Device *device);
|
void disable(Device *device);
|
||||||
|
|||||||
Reference in New Issue
Block a user