Compare commits

...

21 Commits

Author SHA1 Message Date
bicarus dc82c980b1 iidx: show error message for emulated I/O mismatch in LDJ/TDJ sub overlay (#555)
## Link to GitHub Issue, if one exists
#345 

## Description of change
When DLL is configured to use TDJ I/O but spice is in LDJ mode, show an
error in LED ticker suggesting the user to turn on `-iidxtdj`.

When DLL is configured to use LDJ I/O but spice is in TDJ mode, show an
error in TDJ overaly suggesting the user to fix the DLL.

Fix a small bug that caused a white window to be created when `-w` is
enabled even when `-iidxtdj` is not, but DLL is using TDJ I/O.
2026-02-15 19:49:07 -08:00
bicarus 408ed17521 cfg: turn a handful of parameter parsing errors into fatal (#554)
For parameters that require two values (`width, height` or `x, y`), if
the string fails to parse, turn into runtime failure.
2026-02-13 03:22:20 -08:00
bicarus f4050e9adb graphics: custom resolution for subscreen (#553)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Allow user to specify a custom resolution for the sub monitor, in
addition to the existing refresh rate option

This would allow practically any touch monitor (including ones with
weird resolution / aspect ratio / refresh rate) to be used with
IIDX/SDVX.

## Testing
Tested with various resolution / refresh rate combinations on IIDX /
SDVX.
2026-02-12 17:51:38 -08:00
din badae01357 bt5api fix (#552)
## Description of change
use case: user with two ICCA readers in a daisy chain configuration (as
is wired in og IIDX/DDR) wanted to play DDR using their existing
equipment.

Existing `reader/` implementation is only designed for a single reader
per com port vs two readers on one daisy chained com port. This can be
implemented and improved at a future date.

Due to time constraints `eamio-icca.dll` from btools did support both
wavepass and slotted in a daisy chain configuration, and spice does
support bt5api calls.

However, the implementation was not sufficient to allow use of both
modules correctly, often stalling, not did it allow the ability to eject
the card.

I have cleaned up the implementation such that it matches the btools
`eamiotest` implementation and allows the user to use decimal to eject
the card as desired.

As well as properly staging the requests for state of the two individual
readers, like `eamio-icca` expects.

## Testing
Tested with both wavepass and slotted (ICCA and ICCB) modules and in
combination with released `eamio-icca.dll` binaries from btools 5.49 as
released by their github using `MDX` as a test subject.

Works as expected and allows end users to use real readers (if they have
them) as virtual readers in spice's emulation layer.
2026-02-04 19:34:25 -08:00
bicarus 38d9939eb7 cfg: fix early game detection for Polaris Chord and MFG (#551)
## Link to GitHub Issue, if one exists


## Description of change
Some parts of the UI (e.g., Card tab) rely on early auto detection of
the game, which relies on file hints and not the game code. Two games
(PC and MFG) rely on directory presence and not file presence.
2026-02-04 11:40:07 -08:00
bicarus f5a3caf536 signal: avoid spamming log with hook hit messages (#550)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Some people have weird DLLs that keep calling
`AddVectoredExceptionHandler` which ends up spamming the log.

## Testing
Tested IIDX with error + signaldisable to confirm the warning message
being shown, ran Polaris Chord once to check "hook hit" message.
2026-02-03 20:08:15 -08:00
bicarus 027445afb1 overlay: add context menu for overflow operations (#547)
Remove the green + button, remove the bottom checkbox row, and instead
put extra operations like `Add alternate` and `Bind many` to a context
menu.
2026-01-31 23:57:08 -08:00
bicarus 63a3900b79 overlay: add skip button to bind many (#546) 2026-01-31 20:58:15 -08:00
bicarus f542a78347 overlay: add hover delay to tooltip (#545)
Clean up flags used for tooltip display, which adds a tiny delay by
default.
2026-01-31 15:47:23 -08:00
bicarus 4d32dde83e overlay: remove multiple pages from Buttons/Overlay/Lights tabs, replace with tree layout (#541)
The old pages UI was not intuitive and often led to questions like:

* How do I bind multiple controllers to a single input?? (failure to
discover)
* I have a ghost input that won't go away, how do I fix this? Is this a
bug? (non-intuitive UI - there are too many pages and finding out where
each binding is potentially requires scrolling through 100 pages)
* Potential perf issue - user binds too many by accident, or puts
bindings all the way in page 99, requiring us to walk each element in
the vector, wasting precious CPU cycles during an input poll.

This PR removes the multiple pages, and puts everything on a single
page, showing multiple binds in a tree structure.

It also puts a hard limit on how many alternate bindings can be made (8
for buttons, 16 for lights), though if the user has configured more in
older version they will be respected.
2026-01-31 15:16:19 -08:00
bicarus 927170ce73 api: fix unbounded access in getting analog state (#544)
## Link to GitHub Issue, if one exists
#0

## Description of change
Occasionally we would read a bad "vKey" for an analog axis and end up
accessing invalid memory (beyond the size of a vector) and potentially
crash or read junk values.

## Testing
Fixed based on minidump, this was a rare repro though.
2026-01-30 20:53:13 -08:00
bicarus 875a0f0765 overlay: address small polish issues (#543)
* Make a custom widget for truncated text that shows a tooltip only when
truncated
* Use the above widget more consistently in various places
* Ensure tooltips still show even when the preceding widget is disabled
2026-01-30 19:17:44 -08:00
bicarus 3ad88ef15c overlay: fixes for naive binding (#542)
## Description of change
Three bugs:

* Some keys are IME-dependent. Right Alt can be IME mode switch for
Japanese / Korean, and Right Ctrl is often the Kanji button.
* If you press modifiers like Ctrl during bind-many-naive, it will skip
over a button, because of broken for loop logic that ends up calling
`inc_buttons_many_index` too many times
* trying to cancel out of bind-many-naive with left and right mouse
buttons swap doesn't work

## Testing
Validating various IME. I don't have a keyboard in another language
though..
2026-01-30 13:16:14 -08:00
bicarus 7a6cadb176 overlay: refactor code for lights (#539)
No functional change.
2026-01-29 16:13:56 -08:00
Will 4feca23240 Build scripts: support paths with spaces (#540)
Doing dumb things and building inside a path that contains spaces.
Quoted the relevant bits in the build script (and a couple more
shellcheck brought up that realistically won't ever be an issue, but the
squigglies annoy me.
2026-01-29 16:12:23 -08:00
bicarus 2e52bebd3d overlay: bring back "-" placeholder for unbound io, clean up code (#538) 2026-01-29 11:53:16 -08:00
bicarus aa218d5be7 overlay: fix colors of new extension widgets (#537) 2026-01-29 01:58:43 -08:00
bicarus 94f009991a overlay: simplify various input controls by introducing new custom ImGUI widgets (#536)
## Description of change

* Add a red `x` button to replace `Clear` button in binding tabs
(Buttons/Analog/Lights).
* Add a gray `x` button to replace `Clear` button in options tabs next
to input widgets
* Add helper for truncating text and adding ellipsis (...) to deal with
text overflow in tables.
* Reword some UI text for clarity
2026-01-28 02:17:40 -08:00
bicarus c3b9ce3fdc cfg: fix mouse names out of bounds (#535)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Improper bounds check resulting in crash when binding mouse buttons
beyond Mouse 5.

## Testing
Tested with `Edit` function.
2026-01-26 23:42:12 -08:00
bicarus 1517cf9a7c overlay: refactor UI code for I/O binding tabs (#533)
No functional changes, refactor of code only.
2026-01-26 22:24:14 -08:00
bicarus f0f46296f2 cfg: clean up stale buttons and lights alt bindings on save (#534)
## Link to GitHub Issue, if one exists
n/a

## Description of change
For buttons and lights, it's possible to have multiple alternatives.

When you create alternatives and then clear then, the config files
continue to have stale entries. This is because alternatives are stored
in a vector and elements are never deleted when unbound; they get
written out to the XML on modification.

These stale XML entries live forever, and get loaded on next startup of
spice.

This PR fixes that by deleting stale entries - basically, XML nodes will
be kept only until the last valid node (properly bound button or light),
trimming the trailing invalid entries.

## Testing
Manual testing, validating XML.
2026-01-26 22:12:04 -08:00
35 changed files with 2496 additions and 1859 deletions
+2 -2
View File
@@ -170,8 +170,8 @@ else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
# file prefix map for relative working directory
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
endif()
# release with debug info flags
+7 -7
View File
@@ -74,7 +74,7 @@ then
BUILD_TYPE="Debug"
BUILDDIR_32=${BUILDDIR_32_DEBUG}
BUILDDIR_64=${BUILDDIR_64_DEBUG}
DIST_NAME=$(echo ${DIST_NAME} | sed 's/\.[^.]*$/-dbg&/')
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
fi
# determine number of cores
@@ -114,7 +114,7 @@ time (
fi
mkdir -p ${BUILDDIR_32}
pushd ${BUILDDIR_32} > /dev/null
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_32}
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_32}
popd > /dev/null
# 64 bit
@@ -127,7 +127,7 @@ time (
fi
mkdir -p ${BUILDDIR_64}
pushd ${BUILDDIR_64} > /dev/null
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_64}
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
popd > /dev/null
echo ""
@@ -234,7 +234,7 @@ mkdir -p ${OUTDIR}/src
if ((INCLUDE_SRC > 0))
then
echo "Generating source file archive..."
git archive --format tar.gz --prefix=spice2x/ HEAD ./ > ${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz 2>/dev/null || \
git archive --format tar.gz --prefix=spice2x/ HEAD ./ > "${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz" 2>/dev/null || \
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
fi
@@ -249,11 +249,11 @@ if ((DIST_ENABLE > 0))
then
echo "Building dist..."
mkdir -p ${DIST_FOLDER}
rm -rf ${DIST_FOLDER}/${DIST_NAME}
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
pushd ${OUTDIR}/.. > /dev/null
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
echo "Building extras..."
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS} spice2x -z <<< "$DIST_COMMENT"
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" spice2x -z <<< "$DIST_COMMENT"
popd > /dev/null
fi
+3 -3
View File
@@ -1,4 +1,4 @@
#!/bin/bash
docker build --pull $PWD/external/docker -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot=$PWD/../../.git . -t spicetools/spice:latest
docker run --rm -v $PWD/dist:/src/src/spice2x/dist -v $PWD/bin:/src/src/spice2x/bin -v $PWD/.ccache:/src/src/spice2x/.ccache spicetools/spice "$@"
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
docker run --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
+6 -4
View File
@@ -644,10 +644,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
case rawinput::HID: {
// get value
if (inverted) {
value = 1.f - device->hidInfo->value_states[index];
} else {
value = device->hidInfo->value_states[index];
if (index < device->hidInfo->value_states.size()) {
if (inverted) {
value = 1.f - device->hidInfo->value_states[index];
} else {
value = device->hidInfo->value_states[index];
}
}
// deadzone
+33 -13
View File
@@ -58,8 +58,28 @@ std::string Button::getVKeyString() {
return "Pause";
case 0x14:
return "Caps Lock";
case 0x15:
return "IME Kana/Hangul";
case 0x16:
return "IME On";
case 0x17:
return "IME Junja";
case 0x18:
return "IME Final";
case 0x19:
return "IME Hanja/Kanji";
case 0x1A:
return "IME Off";
case 0x1B:
return "Escape";
case 0x1C:
return "IME Convert";
case 0x1D:
return "IME Nonconvert";
case 0x1E:
return "IME Accept";
case 0x1F:
return "IME Mode Change";
case 0x20:
return "Space";
case 0x21:
@@ -255,9 +275,9 @@ std::string Button::getVKeyString() {
case 0xA3:
return "Right Control";
case 0xA4:
return "Left Menu";
return "Left Alt";
case 0xA5:
return "Right Menu";
return "Right Alt";
default:
// check win API
@@ -301,17 +321,17 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
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)) {
static const std::array<const char *, 8>MOUSE_NAMES = {
"Left Mouse",
"Right Mouse",
"Middle Mouse",
"Mouse 1",
"Mouse 2",
"Mouse 3",
"Mouse 4",
"Mouse 5",
};
if (vKey < MOUSE_NAMES.size()) {
btn = MOUSE_NAMES[vKey];
}
return fmt::format("{} ({})", btn, device->desc);
+23
View File
@@ -45,6 +45,7 @@ private:
double debounce_up = 0.0;
double debounce_down = 0.0;
bool invert = false;
bool is_temporary = false;
GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED;
float last_velocity = 0.f;
@@ -164,6 +165,14 @@ public:
this->last_velocity = last_velocity;
}
inline bool isTemporary() const {
return this->is_temporary;
}
inline void setTemporary(bool is_temporary) {
this->is_temporary = is_temporary;
}
inline unsigned short getVelocityThreshold() const {
return this->velocity_threshold;
}
@@ -172,6 +181,20 @@ public:
this->velocity_threshold = velocity_threshold;
}
inline bool isValid() {
// temporarily created by UI
if (isTemporary()) {
return true;
}
// nothing is set
if (getDeviceIdentifier().empty() && getVKey() == INVALID_VKEY) {
return false;
}
return true;
}
void getMidiVKey(int& channel, int& index);
void setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index);
+76 -1
View File
@@ -1,5 +1,6 @@
#include "config.h"
#include "util/logging.h"
#include "cfg/button.h"
/*
* This code absolutely sucks.
@@ -10,6 +11,14 @@
// settings
std::string CONFIG_PATH_OVERRIDE = "";
#define CFG_DEBUG_VERBOSE 0
#if CFG_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
///////////////////
/// Constructor ///
@@ -510,8 +519,10 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
return false;
}
tinyxml2::XMLElement *gameButtonNode = nullptr;
// iterate button nodes
tinyxml2::XMLElement *gameButtonNode = gameButtonsNode->FirstChildElement("button");
gameButtonNode = gameButtonsNode->FirstChildElement("button");
int button_count = 0;
while (gameButtonNode != nullptr) {
const char *buttonNodeName = gameButtonNode->Attribute("name");
@@ -574,6 +585,39 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
}
}
// trim the trailing invalid (unbound) alternatives
tinyxml2::XMLElement *to_delete = nullptr;
gameButtonNode = gameButtonsNode->LastChildElement("button");
while (gameButtonNode != nullptr) {
const char *name = gameButtonNode->Attribute("name");
const char *devid = gameButtonNode->Attribute("devid");
int vkey = INVALID_VKEY;
gameButtonNode->QueryIntAttribute("vkey", &vkey);
// wrong button, ignore and keep iterating backwards
if (name == nullptr || std::string(name) != button.getName()) {
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
continue;
}
// found a node for this button, which means to_delete is not the first one
// delete the node for to_delete
if (to_delete != nullptr) {
log_debug("cfg", "deleting excessive button binding for {}", name);
gameButtonsNode->DeleteChild(to_delete);
to_delete = nullptr;
}
if ((devid == nullptr || strlen(devid) == 0) && vkey == INVALID_VKEY) {
// unbound button with no device ID, make a note of it so it can be deleted later
to_delete = gameButtonNode;
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
} else {
// valid binding found; stop iterating
break;
}
}
// check if button was not found
if (button_count == 0) {
return false;
@@ -760,6 +804,37 @@ bool Config::updateBinding(const Game &game, const Light &light, int alternative
}
}
// trim the trailing invalid (unbound) alternatives
tinyxml2::XMLElement *to_delete = nullptr;
gameLightNode = gameLightsNode->LastChildElement("light");
while (gameLightNode != nullptr) {
const char *name = gameLightNode->Attribute("name");
const char *devid = gameLightNode->Attribute("devid");
// wrong light, ignore and keep iterating backwards
if (name == nullptr || std::string(name) != light.getName()) {
gameLightNode = gameLightNode->PreviousSiblingElement("light");
continue;
}
// found a node for this button, which means to_delete is not the first one
// delete the node for to_delete
if (to_delete != nullptr) {
log_debug("cfg", "deleting excessive light binding for {}", name);
gameLightsNode->DeleteChild(to_delete);
to_delete = nullptr;
}
if (devid == nullptr || strlen(devid) == 0) {
// unbound light, make a note of it so it can be deleted later
to_delete = gameLightNode;
gameLightNode = gameLightNode->PreviousSiblingElement("light");
} else {
// valid binding found; stop iterating
break;
}
}
// check if light was not found
if (light_count == 0) {
return false;
+23
View File
@@ -13,6 +13,7 @@ private:
std::string lightName;
std::string deviceIdentifier = "";
unsigned int index = 0;
bool is_temporary = false;
public:
float last_state = 0.f;
@@ -65,4 +66,26 @@ public:
inline void setIndex(unsigned int index) {
this->index = index;
}
inline bool isTemporary() const {
return this->is_temporary;
}
inline void setTemporary(bool is_temporary) {
this->is_temporary = is_temporary;
}
inline bool isValid() {
// temporarily created by UI
if (isTemporary()) {
return true;
}
// nothing is set
if (getDeviceIdentifier().empty()) {
return false;
}
return true;
}
};
+4 -1
View File
@@ -82,6 +82,7 @@ namespace games::iidx {
bool IIDXIO_LED_TICKER_READONLY = false;
std::mutex IIDX_LED_TICKER_LOCK;
bool IIDX_TDJ_MONITOR_WARNING = false;
iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE = iidx_aio_emulation_state::unknown;
// io
static bool HAS_LIBAIO; // this is how we detect iidx27+
@@ -453,7 +454,7 @@ namespace games::iidx {
}
// windowed subscreen, enabled by default, unless turned off by user
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool()) {
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool() && TDJ_MODE) {
GRAPHICS_IIDX_WSUB = true;
}
@@ -934,6 +935,8 @@ namespace games::iidx {
return;
}
CURRENT_IO_EMULATION_STATE = state;
// log_warning below could be turned into log_fatal in the future once we gain confidence
// that this isn't triggering when it's not supposed to
if (state == iidx_aio_emulation_state::bi2a_com2 && TDJ_MODE) {
+7 -4
View File
@@ -11,6 +11,12 @@
namespace games::iidx {
enum class iidx_aio_emulation_state {
unknown,
bi2a_com2,
bi2x_hook
};
// settings
extern bool FLIP_CAMS;
@@ -38,6 +44,7 @@ namespace games::iidx {
constexpr int IIDX_TAPELED_TOTAL = 17;
// data mapping
extern tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL];
extern iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE;
class IIDXGame : public games::Game {
public:
@@ -64,9 +71,5 @@ namespace games::iidx {
bool is_tdj_fhd();
void apply_audio_hacks();
enum class iidx_aio_emulation_state {
bi2a_com2,
bi2x_hook
};
void update_io_emulation_state(iidx_aio_emulation_state state);
}
+1 -1
View File
@@ -324,7 +324,7 @@ namespace games {
buttons_help.insert({ pc, pc::get_buttons_help() });
analogs.insert({ pc, pc::get_analogs() });
lights.insert({ pc, pc::get_lights() });
file_hints[pc].emplace_back("game/svm_Data");
file_hints[pc].emplace_back("game/svm.exe");
}
const std::vector<std::string> &get_games() {
@@ -945,26 +945,37 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
for (size_t i = 0; i < num_adapters; i++) {
auto *params = &pPresentationParameters[i];
if (!GRAPHICS_WINDOWED && i == 0) {
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
if (!GRAPHICS_WINDOWED){
if (i == 0) {
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
log_misc(
"graphics::d3d9",
"use custom resolution {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
log_misc(
"graphics::d3d9",
"swap full orientation {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
params->BackBufferHeight, params->BackBufferWidth);
std::swap(params->BackBufferWidth, params->BackBufferHeight);
}
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
log_misc(
"graphics::d3d9",
"use custom resolution {}x{} => {}x{}",
"use custom sub resolution {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
log_misc(
"graphics::d3d9",
"swap full orientation {}x{} => {}x{}",
params->BackBufferWidth, params->BackBufferHeight,
params->BackBufferHeight, params->BackBufferWidth);
std::swap(params->BackBufferWidth, params->BackBufferHeight);
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first,
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second);
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
}
}
@@ -992,12 +1003,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
for (size_t i = 0; i < num_adapters; i++) {
auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
if (!GRAPHICS_WINDOWED && i == 0) {
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
if (!GRAPHICS_WINDOWED) {
if (i == 0) {
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
}
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
}
}
+1
View File
@@ -71,6 +71,7 @@ bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false;
bool SUBSCREEN_FORCE_REDRAW = false;
bool D3D9_DEVICE_HOOK_DISABLE = false;
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
bool GRAPHICS_FS_ORIENTATION_SWAP = false;
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
+1
View File
@@ -37,6 +37,7 @@ extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
extern bool GRAPHICS_FS_ORIENTATION_SWAP;
extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT;
@@ -173,7 +173,7 @@ void graphics_load_windowed_parameters() {
cfg::SCREENRESIZE->window_offset_x = result.first;
cfg::SCREENRESIZE->window_offset_y = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -windowpos");
log_fatal("graphics-windowed", "failed to parse -windowpos");
}
}
@@ -202,7 +202,7 @@ void graphics_load_windowed_subscreen_parameters() {
GRAPHICS_WSUB_WIDTH = result.first;
GRAPHICS_WSUB_HEIGHT = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -wsubsize");
log_fatal("graphics-windowed", "failed to parse -iidxwsubsize / -sdvxwsubsize");
}
}
@@ -216,7 +216,7 @@ void graphics_load_windowed_subscreen_parameters() {
GRAPHICS_WSUB_X = result.first;
GRAPHICS_WSUB_Y = result.second;
} else {
log_warning("graphics-windowed", "failed to parse -wsubpos");
log_fatal("graphics-windowed", "failed to parse -iidxwsubpos / -sdvxwsubpos");
}
}
}
+26 -5
View File
@@ -1083,8 +1083,8 @@ int main_implementation(int argc, char *argv[]) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::spice2x_WindowSize].value_text(), result)) {
GRAPHICS_WINDOW_SIZE = result;
} else {
log_warning("launcher", "failed to parse -windowsize");
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
log_fatal("launcher", "failed to parse -windowsize");
}
}
if (options[launcher::Options::spice2x_WindowPosition].is_active()) {
@@ -1385,12 +1385,33 @@ int main_implementation(int argc, char *argv[]) {
);
}
if (launcher::signal::DISABLE && !cfg::CONFIGURATOR_STANDALONE) {
log_warning(
"launcher",
"WARNING - user specified -signaldisable option\n\n\n"
"!!! !!!\n"
"!!! Using -signaldisable option disables all exception handling, !!!\n"
"!!! which means when the game crashes it will likely just vanish !!!\n"
"!!! without troubleshooting information in the logs. !!!\n"
"!!! !!!\n"
);
}
if (options[launcher::Options::FullscreenResolution].is_active()) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
GRAPHICS_FS_CUSTOM_RESOLUTION = result;
} else {
log_warning("launcher", "failed to parse -forceres");
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
log_fatal("launcher", "failed to parse -forceres");
}
}
if (options[launcher::Options::FullscreenSubResolution].is_active()) {
std::pair<uint32_t, uint32_t> result;
if (parse_width_height(options[launcher::Options::FullscreenSubResolution].value_text(), result)) {
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB = result;
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
log_fatal("launcher", "failed to parse -forceressub");
}
}
@@ -1867,7 +1888,7 @@ int main_implementation(int argc, char *argv[]) {
}
// Polaris Chord
if (check_dll("kamunity.dll") && fileutils::dir_exists("game/svm_Data")) {
if (check_dll("kamunity.dll") && fileutils::file_exists("game/svm.exe")) {
avs::game::DLL_NAME = "kamunity.dll";
attach_io = true;
attach_pc = true;
+27 -15
View File
@@ -256,9 +256,21 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.hidden = true,
.category = "Graphics (Full Screen)"
},
{
// FullscreenSubResolution
.title = "Force FS Subscreen Resolution (EXPERIMENTAL)",
.name = "forceressub",
.desc =
"Override fullscreen resolution requested by the game for second monitor, "
"useful if you have a sub monitor that is not quite exactly what the game expects.\n\n"
"WARNING: experimental as we have not done extensive testing to see if this causes desyncs.",
.type = OptionType::Text,
.setting_name = "1280,720",
.category = "Graphics (Full Screen)"
},
{
// FullscreenSubRefreshRate
.title = "Force Submonitor Refresh Rate (EXPERIMENTAL)",
.title = "Force FS Subscreen Refresh Rate (EXPERIMENTAL)",
.name = "graphics-force-refresh-sub",
.desc =
"Override fullscreen refresh rate requested by the game for second monitor, "
@@ -814,7 +826,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options",
},
{
.title = "SDVX Native Touch Handling",
.title = "SDVX FS Subscreen Native Touch Handling",
.name = "sdvxnativetouch",
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
"Requires a touch screen to be connected as a secondary monitor. "
@@ -824,6 +836,18 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
},
{
// spice2x_SDVXSubRedraw
.title = "SDVX FS Subscreen Force Redraw",
.name = "sp2x-sdvxsubredraw",
.display_name = "sdvxsubredraw",
.aliases= "sdvxsubredraw",
.desc = "Check if submonitor in fullscreen mode doesn't update every frame; "
"this option forces subscreen to redraw every frame.",
.type = OptionType::Bool,
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
},
{
// spice2x_SDVXDigitalKnobSensitivity
.title = "SDVX Digital Knob Sensitivity",
@@ -884,18 +908,6 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{"bottomright", "for landscape"},
},
},
{
// spice2x_SDVXSubRedraw
.title = "SDVX Subscreen Force Redraw",
.name = "sp2x-sdvxsubredraw",
.display_name = "sdvxsubredraw",
.aliases= "sdvxsubredraw",
.desc = "Check if second monitor for subscreen doesn't update every frame; "
"forces subscreen to redraw every frame, only needed for newer EG.",
.type = OptionType::Bool,
.game_name = "Sound Voltex",
.category = "Game Options (Advanced)",
},
{
.title = "Force Load DDR Module",
.name = "ddr",
@@ -2251,7 +2263,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
{
// spice2x_IIDXNativeTouch
.title = "IIDX Native Touch Handling",
.title = "IIDX TDJ Subscreen Native Touch Handling",
.name = "sp2x-iidxnativetouch",
.display_name = "iidxnativetouch",
.aliases= "iidxnativetouch",
+2 -1
View File
@@ -31,6 +31,7 @@ namespace launcher {
GraphicsForceRefresh,
FullscreenResolution,
FullscreenOrientationFlip,
FullscreenSubResolution,
FullscreenSubRefreshRate,
Graphics9On12,
spice2x_Dx9On12,
@@ -85,11 +86,11 @@ namespace launcher {
SDVXPrinterJPGQuality,
SDVXDisableCameras,
SDVXNativeTouch,
spice2x_SDVXSubRedraw,
spice2x_SDVXDigitalKnobSensitivity,
SDVXDigitalKnobSocd,
spice2x_SDVXAsioDriver,
spice2x_SDVXSubPos,
spice2x_SDVXSubRedraw,
LoadDDRModule,
DDR43Mode,
DDRSkipCodecRegisteration,
+14 -5
View File
@@ -185,21 +185,30 @@ static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *Exception
}
static BOOL WINAPI SetConsoleCtrlHandler_hook(PHANDLER_ROUTINE pHandlerRoutine, BOOL Add) {
log_misc("signal", "SetConsoleCtrlHandler hook hit");
static std::once_flag printed;
std::call_once(printed, []() {
log_misc("signal", "SetConsoleCtrlHandler hook hit (printing once)");
});
return TRUE;
}
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_hook(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
{
log_info("signal", "SetUnhandledExceptionFilter hook hit");
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
static std::once_flag printed;
std::call_once(printed, []() {
log_info("signal", "SetUnhandledExceptionFilter hook hit (printing once)");
});
return nullptr;
}
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
log_info("signal", "AddVectoredExceptionHandler hook hit");
static std::once_flag printed;
std::call_once(printed, []() {
log_info("signal", "AddVectoredExceptionHandler hook hit (printing once)");
});
return launcher::signal::USE_VEH_WORKAROUND ? INVALID_HANDLE_VALUE : nullptr;
}
+52 -20
View File
@@ -119,6 +119,7 @@ static std::string EAMIO_DLL_NAME = "eamio.dll";
static robin_hood::unordered_map<int, std::thread *> BT5API_THREADS;
static robin_hood::unordered_map<int, int> BT5API_THREAD_RESULTS;
static uint8_t BT5API_CARD_STATES[] = {0, 0};
static uint16_t BT5API_KEYPAD_STATES[] = {0, 0};
static void bt5api_log(const char *bt5_module, const char *fmt, ...) {
@@ -240,46 +241,77 @@ void bt5api_hook(HINSTANCE module) {
void bt5api_poll_reader_card(uint8_t unit_no) {
// check if initialized
if (!EAMIO_DLL) {
// check if initialized or out of bounds
if (!EAMIO_DLL || unit_no >= eamuse_get_game_keypads()) {
return;
}
// poll
if (!eam_io_poll(unit_no)) {
log_warning("bt5api", "polling bt5api reader {} returned failure",
log_warning("bt5api", "polling bt5api reader card {} returned failure",
static_cast<int>(unit_no));
return;
}
// get sensor state
uint8_t sensor_state = eam_io_get_sensor_state(unit_no);
// if we have a change in state.
if(sensor_state != BT5API_CARD_STATES[unit_no]) {
// check for card in
if (sensor_state > BT5API_CARD_STATES[unit_no]) {
uint8_t card_id[8];
eam_io_read_card(unit_no, card_id, 8);
eamuse_card_insert(unit_no, card_id);
// card inserted into the back.
if(sensor_state & (1 << EAM_IO_SENSOR_BACK)) {
uint8_t card_id[8];
log_info("bt5api", "card unit {} inserted", static_cast<int>(unit_no));
// do the bt5 dance
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_CLOSE);
eam_io_poll(unit_no);
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_READ);
eam_io_poll(unit_no);
// ask the api for the card, then insert it into our engine.
eam_io_read_card(unit_no, card_id, 8);
eamuse_card_insert(unit_no, card_id);
}
// if card removed entirely, do another bt5 dance!
if(sensor_state == 0) {
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_CLOSE);
eam_io_poll(unit_no);
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_OPEN);
log_info("bt5api", "card unit {} removed", static_cast<int>(unit_no));
}
}
// save state
// save prev state
BT5API_CARD_STATES[unit_no] = sensor_state;
}
void bt5api_poll_reader_keypad(uint8_t unit_no) {
// check if initialized
if (!EAMIO_DLL) {
// check if initialized or out of bounds
if (!EAMIO_DLL || unit_no >= eamuse_get_game_keypads()) {
return;
}
// poll
if (!eam_io_poll(unit_no)) {
log_warning("bt5api", "polling bt5api reader {} returned failure",
static_cast<int>(unit_no));
uint16_t keypad_current = eam_io_get_keypad_state(unit_no);
uint16_t keypad_changes = ~BT5API_KEYPAD_STATES[unit_no] & keypad_current;
// if user has pressed decimal for the first time, eject the slotted reader.
if(keypad_changes & (1 << EAM_IO_KEYPAD_DECIMAL)) {
log_info("bt5api", "card unit {} ejecting", static_cast<int>(unit_no));
eam_io_card_slot_cmd(unit_no, EAM_IO_CARD_SLOT_CMD_EJECT);
}
// save the last pressed.
BT5API_KEYPAD_STATES[unit_no] = keypad_current;
// get keypad
eamuse_set_keypad_overrides_bt5(unit_no, eam_io_get_keypad_state(unit_no));
// send off the state to the main engine.
eamuse_set_keypad_overrides_bt5(unit_no, keypad_current);
}
void bt5api_dispose() {
+8 -1
View File
@@ -202,7 +202,14 @@ bool eamuse_card_insert_consume(int active_count, int unit_id) {
// bt5api
if (BT5API_ENABLED) {
bt5api_poll_reader_card((uint8_t) index);
// some bt5api want to be polled in order.
if (eamuse_get_game_keypads() > 1) {
bt5api_poll_reader_card(1);
bt5api_poll_reader_card(0);
}
else {
bt5api_poll_reader_card((uint8_t) index);
}
}
// auto insert card
+75 -2
View File
@@ -26,7 +26,7 @@ namespace ImGui {
void HelpMarker(const char* desc) {
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
HelpTooltip(desc);
}
}
@@ -59,7 +59,7 @@ namespace ImGui {
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 1.f, 0.f, 1.f));
ImGui::TextDisabled("(!)");
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
WarnTooltip(desc, warn);
}
}
@@ -92,4 +92,77 @@ namespace ImGui {
ImGui::GetColorU32(ImGuiCol_PlotHistogram),
thickness);
}
std::string TruncateText(const std::string& p_text, float p_truncated_width, bool &truncated) {
std::string truncated_text = p_text;
const float text_width =
ImGui::CalcTextSize(p_text.c_str(), nullptr, true).x;
if (text_width > p_truncated_width) {
truncated = true;
constexpr const char* ELLIPSIS = " ...";
const float ellipsis_size = ImGui::CalcTextSize(ELLIPSIS).x;
int visible_chars = 0;
for (size_t i = 0; i < p_text.size(); i++) {
const float current_width = ImGui::CalcTextSize(
p_text.substr(0, i).c_str(), nullptr, true)
.x;
if (current_width + ellipsis_size > p_truncated_width) {
break;
}
visible_chars = i;
}
truncated_text = (p_text.substr(0, visible_chars) + ELLIPSIS).c_str();
}
return truncated_text;
}
void TextTruncated(const std::string& p_text, float p_truncated_width) {
if (p_text.empty()) {
ImGui::TextDisabled("-");
return;
}
bool truncated = false;
ImGui::TextUnformatted(TruncateText(p_text, p_truncated_width, truncated).c_str());
if (truncated && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
ImGui::HelpTooltip(p_text.c_str());
}
}
bool DeleteButton(const std::string& tooltip) {
ImGui::PushID(tooltip.c_str());
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_Button, 0.7f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.f, 2.f));
bool clicked = ImGui::SmallButton("\u00D7"); // multiplication sign (×)
ImGui::PopStyleVar();
ImGui::PopStyleColor();
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
ImGui::SameLine();
ImGui::HelpTooltip(tooltip.c_str());
}
ImGui::PopID();
return clicked;
}
bool ClearButton(const std::string& tooltip) {
ImGui::PushID(tooltip.c_str());
// same colors as a checkbox
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_FrameBg));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_FrameBgHovered));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_FrameBgActive));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.97f, 0.97f, 0.97f, 1.00f));
bool clicked = ImGui::Button("\u00D7"); // multiplication sign (×)
ImGui::PopStyleColor(4);
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
ImGui::SameLine();
ImGui::HelpTooltip(tooltip.c_str());
}
ImGui::PopID();
return clicked;
}
}
+11
View File
@@ -1,12 +1,23 @@
#pragma once
#include <string>
#include "external/imgui/imgui.h"
namespace ImGui {
constexpr ImGuiHoveredFlags TOOLTIP_FLAGS =
(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_AllowWhenDisabled);
void HelpTooltip(const char* desc);
void HelpMarker(const char* desc);
void WarnTooltip(const char* desc, const char* warn);
void WarnMarker(const char* desc, const char* warn);
void DummyMarker();
void Knob(float fraction, float size, float thickness = 2.f,
float pos_x = -1.f, float pos_y = -1.f);
void TextTruncated(const std::string& p_text, float p_truncated_width);
bool DeleteButton(const std::string& tooltip);
bool ClearButton(const std::string& tooltip);
}
File diff suppressed because it is too large Load Diff
+35 -11
View File
@@ -38,7 +38,6 @@ namespace overlay::windows {
ConfigTab tab_selected = ConfigTab::CONFIG_TAB_INVALID;
// buttons tab
int buttons_page = 0;
bool buttons_keyboard_state[0xFF];
bool buttons_bind_active = false;
bool buttons_many_active = false;
@@ -55,7 +54,6 @@ namespace overlay::windows {
int analogs_devices_control_selected = -1;
// lights tab
int lights_page = 0;
std::vector<rawinput::Device *> lights_devices;
int lights_devices_selected = -1;
int lights_devices_control_selected = -1;
@@ -78,17 +76,33 @@ namespace overlay::windows {
std::string search_filter = "";
std::string search_filter_in_lower_case = "";
public:
Config(SpiceOverlay *overlay);
~Config() override;
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
void build_button(
const std::string &name,
Button &primary_button,
Button *button,
const int button_it,
const int button_it_max,
const int alt_index);
void bind_button_popup(const std::string &bind_name, Button *button, const int button_it_max, const int alt_index);
void naive_button_popup(const std::string &naive_string, Button *button, const int button_it_max, const int alt_index);
void edit_button_popup(
const std::string &edit_name,
const std::string &button_display,
Button *button,
const float button_velocity,
const int alt_index);
void clear_button(Button *button, const int alt_index);
void read_card(int player = -1);
void write_card(int player);
void build_content() override;
void build_buttons(const std::string &name, std::vector<Button> *buttons,
int min = 0, int max = -1);
void build_analogs(const std::string &name, std::vector<Analog> *analogs);
void edit_analog_popup(Analog &analog);
void build_lights(const std::string &name, std::vector<Light> *lights);
void build_light(Light &primary_light, Light *light, const int light_index, const int alt_index);
void clear_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_options(
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
@@ -96,8 +110,18 @@ namespace overlay::windows {
void build_launcher();
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
static void build_page_selector(int *page);
void build_menu(int *game_selected);
void shutdown_system(bool force, bool reboot_instead);
void set_alternating_row_colors(const int row_index);
public:
Config(SpiceOverlay *overlay);
~Config() override;
void read_card(int player = -1);
void write_card(int player);
void build_content() override;
};
}
@@ -85,10 +85,12 @@ namespace overlay::windows {
}
void GenericSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {}
void GenericSubScreen::check_for_errors() {}
void GenericSubScreen::build_content() {
this->flags |= ImGuiWindowFlags_NoBackground;
check_for_errors();
if (this->disabled_message.has_value()) {
this->draws_window = true;
this->flags &= ~ImGuiWindowFlags_NoBackground;
@@ -31,6 +31,7 @@ namespace overlay::windows {
protected:
virtual void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out);
virtual void check_for_errors();
ImVec2 overlay_content_top_left;
ImVec2 overlay_content_size;
std::optional<std::string> disabled_message = std::nullopt;
+18
View File
@@ -14,6 +14,7 @@ namespace overlay::windows {
static const size_t TICKER_SIZE = 9;
static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f);
static const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
static const ImVec4 RED(1.f, 0.f, 0.f, 1.f);
static const float PADDING_Y = 8.f;
static const float PADDING_X = 4.f;
@@ -91,6 +92,23 @@ namespace overlay::windows {
}
void IIDXSegmentDisplay::build_content() {
// explicitly check if TDJ I/O is enabled to account for the fact that I/O emulation may be
// disabled
if (games::iidx::CURRENT_IO_EMULATION_STATE == games::iidx::iidx_aio_emulation_state::bi2x_hook) {
ImGui::TextUnformatted("");
ImGui::TextColored(
YELLOW,
"%s",
"Invalid I/O mode; DLL is configured for TDJ I/O!\n"
"Enable -iidxtdj if you want TDJ subscreen overlay.");
ImGui::TextUnformatted("");
if (ImGui::Button("Close")) {
this->set_active(false);
}
return;
}
char input_ticker[TICKER_SIZE];
// get ticker content from game
+14 -4
View File
@@ -12,11 +12,9 @@ namespace overlay::windows {
this->title = "IIDX TDJ Subscreen";
if (GRAPHICS_IIDX_WSUB) {
this->disabled_message =
"Close this overlay and use the second window.\n"
"If you don't see the window, double check your DLL type and apply TDJ I/O patches as needed.";
this->disabled_message = "Close this overlay and use the second window. (try ALT+TAB)";
} else if (games::iidx::IIDX_TDJ_MONITOR_WARNING) {
this->disabled_message = "TDJ mode subscreen overlay is not compatible with -monitor option";
this->disabled_message = "TDJ mode subscreen overlay is not compatible with -monitor option.";
}
float size = 0.5f;
@@ -44,6 +42,18 @@ namespace overlay::windows {
ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 2));
}
void IIDXSubScreen::check_for_errors() {
if (games::iidx::CURRENT_IO_EMULATION_STATE ==
games::iidx::iidx_aio_emulation_state::bi2a_com2) {
// explicitly check if LDJ I/O is enabled (as opposed to checking if NOT TDJ I/O)
// to account for the fact that I/O emulation may be disabled
this->disabled_message =
"LDJ I/O detected; TDJ mode subscreen overlay requires TDJ I/O.\n"
"Ensure you apply applicable TDJ I/O patches, or run with TDJ DLL.";
}
}
void IIDXSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
if (!this->get_active()) {
return;
+1
View File
@@ -12,6 +12,7 @@ namespace overlay::windows {
protected:
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
void check_for_errors() override;
};
}
+1 -1
View File
@@ -81,7 +81,7 @@ namespace overlay::windows {
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFrameHeightWithSpacing());
this->build_button("+", tall, this->test_button, this->service_button);
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip("SERVICE + TEST");
}
}
+2 -2
View File
@@ -136,7 +136,7 @@ namespace overlay::windows {
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
this->build_button("<", leftright_size, this->left[p], nullptr, this->leftright_light[p]);
this->build_button("+", tiny_size, this->help[p], this->start[p], nullptr);
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip("HELP + START");
}
}
@@ -167,7 +167,7 @@ namespace overlay::windows {
ImGui::BeginGroup();
{
this->build_button("?", tiny_size, this->help[p], nullptr, this->help_light[p]);
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip("HELP");
}
this->build_button(">", leftright_size, this->right[p], nullptr, this->leftright_light[p]);
+1 -1
View File
@@ -120,7 +120,7 @@ namespace overlay::windows {
"Lightning Model cabinets (TDJ) do not have any keypads; they use the subscreen.\n\n"
"Fullscreen mode: bind a key in Overlay tab, and press it in game to show the subscreen, "
"then use your mouse to click. Page Up button is the default binding.\n\n"
"Windowed mode: look for the second window in the taskbar.\n\n"
"Windowed mode: look for the second window in the taskbar (or ALT+TAB).\n\n"
"Windowed mode with -iidxnosub: bring up the subscreen overlay (default Page Up).\n\n"
);
@@ -721,7 +721,7 @@ namespace overlay::windows {
if (style_color_pushed) {
ImGui::PopStyleColor(style_color_pushed);
}
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch);
}
@@ -762,7 +762,7 @@ namespace overlay::windows {
patch.last_status = is_patch_active(patch);
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch);
}
@@ -791,7 +791,7 @@ namespace overlay::windows {
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch);
}
} else if (patch.type == PatchType::Integer) {
@@ -807,7 +807,7 @@ namespace overlay::windows {
apply_patch(patch, true);
config_dirty = true;
}
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch);
}
}
@@ -824,7 +824,7 @@ namespace overlay::windows {
ImGui::InputInt("##dummy_int_input", &patch.patch_number.value);
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered()) {
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
show_patch_tooltip(patch);
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ namespace overlay::windows {
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
} else {
this->disabled_message = "Overlay unavailable in windowed mode! Use the second window instead.";
this->disabled_message = "Overlay unavailable in windowed mode! Use the second window instead. (ALT+TAB)";
}
}