mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
Compare commits
184 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b930ad95f | |||
| 3c60f3966b | |||
| c59d399ab8 | |||
| f69e40fa26 | |||
| e9dcc5717e | |||
| 5bec3d5db7 | |||
| 6ec2b47345 | |||
| ebda16beae | |||
| c29a26f183 | |||
| 2014e9cf75 | |||
| 33f2e1d4b7 | |||
| eb037542b4 | |||
| 039b42ad53 | |||
| 33b4718744 | |||
| 71ba9b6b47 | |||
| 1957917270 | |||
| b9ccddf47d | |||
| cd0ba51b5a | |||
| 1f23d88c7a | |||
| 46e43ab09c | |||
| 96950b6b4e | |||
| 5f94ef4478 | |||
| c4193eddc5 | |||
| c8962a0e77 | |||
| b6a749e5db | |||
| 7dbbe9809e | |||
| 79a3e32d5d | |||
| c5a4e954f9 | |||
| 7b862768b0 | |||
| af8d8dae9f | |||
| d9d5823fdb | |||
| 82dda33800 | |||
| 3876e38636 | |||
| 6bc1357d6a | |||
| 37218e7fe0 | |||
| 678e11eade | |||
| f72313fe45 | |||
| 06374ef78a | |||
| bdde1ec6b8 | |||
| d2176fe4f0 | |||
| fb9eef904a | |||
| 48903998a5 | |||
| f2dca0265f | |||
| 1147ed9858 | |||
| 19d93f4ffd | |||
| 15bfffa1d3 | |||
| 18a9a47c33 | |||
| 6838e69ebe | |||
| 26b3b64794 | |||
| aadd2bdd59 | |||
| d0e24e31ca | |||
| 4fb7f20c7b | |||
| 6ddae70c5a | |||
| b38160d6c2 | |||
| 231c998cc0 | |||
| 127d31a85c | |||
| a8419bbaa0 | |||
| 0fc430ceb3 | |||
| e59a50b64c | |||
| 2042b629e9 | |||
| 440b9ed749 | |||
| 9936088286 | |||
| 4d8a6c1952 | |||
| af69cf9251 | |||
| 1d5550b1e8 | |||
| ec3f6639b7 | |||
| 727b99effd | |||
| 8afff3aaca | |||
| b281517429 | |||
| 54ec9f272e | |||
| a159691097 | |||
| ca70f7b19c | |||
| 7b4227d2a9 | |||
| f89efa2558 | |||
| 6f4824b648 | |||
| 45debeb5c9 | |||
| a01f452eba | |||
| e452734bc1 | |||
| d1779b93fa | |||
| fe9aac7e29 | |||
| f3d887ebcf | |||
| 336500c8ae | |||
| ee3fa58bfa | |||
| a4c3eddc2f | |||
| e0f6cb2cbd | |||
| 1ad6a9412e | |||
| 5af3e0d494 | |||
| b1ee828457 | |||
| 0d60649715 | |||
| 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 |
+2
-1
@@ -29,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
|
||||
|
||||
@@ -58,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
|
||||
@@ -375,6 +383,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/shared/twtouch.cpp
|
||||
games/popn/popn.cpp
|
||||
games/popn/io.cpp
|
||||
games/popn/bi3a_hook.cpp
|
||||
games/bbc/bbc.cpp
|
||||
games/bbc/io.cpp
|
||||
games/hpm/hpm.cpp
|
||||
@@ -441,6 +450,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
|
||||
@@ -513,6 +523,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
hooks/lang.cpp
|
||||
hooks/libraryhook.cpp
|
||||
hooks/networkhook.cpp
|
||||
hooks/icmphook_net.cpp
|
||||
hooks/icmphook_iphlpapi.cpp
|
||||
hooks/powrprof.cpp
|
||||
#hooks/rom.cpp
|
||||
hooks/setupapihook.cpp
|
||||
@@ -554,9 +566,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
|
||||
@@ -571,6 +585,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
overlay/windows/log.cpp
|
||||
overlay/windows/midi.cpp
|
||||
overlay/windows/patch_manager.cpp
|
||||
overlay/windows/popn_sub.cpp
|
||||
overlay/windows/wnd_manager.cpp
|
||||
|
||||
# rawinput
|
||||
@@ -585,6 +600,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
rawinput/smxstage.h
|
||||
rawinput/smxdedicab.cpp
|
||||
rawinput/smxdedicab.h
|
||||
rawinput/xinput.cpp
|
||||
rawinput/xinput.h
|
||||
|
||||
# reader
|
||||
reader/reader.cpp
|
||||
@@ -592,6 +609,9 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
reader/structuredmessage.cpp
|
||||
reader/crypt.cpp
|
||||
|
||||
# sdk
|
||||
sdk/sdk.cpp
|
||||
|
||||
# stubs
|
||||
stubs/stubs.cpp
|
||||
|
||||
@@ -618,6 +638,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
util/time.cpp
|
||||
util/cpuutils.cpp
|
||||
util/netutils.cpp
|
||||
util/precise_timer.cpp
|
||||
util/sysutils.cpp
|
||||
util/lz77.cpp
|
||||
util/tapeled.cpp
|
||||
@@ -803,7 +824,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")
|
||||
|
||||
@@ -813,7 +835,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")
|
||||
|
||||
@@ -823,7 +846,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")
|
||||
|
||||
@@ -833,7 +857,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")
|
||||
|
||||
@@ -841,6 +866,36 @@ if(NOT MSVC)
|
||||
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
||||
endif()
|
||||
|
||||
# sdk_sample_v0_flat_c.dll (32 bit)
|
||||
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
|
||||
add_library(spicetools_sdk_sample_v0_flat_c_32 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
||||
endif()
|
||||
|
||||
# sdk_sample_v0_flat_c.dll (64 bit)
|
||||
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
|
||||
add_library(spicetools_sdk_sample_v0_flat_c_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||
endif()
|
||||
|
||||
# sdk_sample_v0_cpp.dll (64 bit)
|
||||
set(SOURCE_FILES sdk/sample/v0/cpp/v0_cpp.cpp)
|
||||
add_library(spicetools_sdk_sample_v0_cpp_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/cpp/v0_cpp.def)
|
||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_cpp")
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||
endif()
|
||||
|
||||
# output directories
|
||||
####################
|
||||
|
||||
@@ -850,14 +905,14 @@ set_target_properties(spicetools_cfg spicetools_cfg_linux
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
|
||||
|
||||
# output 32bit
|
||||
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm
|
||||
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm spicetools_sdk_sample_v0_flat_c_32
|
||||
PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
|
||||
|
||||
# output 64bit
|
||||
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64
|
||||
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64 spicetools_sdk_sample_v0_flat_c_64 spicetools_sdk_sample_v0_cpp_64
|
||||
PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64"
|
||||
|
||||
@@ -283,6 +283,16 @@ Python is the only one that is fully spec compliant.
|
||||
Other libraries may be missing features and contain bugs; please feel free to
|
||||
contribute code to fill the gaps if you work on a project using these libraries.
|
||||
|
||||
## Spice SDK (for DLL hooks / plugins)
|
||||
|
||||
As an alternative to Spice API, Spice SDK is available.
|
||||
|
||||
This is a flat C, header-only library that can be included in your DLL. Once
|
||||
initialized, you can call directly into helper routines provided by Spicetools
|
||||
executable, making low-latency and high-throughput interactions possible.
|
||||
|
||||
Check the documentation on the wiki for more details.
|
||||
|
||||
## License
|
||||
Unless otherwise noted, all files are licensed under the GPLv3.
|
||||
See the LICENSE file for the full license text.
|
||||
|
||||
+122
-20
@@ -35,24 +35,33 @@ struct ICCA_STATUS_LA9 {
|
||||
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
|
||||
|
||||
enum ICCA_WORKFLOW {
|
||||
STEP,
|
||||
SLEEP,
|
||||
START,
|
||||
INIT,
|
||||
READY,
|
||||
GET_USERID,
|
||||
ACTIVE,
|
||||
EJECT,
|
||||
EJECT_CHECK,
|
||||
END,
|
||||
CLOSE_EJECT,
|
||||
CLOSE_E_CHK,
|
||||
CLOSE_END,
|
||||
ERR_GETUID = -2
|
||||
STEP = 0,
|
||||
SLEEP = 1,
|
||||
START = 2,
|
||||
INIT = 3,
|
||||
READY = 4,
|
||||
GET_USERID = 5,
|
||||
ACTIVE = 6,
|
||||
EJECT = 7,
|
||||
EJECT_CHECK = 8,
|
||||
END = 9,
|
||||
CLOSE_EJECT = 10,
|
||||
CLOSE_E_CHK = 11,
|
||||
CLOSE_END = 12,
|
||||
ERR_GETUID = -2,
|
||||
ERR_REMOVED = -11
|
||||
};
|
||||
|
||||
// used by RA as the status_code
|
||||
enum ICCA_STATUS_CODE {
|
||||
EMPTY = 1,
|
||||
ENGAGED = 2
|
||||
};
|
||||
|
||||
struct ICCA_UNIT {
|
||||
struct ICCA_STATUS status {};
|
||||
enum ICCA_WORKFLOW state = STEP;
|
||||
enum ICCA_WORKFLOW state_jdz = SLEEP;
|
||||
bool card_cmd_pressed = false;
|
||||
bool card_in = false;
|
||||
double card_in_time = 0.0;
|
||||
@@ -75,6 +84,11 @@ static inline int icca_get_active_count() {
|
||||
}
|
||||
|
||||
static inline int icca_get_unit_id(int unit_id) {
|
||||
// on RA the unit_id is zero-indexed
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
return unit_id;
|
||||
}
|
||||
|
||||
if (icca_get_active_count() < 2)
|
||||
return 1;
|
||||
else {
|
||||
@@ -122,9 +136,15 @@ static inline void update_card(int unit_id) {
|
||||
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
|
||||
|
||||
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
||||
unit->state_jdz = ACTIVE;
|
||||
unit->status.error = 0;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
} else {
|
||||
unit->state = ERR_GETUID;
|
||||
unit->state_jdz = ERR_GETUID;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
memset(unit->status.uid, 0, 8);
|
||||
}
|
||||
unit->card_in = true;
|
||||
@@ -132,10 +152,25 @@ static inline void update_card(int unit_id) {
|
||||
} else if (unit->state == EJECT_CHECK) {
|
||||
unit->state = SLEEP;
|
||||
unit->card_in = false;
|
||||
unit->status.front_sensor = 0;
|
||||
unit->status.rear_sensor = 0;
|
||||
} else if (unit->state_jdz == READY) {
|
||||
unit->state = GET_USERID;
|
||||
unit->state_jdz = GET_USERID;
|
||||
unit->status.error = 0;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
unit->card_in = true;
|
||||
unit->card_in_time = get_performance_seconds();
|
||||
}
|
||||
} else {
|
||||
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
||||
if (unit->state_jdz == READY) {
|
||||
unit->state_jdz = GET_USERID;
|
||||
}
|
||||
unit->status.error = 0;
|
||||
unit->status.front_sensor = 1;
|
||||
unit->status.rear_sensor = 1;
|
||||
unit->card_in = true;
|
||||
unit->card_in_time = get_performance_seconds();
|
||||
}
|
||||
@@ -144,6 +179,12 @@ static inline void update_card(int unit_id) {
|
||||
unit->state = CLOSE_EJECT;
|
||||
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
|
||||
unit->card_in = false;
|
||||
memset(unit->status.uid, 0, 8);
|
||||
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
|
||||
unit->state_jdz = ERR_REMOVED;
|
||||
}
|
||||
unit->status.front_sensor = 0;
|
||||
unit->status.rear_sensor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +343,11 @@ static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
|
||||
|
||||
// copy state to output buffer
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
unit->status.status_code = unit->state;
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
|
||||
} else {
|
||||
unit->status.status_code = unit->state;
|
||||
}
|
||||
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
|
||||
|
||||
// funny workaround
|
||||
@@ -387,6 +432,11 @@ static char __cdecl ac_io_icca_req_uid(int unit_id) {
|
||||
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
|
||||
unit_id = icca_get_unit_id(unit_id);
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
// hack for RA - only ever seen it as this
|
||||
*read_state = 8;
|
||||
return 1;
|
||||
}
|
||||
if (unit->card_in) {
|
||||
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
|
||||
unit->state = END;
|
||||
@@ -403,16 +453,61 @@ static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __cdecl ac_io_icca_workflow_jdz(int workflow, int unit_id) {
|
||||
|
||||
unit_id = icca_get_unit_id(unit_id);
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
switch (workflow) {
|
||||
// RA uses "STEP" as more of a polling thing it seems
|
||||
case STEP:
|
||||
switch (unit->state_jdz) {
|
||||
case CLOSE_EJECT:
|
||||
unit->state_jdz = CLOSE_E_CHK;
|
||||
break;
|
||||
case CLOSE_E_CHK:
|
||||
unit->state_jdz = CLOSE_END;
|
||||
break;
|
||||
case CLOSE_END:
|
||||
unit->state_jdz = SLEEP;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SLEEP:
|
||||
unit->state = SLEEP;
|
||||
break;
|
||||
case START:
|
||||
unit->state_jdz = INIT;
|
||||
break;
|
||||
case INIT:
|
||||
case READY:
|
||||
unit->state_jdz = READY;
|
||||
break;
|
||||
case CLOSE_EJECT:
|
||||
unit->state_jdz = CLOSE_EJECT;
|
||||
return CLOSE_E_CHK;
|
||||
case CLOSE_END:
|
||||
unit->state_jdz = SLEEP;
|
||||
break;
|
||||
default:
|
||||
// should probably log a warning here
|
||||
break;
|
||||
}
|
||||
return unit->state_jdz;
|
||||
}
|
||||
|
||||
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
|
||||
// RA uses a different workflow process for the slotted readers; treat it differently
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
return ac_io_icca_workflow_jdz(workflow, unit_id);
|
||||
}
|
||||
|
||||
unit_id = icca_get_unit_id(unit_id);
|
||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||
switch (workflow) {
|
||||
case STEP:
|
||||
if (avs::game::is_model("JDZ"))
|
||||
unit->state = SLEEP;
|
||||
else
|
||||
unit->state = STEP;
|
||||
unit->state = STEP;
|
||||
break;
|
||||
case SLEEP:
|
||||
unit->state = SLEEP;
|
||||
@@ -441,7 +536,6 @@ static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return unit->state;
|
||||
}
|
||||
|
||||
@@ -467,6 +561,14 @@ acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModu
|
||||
void acio::ICCAModule::attach() {
|
||||
ACIOModule::attach();
|
||||
|
||||
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
|
||||
if (avs::game::is_model("JDZ")) {
|
||||
ICCA_UNITS[0].initialized = true;
|
||||
ICCA_UNITS[1].initialized = true;
|
||||
|
||||
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
|
||||
}
|
||||
|
||||
// hooks
|
||||
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
|
||||
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#include "mdxf.h"
|
||||
#include "mdxf_poll.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/ddr/io.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include <mutex>
|
||||
|
||||
#define MDFX_DEBUG_VERBOSE 0
|
||||
|
||||
@@ -108,9 +111,10 @@ static void mdxf_thread_start() {
|
||||
MDXF_THREAD = std::thread([] {
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
|
||||
mdxf_poll(false);
|
||||
std::this_thread::sleep_for(THREAD_PERIOD);
|
||||
timer.sleep(THREAD_PERIOD);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -357,14 +361,17 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
|
||||
// decide on button map
|
||||
const size_t *button_map = nullptr;
|
||||
int player = 0;
|
||||
switch (node) {
|
||||
case 17:
|
||||
case 25:
|
||||
button_map = &buttons_p1[0];
|
||||
player = 1;
|
||||
break;
|
||||
case 18:
|
||||
case 26:
|
||||
button_map = &buttons_p2[0];
|
||||
player = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -374,19 +381,27 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
if (source == EXTERNAL_POLL) {
|
||||
// get buttons
|
||||
auto &buttons = games::ddr::get_buttons();
|
||||
|
||||
// get analogs
|
||||
bool analog_left = false;
|
||||
bool analog_right = false;
|
||||
games::ddr::get_analog_x_axis(player, analog_left, analog_right);
|
||||
bool analog_up = false;
|
||||
bool analog_down = false;
|
||||
games::ddr::get_analog_y_axis(player, analog_up, analog_down);
|
||||
|
||||
uint8_t up_down = 0;
|
||||
uint8_t left_right = 0;
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0])) || analog_up) {
|
||||
up_down |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1])) || analog_down) {
|
||||
up_down |= 0x0F;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2])) || analog_left) {
|
||||
left_right |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3])) || analog_right) {
|
||||
left_right |= 0x0F;
|
||||
}
|
||||
current_state = (uint16_t(up_down) << 8) | left_right;
|
||||
@@ -500,4 +515,4 @@ acio::MDXFModule::~MDXFModule() {
|
||||
if (IS_THREAD_NEEDED) {
|
||||
mdxf_thread_stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "util/circular_buffer.h"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
using namespace acioemu;
|
||||
@@ -46,11 +47,12 @@ ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count)
|
||||
// keypad thread for faster polling
|
||||
if (keypad_thread) {
|
||||
this->keypad_thread = new std::thread([this]() {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (this->cards) {
|
||||
for (int unit = 0; unit < this->node_count; unit++) {
|
||||
this->update_keypad(unit, false);
|
||||
}
|
||||
Sleep(7);
|
||||
timer.sleep(7);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "launcher/launcher.h"
|
||||
#include "games/io.h"
|
||||
#include "util/utils.h"
|
||||
#include "acio/mdxf/mdxf_poll.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -104,6 +105,7 @@ namespace api::modules {
|
||||
for (auto &analog : *this->analogs) {
|
||||
analog.override_enabled = false;
|
||||
}
|
||||
mdxf_poll(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -148,6 +150,7 @@ namespace api::modules {
|
||||
if (analog.getName() == name) {
|
||||
analog.override_state = CLAMP(state, 0.f, 1.f);
|
||||
analog.override_enabled = true;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -167,6 +170,7 @@ namespace api::modules {
|
||||
for (auto &analog : *this->analogs) {
|
||||
if (analog.getName() == name) {
|
||||
analog.override_enabled = false;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "launcher/launcher.h"
|
||||
#include "games/io.h"
|
||||
#include "util/utils.h"
|
||||
#include "acio/mdxf/mdxf_poll.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -107,6 +108,7 @@ namespace api::modules {
|
||||
for (auto &button : *this->buttons) {
|
||||
button.override_enabled = false;
|
||||
}
|
||||
mdxf_poll(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -153,6 +155,7 @@ namespace api::modules {
|
||||
GameAPI::Buttons::BUTTON_PRESSED : GameAPI::Buttons::BUTTON_NOT_PRESSED;
|
||||
button.override_velocity = CLAMP(state, 0.f, 1.f);
|
||||
button.override_enabled = true;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -172,6 +175,7 @@ namespace api::modules {
|
||||
for (auto &button : *this->buttons) {
|
||||
if (button.getName() == name) {
|
||||
button.override_enabled = false;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "avs/game.h"
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -58,6 +59,7 @@ namespace api::modules {
|
||||
// get params
|
||||
auto keypad = req.params[0].GetUint();
|
||||
auto input = std::string(req.params[1].GetString());
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// process all chars
|
||||
for (auto c : input) {
|
||||
@@ -91,11 +93,11 @@ namespace api::modules {
|
||||
|
||||
// set
|
||||
eamuse_set_keypad_overrides(keypad, state);
|
||||
Sleep(sleep_time);
|
||||
timer.sleep(sleep_time);
|
||||
|
||||
// unset
|
||||
eamuse_set_keypad_overrides(keypad, 0);
|
||||
Sleep(sleep_time);
|
||||
timer.sleep(sleep_time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace api::modules {
|
||||
|
||||
// check if file exists in modules
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
return error(res, "Couldn't find " + dll_path.string());
|
||||
return error(res, fmt::format("Couldn't find {}", dll_path));
|
||||
}
|
||||
|
||||
// get module
|
||||
@@ -118,7 +118,7 @@ namespace api::modules {
|
||||
|
||||
// check if file exists in modules
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
return error(res, "Couldn't find " + dll_path.string());
|
||||
return error(res, fmt::format("Couldn't find {}", dll_path));
|
||||
}
|
||||
|
||||
// get module
|
||||
@@ -196,7 +196,7 @@ namespace api::modules {
|
||||
|
||||
// check if file exists in modules
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
return error(res, "Couldn't find " + dll_path.string());
|
||||
return error(res, fmt::format("Couldn't find {}", dll_path));
|
||||
}
|
||||
|
||||
// get module
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
|
||||
@@ -16,6 +17,7 @@ namespace api {
|
||||
controller->init_state(this->state);
|
||||
this->thread = new std::thread([this] () {
|
||||
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// read buffer
|
||||
uint8_t read_buffer[16*1024];
|
||||
@@ -160,7 +162,7 @@ namespace api {
|
||||
|
||||
// slow down on reconnect
|
||||
if (this->running) {
|
||||
Sleep(retry_time);
|
||||
timer.sleep(retry_time);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "external/robin_hood.h"
|
||||
#include "games/popn/popn.h"
|
||||
#include "launcher/logger.h"
|
||||
#include "launcher/signal.h"
|
||||
#include "util/deferlog.h"
|
||||
@@ -1167,7 +1168,7 @@ namespace avs {
|
||||
// Ongaku Paradise
|
||||
{"arkjc9.dll", 0xA00000},
|
||||
|
||||
// KAMUNITY
|
||||
// KAMUNITY
|
||||
{"kamunity.dll", 33554432},
|
||||
};
|
||||
|
||||
@@ -1179,6 +1180,15 @@ namespace avs {
|
||||
return;
|
||||
}
|
||||
|
||||
// hack, both hello! popn and popn hc using popn.dll
|
||||
if (games::popn::is_pikapika_model()) {
|
||||
auto old_size = HEAP_SIZE;
|
||||
HEAP_SIZE = 0x20000000;
|
||||
DEFAULT_HEAP_SIZE_SET = true;
|
||||
log_info("avs-core", "updated heap size: {} -> {}", old_size, avs::core::HEAP_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) {
|
||||
auto old_size = HEAP_SIZE;
|
||||
|
||||
@@ -1506,7 +1516,7 @@ namespace avs {
|
||||
" * It's also possible that you have incomplete game data\n"
|
||||
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
|
||||
"\n"
|
||||
, DLL_NAME, MODULE_PATH.string()) };
|
||||
, DLL_NAME, MODULE_PATH) };
|
||||
log_warning("avs-ea3", "{}", info_str);
|
||||
log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME);
|
||||
}
|
||||
@@ -1747,9 +1757,9 @@ namespace avs {
|
||||
{
|
||||
deferredlogs::defer_error_messages({
|
||||
"AVS filesystem initialization failure was previously detected during boot!",
|
||||
fmt::format(" ERROR: directory could not be created: {}", src_path.string().c_str()),
|
||||
fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH.c_str()),
|
||||
" fix the XML file and try again"
|
||||
fmt::format(" ERROR: directory could not be created: {}", src_path),
|
||||
fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH),
|
||||
" fix the XML file and try again"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1770,14 +1780,14 @@ namespace avs {
|
||||
auto created = std::filesystem::create_directories(real_path, err);
|
||||
|
||||
if (created) {
|
||||
log_info("avs-core", "created '{}' at '{}'", avs_path, real_path.string());
|
||||
log_info("avs-core", "created '{}' at '{}'", avs_path, real_path);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
avs_dir_err(real_path);
|
||||
log_warning("avs-core", "failed to create '{}' folder at '{}': {}",
|
||||
avs_path,
|
||||
real_path.string(),
|
||||
real_path,
|
||||
err.message());
|
||||
}
|
||||
}
|
||||
@@ -1911,6 +1921,15 @@ namespace avs {
|
||||
// create nvram and raw directories if possible for mounttable configurations
|
||||
create_avs_config_fs_table(config, config_node);
|
||||
|
||||
#if !SPICE64
|
||||
// sdvx4 bad log config fix
|
||||
if (avs::game::DLL_NAME == "soundvoltex.dll" && // it's too early for avs::game::is_model
|
||||
property_search_safe(config, config_node, "/log/enable_console")) {
|
||||
log_info("avs-core", "applying SDVX4 avs-config.xml fix for <log><enable_console>");
|
||||
property_search_remove_safe(config, config_node, "/log/enable_console");
|
||||
}
|
||||
#endif
|
||||
|
||||
// set log level
|
||||
if (!LOG_LEVEL_CUSTOM.empty()) {
|
||||
property_search_remove_safe(config, config_node, "/log/level");
|
||||
|
||||
+55
-6
@@ -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"
|
||||
@@ -150,8 +151,8 @@ namespace avs {
|
||||
" * It's also possible that you have incomplete game data\n"
|
||||
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
|
||||
"\n"
|
||||
, DLL_NAME, MODULE_PATH.string()) };
|
||||
|
||||
, DLL_NAME, MODULE_PATH) };
|
||||
|
||||
log_warning("avs-ea3", "{}", info_str);
|
||||
log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME);
|
||||
}
|
||||
@@ -216,7 +217,25 @@ namespace avs {
|
||||
ea3_config_name = "prop/eamuse-config.xml";
|
||||
}
|
||||
|
||||
log_info("avs-ea3", "booting (using {})", ea3_config_name);
|
||||
if (fileutils::file_exists(ea3_config_name)) {
|
||||
log_info("avs-ea3", "found {} on disk", ea3_config_name);
|
||||
} else if (CFG_PATH.size()) {
|
||||
log_fatal("avs-ea3", "user-specified ea3 config file is missing: {}", 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 +547,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
|
||||
|
||||
@@ -73,11 +73,10 @@ namespace avs {
|
||||
|
||||
// load game instance
|
||||
const auto dll_path = MODULE_PATH / DLL_NAME;
|
||||
const auto dll_path_s = dll_path.string();
|
||||
log_info("avs-game", "DLL path: {}", dll_path_s.c_str());
|
||||
log_info("avs-game", "DLL path: {}", dll_path);
|
||||
|
||||
// MAX_PATH is 260
|
||||
if (130 <= dll_path_s.length()) {
|
||||
if (const auto dll_path_len = dll_path.wstring().length(); 130 <= dll_path_len) {
|
||||
log_warning(
|
||||
"avs-game",
|
||||
"PATH TOO LONG WARNING\n\n"
|
||||
@@ -93,7 +92,7 @@ namespace avs {
|
||||
"long, often resulting in random crashes. Move the game contents to\n"
|
||||
"a directory with shorter path.\n"
|
||||
"-------------------------------------------------------------------\n\n",
|
||||
dll_path_s, dll_path_s.length());
|
||||
dll_path, dll_path_len);
|
||||
}
|
||||
|
||||
// ddr gamemdx.dll user error
|
||||
@@ -112,7 +111,7 @@ namespace avs {
|
||||
|
||||
// file not found on disk
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path.string().c_str());
|
||||
log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path);
|
||||
log_warning("avs-game", "double check -exec and -modules parameters; unless you know what you're doing, leave them blank");
|
||||
}
|
||||
|
||||
|
||||
@@ -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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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_ms="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>
|
||||
@@ -23,7 +23,7 @@
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
+109
-25
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# set to EN-US for consistency
|
||||
LANG=en_us_8859_1
|
||||
@@ -41,12 +41,18 @@ 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"}"
|
||||
TOOLCHAIN_WINXP_32="${TOOLCHAIN_WINXP_32:-"/opt/llvm-mingw-xp/toolchain-files/cmake/i686-mingw32-clang.cmake"}"
|
||||
TOOLCHAIN_WINXP_64="${TOOLCHAIN_WINXP_64:-"/opt/llvm-mingw-xp/toolchain-files/cmake/x86_64-mingw32-clang.cmake"}"
|
||||
BUILDDIR_32_RELEASE="./cmake-build-release-32"
|
||||
BUILDDIR_32_DEBUG="./cmake-build-debug-32"
|
||||
BUILDDIR_64_RELEASE="./cmake-build-release-64"
|
||||
BUILDDIR_64_DEBUG="./cmake-build-debug-64"
|
||||
BUILDDIR_WINXP_32_RELEASE="./cmake-build-release-winxp-32"
|
||||
BUILDDIR_WINXP_32_DEBUG="./cmake-build-debug-winxp-32"
|
||||
BUILDDIR_WINXP_64_RELEASE="./cmake-build-release-winxp-64"
|
||||
BUILDDIR_WINXP_64_DEBUG="./cmake-build-debug-winxp-64"
|
||||
DEBUG=0
|
||||
OUTDIR="./bin/spice2x"
|
||||
OUTDIR_EXTRAS="./bin/spice2x/extras"
|
||||
@@ -62,19 +68,36 @@ DIST_FOLDER="./dist"
|
||||
DIST_NAME="spice2x-$(date +%y)-$(date +%m)-$(date +%d).zip"
|
||||
DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
|
||||
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
|
||||
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
|
||||
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
|
||||
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm spicetools_sdk_sample_v0_flat_c_32"
|
||||
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux spicetools_sdk_sample_v0_flat_c_64 spicetools_sdk_sample_v0_cpp_64"
|
||||
TARGETS_XP32="spicetools_cfg spicetools_spice"
|
||||
TARGETS_XP64="spicetools_spice64"
|
||||
|
||||
# determine build type
|
||||
BUILD_TYPE="Release"
|
||||
BUILDDIR_32=${BUILDDIR_32_RELEASE}
|
||||
BUILDDIR_64=${BUILDDIR_64_RELEASE}
|
||||
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_RELEASE}
|
||||
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_RELEASE}
|
||||
if ((DEBUG > 0))
|
||||
then
|
||||
BUILD_TYPE="Debug"
|
||||
BUILDDIR_32=${BUILDDIR_32_DEBUG}
|
||||
BUILDDIR_64=${BUILDDIR_64_DEBUG}
|
||||
DIST_NAME=$(echo ${DIST_NAME} | sed 's/\.[^.]*$/-dbg&/')
|
||||
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_DEBUG}
|
||||
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_DEBUG}
|
||||
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
|
||||
fi
|
||||
|
||||
# is the XP-compatible toolchain installed?
|
||||
XP_MUST_BUILD=0
|
||||
BUILD_XP=0
|
||||
if [ -f "$TOOLCHAIN_WINXP_32" ] && [ -f "$TOOLCHAIN_WINXP_64" ]; then
|
||||
BUILD_XP=1;
|
||||
elif ((XP_MUST_BUILD > 0))
|
||||
then
|
||||
echo "WinXP toolchain not available, aborting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# determine number of cores
|
||||
@@ -90,6 +113,13 @@ echo "Git Branch: $GIT_BRANCH"
|
||||
echo "Git Head: $GIT_HEAD"
|
||||
echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
|
||||
echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
echo "Toolchain for WinXP 32bit targets: $TOOLCHAIN_WINXP_32"
|
||||
echo "Toolchain for WinXP 64bit targets: $TOOLCHAIN_WINXP_64"
|
||||
else
|
||||
echo "WinXP toolchain not available, skipping WinXP builds"
|
||||
fi
|
||||
echo "Distribution Name: $DIST_NAME"
|
||||
echo "Build Type: $BUILD_TYPE"
|
||||
echo "Cores: $CORES"
|
||||
@@ -114,7 +144,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,14 +157,62 @@ 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
|
||||
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
# 32 bit Windows XP
|
||||
echo ""
|
||||
echo "Building 32bit targets (WinXP toolchain)..."
|
||||
echo "========================="
|
||||
if ((CLEAN_BUILD > 0))
|
||||
then
|
||||
rm -rf ${BUILDDIR_WINXP_32}
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_WINXP_32}
|
||||
pushd ${BUILDDIR_WINXP_32} > /dev/null
|
||||
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP32}
|
||||
popd > /dev/null
|
||||
|
||||
# 64 bit Windows XP
|
||||
echo ""
|
||||
echo "Building 64bit targets (WinXP toolchain)..."
|
||||
echo "========================="
|
||||
if ((CLEAN_BUILD > 0))
|
||||
then
|
||||
rm -rf ${BUILDDIR_WINXP_64}
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_WINXP_64}
|
||||
pushd ${BUILDDIR_WINXP_64} > /dev/null
|
||||
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64}
|
||||
popd > /dev/null
|
||||
else
|
||||
echo ""
|
||||
echo "Skipping WinXP builds, toolchain not specified"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Compilation process done :)"
|
||||
echo "==========================="
|
||||
)
|
||||
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
echo ""
|
||||
echo "Checking XP compatibility..."
|
||||
echo "============================="
|
||||
if ! command -v windows_dll_compat_checker &> /dev/null; then
|
||||
echo "WARNING: windows_dll_compat_checker not found, skipping XP compatibility check"
|
||||
else
|
||||
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
|
||||
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
|
||||
windows_dll_compat_checker -s PREMADE/winxp_x86_64_32bit_dlls.ini \
|
||||
${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe \
|
||||
${BUILDDIR_WINXP_32}/spicetools/32/spice.exe
|
||||
fi
|
||||
fi
|
||||
|
||||
# generate PDBs
|
||||
if false # ((DEBUG > 0))
|
||||
then
|
||||
@@ -191,6 +269,12 @@ rm -rf ${OUTDIR_EXTRAS}
|
||||
mkdir -p ${OUTDIR_EXTRAS}
|
||||
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
|
||||
mkdir -p ${OUTDIR_EXTRAS}/linux
|
||||
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/32
|
||||
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/64
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
mkdir -p ${OUTDIR_EXTRAS}/winxp
|
||||
fi
|
||||
|
||||
if false # ((DEBUG > 0))
|
||||
then
|
||||
@@ -199,16 +283,8 @@ then
|
||||
cp ${BUILDDIR_32}/spicetools/spicecfg-pdb.pdb ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.pdb ${OUTDIR} 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kbt.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kld.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.pdb ${OUTDIR} 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kbt.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kld.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
else
|
||||
# release files
|
||||
cp ${BUILDDIR_32}/spicetools/spicecfg.exe ${OUTDIR} 2>/dev/null
|
||||
@@ -216,16 +292,21 @@ else
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice_laa.exe ${OUTDIR_EXTRAS}/largeaddressaware/spice.exe 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice_linux.exe ${OUTDIR_EXTRAS}/linux/spice.exe 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kbt.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kld.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64_linux.exe ${OUTDIR_EXTRAS}/linux/spice64.exe 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kbt.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kld.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/sdk_sample_v0_flat_c.dll ${OUTDIR_EXTRAS}/sdk/samples/32/v0_flat_c.dll 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/sdk_sample_v0_flat_c.dll ${OUTDIR_EXTRAS}/sdk/samples/64/v0_flat_c.dll 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/sdk_sample_v0_cpp.dll ${OUTDIR_EXTRAS}/sdk/samples/64/v0_cpp.dll 2>/dev/null
|
||||
if ((BUILD_XP > 0))
|
||||
then
|
||||
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
cp ${BUILDDIR_WINXP_32}/spicetools/32/spice.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
cp ${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
# pack source files to output directory
|
||||
@@ -234,10 +315,13 @@ 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
|
||||
|
||||
# sdk headers
|
||||
cp -r ./sdk/include ${OUTDIR_EXTRAS}/sdk
|
||||
|
||||
# copy resources
|
||||
rm -rf ${OUTDIR_EXTRAS}/api
|
||||
mkdir ${OUTDIR_EXTRAS}/api
|
||||
@@ -249,11 +333,11 @@ if ((DIST_ENABLE > 0))
|
||||
then
|
||||
echo "Building dist..."
|
||||
mkdir -p ${DIST_FOLDER}
|
||||
rm -rf ${DIST_FOLDER}/${DIST_NAME}
|
||||
pushd ${OUTDIR}/.. > /dev/null
|
||||
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
|
||||
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
|
||||
pushd ${OUTDIR} > /dev/null
|
||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" . -x "extras/*" -z <<< "$DIST_COMMENT"
|
||||
echo "Building extras..."
|
||||
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS} spice2x -z <<< "$DIST_COMMENT"
|
||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" . -z <<< "$DIST_COMMENT"
|
||||
popd > /dev/null
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
docker build --pull external/docker -t spicetools/deps
|
||||
docker build --build-context gitroot=%cd%/../../.git . -t spicetools/spice
|
||||
docker run --rm -it -v %cd%/dist:/src/src/spice2x/dist -v %cd%/bin:/src/src/spice2x/bin -v %cd%/.ccache:/src/src/spice2x/.ccache spicetools/spice %*
|
||||
docker run --rm -i -v %cd%/dist:/src/src/spice2x/dist -v %cd%/bin:/src/src/spice2x/bin -v %cd%/.ccache:/src/src/spice2x/.ccache spicetools/spice %*
|
||||
@REM to generate PDBs, set DEBUG to 1 in build_all.sh, place cv2pdb in external\cv2pdb, and run below
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spicecfg.exe bin\spice2x\spicecfg-pdb.exe bin\spice2x\spicecfg-pdb.pdb
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice.exe bin\spice2x\spice-pdb.exe bin\spice2x\spice-pdb.pdb
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice64.exe bin\spice2x\spice64-pdb.exe bin\spice2x\spice64-pdb.pdb
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice64.exe bin\spice2x\spice64-pdb.exe bin\spice2x\spice64-pdb.pdb
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
#!/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 "$@"
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
|
||||
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
|
||||
|
||||
# Interactive TTY if available, so docker build can be Ctrl+C'd
|
||||
DOCKER_FLAGS=""
|
||||
[ -t 0 ] && DOCKER_FLAGS="-it"
|
||||
|
||||
docker run $DOCKER_FLAGS --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
|
||||
|
||||
@@ -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
|
||||
|
||||
+106
-15
@@ -90,6 +90,10 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
|
||||
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
|
||||
}
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD:
|
||||
return fmt::format("{} ({})",
|
||||
xinput::get_analog_string(static_cast<xinput::XInputAnalogEnum>(index)),
|
||||
device->desc);
|
||||
case rawinput::DESTROYED:
|
||||
return "Device unplugged (" + indexString + ")";
|
||||
default:
|
||||
@@ -203,22 +207,38 @@ float Analog::applyMultiplier(float value) {
|
||||
}
|
||||
|
||||
float Analog::normalizeAnalogValue(float value) {
|
||||
// effectively the same as fmodf(value, 1.f)
|
||||
// for small values, this is MUCH faster than fmodf.
|
||||
float new_value = value;
|
||||
while (new_value > 1.f) {
|
||||
new_value -= 1.f;
|
||||
if (getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||
// effectively the same as fmodf(value, 1.f)
|
||||
// for small values, this is MUCH faster than fmodf.
|
||||
float new_value = value;
|
||||
while (new_value > 1.f) {
|
||||
new_value -= 1.f;
|
||||
}
|
||||
while (new_value < 0.f) {
|
||||
new_value += 1.f;
|
||||
}
|
||||
return new_value;
|
||||
|
||||
} else {
|
||||
// clamp to [0, 1] range
|
||||
return std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
while (new_value < 0.f) {
|
||||
new_value += 1.f;
|
||||
}
|
||||
return new_value;
|
||||
}
|
||||
|
||||
float Analog::applyDeadzone(float raw_value) {
|
||||
float value = raw_value;
|
||||
const auto deadzone = this->getDeadzone();
|
||||
if (deadzone > 0) {
|
||||
auto deadzone = this->getDeadzone();
|
||||
|
||||
// in the past, positive deadzone applied in the center, negative deadzone applied to 0
|
||||
// after each analog value received a type (circular/linear) this has been simpliifed to
|
||||
// positive values only since we can figure out where the rest value is
|
||||
// for back compat, treat negative value as positive
|
||||
if (deadzone < 0.f) {
|
||||
deadzone = -deadzone;
|
||||
}
|
||||
|
||||
// relative mode assumes that user is using a stick, so center is neutral regardless of analog type
|
||||
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive || isRelativeMode()) {
|
||||
|
||||
// calculate values
|
||||
const auto delta = value - 0.5f;
|
||||
@@ -251,7 +271,7 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
}
|
||||
|
||||
} else if (deadzone < 0) {
|
||||
} else {
|
||||
|
||||
// invert for mirror
|
||||
if (this->getDeadzoneMirror()) {
|
||||
@@ -259,8 +279,8 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
|
||||
// deadzone from minimum value
|
||||
if (deadzone > -1 && value > -deadzone) {
|
||||
value = std::min(1.f, (value + deadzone) / (1.f + deadzone));
|
||||
if (deadzone < 1.f && deadzone < value) {
|
||||
value = std::max(0.f, (value - deadzone) / (1.f - deadzone));
|
||||
} else {
|
||||
value = 0.f;
|
||||
}
|
||||
@@ -271,4 +291,75 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
float Analog::getRelativeModeValue(float raw_value) {
|
||||
const auto now = get_performance_seconds();
|
||||
auto delta_time = now - this->rel_mode_last_read_time_s;
|
||||
|
||||
if (this->rel_mode_last_read_time_s != 0.f) {
|
||||
// some heuristics to prevent huge jumps:
|
||||
// * if we went for more than 250ms without polls, discard it (e.g., during loading screens)
|
||||
// * cap the delta at 100ms to prevent huge jumps in case of very infrequent polling
|
||||
if (delta_time < 0.f || 0.25f < delta_time) {
|
||||
delta_time = 0.f;
|
||||
} else if (delta_time > 0.1f) {
|
||||
delta_time = 0.1f;
|
||||
}
|
||||
|
||||
// scale [0, 1] to [-1, 1] to simplify calculations
|
||||
const auto delta_raw_value = (raw_value - 0.5f) * 2.f;
|
||||
|
||||
// target is one revolution per second at max speed at 1.0 sensitivity
|
||||
auto adjusted_delta_value = delta_raw_value * delta_time;
|
||||
|
||||
// multiplier / divisor
|
||||
if (this->getMultiplier() > 1) {
|
||||
adjusted_delta_value *= this->getMultiplier();
|
||||
} else if (this->getMultiplier() < -1) {
|
||||
adjusted_delta_value /= -this->getMultiplier();
|
||||
}
|
||||
|
||||
// sensitivity
|
||||
if (this->isSensitivitySet()) {
|
||||
adjusted_delta_value *= this->getSensitivity();
|
||||
}
|
||||
|
||||
// calculate the new absolute value
|
||||
this->rel_mode_absolute_value += adjusted_delta_value;
|
||||
}
|
||||
|
||||
// update for next poll
|
||||
this->rel_mode_last_read_time_s = now;
|
||||
this->rel_mode_absolute_value = normalizeAnalogValue(this->rel_mode_absolute_value);
|
||||
return this->rel_mode_absolute_value;
|
||||
}
|
||||
|
||||
float Analog::getDelayedValue(float raw_value) {
|
||||
const double delay_ms = static_cast<double>(this->getDelayMs());
|
||||
if (delay_ms == 0.0) {
|
||||
return raw_value;
|
||||
}
|
||||
|
||||
// always push a new value
|
||||
const auto now = get_performance_milliseconds();
|
||||
this->delayed_inputs.emplace(now, raw_value);
|
||||
|
||||
// drain the queue down to reasonable length to prevent unconstrained growth
|
||||
// this would accommodate 1 second at ~1000Hz which is overkill
|
||||
// (UI only allows for 250ms of delay)
|
||||
while (this->delayed_inputs.size() > 1024) {
|
||||
this->delayed_inputs.pop();
|
||||
}
|
||||
|
||||
// pop until we find the oldest value still inside the delay window
|
||||
while (this->delayed_inputs.size() > 1) {
|
||||
const auto delta_t = now - this->delayed_inputs.front().time_in_ms;
|
||||
if (delta_t <= delay_ms) {
|
||||
break;
|
||||
}
|
||||
this->delayed_inputs.pop();
|
||||
}
|
||||
|
||||
return this->delayed_inputs.front().value;
|
||||
}
|
||||
|
||||
+47
-18
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
@@ -13,12 +14,32 @@ namespace rawinput {
|
||||
class RawInputManager;
|
||||
}
|
||||
|
||||
namespace GameAPI::Analogs {
|
||||
enum class AnalogType {
|
||||
// default; values wrap around (below 0 turns into 1, over 1 is 0)
|
||||
// knobs, turntables
|
||||
Circular = 0,
|
||||
|
||||
// typical joystick that rests at the center and caps at [0, 1]
|
||||
LinearCentered = 1,
|
||||
|
||||
// one-directional value (sliders and instruments like piano/drum velocity)
|
||||
// starts at 0 and goes up to 1
|
||||
LinearPositive = 2,
|
||||
};
|
||||
}
|
||||
|
||||
struct AnalogMovingAverage {
|
||||
double time_in_ms;
|
||||
float sine;
|
||||
float cosine;
|
||||
};
|
||||
|
||||
struct AnalogDelayEntry {
|
||||
double time_in_ms;
|
||||
float value;
|
||||
};
|
||||
|
||||
class Analog {
|
||||
private:
|
||||
std::string name;
|
||||
@@ -31,10 +52,11 @@ private:
|
||||
float last_state = 0.5f;
|
||||
bool sensitivity_set = false;
|
||||
bool deadzone_set = false;
|
||||
GameAPI::Analogs::AnalogType type = GameAPI::Analogs::AnalogType::Circular;
|
||||
|
||||
// smoothing function
|
||||
bool smoothing = false;
|
||||
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history;
|
||||
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history = {};
|
||||
int vector_history_index = 0;
|
||||
float smoothed_last_state = 0.f;
|
||||
|
||||
@@ -48,12 +70,13 @@ private:
|
||||
unsigned short divisor_region = 0;
|
||||
|
||||
// relative input mode
|
||||
float absolute_value_for_rel_mode = 0.5f;
|
||||
float rel_mode_absolute_value = 0.5f;
|
||||
float rel_mode_last_read_time_s = 0.f;
|
||||
bool relative_mode = false;
|
||||
|
||||
// circular buffer (delayed input)
|
||||
int delay_buffer_depth = 0;
|
||||
std::queue<float> delay_buffer;
|
||||
// delay
|
||||
uint32_t delay_ms = 0;
|
||||
std::queue<AnalogDelayEntry> delayed_inputs;
|
||||
|
||||
float calculateAngularDifference(float old_rads, float new_rads);
|
||||
float normalizeAngle(float rads);
|
||||
@@ -66,7 +89,8 @@ public:
|
||||
float override_state = 0.5f;
|
||||
|
||||
explicit Analog(std::string name) : name(std::move(name)) {
|
||||
vector_history.fill({0.0, 0.f, 0.f});
|
||||
};
|
||||
explicit Analog(std::string name, GameAPI::Analogs::AnalogType type) : name(std::move(name)), type(type) {
|
||||
};
|
||||
|
||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||
@@ -74,6 +98,8 @@ public:
|
||||
float applyAngularSensitivity(float raw_rads);
|
||||
float applyMultiplier(float raw_value);
|
||||
float applyDeadzone(float raw_value);
|
||||
float getRelativeModeValue(float raw_value);
|
||||
float getDelayedValue(float raw_value);
|
||||
|
||||
inline bool isSet() {
|
||||
if (this->override_enabled) {
|
||||
@@ -90,7 +116,8 @@ public:
|
||||
deadzone_mirror = false;
|
||||
setMultiplier(1);
|
||||
setRelativeMode(false);
|
||||
setDelayBufferDepth(0);
|
||||
setLastState(0.5f);
|
||||
setDelayMs(0);
|
||||
}
|
||||
|
||||
inline void clearBindings() {
|
||||
@@ -192,25 +219,27 @@ public:
|
||||
}
|
||||
|
||||
inline void setRelativeMode(bool relative_mode) {
|
||||
if (relative_mode) {
|
||||
this->smoothing = false;
|
||||
}
|
||||
this->relative_mode = relative_mode;
|
||||
this->absolute_value_for_rel_mode = 0.5f;
|
||||
this->rel_mode_absolute_value = 0.5f;
|
||||
this->rel_mode_last_read_time_s = 0.f;
|
||||
}
|
||||
|
||||
inline float getAbsoluteValue(float relative_delta) {
|
||||
this->absolute_value_for_rel_mode =
|
||||
normalizeAnalogValue(this->absolute_value_for_rel_mode + relative_delta);
|
||||
return this->absolute_value_for_rel_mode;
|
||||
inline GameAPI::Analogs::AnalogType getType() const {
|
||||
return this->type;
|
||||
}
|
||||
|
||||
inline int getDelayBufferDepth() const {
|
||||
return this->delay_buffer_depth;
|
||||
inline void setType(GameAPI::Analogs::AnalogType type) {
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
inline void setDelayBufferDepth(int depth) {
|
||||
this->delay_buffer_depth = depth;
|
||||
inline uint32_t getDelayMs() const {
|
||||
return this->delay_ms;
|
||||
}
|
||||
|
||||
inline std::queue<float> &getDelayBuffer() {
|
||||
return this->delay_buffer;
|
||||
inline void setDelayMs(uint32_t delay_ms) {
|
||||
this->delay_ms = delay_ms;
|
||||
}
|
||||
};
|
||||
|
||||
+211
-68
@@ -1,5 +1,6 @@
|
||||
#include "api.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
|
||||
#include "launcher/superexit.h"
|
||||
@@ -37,7 +38,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 +52,7 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
|
||||
|
||||
if (vkey_defaults) {
|
||||
button.setVKey(vkey_defaults->at(index));
|
||||
button.setVKeyDefault(vkey_defaults->at(index));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,9 +199,19 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
||||
if (vKey < value_states->size()) {
|
||||
auto value = value_states->at(vKey);
|
||||
if (current_button->getAnalogType() == BAT_POSITIVE) {
|
||||
state = value > 0.6f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
float threshold = 0.6f;
|
||||
if (current_button->getBatThreshold() > 0) {
|
||||
threshold = std::clamp(current_button->getBatThreshold() / 100.f, 0.01f, 0.99f);
|
||||
}
|
||||
state = value > threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
|
||||
} else if (current_button->getAnalogType() == BAT_NEGATIVE) {
|
||||
state = value < 0.4f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
float threshold = 0.4f;
|
||||
if (current_button->getBatThreshold() > 0) {
|
||||
threshold = std::clamp((100 - current_button->getBatThreshold()) / 100.f, 0.01f, 0.99f);
|
||||
}
|
||||
state = value < threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
|
||||
} else {
|
||||
state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
}
|
||||
@@ -384,6 +400,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
||||
}
|
||||
case rawinput::PIUIO_DEVICE: {
|
||||
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
break;
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD: {
|
||||
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
|
||||
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
|
||||
state = manager->XINPUT_MGR->is_button_pressed(
|
||||
player,
|
||||
static_cast<xinput::XInputButtonEnum>(vKey)) ?
|
||||
BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@@ -581,7 +607,7 @@ float Buttons::getVelocity(std::unique_ptr<rawinput::RawInputManager> &manager,
|
||||
}
|
||||
}
|
||||
|
||||
float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
||||
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog) {
|
||||
float value = 0.5f;
|
||||
if (!device) {
|
||||
return value;
|
||||
@@ -644,88 +670,107 @@ 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
|
||||
if (analog.isDeadzoneSet()) {
|
||||
// do not apply deadzone to circular analogs since it doesn't make sense (except in relative mode)
|
||||
if (analog.isDeadzoneSet() &&
|
||||
(analog.getType() != AnalogType::Circular || analog.isRelativeMode())) {
|
||||
value = analog.applyDeadzone(value);
|
||||
}
|
||||
|
||||
if (analog.isRelativeMode()) {
|
||||
float relative_delta = value - 0.5f;
|
||||
// built-in scaling to make values reasonable
|
||||
relative_delta /= 80.f;
|
||||
if (analog.getType() == AnalogType::Circular) {
|
||||
|
||||
// integer multiplier/divisor
|
||||
const auto mult = analog.getMultiplier();
|
||||
if (mult < -1) {
|
||||
relative_delta /= -mult;
|
||||
} else if (1 < mult) {
|
||||
relative_delta *= mult;
|
||||
}
|
||||
if (analog.isRelativeMode()) {
|
||||
value = analog.getRelativeModeValue(value);
|
||||
|
||||
// sensitivity (ranges from 0.0 to 4.0)
|
||||
if (analog.isSensitivitySet()) {
|
||||
relative_delta *= analog.getSensitivity();
|
||||
}
|
||||
} else {
|
||||
// integer multiplier
|
||||
value = analog.applyMultiplier(value);
|
||||
|
||||
// translate relative movement to absolute value
|
||||
value = analog.getAbsoluteValue(relative_delta);
|
||||
// smoothing/sensitivity
|
||||
if (analog.getSmoothing() || analog.isSensitivitySet()) {
|
||||
float rads = value * (float) M_TAU;
|
||||
|
||||
} else {
|
||||
// integer multiplier
|
||||
value = analog.applyMultiplier(value);
|
||||
// smoothing
|
||||
if (analog.getSmoothing()) {
|
||||
|
||||
// smoothing/sensitivity
|
||||
if (analog.getSmoothing() || analog.isSensitivitySet()) {
|
||||
float rads = value * (float) M_TAU;
|
||||
// preserve direction
|
||||
if (rads >= M_TAU) {
|
||||
rads -= 0.0001f;
|
||||
}
|
||||
|
||||
// smoothing
|
||||
if (analog.getSmoothing()) {
|
||||
|
||||
// preserve direction
|
||||
if (rads >= M_TAU) {
|
||||
rads -= 0.0001f;
|
||||
// calculate angle
|
||||
rads = analog.getSmoothedValue(rads);
|
||||
}
|
||||
|
||||
// calculate angle
|
||||
rads = analog.getSmoothedValue(rads);
|
||||
// sensitivity
|
||||
if (analog.isSensitivitySet()) {
|
||||
rads = analog.applyAngularSensitivity(rads);
|
||||
}
|
||||
|
||||
// apply to value
|
||||
value = rads * (float) M_1_TAU;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// sensitivity
|
||||
if (analog.isSensitivitySet()) {
|
||||
// adjust curve
|
||||
// values < 1.f : less sensitive around neutral
|
||||
// values > 1.f : more sensitive around neutral
|
||||
float curve = analog.getSensitivity();
|
||||
if (curve <= 0.f) {
|
||||
curve = 0.01f;
|
||||
}
|
||||
curve = 1.f / curve;
|
||||
|
||||
if (analog.getType() == AnalogType::LinearCentered) {
|
||||
// convert 0.0..1.0 to -1.0..+1.0
|
||||
float signed_raw = (value - 0.5f) * 2.0f;
|
||||
// apply curve
|
||||
float sign = signed_raw < 0.0f ? -1.0f : 1.0f;
|
||||
float magnitude = fabsf(signed_raw);
|
||||
float curved = sign * powf(magnitude, curve);
|
||||
// convert back to 0.0..1.0
|
||||
value = curved * 0.5f + 0.5f;
|
||||
} else {
|
||||
value = powf(value, curve);
|
||||
}
|
||||
|
||||
// sensitivity
|
||||
if (analog.isSensitivitySet()) {
|
||||
rads = analog.applyAngularSensitivity(rads);
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
|
||||
// multiplier / divisor
|
||||
if (analog.getMultiplier() < -1) {
|
||||
if (analog.getType() == AnalogType::LinearCentered) {
|
||||
value = (value - 0.5f) / (-analog.getMultiplier()) + 0.5f;
|
||||
} else {
|
||||
value /= -analog.getMultiplier();
|
||||
}
|
||||
|
||||
// apply to value
|
||||
value = rads * (float) M_1_TAU;
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
|
||||
} else if (analog.getMultiplier() > 1) {
|
||||
if (analog.getType() == AnalogType::LinearCentered) {
|
||||
value = (value - 0.5f) * analog.getMultiplier() + 0.5f;
|
||||
} else {
|
||||
value *= analog.getMultiplier();
|
||||
}
|
||||
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
}
|
||||
|
||||
// delay
|
||||
if (0 < analog.getDelayBufferDepth()) {
|
||||
auto& queue = analog.getDelayBuffer();
|
||||
|
||||
// ensure the queue isn't too long; drop old values
|
||||
while (analog.getDelayBufferDepth() <= (int)queue.size()) {
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
// always push new value
|
||||
queue.push(value);
|
||||
|
||||
// get a new value to return
|
||||
if ((int)queue.size() < analog.getDelayBufferDepth()) {
|
||||
// not enough in the queue, stall for now, shouldn't happen often
|
||||
value = analog.getLastState();
|
||||
} else {
|
||||
value = queue.front();
|
||||
queue.pop();
|
||||
}
|
||||
if (analog.getDelayMs() > 0) {
|
||||
value = analog.getDelayedValue(value);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -760,6 +805,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD: {
|
||||
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
|
||||
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
|
||||
value = manager->XINPUT_MGR->get_analog_state(
|
||||
player,
|
||||
static_cast<xinput::XInputAnalogEnum>(index));
|
||||
// invert value
|
||||
if (inverted) {
|
||||
value = 1.f - value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -799,6 +856,42 @@ std::vector<Analog> GameAPI::Analogs::sortAnalogs(
|
||||
return sorted;
|
||||
}
|
||||
|
||||
static std::vector<Analog> sortAnalogsWithTypeInternal(
|
||||
std::vector<Analog> &analogs,
|
||||
const std::initializer_list<GameAPI::Analogs::AnalogWithType> list) {
|
||||
|
||||
std::vector<Analog> sorted;
|
||||
|
||||
bool analog_found;
|
||||
for (auto &a : list) {
|
||||
analog_found = false;
|
||||
|
||||
for (auto &analog : analogs) {
|
||||
if (a.name == analog.getName()) {
|
||||
analog_found = true;
|
||||
analog.setType(a.type);
|
||||
sorted.push_back(analog);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!analog_found) {
|
||||
sorted.emplace_back(a.name, a.type);
|
||||
}
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
void GameAPI::Analogs::sortAnalogsWithType(
|
||||
std::vector<Analog> *analogs,
|
||||
const std::initializer_list<AnalogWithType> list) {
|
||||
|
||||
if (analogs) {
|
||||
*analogs = sortAnalogsWithTypeInternal(*analogs, list);
|
||||
}
|
||||
}
|
||||
|
||||
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
|
||||
|
||||
// check override
|
||||
@@ -815,7 +908,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana
|
||||
return analog.getLastState();
|
||||
}
|
||||
|
||||
float state = getState(device, analog);
|
||||
float state = getState(manager, device, analog);
|
||||
analog.setLastState(state);
|
||||
|
||||
return state;
|
||||
@@ -859,7 +952,45 @@ std::vector<Light> GameAPI::Lights::sortLights(
|
||||
return sorted;
|
||||
}
|
||||
|
||||
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) {
|
||||
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::RawInputManager *manager, rawinput::Device *device, int index, float value) {
|
||||
|
||||
// check device
|
||||
if (!device) {
|
||||
@@ -946,6 +1077,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
|
||||
}
|
||||
break;
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD: {
|
||||
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
|
||||
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
|
||||
if (index < static_cast<int>(xinput::XInputOutputEnum::COUNT)) {
|
||||
manager->XINPUT_MGR->set_output_state(
|
||||
player, static_cast<xinput::XInputOutputEnum>(index), value);
|
||||
// no need to set output_pending; xinput output handled immediately
|
||||
} else {
|
||||
log_warning("api", "invalid xinput light index: {}", index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -971,9 +1114,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh
|
||||
|
||||
// write state
|
||||
if (light.override_enabled) {
|
||||
writeLight(device, light.getIndex(), light.override_state);
|
||||
writeLight(manager, device, light.getIndex(), light.override_state);
|
||||
} else {
|
||||
writeLight(device, light.getIndex(), value);
|
||||
writeLight(manager, device, light.getIndex(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-30
@@ -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.
|
||||
*
|
||||
@@ -89,14 +85,15 @@ namespace GameAPI {
|
||||
const std::vector<Analog> &analogs,
|
||||
const std::vector<std::string> &analog_names);
|
||||
|
||||
template<typename T>
|
||||
void sortAnalogs(std::vector<Analog> *analogs, T t) {
|
||||
const std::vector<std::string> analog_names { t };
|
||||
struct AnalogWithType {
|
||||
const std::string name;
|
||||
const AnalogType type;
|
||||
|
||||
if (analogs) {
|
||||
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names);
|
||||
}
|
||||
}
|
||||
AnalogWithType(std::string name, AnalogType type) :
|
||||
name(std::move(name)), type(type) {}
|
||||
};
|
||||
|
||||
void sortAnalogsWithType(std::vector<Analog> *analogs, const std::initializer_list<AnalogWithType> list);
|
||||
|
||||
template<typename T, typename... Rest>
|
||||
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
|
||||
@@ -107,27 +104,19 @@ namespace GameAPI {
|
||||
}
|
||||
}
|
||||
|
||||
float getState(rawinput::Device *device, Analog &analog);
|
||||
float getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog);
|
||||
float getState(rawinput::RawInputManager *manager, Analog &analog);
|
||||
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
|
||||
}
|
||||
|
||||
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,7 +126,17 @@ namespace GameAPI {
|
||||
}
|
||||
}
|
||||
|
||||
void writeLight(rawinput::Device *device, int index, float value);
|
||||
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::RawInputManager *manager, rawinput::Device *device, int index, float value);
|
||||
void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
|
||||
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
|
||||
|
||||
@@ -152,7 +151,3 @@ namespace GameAPI {
|
||||
void sortOptions(std::vector<Option> &, const std::vector<OptionDefinition> &);
|
||||
}
|
||||
}
|
||||
|
||||
#include "button.h"
|
||||
#include "analog.h"
|
||||
#include "light.h"
|
||||
|
||||
+60
-31
@@ -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
|
||||
@@ -284,7 +306,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
||||
std::string vKeyString = fmt::format("{:#x}", vKey);
|
||||
|
||||
// device must be existing
|
||||
if (this->device_identifier.empty() && vKey == INVALID_VKEY) {
|
||||
if (this->isNaive() && vKey == INVALID_VKEY) {
|
||||
if (this->getInvert()) {
|
||||
return "(always on)";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -301,17 +326,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);
|
||||
@@ -395,6 +420,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
||||
}
|
||||
case rawinput::PIUIO_DEVICE:
|
||||
return "PIUIO " + vKeyString;
|
||||
case rawinput::XINPUT_GAMEPAD:
|
||||
return fmt::format("{} ({})",
|
||||
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
|
||||
device->desc);
|
||||
case rawinput::DESTROYED:
|
||||
return "Device unplugged (" + vKeyString + ")";
|
||||
default:
|
||||
|
||||
@@ -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,25 @@ 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;
|
||||
int bat_threshold = 0; // for positive/negative only, value in percentage, zero is default
|
||||
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 +84,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;
|
||||
}
|
||||
}
|
||||
@@ -88,6 +110,7 @@ public:
|
||||
alternatives.clear();
|
||||
device_identifier = "";
|
||||
analog_type = BAT_NONE;
|
||||
bat_threshold = 0;
|
||||
}
|
||||
|
||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||
@@ -116,6 +139,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;
|
||||
}
|
||||
@@ -140,6 +171,14 @@ public:
|
||||
this->debounce_down = debounce_time_down;
|
||||
}
|
||||
|
||||
inline void setBatThreshold(int bat_threshold) {
|
||||
this->bat_threshold = bat_threshold;
|
||||
}
|
||||
|
||||
inline int getBatThreshold() const {
|
||||
return this->bat_threshold;
|
||||
}
|
||||
|
||||
inline bool getInvert() const {
|
||||
return this->invert;
|
||||
}
|
||||
@@ -164,6 +203,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 +219,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);
|
||||
|
||||
|
||||
+104
-17
@@ -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 ///
|
||||
@@ -19,7 +28,7 @@ Config::Config() {
|
||||
this->status = false;
|
||||
if (CONFIG_PATH_OVERRIDE.length() > 0) {
|
||||
this->configLocation = CONFIG_PATH_OVERRIDE;
|
||||
log_info("cfg", "using custom config file: {}", this->configLocation.string());
|
||||
log_info("cfg", "using custom config file: {}", this->configLocation);
|
||||
} else {
|
||||
this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml";
|
||||
// avoids logging the expanded appdata path as it contains user name
|
||||
@@ -48,7 +57,7 @@ Config::Config() {
|
||||
this->firstFillConfigFile();
|
||||
break;
|
||||
case tinyxml2::XMLError::XML_ERROR_FILE_COULD_NOT_BE_OPENED:
|
||||
log_fatal("cfg", "could not open config file: {}", this->configLocation.string());
|
||||
log_fatal("cfg", "could not open config file: {}", this->configLocation);
|
||||
break;
|
||||
case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND:
|
||||
this->createConfigFile();
|
||||
@@ -63,7 +72,7 @@ Config::Config() {
|
||||
case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN:
|
||||
case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT:
|
||||
case tinyxml2::XMLError::XML_ERROR_PARSING:
|
||||
log_warning("cfg", "Couldn't read config file: {}", this->configLocation.string());
|
||||
log_warning("cfg", "Couldn't read config file: {}", this->configLocation);
|
||||
this->createConfigFile();
|
||||
this->firstFillConfigFile();
|
||||
break;
|
||||
@@ -153,6 +162,7 @@ bool Config::addGame(Game &game) {
|
||||
auto analogType = (int) BAT_NONE;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
int bat_threshold = 0;
|
||||
int velocity_threshold = 0;
|
||||
bool invert = false;
|
||||
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||
@@ -160,6 +170,7 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
||||
@@ -171,6 +182,7 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str());
|
||||
gameButtonNode->SetAttribute("debounce_up", debounce_up);
|
||||
gameButtonNode->SetAttribute("debounce_down", debounce_down);
|
||||
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
|
||||
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
||||
gameButtonNode->SetAttribute("invert", invert);
|
||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||
@@ -179,6 +191,7 @@ bool Config::addGame(Game &game) {
|
||||
button->setAnalogType((ButtonAnalogType) analogType);
|
||||
button->setDebounceUp(debounce_up);
|
||||
button->setDebounceDown(debounce_down);
|
||||
button->setBatThreshold(bat_threshold);
|
||||
button->setVelocityThreshold(velocity_threshold);
|
||||
button->setInvert(invert);
|
||||
if (devid) {
|
||||
@@ -198,6 +211,7 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType());
|
||||
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
||||
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
@@ -239,7 +253,7 @@ bool Config::addGame(Game &game) {
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
int delay_buffer_depth = 0;
|
||||
uint32_t delay = 0;
|
||||
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
|
||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||
@@ -248,7 +262,7 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
|
||||
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
|
||||
const char *devid = gameAnalogNode->Attribute("devid");
|
||||
|
||||
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
|
||||
@@ -264,7 +278,7 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
} else {
|
||||
it.setIndex(static_cast<unsigned short int>(index));
|
||||
@@ -276,7 +290,7 @@ bool Config::addGame(Game &game) {
|
||||
it.setSmoothing(smoothing);
|
||||
it.setMultiplier(multiplier);
|
||||
it.setRelativeMode(relative_mode);
|
||||
it.setDelayBufferDepth(delay_buffer_depth);
|
||||
it.setDelayMs(delay);
|
||||
}
|
||||
} else {
|
||||
gameAnalogNode = this->configFile.NewElement("analog");
|
||||
@@ -289,7 +303,7 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
@@ -423,6 +437,7 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->SetAttribute("analogtype", it.getAnalogType());
|
||||
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
||||
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
@@ -443,7 +458,7 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
|
||||
@@ -510,8 +525,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");
|
||||
@@ -524,6 +541,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType());
|
||||
gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp());
|
||||
gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown());
|
||||
gameButtonNode->SetAttribute("bat_threshold", button.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", button.getInvert());
|
||||
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
||||
@@ -540,6 +558,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
gameButtonNode->SetAttribute("analogtype", 0);
|
||||
gameButtonNode->SetAttribute("debounce_up", 0.0);
|
||||
gameButtonNode->SetAttribute("debounce_down", 0.0);
|
||||
gameButtonNode->SetAttribute("bat_threshold", 0);
|
||||
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
||||
gameButtonNode->SetAttribute("invert", false);
|
||||
gameButtonNode->SetAttribute("devid", "");
|
||||
@@ -574,6 +593,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;
|
||||
@@ -640,7 +692,7 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
|
||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||
} else {
|
||||
gameAnalogNode = this->configFile.NewElement("analog");
|
||||
@@ -652,7 +704,7 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
|
||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
@@ -760,6 +812,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;
|
||||
@@ -871,12 +954,14 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
auto analogType = (int) BAT_NONE;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
int bat_threshold = 0;
|
||||
int velocity_threshold = 0;
|
||||
bool invert = false;
|
||||
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||
const char *devid = gameButtonNode->Attribute("devid");
|
||||
@@ -891,6 +976,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
alt.setAnalogType((ButtonAnalogType) analogType);
|
||||
alt.setDebounceUp(debounce_up);
|
||||
alt.setDebounceDown(debounce_down);
|
||||
alt.setBatThreshold(bat_threshold);
|
||||
alt.setVelocityThreshold(velocity_threshold);
|
||||
alt.setInvert(invert);
|
||||
if (devid) {
|
||||
@@ -909,6 +995,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
button.setAnalogType((ButtonAnalogType) analogType);
|
||||
button.setDebounceUp(debounce_up);
|
||||
button.setDebounceDown(debounce_down);
|
||||
button.setBatThreshold(bat_threshold);
|
||||
button.setVelocityThreshold(velocity_threshold);
|
||||
button.setInvert(invert);
|
||||
if (devid) {
|
||||
@@ -1055,7 +1142,7 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
int delay_buffer_depth = 0;
|
||||
uint32_t delay = 0;
|
||||
gameAnalogNode->QueryIntAttribute("index", &index);
|
||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||
@@ -1064,7 +1151,7 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
|
||||
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
|
||||
const char *devid = gameAnalogNode->Attribute("devid");
|
||||
|
||||
// create analog and add to list
|
||||
@@ -1077,7 +1164,7 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
analog.setSmoothing(smoothing);
|
||||
analog.setMultiplier(multiplier);
|
||||
analog.setRelativeMode(relative_mode);
|
||||
analog.setDelayBufferDepth(delay_buffer_depth);
|
||||
analog.setDelayMs(delay);
|
||||
if (devid) {
|
||||
analog.setDeviceIdentifier(devid);
|
||||
}
|
||||
@@ -1240,7 +1327,7 @@ void Config::saveConfigFile() {
|
||||
// create a .tmp file and write to it...
|
||||
const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false);
|
||||
if (xml_result != tinyxml2::XMLError::XML_SUCCESS) {
|
||||
log_info("cfg", "failed to write file: {}", this->configLocationTemp.string());
|
||||
log_info("cfg", "failed to write file: {}", this->configLocationTemp);
|
||||
return;
|
||||
}
|
||||
// copy the .tmp file to the main file...
|
||||
@@ -1250,4 +1337,4 @@ void Config::saveConfigFile() {
|
||||
}
|
||||
// delete the .tmp file (not critical if this fails)
|
||||
DeleteFileW(this->configLocationTemp.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
#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("bat_threshold", entry.bat_threshold);
|
||||
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;
|
||||
|
||||
int bat = 0;
|
||||
el->QueryIntAttribute("bat_threshold", &bat);
|
||||
entry.bat_threshold = bat;
|
||||
|
||||
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_ms", analog.delay_ms);
|
||||
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->QueryUnsignedAttribute("delay_ms", &a.delay_ms);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
|
||||
|
||||
return "Invalid SMX Dedicab Light (" + index_string + ")";
|
||||
}
|
||||
case rawinput::XINPUT_GAMEPAD: {
|
||||
if (index < static_cast<size_t>(xinput::XInputOutputEnum::COUNT)) {
|
||||
return fmt::format("{} ({})",
|
||||
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(index)),
|
||||
device->desc);
|
||||
}
|
||||
|
||||
return "Invalid XInput Gamepad Light (" + index_string + ")";
|
||||
}
|
||||
case rawinput::DESTROYED:
|
||||
return "Unplugged device (" + index_string + ")";
|
||||
default:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
@@ -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"
|
||||
@@ -19,7 +21,7 @@ namespace cfg {
|
||||
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
|
||||
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
|
||||
if (fileutils::file_exists(this->config_path)) {
|
||||
log_info("ScreenResize", "loading config from: {}", this->config_path.string());
|
||||
log_info("ScreenResize", "loading config from: {}", this->config_path);
|
||||
file_exists = true;
|
||||
}
|
||||
} else {
|
||||
@@ -33,7 +35,7 @@ namespace cfg {
|
||||
|
||||
ScreenResize::~ScreenResize() {
|
||||
}
|
||||
|
||||
|
||||
void ScreenResize::config_load() {
|
||||
std::string config = fileutils::text_read(this->config_path);
|
||||
if (config.empty()) {
|
||||
@@ -76,8 +78,12 @@ namespace cfg {
|
||||
eamuse_get_game(),
|
||||
use_game_setting,
|
||||
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,11 +52,15 @@ 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
|
||||
// client = just the content area without frames.
|
||||
bool window_always_on_top = false;
|
||||
bool window_disable_round_corners = false;
|
||||
bool client_keep_aspect_ratio = true;
|
||||
bool enable_window_resize = false;
|
||||
int window_decoration = 0; // enum type WindowDecorationMode
|
||||
|
||||
+8
@@ -15,3 +15,11 @@ RUN pacman --noconfirm -Syu git \
|
||||
ccache
|
||||
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
||||
RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
|
||||
|
||||
RUN mkdir -p /opt/llvm-mingw-xp \
|
||||
&& curl -fsSL "https://github.com/mon/llvm-mingw-xp/releases/download/llvm-mingw-xp-22.1.0/llvm-mingw-llvm-mingw-xp-22.1.0-msvcrt-ubuntu-22.04-x86_64.tar.xz" \
|
||||
| tar -xJ --strip-components=1 -C /opt/llvm-mingw-xp
|
||||
ENV PATH="$PATH:/opt/llvm-mingw-xp/bin"
|
||||
|
||||
RUN curl -fsSL "https://github.com/mon/windows-dll-compat-checker/releases/download/v1.3/windows_dll_compat_checker-linux-x86_64.tar.xz" \
|
||||
| tar -xJ -C /usr/local/bin
|
||||
|
||||
Vendored
+1
@@ -20,6 +20,7 @@ Usage:
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
+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)) {
|
||||
|
||||
Vendored
+1
@@ -39,6 +39,7 @@
|
||||
#define ROBIN_HOOD_VERSION_PATCH 2 // for backwards-compatible bug fixes
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
|
||||
@@ -38,11 +38,12 @@ std::vector<Analog> &games::bc::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Stick X",
|
||||
"Stick Y"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Stick X", AnalogType::LinearCentered },
|
||||
{ "Stick Y", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -45,13 +45,14 @@ std::vector<Analog> &games::ccj::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Joystick X",
|
||||
"Joystick Y",
|
||||
"Trackball DX",
|
||||
"Trackball DY"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Joystick X", AnalogType::LinearCentered },
|
||||
{ "Joystick Y", AnalogType::LinearCentered },
|
||||
{ "Trackball DX", AnalogType::LinearCentered },
|
||||
{ "Trackball DY", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "util/logging.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "games/io.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include "io.h"
|
||||
|
||||
@@ -112,7 +113,7 @@ namespace games::ccj {
|
||||
static std::chrono::time_point<std::chrono::steady_clock> lastModified = std::chrono::steady_clock::now();
|
||||
static std::chrono::milliseconds debounceDuration(100);
|
||||
auto currentTime = std::chrono::steady_clock::now();
|
||||
bool pressed = get_async_secondary_mouse() & 0x8000;
|
||||
bool pressed = get_async_secondary_mouse();
|
||||
bool focused = GetForegroundWindow() == hWnd;
|
||||
|
||||
if (focused && MOUSE_TRACKBALL_USE_TOGGLE && pressed && (currentTime - lastModified > debounceDuration)) {
|
||||
@@ -243,6 +244,7 @@ namespace games::ccj {
|
||||
log_info("trackball", "thread start, use mouse: {}, toggle: {}", MOUSE_TRACKBALL, MOUSE_TRACKBALL_USE_TOGGLE);
|
||||
|
||||
tbThread = new std::thread([&] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (tbThreadRunning) {
|
||||
if (hWnd && wndProc) {
|
||||
wndProc(hWnd, WM_INPUT, RIM_INPUT, (LPARAM)fakeHandle);
|
||||
@@ -251,7 +253,7 @@ namespace games::ccj {
|
||||
if (!tbThreadRunning)
|
||||
break;
|
||||
|
||||
std::this_thread::sleep_for(10ms);
|
||||
timer.sleep(10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -22,6 +23,7 @@
|
||||
#include "p3io/usbmem.h"
|
||||
|
||||
#include "p4io/p4io.h"
|
||||
#include <cstring>
|
||||
|
||||
using namespace acioemu;
|
||||
|
||||
@@ -52,9 +54,9 @@ namespace games::ddr {
|
||||
return SendMessage_real(hWnd, Msg, wParam, lParam);
|
||||
}
|
||||
|
||||
bool contains_only_ascii(const std::string& str) {
|
||||
bool contains_only_ascii(const std::wstring& str) {
|
||||
for (auto c: str) {
|
||||
if (static_cast<unsigned char>(c) > 127) {
|
||||
if (c > 127) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -79,24 +81,24 @@ namespace games::ddr {
|
||||
}
|
||||
|
||||
if (fileutils::dir_exists(dir)) {
|
||||
log_info("ddr", "looking for codecs in this directory: {}", dir.string());
|
||||
log_info("ddr", "looking for codecs in this directory: {}", dir);
|
||||
} else {
|
||||
log_info("ddr", "codecs directory not found: {}", dir.string());
|
||||
log_info("ddr", "codecs directory not found: {}", dir);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &file : std::filesystem::directory_iterator(dir)) {
|
||||
const auto &filename = file.path().filename();
|
||||
const auto extension = strtolower(filename.extension().string());
|
||||
const auto extension = filename.extension().wstring();
|
||||
|
||||
if (extension != ".dll") {
|
||||
if (wcsicmp(extension.c_str(), L".dll") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
log_info("ddr", "found DLL: {}, size: {} bytes", filename.string(), file.file_size());
|
||||
if (filename == "k-clvsd.dll" || filename.string().find("xactengine") == 0) {
|
||||
log_info("ddr", "found DLL: {}, size: {} bytes", filename, file.file_size());
|
||||
if (filename == "k-clvsd.dll" || filename.wstring().find(L"xactengine") == 0) {
|
||||
const std::wstring wcmd = L"regsvr32.exe /s \"" + file.path().wstring() + L"\"";
|
||||
const std::string cmd = "regsvr32.exe /s \"" + file.path().string() + "\"";
|
||||
const std::string cmd = fmt::format("regsvr32.exe /s \"{}\"", file.path());
|
||||
|
||||
int result = 0;
|
||||
std::thread t([wcmd, &result]() {
|
||||
@@ -108,18 +110,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, 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().wstring())) {
|
||||
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"});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,4 +291,50 @@ namespace games::ddr {
|
||||
// dispose device hook
|
||||
devicehook_dispose();
|
||||
}
|
||||
|
||||
static void get_analog_xy_axis(Analog &analog, bool &less, bool &more) {
|
||||
if (!analog.isSet()) {
|
||||
return;
|
||||
}
|
||||
const auto value = GameAPI::Analogs::getState(RI_MGR, analog);
|
||||
|
||||
// stepmania linux source:
|
||||
// https://github.com/stepmania/stepmania/blob/d55acb1ba26f1c5b5e3048d6d6c0bd116625216f/src/arch/InputHandler/InputHandler_Linux_Event.cpp#L410
|
||||
|
||||
// for example, value cap with range [0, 255]:
|
||||
// 127 = 0.498, neutral
|
||||
// 128 = 0.502, both
|
||||
// apparently some adapters report values like above; this obviously makes a lot of
|
||||
// assumptions about bit width of the HID value cap and how the adapter reports
|
||||
// neutral/both values, but this is good enough for parity with stepmania and its many forks
|
||||
if (0.5001f < value && value < 0.75f) {
|
||||
less |= true;
|
||||
more |= true;
|
||||
|
||||
} else if (value <= 0.25f) {
|
||||
less |= true;
|
||||
} else if (value >= 0.75f) {
|
||||
more |= true;
|
||||
}
|
||||
}
|
||||
|
||||
void get_analog_x_axis(int player, bool &left, bool &right) {
|
||||
if (player != 1 && player != 2) {
|
||||
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_LEFT_RIGHT : Analogs::P2_LEFT_RIGHT);
|
||||
get_analog_xy_axis(analog, left, right);
|
||||
}
|
||||
|
||||
void get_analog_y_axis(int player, bool &up, bool &down) {
|
||||
if (player != 1 && player != 2) {
|
||||
log_fatal("ddr", "invalid player number: {}, expected 1 or 2", player);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &analog = get_analogs().at(player == 1 ? Analogs::P1_UP_DOWN : Analogs::P2_UP_DOWN);
|
||||
get_analog_xy_axis(analog, up, down);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,7 @@ namespace games::ddr {
|
||||
private:
|
||||
void register_codecs();
|
||||
};
|
||||
|
||||
void get_analog_x_axis(int player, bool &left, bool &right);
|
||||
void get_analog_y_axis(int player, bool &up, bool &down);
|
||||
}
|
||||
|
||||
+158
-110
@@ -35,142 +35,190 @@ std::vector<Button> &games::ddr::get_buttons() {
|
||||
return analogs;
|
||||
}
|
||||
|
||||
|
||||
std::string games::ddr::get_buttons_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"For DDR pad arrows, double check that simultaneous Left+Right and Up+Down can be detected.\n"
|
||||
"You should boot the game, enter test menu, and use Foot Panel Check.\n\n"
|
||||
"When mapping arrows, try the following in order:\n\n"
|
||||
" 1. If your controller uses face buttons (A/B/X/Y), bind them here.\n"
|
||||
" 2. If your controller supports XInput, use that.\n"
|
||||
" 3. If your controller uses analog axis for arrows, try Analogs tab.\n"
|
||||
" 4. Otherwise, you will need to use remapping software or get a better adapter."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Analog> &games::ddr::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Dance Dance Revolution");
|
||||
|
||||
using namespace GameAPI::Analogs;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "P1 Left-Right (Axis Fix)", AnalogType::LinearCentered },
|
||||
{ "P1 Up-Down (Axis Fix)", AnalogType::LinearCentered },
|
||||
{ "P2 Left-Right (Axis Fix)", AnalogType::LinearCentered },
|
||||
{ "P2 Up-Down (Axis Fix)", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
return analogs;
|
||||
}
|
||||
|
||||
std::string games::ddr::get_analogs_help() {
|
||||
// keep to max 100 characters wide
|
||||
return
|
||||
"Only use this if your DDR pad outputs analog axis for arrows.\n\n"
|
||||
"If the pad uses face buttons (A/B/X/Y), use Buttons tab instead.\n\n"
|
||||
"Spice will treat values <=25% as Left, >=75% as Right, ~=50% as neutral,\n"
|
||||
"and value between 50% and 75% as both arrows.\n\n"
|
||||
"This is the classic Stepmania \"Axis Fix\" which may or may not work with\n"
|
||||
"your dance pad or your adapter."
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Light> &games::ddr::get_lights() {
|
||||
static std::vector<Light> 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"}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,16 @@ namespace games::ddr {
|
||||
};
|
||||
}
|
||||
|
||||
// all analogs in correct order
|
||||
namespace Analogs {
|
||||
enum {
|
||||
P1_LEFT_RIGHT,
|
||||
P1_UP_DOWN,
|
||||
P2_LEFT_RIGHT,
|
||||
P2_UP_DOWN
|
||||
};
|
||||
}
|
||||
|
||||
// all lights in correct order
|
||||
namespace Lights {
|
||||
enum {
|
||||
@@ -177,5 +187,8 @@ namespace games::ddr {
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "cfg/api.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "../ddr.h"
|
||||
@@ -282,7 +283,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);
|
||||
}
|
||||
|
||||
@@ -459,7 +460,8 @@ int games::ddr::DDRP3IOHandle::device_io(
|
||||
if (nOutBufferSize >= 4) {
|
||||
|
||||
// cool down
|
||||
Sleep(1);
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(1);
|
||||
|
||||
// get controls as single variable (4 bytes)
|
||||
auto &controls = *(uint32_t*) lpOutBuffer;
|
||||
@@ -498,32 +500,80 @@ int games::ddr::DDRP3IOHandle::device_io(
|
||||
|
||||
// shift table
|
||||
static size_t shift_table[] = {
|
||||
30, 28, 29, 8, 9, 10, 11, 12, 24, 25, 14, 15, 16, 17, 18, 19, 20, 26, 27, 22, 23
|
||||
30, 28, 29, // service, test, coin
|
||||
8, // p1 start
|
||||
9, 10, 11, 12, // p1 panel
|
||||
24, 25, 14, 15, // p1 menu
|
||||
16, // p2 start
|
||||
17, 18, 19, 20, // p2 panel
|
||||
26, 27, 22, 23 // p2 menu
|
||||
};
|
||||
static size_t button_table[] = {
|
||||
Buttons::SERVICE,
|
||||
Buttons::TEST,
|
||||
Buttons::COIN_MECH,
|
||||
|
||||
Buttons::P1_START,
|
||||
|
||||
Buttons::P1_PANEL_UP,
|
||||
Buttons::P1_PANEL_DOWN,
|
||||
Buttons::P1_PANEL_LEFT,
|
||||
Buttons::P1_PANEL_RIGHT,
|
||||
|
||||
Buttons::P1_MENU_UP,
|
||||
Buttons::P1_MENU_DOWN,
|
||||
Buttons::P1_MENU_LEFT,
|
||||
Buttons::P1_MENU_RIGHT,
|
||||
|
||||
Buttons::P2_START,
|
||||
|
||||
Buttons::P2_PANEL_UP,
|
||||
Buttons::P2_PANEL_DOWN,
|
||||
Buttons::P2_PANEL_LEFT,
|
||||
Buttons::P2_PANEL_RIGHT,
|
||||
|
||||
Buttons::P2_MENU_UP,
|
||||
Buttons::P2_MENU_DOWN,
|
||||
Buttons::P2_MENU_LEFT,
|
||||
Buttons::P2_MENU_RIGHT,
|
||||
};
|
||||
|
||||
// get analogs
|
||||
struct {
|
||||
bool up;
|
||||
bool down;
|
||||
bool left;
|
||||
bool right;
|
||||
} analog_values[2] = {};
|
||||
games::ddr::get_analog_x_axis(1, analog_values[0].left, analog_values[0].right);
|
||||
games::ddr::get_analog_y_axis(1, analog_values[0].up, analog_values[0].down);
|
||||
games::ddr::get_analog_x_axis(2, analog_values[1].left, analog_values[1].right);
|
||||
games::ddr::get_analog_y_axis(2, analog_values[1].up, analog_values[1].down);
|
||||
if (analog_values[0].up) {
|
||||
controls |= 1 << 9;
|
||||
}
|
||||
if (analog_values[0].down) {
|
||||
controls |= 1 << 10;
|
||||
}
|
||||
if (analog_values[0].left) {
|
||||
controls |= 1 << 11;
|
||||
}
|
||||
if (analog_values[0].right) {
|
||||
controls |= 1 << 12;
|
||||
}
|
||||
if (analog_values[1].up) {
|
||||
controls |= 1 << 17;
|
||||
}
|
||||
if (analog_values[1].down) {
|
||||
controls |= 1 << 18;
|
||||
}
|
||||
if (analog_values[1].left) {
|
||||
controls |= 1 << 19;
|
||||
}
|
||||
if (analog_values[1].right) {
|
||||
controls |= 1 << 20;
|
||||
}
|
||||
|
||||
// update states
|
||||
auto &buttons = get_buttons();
|
||||
size_t count = 0;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "../io.h"
|
||||
@@ -255,7 +256,8 @@ namespace games::ddr {
|
||||
case P4IO_IOCTL_GET_INPUTS: {
|
||||
|
||||
// Prevents this function from being called at its normal 2000-2500 kHz cadence and overloading the CPU, instead reduces it to 250 Hz
|
||||
Sleep(4);
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(4);
|
||||
|
||||
memset(lpOutBuffer, 0, 16);
|
||||
auto controls = (uint32_t*) lpOutBuffer;
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#include "games/game.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/memutils.h"
|
||||
#include "rgb_cam.h"
|
||||
|
||||
#pragma pack(push)
|
||||
|
||||
@@ -150,6 +152,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;
|
||||
|
||||
@@ -219,10 +222,11 @@ namespace games::drs {
|
||||
void start_touch() {
|
||||
std::thread t([] {
|
||||
log_info("drs", "starting touch input thread");
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// main loop
|
||||
while (TRUE) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
timer.sleep(1);
|
||||
|
||||
TOUCH_EVENTS.clear();
|
||||
touch_get_events(TOUCH_EVENTS);
|
||||
@@ -313,6 +317,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();
|
||||
}
|
||||
@@ -35,13 +35,14 @@ std::vector<Analog> &games::ftt::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Pad 1",
|
||||
"Pad 2",
|
||||
"Pad 3",
|
||||
"Pad 4"
|
||||
);
|
||||
using GameAPI::Analogs::AnalogType;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Pad 1", AnalogType::LinearPositive },
|
||||
{ "Pad 2", AnalogType::LinearPositive },
|
||||
{ "Pad 3", AnalogType::LinearPositive },
|
||||
{ "Pad 4", AnalogType::LinearPositive }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "bi2x_hook.h"
|
||||
|
||||
#if SPICE64
|
||||
|
||||
#include <optional>
|
||||
#include <cstdint>
|
||||
#include "util/detour.h"
|
||||
@@ -18,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 {
|
||||
@@ -43,6 +50,7 @@ namespace games::gitadora {
|
||||
};
|
||||
|
||||
struct AIO_IOB2_BI2X_WRFIRM {
|
||||
uint8_t data[0x20f48];
|
||||
};
|
||||
|
||||
struct AIO_SCI_COMM_T {
|
||||
@@ -159,6 +167,15 @@ 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);
|
||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS) == 0x2C8);
|
||||
|
||||
/*
|
||||
* typedefs
|
||||
*/
|
||||
@@ -701,4 +718,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,12 +5,17 @@
|
||||
#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/deferlog.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/sysutils.h"
|
||||
#include "util/utils.h"
|
||||
#include "hooks/setupapihook.h"
|
||||
|
||||
namespace games::gitadora {
|
||||
@@ -18,6 +23,10 @@ namespace games::gitadora {
|
||||
// settings
|
||||
bool TWOCHANNEL = false;
|
||||
std::optional<unsigned int> CAB_TYPE = std::nullopt;
|
||||
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
|
||||
@@ -216,13 +225,305 @@ namespace games::gitadora {
|
||||
|
||||
// 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) {
|
||||
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() && !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");
|
||||
}
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
if (is_arena_model() && !GRAPHICS_WINDOWED && !GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||
const auto &monitors = sysutils::enumerate_monitors();
|
||||
const size_t active_count = monitors.size();
|
||||
log_info("gitadora", "arena model: detected {} active monitor(s)", active_count);
|
||||
if (active_count < 4) {
|
||||
log_info("gitadora", "arena model: enable single monitor mode due to insufficient monitors");
|
||||
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||
GRAPHICS_PREVENT_SECONDARY_WINDOW = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
static decltype(GetDisplayConfigBufferSizes) *GetDisplayConfigBufferSizes_orig = nullptr;
|
||||
static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr;
|
||||
static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr;
|
||||
|
||||
// cached primary real monitor: its path + source/target mode entries.
|
||||
// modeInfoIdx values on the path are renumbered to 0 / 1 so the cache is self-contained.
|
||||
static DISPLAYCONFIG_PATH_INFO real_primary_path = {};
|
||||
static DISPLAYCONFIG_MODE_INFO real_primary_modes[2] = {}; // [0]=source, [1]=target
|
||||
|
||||
// fake monitors appended after the real ones. the game classifies monitors
|
||||
// by outputTechnology + connectorInstance:
|
||||
// HDMI -> main 4k monitor (real primary)
|
||||
// DP connInstance 0 -> left
|
||||
// DP connInstance 1 -> right
|
||||
// DP connInstance 2 -> small (sub/touch)
|
||||
// ids are negated on the fake monitor headers so they can be distinguished
|
||||
// from real ones in DisplayConfigGetDeviceInfo.
|
||||
struct FakeMonitor {
|
||||
LONG id;
|
||||
int width;
|
||||
int height;
|
||||
int offset_x;
|
||||
int offset_y;
|
||||
UINT32 connector_instance;
|
||||
};
|
||||
|
||||
// ORDERING MATTERS: the d3d9 wrapper (FAKE_SUBSCREEN_ADAPTER) hands out
|
||||
// adapters as "\\.\DISPLAY_SPICE_FAKE_{N}" for N=1,2,3. The game maps each
|
||||
// adapter to a swap chain role via its DisplayConfig connector instance,
|
||||
// so the entries here must be listed in the same order the wrapper enumerates
|
||||
// them: id=1 -> adapter 1 -> left, id=2 -> adapter 2 -> right, id=3 -> adapter 3 -> small.
|
||||
static constexpr FakeMonitor FAKE_MONITORS[] = {
|
||||
{ 1, 1080, 1920, -100000, -100000, 0 }, // left (DP connector instance 0)
|
||||
{ 2, 1080, 1920, -200000, -200000, 1 }, // right (DP connector instance 1)
|
||||
{ 3, 800, 1280, -300000, -300000, 2 }, // small (DP connector instance 2, touch)
|
||||
};
|
||||
static constexpr UINT32 FAKE_MONITOR_COUNT = static_cast<UINT32>(std::size(FAKE_MONITORS));
|
||||
|
||||
// call QueryDisplayConfig once, keep only the primary monitor's path and its
|
||||
// two referenced modes (source + target). modeInfoIdx values are rewritten to
|
||||
// 0 and 1 so the cache is self-consistent.
|
||||
static void cache_primary_monitor_info() {
|
||||
UINT32 path_count = 0;
|
||||
UINT32 mode_count = 0;
|
||||
if (GetDisplayConfigBufferSizes_orig(
|
||||
QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count) != ERROR_SUCCESS) {
|
||||
log_fatal("gitadora", "cache_primary_monitor_info: GetDisplayConfigBufferSizes failed");
|
||||
}
|
||||
|
||||
std::vector<DISPLAYCONFIG_PATH_INFO> all_paths(path_count);
|
||||
std::vector<DISPLAYCONFIG_MODE_INFO> all_modes(mode_count);
|
||||
if (QueryDisplayConfig_orig(
|
||||
QDC_ONLY_ACTIVE_PATHS,
|
||||
&path_count, all_paths.data(),
|
||||
&mode_count, all_modes.data(),
|
||||
nullptr) != ERROR_SUCCESS) {
|
||||
log_fatal("gitadora", "cache_primary_monitor_info: QueryDisplayConfig failed");
|
||||
}
|
||||
all_paths.resize(path_count);
|
||||
all_modes.resize(mode_count);
|
||||
|
||||
// pick the primary monitor: source mode at (0, 0)
|
||||
auto primary = std::find_if(all_paths.begin(), all_paths.end(),
|
||||
[&](const auto &p) {
|
||||
const auto idx = p.sourceInfo.modeInfoIdx;
|
||||
return idx < all_modes.size() &&
|
||||
all_modes[idx].infoType == DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE &&
|
||||
all_modes[idx].sourceMode.position.x == 0 &&
|
||||
all_modes[idx].sourceMode.position.y == 0;
|
||||
});
|
||||
if (primary == all_paths.end()) {
|
||||
log_fatal("gitadora", "cache_primary_monitor_info: no primary monitor found");
|
||||
}
|
||||
|
||||
real_primary_modes[0] = all_modes[primary->sourceInfo.modeInfoIdx];
|
||||
real_primary_modes[1] = all_modes[primary->targetInfo.modeInfoIdx];
|
||||
real_primary_path = *primary;
|
||||
real_primary_path.sourceInfo.modeInfoIdx = 0;
|
||||
real_primary_path.targetInfo.modeInfoIdx = 1;
|
||||
|
||||
log_info("gitadora", "cache_primary_monitor_info: cached primary monitor");
|
||||
}
|
||||
|
||||
static
|
||||
LONG
|
||||
WINAPI
|
||||
GetDisplayConfigBufferSizes_hook(
|
||||
UINT32 Flags,
|
||||
UINT32 *pNumPathArrayElements,
|
||||
UINT32 *pNumModeInfoArrayElements)
|
||||
{
|
||||
// populate cached primary real monitor on the first call
|
||||
static std::once_flag populate_once;
|
||||
std::call_once(populate_once, cache_primary_monitor_info);
|
||||
|
||||
// always report exactly 1 real + N fake monitors
|
||||
*pNumPathArrayElements = 1 + FAKE_MONITOR_COUNT;
|
||||
*pNumModeInfoArrayElements = 2 + FAKE_MONITOR_COUNT * 2;
|
||||
|
||||
log_info(
|
||||
"gitadora",
|
||||
"GetDisplayConfigBufferSizes: 1 real path + {} fake monitor(s)",
|
||||
FAKE_MONITOR_COUNT);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// write fake monitor i into the caller's path/mode arrays. layout (single-monitor
|
||||
// assumption): index 0 in both arrays holds the cached primary real monitor, so
|
||||
// fake i occupies path slot (1 + i) and mode slots (2 + i*2) / (2 + i*2 + 1).
|
||||
static void insert_fake_monitor(
|
||||
DISPLAYCONFIG_PATH_INFO *paths,
|
||||
DISPLAYCONFIG_MODE_INFO *modes,
|
||||
UINT32 i)
|
||||
{
|
||||
const FakeMonitor &m = FAKE_MONITORS[i];
|
||||
const UINT32 src_idx = 2 + i * 2;
|
||||
const UINT32 tgt_idx = src_idx + 1;
|
||||
const LUID adapter_id { .LowPart = static_cast<DWORD>(-m.id), .HighPart = -m.id };
|
||||
const UINT32 uid = static_cast<UINT32>(-m.id);
|
||||
|
||||
paths[1 + i] = {
|
||||
.sourceInfo = {
|
||||
.adapterId = adapter_id,
|
||||
.id = uid,
|
||||
.modeInfoIdx = src_idx,
|
||||
.statusFlags = DISPLAYCONFIG_SOURCE_IN_USE,
|
||||
},
|
||||
.targetInfo = {
|
||||
.adapterId = adapter_id,
|
||||
.id = uid,
|
||||
.modeInfoIdx = tgt_idx,
|
||||
.outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL,
|
||||
.rotation = DISPLAYCONFIG_ROTATION_IDENTITY,
|
||||
.scaling = DISPLAYCONFIG_SCALING_IDENTITY,
|
||||
.refreshRate = { .Numerator = 60000, .Denominator = 1000 },
|
||||
.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE,
|
||||
.targetAvailable = TRUE,
|
||||
.statusFlags = DISPLAYCONFIG_TARGET_IN_USE,
|
||||
},
|
||||
.flags = DISPLAYCONFIG_PATH_ACTIVE,
|
||||
};
|
||||
|
||||
modes[src_idx] = {
|
||||
.infoType = DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE,
|
||||
.id = uid,
|
||||
.adapterId = adapter_id,
|
||||
.sourceMode = {
|
||||
.width = static_cast<UINT32>(m.width),
|
||||
.height = static_cast<UINT32>(m.height),
|
||||
.pixelFormat = DISPLAYCONFIG_PIXELFORMAT_32BPP,
|
||||
.position = { .x = m.offset_x, .y = m.offset_y },
|
||||
},
|
||||
};
|
||||
|
||||
modes[tgt_idx] = {
|
||||
.infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET,
|
||||
.id = uid,
|
||||
.adapterId = adapter_id,
|
||||
.targetMode = {},
|
||||
};
|
||||
|
||||
log_misc(
|
||||
"gitadora",
|
||||
"inserted fake monitor: id={}, width={}, height={}, offset_x={}, offset_y={}",
|
||||
m.id, m.width, m.height, m.offset_x, m.offset_y);
|
||||
}
|
||||
|
||||
static
|
||||
LONG
|
||||
WINAPI
|
||||
QueryDisplayConfig_hook(
|
||||
UINT32 flags,
|
||||
UINT32* numPathArrayElements,
|
||||
DISPLAYCONFIG_PATH_INFO* pathArray,
|
||||
UINT32* numModeInfoArrayElements,
|
||||
DISPLAYCONFIG_MODE_INFO* modeInfoArray,
|
||||
DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId)
|
||||
{
|
||||
// copy cached primary real monitor into caller buffers at index 0
|
||||
pathArray[0] = real_primary_path;
|
||||
modeInfoArray[0] = real_primary_modes[0];
|
||||
modeInfoArray[1] = real_primary_modes[1];
|
||||
*numPathArrayElements = 1 + FAKE_MONITOR_COUNT;
|
||||
*numModeInfoArrayElements = 2 + FAKE_MONITOR_COUNT * 2;
|
||||
if (currentTopologyId != nullptr) {
|
||||
*currentTopologyId = DISPLAYCONFIG_TOPOLOGY_EXTEND;
|
||||
}
|
||||
|
||||
log_misc("gitadora", "QueryDisplayConfig returning fake monitor paths and modes");
|
||||
|
||||
// append fake monitors after the real one
|
||||
for (UINT32 i = 0; i < FAKE_MONITOR_COUNT; i++) {
|
||||
insert_fake_monitor(pathArray, modeInfoArray, i);
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static
|
||||
LONG
|
||||
WINAPI
|
||||
DisplayConfigGetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket)
|
||||
{
|
||||
if (requestPacket == nullptr) {
|
||||
return DisplayConfigGetDeviceInfo_orig(requestPacket);
|
||||
}
|
||||
|
||||
// handle fake monitors (negative id) directly without calling orig
|
||||
const auto id = static_cast<int>(requestPacket->id);
|
||||
if (id < 0) {
|
||||
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) {
|
||||
const auto sourceName = reinterpret_cast<DISPLAYCONFIG_SOURCE_DEVICE_NAME*>(requestPacket);
|
||||
// name must match WrappedIDirect3D9::GetAdapterIdentifier
|
||||
const std::string adapter_name = fmt::format("\\\\.\\DISPLAY_SPICE_FAKE_{}", -id);
|
||||
wcscpy(sourceName->viewGdiDeviceName, s2ws(adapter_name).c_str());
|
||||
log_misc("gitadora",
|
||||
"DisplayConfigGetDeviceInfo: fake source id={} name={}", id, adapter_name);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
|
||||
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
|
||||
const LONG fake_id = -id;
|
||||
UINT32 conn_inst = 0xff;
|
||||
for (const auto& f : FAKE_MONITORS) {
|
||||
if (f.id == fake_id) {
|
||||
conn_inst = f.connector_instance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL;
|
||||
targetName->connectorInstance = conn_inst;
|
||||
wcscpy(targetName->monitorFriendlyDeviceName, L"Spice Fake Monitor");
|
||||
wcscpy(targetName->monitorDevicePath, L"\\\\?\\SpiceFakeMonitor");
|
||||
log_misc("gitadora",
|
||||
"DisplayConfigGetDeviceInfo: fake target id={} -> DP connInst {}",
|
||||
id, targetName->connectorInstance);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket);
|
||||
if (ret != ERROR_SUCCESS ||
|
||||
requestPacket->type != DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// override the cached primary real monitor target info to look like HDMI/0
|
||||
const auto targetName = reinterpret_cast<DISPLAYCONFIG_TARGET_DEVICE_NAME*>(requestPacket);
|
||||
const auto& target = real_primary_path.targetInfo;
|
||||
if (target.id == targetName->header.id &&
|
||||
target.adapterId.HighPart == targetName->header.adapterId.HighPart &&
|
||||
target.adapterId.LowPart == targetName->header.adapterId.LowPart)
|
||||
{
|
||||
targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
|
||||
targetName->connectorInstance = 0;
|
||||
log_info("gitadora",
|
||||
"overriding primary monitor (id={}) to pretend to be HDMI",
|
||||
targetName->header.id);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void GitaDoraGame::attach() {
|
||||
Game::attach();
|
||||
|
||||
@@ -279,31 +580,62 @@ namespace games::gitadora {
|
||||
|
||||
// volume change prevention
|
||||
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
|
||||
return;
|
||||
}
|
||||
|
||||
// monitor/touch hooks (windowed or full screen)
|
||||
if (GRAPHICS_FORCE_SINGLE_ADAPTER || GRAPHICS_PREVENT_SECONDARY_WINDOW) {
|
||||
// enable touch hook for subscreen overlay
|
||||
wintouchemu::FORCE = true;
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
||||
|
||||
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
||||
#if !SPICE_XP
|
||||
|
||||
if (!GRAPHICS_WINDOWED) {
|
||||
// monitor hook: always pretend to have 1 primary real monitor + 3 fake monitors
|
||||
// (LEFT / RIGHT / SMALL) so the game accepts the arena cab topology
|
||||
GetDisplayConfigBufferSizes_orig =
|
||||
detour::iat_try("GetDisplayConfigBufferSizes",
|
||||
GetDisplayConfigBufferSizes_hook, avs::game::DLL_INSTANCE);
|
||||
QueryDisplayConfig_orig =
|
||||
detour::iat_try("QueryDisplayConfig",
|
||||
QueryDisplayConfig_hook, avs::game::DLL_INSTANCE);
|
||||
DisplayConfigGetDeviceInfo_orig =
|
||||
detour::iat_try("DisplayConfigGetDeviceInfo",
|
||||
DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// window patch
|
||||
if (GRAPHICS_WINDOWED && !replace_pattern(
|
||||
gdme_module,
|
||||
"754185ED753D8B4118BF0000CB02",
|
||||
"9090????9090??????????????12", 0, 0))
|
||||
{
|
||||
log_warning("gitadora", "windowed mode failed");
|
||||
if (!is_arena_model()) {
|
||||
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
||||
if (GRAPHICS_WINDOWED && !replace_pattern(
|
||||
gdme_module,
|
||||
"754185ED753D8B4118BF0000CB02",
|
||||
"9090????9090??????????????12", 0, 0)) {
|
||||
log_warning("gitadora", "windowed mode failed");
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
|
||||
HMODULE bmsd_module = libutils::try_module("libbmsd.dll");
|
||||
|
||||
// two channel mod
|
||||
if (TWOCHANNEL) {
|
||||
bmsd2_boot_orig = detour::iat_try("bmsd2_boot", bmsd2_boot_hook, bmsd_module);
|
||||
if (is_arena_model()) {
|
||||
log_warning("gitadora", "two channel audio (-2ch) is not supported on Arena Model - use a patch instead");
|
||||
deferredlogs::defer_error_messages({
|
||||
"two channel audio (-2ch) is not supported on Arena Model - use a patch instead",
|
||||
});
|
||||
|
||||
if (!(replace_pattern(bmsd_engine_module, "33000000488D", "03??????????", 0, 0) ||
|
||||
replace_pattern(bmsd_engine_module, "330000000F10", "03??????????", 0, 0)))
|
||||
{
|
||||
log_warning("gitadora", "two channel mode failed");
|
||||
} else {
|
||||
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
|
||||
HMODULE bmsd_module = libutils::try_module("libbmsd.dll");
|
||||
|
||||
bmsd2_boot_orig = detour::iat_try("bmsd2_boot", bmsd2_boot_hook, bmsd_module);
|
||||
if (!(replace_pattern(bmsd_engine_module, "33000000488D", "03??????????", 0, 0) ||
|
||||
replace_pattern(bmsd_engine_module, "330000000F10", "03??????????", 0, 0))) {
|
||||
log_warning("gitadora", "two channel mode failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,17 @@
|
||||
|
||||
#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 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 +43,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;
|
||||
}
|
||||
}
|
||||
|
||||
+133
-101
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,19 +86,21 @@ std::vector<Button> &games::gitadora::get_buttons() {
|
||||
std::vector<Analog> &games::gitadora::get_analogs() {
|
||||
static std::vector<Analog> analogs;
|
||||
|
||||
using namespace GameAPI::Analogs;
|
||||
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("GitaDora");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(&analogs,
|
||||
"Guitar P1 Wail X",
|
||||
"Guitar P1 Wail Y",
|
||||
"Guitar P1 Wail Z",
|
||||
"Guitar P1 Knob",
|
||||
"Guitar P2 Wail X",
|
||||
"Guitar P2 Wail Y",
|
||||
"Guitar P2 Wail Z",
|
||||
"Guitar P2 Knob"
|
||||
);
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{"Guitar P1 Wail X", AnalogType::LinearCentered},
|
||||
{"Guitar P1 Wail Y", AnalogType::LinearCentered},
|
||||
{"Guitar P1 Wail Z", AnalogType::LinearCentered},
|
||||
{"Guitar P1 Knob", AnalogType::Circular},
|
||||
{"Guitar P2 Wail X", AnalogType::LinearCentered},
|
||||
{"Guitar P2 Wail Y", AnalogType::LinearCentered},
|
||||
{"Guitar P2 Wail Z", AnalogType::LinearCentered},
|
||||
{"Guitar P2 Knob", AnalogType::Circular}
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
@@ -107,9 +110,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 +129,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,100 +160,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)"},
|
||||
|
||||
"Title Average R (Arena)",
|
||||
"Title Average G (Arena)",
|
||||
"Title Average B (Arena)",
|
||||
{"Arena Model", "Title Average R (Arena)"},
|
||||
{"Arena Model", "Title Average G (Arena)"},
|
||||
{"Arena Model", "Title Average B (Arena)"},
|
||||
|
||||
"Woofer R (Arena)",
|
||||
"Woofer G (Arena)",
|
||||
"Woofer B (Arena)",
|
||||
{"Arena Model", "Woofer R (Arena)"},
|
||||
{"Arena Model", "Woofer G (Arena)"},
|
||||
{"Arena Model", "Woofer B (Arena)"},
|
||||
|
||||
"Card Reader R (Arena)",
|
||||
"Card Reader G (Arena)",
|
||||
"Card Reader 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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -209,6 +212,7 @@ namespace games::gitadora {
|
||||
// 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;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "iidx.h"
|
||||
@@ -82,7 +83,8 @@ bool games::iidx::IIDXFMSerialHandle::FMSerialDevice::parse_msg(
|
||||
}
|
||||
|
||||
// sleep - otherwise the IO thread will go too hard on the CPU
|
||||
Sleep(1);
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(1);
|
||||
|
||||
// generate message
|
||||
auto msg = this->create_msg(msg_in, 0x2E);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "iidx.h"
|
||||
@@ -82,7 +83,8 @@ bool games::iidx::BI2XSerialHandle::BI2XDevice::parse_msg(
|
||||
}
|
||||
|
||||
// sleep - otherwise the IO thread will go too hard on the CPU
|
||||
Sleep(1);
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
timer.sleep(1);
|
||||
|
||||
// generate message
|
||||
auto msg = this->create_msg(msg_in, 0x2E);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "camera.h"
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <d3d9.h>
|
||||
#include "mf_wrappers.h"
|
||||
@@ -84,7 +84,7 @@ namespace games::iidx {
|
||||
|
||||
bool find_camera_hooks() {
|
||||
std::string dll_name = avs::game::DLL_NAME;
|
||||
dll_path = MODULE_PATH / dll_name;
|
||||
dll_path = MODULE_PATH / dll_name;
|
||||
|
||||
iidx_module = libutils::try_module(dll_path);
|
||||
if (!iidx_module) {
|
||||
@@ -149,7 +149,7 @@ namespace games::iidx {
|
||||
"E800000000488BC8488BD34883C420",
|
||||
"X????XXXXXXXXXX",
|
||||
0, 0));
|
||||
|
||||
|
||||
if (addr_textures_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 1)");
|
||||
return FALSE;
|
||||
@@ -232,7 +232,7 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
static void **__fastcall camera_hook_a(PBYTE a1) {
|
||||
std::call_once(hook_a_init, [&]{
|
||||
std::call_once(hook_a_init, [&]{
|
||||
device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset);
|
||||
auto const preview = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
|
||||
auto const manager = reinterpret_cast<Camera::CCameraManager2*>((uint8_t*)iidx_module + addr_camera_manager);
|
||||
@@ -298,11 +298,11 @@ namespace games::iidx {
|
||||
// Ask for source type = video capture devices.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pAttributes->SetGUID(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Enumerate devices.
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = WrappedMFEnumDeviceSources(pAttributes, &ppDevices, &numDevices);
|
||||
@@ -478,7 +478,7 @@ namespace games::iidx {
|
||||
init_mf_library();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <vector>
|
||||
#include "games/iidx/local_camera.h"
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "util/memutils.h"
|
||||
#include "util/sigscan.h"
|
||||
#include "util/socd_cleaner.h"
|
||||
#include "util/sysutils.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
#include "launcher/signal.h"
|
||||
@@ -66,6 +67,8 @@ namespace games::iidx {
|
||||
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
|
||||
std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt;
|
||||
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||
uint32_t TT_DELAY_P1 = 0;
|
||||
uint32_t TT_DELAY_P2 = 0;
|
||||
uint8_t DIGITAL_TT_SENS = 4;
|
||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt;
|
||||
std::optional<std::string> SCREEN_MODE = std::nullopt;
|
||||
@@ -82,6 +85,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+
|
||||
@@ -422,6 +426,14 @@ namespace games::iidx {
|
||||
init_legacy_camera_hook(FLIP_CAMS);
|
||||
}
|
||||
|
||||
#if SPICE64
|
||||
|
||||
if (TDJ_MODE) {
|
||||
sysutils::hook_EnumDisplayDevicesA();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// init cfgmgr32 hooks
|
||||
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
|
||||
}
|
||||
@@ -453,7 +465,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;
|
||||
}
|
||||
|
||||
@@ -472,7 +484,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"
|
||||
@@ -485,28 +497,28 @@ namespace games::iidx {
|
||||
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
|
||||
}
|
||||
|
||||
// check -monitor + TDJ mode
|
||||
// check -dxmainadapter + TDJ mode
|
||||
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
|
||||
IIDX_TDJ_MONITOR_WARNING = true;
|
||||
log_warning(
|
||||
"iidx",
|
||||
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
|
||||
"!!! compatibility issues with the game !!!\n"
|
||||
"\n\n"
|
||||
"!!! using -dxmainadapter option with TDJ is NOT recommended !!!\n"
|
||||
"!!! due to known compatibility issues with the game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
|
||||
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
|
||||
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
|
||||
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
|
||||
"!!! option instead of -dxmainadapter !!!\n\n"
|
||||
);
|
||||
|
||||
deferredlogs::defer_error_messages({
|
||||
"-monitor option is NOT recommended when running with TDJ mode",
|
||||
"-dxmainadapter option is NOT recommended when running with TDJ mode",
|
||||
" due to known compatibility issues with the game:",
|
||||
" * game may launch in wrong resolution or refresh rate",
|
||||
" * touch / mouse input may stop working in subscreen / overlay",
|
||||
" recommended fix is to NOT use -monitor and instead set the primary",
|
||||
" monitor in Windows settings before launching the game"
|
||||
" use Change Main Monitor (-mainmonitor) option instead"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -771,7 +783,7 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
// return higher 8 bit
|
||||
return (uint8_t) (ret_value >> 2);
|
||||
return (uint8_t)(ret_value >> 2);
|
||||
}
|
||||
|
||||
unsigned char get_slider(uint8_t slider) {
|
||||
@@ -817,7 +829,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() {
|
||||
@@ -883,12 +895,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]
|
||||
@@ -905,8 +917,7 @@ namespace games::iidx {
|
||||
log_warning(
|
||||
"iidx",
|
||||
"Failed to apply ASIO compatibility fix for iidx32+. "
|
||||
"Unless patches are applied, your ASIO device ({}) may hang or fail to work",
|
||||
ASIO_DRIVER->c_str());
|
||||
"Unless patches are applied, your ASIO device may hang or fail to work");
|
||||
} else {
|
||||
log_info(
|
||||
"iidx",
|
||||
@@ -934,6 +945,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);
|
||||
}
|
||||
|
||||
+109
-106
@@ -63,16 +63,17 @@ std::vector<Analog> &games::iidx::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Turntable P1",
|
||||
"Turntable P2",
|
||||
"VEFX",
|
||||
"Low-EQ",
|
||||
"Hi-EQ",
|
||||
"Filter",
|
||||
"Play Volume"
|
||||
);
|
||||
using namespace GameAPI::Analogs;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Turntable P1", AnalogType::Circular },
|
||||
{ "Turntable P2", AnalogType::Circular },
|
||||
{ "VEFX", AnalogType::LinearPositive },
|
||||
{ "Low-EQ", AnalogType::LinearPositive },
|
||||
{ "Hi-EQ", AnalogType::LinearPositive },
|
||||
{ "Filter", AnalogType::LinearPositive },
|
||||
{ "Play Volume", AnalogType::LinearPositive }
|
||||
});
|
||||
}
|
||||
return analogs;
|
||||
}
|
||||
@@ -83,103 +84,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"}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "games/iidx/local_camera.h"
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include "mf_wrappers.h"
|
||||
|
||||
@@ -128,7 +129,7 @@ namespace games::iidx {
|
||||
// TODO: Color space conversion
|
||||
// if (SUCCEEDED(hr)) {
|
||||
// hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
|
||||
// }
|
||||
// }
|
||||
// if (SUCCEEDED(hr)) {
|
||||
// hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, FALSE);
|
||||
// }
|
||||
@@ -189,11 +190,11 @@ namespace games::iidx {
|
||||
&pType
|
||||
);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr)) {
|
||||
if (hr != MF_E_NO_MORE_TYPES) {
|
||||
log_warning("iidx:camhook", "[{}] Cannot get media type {} {:#x}", m_name, i, (ULONG)hr);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
hr = TryMediaType(pType, &bestWidth, &bestFrameRate);
|
||||
@@ -246,7 +247,7 @@ namespace games::iidx {
|
||||
pSelectedMediaType = m_pAutoMediaType;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = ChangeMediaType(pSelectedMediaType);
|
||||
}
|
||||
|
||||
@@ -281,7 +282,7 @@ namespace games::iidx {
|
||||
m_cameraHeight = info.height;
|
||||
UpdateDrawRect();
|
||||
}
|
||||
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
@@ -395,10 +396,11 @@ namespace games::iidx {
|
||||
|
||||
m_device->ColorFill(m_pDestSurf, &targetRect, D3DCOLOR_XRGB(0, 0, 0));
|
||||
}
|
||||
|
||||
|
||||
void IIDXLocalCamera::CreateThread() {
|
||||
// Create thread
|
||||
m_drawThread = new std::thread([this]() {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
|
||||
double accumulator = 0.0;
|
||||
@@ -411,8 +413,8 @@ namespace games::iidx {
|
||||
if (accumulator > 1.0) {
|
||||
accumulator -= 1.0;
|
||||
floorFrameTimeMicroSec += 1;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
|
||||
}
|
||||
timer.sleep(std::chrono::microseconds(floorFrameTimeMicroSec));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -430,12 +432,12 @@ namespace games::iidx {
|
||||
|
||||
log_misc("iidx:camhook", "[{}] Init camera control", m_name);
|
||||
|
||||
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
|
||||
if (FAILED(hr)) {
|
||||
// The device does not support IAMCameraControl
|
||||
hr = m_pSource->QueryInterface(IID_IAMCameraControl, (void**)&m_pCameraControl);
|
||||
if (FAILED(hr)) {
|
||||
// The device does not support IAMCameraControl
|
||||
log_warning("iidx:camhook", "[{}] Camera control not supported", m_name);
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CAMERA_CONTROL_PROP_SIZE; i++) {
|
||||
long minValue = 0;
|
||||
@@ -472,9 +474,9 @@ namespace games::iidx {
|
||||
CameraControlProp prop = m_controlProps.at(i);
|
||||
|
||||
log_misc(
|
||||
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
|
||||
"iidx:camhook", "[{}] >> {} range=({}, {}) default={} delta={} dFlags={} value={} vFlags={}",
|
||||
m_name,
|
||||
CAMERA_CONTROL_LABELS[i],
|
||||
CAMERA_CONTROL_LABELS[i],
|
||||
prop.minValue, prop.maxValue,
|
||||
prop.defaultValue,
|
||||
prop.delta,
|
||||
@@ -570,9 +572,9 @@ namespace games::iidx {
|
||||
|
||||
// Get frame rate
|
||||
hr = MFGetAttributeRatio(
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
(UINT32*)&frameRate.Denominator
|
||||
);
|
||||
if (FAILED(hr)) { goto done; }
|
||||
@@ -580,7 +582,7 @@ namespace games::iidx {
|
||||
info.frameRate = frameRate.Numerator / frameRate.Denominator;
|
||||
|
||||
info.description = fmt::format(
|
||||
"{}x{} @{}FPS {}",
|
||||
"{}x{} @{}FPS {}",
|
||||
info.width,
|
||||
info.height,
|
||||
(int)info.frameRate,
|
||||
@@ -606,7 +608,7 @@ namespace games::iidx {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Return values:
|
||||
* S_OK: this is a "better" media type than the existing one
|
||||
* S_FALSE: valid media type, but not "better"
|
||||
@@ -620,17 +622,17 @@ namespace games::iidx {
|
||||
|
||||
hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "[{}] Failed to get subtype: {:#x}", m_name, (ULONG)hr);
|
||||
return hr;
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "[{}] Failed to get frame size: {:#x}", m_name, (ULONG)hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
|
||||
// Only support format with converters
|
||||
// TODO: verify conversion support with DXVA
|
||||
if (subtype != MFVideoFormat_YUY2 && subtype != MFVideoFormat_NV12) {
|
||||
@@ -639,12 +641,12 @@ namespace games::iidx {
|
||||
|
||||
// Frame rate
|
||||
hr = MFGetAttributeRatio(
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
pType,
|
||||
MF_MT_FRAME_RATE,
|
||||
(UINT32*)&frameRate.Numerator,
|
||||
(UINT32*)&frameRate.Denominator
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr)) {
|
||||
log_warning("iidx:camhook", "[{}] Failed to get frame rate: {:#x}", m_name, (ULONG)hr);
|
||||
return hr;
|
||||
}
|
||||
@@ -667,7 +669,7 @@ namespace games::iidx {
|
||||
// Check if this format has better resolution / frame rate
|
||||
if ((width > *pBestWidth) || (width >= (UINT32)TARGET_SURFACE_WIDTH && frameRateValue >= *pBestFrameRate)) {
|
||||
// log_misc(
|
||||
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
|
||||
// "iidx:camhook", "Better media type {} ({}x{}) @({} FPS)",
|
||||
// GetVideoFormatName(subtype),
|
||||
// width,
|
||||
// height,
|
||||
@@ -730,7 +732,7 @@ namespace games::iidx {
|
||||
|
||||
HRESULT IIDXLocalCamera::FlushDrawCommands() {
|
||||
IDirect3DQuery9* pEventQuery = nullptr;
|
||||
// It is necessary to flush the command queue
|
||||
// It is necessary to flush the command queue
|
||||
// or the data is not ready for the receiver to read.
|
||||
// Adapted from : https://msdn.microsoft.com/en-us/library/windows/desktop/bb172234%28v=vs.85%29.aspx
|
||||
// Also see : http://www.ogre3d.org/forums/viewtopic.php?f=5&t=50486
|
||||
@@ -784,8 +786,8 @@ namespace games::iidx {
|
||||
for (int y = 0; y < TARGET_SURFACE_HEIGHT; y++) {
|
||||
for (int x = 0; x < TARGET_SURFACE_WIDTH; x++) {
|
||||
memcpy(
|
||||
pDest + x * pixelSize,
|
||||
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
|
||||
pDest + x * pixelSize,
|
||||
pSrc + (flip_h ? (TARGET_SURFACE_WIDTH - x - 1) : x) * pixelSize,
|
||||
pixelSize
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if SPICE64
|
||||
#if SPICE64 && !SPICE_XP
|
||||
|
||||
#include <d3d9.h>
|
||||
#include <dxva2api.h>
|
||||
@@ -89,7 +89,7 @@ namespace games::iidx {
|
||||
protected:
|
||||
virtual ~IIDXLocalCamera() {};
|
||||
|
||||
ULONG m_nRefCount;
|
||||
LONG m_nRefCount;
|
||||
CRITICAL_SECTION m_critsec;
|
||||
|
||||
std::string m_name;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "touch/touch.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
#define POKE_NATIVE_TOUCH 0
|
||||
|
||||
@@ -200,6 +201,7 @@ namespace games::iidx::poke {
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
|
||||
// log
|
||||
log_info("poke", "enabled");
|
||||
@@ -319,7 +321,7 @@ namespace games::iidx::poke {
|
||||
}
|
||||
|
||||
// slow down
|
||||
Sleep(50);
|
||||
timer.sleep(50);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
+64
-47
@@ -47,10 +47,11 @@ 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;
|
||||
static robin_hood::unordered_map<std::string, std::vector<std::string>> file_hints;
|
||||
static robin_hood::unordered_map<std::string, std::vector<std::vector<std::string>>> file_hints;
|
||||
|
||||
static void initialize() {
|
||||
|
||||
@@ -67,7 +68,7 @@ namespace games {
|
||||
buttons_help.insert({ bbc, bbc::get_buttons_help() });
|
||||
analogs.insert({ bbc, bbc::get_analogs() });
|
||||
lights.insert({ bbc, bbc::get_lights() });
|
||||
file_hints[bbc].emplace_back("bsch.dll");
|
||||
file_hints[bbc].push_back({"bsch.dll"});
|
||||
|
||||
// hpm
|
||||
const std::string hpm("HELLO! Pop'n Music");
|
||||
@@ -75,41 +76,45 @@ namespace games {
|
||||
buttons.insert({ hpm, hpm::get_buttons() });
|
||||
buttons_help.insert({ hpm, hpm::get_buttons_help() });
|
||||
lights.insert({ hpm, hpm::get_lights() });
|
||||
file_hints[hpm].emplace_back("popn.dll");
|
||||
file_hints[hpm].push_back({"popn.dll", "pdh.dll"});
|
||||
|
||||
// bs
|
||||
const std::string bs("Beatstream");
|
||||
games.push_back(bs);
|
||||
buttons.insert({ bs, bs::get_buttons() });
|
||||
lights.insert({ bs, bs::get_lights() });
|
||||
file_hints[bs].emplace_back("beatstream.dll");
|
||||
file_hints[bs].emplace_back("beatstream1.dll");
|
||||
file_hints[bs].emplace_back("beatstream2.dll");
|
||||
file_hints[bs].push_back({"beatstream.dll"});
|
||||
file_hints[bs].push_back({"beatstream1.dll"});
|
||||
file_hints[bs].push_back({"beatstream2.dll"});
|
||||
|
||||
// ddr
|
||||
const std::string ddr("Dance Dance Revolution");
|
||||
games.push_back(ddr);
|
||||
buttons.insert({ ddr, ddr::get_buttons() });
|
||||
lights.insert({ ddr, ddr::get_lights() });
|
||||
file_hints[ddr].emplace_back("ddr.dll");
|
||||
file_hints[ddr].emplace_back("mdxja_945.dll");
|
||||
file_hints[ddr].emplace_back("arkmdxp3.dll");
|
||||
buttons_help.insert({ ddr, ddr::get_buttons_help() });
|
||||
analogs_help.insert({ ddr, ddr::get_analogs_help() });
|
||||
analogs.insert({ ddr, ddr::get_analogs() });
|
||||
file_hints[ddr].push_back({"ddr.dll"});
|
||||
file_hints[ddr].push_back({"mdxja_945.dll"});
|
||||
file_hints[ddr].push_back({"arkmdxp3.dll"});
|
||||
|
||||
// dea
|
||||
const std::string dea("Dance Evolution");
|
||||
games.push_back(dea);
|
||||
buttons.insert({ dea, dea::get_buttons() });
|
||||
lights.insert({ dea, dea::get_lights() });
|
||||
file_hints[dea].emplace_back("arkkdm.dll");
|
||||
file_hints[dea].push_back({"arkkdm.dll"});
|
||||
|
||||
// gitadora
|
||||
const std::string gitadora("GitaDora");
|
||||
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");
|
||||
file_hints[gitadora].push_back({"gdxg.dll"});
|
||||
|
||||
// iidx
|
||||
const std::string iidx("Beatmania IIDX");
|
||||
@@ -118,7 +123,7 @@ namespace games {
|
||||
buttons_help.insert({ iidx, iidx::get_buttons_help() });
|
||||
analogs.insert({ iidx, iidx::get_analogs() });
|
||||
lights.insert({ iidx, iidx::get_lights() });
|
||||
file_hints[iidx].emplace_back("bm2dx.dll");
|
||||
file_hints[iidx].push_back({"bm2dx.dll"});
|
||||
|
||||
// jb
|
||||
const std::string jb("Jubeat");
|
||||
@@ -126,7 +131,7 @@ namespace games {
|
||||
buttons.insert({ jb, jb::get_buttons() });
|
||||
buttons_help.insert({ jb, jb::get_buttons_help() });
|
||||
lights.insert({ jb, jb::get_lights() });
|
||||
file_hints[jb].emplace_back("jubeat.dll");
|
||||
file_hints[jb].push_back({"jubeat.dll"});
|
||||
|
||||
// mga
|
||||
const std::string mga("Metal Gear");
|
||||
@@ -134,7 +139,7 @@ namespace games {
|
||||
buttons.insert({ mga, mga::get_buttons() });
|
||||
analogs.insert({ mga, mga::get_analogs() });
|
||||
lights.insert({ mga, mga::get_lights() });
|
||||
file_hints[mga].emplace_back("launch.dll");
|
||||
file_hints[mga].push_back({"launch.dll"});
|
||||
|
||||
// museca
|
||||
const std::string museca("Museca");
|
||||
@@ -143,7 +148,7 @@ namespace games {
|
||||
buttons_help.insert({ museca, museca::get_buttons_help() });
|
||||
analogs.insert({ museca, museca::get_analogs() });
|
||||
lights.insert({ museca, museca::get_lights() });
|
||||
file_hints[museca].emplace_back("museca.dll");
|
||||
file_hints[museca].push_back({"museca.dll"});
|
||||
|
||||
// nost
|
||||
const std::string nost("Nostalgia");
|
||||
@@ -152,7 +157,7 @@ namespace games {
|
||||
buttons_help.insert({ nost, nost::get_buttons_help() });
|
||||
analogs.insert({ nost, nost::get_analogs() });
|
||||
lights.insert({ nost, nost::get_lights() });
|
||||
file_hints[nost].emplace_back("nostalgia.dll");
|
||||
file_hints[nost].push_back({"nostalgia.dll"});
|
||||
|
||||
// popn
|
||||
const std::string popn("Pop'n Music");
|
||||
@@ -160,41 +165,42 @@ namespace games {
|
||||
buttons.insert({ popn, popn::get_buttons() });
|
||||
buttons_help.insert({ popn, popn::get_buttons_help() });
|
||||
lights.insert({ popn, popn::get_lights() });
|
||||
file_hints[popn].emplace_back("popn19.dll");
|
||||
file_hints[popn].emplace_back("popn20.dll");
|
||||
file_hints[popn].emplace_back("popn21.dll");
|
||||
file_hints[popn].emplace_back("popn22.dll");
|
||||
file_hints[popn].emplace_back("popn23.dll");
|
||||
file_hints[popn].emplace_back("popn24.dll");
|
||||
file_hints[popn].emplace_back("popn25.dll");
|
||||
file_hints[popn].push_back({"popn19.dll"});
|
||||
file_hints[popn].push_back({"popn20.dll"});
|
||||
file_hints[popn].push_back({"popn21.dll"});
|
||||
file_hints[popn].push_back({"popn22.dll"});
|
||||
file_hints[popn].push_back({"popn23.dll"});
|
||||
file_hints[popn].push_back({"popn24.dll"});
|
||||
file_hints[popn].push_back({"popn25.dll"});
|
||||
file_hints[popn].push_back({"popn.dll", "libaio-iob2_video.dll"});
|
||||
|
||||
// qma
|
||||
const std::string qma("Quiz Magic Academy");
|
||||
games.push_back(qma);
|
||||
buttons.insert({ qma, qma::get_buttons() });
|
||||
lights.insert({ qma, qma::get_lights() });
|
||||
file_hints[qma].emplace_back("client.dll");
|
||||
file_hints[qma].push_back({"client.dll"});
|
||||
|
||||
// rb
|
||||
const std::string rb("Reflec Beat");
|
||||
games.push_back(rb);
|
||||
buttons.insert({ rb, rb::get_buttons() });
|
||||
lights.insert({ rb, rb::get_lights() });
|
||||
file_hints[rb].emplace_back("reflecbeat.dll");
|
||||
file_hints[rb].push_back({"reflecbeat.dll"});
|
||||
|
||||
// shogikai
|
||||
std::string shogikai("Tenkaichi Shogikai");
|
||||
games.push_back(shogikai);
|
||||
buttons.insert({ shogikai, shogikai::get_buttons() });
|
||||
lights.insert({ shogikai, shogikai::get_lights() });
|
||||
file_hints[shogikai].emplace_back("shogi_engine.dll");
|
||||
file_hints[shogikai].push_back({"shogi_engine.dll"});
|
||||
|
||||
// rf3d
|
||||
const std::string rf3d("Road Fighters 3D");
|
||||
games.push_back(rf3d);
|
||||
buttons.insert({ rf3d, rf3d::get_buttons() });
|
||||
analogs.insert({ rf3d, rf3d::get_analogs() });
|
||||
file_hints[rf3d].emplace_back("jgt.dll");
|
||||
file_hints[rf3d].push_back({"jgt.dll"});
|
||||
|
||||
// sc
|
||||
const std::string sc("Steel Chronicle");
|
||||
@@ -202,7 +208,7 @@ namespace games {
|
||||
buttons.insert({ sc, sc::get_buttons() });
|
||||
analogs.insert({ sc, sc::get_analogs() });
|
||||
lights.insert({ sc, sc::get_lights() });
|
||||
file_hints[sc].emplace_back("gamekgg.dll");
|
||||
file_hints[sc].push_back({"gamekgg.dll"});
|
||||
|
||||
// sdvx
|
||||
const std::string sdvx("Sound Voltex");
|
||||
@@ -211,13 +217,13 @@ namespace games {
|
||||
buttons_help.insert({ sdvx, sdvx::get_buttons_help() });
|
||||
analogs.insert({ sdvx, sdvx::get_analogs() });
|
||||
lights.insert({ sdvx, sdvx::get_lights() });
|
||||
file_hints[sdvx].emplace_back("soundvoltex.dll");
|
||||
file_hints[sdvx].push_back({"soundvoltex.dll"});
|
||||
|
||||
// mfc
|
||||
const std::string mfc("Mahjong Fight Club");
|
||||
games.push_back(mfc);
|
||||
buttons.insert({ mfc, mfc::get_buttons() });
|
||||
file_hints[mfc].emplace_back("allinone.dll");
|
||||
file_hints[mfc].push_back({"allinone.dll"});
|
||||
|
||||
// ftt
|
||||
const std::string ftt("FutureTomTom");
|
||||
@@ -226,21 +232,21 @@ namespace games {
|
||||
buttons_help.insert({ ftt, ftt::get_buttons_help() });
|
||||
analogs.insert({ ftt, ftt::get_analogs() });
|
||||
lights.insert({ ftt, ftt::get_lights() });
|
||||
file_hints[ftt].emplace_back("arkmmd.dll");
|
||||
file_hints[ftt].push_back({"arkmmd.dll"});
|
||||
|
||||
// loveplus
|
||||
const std::string loveplus("LovePlus");
|
||||
games.push_back(loveplus);
|
||||
buttons.insert({ loveplus, loveplus::get_buttons() });
|
||||
lights.insert({ loveplus, loveplus::get_lights() });
|
||||
file_hints[loveplus].emplace_back("arkklp.dll");
|
||||
file_hints[loveplus].push_back({"arkklp.dll"});
|
||||
|
||||
// scotto
|
||||
const std::string scotto("Scotto");
|
||||
games.push_back(scotto);
|
||||
buttons.insert({ scotto, scotto::get_buttons() });
|
||||
lights.insert({ scotto, scotto::get_lights() });
|
||||
file_hints[scotto].emplace_back("scotto.dll");
|
||||
file_hints[scotto].push_back({"scotto.dll"});
|
||||
|
||||
// drs
|
||||
const std::string drs("DANCERUSH");
|
||||
@@ -248,13 +254,14 @@ namespace games {
|
||||
buttons.insert({ drs, drs::get_buttons() });
|
||||
buttons_help.insert({ drs, drs::get_buttons_help() });
|
||||
lights.insert({ drs, drs::get_lights() });
|
||||
file_hints[drs].emplace_back("superstep.dll");
|
||||
file_hints[drs].push_back({"superstep.dll"});
|
||||
|
||||
// otoca
|
||||
const std::string otoca("Otoca D'or");
|
||||
games.push_back(otoca);
|
||||
buttons.insert({ otoca, otoca::get_buttons() });
|
||||
file_hints[otoca].emplace_back("arkkep.dll");
|
||||
buttons_help.insert({ otoca, otoca::get_buttons_help() });
|
||||
file_hints[otoca].push_back({"arkkep.dll"});
|
||||
|
||||
// winning eleven
|
||||
const std::string we("Winning Eleven");
|
||||
@@ -262,27 +269,27 @@ namespace games {
|
||||
buttons.insert({ we, we::get_buttons() });
|
||||
analogs.insert({ we, we::get_analogs() });
|
||||
lights.insert({ we, we::get_lights() });
|
||||
file_hints[we].emplace_back("weac12_bootstrap_release.dll");
|
||||
file_hints[we].emplace_back("arknck.dll");
|
||||
file_hints[we].push_back({"weac12_bootstrap_release.dll"});
|
||||
file_hints[we].push_back({"arknck.dll"});
|
||||
|
||||
// silent scope: bone eater
|
||||
const std::string silentscope("Silent Scope: Bone Eater");
|
||||
games.push_back(silentscope);
|
||||
buttons.insert({ silentscope, silentscope::get_buttons() });
|
||||
analogs.insert({ silentscope, silentscope::get_analogs() });
|
||||
file_hints[silentscope].emplace_back("arkndd.dll");
|
||||
file_hints[silentscope].push_back({"arkndd.dll"});
|
||||
|
||||
// charge machine
|
||||
const std::string pcm("Charge Machine");
|
||||
games.push_back(pcm);
|
||||
buttons.insert({ pcm, pcm::get_buttons() });
|
||||
file_hints[pcm].emplace_back("launch.dll");
|
||||
file_hints[pcm].push_back({"launch.dll"});
|
||||
|
||||
// ongaku paradise
|
||||
const std::string op("Ongaku Paradise");
|
||||
games.push_back(op);
|
||||
buttons.insert({ op, onpara::get_buttons() });
|
||||
file_hints[op].emplace_back("arkjc9.dll");
|
||||
file_hints[op].push_back({"arkjc9.dll"});
|
||||
|
||||
// bc
|
||||
const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
|
||||
@@ -290,7 +297,7 @@ namespace games {
|
||||
buttons.insert({ bc, bc::get_buttons() });
|
||||
buttons_help.insert({ bc, bc::get_buttons_help() });
|
||||
analogs.insert({ bc, bc::get_analogs() });
|
||||
file_hints[bc].emplace_back("game/bsac_app.exe");
|
||||
file_hints[bc].push_back({"game/bsac_app.exe"});
|
||||
|
||||
// ccj
|
||||
const std::string ccj("Chase Chase Jokers");
|
||||
@@ -299,21 +306,21 @@ namespace games {
|
||||
buttons_help.insert({ ccj, ccj::get_buttons_help() });
|
||||
analogs.insert({ ccj, ccj::get_analogs() });
|
||||
lights.insert({ ccj, ccj::get_lights() });
|
||||
file_hints[ccj].emplace_back("game/chaseproject.exe");
|
||||
file_hints[ccj].push_back({"game/chaseproject.exe"});
|
||||
|
||||
// QuizKnock STADIUM
|
||||
const std::string qks("QuizKnock STADIUM");
|
||||
games.push_back(qks);
|
||||
buttons.insert({ qks, qks::get_buttons() });
|
||||
buttons_help.insert({ qks, qks::get_buttons_help() });
|
||||
file_hints[qks].emplace_back("game/uks.exe");
|
||||
file_hints[qks].push_back({"game/uks.exe"});
|
||||
|
||||
// Mahjong Fight Girl
|
||||
const std::string mfg("Mahjong Fight Girl");
|
||||
games.push_back(mfg);
|
||||
buttons.insert({ mfg, mfg::get_buttons() });
|
||||
buttons_help.insert({ mfg, mfg::get_buttons_help() });
|
||||
file_hints[mfg].emplace_back("game/MFGClient_Data");
|
||||
file_hints[mfg].push_back({"game/MFGClient_Data"});
|
||||
|
||||
// Polaris Chord
|
||||
const std::string pc("Polaris Chord");
|
||||
@@ -322,7 +329,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].push_back({"game/svm.exe"});
|
||||
}
|
||||
|
||||
const std::vector<std::string> &get_games() {
|
||||
@@ -349,6 +356,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 +405,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);
|
||||
@@ -546,7 +563,7 @@ namespace games {
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
std::vector<std::string> *get_game_file_hints(const std::string &game) {
|
||||
std::vector<std::vector<std::string>> *get_game_file_hints(const std::string &game) {
|
||||
initialize();
|
||||
auto it = file_hints.find(game);
|
||||
if (it == file_hints.end()) {
|
||||
|
||||
@@ -58,10 +58,11 @@ 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);
|
||||
std::vector<Light> *get_lights(const std::string &game);
|
||||
std::vector<Option> *get_options(const std::string &game);
|
||||
std::vector<std::string> *get_game_file_hints(const std::string &game);
|
||||
std::vector<std::vector<std::string>> *get_game_file_hints(const std::string &game);
|
||||
}
|
||||
|
||||
+86
-13
@@ -19,7 +19,7 @@
|
||||
namespace games::jb {
|
||||
|
||||
// touch stuff
|
||||
bool TOUCH_LEGACY_BOX = false;
|
||||
JubeatTouchAlgorithm TOUCH_ALGORITHM = Improved;
|
||||
static bool TOUCH_ENABLE = false;
|
||||
static bool TOUCH_ATTACHED = false;
|
||||
static bool IS_PORTRAIT = true;
|
||||
@@ -78,7 +78,7 @@ namespace games::jb {
|
||||
// note that the IO code in device.cpp will correctly compensate for orientation, depending on the model.
|
||||
TOUCH_POINTS.clear();
|
||||
touch_get_points(TOUCH_POINTS);
|
||||
if (TOUCH_LEGACY_BOX) {
|
||||
if (TOUCH_ALGORITHM == Legacy) {
|
||||
auto offset = IS_PORTRAIT ? 580 : 0;
|
||||
for (auto &tp : TOUCH_POINTS) {
|
||||
|
||||
@@ -94,6 +94,17 @@ namespace games::jb {
|
||||
}
|
||||
} else {
|
||||
for (auto &tp : TOUCH_POINTS) {
|
||||
// check window out of bounds
|
||||
if (IS_PORTRAIT) {
|
||||
if (tp.x > 768 || tp.y > 1360) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (tp.x > 1360 || tp.y > 768) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int x_relative = tp.x;
|
||||
int y_relative = tp.y;
|
||||
|
||||
@@ -110,19 +121,66 @@ namespace games::jb {
|
||||
y_relative -= 8;
|
||||
}
|
||||
|
||||
if (x_relative < 0 || y_relative < 0) {
|
||||
int x_index = -1;
|
||||
int x_hitbox = 0;
|
||||
int y_index = -1;
|
||||
int y_hitbox = 0;
|
||||
|
||||
// x_hitbox and y_hitbox is relative to top-left pixel of each button
|
||||
if (x_relative >= 0) {
|
||||
x_index = x_relative / JB_BUTTON_HITBOX;
|
||||
x_hitbox = x_relative % JB_BUTTON_HITBOX;
|
||||
}
|
||||
if (y_relative >= 0) {
|
||||
y_index = y_relative / JB_BUTTON_HITBOX;
|
||||
y_hitbox = y_relative % JB_BUTTON_HITBOX;
|
||||
}
|
||||
|
||||
// log_info("jb", "raw={}, idx={}, hitbox={}", tp.x, x_index, x_hitbox);
|
||||
|
||||
if (TOUCH_ALGORITHM == Improved) {
|
||||
if (IS_PORTRAIT) {
|
||||
// extend to left border
|
||||
if (x_relative < 0) {
|
||||
x_index = 0;
|
||||
}
|
||||
// right and bottom borders are covered by the hit box
|
||||
} else {
|
||||
// extend to top border
|
||||
if (y_relative < 0) {
|
||||
y_index = 0;
|
||||
}
|
||||
// extend to left border
|
||||
if (x_relative < 0) {
|
||||
x_index = 0;
|
||||
}
|
||||
// bottom border is covered by the hit box
|
||||
// rightmost edge - ignore them entirely
|
||||
if (x_index == 3 && JB_BUTTON_SIZE < x_hitbox) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (x_index < 0 || y_index < 0 || x_index > 3 || y_index > 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// x_hitbox and y_hitbox is relative to top-left pixel of each button
|
||||
int x_index = x_relative / JB_BUTTON_HITBOX;
|
||||
int x_hitbox = x_relative % JB_BUTTON_HITBOX;
|
||||
int y_index = y_relative / JB_BUTTON_HITBOX;
|
||||
int y_hitbox = y_relative % JB_BUTTON_HITBOX;
|
||||
|
||||
// check if the gap was touched
|
||||
if (x_hitbox > JB_BUTTON_SIZE || y_hitbox > JB_BUTTON_SIZE) {
|
||||
continue;
|
||||
if (TOUCH_ALGORITHM == AcAccurate) {
|
||||
// in ac-accurate mode, touching the gap is ignored
|
||||
if (x_hitbox > JB_BUTTON_SIZE || y_hitbox > JB_BUTTON_SIZE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
} else if (TOUCH_ALGORITHM == Improved) {
|
||||
// in improved mode, touching the gap triggers the closest button
|
||||
if (x_index <= 2 && (JB_BUTTON_SIZE + JB_BUTTON_GAP / 2) < x_hitbox) {
|
||||
x_index++;
|
||||
}
|
||||
if (y_index <= 2 && (JB_BUTTON_SIZE + JB_BUTTON_GAP / 2) < y_hitbox) {
|
||||
y_index++;
|
||||
}
|
||||
}
|
||||
|
||||
// set the corresponding state
|
||||
@@ -167,7 +225,7 @@ namespace games::jb {
|
||||
void JBGame::pre_attach() {
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
const auto current_path = std::filesystem::current_path();
|
||||
log_misc("jubeat", "current working directory: {}", current_path.string());
|
||||
log_misc("jubeat", "current working directory: {}", current_path);
|
||||
if (current_path.parent_path() == current_path.root_path()) {
|
||||
log_warning(
|
||||
"jubeat",
|
||||
@@ -178,7 +236,7 @@ namespace games::jb {
|
||||
" c:\\jubeat\\contents\\spice.exe <- OK\n\n"
|
||||
"To fix this, create a new directory and move ALL game files there.\n\n"
|
||||
"Your current working directory: {}\n",
|
||||
current_path.string());
|
||||
current_path);
|
||||
|
||||
log_fatal(
|
||||
"jubeat",
|
||||
@@ -193,6 +251,21 @@ namespace games::jb {
|
||||
// enable touch
|
||||
TOUCH_ENABLE = true;
|
||||
|
||||
switch (TOUCH_ALGORITHM) {
|
||||
case Legacy:
|
||||
log_info("jubeat", "using 'legacy' touch targets");
|
||||
break;
|
||||
case Improved:
|
||||
log_info("jubeat", "using 'improved' touch targets");
|
||||
break;
|
||||
case AcAccurate:
|
||||
log_info("jubeat", "using 'ac accurate' touch targets");
|
||||
break;
|
||||
default:
|
||||
log_fatal("jubeat", "unknown touch algo detected in touch_update, this is a bug");
|
||||
break;
|
||||
}
|
||||
|
||||
// enable debug logging of gftools
|
||||
HMODULE gftools = libutils::try_module("gftools.dll");
|
||||
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
||||
|
||||
@@ -4,8 +4,14 @@
|
||||
|
||||
namespace games::jb {
|
||||
|
||||
enum JubeatTouchAlgorithm {
|
||||
Legacy,
|
||||
Improved,
|
||||
AcAccurate
|
||||
};
|
||||
|
||||
// touch stuff
|
||||
extern bool TOUCH_LEGACY_BOX;
|
||||
extern JubeatTouchAlgorithm TOUCH_ALGORITHM;
|
||||
extern bool TOUCH_STATE[16];
|
||||
void touch_update();
|
||||
|
||||
|
||||
@@ -36,11 +36,11 @@ std::vector<Analog> &games::mga::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Joy X",
|
||||
"Joy Y"
|
||||
);
|
||||
using namespace GameAPI::Analogs;
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Joy X", AnalogType::LinearCentered },
|
||||
{ "Joy Y", AnalogType::LinearCentered }
|
||||
});
|
||||
}
|
||||
|
||||
return analogs;
|
||||
|
||||
@@ -165,37 +165,38 @@ std::vector<Analog> &games::nost::get_analogs() {
|
||||
if (analogs.empty()) {
|
||||
analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
|
||||
|
||||
GameAPI::Analogs::sortAnalogs(
|
||||
&analogs,
|
||||
"Key 1",
|
||||
"Key 2",
|
||||
"Key 3",
|
||||
"Key 4",
|
||||
"Key 5",
|
||||
"Key 6",
|
||||
"Key 7",
|
||||
"Key 8",
|
||||
"Key 9",
|
||||
"Key 10",
|
||||
"Key 11",
|
||||
"Key 12",
|
||||
"Key 13",
|
||||
"Key 14",
|
||||
"Key 15",
|
||||
"Key 16",
|
||||
"Key 17",
|
||||
"Key 18",
|
||||
"Key 19",
|
||||
"Key 20",
|
||||
"Key 21",
|
||||
"Key 22",
|
||||
"Key 23",
|
||||
"Key 24",
|
||||
"Key 25",
|
||||
"Key 26",
|
||||
"Key 27",
|
||||
"Key 28"
|
||||
);
|
||||
using namespace GameAPI::Analogs;
|
||||
|
||||
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||
{ "Key 1", AnalogType::LinearPositive },
|
||||
{ "Key 2", AnalogType::LinearPositive },
|
||||
{ "Key 3", AnalogType::LinearPositive },
|
||||
{ "Key 4", AnalogType::LinearPositive },
|
||||
{ "Key 5", AnalogType::LinearPositive },
|
||||
{ "Key 6", AnalogType::LinearPositive },
|
||||
{ "Key 7", AnalogType::LinearPositive },
|
||||
{ "Key 8", AnalogType::LinearPositive },
|
||||
{ "Key 9", AnalogType::LinearPositive },
|
||||
{ "Key 10", AnalogType::LinearPositive },
|
||||
{ "Key 11", AnalogType::LinearPositive },
|
||||
{ "Key 12", AnalogType::LinearPositive },
|
||||
{ "Key 13", AnalogType::LinearPositive },
|
||||
{ "Key 14", AnalogType::LinearPositive },
|
||||
{ "Key 15", AnalogType::LinearPositive },
|
||||
{ "Key 16", AnalogType::LinearPositive },
|
||||
{ "Key 17", AnalogType::LinearPositive },
|
||||
{ "Key 18", AnalogType::LinearPositive },
|
||||
{ "Key 19", AnalogType::LinearPositive },
|
||||
{ "Key 20", AnalogType::LinearPositive },
|
||||
{ "Key 21", AnalogType::LinearPositive },
|
||||
{ "Key 22", AnalogType::LinearPositive },
|
||||
{ "Key 23", AnalogType::LinearPositive },
|
||||
{ "Key 24", AnalogType::LinearPositive },
|
||||
{ "Key 25", AnalogType::LinearPositive },
|
||||
{ "Key 26", AnalogType::LinearPositive },
|
||||
{ "Key 27", AnalogType::LinearPositive },
|
||||
{ "Key 28", AnalogType::LinearPositive }
|
||||
});
|
||||
}
|
||||
return analogs;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "misc/eamuse.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
|
||||
namespace games::nost::poke {
|
||||
|
||||
@@ -63,6 +64,7 @@ namespace games::nost::poke {
|
||||
// create new thread
|
||||
THREAD_RUNNING = true;
|
||||
THREAD = new std::thread([] {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
const DWORD touch_id = (DWORD)(0xFFFFFFFE);
|
||||
|
||||
const int swipe_anim_total_frames = 6;
|
||||
@@ -178,7 +180,7 @@ namespace games::nost::poke {
|
||||
}
|
||||
|
||||
// slow down
|
||||
Sleep(30);
|
||||
timer.sleep(30);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "touchpanel.h"
|
||||
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
@@ -44,6 +45,9 @@ bool games::onpara::TouchPanelHandle::open(LPCWSTR lpFileName) {
|
||||
}
|
||||
|
||||
int games::onpara::TouchPanelHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
|
||||
|
||||
static thread_local timeutils::PreciseSleepTimer timer;
|
||||
|
||||
DWORD i;
|
||||
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
|
||||
|
||||
@@ -67,7 +71,7 @@ int games::onpara::TouchPanelHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesT
|
||||
enqueue_packet(report);
|
||||
|
||||
// prevent cpu bullying
|
||||
std::this_thread::sleep_for(1ms);
|
||||
timer.sleep(1);
|
||||
}
|
||||
|
||||
// copy from output queue
|
||||
@@ -133,4 +137,4 @@ bool games::onpara::TouchPanelHandle::close() {
|
||||
log_info("touchpanel", "Closed COM1 (Touch Panel)");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user