Compare commits

..

33 Commits

Author SHA1 Message Date
bicarus a8419bbaa0 grpahics: fix borderless for ddr/gitadora (#638)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Allow DDR to be launched in borderless window (honor the spicecfg
option).

Prevent Gitadora arena model from changing the window decoration once
launched as it hangs the game.

## Testing
Tested 32/64bit ddr, gitadora arena.
2026-04-14 18:11:42 -07:00
bicarus 0fc430ceb3 popn: add io emulation for more lights and coin (#637)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Wire up coin and more lights to bi3a_hook.

## Testing
Tested on U region
2026-04-14 09:46:13 -07:00
bicarus e59a50b64c popn: boot with 3 monitors (#636)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Take the monitor hooks added for IIDX/SDVX #612 and apply it to popn as
well now that popn has a subscreen.

There is a debug option in game that actually uses three monitors, which
can be activated via hex edits. This will prevent that from working.

## Testing
Tested with 3 monitors, fs and windowed.
2026-04-13 23:02:56 -07:00
bicarus 2042b629e9 popn: allow game to launch on PCs with only one monitor (#633)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
The game insists on having two monitors. Hook various DX9 and Win32
monitor APIs to fake a monitor if the user only has one. This correctly
convinces the game that it needs to draw two screens instead of just
one.

## Testing
WIP
2026-04-13 03:00:47 -07:00
bicarus 440b9ed749 overlay: fix how input is handled when window loses focus (#632)
## Link to GitHub Issue or related Pull Request, if one exists
#286 

## Description of change
When we migrated to event-based I/O for ImGui, it introduced an issue
where a button would be held down even when the window is not in focus,
because we didn't notify ImGui that the button was released (after the
window lost focus).

Fix the ImGui custom I/O handler so that it immediately releases all
buttons when the window focus is lost.

## Testing
tested on 32-bit windowed mode
2026-04-12 04:44:46 -07:00
bicarus 9936088286 popn: subscreen overlay (#631)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change

Add overlay for subscreen.

Add an option for no subscreen window.

Use the same "force redraw subscreen" logic we used in SDVX to fix the
same issue.

Known issues:

* still crashing when running windowed if there is only one monitor
* having only one monitor causes sunscreen overlay to not work
(NumberOfAdaptersInGroup issue?)

## Testing
2026-04-12 02:11:24 -07:00
bicarus 4d8a6c1952 graphics: fix window position if out of bounds (#630)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Before calling `CreateWindowEx[A/W]`, check dimensions of the window
about to be created, and check if it will be out of bounds (completely
or partially).

If it's completely out of bounds, move it to `(0, 0)` to prevent crashes
or rendering issues.

## Testing
Tested popn and sdvx.
2026-04-11 20:12:43 -07:00
bicarus af69cf9251 popn: new cab lights (#629)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Basic support for lights - subwoofer RGB and button lights.

## Testing
Tested on title screen and in-game.

Tested beat pop as well since that lights up the colors in white/blue.
2026-04-11 18:08:45 -07:00
bicarus 1d5550b1e8 popn: implement bi3a-hook (bio3) (#626)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Turns out running this with bi2x doesn't process buttons properly, so we
have to use bi3a.

## Testing
Seems playable now.
2026-04-11 14:43:45 -07:00
bicarus ec3f6639b7 ccj: fix locale issues with carding in (#628)
## Link to GitHub Issue or related Pull Request, if one exists
#624 

## Description of change
See bug. Add locale hooks specifically for CCJ.

also, fix y axis direction for XInput, it was flipped because XInput
uses opposite values from what RawInput does

## Testing
Tested card in / play one round with Windows regional format set to
French (France) and other language set to English.
2026-04-11 14:42:51 -07:00
bicarus 727b99effd rawinput: fix ID for rawinput devices (#627)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
The IDs don't really get used anywhere if it's not a real HID or MIDI
device, but putting them for completeness since they show up in Controls
overlay menu.

## Testing
2026-04-11 12:04:12 -07:00
bicarus 8afff3aaca popn: implement bi2x hook (#623)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Implement BI2X hook I/O for service/test/coin/headphones and the deka
pop-kuns.
2026-04-10 17:46:12 -07:00
bicarus b281517429 popn: hook DisplayConfigGetDeviceInfo to allow for any GPU port to be used for monitor, get past I/O check (#622)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
The game expects the main display to be *not* HDMI, and at port 2.

Hooking DisplayConfigGetDeviceInfo allows us to boot the game on any
monitor.

Add basic I/O hook. It gets stuck in `USB I/O Checking...` for a couple
minutes but it'll eventually skip and boot to title screen.

## Testing
ugh.
2026-04-10 02:56:32 -07:00
bicarus 54ec9f272e popn: new cab windowed mode (#621)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Get windowed mode to work.

## Testing
tbd... i/o doesn't work right now but it launches two windows at
least...
2026-04-10 01:05:44 -07:00
bicarus a159691097 xinput: bind UI, vibration, hotplug (#620)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
`Naive` button is now called `Naive/XInput` and detects XInput
controllers

Add vibration output as "Lights"

Add proper hotplug support and multiple controllers

Fix misc bugs on x86

## Testing
I tested with two xbox controllers.
2026-04-10 01:04:20 -07:00
bicarus ca70f7b19c popn: distinguish between hpm and new popn (#619)
## Link to GitHub Issue or related Pull Request, if one exists
#618

## Description of change
Enhance game detection logic.

Fix heap size.
2026-04-09 19:54:27 -07:00
bicarus 7b4227d2a9 rawinput: add basic support for xinput (#617)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
Adds preliminary support for XInput controllers. Enough to manually bind
buttons and map analogs.
2026-04-09 02:49:20 -07:00
bicarus f89efa2558 build: flatten file structure in release archives (#615)
Before:

```
.\spice2x\spice.exe
.\spice2x\spicecfg.exe
.\spice2x\spice64.exe
.\spice2x\stubs\..
.\spice2x\extras\..
```

after:

```
.\spice.exe
.\spicecfg.exe
.\spice64.exe
.\stubs\..
.\extras\..
```
2026-04-07 16:48:31 -07:00
bicarus 6f4824b648 misc: improve monitor logging (#614) 2026-04-06 22:22:34 -07:00
bicarus 45debeb5c9 iidx, sdvx: booting fullscreen with three or more monitors (#612)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

Hoping that #180 is also addressed by this change, but I can't test it.

## Description of change

IIDX (TDJ) and SDVX (Valk) will now be able to boot in full screen even
with 3 or more monitors attached.

spice will "hide" from the game any monitors other than the primary
monitor and one other monitor for subscreen, allowing it to boot without
crashing - accomplished by clearing `DISPLAY_DEVICE_ATTACHED_TO_DESKTOP`
flag when monitors are enumerated over `EnumDisplayDevicesA`, and
fortunately both games seem to respect that flag.

Add options (one for IIDX, another for SDVX) that lets you specify the
monitor ID of the second monitor.

When the option is set, user-specified monitor will be used as the
subscreen. When the option is not set, spice will just pick the
lowest-indexed monitor as the subscreen, not counting the primary one of
course.

## Testing
Tested both games with three monitors. Should probably work for four or
more.
2026-04-06 02:59:12 -07:00
bicarus-dev a01f452eba fix build script 2026-04-06 02:37:40 -07:00
Will e452734bc1 Build a WinXP compatible version of Spice (#611)
This was a lot more background work than expected, but that is the
nature of unnecessary hobbies I guess. Most things in here are obvious,
so here's the non-obvious thoughts and notes:

1. All the XP stuff is optional, if the toolchain isn't available it
won't even try
2. The docker build is the "known good" build and now has all the
required tools for XP and checking binaries
a. If you're OK with it, can add the XP_MUST_BUILD flag to
build_docker.sh
4. New `SPICE_XP` flag to comment out blocks
5. We could technically build using the Docker image I've made for my XP
compatible LLVM toolchain, but combining the binaries into a single zip
at the end of it is somewhat tedious, so I've chosen to grab the
binaries from my release
6. I also wrote a DLL checker to make sure the final binaries are
_actually_ XP compatible, which found some issues on x86_64 (I don't
know a single game that runs on 64 bit XP, but it wasn't too much effort
so whatever). This also won't run if missing, just to save the trouble.
7. The build script is still pretty shit, I've made some minor
improvements
  a. `/usr/bin/env bash` instead of `/bin/bash` as it's more correct
  b. Adding `-it` to the docker build, so Ctrl+C actually halts it
c. Main issue is that failures don't halt, because of the custom error
catching function. This has caused a lot of frustration, maybe we can
change that

## Testing
It runs on 32 and 64 bit installs of XP. My jubeat cab is on Win10 these
days and I haven't got around to testing an actual game, but I've
distributed test builds of this to people who *have* confirmed it works
as expected.
2026-04-06 01:14:29 -07:00
bicarus d1779b93fa overlay: remove tab stop from invisible selectable used in options tab (#613)
#598 added extra tab stops to the invisible Selectable widgets, which is
only used for detecting mouse hover
2026-04-06 01:02:56 -07:00
bicarus fe9aac7e29 overlay: fix cosmetic issue in options tab when there are no options for the game (#610)
Number of columns was modified in #598 but it didn't correctly account
for the case where a game doesn't have any game-specific options.
2026-04-05 19:01:09 -07:00
bicarus-dev f3d887ebcf update strings 2026-04-05 17:03:23 -07:00
bicarus 336500c8ae ccj: fix D3D10Warp.dll_unloaded issue (#609)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Fix random crashes with CCJ.

This should remove the need to add `d3d10warp.dll` to `
preload_libraries` in `ee-config.xml`.

## Testing
Tried a couple games and didn't run into the crash..
2026-04-05 16:18:19 -07:00
bicarus ee3fa58bfa graphics: option to change primary monitor before launching game (#608)
## Description of change

Adds `-mainmonitor` option which changes the monitor layout before
launching the game. This is much more reliable than the old `-monitor`
option which operated at DX9 level. This works for TDJ/UFC subscreens,
for example.

Rename `-monitor` option to `-dx9mainadapter` to discourage use.
`-monitor` will continue to work, of course.

Changing of primary monitor happens first before any orientation changes
/ refresh rate changes, which means those options will result in the
logically correct configuration.

Create a new option category for `Monitor` (rotate, refresh rate, etc).

Add a monitor selection widget for `-mainmonitor` option in the
configurator.

Improve how we log names of monitors in the log during boot - should be
less of "Generic PnP Monitor" after this.
2026-04-04 23:26:38 -07:00
bicarus a4c3eddc2f graphics: rewrite auto-rotate and refresh rate options to be more reliable (#607)
## Description of change

Rotate monitor & change the refresh rate **well before** the game is
launched.

This is much faster and more reliable compared to what we do now, which
is trying to do monitor changes during CreateWindow hooks. For example,
SDVX makes a bunch of OS / NVAPI calls to figure out monitor geometry
while it's booting, and by the time CreateWindow is called, the game
already made up its mind, which leads to weird graphical issues.

This affects both windowed mode and full screen.

In addition, add options for normal orientation (landscape) and upside
down (landscape, flipped) options. This can be used if the monitor is
normally rotated to portrait, but the game needs to be in landscape.
2026-04-04 03:23:28 -07:00
bicarus e0f6cb2cbd api: dump key binding strings (#606)
Print out human readable text for vKey when dumping bindings.
2026-04-03 20:22:24 -07:00
bicarus 1ad6a9412e imgui: update imgui to v1.92.7, update dx9 backend to latest (#605) 2026-04-03 01:51:08 -07:00
bicarus 5af3e0d494 imgui: migrate to new event-based IO (#604)
## Link to GitHub Issue or related Pull Request, if one exists
#287

## Description of change
Switch over to new event-based I/O system in ImGui so that we can keep
up with ImGui updates.

Unlike #287, this change does not modify how mouse position is handled.
That continues to be supported by newer versions of ImGui.

## Testing
wip
2026-04-02 21:00:42 -07:00
bicarus-dev b1ee828457 add some logging to device reload 2026-04-01 02:51:03 -07:00
bicarus 0d60649715 overlay: small tweaks to cards tab (#602)
## Link to GitHub Issue or related Pull Request, if one exists
#593 

## Description of change
* Display a confirmation dialog when clicking on `Generate` for the
first time (to prevent accidental loss of card number)
* If `cardN.txt` contents are read back successfully, show card number
in green to indicate success, and red in case of failure.
2026-03-31 00:07:34 -07:00
80 changed files with 17772 additions and 8283 deletions
+4
View File
@@ -383,6 +383,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/shared/twtouch.cpp
games/popn/popn.cpp
games/popn/io.cpp
games/popn/bi3a_hook.cpp
games/bbc/bbc.cpp
games/bbc/io.cpp
games/hpm/hpm.cpp
@@ -582,6 +583,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/log.cpp
overlay/windows/midi.cpp
overlay/windows/patch_manager.cpp
overlay/windows/popn_sub.cpp
overlay/windows/wnd_manager.cpp
# rawinput
@@ -596,6 +598,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
rawinput/smxstage.h
rawinput/smxdedicab.cpp
rawinput/smxdedicab.h
rawinput/xinput.cpp
rawinput/xinput.h
# reader
reader/reader.cpp
+10
View File
@@ -7,6 +7,7 @@
#include <stdint.h>
#include "external/robin_hood.h"
#include "games/popn/popn.h"
#include "launcher/logger.h"
#include "launcher/signal.h"
#include "util/deferlog.h"
@@ -1179,6 +1180,15 @@ namespace avs {
return;
}
// hack, both hello! popn and popn hc using popn.dll
if (games::popn::is_pikapika_model()) {
auto old_size = HEAP_SIZE;
HEAP_SIZE = 0x20000000;
DEFAULT_HEAP_SIZE_SET = true;
log_info("avs-core", "updated heap size: {} -> {}", old_size, avs::core::HEAP_SIZE);
return;
}
if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) {
auto old_size = HEAP_SIZE;
+90 -4
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# set to EN-US for consistency
LANG=en_us_8859_1
@@ -43,10 +43,16 @@ GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse HEAD || echo "none")
TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}"
TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}"
TOOLCHAIN_WINXP_32="${TOOLCHAIN_WINXP_32:-"/opt/llvm-mingw-xp/toolchain-files/cmake/i686-mingw32-clang.cmake"}"
TOOLCHAIN_WINXP_64="${TOOLCHAIN_WINXP_64:-"/opt/llvm-mingw-xp/toolchain-files/cmake/x86_64-mingw32-clang.cmake"}"
BUILDDIR_32_RELEASE="./cmake-build-release-32"
BUILDDIR_32_DEBUG="./cmake-build-debug-32"
BUILDDIR_64_RELEASE="./cmake-build-release-64"
BUILDDIR_64_DEBUG="./cmake-build-debug-64"
BUILDDIR_WINXP_32_RELEASE="./cmake-build-release-winxp-32"
BUILDDIR_WINXP_32_DEBUG="./cmake-build-debug-winxp-32"
BUILDDIR_WINXP_64_RELEASE="./cmake-build-release-winxp-64"
BUILDDIR_WINXP_64_DEBUG="./cmake-build-debug-winxp-64"
DEBUG=0
OUTDIR="./bin/spice2x"
OUTDIR_EXTRAS="./bin/spice2x/extras"
@@ -64,19 +70,36 @@ DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
TARGETS_XP32="spicetools_cfg spicetools_spice"
TARGETS_XP64="spicetools_spice64"
# determine build type
BUILD_TYPE="Release"
BUILDDIR_32=${BUILDDIR_32_RELEASE}
BUILDDIR_64=${BUILDDIR_64_RELEASE}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_RELEASE}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_RELEASE}
if ((DEBUG > 0))
then
BUILD_TYPE="Debug"
BUILDDIR_32=${BUILDDIR_32_DEBUG}
BUILDDIR_64=${BUILDDIR_64_DEBUG}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_DEBUG}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_DEBUG}
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
fi
# is the XP-compatible toolchain installed?
XP_MUST_BUILD=0
BUILD_XP=0
if [ -f "$TOOLCHAIN_WINXP_32" ] && [ -f "$TOOLCHAIN_WINXP_64" ]; then
BUILD_XP=1;
elif ((XP_MUST_BUILD > 0))
then
echo "WinXP toolchain not available, aborting"
exit 1
fi
# determine number of cores
CORES=$(nproc)
@@ -90,6 +113,13 @@ echo "Git Branch: $GIT_BRANCH"
echo "Git Head: $GIT_HEAD"
echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
if ((BUILD_XP > 0))
then
echo "Toolchain for WinXP 32bit targets: $TOOLCHAIN_WINXP_32"
echo "Toolchain for WinXP 64bit targets: $TOOLCHAIN_WINXP_64"
else
echo "WinXP toolchain not available, skipping WinXP builds"
fi
echo "Distribution Name: $DIST_NAME"
echo "Build Type: $BUILD_TYPE"
echo "Cores: $CORES"
@@ -130,11 +160,57 @@ time (
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
popd > /dev/null
if ((BUILD_XP > 0))
then
# 32 bit Windows XP
echo "Building 32bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_32}
fi
mkdir -p ${BUILDDIR_WINXP_32}
pushd ${BUILDDIR_WINXP_32} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP32}
popd > /dev/null
# 64 bit Windows XP
echo ""
echo "Building 64bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_64}
fi
mkdir -p ${BUILDDIR_WINXP_64}
pushd ${BUILDDIR_WINXP_64} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64}
popd > /dev/null
else
echo "Skipping WinXP builds, toolchain not specified"
fi
echo ""
echo "Compilation process done :)"
echo "==========================="
)
if ((BUILD_XP > 0))
then
echo ""
echo "Checking XP compatibility..."
echo "============================="
if ! command -v windows_dll_compat_checker &> /dev/null; then
echo "WARNING: windows_dll_compat_checker not found, skipping XP compatibility check"
else
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
windows_dll_compat_checker -s PREMADE/winxp_x86_64_32bit_dlls.ini \
${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe \
${BUILDDIR_WINXP_32}/spicetools/32/spice.exe
fi
fi
# generate PDBs
if false # ((DEBUG > 0))
then
@@ -191,6 +267,10 @@ rm -rf ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
mkdir -p ${OUTDIR_EXTRAS}/linux
if ((BUILD_XP > 0))
then
mkdir -p ${OUTDIR_EXTRAS}/winxp
fi
if false # ((DEBUG > 0))
then
@@ -226,6 +306,12 @@ else
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
if ((BUILD_XP > 0))
then
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_32}/spicetools/32/spice.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
fi
fi
# pack source files to output directory
@@ -250,10 +336,10 @@ then
echo "Building dist..."
mkdir -p ${DIST_FOLDER}
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
pushd ${OUTDIR}/.. > /dev/null
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
pushd ${OUTDIR} > /dev/null
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" . -x "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}" . -z <<< "$DIST_COMMENT"
popd > /dev/null
fi
+10 -2
View File
@@ -1,4 +1,12 @@
#!/bin/bash
#!/usr/bin/env bash
set -eu
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 "$@"
# Interactive TTY if available, so docker build can be Ctrl+C'd
DOCKER_FLAGS=""
[ -t 0 ] && DOCKER_FLAGS="-it"
docker run $DOCKER_FLAGS --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
+4
View File
@@ -90,6 +90,10 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
}
}
case rawinput::XINPUT_GAMEPAD:
return fmt::format("{} ({})",
xinput::get_analog_string(static_cast<xinput::XInputAnalogEnum>(index)),
device->desc);
case rawinput::DESTROYED:
return "Device unplugged (" + indexString + ")";
default:
+40 -5
View File
@@ -1,5 +1,6 @@
#include "api.h"
#include <cassert>
#include <optional>
#include "launcher/superexit.h"
@@ -389,6 +390,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
}
case rawinput::PIUIO_DEVICE: {
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
}
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
state = manager->XINPUT_MGR->is_button_pressed(
player,
static_cast<xinput::XInputButtonEnum>(vKey)) ?
BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
}
default:
break;
@@ -586,7 +597,7 @@ float Buttons::getVelocity(std::unique_ptr<rawinput::RawInputManager> &manager,
}
}
float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog) {
float value = 0.5f;
if (!device) {
return value;
@@ -767,6 +778,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
}
break;
}
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
value = manager->XINPUT_MGR->get_analog_state(
player,
static_cast<xinput::XInputAnalogEnum>(index));
// invert value
if (inverted) {
value = 1.f - value;
}
break;
}
default:
break;
}
@@ -822,7 +845,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana
return analog.getLastState();
}
float state = getState(device, analog);
float state = getState(manager, device, analog);
analog.setLastState(state);
return state;
@@ -904,7 +927,7 @@ void GameAPI::Lights::sortLightsWithCategory(
}
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) {
void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value) {
// check device
if (!device) {
@@ -991,6 +1014,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
}
break;
}
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
if (index < static_cast<int>(xinput::XInputOutputEnum::COUNT)) {
manager->XINPUT_MGR->set_output_state(
player, static_cast<xinput::XInputOutputEnum>(index), value);
// no need to set output_pending; xinput output handled immediately
} else {
log_warning("api", "invalid xinput light index: {}", index);
}
break;
}
default:
break;
}
@@ -1016,9 +1051,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh
// write state
if (light.override_enabled) {
writeLight(device, light.getIndex(), light.override_state);
writeLight(manager, device, light.getIndex(), light.override_state);
} else {
writeLight(device, light.getIndex(), value);
writeLight(manager, device, light.getIndex(), value);
}
}
+2 -2
View File
@@ -103,7 +103,7 @@ namespace GameAPI {
}
}
float getState(rawinput::Device *device, Analog &analog);
float getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog);
float getState(rawinput::RawInputManager *manager, Analog &analog);
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
}
@@ -135,7 +135,7 @@ namespace GameAPI {
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
void writeLight(rawinput::Device *device, int index, float value);
void writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value);
void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
+6
View File
@@ -278,6 +278,8 @@ std::string Button::getVKeyString() {
return "Left Alt";
case 0xA5:
return "Right Alt";
case INVALID_VKEY:
return "Invalid";
default:
// check win API
@@ -415,6 +417,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
}
case rawinput::PIUIO_DEVICE:
return "PIUIO " + vKeyString;
case rawinput::XINPUT_GAMEPAD:
return fmt::format("{} ({})",
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
device->desc);
case rawinput::DESTROYED:
return "Device unplugged (" + vKeyString + ")";
default:
+9 -1
View File
@@ -83,12 +83,20 @@ public:
if (this->override_enabled) {
return true;
}
// has a device
if (!this->device_identifier.empty()) {
return true;
}
// naive, and has a valid vKey
if (this->vKey != INVALID_VKEY) {
return true;
}
for (auto &alternative : this->alternatives) {
if (alternative.vKey != INVALID_VKEY) {
if (!alternative.getDeviceIdentifier().empty() ||
alternative.getVKey() != INVALID_VKEY) {
return true;
}
}
+9
View File
@@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
return "Invalid SMX Dedicab Light (" + index_string + ")";
}
case rawinput::XINPUT_GAMEPAD: {
if (index < static_cast<size_t>(xinput::XInputOutputEnum::COUNT)) {
return fmt::format("{} ({})",
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(index)),
device->desc);
}
return "Invalid XInput Gamepad Light (" + index_string + ")";
}
case rawinput::DESTROYED:
return "Unplugged device (" + index_string + ")";
default:
+1
View File
@@ -20,6 +20,7 @@ enum class OptionPickerType {
CpuAffinity,
FilePath,
DirectoryPath,
Monitor,
};
struct OptionDefinition {
+8
View File
@@ -15,3 +15,11 @@ RUN pacman --noconfirm -Syu git \
ccache
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
RUN mkdir -p /opt/llvm-mingw-xp \
&& curl -fsSL "https://github.com/mon/llvm-mingw-xp/releases/download/llvm-mingw-xp-22.1.0/llvm-mingw-llvm-mingw-xp-22.1.0-msvcrt-ubuntu-22.04-x86_64.tar.xz" \
| tar -xJ --strip-components=1 -C /opt/llvm-mingw-xp
ENV PATH="$PATH:/opt/llvm-mingw-xp/bin"
RUN curl -fsSL "https://github.com/mon/windows-dll-compat-checker/releases/download/v1.3/windows_dll_compat_checker-linux-x86_64.tar.xz" \
| tar -xJ -C /usr/local/bin
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2023 Omar Cornut
Copyright (c) 2014-2026 Omar Cornut
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+645 -269
View File
@@ -1,16 +1,37 @@
// dear imgui: Renderer for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32)
// dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-03-19: Fixed issue in ImGui_ImplDX9_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575)
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-06-25: DirectX9: Explicitly disable texture state stages after >= 1.
// 2021-05-19: DirectX9: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-04-23: DirectX9: Explicitly setting up more graphics states to increase compatibility with unusual non-default states.
// 2021-03-18: DirectX9: Calling IDirect3DStateBlock9::Capture() after CreateStateBlock() as a workaround for state restoring issues (see #3857).
// 2021-03-03: DirectX9: Added support for IMGUI_USE_BGRA_PACKED_COLOR in user's imconfig file.
// 2021-02-18: DirectX9: Change blending equation to preserve alpha in output buffer.
// 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects().
@@ -22,160 +43,154 @@
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
#include "imgui_impl_dx9.h"
#include <algorithm>
// DirectX
#include <d3d9.h>
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_dx9.h"
// allow std::min use
#ifdef min
#undef min
#endif
// DirectX data
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;
static LPDIRECT3DTEXTURE9 g_FontTexture = NULL;
static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000;
// DirectX
#include <d3d9.h>
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// DirectX data
struct ImGui_ImplDX9_Data
{
LPDIRECT3DDEVICE9 pd3dDevice;
LPDIRECT3DVERTEXBUFFER9 pVB;
LPDIRECT3DINDEXBUFFER9 pIB;
int VertexBufferSize;
int IndexBufferSize;
bool HasRgbaSupport;
ImGui_ImplDX9_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
};
struct CUSTOMVERTEX
{
float pos[3];
D3DCOLOR col;
float uv[2];
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
// Render function.
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
#define IMGUI_COL_TO_DX9_ARGB(_COL) (_COL)
#else
#define IMGUI_COL_TO_DX9_ARGB(_COL) (((_COL) & 0xFF00FF00) | (((_COL) & 0xFF0000) >> 16) | (((_COL) & 0xFF) << 16))
#endif
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Create and grow buffers if needed
if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) {
if (g_pVB) {
g_pVB->Release();
g_pVB = NULL;
}
g_VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (g_pd3dDevice->CreateVertexBuffer(g_VertexBufferSize * sizeof(ImDrawVert),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL) < 0)
return;
}
if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) {
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
g_IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (g_pd3dDevice->CreateIndexBuffer(g_IndexBufferSize * sizeof(ImDrawIdx),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32,
D3DPOOL_DEFAULT, &g_pIB, NULL) < 0)
return;
}
// Forward Declarations
static void ImGui_ImplDX9_InitMultiViewportSupport();
static void ImGui_ImplDX9_ShutdownMultiViewportSupport();
static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
// Backup the DX9 state
IDirect3DStateBlock9 *d3d9_state_block = NULL;
if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0)
return;
struct render_state_t {
IDirect3DSurface9 *back_buffer;
IDirect3DSurface9 *depth_stencil;
IDirect3DSurface9 *render_targets[8];
D3DCAPS9 caps;
};
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
D3DMATRIX last_world, last_view, last_projection;
g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world);
g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view);
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection);
// Copy all vertices into a single contiguous buffer
ImDrawVert *vtx_dst;
ImDrawIdx *idx_dst;
if (g_pVB->Lock(0, (UINT) (draw_data->TotalVtxCount * sizeof(ImDrawVert)), (void **) &vtx_dst,
D3DLOCK_DISCARD) < 0)
return;
if (g_pIB->Lock(0, (UINT) (draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void **) &idx_dst,
D3DLOCK_DISCARD) < 0)
return;
for (int n = 0; n < draw_data->CmdListsCount; n++) {
const ImDrawList *cmd_list = draw_data->CmdLists[n];
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
vtx_dst += cmd_list->VtxBuffer.Size;
idx_dst += cmd_list->IdxBuffer.Size;
}
g_pVB->Unlock();
g_pIB->Unlock();
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(ImDrawVert));
g_pd3dDevice->SetIndices(g_pIB);
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
// Functions
static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data, struct render_state_t *render_state)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
// Setup viewport
D3DVIEWPORT9 vp;
vp.X = vp.Y = 0;
vp.Width = (DWORD) draw_data->DisplaySize.x;
vp.Height = (DWORD) draw_data->DisplaySize.y;
vp.Width = (DWORD)draw_data->DisplaySize.x;
vp.Height = (DWORD)draw_data->DisplaySize.y;
vp.MinZ = 0.0f;
vp.MaxZ = 1.0f;
g_pd3dDevice->SetViewport(&vp);
g_pd3dDevice->SetPixelShader(nullptr);
g_pd3dDevice->SetVertexShader(nullptr);
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
device->SetViewport(&vp);
D3DCAPS9 caps {};
if (FAILED(g_pd3dDevice->GetDeviceCaps(&caps))) {
caps.NumSimultaneousRTs = 0UL;
}
// spice ldj
{
if (FAILED(device->GetDeviceCaps(&render_state->caps))) {
render_state->caps.NumSimultaneousRTs = 0UL;
}
IDirect3DSurface9 *back_buffer = nullptr;
IDirect3DSurface9 *depth_stencil = nullptr;
IDirect3DSurface9 *render_targets[8];
// save all previous render target state
for (size_t target = 0; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
if (FAILED(device->GetRenderTarget(target, &render_state->render_targets[target]))) {
render_state->render_targets[target] = nullptr;
}
}
// save all previous render target state
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
if (FAILED(g_pd3dDevice->GetRenderTarget(target, &render_targets[target]))) {
render_targets[target] = nullptr;
// get the previous depth stencil
if (FAILED(device->GetDepthStencilSurface(&render_state->depth_stencil))) {
render_state->depth_stencil = nullptr;
}
// set the back buffer as the current render target
if (SUCCEEDED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &render_state->back_buffer))) {
device->SetRenderTarget(0, render_state->back_buffer);
device->SetDepthStencilSurface(nullptr);
for (size_t target = 1; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
device->SetRenderTarget(target, nullptr);
}
} else {
render_state->back_buffer = nullptr;
}
}
// get the previous depth stencil
if (FAILED(g_pd3dDevice->GetDepthStencilSurface(&depth_stencil))) {
depth_stencil = nullptr;
}
// set the back buffer as the current render target
if (SUCCEEDED(g_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) {
g_pd3dDevice->SetRenderTarget(0, back_buffer);
g_pd3dDevice->SetDepthStencilSurface(nullptr);
for (size_t target = 1; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
g_pd3dDevice->SetRenderTarget(target, nullptr);
}
} else {
back_buffer = nullptr;
}
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient)
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, false);
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false);
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false);
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, true);
g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, false);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
device->SetPixelShader(nullptr);
device->SetVertexShader(nullptr);
device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
device->SetRenderState(D3DRS_ZENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
device->SetRenderState(D3DRS_FOGENABLE, FALSE);
device->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
device->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
device->SetRenderState(D3DRS_CLIPPING, TRUE);
device->SetRenderState(D3DRS_LIGHTING, FALSE);
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
device->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
// Setup orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
@@ -185,176 +200,537 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
float T = draw_data->DisplayPos.y + 0.5f;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
D3DMATRIX mat_identity = {{{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}};
D3DMATRIX mat_identity = { { { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f } } };
D3DMATRIX mat_projection =
{{{
2.0f / (R - L), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / (T - B), 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
(L + R) / (L - R), (T + B) / (B - T), 0.5f, 1.0f
}}};
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
{ { {
2.0f/(R-L), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f/(T-B), 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
(L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f
} } };
device->SetTransform(D3DTS_WORLD, &mat_identity);
device->SetTransform(D3DTS_VIEW, &mat_identity);
device->SetTransform(D3DTS_PROJECTION, &mat_projection);
}
}
// Render function.
void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX9_UpdateTexture(tex);
// Create and grow buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (device->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
return;
}
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
{
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (device->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
return;
}
// Backup the DX9 state
IDirect3DStateBlock9* state_block = nullptr;
if (device->CreateStateBlock(D3DSBT_ALL, &state_block) < 0)
return;
if (state_block->Capture() < 0)
{
state_block->Release();
return;
}
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
D3DMATRIX last_world, last_view, last_projection;
device->GetTransform(D3DTS_WORLD, &last_world);
device->GetTransform(D3DTS_VIEW, &last_view);
device->GetTransform(D3DTS_PROJECTION, &last_projection);
// Allocate buffers
CUSTOMVERTEX* vtx_dst;
ImDrawIdx* idx_dst;
if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0)
{
state_block->Release();
return;
}
if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0)
{
bd->pVB->Unlock();
state_block->Release();
return;
}
// Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format.
// FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and
// 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR
// 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
const ImDrawVert* vtx_src = draw_list->VtxBuffer.Data;
for (int i = 0; i < draw_list->VtxBuffer.Size; i++)
{
vtx_dst->pos[0] = vtx_src->pos.x;
vtx_dst->pos[1] = vtx_src->pos.y;
vtx_dst->pos[2] = 0.0f;
vtx_dst->col = IMGUI_COL_TO_DX9_ARGB(vtx_src->col);
vtx_dst->uv[0] = vtx_src->uv.x;
vtx_dst->uv[1] = vtx_src->uv.y;
vtx_dst++;
vtx_src++;
}
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
idx_dst += draw_list->IdxBuffer.Size;
}
bd->pVB->Unlock();
bd->pIB->Unlock();
device->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
device->SetIndices(bd->pIB);
device->SetFVF(D3DFVF_CUSTOMVERTEX);
// Setup desired DX state
struct render_state_t render_state = {};
ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
// Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_vtx_offset = 0;
int global_idx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++) {
const ImDrawList *cmd_list = draw_data->CmdLists[n];
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != NULL) {
pcmd->UserCallback(cmd_list, pcmd);
} else {
const RECT r = {(LONG) (pcmd->ClipRect.x - clip_off.x), (LONG) (pcmd->ClipRect.y - clip_off.y),
(LONG) (pcmd->ClipRect.z - clip_off.x), (LONG) (pcmd->ClipRect.w - clip_off.y)};
auto texture = reinterpret_cast<IDirect3DBaseTexture9 *>(pcmd->TextureId);
g_pd3dDevice->SetTexture(0, texture);
g_pd3dDevice->SetScissorRect(&r);
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
pcmd->VtxOffset + global_vtx_offset, 0,
(UINT) cmd_list->VtxBuffer.Size,
pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr)
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
else
pcmd->UserCallback(draw_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
// Apply scissor/clipping rectangle
const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
device->SetScissorRect(&r);
// Bind texture, Draw
const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->GetTexID();
device->SetTexture(0, texture);
device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)draw_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
}
}
global_idx_offset += cmd_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size;
global_idx_offset += draw_list->IdxBuffer.Size;
global_vtx_offset += draw_list->VtxBuffer.Size;
}
if (back_buffer) {
back_buffer->Release();
back_buffer = nullptr;
}
// When using multi-viewports, it appears that there's an odd logic in DirectX9 which prevent subsequent windows
// from rendering until the first window submits at least one draw call, even once. That's our workaround. (see #2560)
if (global_vtx_offset == 0)
bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 0, 0, 0);
// restore previous depth stencil
if (depth_stencil) {
g_pd3dDevice->SetDepthStencilSurface(depth_stencil);
depth_stencil->Release();
depth_stencil = nullptr;
}
// spice ldj
{
if (render_state.back_buffer) {
render_state.back_buffer->Release();
render_state.back_buffer = nullptr;
}
// restore previous depth stencil
if (render_state.depth_stencil) {
device->SetDepthStencilSurface(render_state.depth_stencil);
render_state.depth_stencil->Release();
render_state.depth_stencil = nullptr;
}
// restore all render target state
for (size_t target = 0; target < std::min(8UL, render_state.caps.NumSimultaneousRTs); target++) {
auto render_target = render_state.render_targets[target];
// restore all render target state
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
auto render_target = render_targets[target];
if (render_target) {
g_pd3dDevice->SetRenderTarget(target, render_target);
render_target->Release();
if (render_target) {
device->SetRenderTarget(target, render_target);
render_target->Release();
}
}
}
// restore the DX9 transform
g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection);
// Restore the DX9 transform
device->SetTransform(D3DTS_WORLD, &last_world);
device->SetTransform(D3DTS_VIEW, &last_view);
device->SetTransform(D3DTS_PROJECTION, &last_projection);
// restore the DX9 state
d3d9_state_block->Apply();
d3d9_state_block->Release();
// Restore the DX9 state
state_block->Apply();
state_block->Release();
}
bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device) {
static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMAT format)
{
LPDIRECT3D9 pd3d = nullptr;
if (pDevice->GetDirect3D(&pd3d) != D3D_OK)
return false;
D3DDEVICE_CREATION_PARAMETERS param = {};
D3DDISPLAYMODE mode = {};
if (pDevice->GetCreationParameters(&param) != D3D_OK || pDevice->GetDisplayMode(0, &mode) != D3D_OK)
{
pd3d->Release();
return false;
}
// Font texture should support linear filter, color blend and write to render-target
bool support = (pd3d->CheckDeviceFormat(param.AdapterOrdinal, param.DeviceType, mode.Format, D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER | D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, format)) == D3D_OK;
pd3d->Release();
return support;
}
// Setup back-end capabilities flags
auto &io = ImGui::GetIO();
bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Setup backend capabilities flags
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx9";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
// We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096;
bd->pd3dDevice = device;
bd->pd3dDevice->AddRef();
bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
ImGui_ImplDX9_InitMultiViewportSupport();
g_pd3dDevice = device;
g_pd3dDevice->AddRef();
return true;
}
void ImGui_ImplDX9_Shutdown() {
void ImGui_ImplDX9_Shutdown()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplDX9_ShutdownMultiViewportSupport();
ImGui_ImplDX9_InvalidateDeviceObjects();
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
if (g_pd3dDevice) {
g_pd3dDevice->Release();
g_pd3dDevice = NULL;
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
platform_io.ClearRendererHandlers();
IM_DELETE(bd);
}
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h)
{
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
const bool convert_rgba_to_bgra = (!bd->HasRgbaSupport && tex_use_colors);
#else
const bool convert_rgba_to_bgra = false;
IM_UNUSED(tex_use_colors);
#endif
for (int y = 0; y < h; y++)
{
const ImU32* src_p = (const ImU32*)(const void*)((const unsigned char*)src + src_pitch * y);
ImU32* dst_p = (ImU32*)(void*)((unsigned char*)dst + dst_pitch * y);
if (convert_rgba_to_bgra)
for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy
*dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p);
else
memcpy(dst_p, src_p, w * 4); // Raw copy
}
}
static bool ImGui_ImplDX9_CreateFontsTexture() {
// Build texture atlas
ImGuiIO &io = ImGui::GetIO();
unsigned char *pixels;
int width, height, bytes_per_pixel;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
// Upload texture to graphics system
g_FontTexture = NULL;
if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0)
return false;
D3DLOCKED_RECT tex_locked_rect;
if (g_FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK)
return false;
for (int y = 0; y < height; y++)
memcpy((unsigned char *) tex_locked_rect.pBits + tex_locked_rect.Pitch * y,
pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel));
g_FontTexture->UnlockRect(0);
// Store our identifier
io.Fonts->TexID = (ImTextureID) g_FontTexture;
return true;
}
bool ImGui_ImplDX9_CreateDeviceObjects() {
if (!g_pd3dDevice) {
return false;
}
return ImGui_ImplDX9_CreateFontsTexture();
}
void ImGui_ImplDX9_InvalidateDeviceObjects() {
if (!g_pd3dDevice)
return;
if (g_pVB) {
g_pVB->Release();
g_pVB = NULL;
}
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
if (g_FontTexture) {
g_FontTexture->Release();
g_FontTexture = NULL;
ImGui::GetIO().Fonts->TexID = 0;
} // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
}
void ImGui_ImplDX9_NewFrame() {
if (!g_FontTexture) {
ImGui_ImplDX9_CreateDeviceObjects();
}
IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(g_pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {};
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
} else {
RECT rect {};
GetClientRect(present_params.hDeviceWindow, &rect);
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
}
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
LPDIRECT3DTEXTURE9 dx_tex = nullptr;
HRESULT hr = bd->pd3dDevice->CreateTexture(tex->Width, tex->Height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &dx_tex, nullptr);
if (hr < 0)
{
IM_ASSERT(hr >= 0 && "Backend failed to create texture!");
return;
}
swap_chain->Release();
D3DLOCKED_RECT locked_rect;
if (dx_tex->LockRect(0, &locked_rect, nullptr, 0) == D3D_OK)
{
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixels(), tex->Width * 4, (ImU32*)locked_rect.pBits, (ImU32)locked_rect.Pitch, tex->Width, tex->Height);
dx_tex->UnlockRect(0);
}
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)dx_tex);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)(intptr_t)tex->TexID;
RECT update_rect = { (LONG)tex->UpdateRect.x, (LONG)tex->UpdateRect.y, (LONG)(tex->UpdateRect.x + tex->UpdateRect.w), (LONG)(tex->UpdateRect.y + tex->UpdateRect.h) };
D3DLOCKED_RECT locked_rect;
if (backend_tex->LockRect(0, &locked_rect, &update_rect, 0) == D3D_OK)
for (ImTextureRect& r : tex->Updates)
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixelsAt(r.x, r.y), tex->Width * 4,
(ImU32*)locked_rect.pBits + (r.x - update_rect.left) + (r.y - update_rect.top) * (locked_rect.Pitch / 4), (int)locked_rect.Pitch, r.w, r.h);
backend_tex->UnlockRect(0);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
if (tex->TexID != ImTextureID_Invalid)
if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID)
{
IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex);
backend_tex->Release();
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
}
tex->SetStatus(ImTextureStatus_Destroyed);
}
}
bool ImGui_ImplDX9_CreateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return false;
ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
return true;
}
void ImGui_ImplDX9_InvalidateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return;
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplDX9_UpdateTexture(tex);
}
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
}
void ImGui_ImplDX9_NewFrame()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?");
// IM_UNUSED(bd);
// spice ldj
{
IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(bd->pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {};
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
} else {
RECT rect {};
GetClientRect(present_params.hDeviceWindow, &rect);
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
}
}
swap_chain->Release();
}
}
}
//--------------------------------------------------------------------------------------------------------
// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
//--------------------------------------------------------------------------------------------------------
// Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data.
struct ImGui_ImplDX9_ViewportData
{
IDirect3DSwapChain9* SwapChain;
D3DPRESENT_PARAMETERS d3dpp;
ImGui_ImplDX9_ViewportData() { SwapChain = nullptr; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); }
~ImGui_ImplDX9_ViewportData() { IM_ASSERT(SwapChain == nullptr); }
};
static void ImGui_ImplDX9_CreateWindow(ImGuiViewport* viewport)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = IM_NEW(ImGui_ImplDX9_ViewportData)();
viewport->RendererUserData = vd;
// PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL's WindowID).
// Some backends will leave PlatformHandleRaw == 0, in which case we assume PlatformHandle will contain the HWND.
HWND hwnd = viewport->PlatformHandleRaw ? (HWND)viewport->PlatformHandleRaw : (HWND)viewport->PlatformHandle;
IM_ASSERT(hwnd != 0);
ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS));
vd->d3dpp.Windowed = TRUE;
vd->d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
vd->d3dpp.BackBufferWidth = (UINT)viewport->Size.x;
vd->d3dpp.BackBufferHeight = (UINT)viewport->Size.y;
vd->d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
vd->d3dpp.hDeviceWindow = hwnd;
vd->d3dpp.EnableAutoDepthStencil = FALSE;
vd->d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
vd->d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // Present without vsync
HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr);
IM_ASSERT(hr == D3D_OK);
IM_ASSERT(vd->SwapChain != nullptr);
}
static void ImGui_ImplDX9_DestroyWindow(ImGuiViewport* viewport)
{
// The main viewport (owned by the application) will always have RendererUserData == 0 since we didn't create the data for it.
if (ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData)
{
if (vd->SwapChain)
vd->SwapChain->Release();
vd->SwapChain = nullptr;
ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS));
IM_DELETE(vd);
}
viewport->RendererUserData = nullptr;
}
static void ImGui_ImplDX9_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
if (vd->SwapChain)
{
vd->SwapChain->Release();
vd->SwapChain = nullptr;
vd->d3dpp.BackBufferWidth = (UINT)size.x;
vd->d3dpp.BackBufferHeight = (UINT)size.y;
HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr);
IM_ASSERT(hr == D3D_OK);
}
}
static void ImGui_ImplDX9_RenderWindow(ImGuiViewport* viewport, void*)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
LPDIRECT3DSURFACE9 render_target = nullptr;
LPDIRECT3DSURFACE9 last_render_target = nullptr;
LPDIRECT3DSURFACE9 last_depth_stencil = nullptr;
vd->SwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &render_target);
bd->pd3dDevice->GetRenderTarget(0, &last_render_target);
bd->pd3dDevice->GetDepthStencilSurface(&last_depth_stencil);
bd->pd3dDevice->SetRenderTarget(0, render_target);
bd->pd3dDevice->SetDepthStencilSurface(nullptr);
if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
{
D3DCOLOR clear_col_dx = D3DCOLOR_RGBA((int)(clear_color.x*255.0f), (int)(clear_color.y*255.0f), (int)(clear_color.z*255.0f), (int)(clear_color.w*255.0f));
bd->pd3dDevice->Clear(0, nullptr, D3DCLEAR_TARGET, clear_col_dx, 1.0f, 0);
}
ImGui_ImplDX9_RenderDrawData(viewport->DrawData);
// Restore render target
bd->pd3dDevice->SetRenderTarget(0, last_render_target);
bd->pd3dDevice->SetDepthStencilSurface(last_depth_stencil);
render_target->Release();
last_render_target->Release();
if (last_depth_stencil) last_depth_stencil->Release();
}
static void ImGui_ImplDX9_SwapBuffers(ImGuiViewport* viewport, void*)
{
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
HRESULT hr = vd->SwapChain->Present(nullptr, nullptr, vd->d3dpp.hDeviceWindow, nullptr, 0);
// Let main application handle D3DERR_DEVICELOST by resetting the device.
IM_ASSERT(SUCCEEDED(hr) || hr == D3DERR_DEVICELOST);
}
static void ImGui_ImplDX9_InitMultiViewportSupport()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_CreateWindow = ImGui_ImplDX9_CreateWindow;
platform_io.Renderer_DestroyWindow = ImGui_ImplDX9_DestroyWindow;
platform_io.Renderer_SetWindowSize = ImGui_ImplDX9_SetWindowSize;
platform_io.Renderer_RenderWindow = ImGui_ImplDX9_RenderWindow;
platform_io.Renderer_SwapBuffers = ImGui_ImplDX9_SwapBuffers;
}
static void ImGui_ImplDX9_ShutdownMultiViewportSupport()
{
ImGui::DestroyPlatformWindows();
}
static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
for (int i = 1; i < platform_io.Viewports.Size; i++)
if (!platform_io.Viewports[i]->RendererUserData)
ImGui_ImplDX9_CreateWindow(platform_io.Viewports[i]);
}
static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
for (int i = 1; i < platform_io.Viewports.Size; i++)
if (platform_io.Viewports[i]->RendererUserData)
ImGui_ImplDX9_DestroyWindow(platform_io.Viewports[i]);
}
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE
+25 -12
View File
@@ -1,25 +1,38 @@
// dear imgui: Renderer for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32)
// dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once
#include "imgui.h"
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
struct IDirect3DDevice9;
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device);
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9* device);
IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data);
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data);
// Use if you want to reset your rendering device without losing ImGui state.
// Use if you want to reset your rendering device without losing Dear ImGui state.
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex);
#endif // #ifndef IMGUI_DISABLE
+13 -5
View File
@@ -15,7 +15,8 @@
#pragma once
//---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
// - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
// - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
@@ -29,7 +30,6 @@
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
@@ -49,6 +49,7 @@
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean/ProggyForever), remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Enable Test Engine / Automation features.
@@ -59,12 +60,15 @@
//#define IMGUI_INCLUDE_IMGUI_USER_H
//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h"
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another)
//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support.
#define IMGUI_USE_BGRA_PACKED_COLOR
//---- Performance optimization for DX9
#define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate.
//#define IMGUI_USE_LEGACY_CRC32_ADLER
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32
@@ -83,13 +87,13 @@
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience.
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
// Only works in combination with IMGUI_ENABLE_FREETYPE.
// - lunasvg is currently easier to acquire/install, as e.g. it is part of vcpkg.
// - plutosvg will support more fonts and may load them faster. It currently requires to be built manually but it is fairly easy. See misc/freetype/README for instructions.
// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions.
// - Both require headers to be available in the include path + program to be linked with the library code (not provided).
// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
@@ -130,6 +134,10 @@
//#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set.
// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use)
//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS
//---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID
+3439 -1953
View File
File diff suppressed because it is too large Load Diff
+1023 -517
View File
File diff suppressed because it is too large Load Diff
+2825 -2079
View File
File diff suppressed because it is too large Load Diff
+3216 -1153
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+258 -164
View File
@@ -1,6 +1,7 @@
// Mini memory editor for Dear ImGui (to embed in your game/tools)
// Get latest version at http://www.github.com/ocornut/imgui_club
//
// Licensed under The MIT License (MIT)
// Right-click anywhere to access the Options menu!
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
// The code assume a mono-space font for simplicity!
@@ -39,10 +40,22 @@
// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel]
// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width.
// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size.
// - v0.51 (2024/02/22): fix for layout change in 1.89 when using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#34)
// - v0.52 (2024/03/08): removed unnecessary GetKeyIndex() calls, they are a no-op since 1.87.
// - v0.53 (2024/05/27): fixed right-click popup from not appearing when using DrawContents(). warning fixes. (#35)
// - v0.54 (2024/07/29): allow ReadOnly mode to still select and preview data. (#46) [@DeltaGW2])
// - v0.55 (2024/08/19): added BgColorFn to allow setting background colors independently from highlighted selection. (#27) [@StrikerX3]
// added MouseHoveredAddr public readable field. (#47, #27) [@StrikerX3]
// fixed a data preview crash with 1.91.0 WIP. fixed contiguous highlight color when using data preview.
// *BREAKING* added UserData field passed to all optional function handlers: ReadFn, WriteFn, HighlightFn, BgColorFn. (#50) [@silverweed]
// - v0.56 (2024/11/04): fixed MouseHovered, MouseHoveredAddr not being set when hovering a byte being edited. (#54)
// - v0.57 (2025/03/26): fixed warnings. using ImGui's ImSXX/ImUXX types instead of e.g. int32_t/uint32_t. (#56)
// - v0.58 (2025/03/31): fixed extraneous footer spacing (added in 0.51) breaking vertical auto-resize. (#53)
// - v0.59 (2025/04/08): fixed GotoAddrAndHighlight() not working if OptShowOptions is disabled.
//
// Todo/Bugs:
// TODO:
// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day.
// - PageUp/PageDown are supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
// - PageUp/PageDown are not supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame.
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
@@ -51,15 +64,18 @@
#include <stdio.h> // sprintf, scanf
#include <stdint.h> // uint8_t, etc.
#ifdef _MSC_VER
#define _PRISizeT "I"
#if defined(_MSC_VER) && !defined(snprintf)
#define ImSnprintf _snprintf
#else
#define _PRISizeT "z"
#define ImSnprintf snprintf
#endif
#if defined(_MSC_VER) && !defined(__clang__)
#define _PRISizeT "I"
#else
#define _PRISizeT "z"
#endif
#ifdef _MSC_VER
#if defined(_MSC_VER) || defined(_UCRT)
#pragma warning (push)
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
#endif
@@ -88,9 +104,17 @@ struct MemoryEditor
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr).
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
ImU32 HighlightColor; // // background color of highlighted bytes.
ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
// Function handlers
ImU8 (*ReadFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* mem, size_t off, ImU8 d, void* user_data); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
ImU32 (*BgColorFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return custom background color of individual bytes.
void* UserData; // = NULL // user data forwarded to the function handlers
// Public read-only data
bool MouseHovered; // set when mouse is hovering a value.
size_t MouseHoveredAddr; // the address currently being hovered if MouseHovered is set.
// [Internal State]
bool ContentsWidthChanged;
@@ -101,7 +125,7 @@ struct MemoryEditor
char AddrInputBuf[32];
size_t GotoAddr;
size_t HighlightMin, HighlightMax;
int PreviewEndianess;
int PreviewEndianness;
ImGuiDataType PreviewDataType;
MemoryEditor()
@@ -120,9 +144,11 @@ struct MemoryEditor
OptAddrDigitsCount = 0;
OptFooterExtraHeight = 0.0f;
HighlightColor = IM_COL32(255, 255, 255, 50);
ReadFn = NULL;
WriteFn = NULL;
HighlightFn = NULL;
ReadFn = nullptr;
WriteFn = nullptr;
HighlightFn = nullptr;
BgColorFn = nullptr;
UserData = nullptr;
// State/Internals
ContentsWidthChanged = false;
@@ -131,8 +157,10 @@ struct MemoryEditor
memset(DataInputBuf, 0, sizeof(DataInputBuf));
memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
GotoAddr = (size_t)-1;
MouseHovered = false;
MouseHoveredAddr = 0;
HighlightMin = HighlightMax = (size_t)-1;
PreviewEndianess = 0;
PreviewEndianness = 0;
PreviewDataType = ImGuiDataType_S32;
}
@@ -145,16 +173,16 @@ struct MemoryEditor
struct Sizes
{
int AddrDigitsCount;
float LineHeight;
float GlyphWidth;
float HexCellWidth;
float SpacingBetweenMidCols;
float PosHexStart;
float PosHexEnd;
float PosAsciiStart;
float PosAsciiEnd;
float WindowWidth;
int AddrDigitsCount; // Number of digits required to represent maximum address.
float LineHeight; // Height of each line (no spacing).
float GlyphWidth; // Glyph width (assume mono-space).
float HexCellWidth; // Width of a hex edit cell ~2.5f * GlypHWidth.
float SpacingBetweenMidCols; // Spacing between each columns section (OptMidColsCount).
float OffsetHexMinX;
float OffsetHexMaxX;
float OffsetAsciiMinX;
float OffsetAsciiMaxX;
float WindowWidth; // Ideal window width.
Sizes() { memset(this, 0, sizeof(*this)); }
};
@@ -170,17 +198,17 @@ struct MemoryEditor
s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space
s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere
s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols);
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd;
s.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * Cols);
s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX;
if (OptShowAscii)
{
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1;
if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.OffsetAsciiMaxX = s.OffsetAsciiMinX + Cols * s.GlyphWidth;
}
s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
s.WindowWidth = s.OffsetAsciiMaxX + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
}
// Standalone Memory Editor window
@@ -194,8 +222,6 @@ struct MemoryEditor
Open = true;
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar))
{
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("context");
DrawContents(mem_data, mem_size, base_display_addr);
if (ContentsWidthChanged)
{
@@ -217,6 +243,8 @@ struct MemoryEditor
CalcSizes(s, mem_size, base_display_addr);
ImGuiStyle& style = ImGui::GetStyle();
const ImVec2 contents_pos_start = ImGui::GetCursorScreenPos();
// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
// This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
const float height_separator = style.ItemSpacing.y;
@@ -225,20 +253,21 @@ struct MemoryEditor
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
if (OptShowDataPreview)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3;
ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
ImGui::BeginChild("##scrolling", ImVec2(-FLT_MIN, -footer_height), ImGuiChildFlags_None, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
// We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
const ImVec2 avail_size = ImGui::GetContentRegionAvail();
const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
ImGuiListClipper clipper;
clipper.Begin(line_total_count, s.LineHeight);
bool data_next = false;
if (ReadOnly || DataEditingAddr >= mem_size)
if (DataEditingAddr >= mem_size)
DataEditingAddr = (size_t)-1;
if (DataPreviewAddr >= mem_size)
DataPreviewAddr = (size_t)-1;
@@ -249,16 +278,16 @@ struct MemoryEditor
if (DataEditingAddr != (size_t)-1)
{
// Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered)
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; }
else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols){ data_editing_addr_next = DataEditingAddr + Cols; }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; }
else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
}
// Draw vertical separator
ImVec2 window_pos = ImGui::GetWindowPos();
if (OptShowAscii)
draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
draw_list->AddLine(ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text);
const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text;
@@ -268,36 +297,51 @@ struct MemoryEditor
const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x";
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
MouseHovered = false;
MouseHoveredAddr = 0;
while (clipper.Step())
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
{
size_t addr = (size_t)(line_i * Cols);
size_t addr = (size_t)line_i * Cols;
ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
// Draw Hexadecimal
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{
float byte_pos_x = s.PosHexStart + s.HexCellWidth * n;
float byte_pos_x = s.OffsetHexMinX + s.HexCellWidth * n;
if (OptMidColsCount > 0)
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
ImGui::SameLine(byte_pos_x);
// Draw highlight
bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr));
bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
// Draw highlight or custom background color
const bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
const bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, UserData));
const bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
ImU32 bg_color = 0;
bool is_next_byte_highlighted = false;
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
{
ImVec2 pos = ImGui::GetCursorScreenPos();
float highlight_width = s.GlyphWidth * 2;
bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1)));
is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1, UserData)) || (addr + 1 < DataPreviewAddr + preview_data_type_size));
bg_color = HighlightColor;
}
else if (BgColorFn != nullptr)
{
is_next_byte_highlighted = (addr + 1 < mem_size) && ((BgColorFn(mem_data, addr + 1, UserData) & IM_COL32_A_MASK) != 0);
bg_color = BgColorFn(mem_data, addr, UserData);
}
if (bg_color != 0)
{
float bg_width = s.GlyphWidth * 2;
if (is_next_byte_highlighted || (n + 1 == Cols))
{
highlight_width = s.HexCellWidth;
bg_width = s.HexCellWidth;
if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0)
highlight_width += s.SpacingBetweenMidCols;
bg_width += s.SpacingBetweenMidCols;
}
draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor);
ImVec2 pos = ImGui::GetCursorScreenPos();
draw_list->AddRectFilled(pos, ImVec2(pos.x + bg_width, pos.y + s.LineHeight), bg_color);
}
if (DataEditingAddr == addr)
@@ -308,17 +352,21 @@ struct MemoryEditor
if (DataEditingTakeFocus)
{
ImGui::SetKeyboardFocusHere(0);
sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr);
sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
ImSnprintf(AddrInputBuf, 32, format_data, s.AddrDigitsCount, base_display_addr + addr);
ImSnprintf(DataInputBuf, 32, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
}
struct UserData
struct InputTextUserData
{
// FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
static int Callback(ImGuiInputTextCallbackData* data)
{
UserData* user_data = (UserData*)data->UserData;
InputTextUserData* user_data = (InputTextUserData*)data->UserData;
if (!data->HasSelection())
user_data->CursorPos = data->CursorPos;
#if IMGUI_VERSION_NUM < 19102
if (data->Flags & ImGuiInputTextFlags_ReadOnly)
return 0;
#endif
if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
{
// When not editing a byte, always refresh its InputText content pulled from underlying memory data
@@ -334,39 +382,42 @@ struct MemoryEditor
char CurrentBufOverwrite[3]; // Input
int CursorPos; // Output
};
UserData user_data;
user_data.CursorPos = -1;
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
InputTextUserData input_text_user_data;
input_text_user_data.CursorPos = -1;
ImSnprintf(input_text_user_data.CurrentBufOverwrite, 3, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
#if IMGUI_VERSION_NUM >= 18104
flags |= ImGuiInputTextFlags_AlwaysOverwrite;
#else
flags |= ImGuiInputTextFlags_AlwaysInsertMode;
#endif
if (ReadOnly)
flags |= ImGuiInputTextFlags_ReadOnly;
flags |= ImGuiInputTextFlags_AlwaysOverwrite; // was ImGuiInputTextFlags_AlwaysInsertMode
ImGui::SetNextItemWidth(s.GlyphWidth * 2);
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data))
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, InputTextUserData::Callback, &input_text_user_data))
data_write = data_next = true;
else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
DataEditingAddr = data_editing_addr_next = (size_t)-1;
DataEditingTakeFocus = false;
if (user_data.CursorPos >= 2)
if (input_text_user_data.CursorPos >= 2)
data_write = data_next = true;
if (data_editing_addr_next != (size_t)-1)
data_write = data_next = false;
unsigned int data_input_value = 0;
if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
if (!ReadOnly && data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
{
if (WriteFn)
WriteFn(mem_data, addr, (ImU8)data_input_value);
WriteFn(mem_data, addr, (ImU8)data_input_value, UserData);
else
mem_data[addr] = (ImU8)data_input_value;
}
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = addr;
}
ImGui::PopID();
}
else
{
// NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on.
ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
ImU8 b = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
if (OptShowHexII)
{
@@ -386,10 +437,15 @@ struct MemoryEditor
else
ImGui::Text(format_byte_space, b);
}
if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0))
if (ImGui::IsItemHovered())
{
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
MouseHovered = true;
MouseHoveredAddr = addr;
if (ImGui::IsMouseClicked(0))
{
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
}
}
}
}
@@ -397,15 +453,24 @@ struct MemoryEditor
if (OptShowAscii)
{
// Draw ASCII values
ImGui::SameLine(s.PosAsciiStart);
ImGui::SameLine(s.OffsetAsciiMinX);
ImVec2 pos = ImGui::GetCursorScreenPos();
addr = line_i * Cols;
addr = (size_t)line_i * Cols;
const float mouse_off_x = ImGui::GetIO().MousePos.x - pos.x;
const size_t mouse_addr = (mouse_off_x >= 0.0f && mouse_off_x < s.OffsetAsciiMaxX - s.OffsetAsciiMinX) ? addr + (size_t)(mouse_off_x / s.GlyphWidth) : (size_t)-1;
ImGui::PushID(line_i);
if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight)))
if (ImGui::InvisibleButton("ascii", ImVec2(s.OffsetAsciiMaxX - s.OffsetAsciiMinX, s.LineHeight)))
{
DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth);
DataEditingAddr = DataPreviewAddr = mouse_addr;
DataEditingTakeFocus = true;
}
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = mouse_addr;
}
ImGui::PopID();
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{
@@ -414,7 +479,11 @@ struct MemoryEditor
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
}
unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
else if (BgColorFn)
{
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), BgColorFn(mem_data, addr, UserData));
}
unsigned char c = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
char display_c = (c < 32 || c >= 128) ? '.' : c;
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
pos.x += s.GlyphWidth;
@@ -422,10 +491,14 @@ struct MemoryEditor
}
}
ImGui::PopStyleVar(2);
const float child_width = ImGui::GetWindowSize().x;
ImGui::EndChild();
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
ImVec2 backup_pos = ImGui::GetCursorScreenPos();
ImGui::SetCursorPosX(s.WindowWidth);
ImGui::Dummy(ImVec2(0.0f, 0.0f));
ImGui::SetCursorScreenPos(backup_pos);
if (data_next && DataEditingAddr + 1 < mem_size)
{
@@ -450,18 +523,28 @@ struct MemoryEditor
ImGui::Separator();
DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
}
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
{
IM_UNUSED(mem_data);
ImGuiStyle& style = ImGui::GetStyle();
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
if (GotoAddr != (size_t)-1)
{
if (GotoAddr < mem_size)
{
ImGui::BeginChild("##scrolling");
ImGui::SetScrollY((GotoAddr / Cols) * ImGui::GetTextLineHeight() - avail_size.y * 0.5f);
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
// Options menu
if (ImGui::Button("Options"))
ImGui::OpenPopup("context");
if (ImGui::BeginPopup("context"))
const ImVec2 contents_pos_end(contents_pos_start.x + child_width, ImGui::GetCursorScreenPos().y);
//ImGui::GetForegroundDrawList()->AddRect(contents_pos_start, contents_pos_end, IM_COL32(255, 0, 0, 255));
if (OptShowOptions)
if (ImGui::IsMouseHoveringRect(contents_pos_start, contents_pos_end))
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("OptionsPopup");
if (ImGui::BeginPopup("OptionsPopup"))
{
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; }
@@ -473,6 +556,17 @@ struct MemoryEditor
ImGui::EndPopup();
}
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
{
IM_UNUSED(mem_data);
ImGuiStyle& style = ImGui::GetStyle();
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
// Options menu
if (ImGui::Button("Options"))
ImGui::OpenPopup("OptionsPopup");
ImGui::SameLine();
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
@@ -488,18 +582,11 @@ struct MemoryEditor
}
}
if (GotoAddr != (size_t)-1)
{
if (GotoAddr < mem_size)
{
ImGui::BeginChild("##scrolling");
ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight());
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
//if (MouseHovered)
//{
// ImGui::SameLine();
// ImGui::Text("Hovered: %p", MouseHoveredAddr);
//}
}
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr)
@@ -511,16 +598,21 @@ struct MemoryEditor
ImGui::Text("Preview as:");
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
static const ImGuiDataType supported_data_types[] = { ImGuiDataType_S8, ImGuiDataType_U8, ImGuiDataType_S16, ImGuiDataType_U16, ImGuiDataType_S32, ImGuiDataType_U32, ImGuiDataType_S64, ImGuiDataType_U64, ImGuiDataType_Float, ImGuiDataType_Double };
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
{
for (int n = 0; n < ImGuiDataType_COUNT; n++)
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n))
PreviewDataType = (ImGuiDataType)n;
for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++)
{
ImGuiDataType data_type = supported_data_types[n];
if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
PreviewDataType = data_type;
}
ImGui::EndCombo();
}
ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0");
ImGui::Combo("##combo_endianness", &PreviewEndianness, "LE\0BE\0\0");
char buf[128] = "";
float x = s.GlyphWidth * 6.0f;
@@ -537,18 +629,19 @@ struct MemoryEditor
ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
}
// Utilities for Data Preview
// Utilities for Data Preview (since we don't access imgui_internal.h)
// FIXME: This technically depends on ImGuiDataType order.
const char* DataTypeGetDesc(ImGuiDataType data_type) const
{
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(descs));
return descs[data_type];
}
size_t DataTypeGetSize(ImGuiDataType data_type) const
{
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(sizes));
return sizes[data_type];
}
@@ -561,18 +654,18 @@ struct MemoryEditor
bool IsBigEndian() const
{
uint16_t x = 1;
ImU16 x = 1;
char c[2];
memcpy(c, &x, 2);
return c[0] != 0;
}
static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
static void* EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{
if (is_little_endian)
{
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
ImU8* dst = (ImU8*)_dst;
ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
@@ -583,7 +676,7 @@ struct MemoryEditor
}
}
static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
static void* EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{
if (is_little_endian)
{
@@ -591,23 +684,23 @@ struct MemoryEditor
}
else
{
uint8_t* dst = (uint8_t*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1;
ImU8* dst = (ImU8*)_dst;
ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1);
return _dst;
}
}
void* EndianessCopy(void* dst, void* src, size_t size) const
void* EndiannessCopy(void* dst, void* src, size_t size) const
{
static void* (*fp)(void*, void*, size_t, int) = NULL;
if (fp == NULL)
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianess);
static void* (*fp)(void*, void*, size_t, int) = nullptr;
if (fp == nullptr)
fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianness);
}
const char* FormatBinary(const uint8_t* buf, int width) const
const char* FormatBinary(const ImU8* buf, int width) const
{
IM_ASSERT(width <= 64);
size_t out_n = 0;
@@ -627,19 +720,19 @@ struct MemoryEditor
// [Internal]
void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
{
uint8_t buf[8];
ImU8 buf[8];
size_t elem_size = DataTypeGetSize(data_type);
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
if (ReadFn)
for (int i = 0, n = (int)size; i < n; ++i)
buf[i] = ReadFn(mem_data, addr + i);
buf[i] = ReadFn(mem_data, addr + i, UserData);
else
memcpy(buf, mem_data + addr, size);
if (data_format == DataFormat_Bin)
{
uint8_t binbuf[8];
EndianessCopy(binbuf, buf, size);
ImU8 binbuf[8];
EndiannessCopy(binbuf, buf, size);
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
return;
}
@@ -649,84 +742,85 @@ struct MemoryEditor
{
case ImGuiDataType_S8:
{
int8_t int8 = 0;
EndianessCopy(&int8, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; }
ImS8 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; }
break;
}
case ImGuiDataType_U8:
{
uint8_t uint8 = 0;
EndianessCopy(&uint8, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; }
ImU8 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; }
break;
}
case ImGuiDataType_S16:
{
int16_t int16 = 0;
EndianessCopy(&int16, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; }
ImS16 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break;
}
case ImGuiDataType_U16:
{
uint16_t uint16 = 0;
EndianessCopy(&uint16, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; }
ImU16 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break;
}
case ImGuiDataType_S32:
{
int32_t int32 = 0;
EndianessCopy(&int32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; }
ImS32 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break;
}
case ImGuiDataType_U32:
{
uint32_t uint32 = 0;
EndianessCopy(&uint32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; }
ImU32 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break;
}
case ImGuiDataType_S64:
{
int64_t int64 = 0;
EndianessCopy(&int64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; }
ImS64 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break;
}
case ImGuiDataType_U64:
{
uint64_t uint64 = 0;
EndianessCopy(&uint64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; }
ImU64 data = 0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break;
}
case ImGuiDataType_Float:
{
float float32 = 0.0f;
EndianessCopy(&float32, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; }
float data = 0.0f;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break;
}
case ImGuiDataType_Double:
{
double float64 = 0.0;
EndianessCopy(&float64, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; }
double data = 0.0;
EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break;
}
default:
case ImGuiDataType_COUNT:
break;
} // Switch
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -315,7 +315,7 @@ static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0
if (node->y > min_y) {
// raise min_y higher.
// we've accounted for all waste up to min_y,
// but we'll now add more waste for everything we've visted
// but we'll now add more waste for everything we've visited
waste_area += visited_width * (node->y - min_y);
min_y = node->y;
// the first time through, visited_width might be reduced
@@ -470,7 +470,7 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i
// insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be
// stiched back in
// stitched back in
cur = *res.prev_link;
if (cur->x < res.x) {
+113 -55
View File
@@ -5,7 +5,8 @@
// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)
// - Added name to struct or it may be forward declared in our code.
// - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)
// Grep for [DEAR IMGUI] to find the changes.
// - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
// Grep for [DEAR IMGUI] to find some changes.
// - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*
// stb_textedit.h - v1.14 - public domain - Sean Barrett
@@ -39,6 +40,7 @@
//
// VERSION HISTORY
//
// !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
// 1.14 (2021-07-11) page up/down, various fixes
// 1.13 (2019-02-07) fix bug in undo size management
// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
@@ -141,12 +143,14 @@
// with previous char)
// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
// (return type is int, -1 means not valid to insert)
// (not supported if you want to use UTF-8, see below)
// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based
// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
// as manually wordwrapping for end-of-line positioning
//
// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) try to insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
// returns number of characters actually inserted. [DEAR IMGUI]
//
// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key
//
@@ -178,6 +182,13 @@
// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text
// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text
//
// To support UTF-8:
//
// STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character
// STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character
// Do NOT define STB_TEXTEDIT_KEYTOTEXT.
// Instead, call stb_textedit_text() directly for text contents.
//
// Keyboard input must be encoded as a single integer value; e.g. a character code
// and some bitflags that represent shift states. to simplify the interface, SHIFT must
// be a bitflag, so we can test the shifted state of cursor movements to allow selection,
@@ -250,8 +261,10 @@
// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
// transformed into text and stb_textedit_text() is automatically called.
//
// text: [DEAR IMGUI] added 2024-09
// call this to text inputs sent to the textfield.
// text: (added 2025)
// call this to directly send text input the textfield, which is required
// for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT()
// cannot infer text length.
//
//
// When rendering, you can read the cursor position and selection state from
@@ -400,6 +413,16 @@ typedef struct
#define IMSTB_TEXTEDIT_memmove memmove
#endif
// [DEAR IMGUI]
// Functions must be implemented for UTF8 support
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1)
#endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1)
#endif
/////////////////////////////////////////////////////////////////////////////
//
@@ -407,7 +430,7 @@ typedef struct
//
// traverse the layout to locate the nearest character to a display position
static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y, int* out_side_on_line)
{
StbTexteditRow r;
int n = STB_TEXTEDIT_STRINGLEN(str);
@@ -417,6 +440,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
r.x0 = r.x1 = 0;
r.ymin = r.ymax = 0;
r.num_chars = 0;
*out_side_on_line = 0;
// search rows to find one that straddles 'y'
while (i < n) {
@@ -436,7 +460,10 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
// below all text, return 'after' last character
if (i >= n)
{
*out_side_on_line = 1;
return n;
}
// check if it's before the beginning of the line
if (x < r.x0)
@@ -449,6 +476,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {
float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
if (x < prev_x+w) {
*out_side_on_line = (k == 0) ? 0 : 1;
if (x < prev_x+w/2)
return k+i;
else
@@ -460,6 +488,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
}
// if the last character is a newline, return that. otherwise return 'after' the last character
*out_side_on_line = 1;
if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
return i+r.num_chars-1;
else
@@ -471,6 +500,7 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
{
// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
// goes off the top or bottom of the text
int side_on_line;
if( state->single_line )
{
StbTexteditRow r;
@@ -478,16 +508,18 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
y = r.ymin;
}
state->cursor = stb_text_locate_coord(str, x, y);
state->cursor = stb_text_locate_coord(str, x, y, &side_on_line);
state->select_start = state->cursor;
state->select_end = state->cursor;
state->has_preferred_x = 0;
str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
}
// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
{
int p = 0;
int side_on_line;
// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
// goes off the top or bottom of the text
@@ -501,8 +533,9 @@ static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *sta
if (state->select_start == state->select_end)
state->select_start = state->cursor;
p = stb_text_locate_coord(str, x, y);
p = stb_text_locate_coord(str, x, y, &side_on_line);
state->cursor = state->select_end = p;
str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
}
/////////////////////////////////////////////////////////////////////////////
@@ -552,6 +585,8 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
if (n < i + r.num_chars)
break;
if (str->LastMoveDirectionLR == ImGuiDir_Right && str->Stb->cursor > 0 && str->Stb->cursor == i + r.num_chars && STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] Wrapping point handling
break;
if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line
break; // [DEAR IMGUI]
prev_start = i;
@@ -648,15 +683,33 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt
}
}
// [DEAR IMGUI]
// Functions must be implemented for UTF8 support
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1)
// [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping.
#ifndef STB_TEXTEDIT_MOVELINESTART
static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
{
if (state->single_line)
return 0;
while (cursor > 0) {
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, cursor);
if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
break;
cursor = prev;
}
return cursor;
}
#define STB_TEXTEDIT_MOVELINESTART stb_textedit_move_line_start
#endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1)
#ifndef STB_TEXTEDIT_MOVELINEEND
static int stb_textedit_move_line_end(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
{
int n = STB_TEXTEDIT_STRINGLEN(str);
if (state->single_line)
return n;
while (cursor < n && STB_TEXTEDIT_GETCHAR(str, cursor) != STB_TEXTEDIT_NEWLINE)
cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, cursor);
return cursor;
}
#define STB_TEXTEDIT_MOVELINEEND stb_textedit_move_line_end
#endif
#ifdef STB_TEXTEDIT_IS_SPACE
@@ -668,9 +721,9 @@ static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
#ifndef STB_TEXTEDIT_MOVEWORDLEFT
static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
{
--c; // always move at least one character
while( c >= 0 && !is_word_boundary( str, c ) )
--c;
c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character
while (c >= 0 && !is_word_boundary(str, c))
c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c);
if( c < 0 )
c = 0;
@@ -684,9 +737,9 @@ static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c
static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )
{
const int len = STB_TEXTEDIT_STRINGLEN(str);
++c; // always move at least one character
c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character
while( c < len && !is_word_boundary( str, c ) )
++c;
c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c);
if( c > len )
c = len;
@@ -725,7 +778,8 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
stb_textedit_clamp(str, state);
stb_textedit_delete_selection(str,state);
// try to insert the characters
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {
len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len);
if (len) {
stb_text_makeundo_insert(state, state->cursor, len);
state->cursor += len;
state->has_preferred_x = 0;
@@ -739,6 +793,7 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
#define STB_TEXTEDIT_KEYTYPE int
#endif
// API key: process text input
// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
{
@@ -749,14 +804,15 @@ static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* sta
if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {
text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
if (text_len) {
state->cursor += text_len;
state->has_preferred_x = 0;
}
}
else {
} else {
stb_textedit_delete_selection(str, state); // implicitly clamps
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {
text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
if (text_len) {
stb_text_makeundo_insert(state, state->cursor, text_len);
state->cursor += text_len;
state->has_preferred_x = 0;
@@ -771,6 +827,7 @@ retry:
switch (key) {
default: {
#ifdef STB_TEXTEDIT_KEYTOTEXT
// This is not suitable for UTF-8 support.
int c = STB_TEXTEDIT_KEYTOTEXT(key);
if (c > 0) {
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
@@ -911,15 +968,16 @@ retry:
// [DEAR IMGUI]
// going down while being on the last line shouldn't bring us to that line end
if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
break;
//if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
// break;
// now find character position down a row
state->cursor = start;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0;
for (i=0; i < row.num_chars; ++i) {
for (i=0; i < row.num_chars; ) {
float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break;
@@ -927,10 +985,13 @@ retry:
x += dx;
if (x > goal_x)
break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
i += next - state->cursor;
state->cursor = next;
}
stb_textedit_clamp(str, state);
if (state->cursor == find.first_char + find.length)
str->LastMoveDirectionLR = ImGuiDir_Left;
state->has_preferred_x = 1;
state->preferred_x = goal_x;
@@ -980,8 +1041,9 @@ retry:
state->cursor = find.prev_first;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0;
for (i=0; i < row.num_chars; ++i) {
for (i=0; i < row.num_chars; ) {
float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break;
@@ -989,10 +1051,15 @@ retry:
x += dx;
if (x > goal_x)
break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
i += next - state->cursor;
state->cursor = next;
}
stb_textedit_clamp(str, state);
if (state->cursor == find.first_char)
str->LastMoveDirectionLR = ImGuiDir_Right;
else if (state->cursor == find.prev_first)
str->LastMoveDirectionLR = ImGuiDir_Left;
state->has_preferred_x = 1;
state->preferred_x = goal_x;
@@ -1002,10 +1069,15 @@ retry:
// go to previous line
// (we need to scan previous line the hard way. maybe we could expose this as a new API function?)
prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;
while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE)
--prev_scan;
while (prev_scan > 0)
{
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan);
if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
break;
prev_scan = prev;
}
find.first_char = find.prev_first;
find.prev_first = prev_scan;
find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan);
}
break;
}
@@ -1079,10 +1151,7 @@ retry:
case STB_TEXTEDIT_K_LINESTART:
stb_textedit_clamp(str, state);
stb_textedit_move_to_first(state);
if (state->single_line)
state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor;
state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
state->has_preferred_x = 0;
break;
@@ -1090,13 +1159,9 @@ retry:
case STB_TEXTEDIT_K_LINEEND2:
#endif
case STB_TEXTEDIT_K_LINEEND: {
int n = STB_TEXTEDIT_STRINGLEN(str);
stb_textedit_clamp(str, state);
stb_textedit_move_to_first(state);
if (state->single_line)
state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor;
stb_textedit_move_to_last(str, state);
state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
state->has_preferred_x = 0;
break;
}
@@ -1107,10 +1172,7 @@ retry:
case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
stb_textedit_clamp(str, state);
stb_textedit_prep_selection_at_cursor(state);
if (state->single_line)
state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor;
state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
state->select_end = state->cursor;
state->has_preferred_x = 0;
break;
@@ -1119,13 +1181,9 @@ retry:
case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
#endif
case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
int n = STB_TEXTEDIT_STRINGLEN(str);
stb_textedit_clamp(str, state);
stb_textedit_prep_selection_at_cursor(state);
if (state->single_line)
state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor;
state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
state->select_end = state->cursor;
state->has_preferred_x = 0;
break;
@@ -1300,7 +1358,7 @@ static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
// check type of recorded action:
if (u.insert_length) {
// easy case: was a deletion, so we need to insert n characters
STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
u.insert_length = STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
s->undo_char_point -= u.insert_length;
}
@@ -1351,7 +1409,7 @@ static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
if (r.insert_length) {
// easy case: need to insert n characters
STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
s->redo_char_point += r.insert_length;
}
+3 -3
View File
@@ -886,7 +886,7 @@ STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, fl
// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap
STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff);
// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel
// the same as stbtt_GetCodepointBitmap, but you can specify a subpixel
// shift for the character
STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint);
@@ -4516,8 +4516,8 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
q2[0] = (float)x2;
q2[1] = (float)y2;
if (equal(q0,q1) || equal(q1,q2)) {
x0 = (int)verts[i-1].x;
y0 = (int)verts[i-1].y;
x0 = (int)verts[i-1].x; //-V1048
y0 = (int)verts[i-1].y; //-V1048
x1 = (int)verts[i ].x;
y1 = (int)verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
+4
View File
@@ -65,6 +65,10 @@ namespace games::ccj {
libutils::load_library("libaio-iob2_video.dll");
libutils::load_library("win10actlog.dll");
// we do this to prevent this unexpectedly being unloaded
// which leads to D3D10Warp.dll_unloaded crash
libutils::load_library("d3d10warp.dll");
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
+1
View File
@@ -174,6 +174,7 @@ namespace games::gitadora {
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
static_assert(sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS) == 0x2C8);
/*
* typedefs
+7 -7
View File
@@ -1,6 +1,6 @@
#include "camera.h"
#if SPICE64
#if SPICE64 && !SPICE_XP
#include <d3d9.h>
#include "mf_wrappers.h"
@@ -84,7 +84,7 @@ namespace games::iidx {
bool find_camera_hooks() {
std::string dll_name = avs::game::DLL_NAME;
dll_path = MODULE_PATH / dll_name;
dll_path = MODULE_PATH / dll_name;
iidx_module = libutils::try_module(dll_path);
if (!iidx_module) {
@@ -149,7 +149,7 @@ namespace games::iidx {
"E800000000488BC8488BD34883C420",
"X????XXXXXXXXXX",
0, 0));
if (addr_textures_ptr == nullptr) {
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 1)");
return FALSE;
@@ -232,7 +232,7 @@ namespace games::iidx {
}
static void **__fastcall camera_hook_a(PBYTE a1) {
std::call_once(hook_a_init, [&]{
std::call_once(hook_a_init, [&]{
device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset);
auto const preview = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
auto const manager = reinterpret_cast<Camera::CCameraManager2*>((uint8_t*)iidx_module + addr_camera_manager);
@@ -298,11 +298,11 @@ namespace games::iidx {
// Ask for source type = video capture devices.
if (SUCCEEDED(hr)) {
hr = pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
);
}
// Enumerate devices.
if (SUCCEEDED(hr)) {
hr = WrappedMFEnumDeviceSources(pAttributes, &ppDevices, &numDevices);
@@ -478,7 +478,7 @@ namespace games::iidx {
init_mf_library();
}
}
return result;
}
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#if SPICE64
#if SPICE64 && !SPICE_XP
#include <vector>
#include "games/iidx/local_camera.h"
+17 -8
View File
@@ -30,6 +30,7 @@
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/socd_cleaner.h"
#include "util/sysutils.h"
#include "util/time.h"
#include "util/utils.h"
#include "launcher/signal.h"
@@ -423,6 +424,14 @@ namespace games::iidx {
init_legacy_camera_hook(FLIP_CAMS);
}
#if SPICE64
if (TDJ_MODE) {
sysutils::hook_EnumDisplayDevicesA();
}
#endif
// init cfgmgr32 hooks
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
}
@@ -486,28 +495,28 @@ namespace games::iidx {
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
}
// check -monitor + TDJ mode
// check -dxmainadapter + TDJ mode
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
IIDX_TDJ_MONITOR_WARNING = true;
log_warning(
"iidx",
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
"!!! compatibility issues with the game !!!\n"
"\n\n"
"!!! using -dxmainadapter option with TDJ is NOT recommended !!!\n"
"!!! due to known compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! option instead of -dxmainadapter !!!\n\n"
);
deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with TDJ mode",
"-dxmainadapter option is NOT recommended when running with TDJ mode",
" due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary",
" monitor in Windows settings before launching the game"
" use Change Main Monitor (-mainmonitor) option instead"
});
}
+31 -31
View File
@@ -1,6 +1,6 @@
#include "games/iidx/local_camera.h"
#if SPICE64
#if SPICE64 && !SPICE_XP
#include "util/logging.h"
#include "util/utils.h"
@@ -128,7 +128,7 @@ namespace games::iidx {
// TODO: Color space conversion
// if (SUCCEEDED(hr)) {
// hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
// }
// }
// if (SUCCEEDED(hr)) {
// hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, FALSE);
// }
@@ -189,11 +189,11 @@ namespace games::iidx {
&pType
);
if (FAILED(hr)) {
if (FAILED(hr)) {
if (hr != MF_E_NO_MORE_TYPES) {
log_warning("iidx:camhook", "[{}] Cannot get media type {} {:#x}", m_name, i, (ULONG)hr);
}
break;
break;
}
hr = TryMediaType(pType, &bestWidth, &bestFrameRate);
@@ -246,7 +246,7 @@ namespace games::iidx {
pSelectedMediaType = m_pAutoMediaType;
}
if (SUCCEEDED(hr)) {
if (SUCCEEDED(hr)) {
hr = ChangeMediaType(pSelectedMediaType);
}
@@ -281,7 +281,7 @@ namespace games::iidx {
m_cameraHeight = info.height;
UpdateDrawRect();
}
return hr;
}
@@ -395,7 +395,7 @@ namespace games::iidx {
m_device->ColorFill(m_pDestSurf, &targetRect, D3DCOLOR_XRGB(0, 0, 0));
}
void IIDXLocalCamera::CreateThread() {
// Create thread
m_drawThread = new std::thread([this]() {
@@ -411,7 +411,7 @@ namespace games::iidx {
if (accumulator > 1.0) {
accumulator -= 1.0;
floorFrameTimeMicroSec += 1;
}
}
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
}
});
@@ -430,12 +430,12 @@ namespace games::iidx {
log_misc("iidx:camhook", "[{}] Init camera control", m_name);
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
if (FAILED(hr)) {
// The device does not support IAMCameraControl
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
if (FAILED(hr)) {
// The device does not support IAMCameraControl
log_warning("iidx:camhook", "[{}] Camera control not supported", m_name);
return E_FAIL;
}
}
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
long minValue = 0;
@@ -472,9 +472,9 @@ namespace games::iidx {
CameraControlProp prop = m_controlProps.at(i);
log_misc(
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
m_name,
CAMERA_CONTROL_LABELS[i],
CAMERA_CONTROL_LABELS[i],
prop.minValue, prop.maxValue,
prop.defaultValue,
prop.delta,
@@ -570,9 +570,9 @@ namespace games::iidx {
// Get frame rate
hr = MFGetAttributeRatio(
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
(UINT32*)&frameRate.Denominator
);
if (FAILED(hr)) { goto done; }
@@ -580,7 +580,7 @@ namespace games::iidx {
info.frameRate = frameRate.Numerator / frameRate.Denominator;
info.description = fmt::format(
"{}x{} @{}FPS {}",
"{}x{} @{}FPS {}",
info.width,
info.height,
(int)info.frameRate,
@@ -606,7 +606,7 @@ namespace games::iidx {
return "Unknown";
}
/**
/**
* Return values:
* S_OK: this is a "better" media type than the existing one
* S_FALSE: valid media type, but not "better"
@@ -620,17 +620,17 @@ namespace games::iidx {
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
if (FAILED(hr)) {
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get subtype: {:#x}", m_name, (ULONG)hr);
return hr;
return hr;
}
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
if (FAILED(hr)) {
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame size: {:#x}", m_name, (ULONG)hr);
return hr;
}
// Only support format with converters
// TODO: verify conversion support with DXVA
if (subtype != MFVideoFormat_YUY2 && subtype != MFVideoFormat_NV12) {
@@ -639,12 +639,12 @@ namespace games::iidx {
// Frame rate
hr = MFGetAttributeRatio(
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
pType,
MF_MT_FRAME_RATE,
(UINT32*)&frameRate.Numerator,
(UINT32*)&frameRate.Denominator
);
if (FAILED(hr)) {
if (FAILED(hr)) {
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {:#x}", m_name, (ULONG)hr);
return hr;
}
@@ -667,7 +667,7 @@ namespace games::iidx {
// Check if this format has better resolution / frame rate
if ((width > *pBestWidth) || (width >= (UINT32)TARGET_SURFACE_WIDTH && frameRateValue >= *pBestFrameRate)) {
// log_misc(
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
// GetVideoFormatName(subtype),
// width,
// height,
@@ -730,7 +730,7 @@ namespace games::iidx {
HRESULT IIDXLocalCamera::FlushDrawCommands() {
IDirect3DQuery9* pEventQuery = nullptr;
// It is necessary to flush the command queue
// It is necessary to flush the command queue
// or the data is not ready for the receiver to read.
// Adapted from : https://msdn.microsoft.com/en-us/library/windows/desktop/bb172234%28v=vs.85%29.aspx
// Also see : http://www.ogre3d.org/forums/viewtopic.php?f=5&t=50486
@@ -784,8 +784,8 @@ namespace games::iidx {
for (int y = 0; y < TARGET_SURFACE_HEIGHT; y++) {
for (int x = 0; x < TARGET_SURFACE_WIDTH; x++) {
memcpy(
pDest + x * pixelSize,
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
pDest + x * pixelSize,
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
pixelSize
);
}
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#if SPICE64
#if SPICE64 && !SPICE_XP
#include <d3d9.h>
#include <dxva2api.h>
+47 -46
View File
@@ -51,7 +51,7 @@ namespace games {
static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs;
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
static robin_hood::unordered_map<std::string, std::vector<Option>> options;
static robin_hood::unordered_map<std::string, std::vector<std::string>> file_hints;
static robin_hood::unordered_map<std::string, std::vector<std::vector<std::string>>> file_hints;
static void initialize() {
@@ -68,7 +68,7 @@ namespace games {
buttons_help.insert({ bbc, bbc::get_buttons_help() });
analogs.insert({ bbc, bbc::get_analogs() });
lights.insert({ bbc, bbc::get_lights() });
file_hints[bbc].emplace_back("bsch.dll");
file_hints[bbc].push_back({"bsch.dll"});
// hpm
const std::string hpm("HELLO! Pop'n Music");
@@ -76,32 +76,32 @@ namespace games {
buttons.insert({ hpm, hpm::get_buttons() });
buttons_help.insert({ hpm, hpm::get_buttons_help() });
lights.insert({ hpm, hpm::get_lights() });
file_hints[hpm].emplace_back("popn.dll");
file_hints[hpm].push_back({"popn.dll", "pdh.dll"});
// bs
const std::string bs("Beatstream");
games.push_back(bs);
buttons.insert({ bs, bs::get_buttons() });
lights.insert({ bs, bs::get_lights() });
file_hints[bs].emplace_back("beatstream.dll");
file_hints[bs].emplace_back("beatstream1.dll");
file_hints[bs].emplace_back("beatstream2.dll");
file_hints[bs].push_back({"beatstream.dll"});
file_hints[bs].push_back({"beatstream1.dll"});
file_hints[bs].push_back({"beatstream2.dll"});
// ddr
const std::string ddr("Dance Dance Revolution");
games.push_back(ddr);
buttons.insert({ ddr, ddr::get_buttons() });
lights.insert({ ddr, ddr::get_lights() });
file_hints[ddr].emplace_back("ddr.dll");
file_hints[ddr].emplace_back("mdxja_945.dll");
file_hints[ddr].emplace_back("arkmdxp3.dll");
file_hints[ddr].push_back({"ddr.dll"});
file_hints[ddr].push_back({"mdxja_945.dll"});
file_hints[ddr].push_back({"arkmdxp3.dll"});
// dea
const std::string dea("Dance Evolution");
games.push_back(dea);
buttons.insert({ dea, dea::get_buttons() });
lights.insert({ dea, dea::get_lights() });
file_hints[dea].emplace_back("arkkdm.dll");
file_hints[dea].push_back({"arkkdm.dll"});
// gitadora
const std::string gitadora("GitaDora");
@@ -111,7 +111,7 @@ namespace games {
analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
analogs.insert({ gitadora, gitadora::get_analogs() });
lights.insert({ gitadora, gitadora::get_lights() });
file_hints[gitadora].emplace_back("gdxg.dll");
file_hints[gitadora].push_back({"gdxg.dll"});
// iidx
const std::string iidx("Beatmania IIDX");
@@ -120,7 +120,7 @@ namespace games {
buttons_help.insert({ iidx, iidx::get_buttons_help() });
analogs.insert({ iidx, iidx::get_analogs() });
lights.insert({ iidx, iidx::get_lights() });
file_hints[iidx].emplace_back("bm2dx.dll");
file_hints[iidx].push_back({"bm2dx.dll"});
// jb
const std::string jb("Jubeat");
@@ -128,7 +128,7 @@ namespace games {
buttons.insert({ jb, jb::get_buttons() });
buttons_help.insert({ jb, jb::get_buttons_help() });
lights.insert({ jb, jb::get_lights() });
file_hints[jb].emplace_back("jubeat.dll");
file_hints[jb].push_back({"jubeat.dll"});
// mga
const std::string mga("Metal Gear");
@@ -136,7 +136,7 @@ namespace games {
buttons.insert({ mga, mga::get_buttons() });
analogs.insert({ mga, mga::get_analogs() });
lights.insert({ mga, mga::get_lights() });
file_hints[mga].emplace_back("launch.dll");
file_hints[mga].push_back({"launch.dll"});
// museca
const std::string museca("Museca");
@@ -145,7 +145,7 @@ namespace games {
buttons_help.insert({ museca, museca::get_buttons_help() });
analogs.insert({ museca, museca::get_analogs() });
lights.insert({ museca, museca::get_lights() });
file_hints[museca].emplace_back("museca.dll");
file_hints[museca].push_back({"museca.dll"});
// nost
const std::string nost("Nostalgia");
@@ -154,7 +154,7 @@ namespace games {
buttons_help.insert({ nost, nost::get_buttons_help() });
analogs.insert({ nost, nost::get_analogs() });
lights.insert({ nost, nost::get_lights() });
file_hints[nost].emplace_back("nostalgia.dll");
file_hints[nost].push_back({"nostalgia.dll"});
// popn
const std::string popn("Pop'n Music");
@@ -162,41 +162,42 @@ namespace games {
buttons.insert({ popn, popn::get_buttons() });
buttons_help.insert({ popn, popn::get_buttons_help() });
lights.insert({ popn, popn::get_lights() });
file_hints[popn].emplace_back("popn19.dll");
file_hints[popn].emplace_back("popn20.dll");
file_hints[popn].emplace_back("popn21.dll");
file_hints[popn].emplace_back("popn22.dll");
file_hints[popn].emplace_back("popn23.dll");
file_hints[popn].emplace_back("popn24.dll");
file_hints[popn].emplace_back("popn25.dll");
file_hints[popn].push_back({"popn19.dll"});
file_hints[popn].push_back({"popn20.dll"});
file_hints[popn].push_back({"popn21.dll"});
file_hints[popn].push_back({"popn22.dll"});
file_hints[popn].push_back({"popn23.dll"});
file_hints[popn].push_back({"popn24.dll"});
file_hints[popn].push_back({"popn25.dll"});
file_hints[popn].push_back({"popn.dll", "libaio-iob2_video.dll"});
// qma
const std::string qma("Quiz Magic Academy");
games.push_back(qma);
buttons.insert({ qma, qma::get_buttons() });
lights.insert({ qma, qma::get_lights() });
file_hints[qma].emplace_back("client.dll");
file_hints[qma].push_back({"client.dll"});
// rb
const std::string rb("Reflec Beat");
games.push_back(rb);
buttons.insert({ rb, rb::get_buttons() });
lights.insert({ rb, rb::get_lights() });
file_hints[rb].emplace_back("reflecbeat.dll");
file_hints[rb].push_back({"reflecbeat.dll"});
// shogikai
std::string shogikai("Tenkaichi Shogikai");
games.push_back(shogikai);
buttons.insert({ shogikai, shogikai::get_buttons() });
lights.insert({ shogikai, shogikai::get_lights() });
file_hints[shogikai].emplace_back("shogi_engine.dll");
file_hints[shogikai].push_back({"shogi_engine.dll"});
// rf3d
const std::string rf3d("Road Fighters 3D");
games.push_back(rf3d);
buttons.insert({ rf3d, rf3d::get_buttons() });
analogs.insert({ rf3d, rf3d::get_analogs() });
file_hints[rf3d].emplace_back("jgt.dll");
file_hints[rf3d].push_back({"jgt.dll"});
// sc
const std::string sc("Steel Chronicle");
@@ -204,7 +205,7 @@ namespace games {
buttons.insert({ sc, sc::get_buttons() });
analogs.insert({ sc, sc::get_analogs() });
lights.insert({ sc, sc::get_lights() });
file_hints[sc].emplace_back("gamekgg.dll");
file_hints[sc].push_back({"gamekgg.dll"});
// sdvx
const std::string sdvx("Sound Voltex");
@@ -213,13 +214,13 @@ namespace games {
buttons_help.insert({ sdvx, sdvx::get_buttons_help() });
analogs.insert({ sdvx, sdvx::get_analogs() });
lights.insert({ sdvx, sdvx::get_lights() });
file_hints[sdvx].emplace_back("soundvoltex.dll");
file_hints[sdvx].push_back({"soundvoltex.dll"});
// mfc
const std::string mfc("Mahjong Fight Club");
games.push_back(mfc);
buttons.insert({ mfc, mfc::get_buttons() });
file_hints[mfc].emplace_back("allinone.dll");
file_hints[mfc].push_back({"allinone.dll"});
// ftt
const std::string ftt("FutureTomTom");
@@ -228,21 +229,21 @@ namespace games {
buttons_help.insert({ ftt, ftt::get_buttons_help() });
analogs.insert({ ftt, ftt::get_analogs() });
lights.insert({ ftt, ftt::get_lights() });
file_hints[ftt].emplace_back("arkmmd.dll");
file_hints[ftt].push_back({"arkmmd.dll"});
// loveplus
const std::string loveplus("LovePlus");
games.push_back(loveplus);
buttons.insert({ loveplus, loveplus::get_buttons() });
lights.insert({ loveplus, loveplus::get_lights() });
file_hints[loveplus].emplace_back("arkklp.dll");
file_hints[loveplus].push_back({"arkklp.dll"});
// scotto
const std::string scotto("Scotto");
games.push_back(scotto);
buttons.insert({ scotto, scotto::get_buttons() });
lights.insert({ scotto, scotto::get_lights() });
file_hints[scotto].emplace_back("scotto.dll");
file_hints[scotto].push_back({"scotto.dll"});
// drs
const std::string drs("DANCERUSH");
@@ -250,14 +251,14 @@ namespace games {
buttons.insert({ drs, drs::get_buttons() });
buttons_help.insert({ drs, drs::get_buttons_help() });
lights.insert({ drs, drs::get_lights() });
file_hints[drs].emplace_back("superstep.dll");
file_hints[drs].push_back({"superstep.dll"});
// otoca
const std::string otoca("Otoca D'or");
games.push_back(otoca);
buttons.insert({ otoca, otoca::get_buttons() });
buttons_help.insert({ otoca, otoca::get_buttons_help() });
file_hints[otoca].emplace_back("arkkep.dll");
file_hints[otoca].push_back({"arkkep.dll"});
// winning eleven
const std::string we("Winning Eleven");
@@ -265,27 +266,27 @@ namespace games {
buttons.insert({ we, we::get_buttons() });
analogs.insert({ we, we::get_analogs() });
lights.insert({ we, we::get_lights() });
file_hints[we].emplace_back("weac12_bootstrap_release.dll");
file_hints[we].emplace_back("arknck.dll");
file_hints[we].push_back({"weac12_bootstrap_release.dll"});
file_hints[we].push_back({"arknck.dll"});
// silent scope: bone eater
const std::string silentscope("Silent Scope: Bone Eater");
games.push_back(silentscope);
buttons.insert({ silentscope, silentscope::get_buttons() });
analogs.insert({ silentscope, silentscope::get_analogs() });
file_hints[silentscope].emplace_back("arkndd.dll");
file_hints[silentscope].push_back({"arkndd.dll"});
// charge machine
const std::string pcm("Charge Machine");
games.push_back(pcm);
buttons.insert({ pcm, pcm::get_buttons() });
file_hints[pcm].emplace_back("launch.dll");
file_hints[pcm].push_back({"launch.dll"});
// ongaku paradise
const std::string op("Ongaku Paradise");
games.push_back(op);
buttons.insert({ op, onpara::get_buttons() });
file_hints[op].emplace_back("arkjc9.dll");
file_hints[op].push_back({"arkjc9.dll"});
// bc
const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
@@ -293,7 +294,7 @@ namespace games {
buttons.insert({ bc, bc::get_buttons() });
buttons_help.insert({ bc, bc::get_buttons_help() });
analogs.insert({ bc, bc::get_analogs() });
file_hints[bc].emplace_back("game/bsac_app.exe");
file_hints[bc].push_back({"game/bsac_app.exe"});
// ccj
const std::string ccj("Chase Chase Jokers");
@@ -302,21 +303,21 @@ namespace games {
buttons_help.insert({ ccj, ccj::get_buttons_help() });
analogs.insert({ ccj, ccj::get_analogs() });
lights.insert({ ccj, ccj::get_lights() });
file_hints[ccj].emplace_back("game/chaseproject.exe");
file_hints[ccj].push_back({"game/chaseproject.exe"});
// QuizKnock STADIUM
const std::string qks("QuizKnock STADIUM");
games.push_back(qks);
buttons.insert({ qks, qks::get_buttons() });
buttons_help.insert({ qks, qks::get_buttons_help() });
file_hints[qks].emplace_back("game/uks.exe");
file_hints[qks].push_back({"game/uks.exe"});
// Mahjong Fight Girl
const std::string mfg("Mahjong Fight Girl");
games.push_back(mfg);
buttons.insert({ mfg, mfg::get_buttons() });
buttons_help.insert({ mfg, mfg::get_buttons_help() });
file_hints[mfg].emplace_back("game/MFGClient_Data");
file_hints[mfg].push_back({"game/MFGClient_Data"});
// Polaris Chord
const std::string pc("Polaris Chord");
@@ -325,7 +326,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.exe");
file_hints[pc].push_back({"game/svm.exe"});
}
const std::vector<std::string> &get_games() {
@@ -559,7 +560,7 @@ namespace games {
return &it->second;
}
std::vector<std::string> *get_game_file_hints(const std::string &game) {
std::vector<std::vector<std::string>> *get_game_file_hints(const std::string &game) {
initialize();
auto it = file_hints.find(game);
if (it == file_hints.end()) {
+1 -1
View File
@@ -64,5 +64,5 @@ namespace games {
std::vector<Analog> *get_analogs(const std::string &game);
std::vector<Light> *get_lights(const std::string &game);
std::vector<Option> *get_options(const std::string &game);
std::vector<std::string> *get_game_file_hints(const std::string &game);
std::vector<std::vector<std::string>> *get_game_file_hints(const std::string &game);
}
+561
View File
@@ -0,0 +1,561 @@
#include "bi3a_hook.h"
#if SPICE64 && !SPICE_XP
#include <optional>
#include <cstdint>
#include "util/detour.h"
#include "util/logging.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/io.h"
#include "popn.h"
#include "io.h"
#include "util/tapeled.h"
#include <typeinfo>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
namespace games::popn {
/*
* class definitions
*/
struct AIO_NMGR_IOB2 {
uint8_t data[0xe18];
};
struct AIO_NMGR_IOB5 {
uint8_t data[0xb10];
};
struct AIO_SCI_COMM {
uint8_t data[0x108];
};
struct AIO_SCI_COMM_T {
uint64_t unk_0;
struct AIO_SCI_COMM *aioSciComm;
};
struct AIO_COMM_STATUS {
uint64_t unk[5];
};
struct AIO_IOB5_BI3A {
uint8_t data[0x4FF0];
};
union AIO_IOB5_BI3A__DEVSTATUS {
uint8_t RawBytes[648];
struct {
uint8_t Reserved0[27]; // 0-26
uint8_t Coin0; // 27
uint8_t Coin1; // 28
uint8_t Reserved1[15]; // 29-43
uint8_t TestButton; // 44, io code offset rcx+0xe4
uint8_t ServiceButton; // 45, io code offset rcx+0xe5
uint8_t CoinMech; // 46, io code offset rcx+0xe6
uint8_t Reserved2[19]; // 47-65
uint8_t JOYL_SW; // 66
uint8_t Reserved3[4]; // 67-70
uint8_t JOYR_SW; // 71
uint8_t Headphones; // 72
uint8_t Recorder; // 73, unused
uint8_t Reserved4[295]; // 74-368
uint8_t Buttons_0_8; // 369
uint8_t Button_9; // 370
} Input;
};
// verified with M39-004-2025121500
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe18);
static_assert(sizeof(AIO_NMGR_IOB5) == 0xb10);
static_assert(sizeof(AIO_SCI_COMM) == 0x108);
static_assert(sizeof(AIO_IOB5_BI3A) == 0x4FF0);
static_assert(sizeof(AIO_IOB5_BI3A__DEVSTATUS) == 0x288);
/*
* typedefs
*/
// libaio.dll
typedef AIO_SCI_COMM_T *(__fastcall *aioSciComm_Open_t)(AIO_SCI_COMM_T *unk);
typedef void(__fastcall *aioSci_Destroy_t)(AIO_SCI_COMM *i_pNodeMgr);
typedef void(__fastcall *aioSci_GetCommStatus_t)(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status);
typedef void(__fastcall *aioNodeMgr_Destroy_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
typedef int32_t(__fastcall *aioNodeMgr_GetState_t)(AIO_NMGR_IOB5 *i_pNodeMgr);
typedef bool(__fastcall *aioNodeMgr_IsReady_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
typedef bool(__fastcall *aioNodeMgr_IsError_t)(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State);
typedef void (__fastcall *aioNodeCtl_Destroy_t)(AIO_IOB5_BI3A *i_pNodeCtl);
typedef int32_t (__fastcall *aioNodeCtl_GetState_t)(AIO_IOB5_BI3A *i_pNodeCtl);
typedef bool (__fastcall *aioNodeCtl_IsReady_t)(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State);
typedef bool (__fastcall *aioNodeCtl_IsError_t)(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State);
typedef void(__fastcall *aioNodeCtl_UpdateDevicesStatus_t)();
// libaio-iob5.dll
typedef AIO_NMGR_IOB5 *(__fastcall *aioNMgrIob5_Create_t)(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode);
typedef AIO_IOB5_BI3A *(__fastcall *aioIob5Bi3a_Create_t)(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p);
typedef void(__fastcall *aioIob5Bi3a_GetDeviceStatus_t)(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus);
typedef void(__fastcall *aioIob5Bi3a_SetOutputData_t)(AIO_IOB5_BI3A *a1, unsigned int a2, unsigned __int8 a3);
typedef void(__fastcall *aioIob5Bi3a_SetTapeLedDataPart_t)(AIO_IOB5_BI3A *a1, unsigned int a2, char a3, const void *a4, unsigned int a5, bool a6);
typedef int64_t (__fastcall *aioIob5Bi3a_SetTapeLedDataLimit_t)(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit);
/*
* function pointers
*/
// libaio.dll
static aioSciComm_Open_t aioSciComm_Open_orig = nullptr;
static aioSci_Destroy_t aioSci_Destroy_orig = nullptr;
static aioSci_GetCommStatus_t aioSci_GetCommStatus_orig = nullptr;
static aioNodeMgr_Destroy_t aioNodeMgr_Destroy_orig = nullptr;
static aioNodeMgr_GetState_t aioNodeMgr_GetState_orig = nullptr;
static aioNodeMgr_IsReady_t aioNodeMgr_IsReady_orig = nullptr;
static aioNodeMgr_IsError_t aioNodeMgr_IsError_orig = nullptr;
static aioNodeCtl_Destroy_t aioNodeCtl_Destroy_orig = nullptr;
static aioNodeCtl_GetState_t aioNodeCtl_GetState_orig = nullptr;
static aioNodeCtl_IsReady_t aioNodeCtl_IsReady_orig = nullptr;
static aioNodeCtl_IsError_t aioNodeCtl_IsError_orig = nullptr;
static aioNodeCtl_UpdateDevicesStatus_t aioNodeCtl_UpdateDevicesStatus_orig = nullptr;
// libaio-iob5.dll
static aioNMgrIob5_Create_t aioNMgrIob5_Create_orig = nullptr;
static aioIob5Bi3a_Create_t aioIob5Bi3a_Create_orig = nullptr;
static aioIob5Bi3a_GetDeviceStatus_t aioIob5Bi3a_GetDeviceStatus_orig = nullptr;
static aioIob5Bi3a_SetOutputData_t aioIob5Bi3a_SetOutputData_orig = nullptr;
static aioIob5Bi3a_SetTapeLedDataPart_t aioIob5Bi3a_SetTapeLedDataPart_orig = nullptr;
static aioIob5Bi3a_SetTapeLedDataLimit_t aioIob5Bi3a_SetTapeLedDataLimit_orig = nullptr;
/*
* variables
*/
static AIO_NMGR_IOB2 *aioNmgrIob2;
static AIO_NMGR_IOB5 *aioNmgrIob5;
static AIO_SCI_COMM *aioSciComm;
static AIO_IOB5_BI3A *aioIob5Bi3a;
/*
* implementations
*/
// libaio.dll
static AIO_SCI_COMM_T *__fastcall aioSciComm_Open(AIO_SCI_COMM_T *unk) {
aioSciComm = new AIO_SCI_COMM;
memset(unk, 0, sizeof(AIO_SCI_COMM_T));
unk->aioSciComm = aioSciComm;
return unk;
}
static void __fastcall aioSci_GetCommStatus(AIO_SCI_COMM *i_pNodeMgr, AIO_COMM_STATUS *i_Status) {
if (i_pNodeMgr == aioSciComm) {
memset(i_Status, 0, sizeof(AIO_COMM_STATUS));
} else {
return aioSci_GetCommStatus_orig(i_pNodeMgr, i_Status);
}
}
static void __fastcall aioSci_Destroy(AIO_SCI_COMM *i_pNodeMgr) {
if (i_pNodeMgr == aioSciComm) {
delete aioSciComm;
} else {
return aioSci_Destroy_orig(i_pNodeMgr);
}
}
static void __fastcall aioNodeMgr_Destroy(AIO_NMGR_IOB5 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
delete aioNmgrIob5;
aioNmgrIob5 = nullptr;
} else {
return aioNodeMgr_Destroy_orig(i_pNodeMgr);
}
}
static int32_t __fastcall aioNodeMgr_GetState(AIO_NMGR_IOB5 *i_pNodeMgr) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return 1;
} else {
return aioNodeMgr_GetState_orig(i_pNodeMgr);
}
}
static bool __fastcall aioNodeMgr_IsReady(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return true;
} else {
return aioNodeMgr_IsReady_orig(i_pNodeMgr, i_State);
}
}
static bool __fastcall aioNodeMgr_IsError(AIO_NMGR_IOB5 *i_pNodeMgr, int32_t i_State) {
if (i_pNodeMgr == aioNmgrIob5 ||
i_pNodeMgr == (AIO_NMGR_IOB5 *)aioNmgrIob2) {
return false;
} else {
return aioNodeMgr_IsError_orig(i_pNodeMgr, i_State);
}
}
static void __fastcall aioNodeCtl_Destroy(AIO_IOB5_BI3A *i_pNodeCtl) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioNodeCtl_Destroy_orig(i_pNodeCtl);
}
delete aioIob5Bi3a;
aioIob5Bi3a = nullptr;
}
static int32_t __fastcall aioNodeCtl_GetState(AIO_IOB5_BI3A *i_pNodeCtl) {
if (i_pNodeCtl == aioIob5Bi3a) {
return 1;
}
return aioNodeCtl_GetState_orig(i_pNodeCtl);
}
static bool __fastcall aioNodeCtl_IsReady(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Bi3a) {
return true;
}
return aioNodeCtl_IsReady_orig(i_pNodeCtl, i_State);
}
static bool __fastcall aioNodeCtl_IsError(AIO_IOB5_BI3A *i_pNodeCtl, int32_t i_State) {
if (i_pNodeCtl == aioIob5Bi3a) {
return false;
}
return aioNodeCtl_IsError_orig(i_pNodeCtl, i_State);
}
static void __fastcall aioNodeCtl_UpdateDevicesStatus() {
}
// libaio-iob5.dll
static AIO_NMGR_IOB5 *__fastcall aioNMgrIob5_Create(AIO_SCI_COMM *i_pSci, uint32_t i_bfMode) {
if (i_pSci == aioSciComm) {
aioNmgrIob5 = new AIO_NMGR_IOB5;
log_info("bi3a_hook", "aioNMgrIob5_Create called, returning custom AIO_NMGR_IOB5: {}", fmt::ptr(aioNmgrIob5));
return aioNmgrIob5;
} else {
return aioNMgrIob5_Create_orig(i_pSci, i_bfMode);
}
}
static AIO_IOB5_BI3A *__fastcall aioIob5Bi3a_Create(AIO_NMGR_IOB5 *i_pNodeMgr, uint8_t i, void *p) {
log_info("bi3a_hook", "aioIob5Bi3a_Create called with i_pNodeMgr={}, i={}, p={}", fmt::ptr(i_pNodeMgr), i, fmt::ptr(p));
if (i_pNodeMgr == aioNmgrIob5) {
aioIob5Bi3a = new AIO_IOB5_BI3A;
log_info("bi3a_hook", "aioIob5Bi3a_Create: returning custom AIO_IOB5_BI3A: {}", fmt::ptr(aioIob5Bi3a));
return aioIob5Bi3a;
} else {
return aioIob5Bi3a_Create_orig(i_pNodeMgr, i, p);
}
}
static void __fastcall aioIob5Bi3a_GetDeviceStatus(AIO_IOB5_BI3A *i_pNodeCtl, AIO_IOB5_BI3A__DEVSTATUS *o_DevStatus) {
RI_MGR->devices_flush_output();
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_GetDeviceStatus_orig(i_pNodeCtl, o_DevStatus);
}
memset(o_DevStatus, 0, sizeof(*o_DevStatus));
auto &buttons = get_buttons();
// operator
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test])) {
o_DevStatus->Input.TestButton = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service])) {
o_DevStatus->Input.ServiceButton = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech])) {
o_DevStatus->Input.CoinMech = 1;
}
// buttons 1-9
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button1])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 0);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button2])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 1);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button3])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 2);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button4])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 3);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button5])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button6])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 5);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button7])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 6);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button8])) {
o_DevStatus->Input.Buttons_0_8 |= (1 << 7);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Button9])) {
o_DevStatus->Input.Button_9 = 1;
}
// deka pop-kuns
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::RedPop])) {
o_DevStatus->Input.JOYL_SW = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BluePop])) {
o_DevStatus->Input.JOYR_SW = 1;
}
// audio
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone])) {
o_DevStatus->Input.Headphones = 1;
}
// coin
o_DevStatus->Input.Coin0 += eamuse_coin_get_stock();
}
static void __fastcall aioIob5Bi3a_SetOutputData(AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
}
// 7 = IC_CARD_R
// 8 = IC_CARD_G
// 9 = IC_CARD_B
// 10 = WOOFER_R
// 11 = WOOFER_G
// 12 = WOOFER_B
// values = 0 to 255 in test menu
std::optional<Lights::popn_lights_t> light;
switch (i_CnPin) {
case 7:
light = Lights::IC_Card_R;
break;
case 8:
light = Lights::IC_Card_G;
break;
case 9:
light = Lights::IC_Card_B;
break;
case 10:
light = Lights::WooferLED_R;
break;
case 11:
light = Lights::WooferLED_G;
break;
case 12:
light = Lights::WooferLED_B;
break;
default:
break;
}
if (light.has_value()) {
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights.at(light.value()), i_Data / 255.f);
}
return;
}
static int64_t __fastcall aioIob5Bi3a_SetTapeLedDataLimit(
AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_Channel, uint8_t i_Scale, uint8_t i_Limit) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_SetTapeLedDataLimit_orig(i_pNodeCtl, i_Channel, i_Scale, i_Limit);
}
return 0;
}
struct PopnLight {
int data_index;
Lights::popn_lights_t light;
uint8_t size;
PopnLight(
int data_index, Lights::popn_lights_t light, uint8_t size) :
data_index(data_index), light(light), size(size) {}
};
static void __fastcall aioIob5Bi3a_SetTapeLedDataPart(
AIO_IOB5_BI3A *i_pNodeCtl, uint32_t i_CnPin, char i_LedType, const void *i_pData, uint32_t i_DataSize, bool i_bIsLast) {
if (i_pNodeCtl != aioIob5Bi3a) {
return aioIob5Bi3a_SetTapeLedDataPart_orig(i_pNodeCtl, i_CnPin, i_LedType, i_pData, i_DataSize, i_bIsLast);
}
auto number_of_leds = i_DataSize;
// buttons 1-9, but game passes in wrong length; fix it up
if (i_CnPin == 0 && i_DataSize == 9) {
number_of_leds = i_DataSize * 3;
}
if (FALSE) {
std::string data;
for (uint32_t i = 0; i < number_of_leds; i++) {
auto dataByte = ((uint8_t *)i_pData)[i];
data += fmt::format("{:02X} ", dataByte);
}
log_info(
"bi3a_hook",
"aioIob5Bi3a_SetTapeLedDataPart Pin={}, Size={}, Type={}, data@{} = {}",
i_CnPin, number_of_leds, (uint8_t)i_LedType, fmt::ptr(i_pData), data);
}
auto &lights = get_lights();
if (i_CnPin == 0 && number_of_leds == 9 * 3) {
// special handling converting to non-RGB lights
// take the max(R, G, B) and use it to write the light value
uint8_t light_value[9] = { 0 };
for (uint32_t i = 0; i < number_of_leds; i += 3) {
light_value[i / 3] =
std::max({ ((uint8_t *)i_pData)[i], ((uint8_t *)i_pData)[i + 1], ((uint8_t *)i_pData)[i + 2] });
}
constexpr Lights::popn_lights_t legacy_button_lights[] = {
Lights::popn_lights_t::Button1,
Lights::popn_lights_t::Button2,
Lights::popn_lights_t::Button3,
Lights::popn_lights_t::Button4,
Lights::popn_lights_t::Button5,
Lights::popn_lights_t::Button6,
Lights::popn_lights_t::Button7,
Lights::popn_lights_t::Button8,
Lights::popn_lights_t::Button9
};
static_assert(std::size(legacy_button_lights) == 9);
for (size_t light = 0; light < 9; light++) {
// on the new cab, buttons are colorless plastic and they rely on RGB to be lit at all times, even when "off"
// when translating to legacy on/off lights, treat anything above ~60% brightness as fully on,
// otherwise off, to avoid dimly lit "off" state
GameAPI::Lights::writeLight(
RI_MGR,
lights.at(legacy_button_lights[light]),
light_value[light] > 150 ? 1.f : 0.f);
}
// color buttons
constexpr Lights::popn_lights_t button_lights[] = {
Lights::popn_lights_t::PikaButton1_R, Lights::popn_lights_t::PikaButton1_G, Lights::popn_lights_t::PikaButton1_B,
Lights::popn_lights_t::PikaButton2_R, Lights::popn_lights_t::PikaButton2_G, Lights::popn_lights_t::PikaButton2_B,
Lights::popn_lights_t::PikaButton3_R, Lights::popn_lights_t::PikaButton3_G, Lights::popn_lights_t::PikaButton3_B,
Lights::popn_lights_t::PikaButton4_R, Lights::popn_lights_t::PikaButton4_G, Lights::popn_lights_t::PikaButton4_B,
Lights::popn_lights_t::PikaButton5_R, Lights::popn_lights_t::PikaButton5_G, Lights::popn_lights_t::PikaButton5_B,
Lights::popn_lights_t::PikaButton6_R, Lights::popn_lights_t::PikaButton6_G, Lights::popn_lights_t::PikaButton6_B,
Lights::popn_lights_t::PikaButton7_R, Lights::popn_lights_t::PikaButton7_G, Lights::popn_lights_t::PikaButton7_B,
Lights::popn_lights_t::PikaButton8_R, Lights::popn_lights_t::PikaButton8_G, Lights::popn_lights_t::PikaButton8_B,
Lights::popn_lights_t::PikaButton9_R, Lights::popn_lights_t::PikaButton9_G, Lights::popn_lights_t::PikaButton9_B
};
static_assert(std::size(button_lights) == 9 * 3);
for (size_t light = 0; light < number_of_leds; light++) {
GameAPI::Lights::writeLight(
RI_MGR,
lights.at(button_lights[light]),
((uint8_t *)i_pData)[light] / 255.f);
}
}
if (tapeledutils::is_enabled() && i_CnPin > 0 && i_CnPin < std::size(TAPELED_MAPPING)) {
auto &map = TAPELED_MAPPING[i_CnPin];
const auto data_size = std::min(map.data.capacity(), (size_t)number_of_leds / 3);
// pick a color to use
const auto rgb = tapeledutils::pick_color_from_led_tape((uint8_t *)i_pData, data_size);
// program the lights into API
auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
// tape LED output over API not implemented
// for (size_t i = 0; i < data_size; i++) {
// map.data[i].r = ((uint8_t *)i_pData)[i * 3];
// map.data[i].g = ((uint8_t *)i_pData)[i * 3 + 1];
// map.data[i].b = ((uint8_t *)i_pData)[i * 3 + 2];
// }
}
}
void bi3a_hook_init() {
// avoid double init
static bool initialized = false;
if (initialized) {
return;
} else {
initialized = true;
}
// announce
log_info("bi3a_hook", "init");
// libaio-iob5.dll
const auto libaioIob5Dll = "libaio-iob5.dll";
detour::trampoline_try(libaioIob5Dll, "aioNMgrIob5_Create",
aioNMgrIob5_Create, &aioNMgrIob5_Create_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_Create",
aioIob5Bi3a_Create, &aioIob5Bi3a_Create_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_GetDeviceStatus",
aioIob5Bi3a_GetDeviceStatus, &aioIob5Bi3a_GetDeviceStatus_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetOutputData",
aioIob5Bi3a_SetOutputData, &aioIob5Bi3a_SetOutputData_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataPart",
aioIob5Bi3a_SetTapeLedDataPart, &aioIob5Bi3a_SetTapeLedDataPart_orig);
detour::trampoline_try(libaioIob5Dll, "aioIob5Bi3a_SetTapeLedDataLimit",
aioIob5Bi3a_SetTapeLedDataLimit, &aioIob5Bi3a_SetTapeLedDataLimit_orig);
// libaio.dll
const auto libaioDll = "libaio.dll";
detour::trampoline_try(libaioDll, "?Open@AIO_SCI_COMM@@SA?AU?$AC_ERESULT_T@PEAVAIO_SCI_COMM@@@@PEBD0AEBUSETTING@1@I@Z",
aioSciComm_Open, &aioSciComm_Open_orig);
detour::trampoline_try(libaioDll, "aioSci_GetCommStatus",
aioSci_GetCommStatus, &aioSci_GetCommStatus_orig);
detour::trampoline_try(libaioDll, "aioSci_Destroy",
aioSci_Destroy, &aioSci_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_Destroy",
aioNodeMgr_Destroy, &aioNodeMgr_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_GetState",
aioNodeMgr_GetState, &aioNodeMgr_GetState_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_IsReady",
aioNodeMgr_IsReady, &aioNodeMgr_IsReady_orig);
detour::trampoline_try(libaioDll, "aioNodeMgr_IsError",
aioNodeMgr_IsError, &aioNodeMgr_IsError_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_Destroy",
aioNodeCtl_Destroy, &aioNodeCtl_Destroy_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_GetState",
aioNodeCtl_GetState, &aioNodeCtl_GetState_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_IsReady",
aioNodeCtl_IsReady, &aioNodeCtl_IsReady_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
detour::trampoline_try(libaioDll, "aioNodeCtl_UpdateDevicesStatus",
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
}
}
#endif
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#if SPICE64 && !SPICE_XP
namespace games::popn {
void bi3a_hook_init();
}
#endif
+116 -78
View File
@@ -11,37 +11,7 @@ std::vector<Button> &games::popn::get_buttons() {
"Service",
"Test",
"Coin Mech",
"Button 1",
"Button 2",
"Button 3",
"Button 4",
"Button 5",
"Button 6",
"Button 7",
"Button 8",
"Button 9"
);
}
return buttons;
}
std::string games::popn::get_buttons_help() {
// keep to max 100 characters wide
return
" 2 4 6 8 \n"
" 1 3 5 7 9 "
;
}
std::vector<Light> &games::popn::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Pop'n Music");
GameAPI::Lights::sortLights(
&lights,
"Headphones",
"Button 1",
"Button 2",
"Button 3",
@@ -51,55 +21,123 @@ std::vector<Light> &games::popn::get_lights() {
"Button 7",
"Button 8",
"Button 9",
"Top LED 1",
"Top LED 2",
"Top LED 3",
"Top LED 4",
"Top LED 5",
"Top LED 6",
"Top LED 7",
"Top LED 8",
"Top LED 9",
"Top LED 10",
"Top LED 11",
"Top LED 12",
"Top LED 13",
"Top LED 14",
"Top LED 15",
"Top LED 16",
"Top LED 17",
"Top LED 18",
"Top LED 19",
"Top LED 20",
"Top LED 21",
"Top LED 22",
"Top LED 23",
"Top LED 24",
"Top LED 25",
"Top LED 26",
"Top LED 27",
"Top LED 28",
"Top LED 29",
"Top LED 30",
"Top LED 31",
"Top LED 32",
"Top LED R",
"Top LED G",
"Top LED B",
"Hi Lamp 1",
"Hi Lamp 2",
"Hi Lamp 3",
"Hi Lamp 4",
"Hi Lamp 5",
"Left Lamp 1",
"Left Lamp 2",
"Right Lamp 1",
"Right Lamp 2",
"Woofer LED R",
"Woofer LED G",
"Woofer LED B"
"Red Pop-Kun",
"Blue Pop-Kun"
);
}
return buttons;
}
std::string games::popn::get_buttons_help() {
// keep to max 100 characters wide
return
"RED 2 4 6 8 BLUE\n"
" 1 3 5 7 9"
;
}
std::vector<Light> &games::popn::get_lights() {
static std::vector<Light> lights;
if (lights.empty()) {
lights = GameAPI::Lights::getLights("Pop'n Music");
GameAPI::Lights::sortLightsWithCategory(&lights, {
{"Common", "Button 1"},
{"Common", "Button 2"},
{"Common", "Button 3"},
{"Common", "Button 4"},
{"Common", "Button 5"},
{"Common", "Button 6"},
{"Common", "Button 7"},
{"Common", "Button 8"},
{"Common", "Button 9"},
{"SD", "Hi Lamp 1"},
{"SD", "Hi Lamp 2"},
{"SD", "Hi Lamp 3"},
{"SD", "Hi Lamp 4"},
{"SD", "Hi Lamp 5"},
{"SD", "Left Lamp 1"},
{"SD", "Left Lamp 2"},
{"SD", "Right Lamp 1"},
{"SD", "Right Lamp 2"},
{"HD", "Top LED 1"},
{"HD", "Top LED 2"},
{"HD", "Top LED 3"},
{"HD", "Top LED 4"},
{"HD", "Top LED 5"},
{"HD", "Top LED 6"},
{"HD", "Top LED 7"},
{"HD", "Top LED 8"},
{"HD", "Top LED 9"},
{"HD", "Top LED 10"},
{"HD", "Top LED 11"},
{"HD", "Top LED 12"},
{"HD", "Top LED 13"},
{"HD", "Top LED 14"},
{"HD", "Top LED 15"},
{"HD", "Top LED 16"},
{"HD", "Top LED 17"},
{"HD", "Top LED 18"},
{"HD", "Top LED 19"},
{"HD", "Top LED 20"},
{"HD", "Top LED 21"},
{"HD", "Top LED 22"},
{"HD", "Top LED 23"},
{"HD", "Top LED 24"},
{"HD", "Top LED 25"},
{"HD", "Top LED 26"},
{"HD", "Top LED 27"},
{"HD", "Top LED 28"},
{"HD", "Top LED 29"},
{"HD", "Top LED 30"},
{"HD", "Top LED 31"},
{"HD", "Top LED 32"},
{"HD/Pika", "Top LED R"},
{"HD/Pika", "Top LED G"},
{"HD/Pika", "Top LED B"},
{"HD/Pika", "Woofer LED R"},
{"HD/Pika", "Woofer LED G"},
{"HD/Pika", "Woofer LED B"},
{"Pika", "Button 1 R"},
{"Pika", "Button 1 G"},
{"Pika", "Button 1 B"},
{"Pika", "Button 2 R"},
{"Pika", "Button 2 G"},
{"Pika", "Button 2 B"},
{"Pika", "Button 3 R"},
{"Pika", "Button 3 G"},
{"Pika", "Button 3 B"},
{"Pika", "Button 4 R"},
{"Pika", "Button 4 G"},
{"Pika", "Button 4 B"},
{"Pika", "Button 5 R"},
{"Pika", "Button 5 G"},
{"Pika", "Button 5 B"},
{"Pika", "Button 6 R"},
{"Pika", "Button 6 G"},
{"Pika", "Button 6 B"},
{"Pika", "Button 7 R"},
{"Pika", "Button 7 G"},
{"Pika", "Button 7 B"},
{"Pika", "Button 8 R"},
{"Pika", "Button 8 G"},
{"Pika", "Button 8 B"},
{"Pika", "Button 9 R"},
{"Pika", "Button 9 G"},
{"Pika", "Button 9 B"},
{"Pika", "Red Pop-Kun R"},
{"Pika", "Red Pop-Kun G"},
{"Pika", "Red Pop-Kun B"},
{"Pika", "Blue Pop-Kun R"},
{"Pika", "Blue Pop-Kun G"},
{"Pika", "Blue Pop-Kun B"},
{"Pika", "IC Card R"},
{"Pika", "IC Card G"},
{"Pika", "IC Card B"},
});
}
return lights;
}
+50 -11
View File
@@ -11,6 +11,7 @@ namespace games::popn {
Service,
Test,
CoinMech,
Headphone,
Button1,
Button2,
Button3,
@@ -20,12 +21,14 @@ namespace games::popn {
Button7,
Button8,
Button9,
RedPop,
BluePop
};
}
// all lights in correct order
namespace Lights {
enum {
typedef enum {
Button1,
Button2,
Button3,
@@ -35,6 +38,15 @@ namespace games::popn {
Button7,
Button8,
Button9,
HiLamp1,
HiLamp2,
HiLamp3,
HiLamp4,
HiLamp5,
LeftLamp1,
LeftLamp2,
RightLamp1,
RightLamp2,
TopLED1,
TopLED2,
TopLED3,
@@ -70,19 +82,46 @@ namespace games::popn {
TopLED_R,
TopLED_G,
TopLED_B,
HiLamp1,
HiLamp2,
HiLamp3,
HiLamp4,
HiLamp5,
LeftLamp1,
LeftLamp2,
RightLamp1,
RightLamp2,
WooferLED_R,
WooferLED_G,
WooferLED_B,
};
PikaButton1_R,
PikaButton1_G,
PikaButton1_B,
PikaButton2_R,
PikaButton2_G,
PikaButton2_B,
PikaButton3_R,
PikaButton3_G,
PikaButton3_B,
PikaButton4_R,
PikaButton4_G,
PikaButton4_B,
PikaButton5_R,
PikaButton5_G,
PikaButton5_B,
PikaButton6_R,
PikaButton6_G,
PikaButton6_B,
PikaButton7_R,
PikaButton7_G,
PikaButton7_B,
PikaButton8_R,
PikaButton8_G,
PikaButton8_B,
PikaButton9_R,
PikaButton9_G,
PikaButton9_B,
RedPopKun_R,
RedPopKun_G,
RedPopKun_B,
BluePopKun_R,
BluePopKun_G,
BluePopKun_B,
IC_Card_R,
IC_Card_G,
IC_Card_B,
} popn_lights_t;
}
// getters
+359
View File
@@ -1,7 +1,9 @@
#include "avs/game.h"
#include "popn.h"
#include "bi3a_hook.h"
#include <cstdint>
#include <cstring>
#include "hooks/graphics/graphics.h"
#include "rawinput/rawinput.h"
#include "util/detour.h"
#include "util/fileutils.h"
@@ -9,14 +11,281 @@
#include "util/utils.h"
#include "cfg/button.h"
#include "cfg/api.h"
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "launcher/launcher.h"
#include "launcher/logger.h"
#include "misc/eamuse.h"
#include "util/sysutils.h"
#include "io.h"
#include "util/deferlog.h"
#include "misc/wintouchemu.h"
namespace games::popn {
bool SHOW_PIKA_MONITOR_WARNING = false;
#if SPICE64 && !SPICE_XP
const HMONITOR FAKE_MONITOR_HMONITOR = (HMONITOR)-1;
constexpr LONG FAKE_MONITOR_OFFSET_X_Y = -999999;
constexpr LONG FAKE_MONITOR_WIDTH = 1280;
constexpr LONG FAKE_MONITOR_HEIGHT = 800;
// pin 0 = buttons 1-3, size 9 (but it's actually triple that in reality - game passes in wrong length), values 0-0xFF
// pin 1 = left pop (red pop kun), size 16, values 0-0xFF
// pin 2 = right pop (blue pop kun), size 16, values 0-0xFF
// pin 3 = title, size 44, values 0-0xFF
// pin 4 = speaker, size 40, values 0-0xFF - U region only
// pin 5 = monitor, size 18, values 0-0x7F - U region only
// pin 6 = control panel, size 28, values 0-0x7F - U region only
// pin 7 = cabinet, size 22, values 0-0x7F - U region only
tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL] = {
{
0, // these are button lights and handled specially
0,
0,
0,
"Invalid"
},
{
5, // 5*3; game reports 16 but make it 15
Lights::popn_lights_t::RedPopKun_R,
Lights::popn_lights_t::RedPopKun_G,
Lights::popn_lights_t::RedPopKun_B,
"Red Pop-Kun"
},
{
5, // 5*3; game reports 16 but make it 15
Lights::popn_lights_t::BluePopKun_R,
Lights::popn_lights_t::BluePopKun_G,
Lights::popn_lights_t::BluePopKun_B,
"Blue Pop-Kun"
},
{
14, // 14*3; game reports 44 but make it 42; these are lights above the banner
Lights::popn_lights_t::TopLED_R,
Lights::popn_lights_t::TopLED_G,
Lights::popn_lights_t::TopLED_B,
"Top"
},
};
static decltype(GetDisplayConfigBufferSizes) *GetDisplayConfigBufferSizes_orig = nullptr;
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
static UINT32 pNumPathArrayElements_original = 0;
static UINT32 pNumModeInfoArrayElements_original = 0;
static
LONG
WINAPI
GetDisplayConfigBufferSizes_hook(
UINT32 Flags,
UINT32 *pNumPathArrayElements,
UINT32 *pNumModeInfoArrayElements)
{
const auto ret = GetDisplayConfigBufferSizes_orig(Flags, pNumPathArrayElements, pNumModeInfoArrayElements);
if (FAKE_SUBSCREEN_ADAPTER) {
log_misc("popn", "GetDisplayConfigBufferSizes returning fake monitor paths and modes");
pNumPathArrayElements_original = *pNumPathArrayElements;
pNumModeInfoArrayElements_original = *pNumModeInfoArrayElements;
*pNumPathArrayElements += 1;
*pNumModeInfoArrayElements += 2;
}
return ret;
}
static
LONG
WINAPI
QueryDisplayConfig_hook(
UINT32 flags,
UINT32* numPathArrayElements,
DISPLAYCONFIG_PATH_INFO* pathArray,
UINT32* numModeInfoArrayElements,
DISPLAYCONFIG_MODE_INFO* modeInfoArray,
DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId)
{
if (!FAKE_SUBSCREEN_ADAPTER) {
return QueryDisplayConfig_orig(
flags,
numPathArrayElements, pathArray,
numModeInfoArrayElements, modeInfoArray,
currentTopologyId);
}
// call original to fill in real monitor info
UINT32 num_paths = pNumPathArrayElements_original;
UINT32 num_modes = pNumModeInfoArrayElements_original;
const auto ret = QueryDisplayConfig_orig(
flags,
&num_paths, pathArray,
&num_modes, modeInfoArray,
currentTopologyId);
if (ret != ERROR_SUCCESS) {
log_warning("popn", "QueryDisplayConfig failed with error code {}", ret);
return ret;
}
log_misc("popn", "QueryDisplayConfig returning fake monitor paths and modes");
// insert a fake path
DISPLAYCONFIG_PATH_INFO *path = &pathArray[pNumPathArrayElements_original];
*path = {};
path->flags = DISPLAYCONFIG_PATH_ACTIVE;
path->sourceInfo.adapterId.HighPart = -1;
path->sourceInfo.adapterId.LowPart = -1;
path->sourceInfo.id = -1;
path->sourceInfo.modeInfoIdx = pNumModeInfoArrayElements_original;
path->sourceInfo.statusFlags = DISPLAYCONFIG_SOURCE_IN_USE;
path->targetInfo.adapterId.HighPart = -1;
path->targetInfo.adapterId.LowPart = -1;
path->targetInfo.id = -2;
path->targetInfo.modeInfoIdx = pNumModeInfoArrayElements_original + 1;
path->targetInfo.outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
path->targetInfo.rotation = DISPLAYCONFIG_ROTATION_IDENTITY;
path->targetInfo.scaling = DISPLAYCONFIG_SCALING_IDENTITY;
path->targetInfo.refreshRate.Numerator = 60000;
path->targetInfo.refreshRate.Denominator = 1000;
path->targetInfo.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE;
path->targetInfo.targetAvailable = true;
path->targetInfo.statusFlags = DISPLAYCONFIG_TARGET_IN_USE;
// insert fake mode source
DISPLAYCONFIG_MODE_INFO *mode_source = &modeInfoArray[pNumModeInfoArrayElements_original];
*mode_source = {};
mode_source->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE;
mode_source->id = -1;
mode_source->adapterId.HighPart = -1;
mode_source->adapterId.LowPart = -1;
mode_source->sourceMode.width = FAKE_MONITOR_WIDTH;
mode_source->sourceMode.height = FAKE_MONITOR_HEIGHT;
mode_source->sourceMode.pixelFormat = DISPLAYCONFIG_PIXELFORMAT_32BPP;
mode_source->sourceMode.position.x = FAKE_MONITOR_OFFSET_X_Y;
mode_source->sourceMode.position.y = FAKE_MONITOR_OFFSET_X_Y;
// insert fake mode target
DISPLAYCONFIG_MODE_INFO *mode_target = &modeInfoArray[pNumModeInfoArrayElements_original+1];
*mode_target = {};
mode_target->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET;
mode_target->id = -2;
mode_target->adapterId.HighPart = -1;
mode_target->adapterId.LowPart = -1;
return ret;
}
static
LONG
WINAPI
DisplayConfigGetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket)
{
if (requestPacket == nullptr) {
return DisplayConfigGetDeviceInfo_orig(requestPacket);
}
// fake monitor
if (FAKE_SUBSCREEN_ADAPTER &&
(requestPacket->id == static_cast<UINT32>(-1) ||
requestPacket->id == static_cast<UINT32>(-2)) &&
requestPacket->adapterId.HighPart == static_cast<LONG>(-1) &&
requestPacket->adapterId.LowPart == static_cast<DWORD>(-1)) {
log_misc(
"popn",
"DisplayConfigGetDeviceInfo hook hit for fake monitor, type={}, size={}, id={}, luid={}/{}",
static_cast<int>(requestPacket->type),
requestPacket->size,
requestPacket->id,
requestPacket->adapterId.HighPart,
requestPacket->adapterId.LowPart);
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
const auto target = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
target->flags.value = 0;
target->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
target->connectorInstance = 0;
wcscpy(target->monitorFriendlyDeviceName, L"Spice Fake Monitor");
wcscpy(target->monitorDevicePath, L"\\\\?\\SpiceFakeMonitor");
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
const auto source = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
// value must match WrappedIDirect3D9::GetAdapterIdentifier
wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE");
} else {
log_fatal(
"popn",
"unexpected device info type {} for fake monitor",
static_cast<int>(requestPacket->type));
}
return ERROR_SUCCESS;
}
const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket);
log_misc(
"popn",
"DisplayConfigGetDeviceInfo returned {}, type={}, size={}, id={}, luid={}/{}",
ret,
static_cast<int>(requestPacket->type),
requestPacket->size,
requestPacket->id,
requestPacket->adapterId.HighPart,
requestPacket->adapterId.LowPart);
if (ret == ERROR_SUCCESS) {
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
const auto sourceName = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
log_misc(
"popn",
"... name={}",
ws2s(sourceName->viewGdiDeviceName));
} else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
log_misc(
"popn",
"... flags={}, outputTechnology: {}, connectorInstance={}, friendlyname={} path={}",
targetName->flags.value,
static_cast<int>(targetName->outputTechnology),
targetName->connectorInstance,
ws2s(targetName->monitorFriendlyDeviceName),
ws2s(targetName->monitorDevicePath));
// need to fix up some values for the primary monitor...
const auto monitors = sysutils::enumerate_monitors();
for (const auto& monitor : monitors) {
if (monitor.id == targetName->header.id &&
monitor.adapter_id_HighPart == targetName->header.adapterId.HighPart &&
monitor.adapter_id_LowPart == targetName->header.adapterId.LowPart) {
if (monitor.is_primary) {
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL;
targetName->connectorInstance = 2;
log_info(
"popn",
"... overriding primary monitor ({}) to pretend to be DP port #2",
monitor.display_name);
} else {
// TODO: is this what the game expects for subscreen?
// TODO: what if there are 3+ monitors?
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
targetName->connectorInstance = 0;
}
break;
}
}
}
}
return ret;
}
#endif
#if !SPICE64
static int __cdecl usbCheckAlive() {
return 1;
}
@@ -227,6 +496,8 @@ namespace games::popn {
return MMSYSERR_NOERROR;
}
#endif
static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) {
// get rid of the log spam
@@ -249,6 +520,8 @@ namespace games::popn {
void POPNGame::pre_attach() {
Game::pre_attach();
#if !SPICE64
// only needed for older versions (19-21, inclusive) but create them anyway since 21 and above are all M39
fileutils::dir_create_recursive_log("popn", "dev\\raw\\bookkeeping");
fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking");
@@ -257,11 +530,15 @@ namespace games::popn {
// load without resolving references
// makes game not trigger DLLMain which results in an error due to missing EZUSB device
LoadLibraryExW((MODULE_PATH / "ezusb.dll").c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES);
#endif
}
void POPNGame::attach() {
Game::attach();
#if !SPICE64
/*
* Fast Boot (TM) Patch
* Game tries to create some directories and if it fails it will sleep for 1 second
@@ -320,5 +597,87 @@ namespace games::popn {
detour::inline_hook((void *) usbWdtStartDone,
libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ"));
}
#endif
#if SPICE64 && !SPICE_XP
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value()) {
SHOW_PIKA_MONITOR_WARNING = true;
log_warning(
"popn",
"\n\n"
"!!! using -dxmainadapter option is NOT recommended due to known !!!\n"
"!!! compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! option instead of -dxmainadapter !!!\n\n"
);
deferredlogs::defer_error_messages({
"-dxmainadapter option is NOT recommended due to known compatibility ",
" issues with the game!",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" use Change Main Monitor (-mainmonitor) option instead"
});
}
// monitor hook
GetDisplayConfigBufferSizes_orig =
detour::iat_try("GetDisplayConfigBufferSizes",
GetDisplayConfigBufferSizes_hook, avs::game::DLL_INSTANCE);
QueryDisplayConfig_orig =
detour::iat_try("QueryDisplayConfig",
QueryDisplayConfig_hook, avs::game::DLL_INSTANCE);
DisplayConfigGetDeviceInfo_orig =
detour::iat_try("DisplayConfigGetDeviceInfo",
DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
// GUID_DEVCLASS_USB = {86E0D1E0-11D0-89B0-00A0C9054129}
SETUPAPI_SETTINGS settings{};
settings.class_guid[0] = 0x86E0D1E0;
settings.class_guid[1] = 0x11D08089;
settings.class_guid[2] = 0x0008E49C;
settings.class_guid[3] = 0x731F303E;
// BIO3 - 8058 (maybe U region cabs with more lights?)
// BIO3 - 8054 (probably J region?)
// BIO2 - 8050 (need bi2x_hook, everything will work except buttons 1-9)
const char property[] = "1CCF(8058)_000";
const char property_hardwareid[] = "USB\\VID_1CCF&PID_8058&MI_00\\000";
memcpy(settings.property_devicedesc, property, sizeof(property));
memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid));
setupapihook_init(avs::game::DLL_INSTANCE);
setupapihook_add(settings);
bi3a_hook_init();
// note: if for whatever reason you want to use BIO2, the game needs to be patched
// check popn.dll and look for static table that looks like this:
// 00000001 00000003 00000001 (button 1)
// 00000002 00000004 00000001 (button 2)
// 00000004 00000005 00000001 (button 3)
// 00000008 00000006 00000001 (button 4)
// 00000010 00000007 00000001 (button 5)
// 00000020 00000008 00000001 (button 6)
// 00000040 00000009 00000001 (button 7)
// 00000080 0000000A 00000001 (button 8)
// 00000100 0000000B 00000001 (button 9)
// set third column to 0 and it will work with BIO2
if (!GRAPHICS_WINDOWED) {
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE);
}
sysutils::hook_EnumDisplayDevicesA();
#endif
}
}
+19
View File
@@ -1,9 +1,28 @@
#pragma once
#include "avs/game.h"
#include "games/game.h"
#include "util/tapeled.h"
namespace games::popn {
static inline bool is_pikapika_model() {
#if SPICE64
return (avs::game::is_model("M39") && avs::game::SPEC[0] == 'D');
#else
return false;
#endif
}
#if SPICE64
constexpr int POPN_TAPELED_TOTAL = 4;
extern tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL];
#endif
extern bool SHOW_PIKA_MONITOR_WARNING;
class POPNGame : public games::Game {
public:
POPNGame();
+14 -9
View File
@@ -21,6 +21,7 @@
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/libutils.h"
#include "util/sysutils.h"
#include "misc/eamuse.h"
#include "misc/nativetouchhook.h"
#include "misc/wintouchemu.h"
@@ -350,28 +351,28 @@ namespace games::sdvx {
}
#ifdef SPICE64 // SDVX5+ specific code
// check -monitor + UFC mode
// check -dxmainadapter + UFC mode
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
SHOW_VM_MONITOR_WARNING = true;
log_warning(
"sdvx",
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
"!!! known compatibility issues with the game !!!\n"
"\n\n"
"!!! using -dxmainadapter option with Valkyrie mode is NOT !!!\n"
"!!! recommended due to known compatibility issues with the game !!!\n"
"!!! !!!\n"
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
"!!! !!!\n"
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
"!!! option instead of -dxmainadapter !!!\n\n"
);
deferredlogs::defer_error_messages({
"-monitor option is NOT recommended when running with Valkyrie mode",
"-dxmainadapter option is NOT recommended when running with Valkyrie mode",
" due to known compatibility issues with the game:",
" * game may launch in wrong resolution or refresh rate",
" * touch / mouse input may stop working in subscreen / overlay",
" recommended fix is to NOT use -monitor and instead set the primary",
" monitor in Windows settings before launching the game"
" use Change Main Monitor (-mainmonitor) option instead"
});
}
#endif
@@ -391,7 +392,7 @@ namespace games::sdvx {
}
}
}
void SDVXGame::attach() {
Game::attach();
@@ -466,6 +467,7 @@ namespace games::sdvx {
ENABLE_COM_PORT_SCAN_HOOK = true;
}
}
#endif
// ASIO device hook
@@ -501,6 +503,9 @@ namespace games::sdvx {
// remove log spam
logger::hook_add(sdvx64_spam_remover, nullptr);
if (is_valkyrie_model()) {
sysutils::hook_EnumDisplayDevicesA();
}
#endif
}
@@ -15,6 +15,7 @@
#include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "games/sdvx/sdvx.h"
#include "games/mfc/mfc.h"
#include "games/io.h"
@@ -462,7 +463,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::RegisterSoftwareDevice(void *pIniti
}
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterCount() {
return pReal->GetAdapterCount();
UINT result = pReal->GetAdapterCount();
if (!FAKE_SUBSCREEN_ADAPTER && games::popn::is_pikapika_model() && result == 1) {
FAKE_SUBSCREEN_ADAPTER = true;
log_info(
"graphics::d3d9",
"GetAdapterCount returned 1, popn needs 2 adapters - enabliing fake submonitor mode");
}
if (FAKE_SUBSCREEN_ADAPTER) {
return 2;
}
return result;
}
HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
@@ -470,10 +484,26 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterIdentifier(
DWORD Flags,
D3DADAPTER_IDENTIFIER9 *pIdentifier)
{
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1 && pIdentifier) {
*pIdentifier = {};
strcpy(pIdentifier->DeviceName, "\\\\.\\DISPLAY_SPICE_FAKE");
log_misc(
"graphics::d3d9",
"GetAdapterIdentifier called for fake subscreen adapter 1: {}",
pIdentifier->DeviceName);
return S_OK;
}
CHECK_RESULT(pReal->GetAdapterIdentifier(Adapter, Flags, pIdentifier));
}
UINT STDMETHODCALLTYPE WrappedIDirect3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) {
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
log_misc("graphics::d3d9", "GetAdapterModeCount called for fake subscreen adapter");
return 1;
}
return pReal->GetAdapterModeCount(Adapter, Format);
}
@@ -483,6 +513,19 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
UINT Mode,
D3DDISPLAYMODE *pMode)
{
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
log_misc("graphics::d3d9", "EnumAdapterModes called for fake subscreen adapter");
if (Mode == 0 && pMode) {
pMode->Width = 1280;
pMode->Height = 800;
pMode->RefreshRate = 60;
pMode->Format = D3DFMT_X8R8G8B8;
return S_OK;
} else {
return D3DERR_INVALIDCALL;
}
}
HRESULT ret = pReal->EnumAdapterModes(Adapter, Format, Mode, pMode);
if (SUCCEEDED(ret) && pMode) {
@@ -552,18 +595,6 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
modified = true;
}
}
if (!modified &&
(width > height) &&
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW)) {
log_misc(
"graphics::d3d9", "swapping width and height for mode {}, {}x{} @ {}Hz (-autoorientation)",
Mode, width, height, refresh);
pMode->Height = width;
pMode->Width = height;
modified = true;
}
}
CHECK_RESULT(ret);
@@ -591,6 +622,11 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CheckDeviceFormat(
D3DRESOURCETYPE RType,
D3DFORMAT CheckFormat)
{
if (FAKE_SUBSCREEN_ADAPTER && Adapter == 1) {
log_misc("graphics::d3d9", "CheckDeviceFormat called for fake subscreen adapter");
return D3D_OK;
}
CHECK_RESULT(pReal->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat));
}
@@ -672,7 +708,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
}
// in windowed mode, LDJ will always launch two windows, no special handling needed here
} else if (avs::game::is_model("KFC")) {
if (GRAPHICS_WINDOWED & GRAPHICS_PREVENT_SECONDARY_WINDOW) {
if (GRAPHICS_WINDOWED && GRAPHICS_PREVENT_SECONDARY_WINDOW) {
// user wants windowed mode but does not want subscreen at all
pCaps->NumberOfAdaptersInGroup = 1;
} else {
@@ -687,6 +723,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::GetDeviceCaps(UINT Adapter, D3DDEVT
} else if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
pCaps->NumberOfAdaptersInGroup = 1;
}
} else if (games::popn::is_pikapika_model()) {
pCaps->NumberOfAdaptersInGroup = 2;
}
return ret;
@@ -1033,7 +1071,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
//
// note from MSDN: `pFullscreenDisplayMode` must be NULL for windowed mode.
if (GRAPHICS_WINDOWED) {
if (avs::game::is_model({"LDJ", "KFC"}) && (BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
if (avs::game::is_model({"LDJ", "KFC", "M39"}) &&
(BehaviorFlags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
log_misc("graphics::d3d9", "disabling adapter group device in windowed mode");
D3D9_BEHAVIOR_DISABLE |= D3DCREATE_ADAPTERGROUP_DEVICE;
@@ -1105,7 +1144,8 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
*ppReturnedDeviceInterface = new WrappedIDirect3DDevice9(hFocusWindow, *ppReturnedDeviceInterface);
// initialize sub screen if IIDX/SDVX requested a multi-head context
if (avs::game::is_model({"LDJ", "KFC"}) && (orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
if (avs::game::is_model({"LDJ", "KFC", "M39"}) &&
(orig_behavior_flags & D3DCREATE_ADAPTERGROUP_DEVICE)) {
graphics_d3d9_ldj_init_sub_screen(*ppReturnedDeviceInterface, &pPresentationParameters[1]);
}
}
@@ -1144,8 +1184,6 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
*/
if (GRAPHICS_WINDOWED) {
log_info("graphics::d3d9", "creating additional swap chain");
present_params->Windowed = true;
present_params->FullScreen_RefreshRateInHz = 0;
@@ -1154,18 +1192,23 @@ static void graphics_d3d9_ldj_init_sub_screen(IDirect3DDevice9Ex *device, D3DPRE
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to create additional swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "created additional swap chain for windowed mode");
}
} else {
hr = device->GetSwapChain(1, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to acquire fullscreen sub swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "acquired fullscreen sub swap chain");
return;
}
hr = device->CreateAdditionalSwapChain(present_params, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to get additional swap chain, hr={}", FMT_HRESULT(hr));
} else {
log_info("graphics::d3d9", "created additional swap chain for fullscreen mode");
}
}
}
@@ -1208,7 +1251,10 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
if (SUB_SWAP_CHAIN != nullptr) {
wintouchemu::update();
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW) {
// newer versions of exceed gear needs SUBSCREEN_FORCE_REDRAW
// (when enabled on older versions of EG, you end up with graphical glitches on the subscreen
// early versions of popn HC needs this as well, otherwise the subscreen doesn't update at all
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW || games::popn::is_pikapika_model()) {
SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
}
}
@@ -1408,7 +1454,8 @@ void graphics_d3d9_on_present(
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
const bool is_gfdm_arena =
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW;
if (is_vm || is_tdj || is_gfdm_arena) {
const bool is_pika = games::popn::is_pikapika_model();
if (is_vm || is_tdj || is_gfdm_arena || is_pika) {
graphics_d3d9_ldj_on_present(wrapped_device);
}
@@ -251,7 +251,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
bool create_swap_chain = false;
if (avs::game::is_model({"LDJ", "KFC"})) {
if (avs::game::is_model({"LDJ", "KFC", "M39"})) {
create_swap_chain = true;
} else if (games::gitadora::is_arena_model() &&
games::gitadora::ARENA_SINGLE_WINDOW &&
@@ -305,7 +305,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
}
bool swap = false;
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) {
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC", "M39"})) {
swap = true;
}
if (games::gitadora::is_arena_model() &&
@@ -344,7 +344,7 @@ UINT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetNumberOfSwapChains() {
UINT n = pReal->GetNumberOfSwapChains();
if (sub_swapchain && avs::game::is_model({"LDJ", "KFC"})) {
if (sub_swapchain && avs::game::is_model({"LDJ", "KFC", "M39"})) {
n += 1;
}
+291 -87
View File
@@ -1,3 +1,6 @@
// GetDisplayConfigBufferSizes etc is Vista+
#define _WIN32_WINNT 0x0601
#include <initguid.h>
#include "graphics.h"
@@ -13,6 +16,7 @@
#include "games/ddr/ddr.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "launcher/shutdown.h"
#include "overlay/overlay.h"
@@ -35,6 +39,8 @@ struct CaptureData {
HWND TDJ_SUBSCREEN_WINDOW = nullptr;
HWND SDVX_SUBSCREEN_WINDOW = nullptr;
HWND GFDM_SUBSCREEN_WINDOW = nullptr;
HWND POPN_SUBSCREEN_WINDOW = nullptr;
bool FAKE_SUBSCREEN_ADAPTER = false;
// icon
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
@@ -58,7 +64,6 @@ bool GRAPHICS_CAPTURE_CURSOR = false;
bool GRAPHICS_LOG_HRESULT = false;
bool GRAPHICS_SDVX_FORCE_720 = false;
bool GRAPHICS_SHOW_CURSOR = false;
graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
bool GRAPHICS_WINDOWED = false;
std::vector<HWND> GRAPHICS_WINDOWS;
UINT GRAPHICS_FORCE_REFRESH = 0;
@@ -233,9 +238,9 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
// This allows touches received on subscreen window to be translated correctly.
update_spicetouch_window_dimensions(hWnd);
// log_misc(
// "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match",
// "graphics", "detected window change ({}x{} @ {}, {}), updating touch coord-space to match",
// SPICETOUCH_TOUCH_WIDTH, SPICETOUCH_TOUCH_HEIGHT, SPICETOUCH_TOUCH_X, SPICETOUCH_TOUCH_Y);
// Update SPICETOUCH window if present
if (SPICETOUCH_TOUCH_HWND) {
SetWindowPos(
@@ -301,30 +306,6 @@ static BOOL WINAPI ClipCursor_hook(const RECT *lpRect) {
return ClipCursor_orig(lpRect);
}
static void fix_up_rb_auto_rotate_dimensions(DWORD &w, DWORD &h) {
// reflec beat
// for whatever reason, rb creates the first window it weird dimensions (774x1389 for reflesia, for example)
// so doing ChangeDisplaySettings as-is ends up with DISP_CHANGE_BADMODE
// while a second window (with title D3DProxyWindow) does get created with correct dimensions, it's
// too late to rotate the window then; as a workaround force the display change now with patched up dimensions
bool changed = false;
if (w == 774) {
// LBR, MBR reflesia
w = 768;
h = 1360;
changed = true;
} else if (w == 1086) {
// MBR (before reflesia)
w = 1080;
h = 1920;
changed = true;
}
if (changed) {
log_misc("graphics", "fix auto-rotate dimensions for reflec beat: w={}, h={}", w, h);
}
}
static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU hMenu, HINSTANCE hInstance,
@@ -346,62 +327,6 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
fmt::ptr(hInstance),
fmt::ptr(lpParam));
// set display orientation and/or refresh rate
// only set orientation when the target window is portrait
// (avoid doing this for SDVX subscreen which is in landscape, for example)
auto adjust_orientation =
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW);
// reflec beat
if (avs::game::is_model("KBR")) {
// KBR (rb1) launches landscape and draws rotated by itself, don't do any rotation
adjust_orientation = false;
} else if (avs::game::is_model({"LBR", "MBR"}) && window_name == "D3DProxyWindow") {
// do not auto-rotate for second window (D3DProxyWindow)
adjust_orientation = false;
}
if ((nHeight > nWidth && adjust_orientation) || GRAPHICS_FORCE_REFRESH > 0) {
DEVMODE mode {};
// get display settings
if (EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &mode)) {
if (adjust_orientation) {
DWORD orientation = GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ? DMDO_90 : DMDO_270;
log_misc(
"graphics",
"auto-rotate: call ChangeDisplaySettings and rotate display to DMDO_xx mode {}, w={}, h={}",
orientation, nWidth, nHeight);
mode.dmPelsWidth = nWidth;
mode.dmPelsHeight = nHeight;
mode.dmDisplayOrientation = orientation;
// reflec beat
if (avs::game::is_model({"LBR", "MBR"})) {
fix_up_rb_auto_rotate_dimensions(mode.dmPelsWidth, mode.dmPelsHeight);
}
}
if (GRAPHICS_FORCE_REFRESH > 0) {
log_info(
"graphics",
"call ChangeDisplaySettings to force refresh rate: {} => {} Hz (-graphics-force-refresh)",
mode.dmDisplayFrequency,
GRAPHICS_FORCE_REFRESH);
mode.dmDisplayFrequency = GRAPHICS_FORCE_REFRESH;
}
const auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
if (disp_res != DISP_CHANGE_SUCCESSFUL) {
log_warning("graphics", "ChangeDisplaySettings failed: {}", disp_res);
}
} else {
log_warning("graphics", "failed to get display settings");
}
}
// gfdm
if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
// set window name
@@ -412,17 +337,18 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
bool is_tdj_sub_window = avs::game::is_model("LDJ") && window_name.ends_with(" sub");
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
bool is_popn_sub_window = avs::game::is_model("M39") && window_name.ends_with("Sub Screen");
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
// hide maximize button (prevent misaligned touches)
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) {
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window || is_popn_sub_window) {
dwStyle &= ~(WS_MAXIMIZEBOX);
}
// mouse clicks become misaligned when resized
if (is_gfdm_sub_window) {
dwStyle &= ~(WS_SIZEBOX);
}
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
if ((is_tdj_sub_window || is_sdvx_sub_window || is_popn_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
}
@@ -444,6 +370,10 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
}
}
if (GRAPHICS_WINDOWED) {
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
}
// call original
HWND result = CreateWindowExA_orig(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
hWndParent, hMenu, hInstance, lpParam);
@@ -452,7 +382,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
if (is_tdj_sub_window) {
// TDJ windowed mode: remember the subscreen window handle for later
TDJ_SUBSCREEN_WINDOW = result;
// hook for preventing the closing of subscreen window
if (GRAPHICS_IIDX_WSUB) {
graphics_hook_subscreen_window(TDJ_SUBSCREEN_WINDOW);
@@ -471,6 +401,13 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
}
if (is_popn_sub_window) {
POPN_SUBSCREEN_WINDOW = result;
if (!GRAPHICS_PREVENT_SECONDARY_WINDOW) {
graphics_hook_subscreen_window(POPN_SUBSCREEN_WINDOW);
}
}
disable_touch_indicators(result);
log_misc(
"graphics",
@@ -539,6 +476,10 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
}
}
if (GRAPHICS_WINDOWED) {
graphics_window_check_bounds_before_creation(x, y, nWidth, nHeight);
}
// call original
HWND result = CreateWindowExW_orig(
dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight,
@@ -611,7 +552,7 @@ static BOOL WINAPI MoveWindow_hook(HWND hWnd, int X, int Y, int nWidth, int nHei
if (GRAPHICS_IIDX_WSUB) {
// (Experimental) Show subscreen in windowed mode
graphics_load_windowed_subscreen_parameters();
RECT rect {};
DWORD dwStyle;
@@ -734,6 +675,13 @@ static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
return true;
}
if (games::popn::is_pikapika_model() &&
GRAPHICS_PREVENT_SECONDARY_WINDOW &&
hWnd == POPN_SUBSCREEN_WINDOW) {
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
return true;
}
// call original
return ShowWindow_orig(hWnd, nCmdShow);
}
@@ -1149,3 +1097,259 @@ std::string graphics_screenshot_genpath() {
id++;
}
}
static std::string get_dmdo_string(DWORD dmdo) {
switch (dmdo) {
case DMDO_DEFAULT:
return "DMDO_DEFAULT";
case DMDO_90:
return "DMDO_90";
case DMDO_180:
return "DMDO_180";
case DMDO_270:
return "DMDO_270";
default:
return fmt::format("Unknown ({})", dmdo);
}
}
void change_primary_monitor(const std::string &monitor_name) {
log_misc("graphics", "try changing primary monitor to {}...", monitor_name);
// for WinXP, since these are Vista+ or 7+ APIs
const auto user32 = LoadLibraryA("user32.dll");
if (!user32) {
log_warning("graphics", "can't find user32.dll???");
return;
}
const auto GetDisplayConfigBufferSizes_addr =
reinterpret_cast<decltype(GetDisplayConfigBufferSizes) *>(
GetProcAddress(user32, "GetDisplayConfigBufferSizes"));
const auto QueryDisplayConfig_addr =
reinterpret_cast<decltype(QueryDisplayConfig) *>(
GetProcAddress(user32, "QueryDisplayConfig"));
const auto DisplayConfigGetDeviceInfo_addr =
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
GetProcAddress(user32, "DisplayConfigGetDeviceInfo"));
const auto SetDisplayConfig_addr =
reinterpret_cast<decltype(SetDisplayConfig) *>(
GetProcAddress(user32, "SetDisplayConfig"));
if (GetDisplayConfigBufferSizes_addr == nullptr || QueryDisplayConfig_addr == nullptr ||
DisplayConfigGetDeviceInfo_addr == nullptr || SetDisplayConfig_addr == nullptr) {
log_warning("graphics", "cannot change primary monitor, OS does not support required APIs)");
return;
}
UINT32 path_count = 0;
UINT32 mode_count = 0;
std::vector<DISPLAYCONFIG_PATH_INFO> paths;
std::vector<DISPLAYCONFIG_MODE_INFO> modes;
bool succeeded = false;
// in a retry loop, try to query for display config
// retry loop is needed because it can fail with ERROR_INSUFFICIENT_BUFFER
for (int attempt = 0; attempt < 5; ++attempt) {
auto status = GetDisplayConfigBufferSizes_addr(QDC_DATABASE_CURRENT, &path_count, &mode_count);
if (status != ERROR_SUCCESS) {
log_warning("graphics", "GetDisplayConfigBufferSizes failed: {}", status);
return;
}
paths.resize(path_count);
modes.resize(mode_count);
DISPLAYCONFIG_TOPOLOGY_ID topology_id = DISPLAYCONFIG_TOPOLOGY_INTERNAL;
status = QueryDisplayConfig_addr(
QDC_DATABASE_CURRENT,
&path_count,
paths.data(),
&mode_count,
modes.data(),
&topology_id);
if (status == ERROR_SUCCESS) {
// Shrink to actual returned counts
paths.resize(path_count);
modes.resize(mode_count);
succeeded = true;
break;
}
if (status != ERROR_INSUFFICIENT_BUFFER) {
log_warning("graphics", "QueryDisplayConfig failed: {}", status);
return;
}
Sleep(500);
}
if (!succeeded) {
log_warning("graphics", "QueryDisplayConfig failed after reaching max retries");
return;
}
LONG x = 0;
LONG y = 0;
bool found = false;
// find the new main monitor
for (auto& mode : modes) {
if (mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
continue;
}
DISPLAYCONFIG_SOURCE_DEVICE_NAME name = {};
name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
name.header.size = sizeof(name);
name.header.adapterId = mode.adapterId;
name.header.id = mode.id;
if (DisplayConfigGetDeviceInfo_addr(&name.header) != ERROR_SUCCESS) {
continue;
}
const auto device_name = std::string(ws2s(name.viewGdiDeviceName));
if (monitor_name == device_name) {
x = mode.sourceMode.position.x;
y = mode.sourceMode.position.y;
found = true;
log_info(
"graphics",
"new main monitor target found: {}, old position: ({}, {})",
monitor_name,
x, y);
break;
}
}
if (!found) {
log_fatal(
"graphics",
"new main monitor target not found, check -mainmonitor option: {}",
monitor_name);
return;
}
// update monitor positions so that the new monitor is at (0, 0)
for (auto& mode : modes) {
if (mode.infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
mode.sourceMode.position.x -= x;
mode.sourceMode.position.y -= y;
}
}
// finally, commit the new display config
const auto status = SetDisplayConfig_addr(
path_count,
paths.data(),
mode_count,
modes.data(),
SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG);
if (status != ERROR_SUCCESS) {
log_fatal("graphics", "SetDisplayConfig failed, check -mainmonitor option: {}", status);
}
// a little extra time for windows to settle and redraw things
Sleep(2000);
}
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate) {
// note: all of this is only being done for the primary motnior
// get current settings
DEVMODEA dm = {};
dm.dmSize = sizeof(dm);
if (!EnumDisplaySettingsExA(NULL, ENUM_CURRENT_SETTINGS, &dm, 0)) {
log_warning("graphics", "EnumDisplaySettingsExa failed {}", get_last_error_string());
return;
}
bool needs_update = false;
if (target_orientation.has_value()) {
// convert orientation values, and figure out if resolution needs to be swapped
bool rotate_resolution = false;
DWORD orientation = DMDO_DEFAULT;
switch (target_orientation.value()) {
case ORIENTATION_CW:
orientation = DMDO_90;
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
rotate_resolution = true;
}
break;
case ORIENTATION_CCW:
orientation = DMDO_270;
if (dm.dmDisplayOrientation == DMDO_DEFAULT || dm.dmDisplayOrientation == DMDO_180) {
rotate_resolution = true;
}
break;
case ORIENTATION_FLIPPED:
orientation = DMDO_180;
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
rotate_resolution = true;
}
break;
default:
orientation = DMDO_DEFAULT;
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) {
rotate_resolution = true;
}
break;
}
// update orientation (and resolution if it must be swapped)
if (dm.dmDisplayOrientation != orientation) {
log_misc("graphics",
"current orientation {} => desired orientation {}",
get_dmdo_string(dm.dmDisplayOrientation), get_dmdo_string(orientation));
const DWORD originalWidth = dm.dmPelsWidth;
const DWORD originalHeight = dm.dmPelsHeight;
// change orientation
dm.dmDisplayOrientation = orientation;
dm.dmFields |= DM_DISPLAYORIENTATION;
// rotate resolution
if (rotate_resolution) {
dm.dmPelsWidth = originalHeight;
dm.dmPelsHeight = originalWidth;
dm.dmFields |= DM_PELSHEIGHT | DM_PELSWIDTH;
}
needs_update = true;
}
}
// update refresh rate
if (target_refresh_rate > 0) {
log_misc("graphics",
"current refresh rate {} => desired refresh rate {}",
dm.dmDisplayFrequency, target_refresh_rate);
dm.dmDisplayFrequency = target_refresh_rate;
dm.dmFields |= DM_DISPLAYFREQUENCY;
needs_update = true;
}
if (!needs_update) {
// nothing to do
return;
}
const auto result = ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
if (result != DISP_CHANGE_SUCCESSFUL) {
log_fatal(
"graphics",
"failed to update display settings ({}px x {}px @ {}Hz): error {}, double check options",
dm.dmPelsWidth,
dm.dmPelsHeight,
dm.dmDisplayFrequency,
result);
} else {
log_info("graphics", "display settings updated successfully ({}px x {}px @ {}Hz)",
dm.dmPelsWidth,
dm.dmPelsHeight,
dm.dmDisplayFrequency);
}
// sleep for a little bit after changing monitor settings to delay game launch
Sleep(1000);
}
+8 -2
View File
@@ -15,6 +15,7 @@ enum graphics_orientation {
ORIENTATION_CW = 0,
ORIENTATION_CCW = 1,
ORIENTATION_NORMAL = 2,
ORIENTATION_FLIPPED = 3
};
enum graphics_dx9on12_state {
@@ -29,7 +30,6 @@ extern bool GRAPHICS_LOG_HRESULT;
extern bool GRAPHICS_SDVX_FORCE_720;
extern bool GRAPHICS_SHOW_CURSOR;
extern bool GRAPHICS_WINDOWED;
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
extern std::vector<HWND> GRAPHICS_WINDOWS;
extern UINT GRAPHICS_FORCE_REFRESH;
extern std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
@@ -63,9 +63,11 @@ extern bool GRAPHICS_WSUB_BORDERLESS;
extern bool GRAPHICS_WSUB_ALWAYS_ON_TOP;
extern HWND TDJ_SUBSCREEN_WINDOW;
extern HWND SDVX_SUBSCREEN_WINDOW;
extern HWND POPN_SUBSCREEN_WINDOW;
extern HWND GFDM_SUBSCREEN_WINDOW;
extern bool SUBSCREEN_FORCE_REDRAW;
extern bool FAKE_SUBSCREEN_ADAPTER;
// settings
extern std::string GRAPHICS_DEVICEID;
@@ -99,7 +101,7 @@ std::string graphics_screenshot_genpath();
// graphics_windowed.cpp
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void graphics_capture_initial_window(HWND hWnd);
void graphics_update_window_style(HWND hWnd);
void graphics_update_window_style(HWND hWnd, bool force_update=false);
void graphics_update_z_order(HWND hWnd, bool always_on_top);
void graphics_move_resize_window(HWND hWnd);
bool graphics_window_options_breaks_game();
@@ -107,3 +109,7 @@ bool graphics_window_decoration_change_crashes_game();
bool graphics_window_resize_breaks_game();
bool graphics_window_move_and_resize_breaks_game();
void graphics_load_windowed_subscreen_parameters();
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height);
void change_primary_monitor(const std::string &monitor_name);
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
@@ -121,13 +121,12 @@ void graphics_capture_initial_window(HWND hWnd) {
cfg::SCREENRESIZE->client_height);
graphics_move_resize_window(hWnd);
}
// ddr hangs when window frame doesn't have overlapped
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
log_info(
"graphics-windowed", "[{}] change window style - decoration: {}",
fmt::ptr(hWnd),
cfg::SCREENRESIZE->window_decoration);
graphics_update_window_style(hWnd);
graphics_update_window_style(hWnd, true); // force
}
if (cfg::SCREENRESIZE->window_always_on_top) {
log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
@@ -385,11 +384,11 @@ void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect) {
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio returned");
}
void graphics_update_window_style(HWND hWnd) {
void graphics_update_window_style(HWND hWnd, bool force_update) {
if (!GRAPHICS_WINDOWED) {
return;
}
if (graphics_window_decoration_change_crashes_game()) {
if (!force_update && graphics_window_decoration_change_crashes_game()) {
return;
}
@@ -504,7 +503,14 @@ bool graphics_window_decoration_change_crashes_game() {
static bool result = false;
std::call_once(flag, []() {
// ddr crashes when frame style changes
result = avs::game::is_model("MDX");
if (avs::game::is_model("MDX")) {
result = true;
}
// arena model hangs immediately when decoration changes
if (games::gitadora::is_arena_model()) {
result = true;
}
// changing window decoration also changes dimensions and position
// so this must be prevented
@@ -560,3 +566,56 @@ bool graphics_window_resize_breaks_game() {
return result;
}
void graphics_window_check_bounds_before_creation(int &x, int &y, const int width, const int height) {
RECT rect = {
.left = x,
.top = y,
.right = x + width,
.bottom = y + height
};
HMONITOR mon = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
if (mon != nullptr) {
return;
}
log_warning(
"graphics-windowed",
"window is completely out of bounds, resetting position: ({}, {}) => (0, 0)",
x, y);
x = 0;
y = 0;
rect = {
.left = x,
.top = y,
.right = x + width,
.bottom = y + height
};
mon = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = {};
mi.cbSize = sizeof(mi);
if (!GetMonitorInfo(mon, &mi)) {
log_warning(
"graphics-windowed",
"failed to get monitor info for out of bounds window");
return;
}
log_misc(
"graphics-windowed",
"nearest monitor for this window has work area ({}, {}),({}, {})",
mi.rcWork.left, mi.rcWork.top, mi.rcWork.right, mi.rcWork.bottom);
RECT bounds = mi.rcWork;
const bool fully_visible =
(rect.left >= bounds.left &&
rect.top >= bounds.top &&
rect.right <= bounds.right &&
rect.bottom <= bounds.bottom);
if (!fully_visible) {
log_warning("graphics-windowed", "window will not be fully visible on monitor");
}
}
+52
View File
@@ -30,6 +30,8 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr;
static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr;
#endif
static NTSTATUS NTAPI RtlMultiByteToUnicodeN_hook(
@@ -94,6 +96,10 @@ static LCID WINAPI GetSystemDefaultLCID_hook() {
return 0x411;
}
static LCID WINAPI GetThreadLocale_hook() {
return 0x411;
}
#endif
static int WINAPI MultiByteToWideChar_hook(
@@ -214,6 +220,31 @@ WideCharToMultiByte_hook(
lpUsedDefaultChar);
}
int
WINAPI
GetLocaleInfoA_hook(
LCID Locale,
LCTYPE LCType,
LPSTR lpLCData,
int cchData) {
if (LCType == LOCALE_SISO639LANGNAME && lpLCData != NULL && cchData >= 3) {
log_misc("hooks::lang", "GetLocaleInfoA_hook hit ({:#x}, LOCALE_SISO639LANGNAME), return `ja`", Locale);
strcpy(lpLCData, "ja");
return 3;
}
if (LCType == LOCALE_SISO3166CTRYNAME && lpLCData != NULL && cchData >= 3) {
log_misc("hooks::lang",
"GetLocaleInfoA_hook hit ({:#x}, LOCALE_SISO3166CTRYNAME), return `JP`", Locale);
strcpy(lpLCData, "JP");
return 3;
}
log_misc("hooks::lang", "GetLocaleInfoA_hook hit, {:#x}, {:#x}", Locale, LCType);
return GetLocaleInfoA_orig(Locale, LCType, lpLCData, cchData);
}
#endif
void hooks::lang::early_init() {
@@ -244,6 +275,27 @@ void hooks::lang::early_init() {
}
#ifdef SPICE64
if (avs::game::is_model("UJK")) {
// CCJ build of Unity calls GetThreadLocale,
// and then GetLocaleInfoA to figure out the locale; eventually this is
// used to figure out the decimal separator, which is then used in parsing
// Double values - which fails carding in if it isn't "."
log_info("hooks::lang", "hooking GetThreadLocale");
detour::trampoline_try(
"kernel32.dll",
"GetThreadLocale",
GetThreadLocale_hook,
&GetThreadLocale_orig);
log_info("hooks::lang", "hooking GetLocaleInfoA");
detour::trampoline_try(
"kernel32.dll",
"GetLocaleInfoA",
GetLocaleInfoA_hook,
&GetLocaleInfoA_orig);
}
// for TDJ subscreen search keyboard
if (avs::game::is_model("LDJ") && games::iidx::TDJ_MODE) {
log_info("hooks::lang", "hooking IsDBCSLeadByte");
+78 -30
View File
@@ -351,9 +351,9 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
}
if (options[launcher::Options::DisplayAdapter].is_active() &&
options[launcher::Options::DisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32();
if (options[launcher::Options::DXDisplayAdapter].is_active() &&
options[launcher::Options::DXDisplayAdapter].value_uint32() != D3DADAPTER_DEFAULT) {
D3D9_ADAPTER = options[launcher::Options::DXDisplayAdapter].value_uint32();
// when we fix up adapter numbers, we only fix the first adapter, and not any subsequent
// adapters, so we can't deal with multi-monitor games
@@ -374,12 +374,15 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::AllowEA3Verbose].value_bool()) {
avs::ea3::EA3_DEBUG_VERBOSE = true;
}
std::optional<graphics_orientation> monitor_orientation;
if (options[launcher::Options::spice2x_AutoOrientation].is_active()) {
GRAPHICS_ADJUST_ORIENTATION =
monitor_orientation =
(graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32();
} else if (options[launcher::Options::AdjustOrientation].value_bool()) {
GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_CW;
monitor_orientation = ORIENTATION_CW;
}
if (options[launcher::Options::spice2x_NoD3D9DeviceHook].value_bool()) {
D3D9_DEVICE_HOOK_DISABLE = true;
// touch emulation gets disabled, might as well turn these on
@@ -470,6 +473,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::spice2x_SDVXSubRedraw].value_bool()) {
SUBSCREEN_FORCE_REDRAW = true;
}
if (options[launcher::Options::SDVXSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::SDVXSubMonitorOverride].value_text();
}
if (options[launcher::Options::LoadIIDXModule].value_bool()) {
attach_iidx = true;
}
@@ -584,6 +590,13 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::PopnMusicForceSDMode].value_bool()) {
avs::ea3::PCB_TYPE = 0;
}
if (options[launcher::Options::PopnNoSub].value_bool()) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
}
if (options[launcher::Options::PopnSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::PopnSubMonitorOverride].value_text();
}
if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) {
attach_mga = true;
}
@@ -980,7 +993,7 @@ int main_implementation(int argc, char *argv[]) {
// do not explicitly set GRAPHICS_9_ON_12_STATE to default here; must respect
// legacy Graphics9On12 option from above if set
}
if (options[launcher::Options::NoLegacy].value_bool() && !cfg::CONFIGURATOR_STANDALONE) {
if (options[launcher::Options::NoLegacy].value_bool() && !cfg_run) {
rawinput::NOLEGACY = true;
}
if (options[launcher::Options::RichPresence].value_bool()) {
@@ -1124,6 +1137,9 @@ int main_implementation(int argc, char *argv[]) {
options[launcher::Options::SDVXWindowedSubscreenAlwaysOnTop].value_bool()) {
GRAPHICS_WSUB_ALWAYS_ON_TOP = true;
}
if (options[launcher::Options::IIDXSubMonitorOverride].is_active()) {
sysutils::SECOND_MONITOR_OVERRIDE = options[launcher::Options::IIDXSubMonitorOverride].value_text();
}
if (options[launcher::Options::spice2x_JubeatLegacyTouch].value_bool()) {
games::jb::TOUCH_LEGACY_BOX = true;
@@ -1224,7 +1240,7 @@ int main_implementation(int argc, char *argv[]) {
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::THREAD_MODE;
} else if (options[launcher::Options::DDRP4IOBufferMode].value_text() == "backfill") {
acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::BACKFILL_MODE;
}
}
}
if (options[launcher::Options::MidiAlgoVer].is_active()) {
@@ -1291,7 +1307,19 @@ int main_implementation(int argc, char *argv[]) {
}
// log
#ifndef SPICE_LINUX
#if SPICE_LINUX
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
#endif
#elif SPICE_XP
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for WinXP");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for WinXP");
#endif
#else
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)");
#elif SPICE32_LARGE_ADDRESS_AWARE
@@ -1299,12 +1327,6 @@ int main_implementation(int argc, char *argv[]) {
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)");
#endif
#else
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
#endif
#endif
log_info("launcher", "{}", VERSION_STRING);
@@ -1361,7 +1383,7 @@ int main_implementation(int argc, char *argv[]) {
log_warning(
"launcher",
"multiple values for -{}, command line args take precedence: {}",
option.get_definition().name,
option.get_definition().name,
value);
} else {
log_warning(
@@ -1376,7 +1398,7 @@ int main_implementation(int argc, char *argv[]) {
if (launcher::USE_CMD_OVERRIDE) {
log_info(
"launcher",
"user specified -cmdoverride, therefore command line args took precedence over spicecfg");
"user specified -cmdoverride, therefore command line args took precedence over spicecfg");
} else {
log_warning(
"launcher",
@@ -1742,11 +1764,21 @@ int main_implementation(int argc, char *argv[]) {
break;
}
// HELLO! Pop'n Music
if (check_dll("popn.dll")) {
avs::game::DLL_NAME = "popn.dll";
attach_io = true;
attach_hpm = true;
if (check_dll("libaio-iob2_video.dll")) {
// pop'n music (pika cabinet)
attach_popn = true;
// automatically show cursor in windowed mode to interact with second window (sub)
if (GRAPHICS_WINDOWED) {
GRAPHICS_SHOW_CURSOR = true;
}
} else {
// HELLO! Pop'n Music
attach_hpm = true;
}
break;
}
@@ -2133,6 +2165,12 @@ int main_implementation(int argc, char *argv[]) {
sysutils::print_gpus();
}
// fix up monitor
if (options[launcher::Options::PrimaryMonitor].is_active()) {
change_primary_monitor(options[launcher::Options::PrimaryMonitor].value_text());
}
update_monitor_on_boot(monitor_orientation, GRAPHICS_FORCE_REFRESH);
// initialize raw input
RI_MGR = std::make_unique<rawinput::RawInputManager>();
for (const auto &device : sextet_devices) {
@@ -2217,7 +2255,7 @@ int main_implementation(int argc, char *argv[]) {
game->attach();
}
#ifdef SPICE64
#if SPICE64 && !SPICE_XP
if (!cfg::CONFIGURATOR_STANDALONE) {
if (games::iidx::TDJ_CAMERA) {
games::iidx::init_camera_hooks();
@@ -2498,7 +2536,7 @@ int main_implementation(int argc, char *argv[]) {
// disable poke
games::iidx::poke::disable();
#ifdef SPICE64
#if SPICE64 && !SPICE_XP
games::iidx::camera_release();
#endif
@@ -2569,9 +2607,10 @@ void dump_button_bindings(std::vector<Button> *buttons) {
if (button->isNaive()) {
log_misc(
"rawinput", " [{}] dev=Naive, vkey={}",
"rawinput", " [{}] dev=Naive, vkey={} ({})",
button->getName(),
button->getVKey()
button->getVKey(),
button->getVKeyString()
);
} else {
log_misc(
@@ -2584,16 +2623,25 @@ void dump_button_bindings(std::vector<Button> *buttons) {
}
for (auto& alt : button->getAlternatives()) {
if (alt.getVKey() == INVALID_VKEY) {
if (!alt.isValid()) {
continue;
}
log_misc(
"rawinput", " [{}] (alt) dev={}, vkey={}, analogtype={}",
button->getName(),
alt.isNaive() ? "Naive" : alt.getDeviceIdentifier(),
alt.getVKey(),
static_cast<uint32_t>(alt.getAnalogType())
);
if (alt.isNaive()) {
log_misc(
"rawinput", " [{}] (alt) dev=Naive, vkey={} ({})",
alt.getName(),
alt.getVKey(),
alt.getVKeyString()
);
} else {
log_misc(
"rawinput", " [{}] (alt) dev={}, vkey={}, analogtype={}",
alt.getName(),
alt.getDeviceIdentifier(),
alt.getVKey(),
static_cast<uint32_t>(alt.getAnalogType())
);
}
}
}
}
+75 -16
View File
@@ -23,6 +23,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
"Game Options",
"Common",
"Network",
"Monitor",
"Graphics (Common)",
"Graphics (Full Screen)",
"Graphics (Windowed)",
@@ -206,9 +207,24 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Common",
},
{
.title = "Monitor",
// PrimaryMonitor
.title = "Change Main Monitor",
.name = "mainmonitor",
.desc = "Changes the primary monitor before launching the game. It will be restored on exit.\n\n"
"This may fail to work properly on hybrid laptops with iGPU + dGPU.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY2",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
// DXDisplayAdapter
.title = "DX Primary Display Adapter Override",
.name = "monitor",
.desc = "Sets the display that the game will be opened in, for multiple monitors.\n\n"
.display_name = "dxmainadapter",
.aliases = "dxmainadapter",
.desc = "Prefer to use Change Main Monitor option instead of this one.\n\n"
"Sets the display that the game will be opened in, for multiple monitors.\n\n"
"0 is the primary monitor, 1 is the second monitor, and so on.\n\n"
"Not all games will respect this. Not recommended for multi-monitor games like Lightning Model / Valkyrie Model modes. "
"Disable Full Screen Optimizations for best results.",
@@ -216,21 +232,19 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Graphics (Full Screen)",
},
{
.title = "Only Use One Monitor",
.title = "Only Use Main Monitor For Full Screen",
.name = "graphics-force-single-adapter",
.desc = "Force the graphics device to be opened utilizing only one adapter in multi-monitor systems.\n\n"
"May cause unstable framerate and desyncs, especially if monitors have different refresh rates!",
.type = OptionType::Bool,
.category = "Graphics (Full Screen)",
.category = "Monitor",
},
{
.title = "Force Monitor Refresh Rate",
.title = "Monitor Refresh Rate",
.name = "graphics-force-refresh",
.desc =
"Attempt to change the refresh rate for the primary monitor before the game boots; "
"works in both full screen and windowed modes, but known to fail for some games.",
.desc = "Change the refresh rate for the primary monitor before launching the game. It will be restored on exit.",
.type = OptionType::Integer,
.category = "Graphics (Common)",
.category = "Monitor",
},
{
// FullscreenResolution
@@ -914,6 +928,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{"bottomright", "for landscape"},
},
},
{
// SDVXSubMonitorOverride
.title = "SDVX Subscreen Monitor Override",
.name = "sdvxsubmonitor",
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY3",
.game_name = "Sound Voltex",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
.title = "Force Load DDR Module",
.name = "ddr",
@@ -963,6 +988,26 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Pop'n Music",
.category = "Game Options",
},
{
// PopnNoSub
.title = "Pop'n Music PikaPika Subscreen Disable",
.name = "popnnosub",
.desc = "Prevents PikaPika model subscreen from launching a separate window.",
.type = OptionType::Bool,
.game_name = "Pop'n Music",
.category = "Game Options",
},
{
// PopnSubMonitorOverride
.title = "Pop'n Music PikaPika Subscreen Monitor Override",
.name = "popnsubmonitor",
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY3",
.game_name = "Pop'n Music",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
.title = "Force Load HELLO! Pop'n Music Module",
.name = "hpm",
@@ -1847,17 +1892,20 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
},
{
// spice2x_AutoOrientation
.title = "Auto-rotate Display",
.title = "Rotate Monitor",
.name = "sp2x-autoorientation",
.display_name = "autoorientation",
.aliases= "autoorientation",
.desc = "Automatically adjust the orientation of your display when launched. "
"WARNING: game may launch at incorrect refresh rate! Use in combination with "
"-graphics-force-refresh and potentially either -9on12 or full-screen optimizations (FSO) to fix.",
.desc = "Change the orientation of the primary display before launching the game. It will be restored on exit",
.type = OptionType::Enum,
.category = "Graphics (Common)",
// match graphics_orientation
.elements = {{"0", "90 (CW)"}, {"1", "270 (CCW)"}},
.category = "Monitor",
// match graphics_orientation enum
.elements = {
{"0", "Portrait"},
{"1", "Portrait, Flipped"},
{"2", "Landscape"},
{"3", "Landscape, Flipped"}
},
},
{
.title = "AVS Log Level",
@@ -2326,6 +2374,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.game_name = "Beatmania IIDX",
.category = "Game Options",
},
{
// IIDXSubMonitorOverride
.title = "IIDX TDJ Subscreen Monitor Override",
.name = "iidxsubmonitor",
.desc = "If you have three or more monitors, this option can be set to tell the game which monitor is the subscreen.",
.type = OptionType::Text,
.setting_name = "\\\\.\\DISPLAY3",
.game_name = "Beatmania IIDX",
.category = "Monitor",
.picker = OptionPickerType::Monitor,
},
{
// spice2x_IIDXEmulateSubscreenKeypadTouch
.title = "IIDX TDJ Subscreen Keypad Touch Emulation",
+6 -1
View File
@@ -26,7 +26,8 @@ namespace launcher {
ExecuteScript,
CaptureCursor,
ShowCursor,
DisplayAdapter,
PrimaryMonitor,
DXDisplayAdapter,
GraphicsForceSingleAdapter,
GraphicsForceRefresh,
FullscreenResolution,
@@ -91,12 +92,15 @@ namespace launcher {
SDVXDigitalKnobSocd,
spice2x_SDVXAsioDriver,
spice2x_SDVXSubPos,
SDVXSubMonitorOverride,
LoadDDRModule,
DDR43Mode,
DDRSkipCodecRegisteration,
LoadPopnMusicModule,
PopnMusicForceHDMode,
PopnMusicForceSDMode,
PopnNoSub,
PopnSubMonitorOverride,
LoadHelloPopnMusicModule,
LoadGitaDoraModule,
GitaDoraTwoChannelAudio,
@@ -242,6 +246,7 @@ namespace launcher {
DRSRGBCameraHook,
spice2x_IIDXNativeTouch,
spice2x_IIDXNoSub,
IIDXSubMonitorOverride,
spice2x_IIDXEmulateSubscreenKeypadTouch,
spice2x_AutoCard,
spice2x_LowLatencySharedAudio,
+5
View File
@@ -12,6 +12,7 @@
#include "cfg/screen_resize.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h"
#include "overlay/overlay.h"
#include "overlay/windows/generic_sub.h"
@@ -405,6 +406,10 @@ namespace wintouchemu {
// use mouse position as ImGui overlay will block the touch window
log_info("wintouchemu", "use mouse cursor API for ldj overlay subscreen");
USE_MOUSE = true;
} else if (games::popn::is_pikapika_model()) {
// same as iidx case above
log_info("wintouchemu", "use mouse cursor API for popn overlay subscreen");
USE_MOUSE = true;
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
log_info("wintouchemu", "use mouse cursor API for gitadora overlay subscreen");
USE_MOUSE = true;
+2
View File
@@ -169,8 +169,10 @@ namespace ImGui {
ImGui::PushStyleColor(ImGuiCol_Header, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, 0);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, 0);
ImGui::PushTabStop(false); // prevent tab navigation
ImGui::Selectable("##row", false,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
ImGui::PopTabStop();
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(200, 200, 200, 24));
+177 -86
View File
@@ -15,6 +15,15 @@
#include "util/utils.h"
#include "hooks/graphics/graphics.h"
#define DEBUG_VERBOSE 0
#if 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
#if !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) || \
!defined(IMGUI_DISABLE_DEFAULT_ALLOCATORS) || \
!defined(IMGUI_USE_BGRA_PACKED_COLOR) || \
@@ -33,6 +42,95 @@ static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
static double g_LastMouseMovement = 0.f;
static bool g_MouseCursorAutoHide = false;
constexpr size_t VKEY_MAX = 255;
static std::array<bool, VKEY_MAX> g_KeysDown;
static std::array<bool, ImGuiMouseButton_COUNT> g_MouseDown;
static ImGuiKey get_imgui_key(int vkey) {
switch (vkey) {
case VK_TAB:
return ImGuiKey_Tab;
case VK_LEFT:
return ImGuiKey_LeftArrow;
case VK_RIGHT:
return ImGuiKey_RightArrow;
case VK_UP:
return ImGuiKey_UpArrow;
case VK_DOWN:
return ImGuiKey_DownArrow;
case VK_PRIOR:
return ImGuiKey_PageUp;
case VK_NEXT:
return ImGuiKey_PageDown;
case VK_HOME:
return ImGuiKey_Home;
case VK_END:
return ImGuiKey_End;
case VK_INSERT:
return ImGuiKey_Insert;
case VK_DELETE:
return ImGuiKey_Delete;
case VK_BACK:
return ImGuiKey_Backspace;
case VK_SPACE:
return ImGuiKey_Space;
case VK_RETURN:
return ImGuiKey_Enter;
case VK_ESCAPE:
return ImGuiKey_Escape;
case VK_SHIFT:
case VK_LSHIFT:
return ImGuiKey_LeftShift;
case VK_RSHIFT:
return ImGuiKey_RightShift;
case VK_CONTROL:
case VK_LCONTROL:
return ImGuiKey_LeftCtrl;
case VK_RCONTROL:
return ImGuiKey_RightCtrl;
case 'A':
case 'a':
return ImGuiKey_A;
case 'C':
case 'c':
return ImGuiKey_C;
case 'V':
case 'v':
return ImGuiKey_V;
case 'X':
case 'x':
return ImGuiKey_X;
case 'Y':
case 'y':
return ImGuiKey_Y;
case 'Z':
case 'z':
return ImGuiKey_Z;
default:
return ImGuiKey_None;
}
}
static ImGuiKey get_imgui_mod_key(int vkey) {
switch (vkey) {
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
return ImGuiMod_Shift;
return ImGuiMod_Shift;
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
return ImGuiMod_Ctrl;
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
return ImGuiMod_Alt;
default:
return ImGuiMod_None;
}
}
bool ImGui_ImplSpice_Init(HWND hWnd) {
log_misc("imgui_impl_spice", "init");
@@ -59,30 +157,6 @@ bool ImGui_ImplSpice_Init(HWND hWnd) {
io.BackendPlatformName = "imgui_impl_spice";
io.ConfigErrorRecoveryEnableTooltip = true;
// keyboard mapping
io.KeyMap[ImGuiKey_Tab] = VK_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN;
io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR;
io.KeyMap[ImGuiKey_PageDown] = VK_NEXT;
io.KeyMap[ImGuiKey_Home] = VK_HOME;
io.KeyMap[ImGuiKey_End] = VK_END;
io.KeyMap[ImGuiKey_Insert] = VK_INSERT;
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
io.KeyMap[ImGuiKey_Backspace] = VK_BACK;
io.KeyMap[ImGuiKey_Space] = VK_SPACE;
io.KeyMap[ImGuiKey_Enter] = VK_RETURN;
io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE;
io.KeyMap[ImGuiKey_KeypadEnter] = VK_RETURN;
io.KeyMap[ImGuiKey_A] = 'A';
io.KeyMap[ImGuiKey_C] = 'C';
io.KeyMap[ImGuiKey_V] = 'V';
io.KeyMap[ImGuiKey_X] = 'X';
io.KeyMap[ImGuiKey_Y] = 'Y';
io.KeyMap[ImGuiKey_Z] = 'Z';
// get display size
ImGui_ImplSpice_UpdateDisplaySize();
@@ -191,7 +265,8 @@ static void ImGui_ImplSpice_UpdateMousePos() {
// trigger any overlay, don't process anything else
const auto is_windowed_subscreen =
(GRAPHICS_IIDX_WSUB && active_window == TDJ_SUBSCREEN_WINDOW) ||
(active_window == SDVX_SUBSCREEN_WINDOW);
(active_window == SDVX_SUBSCREEN_WINDOW) ||
(active_window == POPN_SUBSCREEN_WINDOW);
if (is_windowed_subscreen) {
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
return;
@@ -248,7 +323,7 @@ static void ImGui_ImplSpice_UpdateMousePos() {
}
// delay press
io.MouseDown[0] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id;
g_MouseDown[ImGuiMouseButton_Left] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id;
if (last_touch_id == ~0u) {
last_touch_id = tp.id;
}
@@ -274,44 +349,31 @@ void ImGui_ImplSpice_NewFrame() {
io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond;
g_Time = current_time;
// only process new input from devices if window is in focus
// (when not in focus, all mouse/keyboard events are treated as released)
const auto accept_new_input = superexit::has_focus() && !rawinput::OS_WINDOW_ACTIVE;
// remember old state
BYTE KeysDownOld[sizeof(io.KeysDown)];
for (size_t i = 0; i < sizeof(io.KeysDown); i++) {
KeysDownOld[i] = io.KeysDown[i] ? ~0 : 0;
std::array<BYTE, VKEY_MAX> KeysDownOld;
for (size_t i = 0; i < VKEY_MAX; i++) {
KeysDownOld[i] = g_KeysDown[i] ? ~0 : 0;
}
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_LSHIFT];
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_RSHIFT];
const auto MouseDownOld = g_MouseDown;
// reset keys state
io.MouseWheel = 0;
io.KeyCtrl = false;
io.KeyShift = false;
io.KeyAlt = false;
io.KeySuper = false;
memset(io.KeysDown, false, sizeof(io.KeysDown));
memset(io.MouseDown, false, sizeof(io.MouseDown));
// early quit if window not in focus
if (!superexit::has_focus() || rawinput::OS_WINDOW_ACTIVE) {
return;
}
// read keyboard modifiers inputs
io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0;
io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0;
io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0;
io.KeySuper = (::GetKeyState(VK_LWIN) & 0x8000) != 0;
io.KeySuper |= (::GetKeyState(VK_RWIN) & 0x8000) != 0;
g_MouseDown.fill(false);
g_KeysDown.fill(false);
// apply windows mouse buttons
io.MouseDown[0] |= (get_async_primary_mouse()) != 0;
io.MouseDown[1] |= (get_async_secondary_mouse()) != 0;
io.MouseDown[2] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
g_MouseDown[ImGuiMouseButton_Left] |= (get_async_primary_mouse()) != 0;
g_MouseDown[ImGuiMouseButton_Right] |= (get_async_secondary_mouse()) != 0;
g_MouseDown[ImGuiMouseButton_Middle] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
// read new keys state
static long mouse_wheel_last = 0;
long mouse_wheel = 0;
if (RI_MGR != nullptr) {
if (RI_MGR != nullptr && accept_new_input) {
auto devices = RI_MGR->devices_get();
for (auto &device : devices) {
switch (device.type) {
@@ -321,21 +383,21 @@ void ImGui_ImplSpice_NewFrame() {
// mouse button triggers
if (GetSystemMetrics(SM_SWAPBUTTON)) {
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[0] = true;
g_MouseDown[ImGuiMouseButton_Left] = true;
}
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[1] = true;
g_MouseDown[ImGuiMouseButton_Right] = true;
}
} else {
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
io.MouseDown[0] = true;
g_MouseDown[ImGuiMouseButton_Left] = true;
}
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
io.MouseDown[1] = true;
g_MouseDown[ImGuiMouseButton_Right] = true;
}
}
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
io.MouseDown[2] = true;
g_MouseDown[ImGuiMouseButton_Middle] = true;
}
// final mouse wheel value should be all devices combined
@@ -346,33 +408,11 @@ void ImGui_ImplSpice_NewFrame() {
case rawinput::KEYBOARD: {
// iterate all virtual key codes
for (size_t vKey = 0; vKey < 256; vKey++) {
for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
// get state (combined from all pages)
auto &key_states = device.keyboardInfo->key_states;
bool state = false;
for (size_t page_index = 0; page_index < 1024; page_index += 256) {
state |= key_states[page_index + vKey];
}
// trigger
io.KeysDown[vKey] |= state;
// generate character input, but only if WM_CHAR didn't take over the
// functionality
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
UCHAR buf[2];
auto ret = ToAscii(
static_cast<UINT>(vKey),
0,
static_cast<const BYTE *>(KeysDownOld),
reinterpret_cast<LPWORD>(buf),
0);
if (ret > 0) {
for (int i = 0; i < ret; i++) {
overlay::OVERLAY->input_char(buf[i]);
}
}
g_KeysDown[vKey] |= key_states[page_index + vKey];
}
}
break;
@@ -383,22 +423,73 @@ void ImGui_ImplSpice_NewFrame() {
}
}
// process keyboard input from all keyboard collapsed into one state (g_KeysDown)
for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) {
const bool state = g_KeysDown[vKey];
const auto imgui_key = get_imgui_key(vKey);
const auto changed =
(state && !KeysDownOld[vKey]) ||
(!state && KeysDownOld[vKey]);
if (imgui_key != ImGuiKey_None && changed) {
io.AddKeyEvent(imgui_key, state);
log_debug("imgui_impl_spice", "vkey {:#x} added as navigation event, state: {}", static_cast<uint64_t>(vKey), state);
// mod key must also be processed separately
const auto imgui_mod_key = get_imgui_mod_key(vKey);
if (imgui_mod_key != ImGuiMod_None) {
io.AddKeyEvent(imgui_mod_key, state);
}
}
// generate character input, but only if WM_CHAR didn't take over the functionality
// only detecting rising edges here - this means holding a key won't work
// (it's better than repeating a character input every frame - cost we pay for providing input
// on top of rawinput instead of WM_CHAR)
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
UCHAR buf[2];
auto ret = ToAscii(
static_cast<UINT>(vKey),
0,
static_cast<const BYTE *>(KeysDownOld.data()),
reinterpret_cast<LPWORD>(buf),
0);
if (ret > 0) {
for (int i = 0; i < ret; i++) {
overlay::OVERLAY->input_char(buf[i]);
log_debug("imgui_impl_spice", "vkey {:#x} inputted as character", vKey);
}
}
}
}
// set mouse wheel
auto wheel_diff = mouse_wheel - mouse_wheel_last;
mouse_wheel_last = mouse_wheel;
io.MouseWheel = wheel_diff;
io.AddMouseWheelEvent(0, wheel_diff);
// update OS mouse position
const auto old_mouse_pos = io.MousePos;
ImGui_ImplSpice_UpdateMousePos();
if (accept_new_input) {
ImGui_ImplSpice_UpdateMousePos();
}
const auto new_mouse_pos = io.MousePos;
// update mouse buttons
// doing this after ImGui_ImplSpice_UpdateMousePos since it can set g_MouseDown for touch input
for (size_t i = 0; i < g_MouseDown.size(); i++) {
if (MouseDownOld[i] != g_MouseDown[i]) {
io.AddMouseButtonEvent(i, g_MouseDown[i]);
log_debug("imgui_impl_spice", "mouse button {} event", g_MouseDown[i]);
}
}
// automatically hide cursor
if (g_MouseCursorAutoHide) {
if (old_mouse_pos.x != new_mouse_pos.x ||
old_mouse_pos.y != new_mouse_pos.y ||
wheel_diff != 0 ||
io.MouseDown[0] || io.MouseDown[1] || io.MouseDown[2]) {
g_MouseDown[ImGuiMouseButton_Left] || g_MouseDown[ImGuiMouseButton_Right] || g_MouseDown[ImGuiMouseButton_Middle]) {
// mouse moved, update time and show the cursor
g_LastMouseMovement = get_performance_milliseconds();
+3 -3
View File
@@ -501,7 +501,7 @@ void paint_draw_cmd(
const SwOptions& options,
Stats* stats)
{
const auto texture = reinterpret_cast<const Texture*>(pcmd.TextureId);
const auto texture = reinterpret_cast<const Texture*>(pcmd.GetTexID());
const auto offset = pcmd.IdxOffset;
// ImGui uses the first pixel for "white".
@@ -678,7 +678,7 @@ void bind_imgui_painting()
int font_width, font_height;
io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height);
const auto texture = new Texture{tex_data, font_width, font_height};
io.Fonts->TexID = reinterpret_cast<ImTextureID>(texture);
io.Fonts->SetTexID(reinterpret_cast<ImTextureID>(texture));
}
static Stats s_stats; // TODO: pass as an argument?
@@ -700,7 +700,7 @@ void paint_imgui(uint32_t* pixels, int width_pixels, int height_pixels, const Sw
void unbind_imgui_painting()
{
ImGuiIO& io = ImGui::GetIO();
delete reinterpret_cast<Texture*>(io.Fonts->TexID);
delete reinterpret_cast<Texture*>(io.Fonts->TexID.GetTexID());
io.Fonts = nullptr;
}
+9 -5
View File
@@ -5,6 +5,7 @@
#include "games/io.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h"
#include "misc/eamuse.h"
#include "touch/touch.h"
@@ -35,6 +36,7 @@
#include "windows/iopanel_ddr.h"
#include "windows/iopanel_gfdm.h"
#include "windows/iopanel_iidx.h"
#include "windows/popn_sub.h"
#include "windows/sdvx_sub.h"
#include "windows/keypad.h"
#include "windows/log.h"
@@ -69,7 +71,7 @@ namespace overlay {
}
return ImVec2(apply_scaling(x), apply_scaling(y));
}
ImVec2 apply_scaling_to_vector(const ImVec2& input) {
return apply_scaling_to_vector(input.x, input.y);
}
@@ -185,7 +187,7 @@ void overlay::SpiceOverlay::init() {
if (UI_SCALE_PERCENT.has_value()) {
const auto scale = static_cast<float>(UI_SCALE_PERCENT.value()) / 100.f;
ImGui::GetStyle().ScaleAllSizes(scale);
ImGui::GetIO().FontGlobalScale = scale;
ImGui::GetStyle().FontScaleMain = scale;
}
// based on CrimsonVesuvius theme from
@@ -283,7 +285,7 @@ void overlay::SpiceOverlay::init() {
io.FontAllowUserScaling = true;
// add default font
io.Fonts->AddFontDefault();
io.Fonts->AddFontDefaultBitmap();
// add fallback fonts for missing glyph ranges
ImFontConfig config {};
@@ -350,7 +352,7 @@ void overlay::SpiceOverlay::init() {
this->window_add(window_config = new overlay::windows::Config(this));
this->window_add(window_control = new overlay::windows::Control(this));
this->window_add(window_log = new overlay::windows::Log(this));
#ifdef SPICE64
#if SPICE64 && !SPICE_XP
if (avs::game::is_model("LDJ")) {
this->window_add(window_camera = new overlay::windows::CameraControl(this));
}
@@ -415,6 +417,8 @@ void overlay::SpiceOverlay::init() {
window_sub = new overlay::windows::SDVXSubScreen(this);
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
window_sub = new overlay::windows::GitaDoraSubScreen(this);
} else if (games::popn::is_pikapika_model()) {
window_sub = new overlay::windows::PopnSubScreen(this);
}
if (window_sub) {
@@ -738,4 +742,4 @@ void overlay::SpiceOverlay::add_font(const char* font, ImFontConfig* config, con
} else {
log_misc("overlay", "font not found: {}", full_path.string());
}
}
}
@@ -1,6 +1,6 @@
#include "camera_control.h"
#if SPICE64
#if SPICE64 && !SPICE_XP
#include <games/io.h>
#include <strmif.h>
@@ -193,7 +193,7 @@ namespace overlay::windows {
}
ImGui::Separator();
// reset button
if (ImGui::Button("Reset")) {
selectedCamera->ResetCameraControlProps();
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#if SPICE64
#if SPICE64 && !SPICE_XP
#include "overlay/window.h"
#include <strmif.h>
+7 -7
View File
@@ -144,7 +144,7 @@ namespace overlay::windows {
" xxxx xxxx xxxx xxxx ");
ImGui::EndDisabled();
}
ImGui::PopID();
return clicked;
}
@@ -152,8 +152,8 @@ namespace overlay::windows {
void CardManager::open_card_editor() {
if (this->current_card) {
const auto card = this->current_card;
strcpy_s(this->name_buffer, std::size(this->name_buffer), card->name.c_str());
strcpy_s(this->card_buffer, std::size(this->card_buffer), card->id.c_str());
snprintf(this->name_buffer, std::size(this->name_buffer), "%s", card->name.c_str());
snprintf(this->card_buffer, std::size(this->card_buffer), "%s", card->id.c_str());
this->color_buffer[0] = card->color[0];
this->color_buffer[1] = card->color[1];
this->color_buffer[2] = card->color[2];
@@ -227,7 +227,7 @@ namespace overlay::windows {
};
generate_search_string(&card);
this->cards.emplace_back(card);
// mark this card as the selected one
this->current_card = &this->cards.back();
}
@@ -301,7 +301,7 @@ namespace overlay::windows {
// search for card
//
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
// setting ImGuiInputTextFlags_CallbackCharFilter and pressing escape doesn't cause below
// to return true, making it necessary to provide a callback...
ImGui::SetNextItemWidth(overlay::apply_scaling(240));
if (ImGui::InputTextWithHint("", "Type here to search..", &this->search_filter)) {
@@ -399,11 +399,11 @@ namespace overlay::windows {
// cards from card manager JSON
for (auto &card : this->cards) {
if (!this->search_filter_in_lower_case.empty() && !card.search_string.empty()) {
const bool matched =
card.search_string.find(this->search_filter_in_lower_case) != std::string::npos;
if (!matched) {
continue;
}
+252 -89
View File
@@ -34,6 +34,7 @@
#include "util/resutils.h"
#include "util/scope_guard.h"
#include "util/time.h"
#include "util/sysutils.h"
#include "util/utils.h"
#ifdef min
@@ -58,8 +59,9 @@ namespace overlay::windows {
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_RED(1.f, 0.f, 0.f, 1.f);
constexpr uint32_t OPTION_INPUT_TEXT_WIDTH = 512;
std::unique_ptr<AsioDriverList> asio_driver_list;
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
@@ -112,13 +114,23 @@ namespace overlay::windows {
if (cfg::CONFIGURATOR_STANDALONE) {
const auto file_hints = games::get_game_file_hints(game_name);
if (file_hints) {
for (auto &file_hint : *file_hints) {
if (fileutils::file_exists(file_hint) ||
fileutils::dir_exists(file_hint) ||
fileutils::file_exists(std::filesystem::path("modules") / file_hint) ||
fileutils::file_exists(std::filesystem::path("contents") / file_hint) ||
fileutils::file_exists(MODULE_PATH / file_hint))
{
for (auto &file_hint_list : *file_hints) {
bool matches_all_files = true;
for (auto &file_hint : file_hint_list) {
const auto matched =
(fileutils::file_exists(file_hint) ||
fileutils::dir_exists(file_hint) ||
fileutils::file_exists(std::filesystem::path("modules") / file_hint) ||
fileutils::file_exists(std::filesystem::path("contents") / file_hint) ||
fileutils::file_exists(MODULE_PATH / file_hint));
if (!matched) {
matches_all_files = false;
break;
}
}
if (matches_all_files) {
this->games_selected = games_list.size() - 1;
this->games_selected_name = game_name;
eamuse_set_game(game_name);
@@ -513,7 +525,7 @@ namespace overlay::windows {
ImGui::BeginChild("SearchOptions", ImVec2(
0, ImGui::GetWindowContentRegionMax().y - page_offset), false);
// search from all options
ImGui::Spacing();
ImGui::SetNextItemWidth(420.f);
@@ -631,14 +643,14 @@ namespace overlay::windows {
reset_button_to_default(&button, get_keypad_top_row(button));
}
ImGui::CloseCurrentPopup();
}
}
ImGui::SameLine();
if (ImGui::Button("Remove All")) {
for (auto &button : *buttons) {
reset_button_to_default(&button, 0xFF);
}
ImGui::CloseCurrentPopup();
}
}
} else {
if (ImGui::Button("Yes")) {
for (auto &button : *buttons) {
@@ -661,7 +673,7 @@ namespace overlay::windows {
// longest column is probably "Toggle Virtual Keypad P1" in Overlay tab
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(220));
ImGui::TableSetupColumn("Binding", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(140));
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, overlay::apply_scaling(170));
// check if empty
if (!buttons || buttons->empty()) {
@@ -787,7 +799,7 @@ namespace overlay::windows {
// clear button
if (button_display.size() > 0 || alt_index > 0) {
ImGui::SameLine();
if (ImGui::DeleteButton(button_display.size() > 0 ? "Unbind" : "Delete")) {
if (ImGui::DeleteButton(button_display.size() > 0 ? "Unbind" : "Delete")) {
clear_button(button, alt_index);
}
}
@@ -901,8 +913,8 @@ namespace overlay::windows {
// naive binding
ImGui::SameLine();
std::string naive_string = "Naive " + button_name;
if (ImGui::Button("Naive")
std::string naive_string = "Naive/XInput " + button_name;
if (ImGui::Button("Naive/XInput")
|| (buttons_many_active && buttons_many_active_section == name && !buttons_bind_active
&& alt_index == 0
&& buttons_many_naive && buttons_many_index == button_it
@@ -922,11 +934,12 @@ namespace overlay::windows {
}
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
ImGui::HelpTooltip(
"Uses GetAsyncKeyState to check for any keyboard / mouse input. "
"For best performance, Bind should be preferred, but this can be used when:\n"
" * you don't care about which device is used\n"
"Alternative to Bind, using GetAsyncKeyState for KB/Mouse and XInput for controllers.\n\n"
"This can be used when:\n"
" * you want to use XInput for gamepads (for analog triggers)\n"
" * you don't care about which keyboard/mouse is used\n"
" * you want to use input remapping or automation software\n"
" * if you have NKRO issues with Bind");
" * you have NKRO issues with Bind");
}
naive_button_popup(naive_string, button, button_it_max, alt_index);
@@ -986,7 +999,7 @@ namespace overlay::windows {
}
// Naive Many
if (ImGui::MenuItem("Naive (many)")) {
if (ImGui::MenuItem("Naive/XInput (many)")) {
buttons_many_active = true;
buttons_many_index = button_it;
buttons_many_naive = true;
@@ -1067,7 +1080,7 @@ namespace overlay::windows {
{"P1 Keypad 00", VK_OEM_MINUS},
{"P1 Keypad Decimal", VK_OEM_PLUS},
{"P1 Keypad Insert Card", VK_BACK}};
for (const auto &[key, value] : keypad_top_row_defaults) {
if (button.getName() == key) {
return value;
@@ -1421,7 +1434,7 @@ namespace overlay::windows {
}
for (unsigned short ch = 0; ch < midi->pitch_bend.size(); ch++) {
// check pitch bend down
// check pitch bend down
if (midi->pitch_bend[ch] < 0) {
// bind control
@@ -1543,6 +1556,22 @@ namespace overlay::windows {
}
}
// check xinput
if (check_devices) {
xinput::XINPUT_NEW_BUTTON xinput;
if (RI_MGR->XINPUT_MGR->get_any_button_pressed(xinput)) {
button->setDeviceIdentifier(xinput::get_device_desc(xinput.player));
button->setVKey(static_cast<uint16_t>(xinput.button));
::Config::getInstance().updateBinding(
games_list[games_selected], *button, alt_index - 1);
inc_buttons_many_index(button_it_max);
buttons_bind_active = false;
ImGui::CloseCurrentPopup();
check_devices = false;
}
}
// check GetAsyncKeyState
if (check_devices) {
// get new keyboard state
// these are async, and some keys generate multiple vKeys (e.g., VK_SHIFT, VK_LSHIFT)
@@ -1627,7 +1656,7 @@ namespace overlay::windows {
if (ImGui::Selectable("Empty (Naive)", button->isNaive())) {
button->setDeviceIdentifier("");
dirty = true;
}
}
if (button->isNaive()) {
ImGui::SetItemDefaultFocus();
}
@@ -1711,7 +1740,7 @@ namespace overlay::windows {
button->setVelocityThreshold(0);
dirty = true;
}
type = button->getAnalogType();
int midi_channel = 0;
@@ -1758,7 +1787,7 @@ namespace overlay::windows {
} else if (0x20 <= midi_index && midi_index < 0x3F) {
midi_index = 0x40;
// skip range [0x60, 0x65]
// skip range [0x60, 0x65]
} else if (midi_index == 0x65) {
midi_index = 0x5F;
} else if (0x60 <= midi_index && midi_index < 0x65) {
@@ -1842,7 +1871,7 @@ namespace overlay::windows {
ImGui::TextDisabled("Min. %d%%", velocity_threshold * 100 / 127);
} else if (type == ButtonAnalogType::BAT_MIDI_CTRL_PRECISION ||
type == ButtonAnalogType::BAT_MIDI_CTRL_SINGLE) {
type == ButtonAnalogType::BAT_MIDI_CTRL_SINGLE) {
ImGui::TextUnformatted("\n");
ImGui::AlignTextToFramePadding();
@@ -1910,7 +1939,7 @@ namespace overlay::windows {
"Direction",
is_up ? "Pitch Up" : "Pitch Down",
ImGuiComboFlags_HeightSmall)) {
if (ImGui::Selectable("Pitch Up", is_up)) {
button->setAnalogType(ButtonAnalogType::BAT_MIDI_PITCH_UP);
dirty = true;
@@ -2163,6 +2192,9 @@ namespace overlay::windows {
case rawinput::MIDI:
this->analogs_devices.emplace_back(&device);
break;
case rawinput::XINPUT_GAMEPAD:
this->analogs_devices.emplace_back(&device);
break;
default:
continue;
}
@@ -2195,9 +2227,8 @@ namespace overlay::windows {
auto analog_device_changed = ImGui::Combo(
"Device",
&this->analogs_devices_selected,
[](void* data, int i, const char **item) {
*item = ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str();
return true;
[](void* data, int i) {
return ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str();
},
&this->analogs_devices, (int) this->analogs_devices.size());
@@ -2212,7 +2243,7 @@ namespace overlay::windows {
ImGui::SameLine();
ImGui::HelpMarker(device->name.c_str());
}
switch (device->type) {
case rawinput::MOUSE: {
@@ -2282,12 +2313,21 @@ namespace overlay::windows {
if (device->midiInfo->pitch_bend_set[ch]) {
control_names.push_back(fmt::format("Pitch Ch.{}", ch + 1));
analogs_midi_indices.push_back(
ch +
ch +
precision.size() +
single.size() +
onoff.size());
}
}
break;
}
case rawinput::XINPUT_GAMEPAD: {
for (int i = 0; i < static_cast<int>(xinput::XInputAnalogEnum::COUNT); i++) {
const auto v = static_cast<xinput::XInputAnalogEnum>(i);
control_names.push_back(xinput::get_analog_string(v));
}
break;
}
default:
break;
@@ -2316,9 +2356,8 @@ namespace overlay::windows {
// controls
ImGui::Combo("Control",
&this->analogs_devices_control_selected,
[](void* data, int i, const char **item) {
*item = ((std::vector<std::string>*) data)->at(i).c_str();
return true;
[](void* data, int i) {
return ((std::vector<std::string>*) data)->at(i).c_str();
},
&control_names, control_names.size());
@@ -2428,7 +2467,7 @@ namespace overlay::windows {
if (invert != analog.getInvert()) {
analog.setInvert(invert);
}
if (this->analogs_devices_selected >= 0) {
const auto device = this->analogs_devices.at(this->analogs_devices_selected);
if (device->type == rawinput::HID) {
@@ -2725,6 +2764,7 @@ namespace overlay::windows {
case rawinput::PIUIO_DEVICE:
case rawinput::SMX_STAGE:
case rawinput::SMX_DEDICAB:
case rawinput::XINPUT_GAMEPAD:
this->auto_match_devices.emplace_back(&device);
break;
default:
@@ -2783,7 +2823,7 @@ namespace overlay::windows {
bool table_begin = false;
for (size_t i = 0; i < lights->size(); i++) {
auto &light = lights->at(i);
if (current_section != light.getCategory()) {
current_section = light.getCategory();
if (table_begin) {
@@ -2791,7 +2831,7 @@ namespace overlay::windows {
}
render_section_header(current_section.empty() ? "Uncategorized" : current_section);
table_begin = begin_lights_table();
if (!table_begin) {
break;
@@ -2813,7 +2853,7 @@ namespace overlay::windows {
alt_index++;
}
}
if (table_begin) {
ImGui::EndTable();
@@ -2834,7 +2874,7 @@ namespace overlay::windows {
// progress bar
ImGui::TableNextColumn();
// light name
if (alt_index == 0) {
ImGui::ProgressBar(light_state, ImVec2(32.f, 0));
@@ -2865,7 +2905,7 @@ namespace overlay::windows {
// clear light
if (light_display.size() > 0 || alt_index > 0) {
ImGui::SameLine();
if (ImGui::DeleteButton(light_display.size() > 0 ? "Unbind" : "Delete")) {
if (ImGui::DeleteButton(light_display.size() > 0 ? "Unbind" : "Delete")) {
clear_light(light, alt_index);
}
}
@@ -2891,6 +2931,7 @@ namespace overlay::windows {
case rawinput::PIUIO_DEVICE:
case rawinput::SMX_STAGE:
case rawinput::SMX_DEDICAB:
case rawinput::XINPUT_GAMEPAD:
this->lights_devices.emplace_back(&device);
break;
default:
@@ -2948,7 +2989,7 @@ namespace overlay::windows {
games_list[games_selected], *light,
alt_index - 1);
}
void Config::edit_light_popup(Light &primary_light, Light *light, const int alt_index) {
if (ImGui::BeginPopupModal("Light Binding", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
@@ -2956,9 +2997,8 @@ namespace overlay::windows {
bool control_changed = false;
if (ImGui::Combo("Device",
&this->lights_devices_selected,
[] (void* data, int i, const char **item) {
*item = ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str();
return true;
[] (void* data, int i) {
return ((std::vector<rawinput::Device*>*) data)->at(i)->desc.c_str();
},
&this->lights_devices, (int) this->lights_devices.size())) {
this->lights_devices_control_selected = 0;
@@ -3045,6 +3085,15 @@ namespace overlay::windows {
}
break;
}
case rawinput::XINPUT_GAMEPAD: {
// add all names of XInput gamepad device
for (int i = 0; i < static_cast<int>(xinput::XInputOutputEnum::COUNT); i++) {
control_names.emplace_back(
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(i)));
}
break;
}
default:
break;
}
@@ -3053,9 +3102,8 @@ namespace overlay::windows {
// controls
if (ImGui::Combo("Light Control",
&this->lights_devices_control_selected,
[] (void* data, int i, const char **item) {
*item = ((std::vector<std::string> *) data)->at(i).c_str();
return true;
[] (void* data, int i) {
return ((std::vector<std::string> *) data)->at(i).c_str();
},
&control_names, control_names.size())) {
control_changed = true;
@@ -3136,10 +3184,9 @@ namespace overlay::windows {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
bool device_changed = ImGui::Combo("##AutoMatchDevice",
&this->auto_match_device_selected,
[] (void* data, int i, const char **item) {
[] (void* data, int i) {
auto *devs = (std::vector<rawinput::Device*>*) data;
*item = devs->at(i)->desc.c_str();
return true;
return devs->at(i)->desc.c_str();
},
&this->auto_match_devices,
(int) this->auto_match_devices.size());
@@ -3592,6 +3639,13 @@ namespace overlay::windows {
}
break;
}
case rawinput::XINPUT_GAMEPAD: {
for (int i = 0; i < static_cast<int>(xinput::XInputOutputEnum::COUNT); i++) {
raw_names.emplace_back(
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(i)));
}
break;
}
default:
break;
}
@@ -3751,7 +3805,7 @@ namespace overlay::windows {
return detected_controller;
}
void Config::build_cards() {
constexpr float TEXT_INPUT_WIDTH = 240.f;
@@ -3831,16 +3885,24 @@ namespace overlay::windows {
// generate button
ImGui::SameLine();
if (ImGui::Button("Generate")) {
generate_ea_card(buffer);
card_changed = true;
if (!this->keypads_card_overwrite_confirmed[player] && buffer_len > 0) {
ImGui::OpenPopup("Card Generator##GenerateCardConfirm");
} else {
generate_ea_card(buffer);
card_changed = true;
}
}
// clear button
if (option.is_active()) {
ImGui::SameLine();
if (ImGui::Button("Clear")) {
buffer[0] = '\0';
card_changed = true;
if (!this->keypads_card_overwrite_confirmed[player] && buffer_len > 0) {
ImGui::OpenPopup("Clear Card##ClearCardConfirm");
} else {
buffer[0] = '\0';
card_changed = true;
}
}
}
} else {
@@ -3857,6 +3919,61 @@ namespace overlay::windows {
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "Invalid card number");
}
if (ImGui::BeginPopupModal(
"Card Generator##GenerateCardConfirm",
nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted(
"What do you want to do?");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Discard current card and generate new one")) {
this->keypads_card_overwrite_confirmed[player] = true;
generate_ea_card(buffer);
card_changed = true;
ImGui::CloseCurrentPopup();
}
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Keep current card and cancel")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::BeginPopupModal(
"Clear Card##ClearCardConfirm",
nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted(
"This cannot be undone. Are you sure?");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("Yes, delete card number")) {
this->keypads_card_overwrite_confirmed[player] = true;
buffer[0] = '\0';
card_changed = true;
ImGui::CloseCurrentPopup();
}
ImGui::AlignTextToFramePadding();
ImGui::Bullet();
ImGui::SameLine();
if (ImGui::Button("No, keep my card number")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::TreePop();
}
@@ -3995,7 +4112,7 @@ namespace overlay::windows {
}
ImGui::SameLine();
if (ImGui::Button("Edit")) {
if (ImGui::Button("Edit in Notepad")) {
std::filesystem::path path;
if (!bindings.card_paths[player].empty()) {
path = bindings.card_paths[player];
@@ -4018,24 +4135,25 @@ namespace overlay::windows {
// verify card number
this->keypads_card_file_contents_valid[player] = true;
if (this->keypads_card_number[player][0] != 0) {
this->keypads_card_file_contents_valid[player] =
this->keypads_card_file_contents_valid[player] =
validate_ea_card(this->keypads_card_number[player]);
}
// card number box
ImGui::PushStyleColor(
ImGuiCol_Text,
this->keypads_card_file_contents_valid[player] ? ImVec4(1.f, 1.f, 1.f, 1.f) :
ImVec4(1.f, 0.f, 0.f, 1.f));
ImGui::SetNextItemWidth(TEXT_INPUT_WIDTH);
ImGui::BeginDisabled();
if (this->keypads_card_number[player][0] != 0) {
ImGui::PushStyleColor(
ImGuiCol_Text,
this->keypads_card_file_contents_valid[player] ?
TEXT_COLOR_GREEN : TEXT_COLOR_RED);
ImGui::Text("Card from file: %s", this->keypads_card_number[player]);
ImGui::PopStyleColor();
} else {
ImGui::TextUnformatted("Failed to read file.");
ImGui::TextColored(TEXT_COLOR_RED, "Failed to read file.");
}
ImGui::EndDisabled();
ImGui::PopStyleColor();
}
ImGui::TreePop();
@@ -4177,6 +4295,8 @@ namespace overlay::windows {
return "File Picker";
case OptionPickerType::DirectoryPath:
return "Folder Picker";
case OptionPickerType::Monitor:
return "Monitor Picker";
default:
return "Unknown Picker";
};
@@ -4226,7 +4346,9 @@ namespace overlay::windows {
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);
SYSTEM_INFO info;
GetSystemInfo(&info);
const uint64_t cpu_count = info.dwNumberOfProcessors;
uint64_t affinity = 0;
if (!option.value.empty()) {
try {
@@ -4270,7 +4392,7 @@ namespace overlay::windows {
option.value = fmt::format("0x{:X}", affinity);
}
}
ImGui::EndChild();
if (option.value.empty()) {
ImGui::TextUnformatted("Using all CPUs (option default).");
@@ -4407,6 +4529,30 @@ namespace overlay::windows {
ImGui::EndDisabled();
}
} else if (definition.picker == OptionPickerType::Monitor) {
const auto &monitors = sysutils::enumerate_monitors();
if (monitors.empty()) {
ImGui::TextUnformatted("Failed to fetch monitors. Requires Win7+");
} else {
ImGui::TextUnformatted("Pick from monitors:");
ImGui::SetNextItemWidth(300.f);
if (ImGui::BeginListBox("##monitors")) {
for (const auto &monitor : monitors) {
const bool is_selected = option.value == monitor.display_name;
auto friendly = wchar_to_u8(monitor.friendly_name.c_str());
auto str = fmt::format("{} ({})", monitor.display_name, friendly);
if (monitor.is_primary) {
str += " [Primary]";
}
if (ImGui::Selectable(str.c_str(), is_selected)) {
option.value = monitor.display_name;
}
}
ImGui::EndListBox();
}
}
} else {
ImGui::TextUnformatted("No picker available for this option. How did you get here?");
}
@@ -4414,7 +4560,7 @@ namespace overlay::windows {
void Config::build_options(
std::vector<Option> *options, const std::string &category, const std::string *filter) {
int options_count;
int options_count;
// category name
std::string cat = "Options";
@@ -4440,19 +4586,19 @@ namespace overlay::windows {
widget_col_width = overlay::apply_scaling(264);
}
ImGui::TableSetupColumn("Setting", ImGuiTableColumnFlags_WidthFixed, widget_col_width);
// iterate options
options_count = 0;
for (auto &option : *options) {
// get option definition
auto &definition = option.get_definition();
// check category
if (!category.empty() && definition.category != category) {
continue;
}
// check hidden option
if (!this->options_show_hidden && option.value.empty()) {
// skip hidden entries
@@ -4467,7 +4613,7 @@ namespace overlay::windows {
continue;
}
}
// filter
if (filter != nullptr) {
if (filter->empty()) {
@@ -4483,7 +4629,7 @@ namespace overlay::windows {
}
options_count += 1;
// list entry
ImGui::PushID(&option);
ImGui::TableNextRow();
@@ -4495,10 +4641,10 @@ namespace overlay::windows {
if (definition.category.empty()) {
ImGui::TextDisabled("-");
} else {
ImGui::TextDisabled(definition.category.c_str());
ImGui::TextDisabled("%s", definition.category.c_str());
}
}
// option name
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
@@ -4678,7 +4824,7 @@ namespace overlay::windows {
if (!element.second.empty()) {
label += fmt::format(" ({})", element.second);
}
bool selected = current_item == label;
if (ImGui::Selectable(label.c_str(), selected)) {
this->options_dirty = true;
@@ -4749,7 +4895,7 @@ namespace overlay::windows {
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
@@ -4771,7 +4917,7 @@ namespace overlay::windows {
if (ImGui::Button("Save & Close")) {
ImGui::CloseCurrentPopup();
this->options_dirty = true;
::Config::getInstance().updateBinding(games_list[games_selected], option);
::Config::getInstance().updateBinding(games_list[games_selected], option);
}
ImGui::EndPopup();
}
@@ -4779,7 +4925,7 @@ namespace overlay::windows {
// row hover detection (invisible selectable that spans entire row)
ImGui::InvisibleTableRowSelectable();
// next item
ImGui::PopID();
}
@@ -4787,12 +4933,24 @@ namespace overlay::windows {
// check if empty
if (options_count == 0) {
ImGui::TableNextRow();
// option category
if (filter != nullptr) {
ImGui::TableNextColumn();
ImGui::TextDisabled("-");
}
// name of option
ImGui::TableNextColumn();
ImGui::Indent(INDENT);
ImGui::TextDisabled("-");
ImGui::Unindent(INDENT);
ImGui::TableNextColumn();
if (filter == nullptr) {
ImGui::Indent(INDENT);
}
ImGui::TextDisabled("-");
if (filter == nullptr) {
ImGui::Unindent(INDENT);
}
// widget
ImGui::TableNextColumn();
ImGui::TextDisabled("-");
}
@@ -4803,14 +4961,19 @@ namespace overlay::windows {
}
void Config::build_about() {
#ifndef SPICE_LINUX
#if SPICE_LINUX
ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools)\r\n"
"spice2x (a fork of SpiceTools) for Linux\r\n"
"=========================\r\n" +
to_string(VERSION_STRING)).c_str());
#elif SPICE_XP
ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools) for WinXP\r\n"
"=========================\r\n" +
to_string(VERSION_STRING)).c_str());
#else
ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools) for Linux\r\n"
"spice2x (a fork of SpiceTools)\r\n"
"=========================\r\n" +
to_string(VERSION_STRING)).c_str());
#endif
@@ -4819,7 +4982,7 @@ namespace overlay::windows {
if (ImGui::TextLink(PROJECT_URL)) {
launch_shell(PROJECT_URL);
}
ImGui::TextUnformatted("");
ImGui::TextUnformatted(resutil::load_file_string_crlf(IDR_README).c_str());
ImGui::TextUnformatted("");
@@ -4927,7 +5090,7 @@ namespace overlay::windows {
const ImVec2 popup_pos(
ImGui::GetIO().DisplaySize.x / 2 - popup_size.x / 2,
ImGui::GetIO().DisplaySize.y / 2 - popup_size.y / 2);
ImGui::SetNextWindowSize(popup_size, ImGuiCond_Appearing);
ImGui::SetNextWindowPos(popup_pos, ImGuiCond_Appearing);
bool unused_open2 = true;
@@ -5594,7 +5757,7 @@ namespace overlay::windows {
ImGui::EndTable();
}
if (!apply_buttons && !apply_keypads && !apply_analogs && !apply_lights) {
ImGui::EndDisabled();
}
+2 -1
View File
@@ -87,7 +87,7 @@ namespace overlay::windows {
bool auto_match_soft_enabled = true;
bool auto_match_p2 = false;
// keypads tab
// cards tab
int keypads_selected[2] {};
char keypads_card_path[2][1024] {};
std::thread *keypads_card_select = nullptr;
@@ -96,6 +96,7 @@ namespace overlay::windows {
char keypads_card_number[2][18] {};
bool keypads_card_override_valid[2] = { false, false };
bool keypads_card_file_contents_valid[2] = { false, false };
bool keypads_card_overwrite_confirmed[2];
// presets tab
std::vector<ControllerTemplate> templates_cache;
+5
View File
@@ -732,6 +732,10 @@ namespace overlay::windows {
ImGui::Text("Type: PIUIO");
break;
}
case rawinput::XINPUT_GAMEPAD: {
ImGui::Text("Type: XInput");
break;
}
case rawinput::DESTROYED: {
ImGui::Text("Disconnected.");
break;
@@ -739,6 +743,7 @@ namespace overlay::windows {
case rawinput::UNKNOWN:
default:
ImGui::Text("Type: Unknown");
break;
}
ImGui::TreePop();
}
@@ -4,6 +4,7 @@
#include "util/logging.h"
#include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h"
#include "games/popn/popn.h"
namespace overlay::windows {
@@ -72,6 +73,8 @@ namespace overlay::windows {
sub = "Show DRS Dance Floor";
} else if (games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW) {
sub = "Show GITADORA Subscreen";
} else if (games::popn::is_pikapika_model()) {
sub = "Show Pop'n Subscreen";
}
build_button(this->overlay->window_sub, sub, size, NextItem::NEW_LINE, false);
+1 -1
View File
@@ -14,7 +14,7 @@ namespace overlay::windows {
if (GRAPHICS_IIDX_WSUB) {
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 -dxmainadapter option; use -mainmonitor instead";
}
float size = 0.5f;
@@ -40,13 +40,17 @@ namespace overlay::windows {
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
ImGui::PushID("P1");
this->draw_buttons(0);
ImGui::PopID();
ImGui::SameLine();
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
ImGui::PushID("P2");
this->draw_buttons(1);
ImGui::PopID();
}
void DDRIOPanel::draw_buttons(const int p) {
+53
View File
@@ -0,0 +1,53 @@
#undef CINTERFACE
#include "avs/game.h"
#include "popn_sub.h"
#include "games/popn/popn.h"
#include "hooks/graphics/graphics.h"
#include "touch/touch.h"
namespace overlay::windows {
PopnSubScreen::PopnSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
this->title = "Pop'n Subscreen";
if (!games::popn::is_pikapika_model()) {
this->disabled_message = "Game did not launch as Pikapika Pop-kun (invalid <spec>)!";
} else if (games::popn::SHOW_PIKA_MONITOR_WARNING) {
this->disabled_message = "Subscreen overlay is not compatible with -dxmainadapter option, use -mainmonitor instead";
} else if (GRAPHICS_WINDOWED && !GRAPHICS_PREVENT_SECONDARY_WINDOW) {
this->disabled_message = "Subscren overlay was not enabled in spicecfg. Use the second window (ALT+TAB).";
}
this->resize_callback = keep_16_by_10;
float size = 0.5f;
this->init_size = ImVec2(
ImGui::GetIO().DisplaySize.x * size,
(ImGui::GetIO().DisplaySize.x * size * 10 / 16) + ImGui::GetFrameHeight());
this->size_max = ImVec2(
ImGui::GetIO().DisplaySize.x - ImGui::GetFrameHeight() * 2,
ImGui::GetIO().DisplaySize.y - ImGui::GetFrameHeight() * 2);
// middle / bottom
this->init_pos = ImVec2(
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 2));
}
void PopnSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
if (!this->get_active()) {
return;
}
if (GRAPHICS_WINDOWED) {
// Touch needs to be registered on global coords
*x_out = SPICETOUCH_TOUCH_X + xy_in.x * SPICETOUCH_TOUCH_WIDTH;
*y_out = SPICETOUCH_TOUCH_Y + xy_in.y * SPICETOUCH_TOUCH_HEIGHT;
} else {
// Fullscreen mode, scale to game coords
*x_out = xy_in.x * ImGui::GetIO().DisplaySize.x;
*y_out = xy_in.y * ImGui::GetIO().DisplaySize.y;
}
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <windows.h>
#include "overlay/window.h"
#include "overlay/windows/generic_sub.h"
namespace overlay::windows {
class PopnSubScreen : public GenericSubScreen {
public:
PopnSubScreen(SpiceOverlay *overlay);
protected:
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
private:
static void keep_16_by_10(ImGuiSizeCallbackData* data) {
data->DesiredSize.y = (data->DesiredSize.x * 10.f / 16.f) + ImGui::GetFrameHeight();
}
};
}
@@ -213,7 +213,7 @@ namespace overlay::windows {
ImGui::SameLine();
ImGui::HelpMarker(
"Change window decoration. Resizable Window may not cause your mouse cursor to change, "
"but you can still drag to resize. Disabled for some games due to incompatibility.");
"but you can still drag to resize. Disabled for some games due to incompatibility; change in spicecfg instead.");
if (ImGui::Checkbox("Always On Top", &cfg::SCREENRESIZE->window_always_on_top) ) {
graphics_update_z_order(window, cfg::SCREENRESIZE->window_always_on_top);
+1 -1
View File
@@ -13,7 +13,7 @@ namespace overlay::windows {
if (!games::sdvx::is_valkyrie_model()) {
this->disabled_message = "Valkyrie Model mode is not enabled!";
} else if (games::sdvx::SHOW_VM_MONITOR_WARNING) {
this->disabled_message = "VM mode subscreen overlay is not compatible with -monitor option";
this->disabled_message = "VM mode subscreen overlay is not compatible with -dxmainadapter option, use -mainmonitor instead";
} else if (GRAPHICS_WINDOWED) {
if (GRAPHICS_PREVENT_SECONDARY_WINDOW) {
this->disabled_message = "Subscreen has been disabled by the user (-sdvxnosub).";
+2 -1
View File
@@ -31,7 +31,8 @@ namespace rawinput {
SEXTET_OUTPUT,
PIUIO_DEVICE,
SMX_STAGE,
SMX_DEDICAB
SMX_DEDICAB,
XINPUT_GAMEPAD,
};
enum MouseKeys {
+3 -1
View File
@@ -119,8 +119,10 @@ namespace rawinput {
// success
return TRUE;
}
case 7: { // windows 10 reports this for MIDI?
case DBT_DEVNODES_CHANGED: {
// TODO: this can be a little noisy as it gets called on every device
this->ri_mgr->devices_scan_midi();
this->ri_mgr->devices_scan_xinput();
break;
}
}
+106 -2
View File
@@ -8,6 +8,7 @@
#include <setupapi.h>
#include "util/logging.h"
#include "external/robin_hood.h"
#include "util/time.h"
#include "util/utils.h"
@@ -70,6 +71,8 @@ void rawinput::set_midi_algorithm(rawinput::MidiNoteAlgorithm new_algo) {
rawinput::RawInputManager::RawInputManager() {
XINPUT_MGR = std::make_unique<xinput::XInputManager>();
// create input window and load in devices
this->input_hwnd_create();
this->devices_reload();
@@ -106,6 +109,8 @@ void rawinput::RawInputManager::stop() {
// destruct all devices and input window
this->devices_destruct();
this->input_hwnd_destroy();
XINPUT_MGR.reset();
}
void rawinput::RawInputManager::input_hwnd_create() {
@@ -176,7 +181,7 @@ void rawinput::RawInputManager::input_hwnd_destroy() {
void rawinput::RawInputManager::devices_reload() {
this->devices_destruct();
log_info("rawinput", "reloading devices");
log_info("rawinput", "reloading devices...");
// scan for devices
this->devices_scan_rawinput();
@@ -191,6 +196,8 @@ void rawinput::RawInputManager::devices_reload() {
this->devices_scan_smxdedicab();
}
this->devices_scan_xinput();
// check for LIT Board
sextet_register("COM54", "LIT Board", false);
@@ -199,6 +206,7 @@ void rawinput::RawInputManager::devices_reload() {
}
void rawinput::RawInputManager::devices_scan_rawinput(const std::string &device_name) {
log_misc("rawinput", "scan rawinput devices...");
// get number of devices
UINT device_no = 0;
@@ -236,6 +244,8 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
return;
}
log_misc("rawinput", "found rawinput device: {}", device_name);
// extract information out of name
auto device_info = rawinput::RawInputManager::get_device_info(device_name);
auto device_description = rawinput::RawInputManager::rawinput_get_device_description(device_info, device_name);
@@ -810,6 +820,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
}
void rawinput::RawInputManager::devices_scan_midi() {
log_misc("rawinput", "scan MIDI devices...");
// add midi devices
auto midi_device_count = midiInGetNumDevs();
@@ -821,6 +832,9 @@ void rawinput::RawInputManager::devices_scan_midi() {
continue;
}
log_misc("rawinput", "found MIDI device: id {}, name {}, mid {}, pid {}",
midi_device_id, midi_device_caps.szPname, midi_device_caps.wMid, midi_device_caps.wPid);
// open device
HMIDIIN midi_device_handle;
if (midiInOpen(&midi_device_handle,
@@ -920,9 +934,11 @@ void rawinput::RawInputManager::devices_scan_midi() {
}
void rawinput::RawInputManager::devices_scan_piuio() {
log_misc("rawinput", "scan PIUIO devices...");
// add device to vector first so pointer is valid
auto *new_piuio_device = new Device();
new_piuio_device->id = this->devices.size() + 1;
new_piuio_device->type = PIUIO_DEVICE;
new_piuio_device->name = "piuio";
new_piuio_device->desc = "PIUIO";
@@ -950,7 +966,10 @@ void rawinput::RawInputManager::devices_scan_piuio() {
}
void rawinput::RawInputManager::devices_scan_smxstage() {
log_misc("rawinput", "scan SMX Stage devices...");
auto *new_smxstage_device = new Device();
new_smxstage_device->id = this->devices.size() + 1;
new_smxstage_device->type = SMX_STAGE;
new_smxstage_device->name = "smxstage";
new_smxstage_device->desc = "SMX Stage";
@@ -974,7 +993,10 @@ void rawinput::RawInputManager::devices_scan_smxstage() {
}
void rawinput::RawInputManager::devices_scan_smxdedicab() {
log_misc("rawinput", "scan SMX Dedicated Cabinet devices...");
auto *new_smxdedicab_device = new Device();
new_smxdedicab_device->id = this->devices.size() + 1;
new_smxdedicab_device->type = SMX_DEDICAB;
new_smxdedicab_device->name = "smxdedicab";
new_smxdedicab_device->desc = "SMX Dedicated Cabinet";
@@ -997,6 +1019,76 @@ void rawinput::RawInputManager::devices_scan_smxdedicab() {
}
}
void rawinput::RawInputManager::devices_scan_xinput() {
log_misc("rawinput", "scan XInput devices...");
const auto connected_players = XINPUT_MGR->get_available_players();
// first, destroy missing devices
std::vector<std::string> devices_to_remove;
for (auto &device : this->devices) {
if (device.type != XINPUT_GAMEPAD) {
continue;
}
const uint8_t player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device.handle));
if (std::find(connected_players.begin(), connected_players.end(), player) == connected_players.end()) {
devices_to_remove.push_back(device.name);
}
}
for (const auto &name : devices_to_remove) {
this->devices_remove(name);
}
auto create_device = [](const uint8_t player) -> Device {
Device device = {};
device.type = XINPUT_GAMEPAD;
device.name = xinput::get_device_desc(player);
device.desc = fmt::format("XInput Gamepad P{}", player + 1);
device.handle = reinterpret_cast<HANDLE>(player);
device.mutex = new std::mutex();
device.mutex_out = new std::mutex();
return device;
};
// add new devices
for (const auto player : connected_players) {
bool duplicate_found = false;
// check for duplicates first
for (auto &prev_device : this->devices) {
if (prev_device.name != xinput::get_device_desc(player)) {
continue;
}
if (prev_device.type == DESTROYED) {
log_info("rawinput", "overwriting previously destroyed XInput device: {}", prev_device.name);
const auto old_id = prev_device.id;
prev_device = create_device(player);
prev_device.id = old_id;
// notify change
for (auto &cb : this->callback_change) {
cb.f(cb.data, &prev_device);
}
}
duplicate_found = true;
break;
}
if (!duplicate_found) {
// add new device
log_info("rawinput", "adding new XInput device: player {}", player + 1);
auto new_xinput_device = create_device(player);
new_xinput_device.id = this->devices.size() + 1;
auto &device = this->devices.emplace_back(new_xinput_device);
// notify add
for (auto &cb : this->callback_add) {
cb.f(cb.data, &device);
}
}
}
}
void rawinput::RawInputManager::flush_start() {
// start flush thread
@@ -1218,6 +1310,8 @@ std::string rawinput::RawInputManager::rawinput_get_device_description(const raw
void rawinput::RawInputManager::sextet_register(const std::string &port_name, const std::string &alias,
bool warn) {
log_misc("rawinput", "checking for sextet-stream device on {}...", port_name);
// check for any sextet-stream devices
Device device {};
device.type = SEXTET_OUTPUT;
@@ -1258,12 +1352,14 @@ void rawinput::RawInputManager::devices_remove(const std::string &name) {
}
void rawinput::RawInputManager::devices_register() {
// check input window
if (!this->input_hwnd) {
log_warning("rawinput", "trying to register devices without input window");
return;
}
log_misc("rawinput", "registering raw input devices...");
// register keyboard
RAWINPUTDEVICE keyboard_device{};
@@ -2551,6 +2647,10 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
device->smxdedicabInfo->Update();
break;
}
case XINPUT_GAMEPAD: {
// nothing - updates to these are instant
break;
}
default:
break;
}
@@ -2718,6 +2818,10 @@ void rawinput::RawInputManager::devices_print() {
log_misc("rawinput", "device type: SMX_DEDICAB");
break;
}
case XINPUT_GAMEPAD: {
log_misc("rawinput", "device type: XINPUT");
break;
}
case UNKNOWN:
default:
log_warning("rawinput", "device type: UNKNOWN");
+3
View File
@@ -10,6 +10,7 @@
#include "device.h"
#include "hotplug.h"
#include "rawinput/xinput.h"
#include "util/scope_guard.h"
namespace rawinput {
@@ -101,6 +102,7 @@ namespace rawinput {
public:
HWND input_hwnd = nullptr;
std::unique_ptr<xinput::XInputManager> XINPUT_MGR = nullptr;
RawInputManager();
~RawInputManager();
@@ -114,6 +116,7 @@ namespace rawinput {
void devices_scan_rawinput(const std::string &device_name = "");
void devices_scan_midi();
void devices_scan_xinput();
void devices_remove(const std::string &name);
void devices_register();
+407
View File
@@ -0,0 +1,407 @@
#include "rawinput/xinput.h"
#include "util/logging.h"
namespace xinput {
// this is all we need to emulate xinput.h which we avoid including here...
#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD (30 / 255.0f)
#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
#define XINPUT_GAMEPAD_DPAD_UP 0x0001
#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002
#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004
#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008
#define XINPUT_GAMEPAD_START 0x0010
#define XINPUT_GAMEPAD_BACK 0x0020
#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040
#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080
#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100
#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200
#define XINPUT_GAMEPAD_A 0x1000
#define XINPUT_GAMEPAD_B 0x2000
#define XINPUT_GAMEPAD_X 0x4000
#define XINPUT_GAMEPAD_Y 0x8000
typedef struct {
uint32_t dwPacketNumber;
XINPUT_GAMEPAD_STATE Gamepad;
} XINPUT_STATE;
DWORD
WINAPI
XInputGetState(
DWORD dwUserIndex,
XINPUT_STATE *pState
);
typedef struct {
uint16_t wLeftMotorSpeed;
uint16_t wRightMotorSpeed;
} XINPUT_VIBRATION;
DWORD
WINAPI
XInputSetState(
DWORD dwUserIndex,
XINPUT_VIBRATION* pVibration
);
// end xinput definitions
std::string get_button_string(XInputButtonEnum button) {
switch (button) {
case XInputButtonEnum::DPAD_UP:
return "Dpad Up";
case XInputButtonEnum::DPAD_DOWN:
return "Dpad Down";
case XInputButtonEnum::DPAD_LEFT:
return "Dpad Left";
case XInputButtonEnum::DPAD_RIGHT:
return "Dpad Right";
case XInputButtonEnum::START:
return "Start";
case XInputButtonEnum::BACK:
return "Back";
case XInputButtonEnum::LEFT_STICK:
return "Left Stick Click";
case XInputButtonEnum::RIGHT_STICK:
return "Right Stick Click";
case XInputButtonEnum::LEFT_SHOULDER:
return "Left Bumper";
case XInputButtonEnum::RIGHT_SHOULDER:
return "Right Bumper";
case XInputButtonEnum::BUTTON_A:
return "Button A";
case XInputButtonEnum::BUTTON_B:
return "Button B";
case XInputButtonEnum::BUTTON_X:
return "Button X";
case XInputButtonEnum::BUTTON_Y:
return "Button Y";
case XInputButtonEnum::LEFT_TRIGGER:
return "Left Trigger";
case XInputButtonEnum::RIGHT_TRIGGER:
return "Right Trigger";
case XInputButtonEnum::LEFT_STICK_UP:
return "Left Stick, Up";
case XInputButtonEnum::LEFT_STICK_DOWN:
return "Left Stick, Down";
case XInputButtonEnum::LEFT_STICK_LEFT:
return "Left Stick, Left";
case XInputButtonEnum::LEFT_STICK_RIGHT:
return "Left Stick, Right";
case XInputButtonEnum::RIGHT_STICK_UP:
return "Right Stick, Up";
case XInputButtonEnum::RIGHT_STICK_DOWN:
return "Right Stick, Down";
case XInputButtonEnum::RIGHT_STICK_LEFT:
return "Right Stick, Left";
case XInputButtonEnum::RIGHT_STICK_RIGHT:
return "Right Stick, Right";
default:
break;
}
return fmt::format("Unknown Button ({})", static_cast<int>(button));
}
std::string get_analog_string(XInputAnalogEnum analog) {
switch (analog) {
case XInputAnalogEnum::LEFT_TRIGGER:
return "Left Trigger";
case XInputAnalogEnum::RIGHT_TRIGGER:
return "Right Trigger";
case XInputAnalogEnum::LEFT_STICK_X:
return "Left Stick X";
case XInputAnalogEnum::LEFT_STICK_Y:
return "Left Stick Y";
case XInputAnalogEnum::RIGHT_STICK_X:
return "Right Stick X";
case XInputAnalogEnum::RIGHT_STICK_Y:
return "Right Stick Y";
default:
break;
}
return fmt::format("Unknown Analog ({})", static_cast<int>(analog));
}
std::string get_output_string(XInputOutputEnum output) {
switch (output) {
case XInputOutputEnum::LEFT_RUMBLE:
return "Left Rumble";
case XInputOutputEnum::RIGHT_RUMBLE:
return "Right Rumble";
default:
break;
}
return fmt::format("Unknown Output ({})", static_cast<int>(output));
}
std::string get_device_desc(uint8_t player) {
return fmt::format(";XINPUT;{}", player);
}
#if defined(SPICE_XP)
XInputManager::XInputManager() {}
XInputManager::~XInputManager() {}
void XInputManager::stop() {}
std::vector<uint8_t> XInputManager::get_available_players() {
return {};
}
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
return 0.5f;
}
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
return false;
}
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
return false;
}
void XInputManager::set_output_state(uint8_t player, XInputOutputEnum output, float value) {
return;
}
#else
static decltype(XInputGetState) *XInputGetState_addr = nullptr;
static decltype(XInputSetState) *XInputSetState_addr = nullptr;
XInputManager::XInputManager() {
log_info("xinput", "initialize...");
this->xinput_lib = LoadLibraryA("xinput1_3.dll");
if (!this->xinput_lib) {
log_warning("xinput", "failed to load xinput1_3.dll");
return;
}
XInputGetState_addr = reinterpret_cast<decltype(XInputGetState) *>(
GetProcAddress(this->xinput_lib, "XInputGetState"));
if (!XInputGetState_addr) {
log_warning("xinput", "failed to get XInputGetState address");
this->stop();
return;
}
XInputSetState_addr = reinterpret_cast<decltype(XInputSetState) *>(
GetProcAddress(this->xinput_lib, "XInputSetState"));
if (!XInputSetState_addr) {
log_warning("xinput", "failed to get XInputSetState address");
this->stop();
return;
}
initialized = true;
log_info("xinput", "initialized");
}
XInputManager::~XInputManager() {
this->stop();
}
void XInputManager::stop() {
this->initialized = false;
if (this->xinput_lib) {
FreeLibrary(this->xinput_lib);
this->xinput_lib = nullptr;
}
XInputGetState_addr = nullptr;
XInputSetState_addr = nullptr;
log_info("xinput", "destroyed");
}
std::vector<uint8_t> XInputManager::get_available_players() {
std::vector<uint8_t> players;
if (!this->initialized) {
return players;
}
for (uint8_t i = 0; i < XUSER_MAX_COUNT; i++) {
XINPUT_STATE x;
if (XInputGetState_addr(i, &x) == ERROR_SUCCESS) {
players.push_back(i);
}
}
return players;
}
void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state) {
state = {};
state.sThumbLX = 0.5f;
state.sThumbLY = 0.5f;
state.sThumbRX = 0.5f;
state.sThumbRY = 0.5f;
if (!this->initialized) {
return;
}
XINPUT_STATE x;
if (XInputGetState_addr(player, &x) != ERROR_SUCCESS) {
return;
}
state.wButtons = x.Gamepad.wButtons;
state.bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f;
state.bRightTrigger = x.Gamepad.bRightTrigger / 255.0f;
// left stick circular dead zone
{
const auto x_raw = x.Gamepad.sThumbLX;
const auto y_raw = -x.Gamepad.sThumbLY; // flip to make [down = positive]
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw);
if (magnitude > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
const float scaled =
(magnitude - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
state.sThumbLX = (x_raw / magnitude) * scaled;
state.sThumbLY = (y_raw / magnitude) * scaled;
// normalize to [0, 1]
state.sThumbLX = std::clamp((state.sThumbLX + 1.f) / 2.f, 0.f, 1.f);
state.sThumbLY = std::clamp((state.sThumbLY + 1.f) / 2.f, 0.f, 1.f);
} else {
// within deadzone
state.sThumbLX = 0.5f;
state.sThumbLY = 0.5f;
}
}
// right stick circular dead zone
{
const auto x_raw = x.Gamepad.sThumbRX;
const auto y_raw = -x.Gamepad.sThumbRY; // flip to make [down = positive]
const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw);
if (magnitude > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) {
const float scaled =
(magnitude - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) /
(32767.0f - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
state.sThumbRX = (x_raw / magnitude) * scaled;
state.sThumbRY = (y_raw / magnitude) * scaled;
// normalize to [0, 1]
state.sThumbRX = std::clamp((state.sThumbRX + 1.f) / 2.f, 0.f, 1.f);
state.sThumbRY = std::clamp((state.sThumbRY + 1.f) / 2.f, 0.f, 1.f);
} else {
// within deadzone
state.sThumbRX = 0.5f;
state.sThumbRY = 0.5f;
}
}
}
bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) {
XINPUT_GAMEPAD_STATE_NORMALIZED state;
get_state(player, state);
switch (button) {
case XInputButtonEnum::DPAD_UP:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0;
case XInputButtonEnum::DPAD_DOWN:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
case XInputButtonEnum::DPAD_LEFT:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
case XInputButtonEnum::DPAD_RIGHT:
return (state.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
case XInputButtonEnum::START:
return (state.wButtons & XINPUT_GAMEPAD_START) != 0;
case XInputButtonEnum::BACK:
return (state.wButtons & XINPUT_GAMEPAD_BACK) != 0;
case XInputButtonEnum::LEFT_STICK:
return (state.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
case XInputButtonEnum::RIGHT_STICK:
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
case XInputButtonEnum::LEFT_SHOULDER:
return (state.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
case XInputButtonEnum::RIGHT_SHOULDER:
return (state.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
case XInputButtonEnum::BUTTON_A:
return (state.wButtons & XINPUT_GAMEPAD_A) != 0;
case XInputButtonEnum::BUTTON_B:
return (state.wButtons & XINPUT_GAMEPAD_B) != 0;
case XInputButtonEnum::BUTTON_X:
return (state.wButtons & XINPUT_GAMEPAD_X) != 0;
case XInputButtonEnum::BUTTON_Y:
return (state.wButtons & XINPUT_GAMEPAD_Y) != 0;
case XInputButtonEnum::LEFT_TRIGGER:
return state.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
case XInputButtonEnum::RIGHT_TRIGGER:
return state.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
case XInputButtonEnum::LEFT_STICK_UP:
return state.sThumbLY > 0.6f;
case XInputButtonEnum::LEFT_STICK_DOWN:
return state.sThumbLY < 0.4f;
case XInputButtonEnum::LEFT_STICK_LEFT:
return state.sThumbLX < 0.4f;
case XInputButtonEnum::LEFT_STICK_RIGHT:
return state.sThumbLX > 0.6f;
case XInputButtonEnum::RIGHT_STICK_UP:
return state.sThumbRY > 0.6f;
case XInputButtonEnum::RIGHT_STICK_DOWN:
return state.sThumbRY < 0.4f;
case XInputButtonEnum::RIGHT_STICK_LEFT:
return state.sThumbRX < 0.4f;
case XInputButtonEnum::RIGHT_STICK_RIGHT:
return state.sThumbRX > 0.6f;
default:
break;
}
return false;
}
float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) {
XINPUT_GAMEPAD_STATE_NORMALIZED state;
get_state(player, state);
switch (analog) {
case XInputAnalogEnum::LEFT_TRIGGER:
return state.bLeftTrigger;
case XInputAnalogEnum::RIGHT_TRIGGER:
return state.bRightTrigger;
case XInputAnalogEnum::LEFT_STICK_X:
return state.sThumbLX;
case XInputAnalogEnum::LEFT_STICK_Y:
return state.sThumbLY;
case XInputAnalogEnum::RIGHT_STICK_X:
return state.sThumbRX;
case XInputAnalogEnum::RIGHT_STICK_Y:
return state.sThumbRY;
default:
break;
}
return 0.5f;
}
bool XInputManager::get_any_button_pressed(XINPUT_NEW_BUTTON &button) {
for (uint8_t player = 0; player < XUSER_MAX_COUNT; player++) {
for (uint16_t b = 0; b < static_cast<uint16_t>(XInputButtonEnum::COUNT); b++) {
if (is_button_pressed(player, static_cast<XInputButtonEnum>(b))) {
button.player = player;
button.button = static_cast<XInputButtonEnum>(b);
return true;
}
}
}
return false;
}
void XInputManager::set_output_state(uint8_t player, XInputOutputEnum output, float value) {
if (!this->initialized) {
return;
}
if (player >= XUSER_MAX_COUNT) {
return;
}
XINPUT_VIBRATION vibration = {};
if (output == XInputOutputEnum::LEFT_RUMBLE) {
vibration.wLeftMotorSpeed = static_cast<uint16_t>(value * 65535);
} else if (output == XInputOutputEnum::RIGHT_RUMBLE) {
vibration.wRightMotorSpeed = static_cast<uint16_t>(value * 65535);
}
XInputSetState_addr(player, &vibration);
}
#endif
}
+110
View File
@@ -0,0 +1,110 @@
#pragma once
#include <cstdint>
#include <array>
#include <vector>
#include <string>
#include <windows.h>
namespace xinput {
#define XUSER_MAX_COUNT 4
struct XINPUT_GAMEPAD_STATE {
uint16_t wButtons;
uint8_t bLeftTrigger;
uint8_t bRightTrigger;
int16_t sThumbLX;
int16_t sThumbLY;
int16_t sThumbRX;
int16_t sThumbRY;
};
struct XINPUT_GAMEPAD_STATE_NORMALIZED {
uint16_t wButtons;
float bLeftTrigger;
float bRightTrigger;
float sThumbLX;
float sThumbLY;
float sThumbRX;
float sThumbRY;
};
enum class XInputButtonEnum : uint16_t {
// actual buttons
DPAD_UP,
DPAD_DOWN,
DPAD_LEFT,
DPAD_RIGHT,
START,
BACK,
LEFT_STICK,
RIGHT_STICK,
LEFT_SHOULDER,
RIGHT_SHOULDER,
BUTTON_A,
BUTTON_B,
BUTTON_X,
BUTTON_Y,
// analog values that can be used as buttons
LEFT_TRIGGER,
RIGHT_TRIGGER,
LEFT_STICK_UP,
LEFT_STICK_DOWN,
LEFT_STICK_LEFT,
LEFT_STICK_RIGHT,
RIGHT_STICK_UP,
RIGHT_STICK_DOWN,
RIGHT_STICK_LEFT,
RIGHT_STICK_RIGHT,
COUNT
};
enum class XInputAnalogEnum {
// analog values
LEFT_TRIGGER,
RIGHT_TRIGGER,
LEFT_STICK_X,
LEFT_STICK_Y,
RIGHT_STICK_X,
RIGHT_STICK_Y,
COUNT
};
enum class XInputOutputEnum : uint16_t {
LEFT_RUMBLE,
RIGHT_RUMBLE,
COUNT
};
struct XINPUT_NEW_BUTTON {
uint8_t player;
XInputButtonEnum button;
};
std::string get_button_string(XInputButtonEnum button);
std::string get_analog_string(XInputAnalogEnum analog);
std::string get_output_string(XInputOutputEnum output);
std::string get_device_desc(uint8_t player);
class XInputManager {
private:
bool initialized = false;
HMODULE xinput_lib = nullptr;
void get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED &state);
public:
XInputManager();
~XInputManager();
void stop();
std::vector<uint8_t> get_available_players();
bool is_button_pressed(uint8_t player, XInputButtonEnum button);
float get_analog_state(uint8_t player, XInputAnalogEnum analog);
bool get_any_button_pressed(XINPUT_NEW_BUTTON &button);
void set_output_state(uint8_t player, XInputOutputEnum output, float value);
};
}
+1
View File
@@ -243,6 +243,7 @@ static LRESULT CALLBACK SpiceTouchWndProc(HWND hWnd, UINT msg, WPARAM wParam, LP
const auto is_windowed_sub =
(GRAPHICS_IIDX_WSUB && hWnd == TDJ_SUBSCREEN_WINDOW) ||
(hWnd == SDVX_SUBSCREEN_WINDOW) ||
(hWnd == POPN_SUBSCREEN_WINDOW) ||
(hWnd == GFDM_SUBSCREEN_WINDOW);
if (msg == WM_CLOSE) {
+306 -19
View File
@@ -1,12 +1,22 @@
// GetDisplayConfigBufferSizes etc is Vista+
#define _WIN32_WINNT 0x0601
#include "sysutils.h"
#include <cstdlib>
#include <mutex>
#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include "hooks/graphics/graphics.h"
#include "avs/game.h"
#include "util/detour.h"
#include "util/flags_helper.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
#if 0
#define log_dbug(module, format_str, ...) logger::push( \
@@ -17,7 +27,7 @@
namespace sysutils {
#pragma pack(push)
#pragma pack(push)
#pragma pack(1)
// from https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemfirmwaretable
@@ -80,6 +90,9 @@ namespace sysutils {
);
static GetSystemFirmwareTable_t GetSystemFirmwareTable = nullptr;
std::string SECOND_MONITOR_OVERRIDE = "";
static decltype(EnumDisplayDevicesA) *EnumDisplayDevicesA_orig = nullptr;
void print_smbios() {
DWORD bytes_written = 0;
DWORD table_size = 0;
@@ -206,41 +219,87 @@ namespace sysutils {
return strings;
}
static std::string device_flags_to_string(DWORD flags) {
FLAGS_START(flags);
FLAG(flags, DISPLAY_DEVICE_ATTACHED_TO_DESKTOP);
FLAG(flags, DISPLAY_DEVICE_MULTI_DRIVER);
FLAG(flags, DISPLAY_DEVICE_PRIMARY_DEVICE);
FLAG(flags, DISPLAY_DEVICE_MIRRORING_DRIVER);
FLAG(flags, DISPLAY_DEVICE_VGA_COMPATIBLE);
FLAG(flags, DISPLAY_DEVICE_REMOVABLE);
#if !defined(DISPLAY_DEVICE_ACC_DRIVER)
#define DISPLAY_DEVICE_ACC_DRIVER 0x00000040
#endif
FLAG(flags, DISPLAY_DEVICE_ACC_DRIVER);
FLAG(flags, DISPLAY_DEVICE_MODESPRUNED);
FLAG(flags, DISPLAY_DEVICE_RDPUDD);
FLAG(flags, DISPLAY_DEVICE_REMOTE);
FLAG(flags, DISPLAY_DEVICE_DISCONNECT);
FLAG(flags, DISPLAY_DEVICE_TS_COMPATIBLE);
#if !defined(DISPLAY_DEVICE_UNSAFE_MODES_ON)
#define DISPLAY_DEVICE_UNSAFE_MODES_ON 0x00080000
#endif
FLAG(flags, DISPLAY_DEVICE_UNSAFE_MODES_ON);
FLAGS_END(flags);
}
static std::string monitor_flags_to_string(DWORD flags) {
FLAGS_START(flags);
FLAG(flags, DISPLAY_DEVICE_ACTIVE);
FLAG(flags, DISPLAY_DEVICE_ATTACHED);
FLAGS_END(flags);
}
static void print_adapter(DWORD index, PDISPLAY_DEVICEA adapter, bool is_monitor) {
if (adapter->StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) {
return;
}
if (!(adapter->StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
return;
if (!is_monitor) {
log_misc("gpuinfo", "-------- device {} --------", index);
}
std::string prefix("device");
if (is_monitor) {
prefix = " adapter";
}
log_misc("gpuinfo", "{} {} device name : {}", prefix.c_str(), index, adapter->DeviceName);
log_misc("gpuinfo", "{} {} device string : {}", prefix.c_str(), index, adapter->DeviceString);
log_dbug("gpuinfo", "{} {} flags : 0x{:x}", prefix.c_str(), index, adapter->StateFlags);
if (!is_monitor) {
// get extended info for better friendly name of monitors
std::string friendly_name = "unknown";
const auto &monitors = enumerate_monitors();
for (const auto& monitor : monitors) {
if (monitor.display_name == adapter->DeviceName) {
friendly_name = ws2s(monitor.friendly_name.c_str());
break;
}
}
log_misc("gpuinfo", "{} {} name : {} ({}) @ {}",
prefix.c_str(), index,
adapter->DeviceName, friendly_name, adapter->DeviceString);
// resolution, refresh rate
DEVMODEA devmode = {};
devmode.dmSize = sizeof(devmode);
if (EnumDisplaySettingsA(adapter->DeviceName, ENUM_CURRENT_SETTINGS, &devmode)) {
log_misc(
"gpuinfo",
"{} {} resolution : {}px * {}px @ {}Hz",
"gpuinfo", "{} {} resolution : {}px * {}px @ {}Hz",
prefix.c_str(),
index,
devmode.dmPelsWidth, devmode.dmPelsHeight,
devmode.dmDisplayFrequency);
} else {
log_misc("gpuinfo", "EnumDisplaySettingsA failed");
}
log_misc("gpuinfo", "{} {} flags : {}",
prefix.c_str(), index, device_flags_to_string(adapter->StateFlags));
log_misc(
"gpuinfo", "{} {} is primary : {}",
prefix.c_str(),
index,
(adapter->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) ? "yes" : "no");
} else {
log_misc("gpuinfo", "{} {} name : {} ({})",
prefix.c_str(), index,
adapter->DeviceName, adapter->DeviceString);
log_misc("gpuinfo", "{} {} flags : {}",
prefix.c_str(), index, monitor_flags_to_string(adapter->StateFlags));
}
}
@@ -298,4 +357,232 @@ namespace sysutils {
return;
}
}
static std::vector<MonitorEntry> enumerate_monitors_internal() {
// for WinXP, since these are Vista+ or 7+ APIs
const auto user32 = LoadLibraryA("user32.dll");
if (!user32) {
log_warning("graphics", "can't find user32.dll???");
return {};
}
const auto GetDisplayConfigBufferSizes_addr =
reinterpret_cast<decltype(GetDisplayConfigBufferSizes) *>(
GetProcAddress(user32, "GetDisplayConfigBufferSizes"));
const auto QueryDisplayConfig_addr =
reinterpret_cast<decltype(QueryDisplayConfig) *>(
GetProcAddress(user32, "QueryDisplayConfig"));
const auto DisplayConfigGetDeviceInfo_addr =
reinterpret_cast<decltype(DisplayConfigGetDeviceInfo) *>(
GetProcAddress(user32, "DisplayConfigGetDeviceInfo"));
if (GetDisplayConfigBufferSizes_addr == nullptr ||
QueryDisplayConfig_addr == nullptr ||
DisplayConfigGetDeviceInfo_addr == nullptr) {
log_warning("sysutils", "OS does not support display config APIs");
return {};
}
UINT32 path_count = 0;
UINT32 mode_count = 0;
auto status = GetDisplayConfigBufferSizes_addr(QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count);
if (status != ERROR_SUCCESS) {
log_warning("sysutils", "GetDisplayConfigBufferSizes failed: {}", status);
return {};
}
std::vector<DISPLAYCONFIG_PATH_INFO> paths(path_count);
std::vector<DISPLAYCONFIG_MODE_INFO> modes(mode_count);
status = QueryDisplayConfig_addr(
QDC_ONLY_ACTIVE_PATHS,
&path_count,
paths.data(),
&mode_count,
modes.data(),
nullptr);
if (status != ERROR_SUCCESS) {
log_warning("sysutils", "QueryDisplayConfig failed: {}", status);
return {};
}
// shrink to actual returned items
paths.resize(path_count);
modes.resize(mode_count);
std::vector<MonitorEntry> result;
for (const auto& path : paths) {
MonitorEntry entry = {};
entry.adapter_id_LowPart = path.targetInfo.adapterId.LowPart;
entry.adapter_id_HighPart = path.targetInfo.adapterId.HighPart;
entry.id = path.targetInfo.id;
// primary monitor?
for (const auto& mode : modes) {
if (mode.infoType != DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE) {
continue;
}
if (mode.adapterId.HighPart == path.sourceInfo.adapterId.HighPart &&
mode.adapterId.LowPart == path.sourceInfo.adapterId.LowPart &&
mode.id == path.sourceInfo.id) {
if (mode.sourceMode.position.x == 0 && mode.sourceMode.position.y == 0) {
entry.is_primary = true;
}
break;
}
}
// device ID (\\.\DISPLAYn)
DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {};
source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
source_name.header.size = sizeof(source_name);
source_name.header.adapterId = path.sourceInfo.adapterId;
source_name.header.id = path.sourceInfo.id;
if (DisplayConfigGetDeviceInfo_addr(&source_name.header) != ERROR_SUCCESS) {
continue;
}
entry.display_name = ws2s(std::wstring(source_name.viewGdiDeviceName));
// friendly name
DISPLAYCONFIG_TARGET_DEVICE_NAME target_name = {};
target_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
target_name.header.size = sizeof(target_name);
target_name.header.adapterId = path.targetInfo.adapterId;
target_name.header.id = path.targetInfo.id;
if (DisplayConfigGetDeviceInfo_addr(&target_name.header) == ERROR_SUCCESS) {
entry.friendly_name = target_name.monitorFriendlyDeviceName;
} else {
entry.friendly_name = L"(unknown)";
}
// done
result.emplace_back(entry);
}
return result;
}
const std::vector<MonitorEntry>& enumerate_monitors() {
static const std::vector<MonitorEntry> monitors = enumerate_monitors_internal();
return monitors;
}
static
BOOL
__stdcall
EnumDisplayDevicesA_hook(
LPCSTR lpDevice,
DWORD iDevNum,
PDISPLAY_DEVICEA lpDisplayDevice,
DWORD dwFlags
) {
if (EnumDisplayDevicesA_orig == nullptr) {
return false;
}
// caller is enumerating monitors (adapters), not devices
if (lpDevice != nullptr) {
log_misc("sysutils", "EnumDisplayDevicesA: returning original results for device {} [{}]", lpDevice, iDevNum);
return EnumDisplayDevicesA_orig(lpDevice, iDevNum, lpDisplayDevice, dwFlags);
}
// call the original first
const auto result_orig = EnumDisplayDevicesA_orig(nullptr, iDevNum, lpDisplayDevice, dwFlags);
if (!result_orig) {
return result_orig;
}
log_misc(
"sysutils",
"EnumDisplayDevicesA_orig: {} [{}], StateFlags={:#x}",
lpDisplayDevice->DeviceName,
iDevNum,
lpDisplayDevice->StateFlags);
// this one is not relevant
if ((lpDisplayDevice->StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) {
return result_orig;
}
// if this is for the primary device, just return it
if (lpDisplayDevice->StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
log_misc(
"sysutils",
"EnumDisplayDevicesA: returning original results for primary monitor [{}], {}, result={}",
iDevNum,
lpDisplayDevice->DeviceName,
result_orig);
return result_orig;
}
// this is not 100% confirmed, but hope that this helps in cases where second monitors get used
// even though NumberOfAdaptersInGroup was set to 1 on hybrid laptops
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
lpDisplayDevice->StateFlags &= ~DISPLAY_DEVICE_ATTACHED_TO_DESKTOP;
log_info(
"sysutils",
"hiding this monitor, {} @ index {} (-graphics-single-adapter)",
lpDisplayDevice->DeviceName,
iDevNum);
return result_orig;
}
// for the second device (subscreen)...
if (SECOND_MONITOR_OVERRIDE.empty()) {
// if there is no user override, we find the device with lowest index (that isn't primary)
for (DWORD i = 0;; i++) {
DISPLAY_DEVICEA device = {};
device.cb = sizeof(device);
const auto result = EnumDisplayDevicesA_orig(nullptr, i, &device, 0);
if (!result) {
break;
}
if (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
// primary, skip this
continue;
}
if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) {
// disconnected, skip this as well
continue;
}
if (std::string(lpDisplayDevice->DeviceName) == device.DeviceName) {
log_info(
"sysutils",
"returning second monitor {} @ index {}",
lpDisplayDevice->DeviceName,
iDevNum);
return result_orig;
} else {
// otherwise, fall through and hide this monitor
break;
}
}
} else if (SECOND_MONITOR_OVERRIDE == lpDisplayDevice->DeviceName) {
// if there is one preferred by the user, use this one, and hide others
log_info(
"sysutils",
"returning second monitor, {} @ index {} (-iidxsubmonitor / -sdvxsubmonitor)",
lpDisplayDevice->DeviceName,
iDevNum);
return result_orig;
}
// this device should not be used; pretend that it's not connected
log_info(
"sysutils",
"hiding this monitor, {} @ index {}",
lpDisplayDevice->DeviceName,
iDevNum);
lpDisplayDevice->StateFlags &= ~DISPLAY_DEVICE_ATTACHED_TO_DESKTOP;
return result_orig;
}
void hook_EnumDisplayDevicesA() {
// the goal is to trick DX9 into thinking that some monitors are not attached
// so we need to trampoline hook at user32 instead of IAT at game level
log_misc("sysutils", "hooking EnumDisplayDevicesA to hide if there are more than 3 monitors...");
detour::trampoline_try(
"user32.dll",
"EnumDisplayDevicesA",
(void*)EnumDisplayDevicesA_hook,
(void**)&EnumDisplayDevicesA_orig);
}
}
+16 -1
View File
@@ -1,10 +1,25 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
namespace sysutils {
void print_smbios();
void print_gpus();
void print_os();
}
struct MonitorEntry {
std::string display_name; // \\.\DISPLAY1
std::wstring friendly_name; // Dell S2204T; wstring so that it can be converted to utf8 or string
bool is_primary;
uint32_t adapter_id_LowPart;
int32_t adapter_id_HighPart;
uint32_t id;
};
const std::vector<MonitorEntry> &enumerate_monitors();
extern std::string SECOND_MONITOR_OVERRIDE;
void hook_EnumDisplayDevicesA();
}
+1 -1
View File
@@ -38,7 +38,7 @@ namespace unity_utils {
// monitor
if (D3D9_ADAPTER.has_value()) {
args += fmt::format(" -monitor {}", D3D9_ADAPTER.value());
args += fmt::format(" -dxmainadapter {}", D3D9_ADAPTER.value());
}
return args;