mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15bfffa1d3 | |||
| 18a9a47c33 | |||
| 6838e69ebe | |||
| 26b3b64794 | |||
| aadd2bdd59 | |||
| d0e24e31ca | |||
| 4fb7f20c7b | |||
| 6ddae70c5a | |||
| b38160d6c2 | |||
| 231c998cc0 | |||
| 127d31a85c | |||
| a8419bbaa0 | |||
| 0fc430ceb3 | |||
| e59a50b64c | |||
| 2042b629e9 | |||
| 440b9ed749 | |||
| 9936088286 | |||
| 4d8a6c1952 | |||
| af69cf9251 | |||
| 1d5550b1e8 | |||
| ec3f6639b7 | |||
| 727b99effd | |||
| 8afff3aaca | |||
| b281517429 | |||
| 54ec9f272e | |||
| a159691097 | |||
| ca70f7b19c | |||
| 7b4227d2a9 | |||
| f89efa2558 | |||
| 6f4824b648 | |||
| 45debeb5c9 | |||
| a01f452eba | |||
| e452734bc1 | |||
| d1779b93fa | |||
| fe9aac7e29 | |||
| f3d887ebcf | |||
| 336500c8ae | |||
| ee3fa58bfa | |||
| a4c3eddc2f | |||
| e0f6cb2cbd | |||
| 1ad6a9412e | |||
| 5af3e0d494 | |||
| b1ee828457 | |||
| 0d60649715 |
@@ -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
|
||||
|
||||
+122
-20
@@ -35,24 +35,33 @@ struct ICCA_STATUS_LA9 {
|
||||
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
|
||||
|
||||
enum ICCA_WORKFLOW {
|
||||
STEP,
|
||||
SLEEP,
|
||||
START,
|
||||
INIT,
|
||||
READY,
|
||||
GET_USERID,
|
||||
ACTIVE,
|
||||
EJECT,
|
||||
EJECT_CHECK,
|
||||
END,
|
||||
CLOSE_EJECT,
|
||||
CLOSE_E_CHK,
|
||||
CLOSE_END,
|
||||
ERR_GETUID = -2
|
||||
STEP = 0,
|
||||
SLEEP = 1,
|
||||
START = 2,
|
||||
INIT = 3,
|
||||
READY = 4,
|
||||
GET_USERID = 5,
|
||||
ACTIVE = 6,
|
||||
EJECT = 7,
|
||||
EJECT_CHECK = 8,
|
||||
END = 9,
|
||||
CLOSE_EJECT = 10,
|
||||
CLOSE_E_CHK = 11,
|
||||
CLOSE_END = 12,
|
||||
ERR_GETUID = -2,
|
||||
ERR_REMOVED = -11
|
||||
};
|
||||
|
||||
// used by RA as the status_code
|
||||
enum ICCA_STATUS_CODE {
|
||||
EMPTY = 1,
|
||||
ENGAGED = 2
|
||||
};
|
||||
|
||||
struct ICCA_UNIT {
|
||||
struct ICCA_STATUS status {};
|
||||
enum ICCA_WORKFLOW state = STEP;
|
||||
enum ICCA_WORKFLOW state_jdz = SLEEP;
|
||||
bool card_cmd_pressed = false;
|
||||
bool card_in = false;
|
||||
double card_in_time = 0.0;
|
||||
@@ -75,6 +84,11 @@ static inline int icca_get_active_count() {
|
||||
}
|
||||
|
||||
static inline int icca_get_unit_id(int unit_id) {
|
||||
// on RA the unit_id is zero-indexed
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
return unit_id;
|
||||
}
|
||||
|
||||
if (icca_get_active_count() < 2)
|
||||
return 1;
|
||||
else {
|
||||
@@ -122,9 +136,15 @@ static inline void update_card(int unit_id) {
|
||||
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
|
||||
|
||||
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
||||
unit->state_jdz = ACTIVE;
|
||||
unit->status.error = 0;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
} else {
|
||||
unit->state = ERR_GETUID;
|
||||
unit->state_jdz = ERR_GETUID;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
memset(unit->status.uid, 0, 8);
|
||||
}
|
||||
unit->card_in = true;
|
||||
@@ -132,10 +152,25 @@ static inline void update_card(int unit_id) {
|
||||
} else if (unit->state == EJECT_CHECK) {
|
||||
unit->state = SLEEP;
|
||||
unit->card_in = false;
|
||||
unit->status.front_sensor = 0;
|
||||
unit->status.rear_sensor = 0;
|
||||
} else if (unit->state_jdz == READY) {
|
||||
unit->state = GET_USERID;
|
||||
unit->state_jdz = GET_USERID;
|
||||
unit->status.error = 0;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
unit->card_in = true;
|
||||
unit->card_in_time = get_performance_seconds();
|
||||
}
|
||||
} else {
|
||||
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
||||
if (unit->state_jdz == READY) {
|
||||
unit->state_jdz = GET_USERID;
|
||||
}
|
||||
unit->status.error = 0;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
unit->card_in = true;
|
||||
unit->card_in_time = get_performance_seconds();
|
||||
}
|
||||
@@ -144,6 +179,12 @@ static inline void update_card(int unit_id) {
|
||||
unit->state = CLOSE_EJECT;
|
||||
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
|
||||
unit->card_in = false;
|
||||
memset(unit->status.uid, 0, 8);
|
||||
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
|
||||
unit->state_jdz = ERR_REMOVED;
|
||||
}
|
||||
unit->status.front_sensor = 0;
|
||||
unit->status.rear_sensor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +343,11 @@ static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
|
||||
|
||||
// copy state to output buffer
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
unit->status.status_code = unit->state;
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
|
||||
} else {
|
||||
unit->status.status_code = unit->state;
|
||||
}
|
||||
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
|
||||
|
||||
// funny workaround
|
||||
@@ -387,6 +432,11 @@ static char __cdecl ac_io_icca_req_uid(int unit_id) {
|
||||
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
|
||||
unit_id = icca_get_unit_id(unit_id);
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
// hack for RA - only ever seen it as this
|
||||
*read_state = 8;
|
||||
return 1;
|
||||
}
|
||||
if (unit->card_in) {
|
||||
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
|
||||
unit->state = END;
|
||||
@@ -403,16 +453,61 @@ static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __cdecl ac_io_icca_workflow_jdz(int workflow, int unit_id) {
|
||||
|
||||
unit_id = icca_get_unit_id(unit_id);
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
switch (workflow) {
|
||||
// RA uses "STEP" as more of a polling thing it seems
|
||||
case STEP:
|
||||
switch (unit->state_jdz) {
|
||||
case CLOSE_EJECT:
|
||||
unit->state_jdz = CLOSE_E_CHK;
|
||||
break;
|
||||
case CLOSE_E_CHK:
|
||||
unit->state_jdz = CLOSE_END;
|
||||
break;
|
||||
case CLOSE_END:
|
||||
unit->state_jdz = SLEEP;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SLEEP:
|
||||
unit->state = SLEEP;
|
||||
break;
|
||||
case START:
|
||||
unit->state_jdz = INIT;
|
||||
break;
|
||||
case INIT:
|
||||
case READY:
|
||||
unit->state_jdz = READY;
|
||||
break;
|
||||
case CLOSE_EJECT:
|
||||
unit->state_jdz = CLOSE_EJECT;
|
||||
return CLOSE_E_CHK;
|
||||
case CLOSE_END:
|
||||
unit->state_jdz = SLEEP;
|
||||
break;
|
||||
default:
|
||||
// should probably log a warning here
|
||||
break;
|
||||
}
|
||||
return unit->state_jdz;
|
||||
}
|
||||
|
||||
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
|
||||
// RA uses a different workflow process for the slotted readers; treat it differently
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
return ac_io_icca_workflow_jdz(workflow, unit_id);
|
||||
}
|
||||
|
||||
unit_id = icca_get_unit_id(unit_id);
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
switch (workflow) {
|
||||
case STEP:
|
||||
if (avs::game::is_model("JDZ"))
|
||||
unit->state = SLEEP;
|
||||
else
|
||||
unit->state = STEP;
|
||||
unit->state = STEP;
|
||||
break;
|
||||
case SLEEP:
|
||||
unit->state = SLEEP;
|
||||
@@ -441,7 +536,6 @@ static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return unit->state;
|
||||
}
|
||||
|
||||
@@ -467,6 +561,14 @@ acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModu
|
||||
void acio::ICCAModule::attach() {
|
||||
ACIOModule::attach();
|
||||
|
||||
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
ICCA_UNITS[0].initialized = true;
|
||||
ICCA_UNITS[1].initialized = true;
|
||||
|
||||
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
|
||||
}
|
||||
|
||||
// hooks
|
||||
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
|
||||
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 "$@"
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -20,6 +20,7 @@ enum class OptionPickerType {
|
||||
CpuAffinity,
|
||||
FilePath,
|
||||
DirectoryPath,
|
||||
Monitor,
|
||||
};
|
||||
|
||||
struct OptionDefinition {
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace cfg {
|
||||
// window = rectangle including the frame
|
||||
// client = just the content area without frames.
|
||||
bool window_always_on_top = false;
|
||||
bool window_disable_round_corners = false;
|
||||
bool client_keep_aspect_ratio = true;
|
||||
bool enable_window_resize = false;
|
||||
int window_decoration = 0; // enum type WindowDecorationMode
|
||||
|
||||
+8
@@ -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
@@ -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
@@ -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(¶m) != 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
@@ -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
@@ -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
|
||||
|
||||
|
||||
Vendored
+3439
-1953
File diff suppressed because it is too large
Load Diff
Vendored
+1023
-517
File diff suppressed because it is too large
Load Diff
+2825
-2079
File diff suppressed because it is too large
Load Diff
+3216
-1153
File diff suppressed because it is too large
Load Diff
+780
-340
File diff suppressed because it is too large
Load Diff
+258
-164
@@ -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
|
||||
|
||||
+352
-176
File diff suppressed because it is too large
Load Diff
+1497
-961
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -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
@@ -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
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <vector>
|
||||
#include "games/iidx/local_camera.h"
|
||||
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -906,8 +915,7 @@ namespace games::iidx {
|
||||
log_warning(
|
||||
"iidx",
|
||||
"Failed to apply ASIO compatibility fix for iidx32+. "
|
||||
"Unless patches are applied, your ASIO device ({}) may hang or fail to work",
|
||||
ASIO_DRIVER->c_str());
|
||||
"Unless patches are applied, your ASIO device may hang or fail to work");
|
||||
} else {
|
||||
log_info(
|
||||
"iidx",
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <d3d9.h>
|
||||
#include <dxva2api.h>
|
||||
|
||||
+47
-46
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,552 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
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(map, (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
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
namespace games::popn {
|
||||
void bi3a_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
+128
-78
@@ -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,135 @@ 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"},
|
||||
{"Pika", "Speakers R"},
|
||||
{"Pika", "Speakers G"},
|
||||
{"Pika", "Speakers B"},
|
||||
{"Pika", "Monitor R"},
|
||||
{"Pika", "Monitor G"},
|
||||
{"Pika", "Monitor B"},
|
||||
{"Pika", "Control Panel R"},
|
||||
{"Pika", "Control Panel G"},
|
||||
{"Pika", "Control Panel B"},
|
||||
{"Pika", "Cabinet R"},
|
||||
{"Pika", "Cabinet G"},
|
||||
{"Pika", "Cabinet B"},
|
||||
});
|
||||
}
|
||||
|
||||
return lights;
|
||||
}
|
||||
|
||||
+62
-11
@@ -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,58 @@ 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,
|
||||
Speaker_R,
|
||||
Speaker_G,
|
||||
Speaker_B,
|
||||
Monitor_R,
|
||||
Monitor_G,
|
||||
Monitor_B,
|
||||
ControlPanel_R,
|
||||
ControlPanel_G,
|
||||
ControlPanel_B,
|
||||
Cabinet_R,
|
||||
Cabinet_G,
|
||||
Cabinet_B,
|
||||
} popn_lights_t;
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
@@ -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,327 @@
|
||||
#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/nativetouchhook.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
|
||||
namespace games::popn {
|
||||
|
||||
bool SHOW_PIKA_MONITOR_WARNING = false;
|
||||
bool NATIVE_TOUCH = 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-0x7F - 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] button lights: these are button lights and handled specially
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"Invalid"
|
||||
},
|
||||
{
|
||||
// [1] red pop-kun
|
||||
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,
|
||||
0xFF,
|
||||
"Red Pop-Kun"
|
||||
},
|
||||
{
|
||||
// [2] blue 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,
|
||||
0xFF,
|
||||
"Blue Pop-Kun"
|
||||
},
|
||||
{
|
||||
// [3] top LED
|
||||
14, // 14*3; game reports 44 but make it 42
|
||||
Lights::popn_lights_t::TopLED_R,
|
||||
Lights::popn_lights_t::TopLED_G,
|
||||
Lights::popn_lights_t::TopLED_B,
|
||||
0xFF,
|
||||
"Top"
|
||||
},
|
||||
{
|
||||
// [4] speakers
|
||||
13, // 13*3; game reports 40 but make it 39
|
||||
Lights::popn_lights_t::Speaker_G,
|
||||
Lights::popn_lights_t::Speaker_R,
|
||||
Lights::popn_lights_t::Speaker_B,
|
||||
0x7F,
|
||||
"Speakers"
|
||||
},
|
||||
{
|
||||
// [5] monitor
|
||||
6,
|
||||
Lights::popn_lights_t::Monitor_G,
|
||||
Lights::popn_lights_t::Monitor_R,
|
||||
Lights::popn_lights_t::Monitor_B,
|
||||
0x7F,
|
||||
"Monitor"
|
||||
},
|
||||
{
|
||||
// [6] control panel
|
||||
9, // 9*3; game reports 28
|
||||
Lights::popn_lights_t::ControlPanel_G,
|
||||
Lights::popn_lights_t::ControlPanel_R,
|
||||
Lights::popn_lights_t::ControlPanel_B,
|
||||
0x7F,
|
||||
"Control Panel"
|
||||
},
|
||||
{
|
||||
// [7] Cabinet
|
||||
7, // 7*3; game reports 22
|
||||
Lights::popn_lights_t::Cabinet_G,
|
||||
Lights::popn_lights_t::Cabinet_R,
|
||||
Lights::popn_lights_t::Cabinet_B,
|
||||
0x7F,
|
||||
"Cabinet"
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
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 +542,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 +566,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 +576,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 +643,91 @@ 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) {
|
||||
if (NATIVE_TOUCH) {
|
||||
nativetouchhook::hook(avs::game::DLL_INSTANCE);
|
||||
} else {
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
sysutils::hook_EnumDisplayDevicesA();
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
#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 = 8;
|
||||
extern tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL];
|
||||
#endif
|
||||
|
||||
extern bool SHOW_PIKA_MONITOR_WARNING;
|
||||
extern bool NATIVE_TOUCH;
|
||||
|
||||
class POPNGame : public games::Game {
|
||||
public:
|
||||
POPNGame();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,18 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
||||
}
|
||||
}
|
||||
|
||||
// FTT
|
||||
if (avs::game::is_model("MMD")) {
|
||||
// set window name
|
||||
if (!lpWindowName) {
|
||||
lpWindowName = L"Future TomTom";
|
||||
}
|
||||
}
|
||||
|
||||
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 +560,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 +683,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);
|
||||
}
|
||||
@@ -976,6 +932,9 @@ void graphics_hook_subscreen_window(HWND hWnd) {
|
||||
if (GRAPHICS_WSUB_ALWAYS_ON_TOP) {
|
||||
graphics_update_z_order(hWnd, true);
|
||||
}
|
||||
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
|
||||
graphics_set_corner_preference(hWnd, true);
|
||||
}
|
||||
}
|
||||
|
||||
void graphics_screens_register(int screen) {
|
||||
@@ -1149,3 +1108,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);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d9.h>
|
||||
#if !SPICE_XP
|
||||
#include <dwmapi.h>
|
||||
#endif
|
||||
|
||||
#include "external/toojpeg/toojpeg.h"
|
||||
|
||||
@@ -15,6 +18,7 @@ enum graphics_orientation {
|
||||
ORIENTATION_CW = 0,
|
||||
ORIENTATION_CCW = 1,
|
||||
ORIENTATION_NORMAL = 2,
|
||||
ORIENTATION_FLIPPED = 3
|
||||
};
|
||||
|
||||
enum graphics_dx9on12_state {
|
||||
@@ -29,7 +33,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;
|
||||
@@ -50,6 +53,7 @@ extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
|
||||
extern std::optional<std::string> GRAPHICS_WINDOW_POS;
|
||||
extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
|
||||
extern bool GRAPHICS_WINDOW_BACKBUFFER_SCALE;
|
||||
extern bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS;
|
||||
extern std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
extern bool GRAPHICS_IIDX_WSUB;
|
||||
@@ -63,9 +67,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,11 +105,16 @@ 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_set_corner_preference(HWND hWnd, bool disable);
|
||||
void graphics_move_resize_window(HWND hWnd);
|
||||
bool graphics_window_options_breaks_game();
|
||||
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);
|
||||
@@ -24,6 +24,7 @@ std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_WINDOW_SIZE;
|
||||
std::optional<std::string> GRAPHICS_WINDOW_POS;
|
||||
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
|
||||
bool GRAPHICS_WINDOW_BACKBUFFER_SCALE = false;
|
||||
bool GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = false;
|
||||
std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
|
||||
@@ -121,18 +122,21 @@ 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));
|
||||
graphics_update_z_order(hWnd, true);
|
||||
}
|
||||
if (cfg::SCREENRESIZE->window_disable_round_corners) {
|
||||
log_info("graphics-windowed", "[{}] disabling rounded corners", fmt::ptr(hWnd));
|
||||
graphics_set_corner_preference(hWnd, true);
|
||||
}
|
||||
|
||||
// ensure spicetouch coordinates are initialized
|
||||
update_spicetouch_window_dimensions(hWnd);
|
||||
@@ -182,6 +186,10 @@ void graphics_load_windowed_parameters() {
|
||||
cfg::SCREENRESIZE->window_always_on_top = true;
|
||||
}
|
||||
|
||||
if (GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS) {
|
||||
cfg::SCREENRESIZE->window_disable_round_corners = true;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
|
||||
}
|
||||
|
||||
@@ -385,11 +393,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;
|
||||
}
|
||||
|
||||
@@ -470,6 +478,27 @@ void graphics_update_z_order(HWND hWnd, bool always_on_top) {
|
||||
log_debug("graphics-windowed", "graphics_update_z_order returned");
|
||||
}
|
||||
|
||||
void graphics_set_corner_preference(HWND hWnd, bool disable) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if !SPICE_XP
|
||||
log_debug("graphics-windowed", "graphics_set_corner_preference called");
|
||||
|
||||
DWM_WINDOW_CORNER_PREFERENCE corner_preference = disable ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
|
||||
DwmSetWindowAttribute(
|
||||
hWnd,
|
||||
DWMWA_WINDOW_CORNER_PREFERENCE,
|
||||
&corner_preference, sizeof(corner_preference));
|
||||
|
||||
log_debug("graphics-windowed", "graphics_set_corner_preference returned");
|
||||
#else
|
||||
// don't link against DwmSetWindowAttribute on XP
|
||||
log_debug("graphics-windowed", "graphics_set_corner_preference called; ignoring");
|
||||
#endif
|
||||
}
|
||||
|
||||
void graphics_move_resize_window(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
@@ -503,8 +532,15 @@ bool graphics_window_decoration_change_crashes_game() {
|
||||
static std::once_flag flag;
|
||||
static bool result = false;
|
||||
std::call_once(flag, []() {
|
||||
// ddr crashes when frame style changes
|
||||
result = avs::game::is_model("MDX");
|
||||
// ddr and ftt crash when frame style changes
|
||||
if (avs::game::is_model("MDX") || avs::game::is_model("MMD")) {
|
||||
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 +596,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,16 @@ 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::PopnNativeTouch].value_bool()) {
|
||||
games::popn::NATIVE_TOUCH = true;
|
||||
}
|
||||
if (options[launcher::Options::LoadMetalGearArcadeModule].value_bool()) {
|
||||
attach_mga = true;
|
||||
}
|
||||
@@ -980,7 +996,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()) {
|
||||
@@ -1104,6 +1120,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool();
|
||||
GRAPHICS_WINDOW_BACKBUFFER_SCALE = options[launcher::Options::WindowForceScaling].value_bool();
|
||||
GRAPHICS_WINDOW_DISABLE_ROUNDED_CORNERS = options[launcher::Options::WindowDisableRoundedCorners].value_bool();
|
||||
|
||||
// IIDX/SDVX Windowed Subscreen
|
||||
if (options[launcher::Options::spice2x_IIDXWindowedSubscreenSize].is_active()) {
|
||||
@@ -1124,6 +1141,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 +1244,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 +1311,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 +1331,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 +1387,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 +1402,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 +1768,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;
|
||||
}
|
||||
|
||||
@@ -1799,9 +1835,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
attach_ftt = true;
|
||||
|
||||
// the game is windowed by default unless we set the env
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
GRAPHICS_WINDOWED = false;
|
||||
} else {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
SetEnvironmentVariable("DAMAC_VIEWER_FULLSCREEN", "0");
|
||||
}
|
||||
|
||||
@@ -2133,6 +2167,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 +2257,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 +2538,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 +2609,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 +2625,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())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)",
|
||||
@@ -31,6 +32,7 @@ static const std::vector<std::string> CATEGORY_ORDER_BASIC = {
|
||||
|
||||
static const std::vector<std::string> CATEGORY_ORDER_ADVANCED = {
|
||||
"Game Options (Advanced)",
|
||||
"Game Options (Peripherals)",
|
||||
"Overlay",
|
||||
"Network (Advanced)",
|
||||
"Performance",
|
||||
@@ -206,9 +208,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 +233,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
|
||||
@@ -499,7 +514,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Flip the camera order.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXDisableCameras
|
||||
@@ -509,7 +524,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXCabCamAccess
|
||||
@@ -519,7 +534,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.",
|
||||
.type = OptionType::Enum,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
.elements = {
|
||||
{"auto", ""},
|
||||
{"off", ""},
|
||||
@@ -535,7 +550,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Experimental camera support for IIDX 27+ via texture hooking. Requires camera(s) that support YUY2 or NV12 encoding.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXCamHookRatio
|
||||
@@ -547,7 +562,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Enum,
|
||||
.hidden = true,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
.elements = {
|
||||
{"43", "4:3"},
|
||||
{"169", "16:9"},
|
||||
@@ -565,7 +580,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXCamHookTopId
|
||||
@@ -580,7 +595,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "vid_1234&pid_5678",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// IIDXCamHookFrontId
|
||||
@@ -595,7 +610,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "vid_90ab&pid_cdef",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "IIDX Sound Output Device",
|
||||
@@ -758,11 +773,11 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX Force 720p",
|
||||
.title = "SDVX Vivid Wave Force 720p Window (DEPRECATED - use -windowsize instead)",
|
||||
.name = "sdvx720",
|
||||
.desc = "Force Sound Voltex 720p display mode, used by older games. "
|
||||
"For newer games, use window resize function instead.",
|
||||
.desc = "Old & deprecated option for launching Vivid Wave in 720p when using windowed mode.",
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
@@ -772,7 +787,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Enable Sound Voltex printer emulation.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// SDVXPrinterOutputPath
|
||||
@@ -781,7 +796,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Path to folder where images will be stored.",
|
||||
.type = OptionType::Text,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
.picker = OptionPickerType::DirectoryPath,
|
||||
},
|
||||
{
|
||||
@@ -790,7 +805,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Clean up saved images in the output directory on startup.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX Printer Output Overwrite",
|
||||
@@ -798,7 +813,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Always overwrite the same file in output directory.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// SDVXPrinterOutputFormat
|
||||
@@ -808,7 +823,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "(png/bmp/tga/jpg)",
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX Printer JPG Quality",
|
||||
@@ -817,7 +832,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Integer,
|
||||
.setting_name = "(0-100)",
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX Disable Cameras (DEPRECATED - no longer needed)",
|
||||
@@ -828,7 +843,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "SDVX FS Subscreen Native Touch Handling",
|
||||
@@ -914,6 +929,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 +989,38 @@ 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,
|
||||
},
|
||||
{
|
||||
// PopnNativeTouch
|
||||
.title = "Pop'n Music PikaPika Native Touch Handling",
|
||||
.name = "popnnativetouch",
|
||||
.desc = "Disables touch hooks and lets the game access a touch screen directly. "
|
||||
"Requires a touch screen to be connected as a secondary monitor. "
|
||||
"Touch input must be routed to the primary screen via Windows Tablet PC settings. "
|
||||
"Enable this when you get duplicate touch inputs from an actual touch screen.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Pop'n Music",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
.title = "Force Load HELLO! Pop'n Music Module",
|
||||
.name = "hpm",
|
||||
@@ -1847,17 +1905,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",
|
||||
@@ -2170,6 +2231,14 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Bool,
|
||||
.category = "Graphics (Windowed)",
|
||||
},
|
||||
{
|
||||
// WindowDisableRoundedCorners
|
||||
.title = "Disable Round Window Corners",
|
||||
.name = "windownoroundcorners",
|
||||
.desc = "Windows 11 and above only: Disables rounded corners on the game window(s).",
|
||||
.type = OptionType::Bool,
|
||||
.category = "Graphics (Windowed)",
|
||||
},
|
||||
{
|
||||
// spice2x_IIDXWindowedSubscreenSize
|
||||
.title = "IIDX Windowed Subscreen Size",
|
||||
@@ -2299,7 +2368,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"Note: this is NOT for the motion camera - you still need a real RealSense camera for that!",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "DANCERUSH",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// spice2x_IIDXNativeTouch
|
||||
@@ -2326,6 +2395,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",
|
||||
@@ -2622,7 +2702,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Allow game to access camera; camera must be compatible with game.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// LovePlusPrinterOutputPath
|
||||
@@ -2631,7 +2711,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Path to folder where images will be stored.",
|
||||
.type = OptionType::Text,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
.picker = OptionPickerType::DirectoryPath,
|
||||
},
|
||||
{
|
||||
@@ -2640,7 +2720,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Clean up saved images in the output directory on startup.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "LovePlus Printer Output Overwrite",
|
||||
@@ -2648,7 +2728,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Always overwrite the same file in output directory.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// LovePlusPrinterOutputFormat
|
||||
@@ -2658,7 +2738,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "(png/bmp/tga/jpg)",
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
.title = "LovePlus Printer JPG Quality",
|
||||
@@ -2667,7 +2747,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Integer,
|
||||
.setting_name = "(0-100)",
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.category = "Game Options (Peripherals)",
|
||||
},
|
||||
{
|
||||
// OptionConflictResolution
|
||||
@@ -2688,7 +2768,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"option to bypass camera error during boot, allowing you to try out the game.",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Otoca D'or",
|
||||
.category = "Game Options",
|
||||
.category = "Game Options (Peripherals)",
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ namespace launcher {
|
||||
ExecuteScript,
|
||||
CaptureCursor,
|
||||
ShowCursor,
|
||||
DisplayAdapter,
|
||||
PrimaryMonitor,
|
||||
DXDisplayAdapter,
|
||||
GraphicsForceSingleAdapter,
|
||||
GraphicsForceRefresh,
|
||||
FullscreenResolution,
|
||||
@@ -91,12 +92,16 @@ namespace launcher {
|
||||
SDVXDigitalKnobSocd,
|
||||
spice2x_SDVXAsioDriver,
|
||||
spice2x_SDVXSubPos,
|
||||
SDVXSubMonitorOverride,
|
||||
LoadDDRModule,
|
||||
DDR43Mode,
|
||||
DDRSkipCodecRegisteration,
|
||||
LoadPopnMusicModule,
|
||||
PopnMusicForceHDMode,
|
||||
PopnMusicForceSDMode,
|
||||
PopnNoSub,
|
||||
PopnSubMonitorOverride,
|
||||
PopnNativeTouch,
|
||||
LoadHelloPopnMusicModule,
|
||||
LoadGitaDoraModule,
|
||||
GitaDoraTwoChannelAudio,
|
||||
@@ -228,6 +233,7 @@ namespace launcher {
|
||||
spice2x_WindowPosition,
|
||||
spice2x_WindowAlwaysOnTop,
|
||||
WindowForceScaling,
|
||||
WindowDisableRoundedCorners,
|
||||
spice2x_IIDXWindowedSubscreenSize,
|
||||
spice2x_IIDXWindowedSubscreenPosition,
|
||||
IIDXWindowedSubscreenBorderless,
|
||||
@@ -242,6 +248,7 @@ namespace launcher {
|
||||
DRSRGBCameraHook,
|
||||
spice2x_IIDXNativeTouch,
|
||||
spice2x_IIDXNoSub,
|
||||
IIDXSubMonitorOverride,
|
||||
spice2x_IIDXEmulateSubscreenKeypadTouch,
|
||||
spice2x_AutoCard,
|
||||
spice2x_LowLatencySharedAudio,
|
||||
|
||||
@@ -86,13 +86,12 @@ namespace launcher {
|
||||
if (LAUNCHER_ARGC > 0) {
|
||||
|
||||
// build cmd line
|
||||
std::stringstream cmd_line;
|
||||
cmd_line << "START \"\" ";
|
||||
std::string cmd_line = "START \"\" ";
|
||||
for (int i = 0; i < LAUNCHER_ARGC; i++)
|
||||
cmd_line << " \"" << LAUNCHER_ARGV[i] << "\"";
|
||||
cmd_line += " \"" + std::string(LAUNCHER_ARGV[i]) + "\"";
|
||||
|
||||
// run command
|
||||
system(cmd_line.str().c_str());
|
||||
system(cmd_line.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include "overlay/window.h"
|
||||
#include <strmif.h>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "external/imgui/misc/cpp/imgui_stdlib.h"
|
||||
#include "games/io.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "avs/core.h"
|
||||
#include "avs/ea3.h"
|
||||
#include "avs/game.h"
|
||||
@@ -34,6 +35,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 +60,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 +115,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);
|
||||
@@ -271,18 +284,8 @@ namespace overlay::windows {
|
||||
|
||||
// keypad buttons
|
||||
ImGui::TextUnformatted("");
|
||||
if (this->games_selected_name == "Beatmania IIDX") {
|
||||
ImGui::Indent(INDENT);
|
||||
ImGui::TextColored(
|
||||
ImVec4(1, 0.5f, 0.5f, 1.f),
|
||||
"WARNING: Lightning Model (TDJ) I/O will ignore the keypad!");
|
||||
ImGui::TextWrapped(
|
||||
"Use Toggle Sub Screen button to show the overlay and use your mouse, "
|
||||
"connect using SpiceCompanion app, or connect a touch screen to enter "
|
||||
"the PIN.");
|
||||
ImGui::Unindent(INDENT);
|
||||
ImGui::TextUnformatted("");
|
||||
}
|
||||
this->build_keypad_warning();
|
||||
|
||||
auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
|
||||
auto keypad_count = eamuse_get_game_keypads_name();
|
||||
if (keypad_count == 1) {
|
||||
@@ -513,7 +516,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);
|
||||
@@ -590,6 +593,34 @@ namespace overlay::windows {
|
||||
}
|
||||
}
|
||||
|
||||
void Config::build_keypad_warning() {
|
||||
// not checking the -iidxtdj option here, we could do that in the future
|
||||
const bool is_tdj = this->games_selected_name == "Beatmania IIDX";
|
||||
|
||||
// not using games::popn::is_pikapika_model() here since that always returns false on 32-bit
|
||||
const bool is_popn = this->games_selected_name == "Pop'n Music" && avs::game::SPEC[0] == 'D';
|
||||
if (!is_tdj && !is_popn) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::Indent(INDENT);
|
||||
if (is_tdj) {
|
||||
ImGui::TextColored(
|
||||
ImVec4(1, 0.5f, 0.5f, 1.f),
|
||||
"WARNING: Lightning Model (TDJ) I/O will ignore keypad number input!");
|
||||
} else if (is_popn) {
|
||||
ImGui::TextColored(
|
||||
ImVec4(1, 0.5f, 0.5f, 1.f),
|
||||
"WARNING: PikaPika Pop-Kun model will ignore keypad number input!");
|
||||
}
|
||||
ImGui::TextWrapped(
|
||||
"Use Toggle Sub Screen button (Overlay tab) to show the overlay and use your mouse, "
|
||||
"connect using SpiceCompanion app, or connect a touch screen to enter "
|
||||
"the PIN.");
|
||||
ImGui::Unindent(INDENT);
|
||||
ImGui::TextUnformatted("");
|
||||
}
|
||||
|
||||
void Config::build_buttons(const std::string &name, std::vector<Button> *buttons, int min, int max) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "%s Buttons", name.c_str());
|
||||
@@ -631,14 +662,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 +692,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 +818,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 +932,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 +953,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 +1018,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 +1099,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 +1453,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 +1575,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 +1675,7 @@ namespace overlay::windows {
|
||||
if (ImGui::Selectable("Empty (Naive)", button->isNaive())) {
|
||||
button->setDeviceIdentifier("");
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
if (button->isNaive()) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
@@ -1711,7 +1759,7 @@ namespace overlay::windows {
|
||||
button->setVelocityThreshold(0);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
type = button->getAnalogType();
|
||||
|
||||
int midi_channel = 0;
|
||||
@@ -1758,7 +1806,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 +1890,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 +1958,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 +2211,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 +2246,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 +2262,7 @@ namespace overlay::windows {
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(device->name.c_str());
|
||||
}
|
||||
|
||||
|
||||
switch (device->type) {
|
||||
case rawinput::MOUSE: {
|
||||
|
||||
@@ -2282,12 +2332,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 +2375,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 +2486,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 +2783,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 +2842,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 +2850,7 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
render_section_header(current_section.empty() ? "Uncategorized" : current_section);
|
||||
|
||||
|
||||
table_begin = begin_lights_table();
|
||||
if (!table_begin) {
|
||||
break;
|
||||
@@ -2813,7 +2872,7 @@ namespace overlay::windows {
|
||||
alt_index++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
if (table_begin) {
|
||||
ImGui::EndTable();
|
||||
@@ -2834,7 +2893,7 @@ namespace overlay::windows {
|
||||
|
||||
// progress bar
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
|
||||
// light name
|
||||
if (alt_index == 0) {
|
||||
ImGui::ProgressBar(light_state, ImVec2(32.f, 0));
|
||||
@@ -2865,7 +2924,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 +2950,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 +3008,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 +3016,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 +3104,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 +3121,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 +3203,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 +3658,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 +3824,7 @@ namespace overlay::windows {
|
||||
|
||||
return detected_controller;
|
||||
}
|
||||
|
||||
|
||||
void Config::build_cards() {
|
||||
|
||||
constexpr float TEXT_INPUT_WIDTH = 240.f;
|
||||
@@ -3831,16 +3904,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 +3938,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 +4131,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 +4154,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 +4314,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 +4365,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 +4411,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 +4548,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 +4579,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 +4605,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 +4632,7 @@ namespace overlay::windows {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// filter
|
||||
if (filter != nullptr) {
|
||||
if (filter->empty()) {
|
||||
@@ -4483,7 +4648,7 @@ namespace overlay::windows {
|
||||
}
|
||||
|
||||
options_count += 1;
|
||||
|
||||
|
||||
// list entry
|
||||
ImGui::PushID(&option);
|
||||
ImGui::TableNextRow();
|
||||
@@ -4495,10 +4660,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 +4843,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 +4914,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 +4936,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 +4944,7 @@ namespace overlay::windows {
|
||||
|
||||
// row hover detection (invisible selectable that spans entire row)
|
||||
ImGui::InvisibleTableRowSelectable();
|
||||
|
||||
|
||||
// next item
|
||||
ImGui::PopID();
|
||||
}
|
||||
@@ -4787,12 +4952,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 +4980,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 +5001,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 +5109,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 +5776,7 @@ namespace overlay::windows {
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
|
||||
if (!apply_buttons && !apply_keypads && !apply_analogs && !apply_lights) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -192,13 +193,15 @@ namespace overlay::windows {
|
||||
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
|
||||
void build_about();
|
||||
void build_launcher();
|
||||
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
|
||||
void build_keypad_warning();
|
||||
|
||||
void launch_shell(LPCSTR app, LPCSTR file=nullptr);
|
||||
|
||||
void build_menu(int *game_selected);
|
||||
void shutdown_system(bool force, bool reboot_instead);
|
||||
|
||||
|
||||
void set_alternating_row_colors(const int row_index);
|
||||
|
||||
|
||||
bool validate_ea_card(char card_number[16]);
|
||||
|
||||
public:
|
||||
|
||||
@@ -628,13 +628,12 @@ namespace overlay::windows {
|
||||
ImGui::Text("Wheel: %ld", mouse->pos_wheel);
|
||||
|
||||
// keys
|
||||
std::stringstream keys;
|
||||
keys << "[";
|
||||
std::string keys = "[";
|
||||
for (auto key : mouse->key_states) {
|
||||
keys << (key ? "1," : "0,");
|
||||
keys += (key ? "1," : "0,");
|
||||
}
|
||||
keys << "]";
|
||||
ImGui::Text("Keys: %s", keys.str().c_str());
|
||||
keys += "]";
|
||||
ImGui::Text("Keys: %s", keys.c_str());
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -643,15 +642,14 @@ namespace overlay::windows {
|
||||
ImGui::Text("Type: Keyboard");
|
||||
|
||||
// keys
|
||||
std::stringstream keys;
|
||||
keys << "[";
|
||||
std::string keys = "[";
|
||||
for (size_t i = 0; i < std::size(keyboard->key_states); i++) {
|
||||
if (keyboard->key_states[i]) {
|
||||
keys << i << ",";
|
||||
keys += std::to_string(i) + ",";
|
||||
}
|
||||
}
|
||||
keys << "]";
|
||||
ImGui::Text("Keys: %s", keys.str().c_str());
|
||||
keys += "]";
|
||||
ImGui::Text("Keys: %s", keys.c_str());
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -732,6 +730,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 +741,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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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).";
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace rawinput {
|
||||
SEXTET_OUTPUT,
|
||||
PIUIO_DEVICE,
|
||||
SMX_STAGE,
|
||||
SMX_DEDICAB
|
||||
SMX_DEDICAB,
|
||||
XINPUT_GAMEPAD,
|
||||
};
|
||||
|
||||
enum MouseKeys {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -192,8 +192,10 @@ namespace rawinput::touch {
|
||||
contact_count = std::max(contact_count, (size_t) hid->value_states_raw[index]);
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchscreen-packet-reporting-modes
|
||||
// hybrid mode devices will report a contact count of 0 for subsequent reports that are
|
||||
// part of the same initial frame
|
||||
// most laptops and touchscreens are like this - they have the contact count set to 5 but can actually report up to 10 fingers
|
||||
if (contact_count > 0) {
|
||||
if (contact_count > touch_report_count) {
|
||||
touch.remaining_contact_count = contact_count - touch_report_count;
|
||||
@@ -216,6 +218,12 @@ namespace rawinput::touch {
|
||||
touch_points.reserve(touch.elements_x.size());
|
||||
for (size_t i = 0; i < touch.elements_x.size(); i++) {
|
||||
|
||||
// if there are no more touch events to handle, exit out early
|
||||
if (touch_report_count == 0) {
|
||||
break;
|
||||
}
|
||||
touch_report_count--;
|
||||
|
||||
// build touch point
|
||||
HIDTouchPoint hid_tp{};
|
||||
auto pos_x = hid->value_states[touch.elements_x[i]];
|
||||
@@ -247,17 +255,6 @@ namespace rawinput::touch {
|
||||
hid_tp.y = 1.f - hid_tp.y;
|
||||
}
|
||||
|
||||
// check if this touch point should be considered valid
|
||||
//
|
||||
// If "Contact count" reports there are no touch reports remaining and the X and Y
|
||||
// coordinates of this touch point are zero, then this report element should be
|
||||
// skipped.
|
||||
if (touch_report_count == 0 && hid_tp.x == 0.f && hid_tp.y == 0.f) {
|
||||
continue;
|
||||
} else if (touch_report_count > 0) {
|
||||
touch_report_count--;
|
||||
}
|
||||
|
||||
// generate ID (hopefully unique)
|
||||
hid_tp.id = (DWORD) hid->value_states_raw[touch.elements_contact_identifier[i]];
|
||||
hid_tp.id += (DWORD) (0xFFFFFF + device->id * 512);
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -219,20 +219,21 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
|
||||
strreplace(pattern_str, "??", "00");
|
||||
auto pattern_bin = std::make_unique<uint8_t[]>(signature.length() / 2);
|
||||
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// build signature mask
|
||||
std::ostringstream signature_mask;
|
||||
std::string signature_mask;
|
||||
signature_mask.reserve(signature.size() / 2);
|
||||
for (size_t i = 0; i < signature.length(); i += 2) {
|
||||
if (signature[i] == '?') {
|
||||
if (signature[i + 1] == '?') {
|
||||
signature_mask << '?';
|
||||
signature_mask += '?';
|
||||
} else {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
signature_mask << 'X';
|
||||
signature_mask += 'X';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,20 +242,21 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
|
||||
strreplace(replace_data_str, "??", "00");
|
||||
auto replace_data_bin = std::make_unique<uint8_t[]>(replacement.length() / 2);
|
||||
if (!hex2bin(replace_data_str.c_str(), replace_data_bin.get())) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// build replace mask
|
||||
std::ostringstream replace_mask;
|
||||
std::string replace_mask;
|
||||
replace_mask.reserve(replacement.size() / 2);
|
||||
for (size_t i = 0; i < replacement.length(); i += 2) {
|
||||
if (replacement[i] == '?') {
|
||||
if (replacement[i + 1] == '?') {
|
||||
replace_mask << '?';
|
||||
replace_mask += '?';
|
||||
} else {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
replace_mask << 'X';
|
||||
replace_mask += 'X';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,11 +264,11 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
|
||||
return replace_pattern(
|
||||
module,
|
||||
pattern_bin.get(),
|
||||
signature_mask.str().c_str(),
|
||||
signature_mask.c_str(),
|
||||
offset,
|
||||
usage,
|
||||
replace_data_bin.get(),
|
||||
replace_mask.str().c_str()
|
||||
replace_mask.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+306
-19
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace tapeledutils {
|
||||
}
|
||||
|
||||
// for bi2x-style byte array of all colors and LEDs at once
|
||||
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size) {
|
||||
static rgb_float3_t pick_color_from_led_tape_internal(uint8_t *data, size_t data_size, uint8_t divisor) {
|
||||
rgb_float3_t result = {0.f, 0.f, 0.f};
|
||||
if (TAPE_LED_ALGORITHM == TAPE_LED_USE_AVERAGE) {
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace tapeledutils {
|
||||
}
|
||||
|
||||
// normalize
|
||||
const float avg_mult = 1.f / (data_size * 255);
|
||||
const float avg_mult = 1.f / (data_size * divisor);
|
||||
result.r = avg_ri * avg_mult;
|
||||
result.g = avg_gi * avg_mult;
|
||||
result.b = avg_bi * avg_mult;
|
||||
@@ -50,7 +50,7 @@ namespace tapeledutils {
|
||||
}
|
||||
|
||||
// normalize
|
||||
const float single_mult = 1.f / 255;
|
||||
const float single_mult = 1.f / divisor;
|
||||
result.r = color[0] * single_mult;
|
||||
result.g = color[1] * single_mult;
|
||||
result.b = color[2] * single_mult;
|
||||
@@ -58,6 +58,15 @@ namespace tapeledutils {
|
||||
return result;
|
||||
}
|
||||
|
||||
rgb_float3_t pick_color_from_led_tape(tape_led &light, uint8_t *data, size_t data_size) {
|
||||
const auto max_value = (light.max_value > 0) ? light.max_value : 0xFF;
|
||||
return pick_color_from_led_tape_internal(data, data_size, max_value);
|
||||
}
|
||||
|
||||
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size) {
|
||||
return pick_color_from_led_tape_internal(data, data_size, 0xFF);
|
||||
}
|
||||
|
||||
// for bi2a-style that calls for each individual LED
|
||||
size_t get_led_index_using_avg_algo(size_t data_size) {
|
||||
size_t index_to_use;
|
||||
|
||||
@@ -25,13 +25,33 @@ namespace tapeledutils {
|
||||
struct tape_led {
|
||||
std::vector<rgb_float3_t> data;
|
||||
int index_r, index_g, index_b; // Averaged RGB light output indexes
|
||||
uint8_t max_value;
|
||||
std::string lightName;
|
||||
|
||||
tape_led(size_t data_size, int index_r, int index_g, int index_b, std::string lightName)
|
||||
: data(std::vector<rgb_float3_t>(data_size)), index_r(index_r), index_g(index_g), index_b(index_b), lightName(std::move(lightName)) {}
|
||||
tape_led(
|
||||
size_t data_size,
|
||||
int index_r, int index_g, int index_b,
|
||||
std::string lightName)
|
||||
: data(
|
||||
std::vector<rgb_float3_t>(data_size)),
|
||||
index_r(index_r), index_g(index_g), index_b(index_b),
|
||||
max_value(0),
|
||||
lightName(std::move(lightName)) {}
|
||||
|
||||
tape_led(
|
||||
size_t data_size,
|
||||
int index_r, int index_g, int index_b,
|
||||
uint8_t max_value,
|
||||
std::string lightName)
|
||||
: data(
|
||||
std::vector<rgb_float3_t>(data_size)),
|
||||
index_r(index_r), index_g(index_g), index_b(index_b),
|
||||
max_value(max_value),
|
||||
lightName(std::move(lightName)) {}
|
||||
};
|
||||
|
||||
bool is_enabled();
|
||||
rgb_float3_t pick_color_from_led_tape(tape_led &light, uint8_t *data, size_t data_size);
|
||||
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size);
|
||||
size_t get_led_index_using_avg_algo(size_t data_size);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user