mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f3d887ebcf | |||
| 336500c8ae | |||
| ee3fa58bfa | |||
| a4c3eddc2f | |||
| e0f6cb2cbd | |||
| 1ad6a9412e | |||
| 5af3e0d494 | |||
| b1ee828457 | |||
| 0d60649715 | |||
| 220b27159b | |||
| b06a0a7d47 | |||
| d66af8e16b | |||
| ad366ecdd0 | |||
| 22cb4d689c | |||
| b38ea463aa | |||
| 056d1b2d90 | |||
| 37693916d9 | |||
| 889e8b43eb | |||
| 9db2db2340 | |||
| 1bb612f3c9 | |||
| e886ab77af | |||
| fbb3c1d4c3 | |||
| 73a69b2e8f | |||
| 63a0acac24 | |||
| 75ba49ff4a | |||
| b31ddb1ffc | |||
| 458a1495c8 | |||
| eb029faf2e | |||
| d65eb6ed4f | |||
| 97ec2e5d37 | |||
| 97d65b137a | |||
| a9f0e86aa3 | |||
| 88679b7cb2 | |||
| 9ec62a61ac | |||
| 44befb7e9a | |||
| be388f7b49 | |||
| 34ef034345 | |||
| ba4623b009 | |||
| a0a7b86508 | |||
| 6d7927927c | |||
| 837c8a46d6 | |||
| 22aeb64ff9 | |||
| e4b08064b7 | |||
| 1e6b4c16cc | |||
| d6eb8f7125 | |||
| dc6850e479 | |||
| 2d623e179b | |||
| 884d665c1c | |||
| 2842fe7ec6 | |||
| 954e6022d9 | |||
| 955c50a9f3 | |||
| dc82c980b1 | |||
| 408ed17521 | |||
| f4050e9adb | |||
| badae01357 | |||
| 38d9939eb7 | |||
| f5a3caf536 | |||
| 027445afb1 | |||
| 63a3900b79 | |||
| f542a78347 | |||
| 4d32dde83e | |||
| 927170ce73 | |||
| 875a0f0765 | |||
| 3ad88ef15c | |||
| 7a6cadb176 | |||
| 4feca23240 | |||
| 2e52bebd3d | |||
| aa218d5be7 | |||
| 94f009991a | |||
| c3b9ce3fdc | |||
| 1517cf9a7c | |||
| f0f46296f2 | |||
| 1d1981fcde | |||
| 2f538fe2e4 | |||
| 8e01b2e5c8 | |||
| cd14ca800e | |||
| cb59fe14e9 | |||
| 9fd9290cd3 | |||
| 1dd99aac74 | |||
| 9e376f373e | |||
| d4e51b77d1 | |||
| 61181836b0 | |||
| 4e26c8afa5 | |||
| 7684028f99 | |||
| 1d6be8fe4b | |||
| c70cf40af3 | |||
| 5715d4e605 | |||
| 821a705a7d | |||
| 86f2a1fd39 | |||
| 86f218329a | |||
| 66094de38a | |||
| c00cff2afd | |||
| b2cc8d404c | |||
| a0a04ab62f | |||
| a3a59e689e | |||
| a6f29d6a5a | |||
| 9be618d791 | |||
| 6bf7ae9074 | |||
| e3d63c65c1 | |||
| 5d2a9fa1cf | |||
| 0078b72b03 | |||
| b1ee37a066 | |||
| ebf6382e7f | |||
| 8e23ef224a | |||
| 6706ffc321 | |||
| e755b06e35 | |||
| 9ef22712c0 | |||
| 7b903d35e2 | |||
| 7f281a50c5 | |||
| 1354d63b85 | |||
| e745cdcac8 | |||
| bf7666939a | |||
| 7e89037db3 | |||
| 6ffaa77f58 | |||
| 7a1f46d1be | |||
| cf59ae5f89 | |||
| 9c06dd36a0 | |||
| d5d082ec37 | |||
| 17723d290a |
+11
-3
@@ -1,8 +1,7 @@
|
||||
## Contributing
|
||||
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
|
||||
|
||||
### New as of March 2025
|
||||
Pull requests in GitHub are open. You don't need to bother with patch file submissions anymore. To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
|
||||
To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
|
||||
|
||||
### Adding support for games
|
||||
|
||||
@@ -12,6 +11,14 @@ Pull requests in GitHub are open. You don't need to bother with patch file submi
|
||||
* For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan.
|
||||
* For new version of an already supported game: proceed with caution and use generally-accepted community guidelines.
|
||||
|
||||
### Forbidden "features" that will never be accepted
|
||||
|
||||
* Performing decryption of encrypted files
|
||||
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data
|
||||
* Any features that [phone home](https://en.wikipedia.org/wiki/Phoning_home)
|
||||
* This includes automatic updaters.
|
||||
* Exception: we have allowed the "patch import from URL" feature, but this has only been done under very careful considerations and plenty of warnings to the user.
|
||||
|
||||
### Avoiding regressions
|
||||
|
||||
The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions.
|
||||
@@ -22,7 +29,7 @@ Therefore, when making code changes, please be extremely careful about containin
|
||||
|
||||
#### Config file compatibility
|
||||
|
||||
Do not change the names of options, buttons binds, analogs, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
|
||||
Do not change the names of buttons binds, analogs, strings for command line parameters, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
|
||||
|
||||
### Code quality requirements
|
||||
|
||||
@@ -51,6 +58,7 @@ Watch out for legacy OS compatibility. Currently, the minimum support floor is W
|
||||
* Indents are four spaces.
|
||||
* Try to keep under 100 characters per line. This includes comments and string literals.
|
||||
* Avoid making unnecessary formatting or whitespace changes in a PR; e.g., watch out for text editor automatically stripping trailing whitespace. It pollutes the diff and slows down code review.
|
||||
* For signed/unsigned integer types, prefer `cstdint` types; e.g., use `int32_t`, `uint64_t` and not `int` and `unsigned long long`.
|
||||
* Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc.
|
||||
|
||||
OK:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
> Feel free to remove this section after you have read it.
|
||||
|
||||
## Link to GitHub Issue, if one exists
|
||||
## Link to GitHub Issue or related Pull Request, if one exists
|
||||
#0
|
||||
|
||||
## Description of change
|
||||
|
||||
@@ -109,6 +109,12 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
# RapidJSON does this
|
||||
add_compile_definitions(_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING)
|
||||
|
||||
add_compile_options("-Wno-nontrivial-memcall") # imgui, rapidjson
|
||||
add_compile_options("-Wno-deprecated-builtins") # robin_hood
|
||||
add_compile_options("-Wno-deprecated-declarations") # rapidjson
|
||||
add_compile_options("-Wno-inconsistent-missing-override") # headsocket
|
||||
add_compile_options("-Wno-microsoft-exception-spec") # gcc/clang have different noexcept specifiers on d3d9 COM interfaces
|
||||
|
||||
# warnings
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunknown-warning-option")
|
||||
|
||||
@@ -170,8 +176,8 @@ else()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
|
||||
|
||||
# file prefix map for relative working directory
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
|
||||
endif()
|
||||
|
||||
# release with debug info flags
|
||||
@@ -260,6 +266,7 @@ set_source_files_properties(cfg/manifest.rc PROPERTIES LANGUAGE RC)
|
||||
set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC)
|
||||
set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC)
|
||||
set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC)
|
||||
set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC)
|
||||
|
||||
# sources
|
||||
#########
|
||||
@@ -346,6 +353,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
cfg/configurator.cpp
|
||||
cfg/configurator_wnd.cpp
|
||||
cfg/screen_resize.cpp
|
||||
cfg/controller_presets.cpp
|
||||
|
||||
# easrv
|
||||
easrv/easrv.cpp
|
||||
@@ -441,6 +449,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/scotto/io.cpp
|
||||
games/drs/drs.cpp
|
||||
games/drs/io.cpp
|
||||
games/drs/rgb_cam.cpp
|
||||
games/we/we.cpp
|
||||
games/we/io.cpp
|
||||
games/we/touchpanel.cpp
|
||||
@@ -554,9 +563,11 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
overlay/windows/camera_control.cpp
|
||||
overlay/windows/card_manager.cpp
|
||||
overlay/windows/drs_dancefloor.cpp
|
||||
overlay/windows/gfdm_sub.cpp
|
||||
overlay/windows/screen_resize.cpp
|
||||
overlay/windows/sdvx_sub.cpp
|
||||
overlay/windows/config.cpp
|
||||
overlay/windows/controller_presets.cpp
|
||||
overlay/windows/control.cpp
|
||||
overlay/windows/eadev.cpp
|
||||
overlay/windows/fps.cpp
|
||||
@@ -624,6 +635,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
util/execexe.cpp
|
||||
util/dependencies.cpp
|
||||
util/deferlog.cpp
|
||||
util/socd_cleaner.cpp
|
||||
)
|
||||
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
|
||||
@@ -802,7 +814,8 @@ endif()
|
||||
|
||||
# nvcuda.dll
|
||||
set(SOURCE_FILES stubs/nvcuda.cpp)
|
||||
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} stubs/nvcuda.def)
|
||||
set(RESOURCE_FILES stubs/manifest.rc)
|
||||
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuda.def)
|
||||
set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
|
||||
|
||||
@@ -812,7 +825,8 @@ endif()
|
||||
|
||||
# nvcuvid.dll
|
||||
set(SOURCE_FILES stubs/nvcuvid.cpp)
|
||||
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} stubs/nvcuvid.def)
|
||||
set(RESOURCE_FILES stubs/manifest.rc)
|
||||
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuvid.def)
|
||||
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
|
||||
|
||||
@@ -822,7 +836,8 @@ endif()
|
||||
|
||||
# nvEncodeAPI64.dll
|
||||
set(SOURCE_FILES stubs/nvEncodeAPI64.cpp)
|
||||
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} stubs/nvEncodeAPI64.def)
|
||||
set(RESOURCE_FILES stubs/manifest.rc)
|
||||
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvEncodeAPI64.def)
|
||||
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
|
||||
|
||||
@@ -832,7 +847,8 @@ endif()
|
||||
|
||||
# cpusbxpkm.dll (32 bit)
|
||||
set(SOURCE_FILES stubs/cpusbxpkm.cpp)
|
||||
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} stubs/cpusbxpkm.def)
|
||||
set(RESOURCE_FILES stubs/manifest.rc)
|
||||
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/cpusbxpkm.def)
|
||||
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
|
||||
|
||||
|
||||
@@ -9,11 +9,22 @@
|
||||
#include "games/drs/drs.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/tapeled.h"
|
||||
|
||||
using namespace GameAPI;
|
||||
|
||||
#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
|
||||
|
||||
// state
|
||||
static uint8_t STATUS_BUFFER[272] {};
|
||||
static bool STATUS_BUFFER_FREEZE = false;
|
||||
@@ -94,18 +105,25 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
|
||||
}
|
||||
|
||||
// volume left
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
|
||||
const auto now = get_performance_milliseconds();
|
||||
const auto vol_l_state = socd::socd_clean(0,
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
|
||||
now);
|
||||
if (vol_l_state == socd::SocdCCW) {
|
||||
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
|
||||
} else if (vol_l_state == socd::SocdCW) {
|
||||
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
|
||||
// volume right
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
|
||||
const auto vol_r_state = socd::socd_clean(1,
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
|
||||
now);
|
||||
if (vol_r_state == socd::SocdCCW) {
|
||||
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
|
||||
} else if (vol_r_state == socd::SocdCW) {
|
||||
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
|
||||
@@ -127,6 +145,12 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
|
||||
// save volumes in buffer
|
||||
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
|
||||
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
|
||||
|
||||
log_debug(
|
||||
"bi2a",
|
||||
"knobs = {} {}",
|
||||
*((uint16_t*) &STATUS_BUFFER[17]),
|
||||
*((uint16_t*) &STATUS_BUFFER[19]));
|
||||
}
|
||||
|
||||
// DanceDanceRevolution
|
||||
|
||||
@@ -52,7 +52,7 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
|
||||
float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3));
|
||||
float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4));
|
||||
// FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs
|
||||
if (pad1_vel > 0.f) {
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad1))) {
|
||||
STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f);
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) {
|
||||
@@ -67,10 +67,10 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
|
||||
|
||||
// process analogs
|
||||
auto &analogs = games::ftt::get_analogs();
|
||||
auto pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
|
||||
auto pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
|
||||
auto pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
|
||||
auto pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
|
||||
auto &pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
|
||||
auto &pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
|
||||
auto &pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
|
||||
auto &pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
|
||||
if (pad1_analog.isSet()) {
|
||||
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f);
|
||||
STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val);
|
||||
|
||||
@@ -8,10 +8,21 @@
|
||||
#include "games/sdvx/io.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
using namespace GameAPI;
|
||||
|
||||
#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
|
||||
|
||||
// globals
|
||||
uint8_t KFCA_VOL_SOUND = 96;
|
||||
uint8_t KFCA_VOL_HEADPHONE = 96;
|
||||
@@ -342,18 +353,25 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
|
||||
}
|
||||
|
||||
// volume left
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
|
||||
const auto now = get_performance_milliseconds();
|
||||
const auto vol_l_state = socd::socd_clean(0,
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
|
||||
now);
|
||||
if (vol_l_state == socd::SocdCCW) {
|
||||
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
|
||||
} else if (vol_l_state == socd::SocdCW) {
|
||||
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
|
||||
// volume right
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
|
||||
const auto vol_r_state = socd::socd_clean(1,
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
|
||||
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
|
||||
now);
|
||||
if (vol_r_state == socd::SocdCCW) {
|
||||
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
|
||||
} else if (vol_r_state == socd::SocdCW) {
|
||||
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
|
||||
}
|
||||
|
||||
@@ -371,6 +389,8 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
|
||||
// proper loops
|
||||
vol_left %= 1024;
|
||||
vol_right %= 1024;
|
||||
|
||||
log_debug("kfca", "knobs = {} {}", vol_left, vol_right);
|
||||
|
||||
// save volumes in buffer
|
||||
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "acio/icca/icca.h"
|
||||
#include "avs/game.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
@@ -101,13 +102,10 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
|
||||
|
||||
// send version data
|
||||
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
|
||||
if (
|
||||
avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
|
||||
// SDVX Valkyrie cabinet mode
|
||||
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
|
||||
) {
|
||||
if (avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
|
||||
games::sdvx::is_valkyrie_model()) {
|
||||
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
|
||||
} else if (avs::game::is_model({"VFG"})) {
|
||||
} else if (avs::game::is_model("VFG")) {
|
||||
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
|
||||
} else {
|
||||
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
|
||||
@@ -520,11 +518,7 @@ void ICCADevice::update_status(int unit) {
|
||||
buffer[1] = felica ? 0x01 : 0x00;
|
||||
buffer[10] = felica ? 0x01 : 0x00;
|
||||
|
||||
} else if (
|
||||
avs::game::is_model({"LDJ", "TBS", "XIF"}) ||
|
||||
// SDVX Valkyrie cabinet mode
|
||||
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
|
||||
|
||||
} else if (avs::game::is_model({"LDJ", "TBS", "XIF"}) || games::sdvx::is_valkyrie_model()) {
|
||||
// set status to 0 otherwise reader power on fails
|
||||
buffer[0] = 0x00;
|
||||
} else {
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace api::modules {
|
||||
void DRS::touch_set(Request &req, Response &res) {
|
||||
|
||||
// get all touch points
|
||||
games::drs::drs_touch_t touches[16];
|
||||
auto touches = std::make_unique<games::drs::drs_touch_t[]>(req.params.Size());
|
||||
size_t i = 0;
|
||||
for (Value ¶m : req.params.GetArray()) {
|
||||
|
||||
@@ -86,6 +86,6 @@ namespace api::modules {
|
||||
}
|
||||
|
||||
// apply touch points
|
||||
games::drs::fire_touches(touches, i);
|
||||
games::drs::fire_touches(touches.get(), i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1234,6 +1234,7 @@ namespace avs {
|
||||
break;
|
||||
case 'F':
|
||||
new_style = logger::Style::RED;
|
||||
deferredlogs::report_fatal_message();
|
||||
break;
|
||||
default:
|
||||
position = 0;
|
||||
@@ -1302,6 +1303,7 @@ namespace avs {
|
||||
break;
|
||||
case 'F':
|
||||
style = logger::Style::RED;
|
||||
deferredlogs::report_fatal_message();
|
||||
break;
|
||||
case 'W':
|
||||
style = logger::Style::YELLOW;
|
||||
|
||||
+51
-4
@@ -5,8 +5,9 @@
|
||||
#include "build/defs.h"
|
||||
#include "easrv/easrv.h"
|
||||
#include "easrv/smartea.h"
|
||||
#include "games/mfc/mfc.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "hooks/avshook.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/fileutils.h"
|
||||
@@ -216,7 +217,23 @@ namespace avs {
|
||||
ea3_config_name = "prop/eamuse-config.xml";
|
||||
}
|
||||
|
||||
log_info("avs-ea3", "booting (using {})", ea3_config_name);
|
||||
if (avs::core::file_exists(ea3_config_name)) {
|
||||
log_info("avs-ea3", "booting (using {})", ea3_config_name);
|
||||
} else {
|
||||
log_warning("avs-ea3", "looked for the following files in order:");
|
||||
log_warning("avs-ea3", " * prop/ea3-config.xml");
|
||||
log_warning("avs-ea3", " * prop/ea3-cfg.xml");
|
||||
if (avs::game::DLL_NAME == "beatstream1.dll") {
|
||||
log_warning("avs-ea3", " * prop/ea3-config-1.xml");
|
||||
}
|
||||
if (avs::game::DLL_NAME == "beatstream2.dll") {
|
||||
log_warning("avs-ea3", " * prop/ea3-config-2.xml");
|
||||
}
|
||||
log_warning("avs-ea3", " * prop/eamuse-config.xml");
|
||||
log_warning("avs-ea3", "none of the files above were found, game will fail to boot");
|
||||
log_warning("avs-ea3", "usually, this means your game data is incomplete");
|
||||
log_fatal("avs-ea3", "no ea3 config file found in prop directory; check log messages");
|
||||
}
|
||||
|
||||
// remember new config path
|
||||
CFG_PATH = ea3_config_name;
|
||||
@@ -528,12 +545,42 @@ namespace avs {
|
||||
}
|
||||
}
|
||||
|
||||
// update -WINDOW in app-config
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
avs::core::property_search_remove_safe(app_config, nullptr, "/param/cmdline");
|
||||
std::string cmdline{};
|
||||
if (games::gitadora::is_drum()) {
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
cmdline = "-d -DM -WINDOW";
|
||||
} else {
|
||||
cmdline = "-d -DM";
|
||||
}
|
||||
} else {
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
cmdline = "-g -GF -WINDOW";
|
||||
} else {
|
||||
cmdline = "-g -GF";
|
||||
}
|
||||
}
|
||||
log_info("avs-ea3", "using cmdline: `{}`", cmdline);
|
||||
avs::core::property_node_create(app_config, nullptr,
|
||||
avs::core::NODE_TYPE_str, "/param/cmdline", cmdline.c_str());
|
||||
}
|
||||
|
||||
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
|
||||
|
||||
// call the game init
|
||||
log_info("avs-ea3", "calling entry init (init code = {})", init_code_str);
|
||||
log_info("avs-ea3", "calling dll_entry_init (init code = {})", init_code_str);
|
||||
if (!avs::game::entry_init(init_code_str.data(), app_param)) {
|
||||
log_fatal("avs-ea3", "entry init failed :(");
|
||||
for (size_t i = 0; i < 255; i++) {
|
||||
log_warning("avs-ea3", "dll_entry_init failed :(");
|
||||
}
|
||||
deferredlogs::defer_error_messages({
|
||||
"dll_entry_init failed",
|
||||
" init code: " + init_code_str,
|
||||
" this is a fatal error that causes boot failure",
|
||||
" most likely, your prop files have errors"
|
||||
});
|
||||
}
|
||||
|
||||
// accommodate changes to soft id code
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<!-- PHOENIXWAN controller preset for Beatmania IIDX
|
||||
|
||||
Button indices 0-6: keys 1-7
|
||||
Button indices 8-11: E1 (Start for Deluxe Model Style), E2 (Effect), E3 (VEFX), E4 (P1 Start for Lightning Model Style)
|
||||
|
||||
P1 Side P2 Side
|
||||
8 9 10 11 8 9 10 11
|
||||
2 4 6 2 4 6
|
||||
TT 1 3 5 7 1 3 5 7 TT -->
|
||||
<template name="DJ DAO PHOENIXWAN" game="Beatmania IIDX">
|
||||
<buttons>
|
||||
<button name="P1 1" vkey="0" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 2" vkey="1" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 3" vkey="2" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 4" vkey="3" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 5" vkey="4" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 6" vkey="5" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 7" vkey="6" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 1" vkey="0" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 2" vkey="1" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 3" vkey="2" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 4" vkey="3" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 5" vkey="4" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 6" vkey="5" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 7" vkey="6" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<!-- P1 Start: E1 primary (index 8), E4 alternative (index 11) -->
|
||||
<button name="P1 Start" vkey="8" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 Start" vkey="11" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<!-- P2 Start: E1 primary (index 8), E4 alternative (index 11) -->
|
||||
<button name="P2 Start" vkey="8" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 Start" vkey="11" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<!-- EFFECT: E2 (index 9), primary P2 side, alternative P1 side -->
|
||||
<button name="EFFECT" vkey="9" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="EFFECT" vkey="9" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<!-- VEFX: E3 (index 10), primary P2 side, alternative P1 side -->
|
||||
<button name="VEFX" vkey="10" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
</buttons>
|
||||
<analogs>
|
||||
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="P1 1" devid="Player 1" index="0"/>
|
||||
<light name="P1 2" devid="Player 1" index="1"/>
|
||||
<light name="P1 3" devid="Player 1" index="2"/>
|
||||
<light name="P1 4" devid="Player 1" index="3"/>
|
||||
<light name="P1 5" devid="Player 1" index="4"/>
|
||||
<light name="P1 6" devid="Player 1" index="5"/>
|
||||
<light name="P1 7" devid="Player 1" index="6"/>
|
||||
<light name="P2 1" devid="Player 2" index="0"/>
|
||||
<light name="P2 2" devid="Player 2" index="1"/>
|
||||
<light name="P2 3" devid="Player 2" index="2"/>
|
||||
<light name="P2 4" devid="Player 2" index="3"/>
|
||||
<light name="P2 5" devid="Player 2" index="4"/>
|
||||
<light name="P2 6" devid="Player 2" index="5"/>
|
||||
<light name="P2 7" devid="Player 2" index="6"/>
|
||||
<!-- Start buttons bind both E1 (index 8) and E4 (index 11), but lights use
|
||||
Lightning Model style only (one per side) to avoid both lighting up at once -->
|
||||
<light name="P1 Start" devid="Player 1" index="10"/>
|
||||
<light name="P2 Start" devid="Player 2" index="7"/>
|
||||
<light name="VEFX" devid="Player 1" index="8"/>
|
||||
<light name="VEFX" devid="Player 2" index="8"/>
|
||||
<light name="Effect" devid="Player 1" index="9"/>
|
||||
<light name="Effect" devid="Player 2" index="9"/>
|
||||
</lights>
|
||||
</template>
|
||||
<!-- Common "double scratch" BMS playstyle: LSHIFT A S D SPACE L ; ' RSHIFT -->
|
||||
<!-- Uses Up/Down arrows for turntable navigation -->
|
||||
<template name="Keyboard (linear, double scratch, QWERTY)" game="Beatmania IIDX">
|
||||
<buttons>
|
||||
<button name="P1 1" vkey="65" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 2" vkey="83" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 3" vkey="68" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 4" vkey="32" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 5" vkey="76" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 6" vkey="186" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 7" vkey="222" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 TT+" vkey="40" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 TT-" vkey="38" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 TT+/-" vkey="160" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 TT+/- Alternate" vkey="161" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 Start" vkey="13" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 Start" vkey="69" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="EFFECT" vkey="81" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="VEFX" vkey="87" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<analogs/>
|
||||
<lights/>
|
||||
</template>
|
||||
<template name="arcin (arcin-infinitas)" game="Beatmania IIDX">
|
||||
<buttons>
|
||||
<button name="P1 1" vkey="0" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 2" vkey="1" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 3" vkey="2" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 4" vkey="3" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 5" vkey="4" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 6" vkey="5" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 7" vkey="6" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P1 Start" vkey="9" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 1" vkey="0" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 2" vkey="1" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 3" vkey="2" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 4" vkey="3" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 5" vkey="4" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 6" vkey="5" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 7" vkey="6" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="P2 Start" vkey="9" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="EFFECT" vkey="11" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="EFFECT" vkey="11" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="VEFX" vkey="10" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="VEFX" vkey="10" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
|
||||
<analogs>
|
||||
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="P1 1" devid="arcin (1p)" index="0"/>
|
||||
<light name="P1 2" devid="arcin (1p)" index="1"/>
|
||||
<light name="P1 3" devid="arcin (1p)" index="2"/>
|
||||
<light name="P1 4" devid="arcin (1p)" index="3"/>
|
||||
<light name="P1 5" devid="arcin (1p)" index="4"/>
|
||||
<light name="P1 6" devid="arcin (1p)" index="5"/>
|
||||
<light name="P1 7" devid="arcin (1p)" index="6"/>
|
||||
<light name="P2 1" devid="arcin (2p)" index="0"/>
|
||||
<light name="P2 2" devid="arcin (2p)" index="1"/>
|
||||
<light name="P2 3" devid="arcin (2p)" index="2"/>
|
||||
<light name="P2 4" devid="arcin (2p)" index="3"/>
|
||||
<light name="P2 5" devid="arcin (2p)" index="4"/>
|
||||
<light name="P2 6" devid="arcin (2p)" index="5"/>
|
||||
<light name="P2 7" devid="arcin (2p)" index="6"/>
|
||||
<light name="P1 Start" devid="arcin (1p)" index="9"/>
|
||||
<light name="P2 Start" devid="arcin (2p)" index="9"/>
|
||||
</lights>
|
||||
</template>
|
||||
<!-- This controller has many modes; here, we use HID Analog mode which is meant for data -->
|
||||
<template name="DJ DAO Nostroller (Analog mode)" game="Nostalgia">
|
||||
<buttons/>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="Key 1 R" devid="Lights 01-14" index="0"/>
|
||||
<light name="Key 1 G" devid="Lights 01-14" index="1"/>
|
||||
<light name="Key 1 B" devid="Lights 01-14" index="2"/>
|
||||
<light name="Key 2 R" devid="Lights 01-14" index="3"/>
|
||||
<light name="Key 2 G" devid="Lights 01-14" index="4"/>
|
||||
<light name="Key 2 B" devid="Lights 01-14" index="5"/>
|
||||
<light name="Key 3 R" devid="Lights 01-14" index="6"/>
|
||||
<light name="Key 3 G" devid="Lights 01-14" index="7"/>
|
||||
<light name="Key 3 B" devid="Lights 01-14" index="8"/>
|
||||
<light name="Key 4 R" devid="Lights 01-14" index="9"/>
|
||||
<light name="Key 4 G" devid="Lights 01-14" index="10"/>
|
||||
<light name="Key 4 B" devid="Lights 01-14" index="11"/>
|
||||
<light name="Key 5 R" devid="Lights 01-14" index="12"/>
|
||||
<light name="Key 5 G" devid="Lights 01-14" index="13"/>
|
||||
<light name="Key 5 B" devid="Lights 01-14" index="14"/>
|
||||
<light name="Key 6 R" devid="Lights 01-14" index="15"/>
|
||||
<light name="Key 6 G" devid="Lights 01-14" index="16"/>
|
||||
<light name="Key 6 B" devid="Lights 01-14" index="17"/>
|
||||
<light name="Key 7 R" devid="Lights 01-14" index="18"/>
|
||||
<light name="Key 7 G" devid="Lights 01-14" index="19"/>
|
||||
<light name="Key 7 B" devid="Lights 01-14" index="20"/>
|
||||
<light name="Key 8 R" devid="Lights 01-14" index="21"/>
|
||||
<light name="Key 8 G" devid="Lights 01-14" index="22"/>
|
||||
<light name="Key 8 B" devid="Lights 01-14" index="23"/>
|
||||
<light name="Key 9 R" devid="Lights 01-14" index="24"/>
|
||||
<light name="Key 9 G" devid="Lights 01-14" index="25"/>
|
||||
<light name="Key 9 B" devid="Lights 01-14" index="26"/>
|
||||
<light name="Key 10 R" devid="Lights 01-14" index="27"/>
|
||||
<light name="Key 10 G" devid="Lights 01-14" index="28"/>
|
||||
<light name="Key 10 B" devid="Lights 01-14" index="29"/>
|
||||
<light name="Key 11 R" devid="Lights 01-14" index="30"/>
|
||||
<light name="Key 11 G" devid="Lights 01-14" index="31"/>
|
||||
<light name="Key 11 B" devid="Lights 01-14" index="32"/>
|
||||
<light name="Key 12 R" devid="Lights 01-14" index="33"/>
|
||||
<light name="Key 12 G" devid="Lights 01-14" index="34"/>
|
||||
<light name="Key 12 B" devid="Lights 01-14" index="35"/>
|
||||
<light name="Key 13 R" devid="Lights 01-14" index="36"/>
|
||||
<light name="Key 13 G" devid="Lights 01-14" index="37"/>
|
||||
<light name="Key 13 B" devid="Lights 01-14" index="38"/>
|
||||
<light name="Key 14 R" devid="Lights 01-14" index="39"/>
|
||||
<light name="Key 14 G" devid="Lights 01-14" index="40"/>
|
||||
<light name="Key 14 B" devid="Lights 01-14" index="41"/>
|
||||
<light name="Key 15 R" devid="Lights 15-28" index="0"/>
|
||||
<light name="Key 15 G" devid="Lights 15-28" index="1"/>
|
||||
<light name="Key 15 B" devid="Lights 15-28" index="2"/>
|
||||
<light name="Key 16 R" devid="Lights 15-28" index="3"/>
|
||||
<light name="Key 16 G" devid="Lights 15-28" index="4"/>
|
||||
<light name="Key 16 B" devid="Lights 15-28" index="5"/>
|
||||
<light name="Key 17 R" devid="Lights 15-28" index="6"/>
|
||||
<light name="Key 17 G" devid="Lights 15-28" index="7"/>
|
||||
<light name="Key 17 B" devid="Lights 15-28" index="8"/>
|
||||
<light name="Key 18 R" devid="Lights 15-28" index="9"/>
|
||||
<light name="Key 18 G" devid="Lights 15-28" index="10"/>
|
||||
<light name="Key 18 B" devid="Lights 15-28" index="11"/>
|
||||
<light name="Key 19 R" devid="Lights 15-28" index="12"/>
|
||||
<light name="Key 19 G" devid="Lights 15-28" index="13"/>
|
||||
<light name="Key 19 B" devid="Lights 15-28" index="14"/>
|
||||
<light name="Key 20 R" devid="Lights 15-28" index="15"/>
|
||||
<light name="Key 20 G" devid="Lights 15-28" index="16"/>
|
||||
<light name="Key 20 B" devid="Lights 15-28" index="17"/>
|
||||
<light name="Key 21 R" devid="Lights 15-28" index="18"/>
|
||||
<light name="Key 21 G" devid="Lights 15-28" index="19"/>
|
||||
<light name="Key 21 B" devid="Lights 15-28" index="20"/>
|
||||
<light name="Key 22 R" devid="Lights 15-28" index="21"/>
|
||||
<light name="Key 22 G" devid="Lights 15-28" index="22"/>
|
||||
<light name="Key 22 B" devid="Lights 15-28" index="23"/>
|
||||
<light name="Key 23 R" devid="Lights 15-28" index="24"/>
|
||||
<light name="Key 23 G" devid="Lights 15-28" index="25"/>
|
||||
<light name="Key 23 B" devid="Lights 15-28" index="26"/>
|
||||
<light name="Key 24 R" devid="Lights 15-28" index="27"/>
|
||||
<light name="Key 24 G" devid="Lights 15-28" index="28"/>
|
||||
<light name="Key 24 B" devid="Lights 15-28" index="29"/>
|
||||
<light name="Key 25 R" devid="Lights 15-28" index="30"/>
|
||||
<light name="Key 25 G" devid="Lights 15-28" index="31"/>
|
||||
<light name="Key 25 B" devid="Lights 15-28" index="32"/>
|
||||
<light name="Key 26 R" devid="Lights 15-28" index="33"/>
|
||||
<light name="Key 26 G" devid="Lights 15-28" index="34"/>
|
||||
<light name="Key 26 B" devid="Lights 15-28" index="35"/>
|
||||
<light name="Key 27 R" devid="Lights 15-28" index="36"/>
|
||||
<light name="Key 27 G" devid="Lights 15-28" index="37"/>
|
||||
<light name="Key 27 B" devid="Lights 15-28" index="38"/>
|
||||
<light name="Key 28 R" devid="Lights 15-28" index="39"/>
|
||||
<light name="Key 28 G" devid="Lights 15-28" index="40"/>
|
||||
<light name="Key 28 B" devid="Lights 15-28" index="41"/>
|
||||
</lights>
|
||||
</template>
|
||||
<!-- Gitaller has two modes - digital tilt and analog axis. This binds both of them at the same time. -->
|
||||
<template name="DJ DAO Gitaller" game="GitaDora">
|
||||
<buttons>
|
||||
<button name="Guitar P1 Start" vkey="9" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 Help" vkey="8" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 Pick Up" vkey="5" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 Pick Down" vkey="6" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 R" vkey="0" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 G" vkey="1" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 B" vkey="2" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 Y" vkey="3" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 P" vkey="4" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 Wail Up" vkey="10" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Guitar P1 Wail Down" vkey="11" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
</analogs>
|
||||
<lights/>
|
||||
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
|
||||
</template>
|
||||
<template name="DJ DAO FAUCETWO" game="Sound Voltex">
|
||||
<buttons>
|
||||
<button name="BT-A" vkey="1" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="BT-B" vkey="2" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="BT-C" vkey="3" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="BT-D" vkey="4" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="FX-L" vkey="5" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="FX-R" vkey="6" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
<button name="Start" vkey="0" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="BT-A" devid="Lights (MI_02)" index="24"/>
|
||||
<light name="BT-B" devid="Lights (MI_02)" index="25"/>
|
||||
<light name="BT-C" devid="Lights (MI_02)" index="26"/>
|
||||
<light name="BT-D" devid="Lights (MI_02)" index="27"/>
|
||||
<light name="FX-L" devid="Lights (MI_02)" index="28"/>
|
||||
<light name="FX-R" devid="Lights (MI_02)" index="29"/>
|
||||
<light name="Start" devid="Lights (MI_02)" index="30"/>
|
||||
<light name="Upper Left Speaker Avg R" devid="Lights (MI_02)" index="0"/>
|
||||
<light name="Upper Left Speaker Avg G" devid="Lights (MI_02)" index="1"/>
|
||||
<light name="Upper Left Speaker Avg B" devid="Lights (MI_02)" index="2"/>
|
||||
<light name="Upper Right Speaker Avg R" devid="Lights (MI_02)" index="9"/>
|
||||
<light name="Upper Right Speaker Avg G" devid="Lights (MI_02)" index="10"/>
|
||||
<light name="Upper Right Speaker Avg B" devid="Lights (MI_02)" index="11"/>
|
||||
<light name="Lower Left Speaker Avg R" devid="Lights (MI_02)" index="3"/>
|
||||
<light name="Lower Left Speaker Avg G" devid="Lights (MI_02)" index="4"/>
|
||||
<light name="Lower Left Speaker Avg B" devid="Lights (MI_02)" index="5"/>
|
||||
<light name="Lower Right Speaker Avg R" devid="Lights (MI_02)" index="6"/>
|
||||
<light name="Lower Right Speaker Avg G" devid="Lights (MI_02)" index="7"/>
|
||||
<light name="Lower Right Speaker Avg B" devid="Lights (MI_02)" index="8"/>
|
||||
<light name="Control Panel Avg R" devid="Lights (MI_02)" index="15"/>
|
||||
<light name="Control Panel Avg G" devid="Lights (MI_02)" index="16"/>
|
||||
<light name="Control Panel Avg B" devid="Lights (MI_02)" index="17"/>
|
||||
</lights>
|
||||
</template>
|
||||
</templates>
|
||||
@@ -41,8 +41,8 @@ done
|
||||
# settings
|
||||
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
|
||||
GIT_HEAD=$(git rev-parse HEAD || echo "none")
|
||||
TOOLCHAIN_32="/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"
|
||||
TOOLCHAIN_64="/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"
|
||||
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"}"
|
||||
BUILDDIR_32_RELEASE="./cmake-build-release-32"
|
||||
BUILDDIR_32_DEBUG="./cmake-build-debug-32"
|
||||
BUILDDIR_64_RELEASE="./cmake-build-release-64"
|
||||
@@ -74,7 +74,7 @@ then
|
||||
BUILD_TYPE="Debug"
|
||||
BUILDDIR_32=${BUILDDIR_32_DEBUG}
|
||||
BUILDDIR_64=${BUILDDIR_64_DEBUG}
|
||||
DIST_NAME=$(echo ${DIST_NAME} | sed 's/\.[^.]*$/-dbg&/')
|
||||
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
|
||||
fi
|
||||
|
||||
# determine number of cores
|
||||
@@ -114,7 +114,7 @@ time (
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_32}
|
||||
pushd ${BUILDDIR_32} > /dev/null
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_32}
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_32}
|
||||
popd > /dev/null
|
||||
|
||||
# 64 bit
|
||||
@@ -127,7 +127,7 @@ time (
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_64}
|
||||
pushd ${BUILDDIR_64} > /dev/null
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_64}
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
|
||||
popd > /dev/null
|
||||
|
||||
echo ""
|
||||
@@ -234,7 +234,7 @@ mkdir -p ${OUTDIR}/src
|
||||
if ((INCLUDE_SRC > 0))
|
||||
then
|
||||
echo "Generating source file archive..."
|
||||
git archive --format tar.gz --prefix=spice2x/ HEAD ./ > ${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz 2>/dev/null || \
|
||||
git archive --format tar.gz --prefix=spice2x/ HEAD ./ > "${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz" 2>/dev/null || \
|
||||
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
|
||||
fi
|
||||
|
||||
@@ -249,11 +249,11 @@ if ((DIST_ENABLE > 0))
|
||||
then
|
||||
echo "Building dist..."
|
||||
mkdir -p ${DIST_FOLDER}
|
||||
rm -rf ${DIST_FOLDER}/${DIST_NAME}
|
||||
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
|
||||
pushd ${OUTDIR}/.. > /dev/null
|
||||
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
|
||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
|
||||
echo "Building extras..."
|
||||
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS} spice2x -z <<< "$DIST_COMMENT"
|
||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" spice2x -z <<< "$DIST_COMMENT"
|
||||
popd > /dev/null
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
docker build --pull $PWD/external/docker -t spicetools/deps --platform linux/x86_64
|
||||
docker build --build-context gitroot=$PWD/../../.git . -t spicetools/spice:latest
|
||||
docker run --rm -v $PWD/dist:/src/src/spice2x/dist -v $PWD/bin:/src/src/spice2x/bin -v $PWD/.ccache:/src/src/spice2x/.ccache spicetools/spice "$@"
|
||||
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
|
||||
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
|
||||
docker run --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
|
||||
|
||||
@@ -13,5 +13,6 @@ IDR_LICENSES RCDATA "../licenses.txt"
|
||||
IDR_PATCHES RCDATA "../build/patches.json"
|
||||
IDR_README RCDATA "../readme.txt"
|
||||
IDR_DSEGFONT RCDATA "../external/dseg/DSEG14Classic-Italic.ttf"
|
||||
IDR_CONTROLLER_PRESETS_BUILTIN RCDATA "../build/controller_presets.xml"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -91,6 +91,7 @@ public:
|
||||
setMultiplier(1);
|
||||
setRelativeMode(false);
|
||||
setDelayBufferDepth(0);
|
||||
setLastState(0.5f);
|
||||
}
|
||||
|
||||
inline void clearBindings() {
|
||||
|
||||
+50
-5
@@ -37,7 +37,11 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
|
||||
for (auto &bt : buttons) {
|
||||
if (name == bt.getName()) {
|
||||
button_found = true;
|
||||
sorted.push_back(bt);
|
||||
Button button_new = bt;
|
||||
if (vkey_defaults) {
|
||||
button_new.setVKeyDefault(vkey_defaults->at(index));
|
||||
}
|
||||
sorted.push_back(button_new);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -47,6 +51,7 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
|
||||
|
||||
if (vkey_defaults) {
|
||||
button.setVKey(vkey_defaults->at(index));
|
||||
button.setVKeyDefault(vkey_defaults->at(index));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,10 +649,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
||||
case rawinput::HID: {
|
||||
|
||||
// get value
|
||||
if (inverted) {
|
||||
value = 1.f - device->hidInfo->value_states[index];
|
||||
} else {
|
||||
value = device->hidInfo->value_states[index];
|
||||
if (index < device->hidInfo->value_states.size()) {
|
||||
if (inverted) {
|
||||
value = 1.f - device->hidInfo->value_states[index];
|
||||
} else {
|
||||
value = device->hidInfo->value_states[index];
|
||||
}
|
||||
}
|
||||
|
||||
// deadzone
|
||||
@@ -859,6 +866,44 @@ std::vector<Light> GameAPI::Lights::sortLights(
|
||||
return sorted;
|
||||
}
|
||||
|
||||
static std::vector<Light> sortLightsWithCategoryInternal(
|
||||
const std::vector<Light> &lights,
|
||||
const std::initializer_list<GameAPI::Lights::LightAndCategory> list)
|
||||
{
|
||||
std::vector<Light> sorted;
|
||||
|
||||
bool light_found;
|
||||
for (auto &name : list) {
|
||||
light_found = false;
|
||||
|
||||
for (auto &light : lights) {
|
||||
if (name.light_name == light.getName()) {
|
||||
light_found = true;
|
||||
Light light_new = light;
|
||||
light_new.setCategory(name.category_name);
|
||||
sorted.push_back(light_new);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!light_found) {
|
||||
sorted.emplace_back(name.light_name, name.category_name);
|
||||
}
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
void GameAPI::Lights::sortLightsWithCategory(
|
||||
std::vector<Light> *lights,
|
||||
const std::initializer_list<LightAndCategory> list) {
|
||||
|
||||
if (lights) {
|
||||
*lights = sortLightsWithCategoryInternal(*lights, list);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) {
|
||||
|
||||
// check device
|
||||
|
||||
+15
-21
@@ -6,24 +6,20 @@
|
||||
#include <windows.h>
|
||||
#include "option.h"
|
||||
|
||||
#include "button.h"
|
||||
#include "analog.h"
|
||||
#include "light.h"
|
||||
|
||||
namespace rawinput {
|
||||
class RawInputManager;
|
||||
struct Device;
|
||||
}
|
||||
|
||||
class Button;
|
||||
class Analog;
|
||||
class Light;
|
||||
class Game;
|
||||
class Option;
|
||||
|
||||
namespace GameAPI {
|
||||
namespace Buttons {
|
||||
enum State {
|
||||
BUTTON_PRESSED = true,
|
||||
BUTTON_NOT_PRESSED = false
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the config and returns the buttons set by the user.
|
||||
*
|
||||
@@ -113,21 +109,13 @@ namespace GameAPI {
|
||||
}
|
||||
|
||||
namespace Lights {
|
||||
|
||||
std::vector<Light> getLights(const std::string &game_name);
|
||||
|
||||
std::vector<Light> sortLights(
|
||||
const std::vector<Light> &lights,
|
||||
const std::vector<std::string> &light_names);
|
||||
|
||||
template<typename T>
|
||||
void sortLights(std::vector<Light> *lights, T t) {
|
||||
const std::vector<std::string> light_names { t };
|
||||
|
||||
if (lights) {
|
||||
*lights = GameAPI::Lights::sortLights(*lights, light_names);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename... Rest>
|
||||
void sortLights(std::vector<Light> *lights, T t, Rest... rest) {
|
||||
const std::vector<std::string> light_names { t, rest... };
|
||||
@@ -137,6 +125,16 @@ namespace GameAPI {
|
||||
}
|
||||
}
|
||||
|
||||
struct LightAndCategory {
|
||||
const std::string category_name;
|
||||
const std::string light_name;
|
||||
|
||||
LightAndCategory(std::string category_name, std::string light_name) :
|
||||
category_name(std::move(category_name)), light_name(std::move(light_name)) {}
|
||||
};
|
||||
|
||||
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, Light &light, float value);
|
||||
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
|
||||
@@ -152,7 +150,3 @@ namespace GameAPI {
|
||||
void sortOptions(std::vector<Option> &, const std::vector<OptionDefinition> &);
|
||||
}
|
||||
}
|
||||
|
||||
#include "button.h"
|
||||
#include "analog.h"
|
||||
#include "light.h"
|
||||
|
||||
+52
-30
@@ -44,7 +44,7 @@ std::string Button::getVKeyString() {
|
||||
case 0x0C:
|
||||
return "Clear";
|
||||
case 0x0D:
|
||||
return "Enter";
|
||||
return "Enter/Return";
|
||||
case 0x10:
|
||||
return "Shift";
|
||||
case 0x11:
|
||||
@@ -58,8 +58,28 @@ std::string Button::getVKeyString() {
|
||||
return "Pause";
|
||||
case 0x14:
|
||||
return "Caps Lock";
|
||||
case 0x15:
|
||||
return "IME Kana/Hangul";
|
||||
case 0x16:
|
||||
return "IME On";
|
||||
case 0x17:
|
||||
return "IME Junja";
|
||||
case 0x18:
|
||||
return "IME Final";
|
||||
case 0x19:
|
||||
return "IME Hanja/Kanji";
|
||||
case 0x1A:
|
||||
return "IME Off";
|
||||
case 0x1B:
|
||||
return "Escape";
|
||||
case 0x1C:
|
||||
return "IME Convert";
|
||||
case 0x1D:
|
||||
return "IME Nonconvert";
|
||||
case 0x1E:
|
||||
return "IME Accept";
|
||||
case 0x1F:
|
||||
return "IME Mode Change";
|
||||
case 0x20:
|
||||
return "Space";
|
||||
case 0x21:
|
||||
@@ -163,37 +183,37 @@ std::string Button::getVKeyString() {
|
||||
case 0x5D:
|
||||
return "Apps";
|
||||
case 0x60:
|
||||
return "Num 0";
|
||||
return "Numpad 0";
|
||||
case 0x61:
|
||||
return "Num 1";
|
||||
return "Numpad 1";
|
||||
case 0x62:
|
||||
return "Num 2";
|
||||
return "Numpad 2";
|
||||
case 0x63:
|
||||
return "Num 3";
|
||||
return "Numpad 3";
|
||||
case 0x64:
|
||||
return "Num 4";
|
||||
return "Numpad 4";
|
||||
case 0x65:
|
||||
return "Num 5";
|
||||
return "Numpad 5";
|
||||
case 0x66:
|
||||
return "Num 6";
|
||||
return "Numpad 6";
|
||||
case 0x67:
|
||||
return "Num 7";
|
||||
return "Numpad 7";
|
||||
case 0x68:
|
||||
return "Num 8";
|
||||
return "Numpad 8";
|
||||
case 0x69:
|
||||
return "Num 9";
|
||||
return "Numpad 9";
|
||||
case 0x6A:
|
||||
return "*";
|
||||
return "Numpad *";
|
||||
case 0x6B:
|
||||
return "+";
|
||||
return "Numpad +";
|
||||
case 0x6C:
|
||||
return "Seperator";
|
||||
return "Numpad Seperator";
|
||||
case 0x6D:
|
||||
return "-";
|
||||
return "Numpad -";
|
||||
case 0x6E:
|
||||
return ".";
|
||||
return "Numpad .";
|
||||
case 0x6F:
|
||||
return "/";
|
||||
return "Numpad /";
|
||||
case 0x70:
|
||||
return "F1";
|
||||
case 0x71:
|
||||
@@ -255,9 +275,11 @@ std::string Button::getVKeyString() {
|
||||
case 0xA3:
|
||||
return "Right Control";
|
||||
case 0xA4:
|
||||
return "Left Menu";
|
||||
return "Left Alt";
|
||||
case 0xA5:
|
||||
return "Right Menu";
|
||||
return "Right Alt";
|
||||
case INVALID_VKEY:
|
||||
return "Invalid";
|
||||
default:
|
||||
|
||||
// check win API
|
||||
@@ -301,17 +323,17 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
||||
switch (device->type) {
|
||||
case rawinput::MOUSE: {
|
||||
const char *btn = "Unknown";
|
||||
static const char *MOUSE_NAMES[] = {
|
||||
"Left Mouse",
|
||||
"Right Mouse",
|
||||
"Middle Mouse",
|
||||
"Mouse 1",
|
||||
"Mouse 2",
|
||||
"Mouse 3",
|
||||
"Mouse 4",
|
||||
"Mouse 5",
|
||||
};
|
||||
if (vKey < sizeof(MOUSE_NAMES)) {
|
||||
static const std::array<const char *, 8>MOUSE_NAMES = {
|
||||
"Left Mouse",
|
||||
"Right Mouse",
|
||||
"Middle Mouse",
|
||||
"Mouse 1",
|
||||
"Mouse 2",
|
||||
"Mouse 3",
|
||||
"Mouse 4",
|
||||
"Mouse 5",
|
||||
};
|
||||
if (vKey < MOUSE_NAMES.size()) {
|
||||
btn = MOUSE_NAMES[vKey];
|
||||
}
|
||||
return fmt::format("{} ({})", btn, device->desc);
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "api.h"
|
||||
|
||||
namespace rawinput {
|
||||
class RawInputManager;
|
||||
}
|
||||
|
||||
namespace GameAPI {
|
||||
namespace Buttons {
|
||||
enum State {
|
||||
BUTTON_PRESSED = true,
|
||||
BUTTON_NOT_PRESSED = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum ButtonAnalogType {
|
||||
BAT_NONE = 0,
|
||||
BAT_POSITIVE = 1,
|
||||
@@ -41,19 +49,24 @@ private:
|
||||
std::string name;
|
||||
std::string device_identifier = "";
|
||||
unsigned short vKey = INVALID_VKEY;
|
||||
|
||||
// default bindings are always naive
|
||||
unsigned short vKey_default = INVALID_VKEY;
|
||||
|
||||
ButtonAnalogType analog_type = BAT_NONE;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
bool invert = false;
|
||||
bool is_temporary = false;
|
||||
|
||||
GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED;
|
||||
float last_velocity = 0.f;
|
||||
unsigned short velocity_threshold = 0;
|
||||
|
||||
std::string getVKeyString();
|
||||
std::string getMidiNoteString();
|
||||
|
||||
public:
|
||||
std::string getVKeyString();
|
||||
|
||||
// overrides
|
||||
bool override_enabled = false;
|
||||
@@ -70,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;
|
||||
}
|
||||
}
|
||||
@@ -116,6 +137,14 @@ public:
|
||||
this->vKey = vKey;
|
||||
}
|
||||
|
||||
inline unsigned short getVKeyDefault() const {
|
||||
return this->vKey_default;
|
||||
}
|
||||
|
||||
inline void setVKeyDefault(unsigned short vKey_default) {
|
||||
this->vKey_default = vKey_default;
|
||||
}
|
||||
|
||||
inline ButtonAnalogType getAnalogType() const {
|
||||
return this->analog_type;
|
||||
}
|
||||
@@ -164,6 +193,14 @@ public:
|
||||
this->last_velocity = last_velocity;
|
||||
}
|
||||
|
||||
inline bool isTemporary() const {
|
||||
return this->is_temporary;
|
||||
}
|
||||
|
||||
inline void setTemporary(bool is_temporary) {
|
||||
this->is_temporary = is_temporary;
|
||||
}
|
||||
|
||||
inline unsigned short getVelocityThreshold() const {
|
||||
return this->velocity_threshold;
|
||||
}
|
||||
@@ -172,6 +209,20 @@ public:
|
||||
this->velocity_threshold = velocity_threshold;
|
||||
}
|
||||
|
||||
inline bool isValid() {
|
||||
// temporarily created by UI
|
||||
if (isTemporary()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// nothing is set
|
||||
if (getDeviceIdentifier().empty() && getVKey() == INVALID_VKEY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void getMidiVKey(int& channel, int& index);
|
||||
void setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "config.h"
|
||||
#include "util/logging.h"
|
||||
#include "cfg/button.h"
|
||||
|
||||
/*
|
||||
* This code absolutely sucks.
|
||||
@@ -10,6 +11,14 @@
|
||||
// settings
|
||||
std::string CONFIG_PATH_OVERRIDE = "";
|
||||
|
||||
#define CFG_DEBUG_VERBOSE 0
|
||||
|
||||
#if CFG_DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
///////////////////
|
||||
/// Constructor ///
|
||||
@@ -510,8 +519,10 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
return false;
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement *gameButtonNode = nullptr;
|
||||
|
||||
// iterate button nodes
|
||||
tinyxml2::XMLElement *gameButtonNode = gameButtonsNode->FirstChildElement("button");
|
||||
gameButtonNode = gameButtonsNode->FirstChildElement("button");
|
||||
int button_count = 0;
|
||||
while (gameButtonNode != nullptr) {
|
||||
const char *buttonNodeName = gameButtonNode->Attribute("name");
|
||||
@@ -574,6 +585,39 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
}
|
||||
}
|
||||
|
||||
// trim the trailing invalid (unbound) alternatives
|
||||
tinyxml2::XMLElement *to_delete = nullptr;
|
||||
gameButtonNode = gameButtonsNode->LastChildElement("button");
|
||||
while (gameButtonNode != nullptr) {
|
||||
const char *name = gameButtonNode->Attribute("name");
|
||||
const char *devid = gameButtonNode->Attribute("devid");
|
||||
int vkey = INVALID_VKEY;
|
||||
gameButtonNode->QueryIntAttribute("vkey", &vkey);
|
||||
|
||||
// wrong button, ignore and keep iterating backwards
|
||||
if (name == nullptr || std::string(name) != button.getName()) {
|
||||
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
|
||||
continue;
|
||||
}
|
||||
|
||||
// found a node for this button, which means to_delete is not the first one
|
||||
// delete the node for to_delete
|
||||
if (to_delete != nullptr) {
|
||||
log_debug("cfg", "deleting excessive button binding for {}", name);
|
||||
gameButtonsNode->DeleteChild(to_delete);
|
||||
to_delete = nullptr;
|
||||
}
|
||||
|
||||
if ((devid == nullptr || strlen(devid) == 0) && vkey == INVALID_VKEY) {
|
||||
// unbound button with no device ID, make a note of it so it can be deleted later
|
||||
to_delete = gameButtonNode;
|
||||
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
|
||||
} else {
|
||||
// valid binding found; stop iterating
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if button was not found
|
||||
if (button_count == 0) {
|
||||
return false;
|
||||
@@ -760,6 +804,37 @@ bool Config::updateBinding(const Game &game, const Light &light, int alternative
|
||||
}
|
||||
}
|
||||
|
||||
// trim the trailing invalid (unbound) alternatives
|
||||
tinyxml2::XMLElement *to_delete = nullptr;
|
||||
gameLightNode = gameLightsNode->LastChildElement("light");
|
||||
while (gameLightNode != nullptr) {
|
||||
const char *name = gameLightNode->Attribute("name");
|
||||
const char *devid = gameLightNode->Attribute("devid");
|
||||
|
||||
// wrong light, ignore and keep iterating backwards
|
||||
if (name == nullptr || std::string(name) != light.getName()) {
|
||||
gameLightNode = gameLightNode->PreviousSiblingElement("light");
|
||||
continue;
|
||||
}
|
||||
|
||||
// found a node for this button, which means to_delete is not the first one
|
||||
// delete the node for to_delete
|
||||
if (to_delete != nullptr) {
|
||||
log_debug("cfg", "deleting excessive light binding for {}", name);
|
||||
gameLightsNode->DeleteChild(to_delete);
|
||||
to_delete = nullptr;
|
||||
}
|
||||
|
||||
if (devid == nullptr || strlen(devid) == 0) {
|
||||
// unbound light, make a note of it so it can be deleted later
|
||||
to_delete = gameLightNode;
|
||||
gameLightNode = gameLightNode->PreviousSiblingElement("light");
|
||||
} else {
|
||||
// valid binding found; stop iterating
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if light was not found
|
||||
if (light_count == 0) {
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
#include "overlay/windows/controller_presets.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "cfg/resource.h"
|
||||
#include "external/tinyxml2/tinyxml2.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/resutils.h"
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
static std::filesystem::path get_templates_path() {
|
||||
return fileutils::get_config_file_path("ControllerPresets", "spicetools_presets.xml");
|
||||
}
|
||||
|
||||
static void write_button_entry(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
|
||||
const std::string &name, const TemplateButtonEntry &entry) {
|
||||
auto *el = doc.NewElement("button");
|
||||
el->SetAttribute("name", name.c_str());
|
||||
el->SetAttribute("vkey", entry.vKey);
|
||||
el->SetAttribute("analogtype", (int)entry.analog_type);
|
||||
el->SetAttribute("devid", entry.device_identifier.c_str());
|
||||
el->SetAttribute("invert", entry.invert);
|
||||
el->SetAttribute("debounce_up", entry.debounce_up);
|
||||
el->SetAttribute("debounce_down", entry.debounce_down);
|
||||
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
||||
parent->InsertEndChild(el);
|
||||
}
|
||||
|
||||
static TemplateButtonEntry read_button_entry(tinyxml2::XMLElement *el) {
|
||||
TemplateButtonEntry entry;
|
||||
int vkey = INVALID_VKEY;
|
||||
el->QueryIntAttribute("vkey", &vkey);
|
||||
entry.vKey = (unsigned short)vkey;
|
||||
|
||||
int atype = 0;
|
||||
el->QueryIntAttribute("analogtype", &atype);
|
||||
entry.analog_type = (ButtonAnalogType)atype;
|
||||
|
||||
const char *devid = el->Attribute("devid");
|
||||
entry.device_identifier = devid ? devid : "";
|
||||
|
||||
el->QueryBoolAttribute("invert", &entry.invert);
|
||||
el->QueryDoubleAttribute("debounce_up", &entry.debounce_up);
|
||||
el->QueryDoubleAttribute("debounce_down", &entry.debounce_down);
|
||||
|
||||
int vel = 0;
|
||||
el->QueryIntAttribute("velocity_threshold", &vel);
|
||||
entry.velocity_threshold = (unsigned short)vel;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
static void write_analog(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
|
||||
const TemplateAnalogBinding &analog) {
|
||||
auto *el = doc.NewElement("analog");
|
||||
el->SetAttribute("name", analog.name.c_str());
|
||||
el->SetAttribute("devid", analog.device_identifier.c_str());
|
||||
el->SetAttribute("index", analog.index);
|
||||
el->SetAttribute("sensivity", analog.sensitivity); // keep typo for compat
|
||||
el->SetAttribute("deadzone", analog.deadzone);
|
||||
el->SetAttribute("deadzone_mirror", analog.deadzone_mirror);
|
||||
el->SetAttribute("invert", analog.invert);
|
||||
el->SetAttribute("smoothing", analog.smoothing);
|
||||
el->SetAttribute("multiplier", analog.multiplier);
|
||||
el->SetAttribute("relative", analog.relative_mode);
|
||||
el->SetAttribute("delay", analog.delay_buffer_depth);
|
||||
parent->InsertEndChild(el);
|
||||
}
|
||||
|
||||
static TemplateAnalogBinding read_analog(tinyxml2::XMLElement *el) {
|
||||
TemplateAnalogBinding a;
|
||||
const char *name = el->Attribute("name");
|
||||
a.name = name ? name : "";
|
||||
|
||||
const char *devid = el->Attribute("devid");
|
||||
a.device_identifier = devid ? devid : "";
|
||||
|
||||
int idx = 0xFF;
|
||||
el->QueryIntAttribute("index", &idx);
|
||||
a.index = (unsigned short)idx;
|
||||
|
||||
el->QueryFloatAttribute("sensivity", &a.sensitivity); // keep typo
|
||||
el->QueryFloatAttribute("deadzone", &a.deadzone);
|
||||
el->QueryBoolAttribute("deadzone_mirror", &a.deadzone_mirror);
|
||||
el->QueryBoolAttribute("invert", &a.invert);
|
||||
el->QueryBoolAttribute("smoothing", &a.smoothing);
|
||||
el->QueryIntAttribute("multiplier", &a.multiplier);
|
||||
el->QueryBoolAttribute("relative", &a.relative_mode);
|
||||
el->QueryIntAttribute("delay", &a.delay_buffer_depth);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
static void write_light_entry(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
|
||||
const std::string &name, const TemplateLightEntry &entry) {
|
||||
auto *el = doc.NewElement("light");
|
||||
el->SetAttribute("name", name.c_str());
|
||||
el->SetAttribute("devid", entry.device_identifier.c_str());
|
||||
el->SetAttribute("index", entry.index);
|
||||
parent->InsertEndChild(el);
|
||||
}
|
||||
|
||||
static TemplateLightEntry read_light_entry(tinyxml2::XMLElement *el) {
|
||||
TemplateLightEntry entry;
|
||||
const char *devid = el->Attribute("devid");
|
||||
entry.device_identifier = devid ? devid : "";
|
||||
|
||||
int idx = 0;
|
||||
el->QueryIntAttribute("index", &idx);
|
||||
entry.index = (unsigned int)idx;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Parse all templates from an already-loaded XMLDocument.
|
||||
// is_builtin controls the is_builtin flag on each returned template.
|
||||
static std::vector<ControllerTemplate> load_templates_from_doc(
|
||||
tinyxml2::XMLDocument &doc, bool is_builtin) {
|
||||
std::vector<ControllerTemplate> templates;
|
||||
|
||||
auto *root = doc.RootElement();
|
||||
if (!root) {
|
||||
return templates;
|
||||
}
|
||||
|
||||
auto *tmpl_el = root->FirstChildElement("template");
|
||||
while (tmpl_el) {
|
||||
ControllerTemplate tmpl;
|
||||
const char *name = tmpl_el->Attribute("name");
|
||||
const char *game = tmpl_el->Attribute("game");
|
||||
tmpl.name = name ? name : "";
|
||||
tmpl.game_name = game ? game : "";
|
||||
tmpl.is_builtin = is_builtin;
|
||||
|
||||
// buttons
|
||||
auto *buttons_el = tmpl_el->FirstChildElement("buttons");
|
||||
if (buttons_el) {
|
||||
auto *btn_el = buttons_el->FirstChildElement("button");
|
||||
while (btn_el) {
|
||||
const char *btn_name = btn_el->Attribute("name");
|
||||
std::string btn_name_str = btn_name ? btn_name : "";
|
||||
|
||||
TemplateButtonBinding *binding = nullptr;
|
||||
for (auto &b : tmpl.buttons) {
|
||||
if (b.name == btn_name_str) { binding = &b; break; }
|
||||
}
|
||||
if (!binding) {
|
||||
tmpl.buttons.push_back({btn_name_str, read_button_entry(btn_el), {}});
|
||||
} else {
|
||||
binding->alternatives.push_back(read_button_entry(btn_el));
|
||||
}
|
||||
btn_el = btn_el->NextSiblingElement("button");
|
||||
}
|
||||
}
|
||||
|
||||
// keypad buttons
|
||||
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
|
||||
if (kp_el) {
|
||||
auto *btn_el = kp_el->FirstChildElement("button");
|
||||
while (btn_el) {
|
||||
const char *btn_name = btn_el->Attribute("name");
|
||||
std::string btn_name_str = btn_name ? btn_name : "";
|
||||
|
||||
TemplateButtonBinding *binding = nullptr;
|
||||
for (auto &b : tmpl.keypad_buttons) {
|
||||
if (b.name == btn_name_str) { binding = &b; break; }
|
||||
}
|
||||
if (!binding) {
|
||||
tmpl.keypad_buttons.push_back({btn_name_str, read_button_entry(btn_el), {}});
|
||||
} else {
|
||||
binding->alternatives.push_back(read_button_entry(btn_el));
|
||||
}
|
||||
btn_el = btn_el->NextSiblingElement("button");
|
||||
}
|
||||
}
|
||||
|
||||
// analogs
|
||||
auto *analogs_el = tmpl_el->FirstChildElement("analogs");
|
||||
if (analogs_el) {
|
||||
auto *analog_el = analogs_el->FirstChildElement("analog");
|
||||
while (analog_el) {
|
||||
tmpl.analogs.push_back(read_analog(analog_el));
|
||||
analog_el = analog_el->NextSiblingElement("analog");
|
||||
}
|
||||
}
|
||||
|
||||
// lights
|
||||
auto *lights_el = tmpl_el->FirstChildElement("lights");
|
||||
if (lights_el) {
|
||||
auto *light_el = lights_el->FirstChildElement("light");
|
||||
while (light_el) {
|
||||
const char *light_name = light_el->Attribute("name");
|
||||
std::string light_name_str = light_name ? light_name : "";
|
||||
|
||||
TemplateLightBinding *binding = nullptr;
|
||||
for (auto &l : tmpl.lights) {
|
||||
if (l.name == light_name_str) { binding = &l; break; }
|
||||
}
|
||||
if (!binding) {
|
||||
tmpl.lights.push_back({light_name_str, read_light_entry(light_el), {}});
|
||||
} else {
|
||||
binding->alternatives.push_back(read_light_entry(light_el));
|
||||
}
|
||||
light_el = light_el->NextSiblingElement("light");
|
||||
}
|
||||
}
|
||||
|
||||
templates.push_back(std::move(tmpl));
|
||||
tmpl_el = tmpl_el->NextSiblingElement("template");
|
||||
}
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
static std::vector<ControllerTemplate> load_builtin_templates() {
|
||||
auto xml = resutil::load_file_string(IDR_CONTROLLER_PRESETS_BUILTIN);
|
||||
tinyxml2::XMLDocument doc;
|
||||
auto err = doc.Parse(xml.c_str());
|
||||
if (err != tinyxml2::XML_SUCCESS) {
|
||||
log_warning("templates", "failed to parse builtin templates XML: {}", (int)err);
|
||||
return {};
|
||||
}
|
||||
return load_templates_from_doc(doc, true);
|
||||
}
|
||||
|
||||
std::vector<ControllerTemplate> load_user_templates() {
|
||||
std::vector<ControllerTemplate> templates;
|
||||
auto path = get_templates_path();
|
||||
|
||||
tinyxml2::XMLDocument doc;
|
||||
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) {
|
||||
return templates;
|
||||
}
|
||||
return load_templates_from_doc(doc, false);
|
||||
}
|
||||
|
||||
bool save_user_template(
|
||||
const ControllerTemplate &tmpl,
|
||||
const bool save_buttons,
|
||||
const bool save_keypads,
|
||||
const bool save_analogs,
|
||||
const bool save_lights) {
|
||||
|
||||
auto path = get_templates_path();
|
||||
auto path_tmp = path;
|
||||
path_tmp.replace_extension(L"tmp");
|
||||
|
||||
tinyxml2::XMLDocument doc;
|
||||
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) {
|
||||
auto *decl = doc.NewDeclaration();
|
||||
doc.InsertFirstChild(decl);
|
||||
auto *root = doc.NewElement("templates");
|
||||
doc.InsertEndChild(root);
|
||||
}
|
||||
|
||||
auto *root = doc.RootElement();
|
||||
if (!root) {
|
||||
root = doc.NewElement("templates");
|
||||
doc.InsertEndChild(root);
|
||||
}
|
||||
|
||||
// remove existing template with same name and game
|
||||
auto *existing = root->FirstChildElement("template");
|
||||
while (existing) {
|
||||
auto *next = existing->NextSiblingElement("template");
|
||||
const char *ename = existing->Attribute("name");
|
||||
const char *egame = existing->Attribute("game");
|
||||
if (ename && egame &&
|
||||
std::string(ename) == tmpl.name &&
|
||||
std::string(egame) == tmpl.game_name) {
|
||||
root->DeleteChild(existing);
|
||||
}
|
||||
existing = next;
|
||||
}
|
||||
|
||||
auto *tmpl_el = doc.NewElement("template");
|
||||
tmpl_el->SetAttribute("name", tmpl.name.c_str());
|
||||
tmpl_el->SetAttribute("game", tmpl.game_name.c_str());
|
||||
|
||||
// buttons — skip unbound entries
|
||||
auto *buttons_el = doc.NewElement("buttons");
|
||||
if (save_buttons) {
|
||||
for (auto &btn : tmpl.buttons) {
|
||||
if (!btn.primary.is_unbound()) {
|
||||
write_button_entry(doc, buttons_el, btn.name, btn.primary);
|
||||
}
|
||||
for (auto &alt : btn.alternatives) {
|
||||
if (!alt.is_unbound()) {
|
||||
write_button_entry(doc, buttons_el, btn.name, alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tmpl_el->InsertEndChild(buttons_el);
|
||||
|
||||
// keypad buttons — skip unbound entries
|
||||
if (!tmpl.keypad_buttons.empty()) {
|
||||
auto *kp_el = doc.NewElement("keypad_buttons");
|
||||
if (save_keypads) {
|
||||
for (auto &btn : tmpl.keypad_buttons) {
|
||||
if (!btn.primary.is_unbound()) {
|
||||
write_button_entry(doc, kp_el, btn.name, btn.primary);
|
||||
}
|
||||
for (auto &alt : btn.alternatives) {
|
||||
if (!alt.is_unbound()) {
|
||||
write_button_entry(doc, kp_el, btn.name, alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tmpl_el->InsertEndChild(kp_el);
|
||||
}
|
||||
|
||||
// analogs — skip unbound entries
|
||||
auto *analogs_el = doc.NewElement("analogs");
|
||||
if (save_analogs) {
|
||||
for (auto &a : tmpl.analogs) {
|
||||
if (!a.is_unbound()) {
|
||||
write_analog(doc, analogs_el, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
tmpl_el->InsertEndChild(analogs_el);
|
||||
|
||||
// lights — skip unbound entries
|
||||
auto *lights_el = doc.NewElement("lights");
|
||||
if (save_lights) {
|
||||
for (auto &l : tmpl.lights) {
|
||||
if (!l.primary.is_unbound()) {
|
||||
write_light_entry(doc, lights_el, l.name, l.primary);
|
||||
}
|
||||
for (auto &alt : l.alternatives) {
|
||||
if (!alt.is_unbound()) {
|
||||
write_light_entry(doc, lights_el, l.name, alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tmpl_el->InsertEndChild(lights_el);
|
||||
|
||||
root->InsertEndChild(tmpl_el);
|
||||
|
||||
// ensure directory exists
|
||||
if (!path.parent_path().empty() && !std::filesystem::exists(path.parent_path())) {
|
||||
fileutils::dir_create_recursive(path.parent_path());
|
||||
}
|
||||
|
||||
// save atomically via temp file
|
||||
auto save_err = doc.SaveFile(path_tmp.c_str());
|
||||
if (save_err != tinyxml2::XML_SUCCESS) {
|
||||
log_warning("templates", "failed to save templates file: {}", (int)save_err);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(path_tmp, path, ec);
|
||||
if (ec) {
|
||||
log_warning("templates", "failed to rename templates file: {}", ec.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool delete_user_template(const std::string &game_name, const std::string &template_name) {
|
||||
auto path = get_templates_path();
|
||||
|
||||
tinyxml2::XMLDocument doc;
|
||||
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) return false;
|
||||
|
||||
auto *root = doc.RootElement();
|
||||
if (!root) return false;
|
||||
|
||||
bool found = false;
|
||||
auto *tmpl_el = root->FirstChildElement("template");
|
||||
while (tmpl_el) {
|
||||
auto *next = tmpl_el->NextSiblingElement("template");
|
||||
const char *ename = tmpl_el->Attribute("name");
|
||||
const char *egame = tmpl_el->Attribute("game");
|
||||
if (ename && egame &&
|
||||
std::string(ename) == template_name &&
|
||||
std::string(egame) == game_name) {
|
||||
root->DeleteChild(tmpl_el);
|
||||
found = true;
|
||||
}
|
||||
tmpl_el = next;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
auto path_tmp = path;
|
||||
path_tmp.replace_extension(L"tmp");
|
||||
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(path_tmp, path, ec);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
bool rename_user_template(const std::string &game_name,
|
||||
const std::string &old_name, const std::string &new_name) {
|
||||
auto path = get_templates_path();
|
||||
|
||||
tinyxml2::XMLDocument doc;
|
||||
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) return false;
|
||||
|
||||
auto *root = doc.RootElement();
|
||||
if (!root) return false;
|
||||
|
||||
bool found = false;
|
||||
auto *tmpl_el = root->FirstChildElement("template");
|
||||
while (tmpl_el) {
|
||||
const char *ename = tmpl_el->Attribute("name");
|
||||
const char *egame = tmpl_el->Attribute("game");
|
||||
if (ename && egame &&
|
||||
std::string(ename) == old_name &&
|
||||
std::string(egame) == game_name) {
|
||||
tmpl_el->SetAttribute("name", new_name.c_str());
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
tmpl_el = tmpl_el->NextSiblingElement("template");
|
||||
}
|
||||
|
||||
if (found) {
|
||||
auto path_tmp = path;
|
||||
path_tmp.replace_extension(L"tmp");
|
||||
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(path_tmp, path, ec);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
std::vector<ControllerTemplate> get_templates_for_game(const std::string &game_name) {
|
||||
std::vector<ControllerTemplate> result;
|
||||
|
||||
log_misc("templates", "loading built-in templates for {}", game_name);
|
||||
for (auto &t : load_builtin_templates()) {
|
||||
if (t.game_name == game_name) {
|
||||
result.push_back(std::move(t));
|
||||
}
|
||||
}
|
||||
|
||||
log_misc("templates", "loading user templates for {}", game_name);
|
||||
for (auto &t : load_user_templates()) {
|
||||
if (t.game_name == game_name) {
|
||||
result.push_back(std::move(t));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,8 +11,10 @@ class Light {
|
||||
private:
|
||||
std::vector<Light> alternatives;
|
||||
std::string lightName;
|
||||
std::string lightCategory;
|
||||
std::string deviceIdentifier = "";
|
||||
unsigned int index = 0;
|
||||
bool is_temporary = false;
|
||||
|
||||
public:
|
||||
float last_state = 0.f;
|
||||
@@ -22,6 +24,8 @@ public:
|
||||
float override_state = 0.f;
|
||||
|
||||
explicit Light(std::string lightName) : lightName(std::move(lightName)) {};
|
||||
explicit Light(std::string lightName, std::string lightCategory) :
|
||||
lightName(std::move(lightName)), lightCategory(std::move(lightCategory)) {};
|
||||
|
||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||
|
||||
@@ -50,6 +54,14 @@ public:
|
||||
return this->lightName;
|
||||
}
|
||||
|
||||
inline const std::string &getCategory() const {
|
||||
return this->lightCategory;
|
||||
}
|
||||
|
||||
inline void setCategory(std::string category) {
|
||||
this->lightCategory = std::move(category);
|
||||
}
|
||||
|
||||
inline const std::string &getDeviceIdentifier() const {
|
||||
return this->deviceIdentifier;
|
||||
}
|
||||
@@ -65,4 +77,26 @@ public:
|
||||
inline void setIndex(unsigned int index) {
|
||||
this->index = index;
|
||||
}
|
||||
|
||||
inline bool isTemporary() const {
|
||||
return this->is_temporary;
|
||||
}
|
||||
|
||||
inline void setTemporary(bool is_temporary) {
|
||||
this->is_temporary = is_temporary;
|
||||
}
|
||||
|
||||
inline bool isValid() {
|
||||
// temporarily created by UI
|
||||
if (isTemporary()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// nothing is set
|
||||
if (getDeviceIdentifier().empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,6 +13,16 @@ enum class OptionType {
|
||||
Hex,
|
||||
};
|
||||
|
||||
enum class OptionPickerType {
|
||||
None,
|
||||
AsioDriver,
|
||||
EACard,
|
||||
CpuAffinity,
|
||||
FilePath,
|
||||
DirectoryPath,
|
||||
Monitor,
|
||||
};
|
||||
|
||||
struct OptionDefinition {
|
||||
std::string title;
|
||||
// unique identifier used for flag matching but also stored in config files
|
||||
@@ -32,6 +42,10 @@ struct OptionDefinition {
|
||||
bool sensitive = false;
|
||||
std::vector<std::pair<std::string, std::string>> elements = {};
|
||||
bool disabled = false;
|
||||
OptionPickerType picker = OptionPickerType::None;
|
||||
|
||||
// for OptionPickerType::FilePath
|
||||
std::string file_extension = "";
|
||||
};
|
||||
|
||||
class Option {
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
#define IDR_PATCHES 132
|
||||
#define IDR_README 133
|
||||
#define IDR_DSEGFONT 134
|
||||
#define IDR_CONTROLLER_PRESETS_BUILTIN 135
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include "screen_resize.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "external/rapidjson/pointer.h"
|
||||
#include "external/rapidjson/prettywriter.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
@@ -78,6 +80,10 @@ namespace cfg {
|
||||
root);
|
||||
|
||||
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
|
||||
if (this->enable_screen_resize) {
|
||||
log_misc("ScreenResize", "enabled by config file");
|
||||
}
|
||||
|
||||
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
|
||||
for (size_t i = 0; i < std::size(this->scene_settings); i++) {
|
||||
auto& scene = this->scene_settings[i];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
@@ -51,6 +52,9 @@ namespace cfg {
|
||||
bool enable_linear_filter = true;
|
||||
fullscreen_setting scene_settings[4];
|
||||
|
||||
// state
|
||||
std::atomic<bool> need_surface_clean = false;
|
||||
|
||||
// windowed mode sizing
|
||||
// Windows terminology:
|
||||
// window = rectangle including the frame
|
||||
@@ -69,6 +73,7 @@ namespace cfg {
|
||||
uint32_t init_client_height = 0;
|
||||
float init_client_aspect_ratio = 1.f;
|
||||
uint32_t init_window_style = 0;
|
||||
uint32_t init_window_style_ex = 0;
|
||||
uint32_t window_deco_width = 0;
|
||||
uint32_t window_deco_height = 0;
|
||||
|
||||
|
||||
+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)) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -16,15 +18,19 @@ namespace games::ccj {
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0xf8];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t data[0xe30];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_TBS {
|
||||
uint8_t data[0x4680];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB__NODEINFO {
|
||||
@@ -72,6 +78,12 @@ namespace games::ccj {
|
||||
AIO_IOB2_BI2X_AC1__OUTPUTDATA OutputData;
|
||||
};
|
||||
|
||||
// checked with UJK-001-2024060400
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe30);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_TBS) == 0x4680);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
|
||||
static void write_iccr_led(Lights::ccj_lights_t light, uint8_t value);
|
||||
|
||||
/*
|
||||
@@ -621,4 +633,6 @@ namespace games::ccj {
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
|
||||
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::ccj {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -65,12 +65,20 @@ 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);
|
||||
|
||||
#if SPICE64
|
||||
|
||||
// insert BI2X hooks
|
||||
bi2x_hook_init();
|
||||
|
||||
#endif
|
||||
|
||||
// insert trackball hooks
|
||||
trackball_hook_init();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "hooks/setupapihook.h"
|
||||
#include "hooks/sleephook.h"
|
||||
#include "hooks/input/dinput8/hook.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/fileutils.h"
|
||||
@@ -108,18 +109,32 @@ namespace games::ddr {
|
||||
log_info("ddr", "`{}` returned {}", cmd, result);
|
||||
} else {
|
||||
log_warning("ddr", "`{}` failed, returned {}", cmd, result);
|
||||
deferredlogs::defer_error_messages({
|
||||
"DDR codec registration failure",
|
||||
fmt::format(" {} failed to register with error {}", filename.string(), result)});
|
||||
}
|
||||
|
||||
if (!contains_only_ascii(file.path().string())) {
|
||||
log_warning(
|
||||
"ddr",
|
||||
"BAD PATH ERROR\n\n\n"
|
||||
"!!! !!!\n"
|
||||
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
|
||||
"!!! this may cause the game to crash! !!!\n"
|
||||
"!!! !!!\n"
|
||||
);
|
||||
}
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, [file]() {
|
||||
if (!contains_only_ascii(file.path().string())) {
|
||||
log_warning(
|
||||
"ddr",
|
||||
"BAD PATH ERROR\n\n"
|
||||
"!!! !!!\n"
|
||||
"!!! filesystem path to codec contains non-ASCII characters! !!!\n"
|
||||
"!!! this may cause failures when loading codecs, and !!!\n"
|
||||
"!!! potentially leading the game to crash! !!!\n"
|
||||
"!!! !!!\n"
|
||||
);
|
||||
|
||||
deferredlogs::defer_error_messages({
|
||||
"path to DDR codecs contain non-Latin characters",
|
||||
" this may lead to movies not playing, white screen issue,",
|
||||
" game hanging, or potentially a crash",
|
||||
" move the game to a directory that does not contain non-Latin characters",
|
||||
" if you encountered any issues"});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+114
-110
@@ -41,136 +41,140 @@ std::vector<Light> &games::ddr::get_lights() {
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("Dance Dance Revolution");
|
||||
|
||||
GameAPI::Lights::sortLights(
|
||||
&lights,
|
||||
"P1 Foot Left",
|
||||
"P1 Foot Up",
|
||||
"P1 Foot Right",
|
||||
"P1 Foot Down",
|
||||
"P2 Foot Left",
|
||||
"P2 Foot Up",
|
||||
"P2 Foot Right",
|
||||
"P2 Foot Down",
|
||||
"Spot Red",
|
||||
"Spot Blue",
|
||||
"Top Spot Red",
|
||||
"Top Spot Blue",
|
||||
"P1 Halogen Upper",
|
||||
"P1 Halogen Lower",
|
||||
"P2 Halogen Upper",
|
||||
"P2 Halogen Lower",
|
||||
"P1 Button",
|
||||
"P2 Button",
|
||||
"Neon",
|
||||
"HD P1 Start",
|
||||
"HD P1 Menu Left-Right",
|
||||
"HD P1 Menu Up-Down",
|
||||
"HD P2 Start",
|
||||
"HD P2 Menu Left-Right",
|
||||
"HD P2 Menu Up-Down",
|
||||
"HD P1 Speaker F R",
|
||||
"HD P1 Speaker F G",
|
||||
"HD P1 Speaker F B",
|
||||
"HD P1 Speaker W R",
|
||||
"HD P1 Speaker W G",
|
||||
"HD P1 Speaker W B",
|
||||
"HD P2 Speaker F R",
|
||||
"HD P2 Speaker F G",
|
||||
"HD P2 Speaker F B",
|
||||
"HD P2 Speaker W R",
|
||||
"HD P2 Speaker W G",
|
||||
"HD P2 Speaker W B",
|
||||
GameAPI::Lights::sortLightsWithCategory(
|
||||
&lights,
|
||||
{
|
||||
{"General", "P1 Foot Left"},
|
||||
{"General", "P1 Foot Up"},
|
||||
{"General", "P1 Foot Right"},
|
||||
{"General", "P1 Foot Down"},
|
||||
{"General", "P2 Foot Left"},
|
||||
{"General", "P2 Foot Up"},
|
||||
{"General", "P2 Foot Right"},
|
||||
{"General", "P2 Foot Down"},
|
||||
|
||||
"WHITE Speaker Top R",
|
||||
"WHITE Speaker Top G",
|
||||
"WHITE Speaker Top B",
|
||||
"WHITE Speaker Bottom R",
|
||||
"WHITE Speaker Bottom G",
|
||||
"WHITE Speaker Bottom B",
|
||||
"WHITE Woofer R",
|
||||
"WHITE Woofer G",
|
||||
"WHITE Woofer B",
|
||||
{"SD", "Spot Red"},
|
||||
{"SD", "Spot Blue"},
|
||||
{"SD", "Top Spot Red"},
|
||||
{"SD", "Top Spot Blue"},
|
||||
{"SD", "P1 Halogen Upper"},
|
||||
{"SD", "P1 Halogen Lower"},
|
||||
{"SD", "P2 Halogen Upper"},
|
||||
{"SD", "P2 Halogen Lower"},
|
||||
{"SD", "P1 Button"},
|
||||
{"SD", "P2 Button"},
|
||||
{"SD", "Neon"},
|
||||
|
||||
"GOLD P1 Menu Start",
|
||||
"GOLD P1 Menu Up",
|
||||
"GOLD P1 Menu Down",
|
||||
"GOLD P1 Menu Left",
|
||||
"GOLD P1 Menu Right",
|
||||
{"HD", "HD P1 Start"},
|
||||
{"HD", "HD P1 Menu Left-Right"},
|
||||
{"HD", "HD P1 Menu Up-Down"},
|
||||
{"HD", "HD P2 Start"},
|
||||
{"HD", "HD P2 Menu Left-Right"},
|
||||
{"HD", "HD P2 Menu Up-Down"},
|
||||
{"HD", "HD P1 Speaker F R"},
|
||||
{"HD", "HD P1 Speaker F G"},
|
||||
{"HD", "HD P1 Speaker F B"},
|
||||
{"HD", "HD P1 Speaker W R"},
|
||||
{"HD", "HD P1 Speaker W G"},
|
||||
{"HD", "HD P1 Speaker W B"},
|
||||
{"HD", "HD P2 Speaker F R"},
|
||||
{"HD", "HD P2 Speaker F G"},
|
||||
{"HD", "HD P2 Speaker F B"},
|
||||
{"HD", "HD P2 Speaker W R"},
|
||||
{"HD", "HD P2 Speaker W G"},
|
||||
{"HD", "HD P2 Speaker W B"},
|
||||
|
||||
"GOLD P2 Menu Start",
|
||||
"GOLD P2 Menu Up",
|
||||
"GOLD P2 Menu Down",
|
||||
"GOLD P2 Menu Left",
|
||||
"GOLD P2 Menu Right",
|
||||
{"WHITE", "WHITE Speaker Top R"},
|
||||
{"WHITE", "WHITE Speaker Top G"},
|
||||
{"WHITE", "WHITE Speaker Top B"},
|
||||
{"WHITE", "WHITE Speaker Bottom R"},
|
||||
{"WHITE", "WHITE Speaker Bottom G"},
|
||||
{"WHITE", "WHITE Speaker Bottom B"},
|
||||
{"WHITE", "WHITE Woofer R"},
|
||||
{"WHITE", "WHITE Woofer G"},
|
||||
{"WHITE", "WHITE Woofer B"},
|
||||
|
||||
"GOLD Title Panel Left",
|
||||
"GOLD Title Panel Center",
|
||||
"GOLD Title Panel Right",
|
||||
{"GOLD", "GOLD P1 Menu Start"},
|
||||
{"GOLD", "GOLD P1 Menu Up"},
|
||||
{"GOLD", "GOLD P1 Menu Down"},
|
||||
{"GOLD", "GOLD P1 Menu Left"},
|
||||
{"GOLD", "GOLD P1 Menu Right"},
|
||||
|
||||
"GOLD P1 Woofer Corner",
|
||||
"GOLD P2 Woofer Corner",
|
||||
{"GOLD", "GOLD P2 Menu Start"},
|
||||
{"GOLD", "GOLD P2 Menu Up"},
|
||||
{"GOLD", "GOLD P2 Menu Down"},
|
||||
{"GOLD", "GOLD P2 Menu Left"},
|
||||
{"GOLD", "GOLD P2 Menu Right"},
|
||||
|
||||
"GOLD P1 Card Unit R",
|
||||
"GOLD P1 Card Unit G",
|
||||
"GOLD P1 Card Unit B",
|
||||
{"GOLD", "GOLD Title Panel Left"},
|
||||
{"GOLD", "GOLD Title Panel Center"},
|
||||
{"GOLD", "GOLD Title Panel Right"},
|
||||
|
||||
"GOLD P2 Card Unit R",
|
||||
"GOLD P2 Card Unit G",
|
||||
"GOLD P2 Card Unit B",
|
||||
{"GOLD", "GOLD P1 Woofer Corner"},
|
||||
{"GOLD", "GOLD P2 Woofer Corner"},
|
||||
|
||||
"GOLD Top Panel Avg R",
|
||||
"GOLD Top Panel Avg G",
|
||||
"GOLD Top Panel Avg B",
|
||||
{"GOLD", "GOLD P1 Card Unit R"},
|
||||
{"GOLD", "GOLD P1 Card Unit G"},
|
||||
{"GOLD", "GOLD P1 Card Unit B"},
|
||||
|
||||
"GOLD Monitor Side Left Avg R",
|
||||
"GOLD Monitor Side Left Avg G",
|
||||
"GOLD Monitor Side Left Avg B",
|
||||
{"GOLD", "GOLD P2 Card Unit R"},
|
||||
{"GOLD", "GOLD P2 Card Unit G"},
|
||||
{"GOLD", "GOLD P2 Card Unit B"},
|
||||
|
||||
"GOLD Monitor Side Right Avg R",
|
||||
"GOLD Monitor Side Right Avg G",
|
||||
"GOLD Monitor Side Right Avg B",
|
||||
{"GOLD", "GOLD Top Panel Avg R"},
|
||||
{"GOLD", "GOLD Top Panel Avg G"},
|
||||
{"GOLD", "GOLD Top Panel Avg B"},
|
||||
|
||||
"GOLD P1 Foot Up Avg R",
|
||||
"GOLD P1 Foot Up Avg G",
|
||||
"GOLD P1 Foot Up Avg B",
|
||||
{"GOLD", "GOLD Monitor Side Left Avg R"},
|
||||
{"GOLD", "GOLD Monitor Side Left Avg G"},
|
||||
{"GOLD", "GOLD Monitor Side Left Avg B"},
|
||||
|
||||
"GOLD P1 Foot Down Avg R",
|
||||
"GOLD P1 Foot Down Avg G",
|
||||
"GOLD P1 Foot Down Avg B",
|
||||
{"GOLD", "GOLD Monitor Side Right Avg R"},
|
||||
{"GOLD", "GOLD Monitor Side Right Avg G"},
|
||||
{"GOLD", "GOLD Monitor Side Right Avg B"},
|
||||
|
||||
"GOLD P1 Foot Left Avg R",
|
||||
"GOLD P1 Foot Left Avg G",
|
||||
"GOLD P1 Foot Left Avg B",
|
||||
{"GOLD", "GOLD P1 Foot Up Avg R"},
|
||||
{"GOLD", "GOLD P1 Foot Up Avg G"},
|
||||
{"GOLD", "GOLD P1 Foot Up Avg B"},
|
||||
|
||||
"GOLD P1 Foot Right Avg R",
|
||||
"GOLD P1 Foot Right Avg G",
|
||||
"GOLD P1 Foot Right Avg B",
|
||||
{"GOLD", "GOLD P1 Foot Down Avg R"},
|
||||
{"GOLD", "GOLD P1 Foot Down Avg G"},
|
||||
{"GOLD", "GOLD P1 Foot Down Avg B"},
|
||||
|
||||
"GOLD P2 Foot Up Avg R",
|
||||
"GOLD P2 Foot Up Avg G",
|
||||
"GOLD P2 Foot Up Avg B",
|
||||
{"GOLD", "GOLD P1 Foot Left Avg R"},
|
||||
{"GOLD", "GOLD P1 Foot Left Avg G"},
|
||||
{"GOLD", "GOLD P1 Foot Left Avg B"},
|
||||
|
||||
"GOLD P2 Foot Down Avg R",
|
||||
"GOLD P2 Foot Down Avg G",
|
||||
"GOLD P2 Foot Down Avg B",
|
||||
{"GOLD", "GOLD P1 Foot Right Avg R"},
|
||||
{"GOLD", "GOLD P1 Foot Right Avg G"},
|
||||
{"GOLD", "GOLD P1 Foot Right Avg B"},
|
||||
|
||||
"GOLD P2 Foot Left Avg R",
|
||||
"GOLD P2 Foot Left Avg G",
|
||||
"GOLD P2 Foot Left Avg B",
|
||||
{"GOLD", "GOLD P2 Foot Up Avg R"},
|
||||
{"GOLD", "GOLD P2 Foot Up Avg G"},
|
||||
{"GOLD", "GOLD P2 Foot Up Avg B"},
|
||||
|
||||
"GOLD P2 Foot Right Avg R",
|
||||
"GOLD P2 Foot Right Avg G",
|
||||
"GOLD P2 Foot Right Avg B",
|
||||
{"GOLD", "GOLD P2 Foot Down Avg R"},
|
||||
{"GOLD", "GOLD P2 Foot Down Avg G"},
|
||||
{"GOLD", "GOLD P2 Foot Down Avg B"},
|
||||
|
||||
"GOLD P1 Stage Corner Up-Left",
|
||||
"GOLD P1 Stage Corner Up-Right",
|
||||
"GOLD P1 Stage Corner Down-Left",
|
||||
"GOLD P1 Stage Corner Down-Right",
|
||||
{"GOLD", "GOLD P2 Foot Left Avg R"},
|
||||
{"GOLD", "GOLD P2 Foot Left Avg G"},
|
||||
{"GOLD", "GOLD P2 Foot Left Avg B"},
|
||||
|
||||
"GOLD P2 Stage Corner Up-Left",
|
||||
"GOLD P2 Stage Corner Up-Right",
|
||||
"GOLD P2 Stage Corner Down-Left",
|
||||
"GOLD P2 Stage Corner Down-Right"
|
||||
{"GOLD", "GOLD P2 Foot Right Avg R"},
|
||||
{"GOLD", "GOLD P2 Foot Right Avg G"},
|
||||
{"GOLD", "GOLD P2 Foot Right Avg B"},
|
||||
|
||||
{"GOLD", "GOLD P1 Stage Corner Up-Left"},
|
||||
{"GOLD", "GOLD P1 Stage Corner Up-Right"},
|
||||
{"GOLD", "GOLD P1 Stage Corner Down-Left"},
|
||||
{"GOLD", "GOLD P1 Stage Corner Down-Right"},
|
||||
|
||||
{"GOLD", "GOLD P2 Stage Corner Up-Left"},
|
||||
{"GOLD", "GOLD P2 Stage Corner Up-Right"},
|
||||
{"GOLD", "GOLD P2 Stage Corner Down-Left"},
|
||||
{"GOLD", "GOLD P2 Stage Corner Down-Right"}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@ int games::ddr::DDRP3IOHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWri
|
||||
}
|
||||
|
||||
// use both sat spots for a neon pulse
|
||||
float value_neon = (light_bits & hd_mapping_bits[0] && hd_mapping_bits[1]) ? 1.f : 0.f;
|
||||
float value_neon = (light_bits & hd_mapping_bits[0] && light_bits & hd_mapping_bits[1]) ? 1.f : 0.f;
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::NEON], value_neon);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/memutils.h"
|
||||
#include "rgb_cam.h"
|
||||
|
||||
#pragma pack(push)
|
||||
|
||||
@@ -150,6 +151,7 @@ namespace games::drs {
|
||||
char DRS_TAPELED[DRS_TAPELED_COLS_TOTAL][3] {};
|
||||
bool DISABLE_TOUCH = false;
|
||||
bool TRANSPOSE_TOUCH = false;
|
||||
bool RGB_CAMERA_HOOK = false;
|
||||
|
||||
std::vector<TouchEvent> TOUCH_EVENTS;
|
||||
|
||||
@@ -313,6 +315,10 @@ namespace games::drs {
|
||||
} else {
|
||||
log_info("drs", "no native input method detected");
|
||||
}
|
||||
|
||||
if (RGB_CAMERA_HOOK) {
|
||||
init_rgb_camera_hook();
|
||||
}
|
||||
}
|
||||
|
||||
void DRSGame::detach() {
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace games::drs {
|
||||
extern std::vector<TouchEvent> TOUCH_EVENTS;
|
||||
extern bool DISABLE_TOUCH;
|
||||
extern bool TRANSPOSE_TOUCH;
|
||||
extern bool RGB_CAMERA_HOOK;
|
||||
|
||||
void fire_touches(drs_touch_t *events, size_t event_count);
|
||||
void start_touch();
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "games/drs/rgb_cam.h"
|
||||
|
||||
// set version to Windows 7 to enable Media Foundation functions
|
||||
#ifdef _WIN32_WINNT
|
||||
#undef _WIN32_WINNT
|
||||
#endif
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
// turn on C-style COM objects
|
||||
#define CINTERFACE
|
||||
|
||||
#include <mfobjects.h>
|
||||
#include <mfidl.h>
|
||||
#include <string>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "hooks/cfgmgr32hook.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
static VTBL_TYPE(IMFActivate, GetAllocatedString) GetAllocatedString_orig = nullptr;
|
||||
static decltype(MFEnumDeviceSources) *MFEnumDeviceSources_orig = nullptr;
|
||||
|
||||
namespace games::drs {
|
||||
|
||||
/*
|
||||
* Camera related stuff
|
||||
*/
|
||||
static std::wstring CAMERA0_ID;
|
||||
|
||||
static HRESULT WINAPI GetAllocatedString_hook(IMFActivate* This, REFGUID guidKey, LPWSTR *ppwszValue,
|
||||
UINT32 *pcchLength)
|
||||
{
|
||||
// call the original
|
||||
HRESULT result = GetAllocatedString_orig(This, guidKey, ppwszValue, pcchLength);
|
||||
|
||||
// log the cam name
|
||||
log_misc("drs::rgbcam", "obtained {}", ws2s(*ppwszValue));
|
||||
|
||||
// try first camera
|
||||
wchar_t *pwc = nullptr;
|
||||
if (CAMERA0_ID.length() == 23) {
|
||||
pwc = wcsstr(*ppwszValue, CAMERA0_ID.c_str());
|
||||
}
|
||||
|
||||
// check if camera could be identified
|
||||
if (pwc) {
|
||||
|
||||
// fake the USB IDs
|
||||
pwc[4] = L'2';
|
||||
pwc[5] = L'8';
|
||||
pwc[6] = L'8';
|
||||
pwc[7] = L'c';
|
||||
|
||||
pwc[13] = L'0';
|
||||
pwc[14] = L'0';
|
||||
pwc[15] = L'0';
|
||||
pwc[16] = L'2';
|
||||
|
||||
pwc[21] = L'0';
|
||||
pwc[22] = L'0';
|
||||
|
||||
log_misc("drs::rgbcam", "replaced {}", ws2s(*ppwszValue));
|
||||
}
|
||||
|
||||
// return original result
|
||||
return result;
|
||||
}
|
||||
|
||||
static void hook_camera(std::wstring camera_id, std::string camera_instance) {
|
||||
// don't hook if camera 0 is already hooked
|
||||
if (CAMERA0_ID.length() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// save the camera ID
|
||||
CAMERA0_ID = camera_id;
|
||||
|
||||
// cfgmgr hook
|
||||
CFGMGR32_HOOK_SETTING camera_setting;
|
||||
camera_setting.device_instance = 0xDEADBEEF;
|
||||
camera_setting.parent_instance = ~camera_setting.device_instance;
|
||||
camera_setting.device_id = "USB\\VEN_8086&DEV_8C2D";
|
||||
camera_setting.device_node_id = "USB\\VID_288C&PID_0002&MI_00\\?&????????&?&????";
|
||||
if (camera_instance.length() == 17) {
|
||||
for (int i = 0; i < 17; i++) {
|
||||
camera_setting.device_node_id[28 + i] = camera_instance[i];
|
||||
}
|
||||
}
|
||||
cfgmgr32hook_add(camera_setting);
|
||||
|
||||
log_info("drs::rgbcam", "hooked camera @ {}", ws2s(camera_id));
|
||||
}
|
||||
|
||||
static void hook_camera_vtable(IMFActivate *camera) {
|
||||
|
||||
// hook allocated string method for camera identification
|
||||
memutils::VProtectGuard camera_guard(camera->lpVtbl);
|
||||
camera->lpVtbl->GetAllocatedString = GetAllocatedString_hook;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI MFEnumDeviceSources_hook(IMFAttributes *pAttributes, IMFActivate ***pppSourceActivate,
|
||||
UINT32 *pcSourceActivate) {
|
||||
|
||||
// call original function
|
||||
HRESULT result_orig = MFEnumDeviceSources_orig(pAttributes, pppSourceActivate, pcSourceActivate);
|
||||
|
||||
// check for capture devices
|
||||
if (FAILED(result_orig) || !*pcSourceActivate) {
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
// get camera
|
||||
IMFActivate *camera = **pppSourceActivate;
|
||||
|
||||
// save original method for later use
|
||||
if (GetAllocatedString_orig == nullptr) {
|
||||
GetAllocatedString_orig = camera->lpVtbl->GetAllocatedString;
|
||||
}
|
||||
|
||||
// hook allocated string method for camera identification
|
||||
hook_camera_vtable(camera);
|
||||
|
||||
// get camera link
|
||||
LPWSTR camera_link_lpwstr;
|
||||
UINT32 camera_link_length;
|
||||
if (SUCCEEDED(GetAllocatedString_orig(
|
||||
camera,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
||||
&camera_link_lpwstr,
|
||||
&camera_link_length))) {
|
||||
|
||||
// cut name to make ID
|
||||
std::wstring camera_link_ws = std::wstring(camera_link_lpwstr);
|
||||
std::wstring camera_id = camera_link_ws.substr(8, 23);
|
||||
|
||||
log_info("drs::rgbcam", "found video capture device: {}", ws2s(camera_link_ws));
|
||||
|
||||
// only support cameras that start with \\?\usb
|
||||
if (!string_begins_with(camera_link_ws, L"\\\\?\\usb")) {
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
// get camera instance
|
||||
std::string camera_link = ws2s(camera_link_ws);
|
||||
std::string camera_instance = camera_link.substr(32, 17);
|
||||
|
||||
// hook the camera
|
||||
hook_camera(camera_id, camera_instance);
|
||||
} else {
|
||||
log_warning("drs::rgbcam", "failed to open camera");
|
||||
}
|
||||
|
||||
// return result
|
||||
return result_orig;
|
||||
}
|
||||
|
||||
void init_rgb_camera_hook() {
|
||||
log_info("drs::rgbcam", "initializing camera hook");
|
||||
|
||||
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
|
||||
|
||||
// camera media framework hook
|
||||
MFEnumDeviceSources_orig = detour::iat_try(
|
||||
"MFEnumDeviceSources", MFEnumDeviceSources_hook, avs::game::DLL_INSTANCE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace games::drs {
|
||||
void init_rgb_camera_hook();
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -17,24 +20,29 @@ namespace games::gitadora {
|
||||
// */
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t data[0xe18];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__SETTING {
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB5 {
|
||||
uint8_t data[0xb10];
|
||||
};
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0x100];
|
||||
};
|
||||
|
||||
struct AIO_IOB5_Y32D {
|
||||
uint8_t data[0x2e8];
|
||||
};
|
||||
|
||||
// struct AIO_IOB5_Y33I {
|
||||
// };
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1 {
|
||||
uint8_t data[0x4570];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB__NODEINFO {
|
||||
@@ -42,6 +50,7 @@ namespace games::gitadora {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_SCI_COMM_T {
|
||||
@@ -158,6 +167,14 @@ namespace games::gitadora {
|
||||
uint8_t unk[0xB1];
|
||||
};
|
||||
|
||||
// verified with M32-007-2025092400
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe18);
|
||||
static_assert(sizeof(AIO_NMGR_IOB5) == 0xb10);
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0x100);
|
||||
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
@@ -323,10 +340,38 @@ namespace games::gitadora {
|
||||
}
|
||||
|
||||
void __fastcall aioIob2Bi2xAC1_SetOutputData(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_CnPin, uint8_t i_Data) {
|
||||
|
||||
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_SetOutputData_orig(i_pNodeCtl, i_CnPin, i_Data);
|
||||
}
|
||||
|
||||
std::optional<Lights::gitadora_lights_t> light;
|
||||
switch (i_CnPin) {
|
||||
case 14:
|
||||
light = Lights::ArenaWooferR;
|
||||
break;
|
||||
case 15:
|
||||
light = Lights::ArenaWooferG;
|
||||
break;
|
||||
case 16:
|
||||
light = Lights::ArenaWooferB;
|
||||
break;
|
||||
case 17:
|
||||
light = Lights::ArenaCardR;
|
||||
break;
|
||||
case 18:
|
||||
light = Lights::ArenaCardG;
|
||||
break;
|
||||
case 19:
|
||||
light = Lights::ArenaCardB;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (light.has_value()) {
|
||||
auto &lights = games::gitadora::get_lights();
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights.at(light.value()), i_Data / 255.f);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIob2Bi2xAC1_SetTapeLedDataPart(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_TapeLedCh,
|
||||
@@ -336,6 +381,16 @@ namespace games::gitadora {
|
||||
if (i_pNodeCtl != aioIob2Bi2xAc1) {
|
||||
return aioIob2Bi2xAC1_SetTapeLedDataPart_orig(i_pNodeCtl, i_TapeLedCh, i_Offset, i_pData, i_cntTapeLed, i_bReverse);
|
||||
}
|
||||
|
||||
if (tapeledutils::is_enabled() &&
|
||||
i_TapeLedCh == 0 && i_Offset == 0 && i_cntTapeLed == 62) {
|
||||
|
||||
auto &lights = get_lights();
|
||||
const auto rgb = tapeledutils::pick_color_from_led_tape(i_pData, i_cntTapeLed);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgR], rgb.r);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgG], rgb.g);
|
||||
GameAPI::Lights::writeLight(RI_MGR, lights[Lights::ArenaTitleAvgB], rgb.b);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall aioIob2Bi2x_SetTapeLedDataLimit(AIO_IOB2_BI2X_AC1 *i_pNodeCtl, uint32_t i_Channel,
|
||||
@@ -551,6 +606,7 @@ namespace games::gitadora {
|
||||
}
|
||||
|
||||
// libaio-iob5_y32.dll
|
||||
|
||||
static AIO_IOB5_Y32D *__fastcall aioIob5Y32d_Create(AIO_NMGR_IOB5 *i_pSci, uint32_t i_bfMode) {
|
||||
|
||||
if (i_pSci == aioNmgrIob5) {
|
||||
@@ -661,4 +717,6 @@ namespace games::gitadora {
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_UpdateDevicesStatus",
|
||||
aioNodeCtl_UpdateDevicesStatus, &aioNodeCtl_UpdateDevicesStatus_orig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::gitadora {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
#include "cfg/configurator.h"
|
||||
#include "hooks/audio/mme.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/cpuutils.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "hooks/setupapihook.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
@@ -17,6 +20,11 @@ namespace games::gitadora {
|
||||
// settings
|
||||
bool TWOCHANNEL = false;
|
||||
std::optional<unsigned int> CAB_TYPE = std::nullopt;
|
||||
bool ARENA_SINGLE_WINDOW = false;
|
||||
bool P1_LEFTY = false;
|
||||
bool P2_LEFTY = false;
|
||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
||||
|
||||
/*
|
||||
* Prevent GitaDora from creating folders on F drive
|
||||
@@ -212,6 +220,23 @@ namespace games::gitadora {
|
||||
log_fatal("gitadora", "Cabinet type 3 (SD2) not supported on XG series");
|
||||
}
|
||||
#endif
|
||||
|
||||
// arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay
|
||||
// force scaling to make things usable
|
||||
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() &&
|
||||
GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
overlay::UI_SCALE_PERCENT = 250;
|
||||
}
|
||||
|
||||
// for guitar wail SOCD cleaning
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
|
||||
// for guitar picking
|
||||
if (PICK_ALGO.has_value()) {
|
||||
log_info("gitadora", "guitar pick SOCD algorithm: {}", static_cast<int>(PICK_ALGO.value()));
|
||||
} else {
|
||||
log_info("gitadora", "guitar pick SOCD algorithm: legacy");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +296,13 @@ namespace games::gitadora {
|
||||
|
||||
// volume change prevention
|
||||
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
|
||||
|
||||
// touch hook
|
||||
if (ARENA_SINGLE_WINDOW) {
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,18 @@
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/game.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
|
||||
// settings
|
||||
extern bool TWOCHANNEL;
|
||||
extern std::optional<unsigned int> CAB_TYPE;
|
||||
extern bool ARENA_SINGLE_WINDOW;
|
||||
extern bool P1_LEFTY;
|
||||
extern bool P2_LEFTY;
|
||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
||||
|
||||
class GitaDoraGame : public games::Game {
|
||||
public:
|
||||
@@ -38,4 +44,12 @@ namespace games::gitadora {
|
||||
);
|
||||
}
|
||||
|
||||
static inline bool is_player_lefty(size_t player) {
|
||||
if (player == 0) {
|
||||
return P1_LEFTY;
|
||||
} else if (player == 1) {
|
||||
return P2_LEFTY;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,17 +65,18 @@ std::vector<Button> &games::gitadora::get_buttons() {
|
||||
"Drum Help",
|
||||
"Drum Button Extra 1",
|
||||
"Drum Button Extra 2",
|
||||
|
||||
"Drum Left Cymbal",
|
||||
"Drum Hi-Hat",
|
||||
"Drum Hi-Hat Closed",
|
||||
"Drum Hi-Hat Half-Open",
|
||||
"Drum Left Pedal",
|
||||
"Drum Snare",
|
||||
"Drum Hi-Tom",
|
||||
"Drum Low-Tom",
|
||||
"Drum Right Cymbal",
|
||||
"Drum Bass Pedal",
|
||||
"Drum Left Cymbal",
|
||||
"Drum Left Pedal",
|
||||
"Drum Floor Tom"
|
||||
"Drum Low-Tom",
|
||||
"Drum Floor Tom",
|
||||
"Drum Right Cymbal",
|
||||
"Drum Hi-Hat Closed",
|
||||
"Drum Hi-Hat Half-Open"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,9 +108,18 @@ std::string games::gitadora::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"guitar:\n"
|
||||
"\n"
|
||||
" < R G B Y P --- Pick ] \n"
|
||||
"\n"
|
||||
" If you normally hold your guitar left-handed (frets on right hand):\n"
|
||||
" * here, hold the guitar as right-handed (frets on left hand),\n"
|
||||
" and bind the buttons, pick, and wail controls\n"
|
||||
" * in Options tab, turn on GitaDora Lefty Guitar\n"
|
||||
" * finally, use the in-game option to turn on LEFT mode\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"drums:\n"
|
||||
"\n"
|
||||
" LeftCymbal RightCymbal\n"
|
||||
" HiHat HiTom LowTom \n"
|
||||
" Snare FloorTom \n"
|
||||
@@ -117,10 +127,28 @@ std::string games::gitadora::get_buttons_help() {
|
||||
" Left Bass\n"
|
||||
" Pedal Pedal\n"
|
||||
"\n"
|
||||
"Drums are NOT velocity-sensitive!\n"
|
||||
" For MIDI drums with Open/Closed HiHat configurations or pads with\n"
|
||||
" multiple hit zones, ensure you bind all variation using the Pages\n"
|
||||
" button at the bottom."
|
||||
;
|
||||
}
|
||||
std::string games::gitadora::get_analogs_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"guitar:\n"
|
||||
"\n"
|
||||
"For MIDI drums with Open/Closed HiHat configurations, bind variations below. "
|
||||
"v2_drum algorithm might work better for those drums."
|
||||
"X axis: 0% when body is held facing monitor, 50% when flat on a table\n"
|
||||
"Y axis: 50% when held horizontal, 0% when neck is raised, 100% pointing down\n"
|
||||
"Z axis: 50% at rest, 100% when swinging neck toward monitor (only used in XG series)\n"
|
||||
"\n"
|
||||
"You need both X and Y axis for up/down wail to work correctly in-game.\n"
|
||||
"If you only have Y axis, consider using digital wailing instead.\n"
|
||||
"\n"
|
||||
"Ensure you clear all analog bindings if you want to use digital wailing.\n"
|
||||
"\n"
|
||||
"If you hold your guitar left-handed (frets on the right hand),\n"
|
||||
"analog bindings will likely not work as intended.\n"
|
||||
"Consider using digital wailing in Buttons tab."
|
||||
;
|
||||
}
|
||||
|
||||
@@ -130,88 +158,102 @@ std::vector<Light> &games::gitadora::get_lights() {
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("GitaDora");
|
||||
|
||||
GameAPI::Lights::sortLights(&lights,
|
||||
"Guitar P1 Motor",
|
||||
"Guitar P2 Motor",
|
||||
GameAPI::Lights::sortLightsWithCategory(&lights,
|
||||
{
|
||||
{"Guitar", "Guitar P1 Motor"},
|
||||
{"Guitar", "Guitar P2 Motor"},
|
||||
|
||||
"P1 Start",
|
||||
"P1 Menu Up Down (DX)",
|
||||
"P1 Menu Left Right (DX)",
|
||||
"P1 Help (DX)",
|
||||
{"Menu", "P1 Start"},
|
||||
{"Menu", "P1 Menu Up Down (DX)"},
|
||||
{"Menu", "P1 Menu Left Right (DX)"},
|
||||
{"Menu", "P1 Help (DX)"},
|
||||
|
||||
"P2 Start",
|
||||
"P2 Menu Up Down (DX)",
|
||||
"P2 Menu Left Right (DX)",
|
||||
"P2 Help (DX)",
|
||||
{"Menu", "P2 Start"},
|
||||
{"Menu", "P2 Menu Up Down (DX)"},
|
||||
{"Menu", "P2 Menu Left Right (DX)"},
|
||||
{"Menu", "P2 Help (DX)"},
|
||||
|
||||
"Drum Left Cymbal",
|
||||
"Drum Hi-Hat",
|
||||
"Drum Snare",
|
||||
"Drum High Tom",
|
||||
"Drum Low Tom",
|
||||
"Drum Floor Tom",
|
||||
"Drum Right Cymbal",
|
||||
{"Drums", "Drum Left Cymbal"},
|
||||
{"Drums", "Drum Hi-Hat"},
|
||||
{"Drums", "Drum Snare"},
|
||||
{"Drums", "Drum High Tom"},
|
||||
{"Drums", "Drum Low Tom"},
|
||||
{"Drums", "Drum Floor Tom"},
|
||||
{"Drums", "Drum Right Cymbal"},
|
||||
|
||||
"Drum Woofer R",
|
||||
"Drum Woofer G",
|
||||
"Drum Woofer B",
|
||||
{"DX/SD", "Drum Woofer R"},
|
||||
{"DX/SD", "Drum Woofer G"},
|
||||
{"DX/SD", "Drum Woofer B"},
|
||||
|
||||
"Drum Stage R (DX)",
|
||||
"Drum Stage G (DX)",
|
||||
"Drum Stage B (DX)",
|
||||
{"DX/SD", "Drum Stage R (DX)"},
|
||||
{"DX/SD", "Drum Stage G (DX)"},
|
||||
{"DX/SD", "Drum Stage B (DX)"},
|
||||
|
||||
"Spot Left (DX)",
|
||||
"Spot Right (DX)",
|
||||
"Spot Center Left (DX)",
|
||||
"Spot Center Right (DX)",
|
||||
{"DX/SD", "Spot Left (DX)"},
|
||||
{"DX/SD", "Spot Right (DX)"},
|
||||
{"DX/SD", "Spot Center Left (DX)"},
|
||||
{"DX/SD", "Spot Center Right (DX)"},
|
||||
|
||||
"Drum Spot Rear Left (DX)",
|
||||
"Drum Spot Rear Right (DX)",
|
||||
{"DX/SD", "Drum Spot Rear Left (DX)"},
|
||||
{"DX/SD", "Drum Spot Rear Right (DX)"},
|
||||
|
||||
"Guitar Lower Left R (DX)",
|
||||
"Guitar Lower Left G (DX)",
|
||||
"Guitar Lower Left B (DX)",
|
||||
"Guitar Lower Right R (DX)",
|
||||
"Guitar Lower Right G (DX)",
|
||||
"Guitar Lower Right B (DX)",
|
||||
{"DX/SD", "Guitar Lower Left R (DX)"},
|
||||
{"DX/SD", "Guitar Lower Left G (DX)"},
|
||||
{"DX/SD", "Guitar Lower Left B (DX)"},
|
||||
{"DX/SD", "Guitar Lower Right R (DX)"},
|
||||
{"DX/SD", "Guitar Lower Right G (DX)"},
|
||||
{"DX/SD", "Guitar Lower Right B (DX)"},
|
||||
|
||||
"Guitar Left Speaker Upper R (DX)",
|
||||
"Guitar Left Speaker Upper G (DX)",
|
||||
"Guitar Left Speaker Upper B (DX)",
|
||||
"Guitar Left Speaker Mid Up Left R (DX)",
|
||||
"Guitar Left Speaker Mid Up Left G (DX)",
|
||||
"Guitar Left Speaker Mid Up Left B (DX)",
|
||||
"Guitar Left Speaker Mid Up Right R (DX)",
|
||||
"Guitar Left Speaker Mid Up Right G (DX)",
|
||||
"Guitar Left Speaker Mid Up Right B (DX)",
|
||||
"Guitar Left Speaker Mid Low Left R (DX)",
|
||||
"Guitar Left Speaker Mid Low Left G (DX)",
|
||||
"Guitar Left Speaker Mid Low Left B (DX)",
|
||||
"Guitar Left Speaker Mid Low Right R (DX)",
|
||||
"Guitar Left Speaker Mid Low Right G (DX)",
|
||||
"Guitar Left Speaker Mid Low Right B (DX)",
|
||||
"Guitar Left Speaker Lower R (DX)",
|
||||
"Guitar Left Speaker Lower G (DX)",
|
||||
"Guitar Left Speaker Lower B (DX)",
|
||||
{"DX/SD", "Guitar Left Speaker Upper R (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Upper G (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Upper B (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Up Left R (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Up Left G (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Up Left B (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Up Right R (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Up Right G (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Up Right B (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Low Left R (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Low Left G (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Low Left B (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Low Right R (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Low Right G (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Mid Low Right B (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Lower R (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Lower G (DX)"},
|
||||
{"DX/SD", "Guitar Left Speaker Lower B (DX)"},
|
||||
|
||||
"Guitar Right Speaker Upper R (DX)",
|
||||
"Guitar Right Speaker Upper G (DX)",
|
||||
"Guitar Right Speaker Upper B (DX)",
|
||||
"Guitar Right Speaker Mid Up Left R (DX)",
|
||||
"Guitar Right Speaker Mid Up Left G (DX)",
|
||||
"Guitar Right Speaker Mid Up Left B (DX)",
|
||||
"Guitar Right Speaker Mid Up Right R (DX)",
|
||||
"Guitar Right Speaker Mid Up Right G (DX)",
|
||||
"Guitar Right Speaker Mid Up Right B (DX)",
|
||||
"Guitar Right Speaker Mid Low Left R (DX)",
|
||||
"Guitar Right Speaker Mid Low Left G (DX)",
|
||||
"Guitar Right Speaker Mid Low Left B (DX)",
|
||||
"Guitar Right Speaker Mid Low Right R (DX)",
|
||||
"Guitar Right Speaker Mid Low Right G (DX)",
|
||||
"Guitar Right Speaker Mid Low Right B (DX)",
|
||||
"Guitar Right Speaker Lower R (DX)",
|
||||
"Guitar Right Speaker Lower G (DX)",
|
||||
"Guitar Right Speaker Lower B (DX)"
|
||||
{"DX/SD", "Guitar Right Speaker Upper R (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Upper G (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Upper B (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Up Left R (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Up Left G (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Up Left B (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Up Right R (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Up Right G (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Up Right B (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Low Left R (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Low Left G (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Low Left B (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Low Right R (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Low Right G (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Mid Low Right B (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Lower R (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Lower G (DX)"},
|
||||
{"DX/SD", "Guitar Right Speaker Lower B (DX)"},
|
||||
|
||||
{"Arena Model", "Title Average R (Arena)"},
|
||||
{"Arena Model", "Title Average G (Arena)"},
|
||||
{"Arena Model", "Title Average B (Arena)"},
|
||||
|
||||
{"Arena Model", "Woofer R (Arena)"},
|
||||
{"Arena Model", "Woofer G (Arena)"},
|
||||
{"Arena Model", "Woofer B (Arena)"},
|
||||
|
||||
{"Arena Model", "Card Reader R (Arena)"},
|
||||
{"Arena Model", "Card Reader G (Arena)"},
|
||||
{"Arena Model", "Card Reader B (Arena)"}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,18 +66,21 @@ namespace games::gitadora {
|
||||
DrumHelp,
|
||||
DrumButtonExtra1,
|
||||
DrumButtonExtra2,
|
||||
// note: this is the order they appear in the test menu
|
||||
// note: this is the order of drums in the default layout
|
||||
DrumLeftCymbal,
|
||||
DrumHiHat,
|
||||
DrumHiHatClosed,
|
||||
DrumHiHatHalfOpen,
|
||||
DrumLeftPedal,
|
||||
DrumSnare,
|
||||
DrumHiTom,
|
||||
DrumLowTom,
|
||||
DrumRightCymbal,
|
||||
DrumBassPedal,
|
||||
DrumLeftCymbal,
|
||||
DrumLeftPedal,
|
||||
DrumFloorTom
|
||||
DrumLowTom,
|
||||
DrumFloorTom,
|
||||
DrumRightCymbal,
|
||||
|
||||
// these only exist for back compat
|
||||
// (created before it was possible to map multiple input to a button)
|
||||
DrumHiHatClosed,
|
||||
DrumHiHatHalfOpen
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,13 +194,25 @@ namespace games::gitadora {
|
||||
GuitarRightSpeakerMidLowRightB,
|
||||
GuitarRightSpeakerLowerR,
|
||||
GuitarRightSpeakerLowerG,
|
||||
GuitarRightSpeakerLowerB
|
||||
GuitarRightSpeakerLowerB,
|
||||
|
||||
// arena model (common for both guitar and drum)
|
||||
ArenaTitleAvgR,
|
||||
ArenaTitleAvgG,
|
||||
ArenaTitleAvgB,
|
||||
ArenaWooferR,
|
||||
ArenaWooferG,
|
||||
ArenaWooferB,
|
||||
ArenaCardR,
|
||||
ArenaCardG,
|
||||
ArenaCardB,
|
||||
} gitadora_lights_t;
|
||||
}
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::string get_analogs_help();
|
||||
std::vector<Analog> &get_analogs();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/utils.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "io.h"
|
||||
#include "j33i.h"
|
||||
|
||||
|
||||
#define GUITAR_BTN_R 0x7
|
||||
#define GUITAR_BTN_G 0x6
|
||||
#define GUITAR_BTN_B 0x5
|
||||
@@ -91,28 +92,50 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
|
||||
payload.buttons |= 1 << GUITAR_BTN_P;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp])) {
|
||||
if (!GFDM_GF_PICK_STATE_UP) {
|
||||
GFDM_GF_PICK_STATE_UP = true;
|
||||
payload.buttons |= 1 << GUITAR_PICK_UP;
|
||||
|
||||
const auto pick_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickUp]);
|
||||
const auto pick_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown]);
|
||||
if (!games::gitadora::PICK_ALGO.has_value()) {
|
||||
// legacy behavior (detect rising edges only, input for 1 frame)
|
||||
if (pick_up) {
|
||||
if (!GFDM_GF_PICK_STATE_UP) {
|
||||
GFDM_GF_PICK_STATE_UP = true;
|
||||
payload.buttons |= 1 << GUITAR_PICK_UP;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_UP = false;
|
||||
}
|
||||
|
||||
if (pick_down) {
|
||||
if (!GFDM_GF_PICK_STATE_DOWN) {
|
||||
GFDM_GF_PICK_STATE_DOWN = true;
|
||||
payload.buttons |= 1 << GUITAR_PICK_DOWN;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_DOWN = false;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_UP = false;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1PickDown])) {
|
||||
if (!GFDM_GF_PICK_STATE_DOWN) {
|
||||
GFDM_GF_PICK_STATE_DOWN = true;
|
||||
const auto socd = socd::socd_clean(
|
||||
0,
|
||||
pick_up,
|
||||
pick_down,
|
||||
get_performance_milliseconds(),
|
||||
games::gitadora::PICK_ALGO.value()
|
||||
);
|
||||
if (socd == socd::SocdCCW) {
|
||||
payload.buttons |= 1 << GUITAR_PICK_UP;
|
||||
} else if (socd == socd::SocdCW) {
|
||||
payload.buttons |= 1 << GUITAR_PICK_DOWN;
|
||||
} else if (socd == socd::SocdBoth) {
|
||||
payload.buttons |= 1 << GUITAR_PICK_UP;
|
||||
payload.buttons |= 1 << GUITAR_PICK_DOWN;
|
||||
}
|
||||
} else {
|
||||
GFDM_GF_PICK_STATE_DOWN = false;
|
||||
}
|
||||
|
||||
auto &analogs = get_analogs();
|
||||
|
||||
// get x,y,z analog values [-0.5f, 0.5f], centered at 0.f
|
||||
float x = 0.f;
|
||||
// get x,y,z analog values [-0.5f, 0.5f]
|
||||
float x = is_player_lefty(0) ? 0.3f : -0.5f;
|
||||
auto x_analog = analogs[Analogs::GuitarP1WailX];
|
||||
if (x_analog.isSet()) {
|
||||
x = GameAPI::Analogs::getState(RI_MGR, x_analog) - 0.5f;
|
||||
@@ -123,11 +146,6 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
|
||||
if (y_analog.isSet()) {
|
||||
y = GameAPI::Analogs::getState(RI_MGR, y_analog) - 0.5f;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailUp])) {
|
||||
y = -0.5f;
|
||||
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailDown])) {
|
||||
y = 0.5f;
|
||||
}
|
||||
|
||||
float z = 0.f;
|
||||
auto z_analog = analogs[Analogs::GuitarP1WailZ];
|
||||
@@ -135,6 +153,31 @@ bool games::gitadora::J33ISerialDevice::parse_msg(
|
||||
z = GameAPI::Analogs::getState(RI_MGR, z_analog) - 0.5f;
|
||||
}
|
||||
|
||||
// handle digital wail up/down
|
||||
const auto wail_up = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailUp]);
|
||||
const auto wail_down = GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::GuitarP1WailDown]);
|
||||
const auto wail_result =
|
||||
socd::get_guitar_wail(0, wail_up, wail_down, get_performance_milliseconds());
|
||||
if (!is_player_lefty(0)) {
|
||||
// righty
|
||||
if (wail_result == socd::TiltUp) {
|
||||
x = 0;
|
||||
y = -0.5f;
|
||||
} else if (wail_result == socd::TiltDown) {
|
||||
x = -0.5f;
|
||||
y = 0.5f;
|
||||
}
|
||||
} else {
|
||||
// lefty
|
||||
if (wail_result == socd::TiltUp) {
|
||||
x = 0.f;
|
||||
y = 0.f;
|
||||
} else if (wail_result == socd::TiltDown) {
|
||||
x = 0.5f;
|
||||
y = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
// convert x,y,z to unsigned integer values that the game expects
|
||||
const uint16_t x_int = x * ARENA_MODEL_WAIL_MAX * 2;
|
||||
const uint16_t y_int = y * ARENA_MODEL_WAIL_MAX * 2;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -24,17 +26,28 @@ namespace games::iidx {
|
||||
uint8_t data[0x10];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t dummy0[0x50];
|
||||
struct AIO_NMGR_IOB2_VTABLE {
|
||||
// other functions here, but they never get called
|
||||
uint8_t dummy[0x50];
|
||||
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
|
||||
uint8_t dummy1[0x9A0];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
AIO_NMGR_IOB2_VTABLE *vptr;
|
||||
uint8_t dummy[0x9F0];
|
||||
};
|
||||
|
||||
// confirmed 0x9F8 in iidx27, iidx32 aioNMgrIob2_Create
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
|
||||
|
||||
struct AIO_IOB2_BI2X_TDJ {
|
||||
// who knows
|
||||
uint8_t data[0x13F8];
|
||||
};
|
||||
|
||||
// confirmed 0x13F8 in iidx27, iidx32 in aioIob2Bi2xTDJ_Create
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_TDJ) == 0x13F8);
|
||||
|
||||
struct AIO_IOB2_BI2X_TDJ__DEVSTATUS {
|
||||
// of course you could work with variables here
|
||||
uint8_t buffer[0xCA];
|
||||
@@ -78,7 +91,7 @@ namespace games::iidx {
|
||||
// libaio-iob.dll
|
||||
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
|
||||
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
|
||||
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
|
||||
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
@@ -443,7 +456,7 @@ namespace games::iidx {
|
||||
}
|
||||
}
|
||||
|
||||
static AC_HNDLIF* __fastcall aioIob2Bi2X_OpenSciUsbCdc(uint8_t device_num) {
|
||||
static AC_HNDLIF* __fastcall aioIob2Bi2x_OpenSciUsbCdc(uint8_t device_num) {
|
||||
if (acHndlif == nullptr) {
|
||||
acHndlif = new AC_HNDLIF;
|
||||
memset(acHndlif->data, 0x0, sizeof(acHndlif->data));
|
||||
@@ -456,14 +469,16 @@ namespace games::iidx {
|
||||
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
|
||||
};
|
||||
|
||||
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
|
||||
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
|
||||
if (aioNmgrIob2 == nullptr) {
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2;
|
||||
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
|
||||
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2{};
|
||||
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
|
||||
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||
}
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create");
|
||||
return &aioNmgrIob2;
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
|
||||
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
|
||||
|
||||
return aioNmgrIob2;
|
||||
}
|
||||
|
||||
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
|
||||
@@ -520,10 +535,12 @@ namespace games::iidx {
|
||||
// hook IOB
|
||||
const auto libaioIobDll = "libaio-iob.dll";
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_OpenSciUsbCdc",
|
||||
aioIob2Bi2X_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
aioIob2Bi2x_OpenSciUsbCdc, &aioIob2Bi2x_OpenSciUsbCdc_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioIob2Bi2x_WriteFirmGetState",
|
||||
aioIob2Bi2x_WriteFirmGetState, &aioIob2Bi2x_WriteFirmGetState_orig);
|
||||
detour::trampoline_try(libaioIobDll, "aioNMgrIob2_Create",
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::iidx {
|
||||
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "util/libutils.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "launcher/signal.h"
|
||||
|
||||
@@ -80,6 +82,7 @@ namespace games::iidx {
|
||||
bool IIDXIO_LED_TICKER_READONLY = false;
|
||||
std::mutex IIDX_LED_TICKER_LOCK;
|
||||
bool IIDX_TDJ_MONITOR_WARNING = false;
|
||||
iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE = iidx_aio_emulation_state::unknown;
|
||||
|
||||
// io
|
||||
static bool HAS_LIBAIO; // this is how we detect iidx27+
|
||||
@@ -251,6 +254,8 @@ namespace games::iidx {
|
||||
}
|
||||
}
|
||||
|
||||
#if SPICE64
|
||||
|
||||
typedef void* (*aioIob2Bi2x_CreateWriteFirmContext_t)(unsigned int, int);
|
||||
static aioIob2Bi2x_CreateWriteFirmContext_t aioIob2Bi2x_CreateWriteFirmContext_orig = nullptr;
|
||||
|
||||
@@ -269,6 +274,8 @@ namespace games::iidx {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
|
||||
logger::hook_add(log_hook, this);
|
||||
}
|
||||
@@ -319,6 +326,8 @@ namespace games::iidx {
|
||||
devicehook_add(new IIDXFMSerialHandle());
|
||||
}
|
||||
|
||||
#if SPICE64
|
||||
|
||||
// check for new I/O DLL
|
||||
auto aio = libutils::try_library("libaio.dll");
|
||||
if (aio != nullptr) {
|
||||
@@ -384,6 +393,8 @@ namespace games::iidx {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (hooks::audio::ENABLED) {
|
||||
apply_audio_hacks();
|
||||
} else {
|
||||
@@ -443,7 +454,7 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
// windowed subscreen, enabled by default, unless turned off by user
|
||||
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool()) {
|
||||
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool() && TDJ_MODE) {
|
||||
GRAPHICS_IIDX_WSUB = true;
|
||||
}
|
||||
|
||||
@@ -462,7 +473,7 @@ namespace games::iidx {
|
||||
"!!! please do the following instead: !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! Revert your changes to XML file so it says !!!\n"
|
||||
"!!! <model __type=\"str\">LDJ</model> !!!\n"
|
||||
"!!! <model __type=\"str\">LDJ</model> !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag !!!\n"
|
||||
"!!! in command line !!!\n"
|
||||
@@ -475,30 +486,40 @@ 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"
|
||||
});
|
||||
}
|
||||
|
||||
// socd
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
|
||||
if (options->at(launcher::Options::IIDXDigitalTTSocd).is_active()) {
|
||||
if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "last") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
} else if (options->at(launcher::Options::IIDXDigitalTTSocd).value_text() == "first") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferFirst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IIDXGame::detach() {
|
||||
@@ -722,13 +743,12 @@ namespace games::iidx {
|
||||
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
|
||||
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
|
||||
|
||||
// TT+
|
||||
if (ttp)
|
||||
const auto tt_socd = socd::socd_clean(player, ttm, ttp, get_performance_milliseconds());
|
||||
if (tt_socd == socd::SocdCW) {
|
||||
IIDXIO_TT_STATE[player] += change;
|
||||
|
||||
// TT-
|
||||
if (ttm)
|
||||
} else if (tt_socd == socd::SocdCCW) {
|
||||
IIDXIO_TT_STATE[player] -= change;
|
||||
}
|
||||
|
||||
// TT+/-
|
||||
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
|
||||
@@ -798,7 +818,7 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
bool is_tdj_fhd() {
|
||||
return TDJ_MODE && avs::game::is_ext(2022101900, MAXINT);
|
||||
return TDJ_MODE && avs::game::is_ext(2022101900, INT_MAX);
|
||||
}
|
||||
|
||||
void apply_audio_hacks() {
|
||||
@@ -864,12 +884,12 @@ namespace games::iidx {
|
||||
|
||||
// patch iidx32+ for asio compatibility
|
||||
// only do this if NOT wasapi (as opposed to checking if it's asio)
|
||||
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
|
||||
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
|
||||
// other devices as a XONAR, don't check for the exact string (common ASIO workaround for INF)
|
||||
if (avs::game::is_ext(2024090100, MAXINT) &&
|
||||
if (avs::game::is_ext(2024090100, INT_MAX) &&
|
||||
!(SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() &&
|
||||
SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi")) {
|
||||
|
||||
|
||||
// in iidx32 final:
|
||||
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
|
||||
// 48 8b 4b 08 mov rcx,QWORD PTR [rbx+0x8]
|
||||
@@ -915,6 +935,8 @@ namespace games::iidx {
|
||||
return;
|
||||
}
|
||||
|
||||
CURRENT_IO_EMULATION_STATE = state;
|
||||
|
||||
// log_warning below could be turned into log_fatal in the future once we gain confidence
|
||||
// that this isn't triggering when it's not supposed to
|
||||
if (state == iidx_aio_emulation_state::bi2a_com2 && TDJ_MODE) {
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
|
||||
namespace games::iidx {
|
||||
|
||||
enum class iidx_aio_emulation_state {
|
||||
unknown,
|
||||
bi2a_com2,
|
||||
bi2x_hook
|
||||
};
|
||||
|
||||
// settings
|
||||
|
||||
extern bool FLIP_CAMS;
|
||||
@@ -38,6 +44,7 @@ namespace games::iidx {
|
||||
constexpr int IIDX_TAPELED_TOTAL = 17;
|
||||
// data mapping
|
||||
extern tapeledutils::tape_led TAPELED_MAPPING[IIDX_TAPELED_TOTAL];
|
||||
extern iidx_aio_emulation_state CURRENT_IO_EMULATION_STATE;
|
||||
|
||||
class IIDXGame : public games::Game {
|
||||
public:
|
||||
@@ -64,9 +71,5 @@ namespace games::iidx {
|
||||
bool is_tdj_fhd();
|
||||
void apply_audio_hacks();
|
||||
|
||||
enum class iidx_aio_emulation_state {
|
||||
bi2a_com2,
|
||||
bi2x_hook
|
||||
};
|
||||
void update_io_emulation_state(iidx_aio_emulation_state state);
|
||||
}
|
||||
|
||||
@@ -83,103 +83,105 @@ std::vector<Light> &games::iidx::get_lights() {
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("Beatmania IIDX");
|
||||
|
||||
GameAPI::Lights::sortLights(
|
||||
GameAPI::Lights::sortLightsWithCategory(
|
||||
&lights,
|
||||
"P1 1",
|
||||
"P1 2",
|
||||
"P1 3",
|
||||
"P1 4",
|
||||
"P1 5",
|
||||
"P1 6",
|
||||
"P1 7",
|
||||
"P2 1",
|
||||
"P2 2",
|
||||
"P2 3",
|
||||
"P2 4",
|
||||
"P2 5",
|
||||
"P2 6",
|
||||
"P2 7",
|
||||
"P1 Start",
|
||||
"P2 Start",
|
||||
"VEFX",
|
||||
"Effect",
|
||||
"Spot Light 1",
|
||||
"Spot Light 2",
|
||||
"Spot Light 3",
|
||||
"Spot Light 4",
|
||||
"Spot Light 5",
|
||||
"Spot Light 6",
|
||||
"Spot Light 7",
|
||||
"Spot Light 8",
|
||||
"Neon Lamp",
|
||||
"Woofer R",
|
||||
"Woofer G",
|
||||
"Woofer B",
|
||||
"IC Card Reader P1 R",
|
||||
"IC Card Reader P1 G",
|
||||
"IC Card Reader P1 B",
|
||||
"IC Card Reader P2 R",
|
||||
"IC Card Reader P2 G",
|
||||
"IC Card Reader P2 B",
|
||||
"Turntable P1 R",
|
||||
"Turntable P1 G",
|
||||
"Turntable P1 B",
|
||||
"Turntable P2 R",
|
||||
"Turntable P2 G",
|
||||
"Turntable P2 B",
|
||||
"Turntable P1 Resistance",
|
||||
"Turntable P2 Resistance",
|
||||
"Stage Left Avg R",
|
||||
"Stage Left Avg G",
|
||||
"Stage Left Avg B",
|
||||
"Stage Right Avg R",
|
||||
"Stage Right Avg G",
|
||||
"Stage Right Avg B",
|
||||
"Cabinet Left Avg R",
|
||||
"Cabinet Left Avg G",
|
||||
"Cabinet Left Avg B",
|
||||
"Cabinet Right Avg R",
|
||||
"Cabinet Right Avg G",
|
||||
"Cabinet Right Avg B",
|
||||
"Control Panel Under Avg R",
|
||||
"Control Panel Under Avg G",
|
||||
"Control Panel Under Avg B",
|
||||
"Ceiling Left Avg R",
|
||||
"Ceiling Left Avg G",
|
||||
"Ceiling Left Avg B",
|
||||
"Title Left Avg R",
|
||||
"Title Left Avg G",
|
||||
"Title Left Avg B",
|
||||
"Title Right Avg R",
|
||||
"Title Right Avg G",
|
||||
"Title Right Avg B",
|
||||
"Ceiling Right Avg R",
|
||||
"Ceiling Right Avg G",
|
||||
"Ceiling Right Avg B",
|
||||
"Touch Panel Left Avg R",
|
||||
"Touch Panel Left Avg G",
|
||||
"Touch Panel Left Avg B",
|
||||
"Touch Panel Right Avg R",
|
||||
"Touch Panel Right Avg G",
|
||||
"Touch Panel Right Avg B",
|
||||
"Side Panel Left Inner Avg R",
|
||||
"Side Panel Left Inner Avg G",
|
||||
"Side Panel Left Inner Avg B",
|
||||
"Side Panel Left Outer Avg R",
|
||||
"Side Panel Left Outer Avg G",
|
||||
"Side Panel Left Outer Avg B",
|
||||
"Side Panel Left Avg R",
|
||||
"Side Panel Left Avg G",
|
||||
"Side Panel Left Avg B",
|
||||
"Side Panel Right Outer Avg R",
|
||||
"Side Panel Right Outer Avg G",
|
||||
"Side Panel Right Outer Avg B",
|
||||
"Side Panel Right Inner Avg R",
|
||||
"Side Panel Right Inner Avg G",
|
||||
"Side Panel Right Inner Avg B",
|
||||
"Side Panel Right Avg R",
|
||||
"Side Panel Right Avg G",
|
||||
"Side Panel Right Avg B"
|
||||
{
|
||||
{"Common", "P1 1"},
|
||||
{"Common", "P1 2"},
|
||||
{"Common", "P1 3"},
|
||||
{"Common", "P1 4"},
|
||||
{"Common", "P1 5"},
|
||||
{"Common", "P1 6"},
|
||||
{"Common", "P1 7"},
|
||||
{"Common", "P2 1"},
|
||||
{"Common", "P2 2"},
|
||||
{"Common", "P2 3"},
|
||||
{"Common", "P2 4"},
|
||||
{"Common", "P2 5"},
|
||||
{"Common", "P2 6"},
|
||||
{"Common", "P2 7"},
|
||||
{"Common", "P1 Start"},
|
||||
{"Common", "P2 Start"},
|
||||
{"Common", "VEFX"},
|
||||
{"Common", "Effect"},
|
||||
{"DX", "Spot Light 1"},
|
||||
{"DX", "Spot Light 2"},
|
||||
{"DX", "Spot Light 3"},
|
||||
{"DX", "Spot Light 4"},
|
||||
{"DX", "Spot Light 5"},
|
||||
{"DX", "Spot Light 6"},
|
||||
{"DX", "Spot Light 7"},
|
||||
{"DX", "Spot Light 8"},
|
||||
{"DX", "Neon Lamp"},
|
||||
{"Lightning Model", "Woofer R"},
|
||||
{"Lightning Model", "Woofer G"},
|
||||
{"Lightning Model", "Woofer B"},
|
||||
{"Lightning Model", "IC Card Reader P1 R"},
|
||||
{"Lightning Model", "IC Card Reader P1 G"},
|
||||
{"Lightning Model", "IC Card Reader P1 B"},
|
||||
{"Lightning Model", "IC Card Reader P2 R"},
|
||||
{"Lightning Model", "IC Card Reader P2 G"},
|
||||
{"Lightning Model", "IC Card Reader P2 B"},
|
||||
{"Lightning Model", "Turntable P1 R"},
|
||||
{"Lightning Model", "Turntable P1 G"},
|
||||
{"Lightning Model", "Turntable P1 B"},
|
||||
{"Lightning Model", "Turntable P2 R"},
|
||||
{"Lightning Model", "Turntable P2 G"},
|
||||
{"Lightning Model", "Turntable P2 B"},
|
||||
{"Lightning Model", "Turntable P1 Resistance"},
|
||||
{"Lightning Model", "Turntable P2 Resistance"},
|
||||
{"Lightning Model", "Stage Left Avg R"},
|
||||
{"Lightning Model", "Stage Left Avg G"},
|
||||
{"Lightning Model", "Stage Left Avg B"},
|
||||
{"Lightning Model", "Stage Right Avg R"},
|
||||
{"Lightning Model", "Stage Right Avg G"},
|
||||
{"Lightning Model", "Stage Right Avg B"},
|
||||
{"Lightning Model", "Cabinet Left Avg R"},
|
||||
{"Lightning Model", "Cabinet Left Avg G"},
|
||||
{"Lightning Model", "Cabinet Left Avg B"},
|
||||
{"Lightning Model", "Cabinet Right Avg R"},
|
||||
{"Lightning Model", "Cabinet Right Avg G"},
|
||||
{"Lightning Model", "Cabinet Right Avg B"},
|
||||
{"Lightning Model", "Control Panel Under Avg R"},
|
||||
{"Lightning Model", "Control Panel Under Avg G"},
|
||||
{"Lightning Model", "Control Panel Under Avg B"},
|
||||
{"Lightning Model", "Ceiling Left Avg R"},
|
||||
{"Lightning Model", "Ceiling Left Avg G"},
|
||||
{"Lightning Model", "Ceiling Left Avg B"},
|
||||
{"Lightning Model", "Title Left Avg R"},
|
||||
{"Lightning Model", "Title Left Avg G"},
|
||||
{"Lightning Model", "Title Left Avg B"},
|
||||
{"Lightning Model", "Title Right Avg R"},
|
||||
{"Lightning Model", "Title Right Avg G"},
|
||||
{"Lightning Model", "Title Right Avg B"},
|
||||
{"Lightning Model", "Ceiling Right Avg R"},
|
||||
{"Lightning Model", "Ceiling Right Avg G"},
|
||||
{"Lightning Model", "Ceiling Right Avg B"},
|
||||
{"Lightning Model", "Touch Panel Left Avg R"},
|
||||
{"Lightning Model", "Touch Panel Left Avg G"},
|
||||
{"Lightning Model", "Touch Panel Left Avg B"},
|
||||
{"Lightning Model", "Touch Panel Right Avg R"},
|
||||
{"Lightning Model", "Touch Panel Right Avg G"},
|
||||
{"Lightning Model", "Touch Panel Right Avg B"},
|
||||
{"Lightning Model", "Side Panel Left Inner Avg R"},
|
||||
{"Lightning Model", "Side Panel Left Inner Avg G"},
|
||||
{"Lightning Model", "Side Panel Left Inner Avg B"},
|
||||
{"Lightning Model", "Side Panel Left Outer Avg R"},
|
||||
{"Lightning Model", "Side Panel Left Outer Avg G"},
|
||||
{"Lightning Model", "Side Panel Left Outer Avg B"},
|
||||
{"Lightning Model", "Side Panel Left Avg R"},
|
||||
{"Lightning Model", "Side Panel Left Avg G"},
|
||||
{"Lightning Model", "Side Panel Left Avg B"},
|
||||
{"Lightning Model", "Side Panel Right Outer Avg R"},
|
||||
{"Lightning Model", "Side Panel Right Outer Avg G"},
|
||||
{"Lightning Model", "Side Panel Right Outer Avg B"},
|
||||
{"Lightning Model", "Side Panel Right Inner Avg R"},
|
||||
{"Lightning Model", "Side Panel Right Inner Avg G"},
|
||||
{"Lightning Model", "Side Panel Right Inner Avg B"},
|
||||
{"Lightning Model", "Side Panel Right Avg R"},
|
||||
{"Lightning Model", "Side Panel Right Avg G"},
|
||||
{"Lightning Model", "Side Panel Right Avg B"}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace games::iidx {
|
||||
protected:
|
||||
virtual ~IIDXLocalCamera() {};
|
||||
|
||||
ULONG m_nRefCount;
|
||||
LONG m_nRefCount;
|
||||
CRITICAL_SECTION m_critsec;
|
||||
|
||||
std::string m_name;
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace games {
|
||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_keypads;
|
||||
static robin_hood::unordered_map<std::string, std::vector<Button>> buttons_overlay;
|
||||
static robin_hood::unordered_map<std::string, std::string> buttons_help;
|
||||
static robin_hood::unordered_map<std::string, std::string> analogs_help;
|
||||
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;
|
||||
@@ -107,6 +108,7 @@ namespace games {
|
||||
games.push_back(gitadora);
|
||||
buttons.insert({ gitadora, gitadora::get_buttons() });
|
||||
buttons_help.insert({ gitadora, gitadora::get_buttons_help() });
|
||||
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");
|
||||
@@ -254,6 +256,7 @@ namespace games {
|
||||
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");
|
||||
|
||||
// winning eleven
|
||||
@@ -322,7 +325,7 @@ namespace games {
|
||||
buttons_help.insert({ pc, pc::get_buttons_help() });
|
||||
analogs.insert({ pc, pc::get_analogs() });
|
||||
lights.insert({ pc, pc::get_lights() });
|
||||
file_hints[pc].emplace_back("game/svm_Data");
|
||||
file_hints[pc].emplace_back("game/svm.exe");
|
||||
}
|
||||
|
||||
const std::vector<std::string> &get_games() {
|
||||
@@ -349,6 +352,15 @@ namespace games {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string get_analogs_help(const std::string &game) {
|
||||
initialize();
|
||||
auto it = analogs_help.find(game);
|
||||
if (it == analogs_help.end()) {
|
||||
return "";
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
static std::vector<Button> gen_buttons_keypads(const std::string &game) {
|
||||
auto buttons = GameAPI::Buttons::getButtons(game);
|
||||
std::vector<std::string> names;
|
||||
@@ -389,7 +401,8 @@ namespace games {
|
||||
vkey_defaults.push_back(unit == 0 ? VK_NUMPAD9 : 0xFF);
|
||||
|
||||
names.emplace_back(prefix + "00");
|
||||
vkey_defaults.push_back(unit == 0 ? VK_RETURN : 0xFF);
|
||||
// this used to be VK_RETURN, but that shares a value with normal Enter key
|
||||
vkey_defaults.push_back(unit == 0 ? VK_SUBTRACT : 0xFF);
|
||||
|
||||
names.emplace_back(prefix + "Decimal");
|
||||
vkey_defaults.push_back(unit == 0 ? VK_DECIMAL : 0xFF);
|
||||
|
||||
@@ -58,6 +58,7 @@ namespace games {
|
||||
const std::vector<std::string> &get_games();
|
||||
std::vector<Button> *get_buttons(const std::string &game);
|
||||
std::string get_buttons_help(const std::string &game);
|
||||
std::string get_analogs_help(const std::string &game);
|
||||
std::vector<Button> *get_buttons_keypads(const std::string &game);
|
||||
std::vector<Button> *get_buttons_overlay(const std::string &game);
|
||||
std::vector<Analog> *get_analogs(const std::string &game);
|
||||
|
||||
@@ -23,6 +23,11 @@ std::vector<Button> &games::otoca::get_buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string games::otoca::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return " BT_Left Lever BT_Right";
|
||||
}
|
||||
|
||||
std::vector<Light> &games::otoca::get_lights() {
|
||||
static std::vector<Light> lights;
|
||||
|
||||
|
||||
@@ -30,5 +30,6 @@ namespace games::otoca {
|
||||
|
||||
// getters
|
||||
std::vector<Button> &get_buttons();
|
||||
std::string get_buttons_help();
|
||||
std::vector<Light> &get_lights();
|
||||
}
|
||||
|
||||
@@ -1,16 +1,121 @@
|
||||
#include "otoca.h"
|
||||
#include "p4io.h"
|
||||
#include "games/shared/printer.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#define OTOCA_DEBUG_VERBOSE 0
|
||||
#if OTOCA_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
|
||||
|
||||
namespace games::otoca {
|
||||
|
||||
bool BYPASS_CAMERA = false;
|
||||
|
||||
// for dev only
|
||||
static bool CAMLIB_CALL_ORIGINAL = false;
|
||||
|
||||
OtocaGame::OtocaGame() : Game("Otoca D'or") {
|
||||
}
|
||||
|
||||
OtocaGame::~OtocaGame() {
|
||||
}
|
||||
|
||||
typedef void (__cdecl *LibCameraInit_t)();
|
||||
static LibCameraInit_t LibCameraInit_orig = nullptr;
|
||||
static void __cdecl LibCameraInit_hook() {
|
||||
if (CAMLIB_CALL_ORIGINAL) {
|
||||
LibCameraInit_orig();
|
||||
log_debug("otoca::cam", "LibCameraInit returned");
|
||||
}
|
||||
}
|
||||
|
||||
typedef int (__cdecl *LibCameraOpen_t)(double, int, int);
|
||||
static LibCameraOpen_t LibCameraOpen_orig = nullptr;
|
||||
static int __cdecl LibCameraOpen_hook(double a1, int a2, int a3) {
|
||||
int ret = 0;
|
||||
if (CAMLIB_CALL_ORIGINAL) {
|
||||
// with webcam, (15, 2, 0) returned 0
|
||||
ret = LibCameraOpen_orig(a1, a2, a3);
|
||||
log_debug("otoca::cam", "LibCameraOpen({}, {}, {}) returned {}", a1, a2, a3, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef int (__cdecl *LibCameraStop_t)(int);
|
||||
static LibCameraStop_t LibCameraStop_orig = nullptr;
|
||||
static int __cdecl LibCameraStop_hook(int a1) {
|
||||
int ret = 0;
|
||||
if (CAMLIB_CALL_ORIGINAL) {
|
||||
// with webcam,
|
||||
ret = LibCameraStop_orig(a1);
|
||||
log_debug("otoca::cam", "LibCameraStop({}) returned {}", a1, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef void (__cdecl *LibCameraGetCameraNr_t)();
|
||||
static LibCameraGetCameraNr_t LibCameraGetCameraNr_orig = nullptr;
|
||||
static void __cdecl LibCameraGetCameraNr_hook() {
|
||||
if (CAMLIB_CALL_ORIGINAL) {
|
||||
LibCameraGetCameraNr_orig();
|
||||
log_debug("otoca::cam", "LibCameraGetCameraNr returned");
|
||||
}
|
||||
}
|
||||
|
||||
typedef int (__cdecl *LibCameraRun_t)(int);
|
||||
static LibCameraRun_t LibCameraRun_orig = nullptr;
|
||||
static int __cdecl LibCameraRun_hook(int a1) {
|
||||
int ret = 0;
|
||||
if (CAMLIB_CALL_ORIGINAL) {
|
||||
// with webcam, (0) returned 0
|
||||
ret = LibCameraRun_orig(a1);
|
||||
log_debug("otoca::cam", "LibCameraRun({}) returned {}", a1, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef int (__cdecl *LibCameraGetImage_t)(int, void *);
|
||||
static LibCameraGetImage_t LibCameraGetImage_orig = nullptr;
|
||||
static int __cdecl LibCameraGetImage_hook(int a1, void *a2) {
|
||||
int ret = 0;
|
||||
if (CAMLIB_CALL_ORIGINAL) {
|
||||
// not sure if this is called
|
||||
ret = LibCameraGetImage_orig(a1, a2);
|
||||
log_debug("otoca::cam", "LibCameraGetImage({}, {}) returned {}", a1, fmt::ptr(a2), ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef int (__cdecl *LibCameraGetBrightness_t)(int *, int);
|
||||
static LibCameraGetBrightness_t LibCameraGetBrightness_orig = nullptr;
|
||||
static int __cdecl LibCameraGetBrightness_hook(int *a1, int a2) {
|
||||
int ret = 0;
|
||||
if (CAMLIB_CALL_ORIGINAL) {
|
||||
// with webcam, (ptr,) returned
|
||||
ret = LibCameraGetBrightness_orig(a1, a2);
|
||||
}
|
||||
log_debug("otoca::cam", "LibCameraGetBrightness({}, {}) returned {}", fmt::ptr(a1), a2, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef int (__cdecl *LibCameraGetBrightnessRange_t)(int *, int *, int);
|
||||
static LibCameraGetBrightnessRange_t LibCameraGetBrightnessRange_orig = nullptr;
|
||||
static int __cdecl LibCameraGetBrightnessRange_hook(int *a1, int *a2, int a3) {
|
||||
int ret = 0;
|
||||
if (CAMLIB_CALL_ORIGINAL) {
|
||||
// with webcam, (ptr, ptr, 0) returned 0
|
||||
ret = LibCameraGetBrightnessRange_orig(a1, a2, a3);
|
||||
log_debug("otoca::cam", "LibCameraGetBrightnessRange({}, {}, {}) returned {}", fmt::ptr(a1), fmt::ptr(a2), a3, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OtocaGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
@@ -23,5 +128,42 @@ namespace games::otoca {
|
||||
// initialize IO hooks
|
||||
p4io_hook();
|
||||
games::shared::printer_attach();
|
||||
|
||||
if (BYPASS_CAMERA) {
|
||||
libutils::try_library("libcamera.dll");
|
||||
const auto libcamera = "libcamera.dll";
|
||||
detour::trampoline_try(
|
||||
libcamera,
|
||||
"?LibCameraInit@@YAXXZ",
|
||||
LibCameraInit_hook, &LibCameraInit_orig);
|
||||
detour::trampoline_try(
|
||||
libcamera,
|
||||
"?LibCameraOpen@@YA?AW4LIBCAMERA_STATUS@@NHPAUt_libcamera_open_param@@@Z",
|
||||
LibCameraOpen_hook, &LibCameraOpen_orig);
|
||||
detour::trampoline_try(
|
||||
libcamera,
|
||||
"?LibCameraStop@@YA?AW4LIBCAMERA_STATUS@@H@Z",
|
||||
LibCameraStop_hook, &LibCameraStop_orig);
|
||||
detour::trampoline_try(
|
||||
libcamera,
|
||||
"?LibCameraGetCameraNr@@YAHXZ",
|
||||
LibCameraGetCameraNr_hook, &LibCameraGetCameraNr_orig);
|
||||
detour::trampoline_try(
|
||||
libcamera,
|
||||
"?LibCameraRun@@YA?AW4LIBCAMERA_STATUS@@H@Z",
|
||||
LibCameraRun_hook, &LibCameraRun_orig);
|
||||
detour::trampoline_try(
|
||||
libcamera,
|
||||
"?LibCameraGetImage@@YA?AW4LIBCAMERA_STATUS@@HPAX@Z",
|
||||
LibCameraGetImage_hook, &LibCameraGetImage_orig);
|
||||
detour::trampoline_try(
|
||||
libcamera,
|
||||
"?LibCameraGetBrightness@@YA?AW4LIBCAMERA_STATUS@@PAJH@Z",
|
||||
LibCameraGetBrightness_hook, &LibCameraGetBrightness_orig);
|
||||
detour::trampoline_try(
|
||||
libcamera,
|
||||
"?LibCameraGetBrightnessRange@@YA?AW4LIBCAMERA_STATUS@@PAJ0H@Z",
|
||||
LibCameraGetBrightnessRange_hook, &LibCameraGetBrightnessRange_orig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
namespace games::otoca {
|
||||
|
||||
extern bool BYPASS_CAMERA;
|
||||
|
||||
class OtocaGame : public games::Game {
|
||||
public:
|
||||
OtocaGame();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include "util/execexe.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
@@ -9,9 +11,10 @@
|
||||
#include "util/tapeled.h"
|
||||
#include "pc.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/time.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
|
||||
namespace games::pc {
|
||||
bool PC_KNOB_MODE = false;
|
||||
|
||||
constexpr float RELATIVE_DECAY = 0.02f;
|
||||
struct FADER_RELATIVE {
|
||||
@@ -75,18 +78,22 @@ namespace games::pc {
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0xf8];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t data[0xe30];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1 {
|
||||
uint8_t data[0x4538];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__SETTING {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1__INPUT
|
||||
@@ -191,6 +198,12 @@ namespace games::pc {
|
||||
AIO_IOB2_BI2X_AC1__ICNPIN ICnPinHist[20];
|
||||
};
|
||||
|
||||
// verified with XIF-2024122300
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0xe30);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4538);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
@@ -338,14 +351,21 @@ namespace games::pc {
|
||||
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderL]) - 0.5f) * 2;
|
||||
}
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]) &&
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
|
||||
val = 0.f;
|
||||
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left])) {
|
||||
|
||||
const auto now = get_performance_milliseconds();
|
||||
|
||||
const auto fader_l_socd = socd::socd_clean(
|
||||
0, // Fader-L
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right]),
|
||||
now);
|
||||
|
||||
if (fader_l_socd == socd::SocdCCW) {
|
||||
val = -1.0f;
|
||||
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderL_Right])) {
|
||||
} else if (fader_l_socd == socd::SocdCW) {
|
||||
val = +1.0f;
|
||||
}
|
||||
|
||||
o_DevStatus->Input.CN15_7 = (val < 0.2f) ? 0xFF : 0;
|
||||
o_DevStatus->Input.CN15_6 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
|
||||
o_DevStatus->Input.CN15_5 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
|
||||
@@ -363,14 +383,19 @@ namespace games::pc {
|
||||
val = (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::FaderR]) - 0.5f) * 2;
|
||||
}
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]) &&
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
|
||||
val = 0.f;
|
||||
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left])) {
|
||||
|
||||
const auto fader_r_socd = socd::socd_clean(
|
||||
1, // Fader-R
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right]),
|
||||
now);
|
||||
|
||||
if (fader_r_socd == socd::SocdCCW) {
|
||||
val = -1.0f;
|
||||
} else if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FaderR_Right])) {
|
||||
} else if (fader_r_socd == socd::SocdCW) {
|
||||
val = +1.0f;
|
||||
}
|
||||
|
||||
o_DevStatus->Input.CN11_20 = (val < 0.2f) ? 0xFF : 0;
|
||||
o_DevStatus->Input.CN11_19 = (val > -0.85f && val < 0.35f) ? 0xFF : 0;
|
||||
o_DevStatus->Input.CN9_10 = (val > -0.6f && val < 0.6f) ? 0xFF : 0;
|
||||
@@ -737,3 +762,5 @@ namespace games::pc {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::pc {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// QueryDisplayConfig
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
#include "pc.h"
|
||||
|
||||
#include <format>
|
||||
@@ -12,10 +15,12 @@
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
|
||||
namespace games::pc {
|
||||
std::string PC_INJECT_ARGS = "";
|
||||
bool PC_NO_IO = false;
|
||||
bool PC_KNOB_MODE = false;
|
||||
|
||||
static acioemu::ACIOHandle *acioHandle = nullptr;
|
||||
static std::wstring portName = L"COM1";
|
||||
@@ -30,10 +35,10 @@ namespace games::pc {
|
||||
if (pRawInputDevices &&
|
||||
(uiNumDevices > 0) &&
|
||||
(pRawInputDevices[0].hwndTarget == RI_MGR->input_hwnd)) {
|
||||
|
||||
|
||||
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
|
||||
}
|
||||
|
||||
|
||||
// otherwise, it must be the game; prevent the game from registering for raw input
|
||||
// and hijacking WM_INPUT messages.
|
||||
SetLastError(0xDEADBEEF);
|
||||
@@ -56,11 +61,16 @@ namespace games::pc {
|
||||
}
|
||||
|
||||
LONG WINAPI QueryDisplayConfig_hook(UINT32, UINT32*, DISPLAYCONFIG_PATH_INFO*, UINT32*, DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*) {
|
||||
// make unity fallback to EnumDisplaySettingsW as I don't
|
||||
// make unity fallback to EnumDisplaySettingsW as I don't
|
||||
// want to deal with this api which is way more complex
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
void PCGame::pre_attach() {
|
||||
// SOCD
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
}
|
||||
|
||||
void PCGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
@@ -78,17 +88,22 @@ namespace games::pc {
|
||||
execexe::load_library("libaio-iob2_video.dll");
|
||||
execexe::load_library("win10actlog.dll");
|
||||
|
||||
#if SPICE64
|
||||
|
||||
if (!PC_NO_IO) {
|
||||
bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
});
|
||||
|
||||
const auto user32Dll = "user32.dll";
|
||||
detour::trampoline_try(user32Dll, "RegisterRawInputDevices",
|
||||
RegisterRawInputDevices_hook, &RegisterRawInputDevices_orig);
|
||||
detour::trampoline_try(user32Dll, "QueryDisplayConfig",
|
||||
detour::trampoline_try(user32Dll, "QueryDisplayConfig",
|
||||
QueryDisplayConfig_hook, &QueryDisplayConfig_orig);
|
||||
detour::trampoline_try(user32Dll, "EnumDisplaySettingsW",
|
||||
detour::trampoline_try(user32Dll, "EnumDisplaySettingsW",
|
||||
EnumDisplaySettingsW_hook, &EnumDisplaySettingsW_orig);
|
||||
|
||||
if (GRAPHICS_SHOW_CURSOR) {
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace games::pc {
|
||||
public:
|
||||
PCGame() : Game("Polaris Chord") {}
|
||||
|
||||
virtual void pre_attach() override;
|
||||
virtual void attach() override;
|
||||
virtual void detach() override;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
@@ -15,15 +17,19 @@ namespace games::qks {
|
||||
*/
|
||||
|
||||
struct AIO_SCI_COMM {
|
||||
uint8_t data[0xf8];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB {
|
||||
uint8_t data[0xa00];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_AC1 {
|
||||
uint8_t data[0x3908];
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB__NODEINFO {
|
||||
@@ -174,6 +180,12 @@ namespace games::qks {
|
||||
AIO_IOB2_BI2X_AC1__SETTING Setting;
|
||||
};
|
||||
|
||||
// verified with UKS-001-2024030600
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x3908);
|
||||
static_assert(sizeof(AIO_SCI_COMM) == 0xf8);
|
||||
static_assert(sizeof(AIO_NMGR_IOB) == 0xa00);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
@@ -602,4 +614,6 @@ namespace games::qks {
|
||||
detour::trampoline_try(libaioDll, "aioNodeCtl_IsError",
|
||||
aioNodeCtl_IsError, &aioNodeCtl_IsError_orig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace games::qks {
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -86,9 +86,13 @@ namespace games::qks {
|
||||
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(9),
|
||||
execexe_CreateFileA_hook,&execexe_CreateFileA_orig);
|
||||
|
||||
#if SPICE64
|
||||
|
||||
// insert BI2X hooks
|
||||
bi2x_hook_init();
|
||||
|
||||
#endif
|
||||
|
||||
// add card reader
|
||||
acioHandle = new acioemu::ACIOHandle(portName.c_str());
|
||||
devicehook_init_trampoline();
|
||||
|
||||
@@ -88,10 +88,10 @@ namespace games::qma {
|
||||
// create quiz directory
|
||||
static bool dirs_created = false;
|
||||
if (!dirs_created && _wcsnicmp(lpFileName, L"E:\\LMA", 6) == 0) {
|
||||
dirs_created = true;
|
||||
fileutils::dir_create_recursive("E:\\LMA\\quiz11");
|
||||
fileutils::dir_create_recursive("E:\\LMA\\quiz13");
|
||||
fileutils::dir_create_recursive("E:\\LMA\\quiz15");
|
||||
dirs_created = true;
|
||||
}
|
||||
|
||||
// return result
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
@@ -10,12 +12,22 @@
|
||||
#include "launcher/options.h"
|
||||
#include "io.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/tapeled.h"
|
||||
#include "util/time.h"
|
||||
#include "acioemu/icca.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
|
||||
|
||||
namespace games::sdvx {
|
||||
constexpr bool BI2X_PASSTHROUGH = false;
|
||||
bool BI2X_INITIALIZED = false;
|
||||
|
||||
/*
|
||||
* class definitions
|
||||
@@ -26,20 +38,54 @@ namespace games::sdvx {
|
||||
uint8_t data[0x10];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
uint8_t dummy0[0x50];
|
||||
struct AIO_NMGR_IOB2_VTABLE {
|
||||
// other functions here, but they never get called
|
||||
uint8_t dummy[0x50];
|
||||
void (__fastcall *pAIO_NMGR_IOB_BeginManage)(int64_t a1);
|
||||
uint8_t dummy1[0x9A0];
|
||||
};
|
||||
|
||||
struct AIO_NMGR_IOB2 {
|
||||
AIO_NMGR_IOB2_VTABLE *vptr;
|
||||
uint8_t dummy[0x9F0];
|
||||
};
|
||||
|
||||
// confirmed in EG final in aioNMgrIob2_Create
|
||||
static_assert(sizeof(AIO_NMGR_IOB2) == 0x9F8);
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC {
|
||||
// who knows
|
||||
uint8_t data[0x13F8];
|
||||
uint8_t data[0x39D8];
|
||||
};
|
||||
|
||||
// confirmed in EG final in aioIob2Bi2xUFC_Create
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_UFC) == 0x39D8);
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC__INPUT {
|
||||
uint8_t DevIoCounter;
|
||||
uint8_t bExIoAErr;
|
||||
uint8_t bExIoBErr;
|
||||
uint8_t bPcPowerOn;
|
||||
uint8_t bPcPowerCheck;
|
||||
uint8_t CoinCount;
|
||||
uint8_t bTest;
|
||||
uint8_t bService;
|
||||
uint8_t bCoinSw;
|
||||
uint8_t bCoinJam;
|
||||
uint8_t bHPDetect;
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_UFC__DEVSTATUS {
|
||||
// of course you could work with variables here
|
||||
uint8_t buffer[0x19E];
|
||||
uint8_t InputCounter;
|
||||
uint8_t OutputCounter;
|
||||
uint8_t IoResetCounter;
|
||||
uint8_t TapeLedCounter;
|
||||
uint8_t TapeLedRate[8];
|
||||
AIO_IOB2_BI2X_UFC__INPUT Input;
|
||||
uint8_t unk_1[289];
|
||||
uint16_t AnalogLeft;
|
||||
uint16_t AnalogRight;
|
||||
uint8_t Buttons;
|
||||
uint8_t unk_2[97];
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -66,7 +112,7 @@ namespace games::sdvx {
|
||||
// libaio-iob.dll
|
||||
typedef AC_HNDLIF* (__fastcall *aioIob2Bi2x_OpenSciUsbCdc_t)(uint8_t device_num);
|
||||
typedef int64_t (__fastcall *aioIob2Bi2x_WriteFirmGetState_t)(int64_t a1);
|
||||
typedef AIO_NMGR_IOB2** (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
|
||||
typedef AIO_NMGR_IOB2* (__fastcall *aioNMgrIob2_Create_t)(AC_HNDLIF *a1, unsigned int a2);
|
||||
|
||||
/*
|
||||
* function pointers
|
||||
@@ -133,8 +179,8 @@ namespace games::sdvx {
|
||||
AIO_IOB2_BI2X_UFC__GetDeviceStatus_orig(This, status);
|
||||
}
|
||||
|
||||
status->buffer[0] = count;
|
||||
status->buffer[12] = count;
|
||||
status->InputCounter = count;
|
||||
status->Input.DevIoCounter = count;
|
||||
count++;
|
||||
|
||||
// get buttons
|
||||
@@ -142,41 +188,51 @@ namespace games::sdvx {
|
||||
|
||||
// control buttons
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Test]))
|
||||
status->buffer[18] = 0x01;
|
||||
status->Input.bTest = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Service]))
|
||||
status->buffer[19] = 0x01;
|
||||
status->Input.bService = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::CoinMech]))
|
||||
status->buffer[20] = 0x01;
|
||||
status->Input.bCoinSw = 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Start]))
|
||||
status->buffer[316] |= 0x01;
|
||||
status->Buttons |= 0x01;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_A]))
|
||||
status->buffer[316] |= 0x02;
|
||||
status->Buttons |= 0x02;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_B]))
|
||||
status->buffer[316] |= 0x04;
|
||||
status->Buttons |= 0x04;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_C]))
|
||||
status->buffer[316] |= 0x08;
|
||||
status->Buttons |= 0x08;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::BT_D]))
|
||||
status->buffer[316] |= 0x10;
|
||||
status->Buttons |= 0x10;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_L]))
|
||||
status->buffer[316] |= 0x20;
|
||||
status->Buttons |= 0x20;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::FX_R]))
|
||||
status->buffer[316] |= 0x40;
|
||||
status->Buttons |= 0x40;
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Headphone]))
|
||||
status->buffer[22] = 0x01;
|
||||
status->Input.bHPDetect = 0x01;
|
||||
|
||||
status->Input.CoinCount += eamuse_coin_get_stock();
|
||||
|
||||
const auto now = get_performance_milliseconds();
|
||||
|
||||
// volume left
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left])) {
|
||||
const auto vol_l_state = socd::socd_clean(0,
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right]),
|
||||
now);
|
||||
if (vol_l_state == socd::SocdCCW) {
|
||||
VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right])) {
|
||||
} else if (vol_l_state == socd::SocdCW) {
|
||||
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
|
||||
// volume right
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left])) {
|
||||
const auto vol_r_state = socd::socd_clean(1,
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left]),
|
||||
GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right]),
|
||||
now);
|
||||
if (vol_r_state == socd::SocdCCW) {
|
||||
VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right])) {
|
||||
} else if (vol_r_state == socd::SocdCW) {
|
||||
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
|
||||
}
|
||||
|
||||
@@ -189,8 +245,14 @@ namespace games::sdvx {
|
||||
vol_right += (uint16_t) (GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::VOL_R]) * 65535);
|
||||
}
|
||||
|
||||
*((uint16_t*) &status->buffer[312]) = vol_left;
|
||||
*((uint16_t*) &status->buffer[314]) = vol_right;
|
||||
status->AnalogLeft = vol_left;
|
||||
status->AnalogRight = vol_right;
|
||||
|
||||
log_debug(
|
||||
"bi2x_hook",
|
||||
"knobs = {} {}",
|
||||
status->AnalogLeft,
|
||||
status->AnalogRight);
|
||||
}
|
||||
|
||||
static void __fastcall AIO_IOB2_BI2X_UFC__IoReset(AIO_IOB2_BI2X_UFC *This,
|
||||
@@ -345,21 +407,21 @@ namespace games::sdvx {
|
||||
log_info("bi2x_hook", "AIO_NMGR_IOB::BeginManage");
|
||||
}
|
||||
|
||||
static AIO_NMGR_IOB2** __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
|
||||
static AIO_NMGR_IOB2* __fastcall aioNMgrIob2_Create(AC_HNDLIF *a1, unsigned int a2) {
|
||||
if (aioNmgrIob2 == nullptr) {
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2;
|
||||
memset(aioNmgrIob2, 0x0, sizeof(AIO_NMGR_IOB2));
|
||||
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||
aioNmgrIob2 = new AIO_NMGR_IOB2{};
|
||||
aioNmgrIob2->vptr = new AIO_NMGR_IOB2_VTABLE{};
|
||||
aioNmgrIob2->vptr->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
|
||||
}
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create");
|
||||
BI2X_INITIALIZED = true;
|
||||
log_info("bi2x_hook", "aioNMgrIob2_Create returned {}, size=0x{:x}, vptr @ {}",
|
||||
fmt::ptr(aioNmgrIob2), sizeof(*aioNmgrIob2), fmt::ptr(aioNmgrIob2->vptr));
|
||||
|
||||
// enable hack to make PIN pad work for KFC in BI2X mode
|
||||
// this explicit check in the I/O init path is necessary
|
||||
// (as opposed to just doing a check for "isValkyrieCabMode?")
|
||||
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode
|
||||
acioemu::ICCA_DEVICE_HACK = true;
|
||||
return &aioNmgrIob2;
|
||||
return aioNmgrIob2;
|
||||
}
|
||||
|
||||
static int64_t __fastcall aioIob2Bi2x_WriteFirmGetState(int64_t a1) {
|
||||
@@ -411,3 +473,5 @@ namespace games::sdvx {
|
||||
aioNMgrIob2_Create, &aioNMgrIob2_Create_orig);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
|
||||
namespace games::sdvx {
|
||||
|
||||
void bi2x_hook_init();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -65,76 +65,78 @@ std::vector<Light> &games::sdvx::get_lights() {
|
||||
if (lights.empty()) {
|
||||
lights = GameAPI::Lights::getLights("Sound Voltex");
|
||||
|
||||
GameAPI::Lights::sortLights(
|
||||
GameAPI::Lights::sortLightsWithCategory(
|
||||
&lights,
|
||||
"BT-A",
|
||||
"BT-B",
|
||||
"BT-C",
|
||||
"BT-D",
|
||||
"FX-L",
|
||||
"FX-R",
|
||||
"Start",
|
||||
"Wing Left Up R",
|
||||
"Wing Left Up G",
|
||||
"Wing Left Up B",
|
||||
"Wing Right Up R",
|
||||
"Wing Right Up G",
|
||||
"Wing Right Up B",
|
||||
"Wing Left Low R",
|
||||
"Wing Left Low G",
|
||||
"Wing Left Low B",
|
||||
"Wing Right Low R",
|
||||
"Wing Right Low G",
|
||||
"Wing Right Low B",
|
||||
"Woofer R",
|
||||
"Woofer G",
|
||||
"Woofer B",
|
||||
"Controller R",
|
||||
"Controller G",
|
||||
"Controller B",
|
||||
"Generator R",
|
||||
"Generator G",
|
||||
"Generator B",
|
||||
"Pop",
|
||||
"Title Left",
|
||||
"Title Right",
|
||||
"Volume Sound",
|
||||
"Volume Headphone",
|
||||
"Volume External",
|
||||
"Volume Woofer",
|
||||
"IC Card Reader R",
|
||||
"IC Card Reader G",
|
||||
"IC Card Reader B",
|
||||
"Title Avg R",
|
||||
"Title Avg G",
|
||||
"Title Avg B",
|
||||
"Upper Left Speaker Avg R",
|
||||
"Upper Left Speaker Avg G",
|
||||
"Upper Left Speaker Avg B",
|
||||
"Upper Right Speaker Avg R",
|
||||
"Upper Right Speaker Avg G",
|
||||
"Upper Right Speaker Avg B",
|
||||
"Left Wing Avg R",
|
||||
"Left Wing Avg G",
|
||||
"Left Wing Avg B",
|
||||
"Right Wing Avg R",
|
||||
"Right Wing Avg G",
|
||||
"Right Wing Avg B",
|
||||
"Lower Left Speaker Avg R",
|
||||
"Lower Left Speaker Avg G",
|
||||
"Lower Left Speaker Avg B",
|
||||
"Lower Right Speaker Avg R",
|
||||
"Lower Right Speaker Avg G",
|
||||
"Lower Right Speaker Avg B",
|
||||
"Control Panel Avg R",
|
||||
"Control Panel Avg G",
|
||||
"Control Panel Avg B",
|
||||
"Woofer Avg R",
|
||||
"Woofer Avg G",
|
||||
"Woofer Avg B",
|
||||
"V Unit Avg R",
|
||||
"V Unit Avg G",
|
||||
"V Unit Avg B"
|
||||
{
|
||||
{"Common", "BT-A"},
|
||||
{"Common", "BT-B"},
|
||||
{"Common", "BT-C"},
|
||||
{"Common", "BT-D"},
|
||||
{"Common", "FX-L"},
|
||||
{"Common", "FX-R"},
|
||||
{"Common", "Start"},
|
||||
{"Nemsys", "Wing Left Up R"},
|
||||
{"Nemsys", "Wing Left Up G"},
|
||||
{"Nemsys", "Wing Left Up B"},
|
||||
{"Nemsys", "Wing Right Up R"},
|
||||
{"Nemsys", "Wing Right Up G"},
|
||||
{"Nemsys", "Wing Right Up B"},
|
||||
{"Nemsys", "Wing Left Low R"},
|
||||
{"Nemsys", "Wing Left Low G"},
|
||||
{"Nemsys", "Wing Left Low B"},
|
||||
{"Nemsys", "Wing Right Low R"},
|
||||
{"Nemsys", "Wing Right Low G"},
|
||||
{"Nemsys", "Wing Right Low B"},
|
||||
{"Nemsys", "Woofer R"},
|
||||
{"Nemsys", "Woofer G"},
|
||||
{"Nemsys", "Woofer B"},
|
||||
{"Nemsys", "Controller R"},
|
||||
{"Nemsys", "Controller G"},
|
||||
{"Nemsys", "Controller B"},
|
||||
{"Nemsys", "Generator R"},
|
||||
{"Nemsys", "Generator G"},
|
||||
{"Nemsys", "Generator B"},
|
||||
{"Nemsys", "Pop"},
|
||||
{"Nemsys", "Title Left"},
|
||||
{"Nemsys", "Title Right"},
|
||||
{"System", "Volume Sound"},
|
||||
{"System", "Volume Headphone"},
|
||||
{"System", "Volume External"},
|
||||
{"System", "Volume Woofer"},
|
||||
{"Valkyrie", "IC Card Reader R"},
|
||||
{"Valkyrie", "IC Card Reader G"},
|
||||
{"Valkyrie", "IC Card Reader B"},
|
||||
{"Valkyrie", "Title Avg R"},
|
||||
{"Valkyrie", "Title Avg G"},
|
||||
{"Valkyrie", "Title Avg B"},
|
||||
{"Valkyrie", "Upper Left Speaker Avg R"},
|
||||
{"Valkyrie", "Upper Left Speaker Avg G"},
|
||||
{"Valkyrie", "Upper Left Speaker Avg B"},
|
||||
{"Valkyrie", "Upper Right Speaker Avg R"},
|
||||
{"Valkyrie", "Upper Right Speaker Avg G"},
|
||||
{"Valkyrie", "Upper Right Speaker Avg B"},
|
||||
{"Valkyrie", "Left Wing Avg R"},
|
||||
{"Valkyrie", "Left Wing Avg G"},
|
||||
{"Valkyrie", "Left Wing Avg B"},
|
||||
{"Valkyrie", "Right Wing Avg R"},
|
||||
{"Valkyrie", "Right Wing Avg G"},
|
||||
{"Valkyrie", "Right Wing Avg B"},
|
||||
{"Valkyrie", "Lower Left Speaker Avg R"},
|
||||
{"Valkyrie", "Lower Left Speaker Avg G"},
|
||||
{"Valkyrie", "Lower Left Speaker Avg B"},
|
||||
{"Valkyrie", "Lower Right Speaker Avg R"},
|
||||
{"Valkyrie", "Lower Right Speaker Avg G"},
|
||||
{"Valkyrie", "Lower Right Speaker Avg B"},
|
||||
{"Valkyrie", "Control Panel Avg R"},
|
||||
{"Valkyrie", "Control Panel Avg G"},
|
||||
{"Valkyrie", "Control Panel Avg B"},
|
||||
{"Valkyrie", "Woofer Avg R"},
|
||||
{"Valkyrie", "Woofer Avg G"},
|
||||
{"Valkyrie", "Woofer Avg B"},
|
||||
{"Valkyrie", "V Unit Avg R"},
|
||||
{"Valkyrie", "V Unit Avg G"},
|
||||
{"Valkyrie", "V Unit Avg B"}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/shared/lcdhandle.h"
|
||||
#include "games/io.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
@@ -19,9 +18,12 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/time.h"
|
||||
#include "util/libutils.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "misc/nativetouchhook.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "bi2x_hook.h"
|
||||
#include "camera.h"
|
||||
#include "io.h"
|
||||
@@ -64,10 +66,6 @@ namespace games::sdvx {
|
||||
|
||||
// prevent the game from randomly picking an ASIO device
|
||||
if (!USE_ASIO) {
|
||||
log_info(
|
||||
"sdvx::asio",
|
||||
"game tried to access HKLM\\SOFTWARE\\ASIO but ASIO is disabled; "
|
||||
"access prevented, ASIO init will fail");
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
// convince the game to use our hook (so we can swap in the preferred device)
|
||||
@@ -236,6 +234,27 @@ namespace games::sdvx {
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool logged_missing_layer_error = false;
|
||||
// this is raw SHIFT-JIS text for
|
||||
// W:BM2D: CreateLayer() 指定したレイヤーは存在しません
|
||||
if (!logged_missing_layer_error &&
|
||||
data.find(
|
||||
"\x57\x3A\x42\x4D\x32\x44\x3A\x20\x43\x72\x65\x61\x74\x65\x4C\x61\x79\x65\x72\x28\x29\x20"
|
||||
"\x8E\x77\x92\xE8\x82\xB5\x82\xBD\x83\x8C\x83\x43\x83\x84\x81\x5B\x82\xCD\x91\xB6\x8D\xDD"
|
||||
"\x82\xB5\x82\xDC\x82\xB9\x82\xF1") != std::string::npos) {
|
||||
|
||||
logged_missing_layer_error = true;
|
||||
deferredlogs::defer_error_messages({
|
||||
"sdvx game engine detected missing or corrupt file(s); raw SHIFT-JIS error was:",
|
||||
std::string(" ") + data,
|
||||
" this will almost certainly cause your game to crash",
|
||||
" this is often caused by mismatched game DLLs or missing resources from game updates",
|
||||
" double check the integrity of your game data and try again"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -331,29 +350,28 @@ namespace games::sdvx {
|
||||
}
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
this->VALKYRIE_MODEL = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
// check -monitor + UFC mode
|
||||
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && this->VALKYRIE_MODEL) {
|
||||
// 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"
|
||||
"iidx",
|
||||
"\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
|
||||
@@ -362,6 +380,16 @@ namespace games::sdvx {
|
||||
this->detect_sound_output_device();
|
||||
#endif
|
||||
|
||||
// SOCD
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
if (options->at(launcher::Options::SDVXDigitalKnobSocd).is_active()) {
|
||||
if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "neutral") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::Neutral;
|
||||
} else if (options->at(launcher::Options::SDVXDigitalKnobSocd).value_text() == "last") {
|
||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDVXGame::attach() {
|
||||
@@ -370,7 +398,7 @@ namespace games::sdvx {
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
|
||||
// LCD handle
|
||||
if (!this->VALKYRIE_MODEL) {
|
||||
if (!is_valkyrie_model()) {
|
||||
devicehook_init();
|
||||
devicehook_add(new games::shared::LCDHandle());
|
||||
}
|
||||
@@ -409,10 +437,12 @@ namespace games::sdvx {
|
||||
nvapi_hook::initialize(avs::game::DLL_INSTANCE);
|
||||
}
|
||||
|
||||
if (this->VALKYRIE_MODEL) {
|
||||
// hook touch window
|
||||
// in windowed mode, game can accept mouse input on the second screen
|
||||
if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
||||
if (is_valkyrie_model()) {
|
||||
if (NATIVETOUCH) {
|
||||
nativetouchhook::hook(avs::game::DLL_INSTANCE);
|
||||
} else if (!NATIVETOUCH && !GRAPHICS_WINDOWED) {
|
||||
// hook touch window
|
||||
// in windowed mode, game can accept mouse input on the second screen
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook_title_ends(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/game.h"
|
||||
|
||||
namespace games::sdvx {
|
||||
@@ -20,12 +21,18 @@ namespace games::sdvx {
|
||||
extern bool NATIVETOUCH;
|
||||
extern uint8_t DIGITAL_KNOB_SENS;
|
||||
extern std::optional<std::string> ASIO_DRIVER;
|
||||
extern bool BI2X_INITIALIZED;
|
||||
extern SdvxOverlayPosition OVERLAY_POS;
|
||||
|
||||
// states
|
||||
extern bool SHOW_VM_MONITOR_WARNING;
|
||||
|
||||
static inline bool is_valkyrie_model() {
|
||||
return (
|
||||
avs::game::is_model("KFC") &&
|
||||
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H')
|
||||
);
|
||||
}
|
||||
|
||||
class SDVXGame : public games::Game {
|
||||
public:
|
||||
SDVXGame();
|
||||
@@ -34,11 +41,6 @@ namespace games::sdvx {
|
||||
virtual void post_attach() override;
|
||||
virtual void detach() override;
|
||||
private:
|
||||
// don't be tempted to make this into a public or a global variable
|
||||
// various modules need to check if VM mode is active and they should
|
||||
// not depend on the fact that SDVX module is active (since it can be
|
||||
// inactive on cabs or partial I/O setup)
|
||||
bool VALKYRIE_MODEL = false;
|
||||
void detect_sound_output_device();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ namespace games::shared {
|
||||
image_data[index + 2] = tmp;
|
||||
}
|
||||
|
||||
if (avs::game::is_model({"KLP", "KFC"})) {
|
||||
if (avs::game::is_model({"KLP", "KFC", "NCG"})) {
|
||||
// rotate image clockwise
|
||||
log_misc("printer", "rotate clockwise...");
|
||||
auto rotated = new uint8_t[image_width * image_height * 3];
|
||||
|
||||
@@ -42,7 +42,8 @@ namespace hooks::audio {
|
||||
bool USE_DUMMY = false;
|
||||
WAVEFORMATEXTENSIBLE FORMAT {};
|
||||
std::optional<Backend> BACKEND = std::nullopt;
|
||||
size_t ASIO_DRIVER_ID = 0;
|
||||
std::optional<size_t> ASIO_DRIVER_ID = std::nullopt;
|
||||
std::string ASIO_DRIVER_NAME = "";
|
||||
bool ASIO_FORCE_UNLOAD_ON_STOP = false;
|
||||
|
||||
// private globals
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmreg.h>
|
||||
@@ -20,7 +21,8 @@ namespace hooks::audio {
|
||||
extern bool USE_DUMMY;
|
||||
extern WAVEFORMATEXTENSIBLE FORMAT;
|
||||
extern std::optional<Backend> BACKEND;
|
||||
extern size_t ASIO_DRIVER_ID;
|
||||
extern std::optional<size_t> ASIO_DRIVER_ID;
|
||||
extern std::string ASIO_DRIVER_NAME;
|
||||
extern bool ASIO_FORCE_UNLOAD_ON_STOP;
|
||||
extern bool LOW_LATENCY_SHARED_WASAPI;
|
||||
|
||||
|
||||
@@ -82,7 +82,10 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (iid == IID_IAudioClient) {
|
||||
// almost all games request IAudioClient
|
||||
// so far we have not seen any games request IAudioClient2
|
||||
// SDVX EG Final uses IID_IAudioClient3, but only when shared mode patch is on
|
||||
if (iid == IID_IAudioClient || iid == IID_IAudioClient3) {
|
||||
|
||||
// prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM
|
||||
// like ASIO4All or FlexASIO
|
||||
@@ -92,23 +95,18 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
}
|
||||
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
|
||||
|
||||
auto client = reinterpret_cast<IAudioClient *>(*ppInterface);
|
||||
|
||||
// release old audio client if initialized
|
||||
if (hooks::audio::CLIENT) {
|
||||
hooks::audio::CLIENT->Release();
|
||||
}
|
||||
|
||||
/*
|
||||
ret = wrap_audio_client(pReal, dwClsCtx, pActivationParams, &client);
|
||||
if (FAILED(ret)) {
|
||||
return ret;
|
||||
IAudioClient *client = nullptr;
|
||||
if (iid == IID_IAudioClient) {
|
||||
client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
|
||||
} else { // IID_IAudioClient3
|
||||
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
|
||||
}
|
||||
*/
|
||||
client = wrap_audio_client(client);
|
||||
|
||||
*ppInterface = client;
|
||||
|
||||
// persist the audio client
|
||||
hooks::audio::CLIENT = client;
|
||||
hooks::audio::CLIENT->AddRef();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "hooks/audio/implementations/asio.h"
|
||||
#include "hooks/audio/implementations/wave_out.h"
|
||||
//#include "util/co_task_mem_ptr.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "defs.h"
|
||||
#include "dummy_audio_client.h"
|
||||
@@ -40,114 +41,9 @@ static void fix_rec_format(WAVEFORMATEX *pFormat) {
|
||||
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign;
|
||||
}
|
||||
|
||||
// TODO(felix): is it appropriate to automatically switch to shared mode? should we do a
|
||||
// `MessageBox` to notify the user?
|
||||
/*
|
||||
static bool check_for_exclusive_access(IAudioClient *client) {
|
||||
static bool checked_once = false;
|
||||
static bool previous_check_result = false;
|
||||
|
||||
CoTaskMemPtr<WAVEFORMATEX> mix_format;
|
||||
REFERENCE_TIME requested_duration = 0;
|
||||
|
||||
if (checked_once) {
|
||||
return previous_check_result;
|
||||
}
|
||||
if (audio::BACKEND.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// scope function so it has access to the local static variables
|
||||
auto set_result = [](bool result) {
|
||||
checked_once = true;
|
||||
previous_check_result = result;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
HRESULT ret = client->GetMixFormat(mix_format.ppv());
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetMixFormat", ret);
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "Mix Format:");
|
||||
print_format(mix_format.data());
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (ret == AUDCLNT_E_UNSUPPORTED_FORMAT) {
|
||||
auto mix_format_ex = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.data());
|
||||
|
||||
log_warning("audio::wasapi", "device does not natively support the mix format, converting to PCM");
|
||||
|
||||
if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
|
||||
IsEqualGUID(GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, mix_format_ex->SubFormat))
|
||||
{
|
||||
mix_format_ex->Format.wBitsPerSample = 16;
|
||||
mix_format_ex->Format.nBlockAlign = mix_format_ex->Format.nChannels * (mix_format_ex->Format.wBitsPerSample / 8);
|
||||
mix_format_ex->Format.nAvgBytesPerSec = mix_format_ex->Format.nSamplesPerSec * mix_format_ex->Format.nBlockAlign;
|
||||
mix_format_ex->Samples.wValidBitsPerSample = 16;
|
||||
mix_format_ex->SubFormat = GUID_KSDATAFORMAT_SUBTYPE_PCM;
|
||||
} else if (mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
|
||||
mix_format->wBitsPerSample = 16;
|
||||
mix_format->nBlockAlign = mix_format->nChannels * (mix_format->wBitsPerSample / 8);
|
||||
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
|
||||
mix_format->wFormatTag = WAVE_FORMAT_PCM;
|
||||
} else {
|
||||
log_warning("audio::wasapi", "mix format is not a floating point format");
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (FAILED(ret)) {
|
||||
log_warning("audio::wasapi", "mix format is not supported");
|
||||
return set_result(false);
|
||||
}
|
||||
}
|
||||
|
||||
ret = client->GetDevicePeriod(nullptr, &requested_duration);
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetDevicePeriod", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = client->Initialize(
|
||||
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
requested_duration,
|
||||
requested_duration,
|
||||
mix_format.data(),
|
||||
nullptr);
|
||||
|
||||
if (ret == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED || SUCCEEDED(ret)) {
|
||||
log_info("audio::wasapi", "exclusive mode is available, disabling backend");
|
||||
return set_result(true);
|
||||
} else {
|
||||
log_warning("audio::wasapi", "exclusive mode is not available, enabling backend, hr={}", FMT_HRESULT(ret));
|
||||
}
|
||||
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
HRESULT wrap_audio_client(
|
||||
IMMDevice *device,
|
||||
DWORD cls_ctx,
|
||||
PROPVARIANT *activation_params,
|
||||
IAudioClient **audio_client)
|
||||
{
|
||||
auto exclusive_available = check_for_exclusive_access(*audio_client);
|
||||
(*audio_client)->Stop();
|
||||
(*audio_client)->Reset();
|
||||
(*audio_client)->Release();
|
||||
*audio_client = nullptr;
|
||||
|
||||
SAFE_CALL("IMMDevice", "Activate", device->Activate(
|
||||
IID_IAudioClient,
|
||||
cls_ctx,
|
||||
activation_params,
|
||||
reinterpret_cast<void **>(audio_client)));
|
||||
*/
|
||||
IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
log_misc("audio::wasapi", "wrapping IAudioClient");
|
||||
|
||||
AudioBackend *backend = nullptr;
|
||||
bool requires_dummy = false;
|
||||
|
||||
@@ -164,9 +60,6 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//} else if (!exclusive_available) {
|
||||
// backend = new WaveOutBackend();
|
||||
//}
|
||||
|
||||
IAudioClient *new_client;
|
||||
|
||||
@@ -182,6 +75,23 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
|
||||
return new_client;
|
||||
}
|
||||
IAudioClient3 *wrap_audio_client3(IAudioClient3 *audio_client) {
|
||||
// TODO: ASIO backend for IAudioClient3, if there is a game that needs it
|
||||
log_misc("audio::wasapi", "wrapping IAudioClient3");
|
||||
|
||||
if (hooks::audio::BACKEND.has_value()) {
|
||||
log_fatal(
|
||||
"audio::wasapi",
|
||||
"IAudioClient3 does not currently support backends! clear -audiobackend and try again");
|
||||
}
|
||||
if (hooks::audio::USE_DUMMY) {
|
||||
log_fatal(
|
||||
"audio::wasapi",
|
||||
"IAudioClient3 does not currently support dummy context, clear -audiodummy and try again");
|
||||
}
|
||||
|
||||
return new WrappedIAudioClient(audio_client, nullptr);
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
@@ -190,8 +100,8 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIAudioClient ||
|
||||
riid == IID_IAudioClient)
|
||||
{
|
||||
riid == IID_IAudioClient ||
|
||||
riid == IID_IAudioClient3) {
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
@@ -282,20 +192,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret));
|
||||
|
||||
/*
|
||||
if (ShareMode == AUDCLNT_SHAREMODE_SHARED) {
|
||||
IAudioClockAdjustment *clock = nullptr;
|
||||
|
||||
SAFE_CALL("IAudioClient", "GetService", pReal->GetService(
|
||||
IID_IAudioClockAdjustment,
|
||||
reinterpret_cast<void **>(&clock)));
|
||||
|
||||
SAFE_CALL("IAudioClockAdjustment", "SetSampleRate", clock->SetSampleRate(
|
||||
static_cast<float>(pFormat->nSamplesPerSec)));
|
||||
}
|
||||
*/
|
||||
|
||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||
|
||||
return ret;
|
||||
@@ -482,3 +378,93 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetService(REFIID riid, void **pp
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsOffloadCapable(
|
||||
AUDIO_STREAM_CATEGORY Category,
|
||||
BOOL *pbOffloadCapable) {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
CHECK_RESULT(pReal3->IsOffloadCapable(Category, pbOffloadCapable));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::SetClientProperties(
|
||||
const AudioClientProperties *pProperties) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->SetClientProperties(pProperties));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSizeLimits(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
BOOL bEventDriven,
|
||||
REFERENCE_TIME *phnsMinBufferDuration,
|
||||
REFERENCE_TIME *phnsMaxBufferDuration) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetBufferSizeLimits(
|
||||
pFormat,
|
||||
bEventDriven,
|
||||
phnsMinBufferDuration,
|
||||
phnsMaxBufferDuration));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetSharedModeEnginePeriod(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
UINT32 *pDefaultPeriodInFrames,
|
||||
UINT32 *pFundamentalPeriodInFrames,
|
||||
UINT32 *pMinPeriodInFrames,
|
||||
UINT32 *pMaxPeriodInFrames) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetSharedModeEnginePeriod(
|
||||
pFormat,
|
||||
pDefaultPeriodInFrames,
|
||||
pFundamentalPeriodInFrames,
|
||||
pMinPeriodInFrames,
|
||||
pMaxPeriodInFrames));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetCurrentSharedModeEnginePeriod(
|
||||
WAVEFORMATEX **ppFormat,
|
||||
UINT32 *pCurrentPeriodInFrames) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetCurrentSharedModeEnginePeriod(
|
||||
ppFormat,
|
||||
pCurrentPeriodInFrames));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::InitializeSharedAudioStream(
|
||||
DWORD StreamFlags,
|
||||
UINT32 PeriodInFrames,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) {
|
||||
|
||||
if (!pFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
// verbose output
|
||||
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream hook hit");
|
||||
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(AUDCLNT_SHAREMODE_SHARED));
|
||||
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
|
||||
log_info("audio::wasapi", "... PeriodInFrames : {}", PeriodInFrames);
|
||||
print_format(pFormat);
|
||||
|
||||
// call next
|
||||
HRESULT ret = pReal3->InitializeSharedAudioStream(
|
||||
StreamFlags,
|
||||
PeriodInFrames,
|
||||
pFormat,
|
||||
AudioSessionGuid);
|
||||
|
||||
// check for failure
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient3", "InitializeSharedAudioStream", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
|
||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,16 @@ static const GUID IID_WrappedIAudioClient = {
|
||||
};
|
||||
|
||||
IAudioClient *wrap_audio_client(IAudioClient *client);
|
||||
IAudioClient3 *wrap_audio_client3(IAudioClient3 *client);
|
||||
|
||||
struct WrappedIAudioClient : IAudioClient {
|
||||
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) : pReal(orig), backend(backend) {
|
||||
struct WrappedIAudioClient : IAudioClient3 {
|
||||
|
||||
explicit WrappedIAudioClient(IAudioClient3 *orig3, AudioBackend *backend) :
|
||||
pReal(orig3), pReal3(orig3), backend(backend) {
|
||||
}
|
||||
|
||||
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) :
|
||||
pReal(orig), pReal3(nullptr), backend(backend) {
|
||||
}
|
||||
|
||||
WrappedIAudioClient(const WrappedIAudioClient &) = delete;
|
||||
@@ -47,9 +54,43 @@ struct WrappedIAudioClient : IAudioClient {
|
||||
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioClient *const pReal;
|
||||
AudioBackend *const backend;
|
||||
#pragma region IAudioClient2
|
||||
HRESULT STDMETHODCALLTYPE IsOffloadCapable(
|
||||
AUDIO_STREAM_CATEGORY Category,
|
||||
BOOL *pbOffloadCapable) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE SetClientProperties(
|
||||
const AudioClientProperties *pProperties) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
BOOL bEventDriven,
|
||||
REFERENCE_TIME *phnsMinBufferDuration,
|
||||
REFERENCE_TIME *phnsMaxBufferDuration) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioClient3
|
||||
HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
UINT32 *pDefaultPeriodInFrames,
|
||||
UINT32 *pFundamentalPeriodInFrames,
|
||||
UINT32 *pMinPeriodInFrames,
|
||||
UINT32 *pMaxPeriodInFrames) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
|
||||
WAVEFORMATEX **ppFormat,
|
||||
UINT32 *pCurrentPeriodInFrames) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
|
||||
DWORD StreamFlags,
|
||||
UINT32 PeriodInFrames,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioClient *const pReal;
|
||||
IAudioClient3 *const pReal3;
|
||||
AudioBackend *const backend;
|
||||
bool exclusive_mode = false;
|
||||
int frame_size = 0;
|
||||
};
|
||||
|
||||
@@ -149,5 +149,12 @@ LowLatencyAudioClient *LowLatencyAudioClient::Create(IMMDevice *device) {
|
||||
log_info("audio::lowlatency", "... max buffer size : {} samples ({} ms)", maxPeriod, 1000.0f * maxPeriod / pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... default buffer size : {} samples ({} ms)", defaultPeriod, 1000.0f * defaultPeriod / pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... Windows will use minimum buffer size (instead of default) for shared mode audio clients from now on");
|
||||
if (minPeriod < defaultPeriod) {
|
||||
log_info("audio::lowlatency", "minimum period is less than default period, you will see latency improvement");
|
||||
} else {
|
||||
log_warning("audio::lowlatency", "minimum period is not less than default period, you will see NO latency improvement");
|
||||
log_warning("audio::lowlatency", "this an audio driver / hardware limitation");
|
||||
}
|
||||
|
||||
return new LowLatencyAudioClient(audioClient);
|
||||
}
|
||||
|
||||
@@ -218,13 +218,40 @@ void AsioBackend::set_thread_state(AsioThreadState state) {
|
||||
bool AsioBackend::load_driver() {
|
||||
AsioDriverList asio_driver_list;
|
||||
|
||||
for (const auto &driver : asio_driver_list.driver_list) {
|
||||
log_info("audio::asio", "Driver {}", driver.id);
|
||||
log_info("audio::asio", "... Name : {}", driver.name);
|
||||
log_info("audio::asio", "... Path : {}", driver.dll_path);
|
||||
size_t driver_id = 0;
|
||||
std::string driver_name = "";
|
||||
if (hooks::audio::ASIO_DRIVER_ID.has_value()) {
|
||||
driver_id = hooks::audio::ASIO_DRIVER_ID.value();
|
||||
}
|
||||
|
||||
const auto driver_id = hooks::audio::ASIO_DRIVER_ID;
|
||||
for (const auto &driver : asio_driver_list.driver_list) {
|
||||
log_info("audio::asio", " Driver ID : {}", driver.id);
|
||||
log_info("audio::asio", " ... Name : {}", driver.name);
|
||||
log_info("audio::asio", " ... Path : {}", driver.dll_path);
|
||||
|
||||
if (!hooks::audio::ASIO_DRIVER_NAME.empty()) {
|
||||
// match on driver name
|
||||
if (driver.name == hooks::audio::ASIO_DRIVER_NAME) {
|
||||
driver_id = driver.id;
|
||||
driver_name = driver.name;
|
||||
}
|
||||
} else if (driver.id == driver_id) {
|
||||
// match on driver index (0 for default or user-specified)
|
||||
driver_name = driver.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (driver_name.empty()) {
|
||||
if (!hooks::audio::ASIO_DRIVER_NAME.empty()) {
|
||||
log_warning(
|
||||
"audio::asio",
|
||||
"tried to look for ASIO driver but failed: {}", hooks::audio::ASIO_DRIVER_NAME);
|
||||
}
|
||||
log_fatal(
|
||||
"audio::asio",
|
||||
"could not find ASIO driver to use for conversion, fix your audio options and try again");
|
||||
}
|
||||
log_info("audio::asio", "will attempt to use the following ASIO driver: ID = {}, Name = {}", driver_id, driver_name);
|
||||
|
||||
IAsio *driver = nullptr;
|
||||
auto ret = asio_driver_list.open_driver(driver_id, reinterpret_cast<void **>(&driver));
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "games/mfc/mfc.h"
|
||||
@@ -41,6 +42,15 @@
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#define D3D9_BACKEND_DEBUG 0
|
||||
|
||||
#if D3D9_BACKEND_DEBUG
|
||||
#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
|
||||
|
||||
#define CHECK_RESULT(x) \
|
||||
do { \
|
||||
HRESULT __ret = (x); \
|
||||
@@ -268,7 +278,7 @@ static bool is_dx9_on_12_enabled() {
|
||||
}
|
||||
|
||||
if (GRAPHICS_9_ON_12_STATE == DX9ON12_FORCE_ON) {
|
||||
if (avs::game::is_model("LDJ") && avs::game::is_ext(2023091500, MAXINT)) {
|
||||
if (avs::game::is_model("LDJ") && avs::game::is_ext(2023091500, INT_MAX)) {
|
||||
deferredlogs::defer_error_messages({
|
||||
"dx9on12 was force enabled by user (-dx9on12)",
|
||||
" IIDX31+ is known to be incompatible with DX 9on12, leading to blank screen or crashes",
|
||||
@@ -514,7 +524,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
if (!modified && games::iidx::is_tdj_fhd() && refresh < 110) {
|
||||
if ((width == 1920 && height == 1080) ||
|
||||
(width == 1280 && height == 720)){
|
||||
log_misc(
|
||||
log_debug(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
@@ -527,7 +537,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
|
||||
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
|
||||
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
|
||||
log_misc(
|
||||
log_debug(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
@@ -535,25 +545,13 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
|
||||
}
|
||||
|
||||
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
|
||||
log_misc(
|
||||
log_debug(
|
||||
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-iidxforce720p)",
|
||||
Mode, width, height, refresh);
|
||||
memset(pMode, 0, sizeof(*pMode));
|
||||
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);
|
||||
@@ -803,6 +801,14 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
"either use -graphics-force-refresh option or change the desktop resolution beforehand.");
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
}
|
||||
|
||||
// force single adapter
|
||||
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
|
||||
@@ -927,26 +933,37 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
auto *params = &pPresentationParameters[i];
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
|
||||
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
|
||||
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
if (!GRAPHICS_WINDOWED){
|
||||
if (i == 0) {
|
||||
GRAPHICS_FS_ORIGINAL_WIDTH = params->BackBufferWidth;
|
||||
GRAPHICS_FS_ORIGINAL_HEIGHT = params->BackBufferHeight;
|
||||
log_misc("graphics::d3d9", "original resolution: {}x{}", GRAPHICS_FS_ORIGINAL_WIDTH, GRAPHICS_FS_ORIGINAL_HEIGHT);
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"use custom resolution {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"swap full orientation {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
params->BackBufferHeight, params->BackBufferWidth);
|
||||
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
||||
}
|
||||
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"use custom resolution {}x{} => {}x{}",
|
||||
"use custom sub resolution {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
log_misc(
|
||||
"graphics::d3d9",
|
||||
"swap full orientation {}x{} => {}x{}",
|
||||
params->BackBufferWidth, params->BackBufferHeight,
|
||||
params->BackBufferHeight, params->BackBufferWidth);
|
||||
std::swap(params->BackBufferWidth, params->BackBufferHeight);
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first,
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second);
|
||||
params->BackBufferWidth = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
|
||||
params->BackBufferHeight = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,12 +991,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
for (size_t i = 0; i < num_adapters; i++) {
|
||||
auto *fullscreen_display_mode = &pFullscreenDisplayMode[i];
|
||||
|
||||
if (!GRAPHICS_WINDOWED && i == 0) {
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
if (i == 0) {
|
||||
if (GRAPHICS_FS_CUSTOM_RESOLUTION.has_value()) {
|
||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION.value().first;
|
||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION.value().second;
|
||||
} else if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
std::swap(fullscreen_display_mode->Width, fullscreen_display_mode->Height);
|
||||
}
|
||||
} else if (i == 1 && GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.has_value()) {
|
||||
fullscreen_display_mode->Width = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().first;
|
||||
fullscreen_display_mode->Height = GRAPHICS_FS_CUSTOM_RESOLUTION_SUB.value().second;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1022,6 +1044,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
}
|
||||
}
|
||||
|
||||
if (!GRAPHICS_WINDOWED && num_adapters >= 2 && GRAPHICS_FORCE_REFRESH_SUB.has_value()) {
|
||||
log_info("graphics::d3d9", "force sub refresh rate: {} => {} Hz (-graphics-force-refresh-sub option)",
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz,
|
||||
GRAPHICS_FORCE_REFRESH_SUB.value());
|
||||
|
||||
pPresentationParameters[1].FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
if (pFullscreenDisplayMode) {
|
||||
pFullscreenDisplayMode[1].RefreshRate = GRAPHICS_FORCE_REFRESH_SUB.value();
|
||||
}
|
||||
}
|
||||
|
||||
// force single adapter
|
||||
if (GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
log_info("graphics::d3d9", "disabling adapter group device with force single adapter mode");
|
||||
@@ -1142,9 +1175,15 @@ IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen() {
|
||||
}
|
||||
|
||||
static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
|
||||
// iidx/sdvx
|
||||
int swapchain = 1;
|
||||
if (games::gitadora::is_arena_model()) {
|
||||
swapchain = 3;
|
||||
}
|
||||
|
||||
if (!ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE && SUB_SWAP_CHAIN == nullptr) {
|
||||
ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = true;
|
||||
HRESULT hr = wrapped_device->GetSwapChain(1, &SUB_SWAP_CHAIN);
|
||||
HRESULT hr = wrapped_device->GetSwapChain(swapchain, &SUB_SWAP_CHAIN);
|
||||
if (FAILED(hr)) {
|
||||
log_warning(
|
||||
"graphics::d3d9",
|
||||
@@ -1353,11 +1392,11 @@ void graphics_d3d9_on_present(
|
||||
}
|
||||
|
||||
// for IIDX TDJ / SDVX UFC, handle subscreen
|
||||
const bool is_vm =
|
||||
avs::game::is_model("KFC") &&
|
||||
(avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H');
|
||||
const bool is_vm = games::sdvx::is_valkyrie_model();
|
||||
const bool is_tdj = avs::game::is_model("LDJ") && games::iidx::TDJ_MODE;
|
||||
if (is_vm || is_tdj) {
|
||||
const bool is_gfdm_arena =
|
||||
games::gitadora::is_arena_model() && games::gitadora::ARENA_SINGLE_WINDOW;
|
||||
if (is_vm || is_tdj || is_gfdm_arena) {
|
||||
graphics_d3d9_ldj_on_present(wrapped_device);
|
||||
}
|
||||
|
||||
@@ -1519,4 +1558,4 @@ void update_backbuffer_dimensions(D3DPRESENT_PARAMETERS *params) {
|
||||
params->BackBufferHeight);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/flags_helper.h"
|
||||
@@ -249,8 +250,16 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::CreateAdditionalSwapChain(
|
||||
WRAP_VERBOSE;
|
||||
|
||||
HRESULT hr = pReal->CreateAdditionalSwapChain(pPresentationParameters, ppSwapChain);
|
||||
|
||||
bool create_swap_chain = false;
|
||||
if (avs::game::is_model({"LDJ", "KFC"})) {
|
||||
create_swap_chain = true;
|
||||
} else if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
pPresentationParameters->BackBufferWidth == 800) {
|
||||
create_swap_chain = true;
|
||||
}
|
||||
|
||||
if (create_swap_chain) {
|
||||
if (SUCCEEDED(hr) && !sub_swapchain) {
|
||||
sub_swapchain = new WrappedIDirect3DSwapChain9(this, *ppSwapChain);
|
||||
sub_swapchain->should_run_hooks = false;
|
||||
@@ -295,7 +304,17 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
|
||||
return D3D_OK;
|
||||
}
|
||||
|
||||
bool swap = false;
|
||||
if (iSwapChain == 1 && avs::game::is_model({"LDJ", "KFC"})) {
|
||||
swap = true;
|
||||
}
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
iSwapChain == 3) {
|
||||
swap = true;
|
||||
}
|
||||
|
||||
if (swap) {
|
||||
if (sub_swapchain) {
|
||||
sub_swapchain->AddRef();
|
||||
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(sub_swapchain);
|
||||
@@ -574,7 +593,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::UpdateTexture(
|
||||
WRAP_DEBUG;
|
||||
|
||||
if (CUSTOM_RESET) {
|
||||
#ifdef __GNUC__
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
|
||||
// Do a pointer compare rather than `QueryInterface` because it is a lot cheaper than incrementing
|
||||
// the reference count
|
||||
@@ -742,16 +761,14 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
|
||||
const int w = param.BackBufferWidth;
|
||||
const int h = param.BackBufferHeight;
|
||||
|
||||
if (cfg::SCREENRESIZE->enable_screen_resize) {
|
||||
RECT rect {
|
||||
0,
|
||||
0,
|
||||
(LONG)topSurface_width,
|
||||
(LONG)topSurface_height,
|
||||
};
|
||||
pReal->ColorFill(topSurface, &rect, D3DCOLOR_XRGB(0, 0, 0));
|
||||
// this code used to clear the surface using ColorFill on every call, but
|
||||
// this turned out to be very expensive, leading to major frame drops in
|
||||
// SDVX Live2D scenario
|
||||
if (cfg::SCREENRESIZE->need_surface_clean) {
|
||||
pReal->ColorFill(topSurface, nullptr, D3DCOLOR_XRGB(0, 0, 0));
|
||||
cfg::SCREENRESIZE->need_surface_clean = false;
|
||||
}
|
||||
|
||||
|
||||
D3DLOCKED_RECT rect;
|
||||
HRESULT hr;
|
||||
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
|
||||
|
||||
@@ -58,10 +58,10 @@ 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;
|
||||
std::optional<uint32_t> GRAPHICS_FORCE_REFRESH_SUB;
|
||||
std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
|
||||
bool GRAPHICS_PREVENT_SECONDARY_WINDOW = false;
|
||||
@@ -70,6 +70,7 @@ bool GRAPHICS_9_ON_12_REQUESTED_BY_GAME = false;
|
||||
bool SUBSCREEN_FORCE_REDRAW = false;
|
||||
bool D3D9_DEVICE_HOOK_DISABLE = false;
|
||||
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
|
||||
std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
|
||||
bool GRAPHICS_FS_ORIENTATION_SWAP = false;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_WIDTH = 0;
|
||||
uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT = 0;
|
||||
@@ -96,6 +97,7 @@ static decltype(SetCursor) *SetCursor_orig = nullptr;
|
||||
static decltype(SetWindowLongA) *SetWindowLongA_orig = nullptr;
|
||||
static decltype(SetWindowLongW) *SetWindowLongW_orig = nullptr;
|
||||
static decltype(SetWindowPos) *SetWindowPos_orig = nullptr;
|
||||
static decltype(ShowWindow) *ShowWindow_orig = nullptr;
|
||||
|
||||
static void reset_window_hook(HWND hWnd) {
|
||||
overlay::destroy(hWnd);
|
||||
@@ -319,48 +321,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)
|
||||
const auto adjust_orientation =
|
||||
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
|
||||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW);
|
||||
|
||||
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 {}",
|
||||
orientation);
|
||||
mode.dmPelsWidth = nWidth;
|
||||
mode.dmPelsHeight = nHeight;
|
||||
mode.dmDisplayOrientation = orientation;
|
||||
}
|
||||
|
||||
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", "failed to change display settings: {}", 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
|
||||
@@ -373,7 +333,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
bool is_sdvx_sub_window = avs::game::is_model("KFC") && window_name.ends_with(" Sub Screen");
|
||||
bool is_gfdm_sub_window = games::gitadora::is_arena_model() && window_name.ends_with("SMALL");
|
||||
|
||||
// TDJ windowed mode with subscreen: hide maximize button
|
||||
// hide maximize button (prevent misaligned touches)
|
||||
if ((is_tdj_sub_window && GRAPHICS_IIDX_WSUB) || is_sdvx_sub_window || is_gfdm_sub_window) {
|
||||
dwStyle &= ~(WS_MAXIMIZEBOX);
|
||||
}
|
||||
@@ -381,7 +341,7 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
if (is_gfdm_sub_window) {
|
||||
dwStyle &= ~(WS_SIZEBOX);
|
||||
}
|
||||
if ((is_tdj_sub_window || is_sdvx_sub_window || is_gfdm_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
|
||||
if ((is_tdj_sub_window || is_sdvx_sub_window) && GRAPHICS_WSUB_BORDERLESS) {
|
||||
dwStyle &= ~(WS_OVERLAPPEDWINDOW);
|
||||
}
|
||||
|
||||
@@ -424,12 +384,19 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
|
||||
graphics_hook_subscreen_window(SDVX_SUBSCREEN_WINDOW);
|
||||
}
|
||||
|
||||
if (is_gfdm_sub_window) {
|
||||
// only hook touch window if multiple windows are allowed
|
||||
if (is_gfdm_sub_window && !games::gitadora::ARENA_SINGLE_WINDOW) {
|
||||
GFDM_SUBSCREEN_WINDOW = result;
|
||||
graphics_hook_subscreen_window(GFDM_SUBSCREEN_WINDOW);
|
||||
}
|
||||
|
||||
disable_touch_indicators(result);
|
||||
log_misc(
|
||||
"graphics",
|
||||
"CreateWindowExA returned {}, {}",
|
||||
fmt::ptr(result),
|
||||
lpWindowName ? lpWindowName : "(null)");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -461,7 +428,8 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
||||
}
|
||||
|
||||
// windowed mode adjustments
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
// check the width to filter out invisible system-created windows
|
||||
if (GRAPHICS_WINDOWED && nWidth > 0) {
|
||||
|
||||
// change window style
|
||||
dwExStyle = 0;
|
||||
@@ -496,6 +464,12 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
|
||||
hWndParent, hMenu, hInstance, lpParam);
|
||||
GRAPHICS_WINDOWS.push_back(result);
|
||||
|
||||
log_misc(
|
||||
"graphics",
|
||||
"CreateWindowExW returned {}, {}",
|
||||
fmt::ptr(result),
|
||||
lpWindowName ? ws2s(lpWindowName) : "(null)");
|
||||
|
||||
disable_touch_indicators(result);
|
||||
return result;
|
||||
}
|
||||
@@ -660,10 +634,29 @@ static BOOL WINAPI SetWindowPos_hook(HWND hWnd, HWND hWndInsertAfter,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// prevent gitadora arena model from shifting windows around if the user has preferences
|
||||
if (GRAPHICS_WINDOWED && games::gitadora::is_arena_model() &&
|
||||
GRAPHICS_HOOKED_WINDOW.has_value() && hWnd == GRAPHICS_HOOKED_WINDOW.value() &&
|
||||
cfg::SCREENRESIZE->enable_window_resize) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// call original
|
||||
return SetWindowPos_orig(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags);
|
||||
}
|
||||
|
||||
static BOOL WINAPI ShowWindow_hook(HWND hWnd, int nCmdShow) {
|
||||
if (games::gitadora::is_arena_model() &&
|
||||
games::gitadora::ARENA_SINGLE_WINDOW &&
|
||||
hWnd != GRAPHICS_HOOKED_WINDOW) {
|
||||
log_info("graphics", "ShowWindow_hook - hiding sub window {}", fmt::ptr(hWnd));
|
||||
return true;
|
||||
}
|
||||
|
||||
// call original
|
||||
return ShowWindow_orig(hWnd, nCmdShow);
|
||||
}
|
||||
|
||||
static ATOM WINAPI RegisterClassA_hook(const WNDCLASSA *lpWndClass) {
|
||||
|
||||
// check for null
|
||||
@@ -806,6 +799,7 @@ void graphics_init() {
|
||||
SetWindowLongA_orig = detour::iat_try("SetWindowLongA", SetWindowLongA_hook);
|
||||
SetWindowLongW_orig = detour::iat_try("SetWindowLongW", SetWindowLongW_hook);
|
||||
SetWindowPos_orig = detour::iat_try("SetWindowPos", SetWindowPos_hook);
|
||||
ShowWindow_orig = detour::iat_try("ShowWindow", ShowWindow_hook);
|
||||
|
||||
detour::iat_try("MessageBoxA", MessageBoxA_hook);
|
||||
detour::iat_try("MessageBoxExA", MessageBoxExA_hook);
|
||||
@@ -977,6 +971,7 @@ bool graphics_capture_receive_jpeg(int screen, TooJpeg::WRITE_ONE_BYTE receiver,
|
||||
auto capture_width = capture.width;
|
||||
auto capture_height = capture.height;
|
||||
auto capture_timestamp = capture.timestamp;
|
||||
capture.data = nullptr;
|
||||
lock.unlock();
|
||||
|
||||
// validate data
|
||||
@@ -1073,3 +1068,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);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ enum graphics_orientation {
|
||||
ORIENTATION_CW = 0,
|
||||
ORIENTATION_CCW = 1,
|
||||
ORIENTATION_NORMAL = 2,
|
||||
ORIENTATION_FLIPPED = 3
|
||||
};
|
||||
|
||||
enum graphics_dx9on12_state {
|
||||
@@ -29,13 +30,14 @@ 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;
|
||||
extern std::optional<int> GRAPHICS_FORCE_VSYNC_BUFFER;
|
||||
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
|
||||
extern bool GRAPHICS_PREVENT_SECONDARY_WINDOW;
|
||||
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION;
|
||||
extern std::optional<std::pair<uint32_t, uint32_t>> GRAPHICS_FS_CUSTOM_RESOLUTION_SUB;
|
||||
extern bool GRAPHICS_FS_ORIENTATION_SWAP;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_WIDTH;
|
||||
extern uint32_t GRAPHICS_FS_ORIGINAL_HEIGHT;
|
||||
@@ -48,6 +50,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 std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
extern bool GRAPHICS_IIDX_WSUB;
|
||||
extern std::optional<std::string> GRAPHICS_WSUB_SIZE;
|
||||
@@ -99,5 +102,11 @@ void graphics_capture_initial_window(HWND hWnd);
|
||||
void graphics_update_window_style(HWND hWnd);
|
||||
void graphics_update_z_order(HWND hWnd, bool always_on_top);
|
||||
void graphics_move_resize_window(HWND hWnd);
|
||||
bool graphics_window_change_crashes_game();
|
||||
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 change_primary_monitor(const std::string &monitor_name);
|
||||
void update_monitor_on_boot(std::optional<graphics_orientation> target_orientation, UINT target_refresh_rate);
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/logging.h"
|
||||
@@ -23,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;
|
||||
std::optional<HWND> GRAPHICS_HOOKED_WINDOW;
|
||||
|
||||
// IIDX Windowed Subscreen - starts out as false, enabled by IIDX module on pre-attach as needed
|
||||
bool GRAPHICS_IIDX_WSUB = false;
|
||||
@@ -55,15 +57,22 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
|
||||
graphics_load_windowed_parameters();
|
||||
|
||||
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
|
||||
GRAPHICS_HOOKED_WINDOW = hWnd;
|
||||
|
||||
log_debug("graphics-windowed", "graphics_capture_initial_window called");
|
||||
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
|
||||
cfg::SCREENRESIZE->init_window_style_ex = GetWindowLong(hWnd, GWL_EXSTYLE);
|
||||
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_capture_initial_window called, hWnd={}",
|
||||
fmt::ptr(hWnd));
|
||||
|
||||
RECT rect;
|
||||
if (!GetClientRect(hWnd, &rect)) {
|
||||
log_warning(
|
||||
"graphics",
|
||||
"graphics_capture_initial_window - GetClientRect failed, GLE: {}",
|
||||
"graphics-windowed",
|
||||
"[{}] graphics_capture_initial_window - GetClientRect failed, GLE: {}",
|
||||
fmt::ptr(hWnd),
|
||||
GetLastError());
|
||||
return;
|
||||
}
|
||||
@@ -75,12 +84,22 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_capture_initial_window initial window size {}x{}, ratio {}",
|
||||
"[{}] graphics_capture_initial_window initial window size {}x{}, ratio {}",
|
||||
fmt::ptr(hWnd),
|
||||
client_w, client_h, cfg::SCREENRESIZE->init_client_aspect_ratio);
|
||||
|
||||
// ensure frame size is captured
|
||||
graphics_wm_style_changed(hWnd, false);
|
||||
|
||||
// if the game is not compatible, ignore user-supplied values and keep the current size
|
||||
if (graphics_window_resize_breaks_game()) {
|
||||
cfg::SCREENRESIZE->client_width = client_w;
|
||||
cfg::SCREENRESIZE->client_height = client_h;
|
||||
}
|
||||
if (graphics_window_move_and_resize_breaks_game()) {
|
||||
cfg::SCREENRESIZE->enable_window_resize = false;
|
||||
}
|
||||
|
||||
// if there was no user-supplied dimension, seed it with the current size
|
||||
// so that the next resize operation will work
|
||||
if (cfg::SCREENRESIZE->client_width == 0) {
|
||||
@@ -94,7 +113,8 @@ void graphics_capture_initial_window(HWND hWnd) {
|
||||
// resize must be done before applying the border
|
||||
if (cfg::SCREENRESIZE->enable_window_resize) {
|
||||
log_info(
|
||||
"graphics-windowed", "change window rect - window offset: {}x{}, client size: {}x{}",
|
||||
"graphics-windowed", "[{}] change window rect - window offset: {}x{}, client size: {}x{}",
|
||||
fmt::ptr(hWnd),
|
||||
cfg::SCREENRESIZE->window_offset_x,
|
||||
cfg::SCREENRESIZE->window_offset_y,
|
||||
cfg::SCREENRESIZE->client_width,
|
||||
@@ -104,12 +124,13 @@ void graphics_capture_initial_window(HWND 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: {}",
|
||||
"graphics-windowed", "[{}] change window style - decoration: {}",
|
||||
fmt::ptr(hWnd),
|
||||
cfg::SCREENRESIZE->window_decoration);
|
||||
graphics_update_window_style(hWnd);
|
||||
}
|
||||
if (cfg::SCREENRESIZE->window_always_on_top) {
|
||||
log_info("graphics-windowed", "change window z-order - always on top");
|
||||
log_info("graphics-windowed", "[{}] change window z-order - always on top", fmt::ptr(hWnd));
|
||||
graphics_update_z_order(hWnd, true);
|
||||
}
|
||||
|
||||
@@ -152,7 +173,7 @@ void graphics_load_windowed_parameters() {
|
||||
cfg::SCREENRESIZE->window_offset_x = result.first;
|
||||
cfg::SCREENRESIZE->window_offset_y = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -windowpos");
|
||||
log_fatal("graphics-windowed", "failed to parse -windowpos");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +202,7 @@ void graphics_load_windowed_subscreen_parameters() {
|
||||
GRAPHICS_WSUB_WIDTH = result.first;
|
||||
GRAPHICS_WSUB_HEIGHT = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubsize");
|
||||
log_fatal("graphics-windowed", "failed to parse -iidxwsubsize / -sdvxwsubsize");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +216,7 @@ void graphics_load_windowed_subscreen_parameters() {
|
||||
GRAPHICS_WSUB_X = result.first;
|
||||
GRAPHICS_WSUB_Y = result.second;
|
||||
} else {
|
||||
log_warning("graphics-windowed", "failed to parse -wsubpos");
|
||||
log_fatal("graphics-windowed", "failed to parse -iidxwsubpos / -sdvxwsubpos");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,20 +389,29 @@ void graphics_update_window_style(HWND hWnd) {
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
if (graphics_window_change_crashes_game()) {
|
||||
if (graphics_window_decoration_change_crashes_game()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("graphics-windowed", "graphics_update_window_style called");
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style called for hWnd={}",
|
||||
fmt::ptr(hWnd));
|
||||
|
||||
// update frame style
|
||||
auto style = cfg::SCREENRESIZE->init_window_style;
|
||||
auto style_ex = cfg::SCREENRESIZE->init_window_style_ex;
|
||||
switch (cfg::SCREENRESIZE->window_decoration) {
|
||||
case cfg::WindowDecorationMode::Borderless:
|
||||
style &= ~WS_OVERLAPPEDWINDOW;
|
||||
style_ex &= ~WS_EX_CLIENTEDGE;
|
||||
style_ex &= ~WS_EX_WINDOWEDGE;
|
||||
break;
|
||||
case cfg::WindowDecorationMode::ResizableFrame:
|
||||
style |= WS_OVERLAPPEDWINDOW;
|
||||
if (graphics_window_resize_breaks_game()) {
|
||||
style &= ~WS_SIZEBOX;
|
||||
}
|
||||
break;
|
||||
case cfg::WindowDecorationMode::Default:
|
||||
default:
|
||||
@@ -393,11 +423,26 @@ void graphics_update_window_style(HWND hWnd) {
|
||||
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
|
||||
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
|
||||
style);
|
||||
SetWindowLong(hWnd, GWL_STYLE, style);
|
||||
|
||||
const auto ret = SetWindowLong(hWnd, GWL_STYLE, style);
|
||||
(void)ret;
|
||||
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style - SetWindowLong(GWL_STYLE) returned {:x}, GLE={}",
|
||||
ret,
|
||||
GetLastError());
|
||||
|
||||
const auto ret2 = SetWindowLong(hWnd, GWL_EXSTYLE, style_ex);
|
||||
(void)ret2;
|
||||
log_debug(
|
||||
"graphics-windowed",
|
||||
"graphics_update_window_style SetWindowLong(GWL_EXSTYLE) returned {:x}, GLE={}",
|
||||
ret2,
|
||||
GetLastError());
|
||||
|
||||
// SetWindowPos must be called after SetWindowLong if the frame style changed
|
||||
// this will be done in WM_STYLECHANGED handler
|
||||
log_debug("graphics-windowed", "graphics_update_window_style returned");
|
||||
}
|
||||
|
||||
void graphics_update_z_order(HWND hWnd, bool always_on_top) {
|
||||
@@ -454,12 +499,19 @@ void graphics_move_resize_window(HWND hWnd) {
|
||||
log_debug("graphics-windowed", "graphics_move_resize_window returned");
|
||||
}
|
||||
|
||||
bool graphics_window_change_crashes_game() {
|
||||
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");
|
||||
|
||||
// changing window decoration also changes dimensions and position
|
||||
// so this must be prevented
|
||||
if (graphics_window_move_and_resize_breaks_game()) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
@@ -468,3 +520,43 @@ bool graphics_window_change_crashes_game() {
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool graphics_window_move_and_resize_breaks_game() {
|
||||
static std::once_flag flag;
|
||||
static bool result = false;
|
||||
|
||||
std::call_once(flag, []() {
|
||||
// games like reflec beat in use spice touch overlay for windowed mode
|
||||
// and mouse input breaks if window is moved as the overlay does not
|
||||
// currently support window moves/resizes
|
||||
result = avs::game::is_model({"KBR", "LBR", "MBR"});
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
"ignoring changes to window properties due to incompatibility with this game");
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool graphics_window_resize_breaks_game() {
|
||||
// for whatever reason, gitadora games on dx/sd/sd2 cabs behave poorly when window is resized
|
||||
// (eihter at launch or while it's running)
|
||||
// usually results in: soft lock during scene transtions or really long transitions,
|
||||
// backgrounds of menus not rendering, etc.
|
||||
bool is_gitadora = avs::game::is_model({ "J32", "J33", "K32", "K33", "L32", "L33", "M32" });
|
||||
bool is_arena = games::gitadora::is_arena_model();
|
||||
|
||||
bool result = graphics_window_move_and_resize_breaks_game() || (is_gitadora && !is_arena);
|
||||
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [&result]() {
|
||||
if (result) {
|
||||
log_warning(
|
||||
"graphics-windowed",
|
||||
"ignoring changes to window size due to incompatibility with this game");
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// GetLocaleInfoEx
|
||||
#define _WIN32_WINNT 0x0600
|
||||
|
||||
#include "lang.h"
|
||||
|
||||
#define WIN32_NO_STATUS
|
||||
@@ -10,6 +13,7 @@
|
||||
#include "avs/game.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
@@ -220,8 +224,7 @@ void hooks::lang::early_init() {
|
||||
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
if (avs::game::is_model("KFC") && isValkyrieCabinetMode) {
|
||||
if (games::sdvx::is_valkyrie_model()) {
|
||||
log_info("hooks::lang", "hooking GetSystemDefaultLCID");
|
||||
detour::trampoline_try(
|
||||
"kernel32.dll",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// DisplayConfigSetDeviceInfo
|
||||
#define _WIN32_WINNT 0x0601
|
||||
|
||||
#include "winuser.h"
|
||||
|
||||
#include "util/detour.h"
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/peb.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/sysutils.h"
|
||||
#include "util/tapeled.h"
|
||||
#include "util/time.h"
|
||||
@@ -326,6 +327,10 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::GraphicsForceRefresh].is_active()) {
|
||||
GRAPHICS_FORCE_REFRESH = options[launcher::Options::GraphicsForceRefresh].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::FullscreenSubRefreshRate].is_active()) {
|
||||
GRAPHICS_FORCE_REFRESH_SUB =
|
||||
options[launcher::Options::FullscreenSubRefreshRate].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::GraphicsForceSingleAdapter].value_bool()) {
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||
}
|
||||
@@ -346,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
|
||||
@@ -369,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
|
||||
@@ -554,6 +562,10 @@ int main_implementation(int argc, char *argv[]) {
|
||||
nvenc_hook::FORCE_DISABLE = true;
|
||||
}
|
||||
#endif
|
||||
if (options[launcher::Options::OtocaCamHook].value_bool()) {
|
||||
games::otoca::BYPASS_CAMERA = true;
|
||||
}
|
||||
|
||||
if (options[launcher::Options::LoadJubeatModule].value_bool()) {
|
||||
attach_jb = true;
|
||||
}
|
||||
@@ -584,6 +596,25 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
|
||||
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraArenaSingleWindow].value_bool() && GRAPHICS_WINDOWED) {
|
||||
games::gitadora::ARENA_SINGLE_WINDOW = true;
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraWailHold].is_active()) {
|
||||
socd::TILT_HOLD_MS = options[launcher::Options::GitaDoraWailHold].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraPickAlgo].is_active()) {
|
||||
const auto text = options[launcher::Options::GitaDoraPickAlgo].value_text();
|
||||
if (text == "legacy") {
|
||||
games::gitadora::PICK_ALGO.reset();
|
||||
} else if (text == "neutral") {
|
||||
games::gitadora::PICK_ALGO = socd::SocdAlgorithm::Neutral;
|
||||
} else if (text == "raw") {
|
||||
games::gitadora::PICK_ALGO = socd::SocdAlgorithm::None;
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraSubOverlaySize].is_active()) {
|
||||
games::gitadora::SUBSCREEN_OVERLAY_SIZE = options[launcher::Options::GitaDoraSubOverlaySize].value_text();
|
||||
}
|
||||
if (options[launcher::Options::LoadNostalgiaModule].value_bool()) {
|
||||
attach_nostalgia = true;
|
||||
}
|
||||
@@ -605,6 +636,17 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::GitaDoraTwoChannelAudio].value_bool()) {
|
||||
games::gitadora::TWOCHANNEL = true;
|
||||
}
|
||||
if (options[launcher::Options::GitaDoraLefty].is_active()) {
|
||||
const auto text = options[launcher::Options::GitaDoraLefty].value_text();
|
||||
if (text == "p1") {
|
||||
games::gitadora::P1_LEFTY = true;
|
||||
} else if (text == "p2") {
|
||||
games::gitadora::P2_LEFTY = true;
|
||||
} else if (text == "both") {
|
||||
games::gitadora::P1_LEFTY = true;
|
||||
games::gitadora::P2_LEFTY = true;
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::LoadDDRModule].value_bool()) {
|
||||
attach_ddr = true;
|
||||
}
|
||||
@@ -941,7 +983,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()) {
|
||||
@@ -959,6 +1001,12 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::DisableOverlay].value_bool()) {
|
||||
overlay::ENABLED = false;
|
||||
}
|
||||
if (options[launcher::Options::OverlayScaling].is_active() && !cfg::CONFIGURATOR_STANDALONE && !cfg_run) {
|
||||
const auto val = options[launcher::Options::OverlayScaling].value_uint32();
|
||||
if (10 <= val && val <= 400 && val != 100) {
|
||||
overlay::UI_SCALE_PERCENT = options[launcher::Options::OverlayScaling].value_uint32();
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::DisableAudioHooks].value_bool()) {
|
||||
hooks::audio::ENABLED = false;
|
||||
deferredlogs::defer_error_messages({
|
||||
@@ -970,18 +1018,26 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::spice2x_DisableVolumeHook].value_bool()) {
|
||||
hooks::audio::VOLUME_HOOK_ENABLED = false;
|
||||
}
|
||||
|
||||
if (options[launcher::Options::AudioBackend].is_active()) {
|
||||
auto &name = options[launcher::Options::AudioBackend].value_text();
|
||||
auto backend = hooks::audio::name_to_backend(name.c_str());
|
||||
if (!backend.has_value() && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "invalid audio backend: {}", name);
|
||||
}
|
||||
|
||||
hooks::audio::BACKEND = backend;
|
||||
}
|
||||
|
||||
if (options[launcher::Options::AsioDriverId].is_active()) {
|
||||
hooks::audio::ASIO_DRIVER_ID = options[launcher::Options::AsioDriverId].value_uint32();
|
||||
}
|
||||
if (options[launcher::Options::AsioDriverName].is_active()) {
|
||||
hooks::audio::BACKEND = hooks::audio::name_to_backend("asio");
|
||||
hooks::audio::ASIO_DRIVER_NAME = options[launcher::Options::AsioDriverName].value_text();
|
||||
// this new option overrides the old one
|
||||
hooks::audio::ASIO_DRIVER_ID.reset();
|
||||
}
|
||||
|
||||
if (options[launcher::Options::AudioDummy].value_bool()) {
|
||||
hooks::audio::USE_DUMMY = true;
|
||||
}
|
||||
@@ -1042,8 +1098,8 @@ int main_implementation(int argc, char *argv[]) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::spice2x_WindowSize].value_text(), result)) {
|
||||
GRAPHICS_WINDOW_SIZE = result;
|
||||
} else {
|
||||
log_warning("launcher", "failed to parse -windowsize");
|
||||
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "failed to parse -windowsize");
|
||||
}
|
||||
}
|
||||
if (options[launcher::Options::spice2x_WindowPosition].is_active()) {
|
||||
@@ -1087,6 +1143,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::spice2x_DRSTransposeTouch].value_bool()) {
|
||||
games::drs::TRANSPOSE_TOUCH = true;
|
||||
}
|
||||
if (options[launcher::Options::DRSRGBCameraHook].value_bool()) {
|
||||
games::drs::RGB_CAMERA_HOOK = true;
|
||||
}
|
||||
if (options[launcher::Options::spice2x_AutoCard].is_active()) {
|
||||
const auto text = options[launcher::Options::spice2x_AutoCard].value_text();
|
||||
if (text == "p1") {
|
||||
@@ -1341,12 +1400,33 @@ int main_implementation(int argc, char *argv[]) {
|
||||
);
|
||||
}
|
||||
|
||||
if (launcher::signal::DISABLE && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_warning(
|
||||
"launcher",
|
||||
"WARNING - user specified -signaldisable option\n\n\n"
|
||||
"!!! !!!\n"
|
||||
"!!! Using -signaldisable option disables all exception handling, !!!\n"
|
||||
"!!! which means when the game crashes it will likely just vanish !!!\n"
|
||||
"!!! without troubleshooting information in the logs. !!!\n"
|
||||
"!!! !!!\n"
|
||||
);
|
||||
}
|
||||
|
||||
if (options[launcher::Options::FullscreenResolution].is_active()) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION = result;
|
||||
} else {
|
||||
log_warning("launcher", "failed to parse -forceres");
|
||||
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "failed to parse -forceres");
|
||||
}
|
||||
}
|
||||
|
||||
if (options[launcher::Options::FullscreenSubResolution].is_active()) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::FullscreenSubResolution].value_text(), result)) {
|
||||
GRAPHICS_FS_CUSTOM_RESOLUTION_SUB = result;
|
||||
} else if (!cfg_run && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "failed to parse -forceressub");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1430,6 +1510,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
// early hooks
|
||||
for (auto &hook : early_hooks) {
|
||||
log_info("launcher", "loading early hook DLL {}", hook);
|
||||
libutils::print_dll_info(hook);
|
||||
HMODULE module;
|
||||
if (!(module = libutils::try_library(hook))) {
|
||||
log_warning("launcher", "failed to load early hook {}: {}", hook, get_last_error_string());
|
||||
@@ -1823,7 +1904,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Polaris Chord
|
||||
if (check_dll("kamunity.dll") && fileutils::dir_exists("game/svm_Data")) {
|
||||
if (check_dll("kamunity.dll") && fileutils::file_exists("game/svm.exe")) {
|
||||
avs::game::DLL_NAME = "kamunity.dll";
|
||||
attach_io = true;
|
||||
attach_pc = true;
|
||||
@@ -1862,7 +1943,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
"This usually means one of the following:\n\n"
|
||||
" 1. You put spice executables in the wrong place, or\n"
|
||||
" 2. XML files in prop directory are malformed or missing, or\n"
|
||||
" 3. You passed in invalid options (usually, path overrides like -exec)\n\n"
|
||||
" 3. You passed in invalid options (do not specify -exec or -modules if you don't know what you're doing)\n\n"
|
||||
);
|
||||
|
||||
log_fatal("launcher", "module auto detection failed, see log.txt for troubleshooting");
|
||||
@@ -2055,6 +2136,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) {
|
||||
@@ -2230,6 +2317,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
// load hooks
|
||||
for (auto &hook : game_hooks) {
|
||||
log_info("launcher", "loading hook DLL {}", hook);
|
||||
libutils::print_dll_info(hook);
|
||||
HMODULE module;
|
||||
if (!(module = libutils::try_library(hook))) {
|
||||
log_warning("launcher", "failed to load hook {}: {}", hook, get_last_error_string());
|
||||
@@ -2490,9 +2578,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(
|
||||
@@ -2505,16 +2594,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())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
#include "launcher/launcher.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#define FOREGROUND_GREY (8)
|
||||
#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
|
||||
#define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN)
|
||||
#define FOREGROUND_GREY (8)
|
||||
#define FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
|
||||
#define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN)
|
||||
#define FOREGROUND_CYAN (FOREGROUND_GREEN | FOREGROUND_BLUE)
|
||||
#define FOREGROUND_MAGENTA (FOREGROUND_RED | FOREGROUND_BLUE)
|
||||
|
||||
namespace logger {
|
||||
|
||||
@@ -103,9 +105,6 @@ namespace logger {
|
||||
last_style = content.second;
|
||||
|
||||
switch (content.second) {
|
||||
case Style::DEFAULT:
|
||||
set_console_color(hTerminal, FOREGROUND_WHITE);
|
||||
break;
|
||||
case Style::GREY:
|
||||
set_console_color(hTerminal, FOREGROUND_GREY);
|
||||
break;
|
||||
@@ -115,6 +114,13 @@ namespace logger {
|
||||
case Style::RED:
|
||||
set_console_color(hTerminal, FOREGROUND_RED);
|
||||
break;
|
||||
case Style::SPECIAL:
|
||||
set_console_color(hTerminal, FOREGROUND_CYAN);
|
||||
break;
|
||||
case Style::DEFAULT:
|
||||
default:
|
||||
set_console_color(hTerminal, FOREGROUND_WHITE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user