mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
Compare commits
178 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf86fbd238 | |||
| 3fdb369128 | |||
| db7defff5a | |||
| 451cbec0b9 | |||
| 5c69e295ab | |||
| 5065a92d55 | |||
| 5483e8e2c0 | |||
| e8949a2612 | |||
| db71a0b24d | |||
| d3d5422768 | |||
| 5185f753e5 | |||
| eb897d0f1b | |||
| 50fc80a1c3 | |||
| 301e15c44d | |||
| b6f886a7c5 | |||
| 0b085286d3 | |||
| 29d3d9bb52 | |||
| bf05a3ef30 | |||
| 6280ac3b0a | |||
| cc9df27569 | |||
| ac51acdafe | |||
| 6fec169347 | |||
| 4af006e869 | |||
| 256ef4d341 | |||
| 698f2ffc2c | |||
| 4879a35eb8 | |||
| fd7e850789 | |||
| 0cf07c38da | |||
| 1f073b2d75 | |||
| 3037214932 | |||
| 18d6d9783a | |||
| 267add3227 | |||
| a170627f52 | |||
| 5c244eaca7 | |||
| 6bcadccf09 | |||
| ccb3bac48c | |||
| 0a7ecf82a9 | |||
| 1c6929dd84 | |||
| 1d3d9040f2 | |||
| 8f1069628e | |||
| f8c585a9d1 | |||
| 9651051990 | |||
| a54a62d0d7 | |||
| 100caa0f5c | |||
| bb87aa2944 | |||
| 2e2968d1bc | |||
| 0457262472 | |||
| f23c359114 | |||
| 81e74dcb97 | |||
| 8ead941c6c | |||
| ece03cabba | |||
| 4b7f68f920 | |||
| 2dae86a6f2 | |||
| 85058c2156 | |||
| ea2f4c5572 | |||
| 48033816a8 | |||
| b517ef3182 | |||
| 6fef16353e | |||
| 84ea3f9e8e | |||
| 6bb6b0a301 | |||
| 465d6c6c18 | |||
| ef384e85dd | |||
| 7ee04879e2 | |||
| 4c73200f58 | |||
| 31aabe9786 | |||
| 4ea55a61b8 | |||
| 0209b80a22 | |||
| 0511dfb6ca | |||
| b558df3340 | |||
| 89602cfcab | |||
| b4557993c9 | |||
| f119d7c988 | |||
| b3d8d0aea9 | |||
| a977cba772 | |||
| 489d40d87d | |||
| 3a468a9a3b | |||
| a2e220d3f4 | |||
| febc02b50b | |||
| 311404f56b | |||
| d929cdf7c8 | |||
| 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 |
@@ -11,9 +11,7 @@ assignees: ''
|
|||||||
*name of game, version of game*
|
*name of game, version of game*
|
||||||
|
|
||||||
## Version of spice2x
|
## Version of spice2x
|
||||||
*version of spice, this can be seen in the About tab, and in log.txt*
|
*please remember to test the LATEST beta version before posting a bug about it*
|
||||||
|
|
||||||
*please remember to test the LATEST beta version before posting a bug about it**
|
|
||||||
|
|
||||||
## Describe the issue
|
## Describe the issue
|
||||||
*what's the issue?*
|
*what's the issue?*
|
||||||
@@ -22,3 +20,7 @@ assignees: ''
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> Please make sure you remove any personally identifiable information from the log file.
|
> Please make sure you remove any personally identifiable information from the log file.
|
||||||
> Please set `-loglevel` to `all` before launching the game for more verbose logs.
|
> Please set `-loglevel` to `all` before launching the game for more verbose logs.
|
||||||
|
|
||||||
|
## A note on issue management
|
||||||
|
|
||||||
|
`Closed as not planned` means exactly that - it's closed, because it's not on our plans to address them any time soon. It doesn't mean your issue is invalid, it just means it's not a priority, and we can always revisit them later.
|
||||||
|
|||||||
+151
-21
@@ -235,6 +235,13 @@ add_compile_definitions(
|
|||||||
_WIN32_IE=0x0400
|
_WIN32_IE=0x0400
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# SPICE_XP: set by build_all.sh for the WinXP-compat toolchains. Gates out
|
||||||
|
# the DX11 overlay backend
|
||||||
|
option(SPICE_XP "Build for the WinXP-compat toolchain" OFF)
|
||||||
|
if(SPICE_XP)
|
||||||
|
add_compile_definitions(SPICE_XP=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
# acioemu log
|
# acioemu log
|
||||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DACIOEMU_LOG")
|
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DACIOEMU_LOG")
|
||||||
|
|
||||||
@@ -266,6 +273,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/icon.rc PROPERTIES LANGUAGE RC)
|
||||||
set_source_files_properties(cfg/Win32D.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(build/manifest64.rc PROPERTIES LANGUAGE RC)
|
||||||
|
set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC)
|
||||||
|
|
||||||
# sources
|
# sources
|
||||||
#########
|
#########
|
||||||
@@ -382,6 +390,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
games/shared/twtouch.cpp
|
games/shared/twtouch.cpp
|
||||||
games/popn/popn.cpp
|
games/popn/popn.cpp
|
||||||
games/popn/io.cpp
|
games/popn/io.cpp
|
||||||
|
games/popn/bi3a_hook.cpp
|
||||||
games/bbc/bbc.cpp
|
games/bbc/bbc.cpp
|
||||||
games/bbc/io.cpp
|
games/bbc/io.cpp
|
||||||
games/hpm/hpm.cpp
|
games/hpm/hpm.cpp
|
||||||
@@ -399,6 +408,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
games/iidx/mf_wrappers.cpp
|
games/iidx/mf_wrappers.cpp
|
||||||
games/sdvx/bi2x_hook.cpp
|
games/sdvx/bi2x_hook.cpp
|
||||||
games/sdvx/sdvx.cpp
|
games/sdvx/sdvx.cpp
|
||||||
|
games/sdvx/sdvx_live2d.cpp
|
||||||
games/sdvx/io.cpp
|
games/sdvx/io.cpp
|
||||||
games/sdvx/camera.cpp
|
games/sdvx/camera.cpp
|
||||||
games/jb/jb.cpp
|
games/jb/jb.cpp
|
||||||
@@ -407,6 +417,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
games/nost/io.cpp
|
games/nost/io.cpp
|
||||||
games/nost/poke.cpp
|
games/nost/poke.cpp
|
||||||
games/gitadora/gitadora.cpp
|
games/gitadora/gitadora.cpp
|
||||||
|
games/gitadora/asio.cpp
|
||||||
games/gitadora/io.cpp
|
games/gitadora/io.cpp
|
||||||
games/gitadora/handle.cpp
|
games/gitadora/handle.cpp
|
||||||
games/gitadora/j32d.cpp
|
games/gitadora/j32d.cpp
|
||||||
@@ -484,6 +495,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
# hooks
|
# hooks
|
||||||
hooks/audio/acm.cpp
|
hooks/audio/acm.cpp
|
||||||
hooks/audio/audio.cpp
|
hooks/audio/audio.cpp
|
||||||
|
hooks/audio/asio_driver_scan.cpp
|
||||||
|
hooks/audio/asio_proxy.cpp
|
||||||
hooks/audio/buffer.cpp
|
hooks/audio/buffer.cpp
|
||||||
hooks/audio/mme.cpp
|
hooks/audio/mme.cpp
|
||||||
hooks/audio/util.cpp
|
hooks/audio/util.cpp
|
||||||
@@ -492,8 +505,13 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
hooks/audio/backends/mmdevice/device.cpp
|
hooks/audio/backends/mmdevice/device.cpp
|
||||||
hooks/audio/backends/mmdevice/device_collection.cpp
|
hooks/audio/backends/mmdevice/device_collection.cpp
|
||||||
hooks/audio/backends/mmdevice/device_enumerator.cpp
|
hooks/audio/backends/mmdevice/device_enumerator.cpp
|
||||||
|
hooks/audio/backends/mmdevice/null_device.cpp
|
||||||
|
hooks/audio/backends/mmdevice/null_discard_backend.cpp
|
||||||
hooks/audio/backends/wasapi/audio_client.cpp
|
hooks/audio/backends/wasapi/audio_client.cpp
|
||||||
hooks/audio/backends/wasapi/audio_render_client.cpp
|
hooks/audio/backends/wasapi/audio_render_client.cpp
|
||||||
|
hooks/audio/backends/wasapi/downmix.cpp
|
||||||
|
hooks/audio/backends/wasapi/resample.cpp
|
||||||
|
hooks/audio/backends/wasapi/shared.cpp
|
||||||
hooks/audio/backends/wasapi/dummy_audio_client.cpp
|
hooks/audio/backends/wasapi/dummy_audio_client.cpp
|
||||||
hooks/audio/backends/wasapi/dummy_audio_clock.cpp
|
hooks/audio/backends/wasapi/dummy_audio_clock.cpp
|
||||||
hooks/audio/backends/wasapi/dummy_audio_render_client.cpp
|
hooks/audio/backends/wasapi/dummy_audio_render_client.cpp
|
||||||
@@ -512,15 +530,23 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
hooks/graphics/nvenc_hook.cpp
|
hooks/graphics/nvenc_hook.cpp
|
||||||
hooks/graphics/backends/d3d9/d3d9_backend.cpp
|
hooks/graphics/backends/d3d9/d3d9_backend.cpp
|
||||||
hooks/graphics/backends/d3d9/d3d9_device.cpp
|
hooks/graphics/backends/d3d9/d3d9_device.cpp
|
||||||
|
hooks/graphics/backends/d3d9/d3d9_live2d.cpp
|
||||||
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp
|
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp
|
||||||
hooks/graphics/backends/d3d9/d3d9_swapchain.cpp
|
hooks/graphics/backends/d3d9/d3d9_swapchain.cpp
|
||||||
hooks/graphics/backends/d3d9/d3d9_texture.cpp
|
hooks/graphics/backends/d3d9/d3d9_texture.cpp
|
||||||
|
hooks/graphics/backends/d3d11/d3d11_backend.cpp
|
||||||
|
hooks/graphics/backends/d3d11/d3d11_swapchain.cpp
|
||||||
|
hooks/graphics/backends/d3d11/d3d11_factory.cpp
|
||||||
|
hooks/graphics/backends/d3d11/d3d11_vtable_capture.cpp
|
||||||
|
hooks/graphics/backends/d3d11/d3d11_screenshot.cpp
|
||||||
hooks/input/dinput8/fake_backend.cpp
|
hooks/input/dinput8/fake_backend.cpp
|
||||||
hooks/input/dinput8/fake_device.cpp
|
hooks/input/dinput8/fake_device.cpp
|
||||||
hooks/input/dinput8/hook.cpp
|
hooks/input/dinput8/hook.cpp
|
||||||
hooks/lang.cpp
|
hooks/lang.cpp
|
||||||
hooks/libraryhook.cpp
|
hooks/libraryhook.cpp
|
||||||
hooks/networkhook.cpp
|
hooks/networkhook.cpp
|
||||||
|
hooks/icmphook_net.cpp
|
||||||
|
hooks/icmphook_iphlpapi.cpp
|
||||||
hooks/powrprof.cpp
|
hooks/powrprof.cpp
|
||||||
#hooks/rom.cpp
|
#hooks/rom.cpp
|
||||||
hooks/setupapihook.cpp
|
hooks/setupapihook.cpp
|
||||||
@@ -554,6 +580,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
|
|
||||||
# overlay
|
# overlay
|
||||||
overlay/overlay.cpp
|
overlay/overlay.cpp
|
||||||
|
overlay/notifications.cpp
|
||||||
overlay/window.cpp
|
overlay/window.cpp
|
||||||
overlay/imgui/extensions.cpp
|
overlay/imgui/extensions.cpp
|
||||||
overlay/imgui/impl_spice.cpp
|
overlay/imgui/impl_spice.cpp
|
||||||
@@ -580,9 +607,15 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
overlay/windows/keypad.cpp
|
overlay/windows/keypad.cpp
|
||||||
overlay/windows/log.cpp
|
overlay/windows/log.cpp
|
||||||
overlay/windows/midi.cpp
|
overlay/windows/midi.cpp
|
||||||
|
overlay/windows/obs.cpp
|
||||||
|
overlay/windows/obs_websocket.cpp
|
||||||
overlay/windows/patch_manager.cpp
|
overlay/windows/patch_manager.cpp
|
||||||
|
overlay/windows/popn_sub.cpp
|
||||||
overlay/windows/wnd_manager.cpp
|
overlay/windows/wnd_manager.cpp
|
||||||
|
|
||||||
|
# external
|
||||||
|
external/easywsclient/easywsclient.cpp
|
||||||
|
|
||||||
# rawinput
|
# rawinput
|
||||||
rawinput/rawinput.cpp
|
rawinput/rawinput.cpp
|
||||||
rawinput/sextet.cpp
|
rawinput/sextet.cpp
|
||||||
@@ -595,6 +628,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
rawinput/smxstage.h
|
rawinput/smxstage.h
|
||||||
rawinput/smxdedicab.cpp
|
rawinput/smxdedicab.cpp
|
||||||
rawinput/smxdedicab.h
|
rawinput/smxdedicab.h
|
||||||
|
rawinput/xinput.cpp
|
||||||
|
rawinput/xinput.h
|
||||||
|
|
||||||
# reader
|
# reader
|
||||||
reader/reader.cpp
|
reader/reader.cpp
|
||||||
@@ -602,6 +637,9 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
reader/structuredmessage.cpp
|
reader/structuredmessage.cpp
|
||||||
reader/crypt.cpp
|
reader/crypt.cpp
|
||||||
|
|
||||||
|
# sdk
|
||||||
|
sdk/sdk.cpp
|
||||||
|
|
||||||
# stubs
|
# stubs
|
||||||
stubs/stubs.cpp
|
stubs/stubs.cpp
|
||||||
|
|
||||||
@@ -628,6 +666,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
util/time.cpp
|
util/time.cpp
|
||||||
util/cpuutils.cpp
|
util/cpuutils.cpp
|
||||||
util/netutils.cpp
|
util/netutils.cpp
|
||||||
|
util/precise_timer.cpp
|
||||||
util/sysutils.cpp
|
util/sysutils.cpp
|
||||||
util/lz77.cpp
|
util/lz77.cpp
|
||||||
util/tapeled.cpp
|
util/tapeled.cpp
|
||||||
@@ -639,37 +678,74 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
|
|
||||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
|
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
|
||||||
|
|
||||||
|
# guard against statically importing DLLs that must always be loaded dynamically:
|
||||||
|
# * DLLs users override via the modules directory (e.g. DXVK's d3d9.dll) - a
|
||||||
|
# static import loads the system copy at startup and preempts the override
|
||||||
|
# (issue #779).
|
||||||
|
# * Media Foundation DLLs (mf/mfplat/mfreadwrite) - a static import breaks
|
||||||
|
# Unity games.
|
||||||
|
# the check runs objdump on each produced binary and fails the build if any
|
||||||
|
# forbidden DLL is imported.
|
||||||
|
set(SPICE_FORBIDDEN_STATIC_IMPORTS
|
||||||
|
d3d8.dll d3d9.dll d3d10core.dll d3d11.dll dxgi.dll opengl32.dll
|
||||||
|
mf.dll mfplat.dll mfreadwrite.dll)
|
||||||
|
|
||||||
|
function(spice_guard_dll_imports target)
|
||||||
|
if(MSVC)
|
||||||
|
# objdump-based parsing assumes a GNU/LLVM toolchain
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
add_custom_command(TARGET ${target} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DOBJDUMP=${CMAKE_OBJDUMP}
|
||||||
|
-DTARGET_FILE=$<TARGET_FILE:${target}>
|
||||||
|
"-DFORBIDDEN=${SPICE_FORBIDDEN_STATIC_IMPORTS}"
|
||||||
|
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_no_static_dll_imports.cmake
|
||||||
|
VERBATIM
|
||||||
|
COMMENT "Checking ${target} for forbidden static DLL imports")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# spice.exe / spice_laa.exe shared objects
|
||||||
|
###########################################
|
||||||
|
# spice.exe and spice_laa.exe are compiled identically; the only difference is
|
||||||
|
# the large-address-aware bit, which is set at link time. Compile the sources
|
||||||
|
# once into a shared OBJECT library so spice_laa.exe only costs an extra link
|
||||||
|
# instead of a full recompile.
|
||||||
|
|
||||||
|
add_library(spicetools_spice_objs OBJECT ${SOURCE_FILES})
|
||||||
|
target_link_libraries(spicetools_spice_objs
|
||||||
|
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
||||||
|
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||||
|
target_link_libraries(spicetools_spice_objs PUBLIC winscard)
|
||||||
|
|
||||||
|
if(NOT MSVC)
|
||||||
|
set_target_properties(spicetools_spice_objs PROPERTIES COMPILE_FLAGS "-m32")
|
||||||
|
endif()
|
||||||
|
|
||||||
# spice.exe
|
# spice.exe
|
||||||
###########
|
###########
|
||||||
|
|
||||||
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
|
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
|
||||||
add_executable(spicetools_spice ${SOURCE_FILES} ${RESOURCE_FILES})
|
add_executable(spicetools_spice ${RESOURCE_FILES})
|
||||||
target_link_libraries(spicetools_spice
|
target_link_libraries(spicetools_spice PRIVATE spicetools_spice_objs)
|
||||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
|
||||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
|
||||||
target_link_libraries(spicetools_spice PUBLIC winscard)
|
|
||||||
set_target_properties(spicetools_spice PROPERTIES PREFIX "")
|
set_target_properties(spicetools_spice PROPERTIES PREFIX "")
|
||||||
set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice")
|
set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice")
|
||||||
|
|
||||||
IF(NOT MSVC)
|
IF(NOT MSVC)
|
||||||
set_target_properties(spicetools_spice PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
set_target_properties(spicetools_spice PROPERTIES LINK_FLAGS "-m32")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# spice_laa.exe
|
# spice_laa.exe
|
||||||
###########
|
###########
|
||||||
|
|
||||||
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
|
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
|
||||||
add_executable(spicetools_spice_laa ${SOURCE_FILES} ${RESOURCE_FILES})
|
add_executable(spicetools_spice_laa ${RESOURCE_FILES})
|
||||||
target_link_libraries(spicetools_spice_laa
|
target_link_libraries(spicetools_spice_laa PRIVATE spicetools_spice_objs)
|
||||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
|
||||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
|
||||||
target_link_libraries(spicetools_spice_laa PUBLIC winscard)
|
|
||||||
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
|
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
|
||||||
set_target_properties(spicetools_spice_laa PROPERTIES OUTPUT_NAME "spice_laa")
|
set_target_properties(spicetools_spice_laa PROPERTIES OUTPUT_NAME "spice_laa")
|
||||||
target_compile_definitions(spicetools_spice_laa PRIVATE SPICE32_LARGE_ADDRESS_AWARE=1)
|
|
||||||
|
|
||||||
IF(NOT MSVC)
|
IF(NOT MSVC)
|
||||||
set_target_properties(spicetools_spice_laa PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32 -Wl,--large-address-aware")
|
set_target_properties(spicetools_spice_laa PROPERTIES LINK_FLAGS "-m32 -Wl,--large-address-aware")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# spice_linux.exe
|
# spice_linux.exe
|
||||||
@@ -702,6 +778,10 @@ target_link_libraries(spicetools_spice64 PUBLIC winscard)
|
|||||||
set_target_properties(spicetools_spice64 PROPERTIES PREFIX "")
|
set_target_properties(spicetools_spice64 PROPERTIES PREFIX "")
|
||||||
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
|
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
|
||||||
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
|
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
|
||||||
|
if(NOT SPICE_XP)
|
||||||
|
target_link_libraries(spicetools_spice64 PUBLIC d3d11 dxgi)
|
||||||
|
target_compile_definitions(spicetools_spice64 PRIVATE SPICE_D3D11=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
IF(NOT MSVC)
|
IF(NOT MSVC)
|
||||||
set_target_properties(spicetools_spice64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
set_target_properties(spicetools_spice64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||||
@@ -721,6 +801,9 @@ set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "")
|
|||||||
set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux")
|
set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux")
|
||||||
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1)
|
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1)
|
||||||
target_compile_definitions(spicetools_spice64_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
|
target_compile_definitions(spicetools_spice64_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
|
||||||
|
# spice64_linux is never built under the WinXP toolchain, so dx11 is always on.
|
||||||
|
target_link_libraries(spicetools_spice64_linux PUBLIC d3d11 dxgi)
|
||||||
|
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE_D3D11=1)
|
||||||
|
|
||||||
IF(NOT MSVC)
|
IF(NOT MSVC)
|
||||||
set_target_properties(spicetools_spice64_linux PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
set_target_properties(spicetools_spice64_linux PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||||
@@ -733,7 +816,7 @@ set(SOURCE_FILES ${SOURCE_FILES} launcher/options.h launcher/options.cpp)
|
|||||||
set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc)
|
set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc)
|
||||||
add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
|
add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||||
target_link_libraries(spicetools_cfg
|
target_link_libraries(spicetools_cfg
|
||||||
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
||||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||||
target_link_libraries(spicetools_cfg PUBLIC winscard)
|
target_link_libraries(spicetools_cfg PUBLIC winscard)
|
||||||
set_target_properties(spicetools_cfg PROPERTIES PREFIX "")
|
set_target_properties(spicetools_cfg PROPERTIES PREFIX "")
|
||||||
@@ -751,7 +834,7 @@ set(SOURCE_FILES ${SOURCE_FILES} launcher/options.h launcher/options.cpp)
|
|||||||
set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc)
|
set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc)
|
||||||
add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
|
add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||||
target_link_libraries(spicetools_cfg_linux
|
target_link_libraries(spicetools_cfg_linux
|
||||||
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
||||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||||
set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "")
|
set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "")
|
||||||
set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux")
|
set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux")
|
||||||
@@ -813,7 +896,8 @@ endif()
|
|||||||
|
|
||||||
# nvcuda.dll
|
# nvcuda.dll
|
||||||
set(SOURCE_FILES stubs/nvcuda.cpp)
|
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 PREFIX "")
|
||||||
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
|
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
|
||||||
|
|
||||||
@@ -823,7 +907,8 @@ endif()
|
|||||||
|
|
||||||
# nvcuvid.dll
|
# nvcuvid.dll
|
||||||
set(SOURCE_FILES stubs/nvcuvid.cpp)
|
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 PREFIX "")
|
||||||
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
|
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
|
||||||
|
|
||||||
@@ -833,7 +918,8 @@ endif()
|
|||||||
|
|
||||||
# nvEncodeAPI64.dll
|
# nvEncodeAPI64.dll
|
||||||
set(SOURCE_FILES stubs/nvEncodeAPI64.cpp)
|
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 PREFIX "")
|
||||||
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
|
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
|
||||||
|
|
||||||
@@ -843,7 +929,8 @@ endif()
|
|||||||
|
|
||||||
# cpusbxpkm.dll (32 bit)
|
# cpusbxpkm.dll (32 bit)
|
||||||
set(SOURCE_FILES stubs/cpusbxpkm.cpp)
|
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 PREFIX "")
|
||||||
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
|
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
|
||||||
|
|
||||||
@@ -851,6 +938,36 @@ if(NOT MSVC)
|
|||||||
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
||||||
endif()
|
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
|
# output directories
|
||||||
####################
|
####################
|
||||||
|
|
||||||
@@ -860,15 +977,28 @@ set_target_properties(spicetools_cfg spicetools_cfg_linux
|
|||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
|
||||||
|
|
||||||
# output 32bit
|
# 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
|
PROPERTIES
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
|
||||||
|
|
||||||
# output 64bit
|
# 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
|
PROPERTIES
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64"
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64")
|
||||||
|
|
||||||
|
# forbidden static DLL import guard
|
||||||
|
###################################
|
||||||
|
# apply the check to every executable and DLL produced by this project.
|
||||||
|
get_property(spice_all_targets DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY BUILDSYSTEM_TARGETS)
|
||||||
|
foreach(spice_target IN LISTS spice_all_targets)
|
||||||
|
get_target_property(spice_target_type ${spice_target} TYPE)
|
||||||
|
if(spice_target_type STREQUAL "EXECUTABLE"
|
||||||
|
OR spice_target_type STREQUAL "SHARED_LIBRARY"
|
||||||
|
OR spice_target_type STREQUAL "MODULE_LIBRARY")
|
||||||
|
spice_guard_dll_imports(${spice_target})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|||||||
@@ -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
|
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.
|
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
|
## License
|
||||||
Unless otherwise noted, all files are licensed under the GPLv3.
|
Unless otherwise noted, all files are licensed under the GPLv3.
|
||||||
See the LICENSE file for the full license text.
|
See the LICENSE file for the full license text.
|
||||||
|
|||||||
+120
-18
@@ -35,24 +35,33 @@ struct ICCA_STATUS_LA9 {
|
|||||||
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
|
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
|
||||||
|
|
||||||
enum ICCA_WORKFLOW {
|
enum ICCA_WORKFLOW {
|
||||||
STEP,
|
STEP = 0,
|
||||||
SLEEP,
|
SLEEP = 1,
|
||||||
START,
|
START = 2,
|
||||||
INIT,
|
INIT = 3,
|
||||||
READY,
|
READY = 4,
|
||||||
GET_USERID,
|
GET_USERID = 5,
|
||||||
ACTIVE,
|
ACTIVE = 6,
|
||||||
EJECT,
|
EJECT = 7,
|
||||||
EJECT_CHECK,
|
EJECT_CHECK = 8,
|
||||||
END,
|
END = 9,
|
||||||
CLOSE_EJECT,
|
CLOSE_EJECT = 10,
|
||||||
CLOSE_E_CHK,
|
CLOSE_E_CHK = 11,
|
||||||
CLOSE_END,
|
CLOSE_END = 12,
|
||||||
ERR_GETUID = -2
|
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_UNIT {
|
||||||
struct ICCA_STATUS status {};
|
struct ICCA_STATUS status {};
|
||||||
enum ICCA_WORKFLOW state = STEP;
|
enum ICCA_WORKFLOW state = STEP;
|
||||||
|
enum ICCA_WORKFLOW state_jdz = SLEEP;
|
||||||
bool card_cmd_pressed = false;
|
bool card_cmd_pressed = false;
|
||||||
bool card_in = false;
|
bool card_in = false;
|
||||||
double card_in_time = 0.0;
|
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) {
|
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)
|
if (icca_get_active_count() < 2)
|
||||||
return 1;
|
return 1;
|
||||||
else {
|
else {
|
||||||
@@ -122,9 +136,15 @@ static inline void update_card(int unit_id) {
|
|||||||
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
|
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
|
||||||
|
|
||||||
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
||||||
|
unit->state_jdz = ACTIVE;
|
||||||
unit->status.error = 0;
|
unit->status.error = 0;
|
||||||
|
unit->status.front_sensor = 1;
|
||||||
|
unit->status.rear_sensor = 1;
|
||||||
} else {
|
} else {
|
||||||
unit->state = ERR_GETUID;
|
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);
|
memset(unit->status.uid, 0, 8);
|
||||||
}
|
}
|
||||||
unit->card_in = true;
|
unit->card_in = true;
|
||||||
@@ -132,10 +152,25 @@ static inline void update_card(int unit_id) {
|
|||||||
} else if (unit->state == EJECT_CHECK) {
|
} else if (unit->state == EJECT_CHECK) {
|
||||||
unit->state = SLEEP;
|
unit->state = SLEEP;
|
||||||
unit->card_in = false;
|
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 {
|
} else {
|
||||||
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
|
||||||
|
if (unit->state_jdz == READY) {
|
||||||
|
unit->state_jdz = GET_USERID;
|
||||||
|
}
|
||||||
unit->status.error = 0;
|
unit->status.error = 0;
|
||||||
|
unit->status.front_sensor = 1;
|
||||||
|
unit->status.rear_sensor = 1;
|
||||||
unit->card_in = true;
|
unit->card_in = true;
|
||||||
unit->card_in_time = get_performance_seconds();
|
unit->card_in_time = get_performance_seconds();
|
||||||
}
|
}
|
||||||
@@ -144,6 +179,12 @@ static inline void update_card(int unit_id) {
|
|||||||
unit->state = CLOSE_EJECT;
|
unit->state = CLOSE_EJECT;
|
||||||
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
|
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
|
||||||
unit->card_in = false;
|
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
|
// copy state to output buffer
|
||||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||||
|
if (avs::game::is_model("JDZ")) {
|
||||||
|
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
|
||||||
|
} else {
|
||||||
unit->status.status_code = unit->state;
|
unit->status.status_code = unit->state;
|
||||||
|
}
|
||||||
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
|
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
|
||||||
|
|
||||||
// funny workaround
|
// 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) {
|
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
|
||||||
unit_id = icca_get_unit_id(unit_id);
|
unit_id = icca_get_unit_id(unit_id);
|
||||||
ICCA_UNIT *unit = &ICCA_UNITS[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 (unit->card_in) {
|
||||||
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
|
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
|
||||||
unit->state = END;
|
unit->state = END;
|
||||||
@@ -403,15 +453,60 @@ static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
|
|||||||
return 0;
|
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) {
|
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);
|
unit_id = icca_get_unit_id(unit_id);
|
||||||
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
|
||||||
switch (workflow) {
|
switch (workflow) {
|
||||||
case STEP:
|
case STEP:
|
||||||
if (avs::game::is_model("JDZ"))
|
|
||||||
unit->state = SLEEP;
|
|
||||||
else
|
|
||||||
unit->state = STEP;
|
unit->state = STEP;
|
||||||
break;
|
break;
|
||||||
case SLEEP:
|
case SLEEP:
|
||||||
@@ -441,7 +536,6 @@ static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return unit->state;
|
return unit->state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,6 +561,14 @@ acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModu
|
|||||||
void acio::ICCAModule::attach() {
|
void acio::ICCAModule::attach() {
|
||||||
ACIOModule::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
|
// hooks
|
||||||
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
|
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
|
||||||
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
|
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
#include "mdxf.h"
|
#include "mdxf.h"
|
||||||
#include "mdxf_poll.h"
|
#include "mdxf_poll.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
|
#include "games/ddr/ddr.h"
|
||||||
#include "games/ddr/io.h"
|
#include "games/ddr/io.h"
|
||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#define MDFX_DEBUG_VERBOSE 0
|
#define MDFX_DEBUG_VERBOSE 0
|
||||||
|
|
||||||
@@ -108,9 +111,10 @@ static void mdxf_thread_start() {
|
|||||||
MDXF_THREAD = std::thread([] {
|
MDXF_THREAD = std::thread([] {
|
||||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||||
|
|
||||||
|
timeutils::PreciseSleepTimer timer;
|
||||||
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
|
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
|
||||||
mdxf_poll(false);
|
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
|
// decide on button map
|
||||||
const size_t *button_map = nullptr;
|
const size_t *button_map = nullptr;
|
||||||
|
int player = 0;
|
||||||
switch (node) {
|
switch (node) {
|
||||||
case 17:
|
case 17:
|
||||||
case 25:
|
case 25:
|
||||||
button_map = &buttons_p1[0];
|
button_map = &buttons_p1[0];
|
||||||
|
player = 1;
|
||||||
break;
|
break;
|
||||||
case 18:
|
case 18:
|
||||||
case 26:
|
case 26:
|
||||||
button_map = &buttons_p2[0];
|
button_map = &buttons_p2[0];
|
||||||
|
player = 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,19 +381,27 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
|||||||
if (source == EXTERNAL_POLL) {
|
if (source == EXTERNAL_POLL) {
|
||||||
// get buttons
|
// get buttons
|
||||||
auto &buttons = games::ddr::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 up_down = 0;
|
||||||
uint8_t left_right = 0;
|
uint8_t left_right = 0;
|
||||||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0])) || analog_up) {
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
|
|
||||||
up_down |= 0xF0;
|
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;
|
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;
|
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;
|
left_right |= 0x0F;
|
||||||
}
|
}
|
||||||
current_state = (uint16_t(up_down) << 8) | left_right;
|
current_state = (uint16_t(up_down) << 8) | left_right;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "util/circular_buffer.h"
|
#include "util/circular_buffer.h"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "games/sdvx/sdvx.h"
|
#include "games/sdvx/sdvx.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
using namespace acioemu;
|
using namespace acioemu;
|
||||||
@@ -46,11 +47,12 @@ ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count)
|
|||||||
// keypad thread for faster polling
|
// keypad thread for faster polling
|
||||||
if (keypad_thread) {
|
if (keypad_thread) {
|
||||||
this->keypad_thread = new std::thread([this]() {
|
this->keypad_thread = new std::thread([this]() {
|
||||||
|
timeutils::PreciseSleepTimer timer;
|
||||||
while (this->cards) {
|
while (this->cards) {
|
||||||
for (int unit = 0; unit < this->node_count; unit++) {
|
for (int unit = 0; unit < this->node_count; unit++) {
|
||||||
this->update_keypad(unit, false);
|
this->update_keypad(unit, false);
|
||||||
}
|
}
|
||||||
Sleep(7);
|
timer.sleep(7);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
|
#include "overlay/notifications.h"
|
||||||
|
|
||||||
#include "module.h"
|
#include "module.h"
|
||||||
#include "modules/analogs.h"
|
#include "modules/analogs.h"
|
||||||
#include "modules/buttons.h"
|
#include "modules/buttons.h"
|
||||||
@@ -197,6 +199,9 @@ void Controller::connection_handler(api::ClientState client_state) {
|
|||||||
|
|
||||||
// log connection
|
// log connection
|
||||||
log_info("api", "client connected: {}", client_address_str);
|
log_info("api", "client connected: {}", client_address_str);
|
||||||
|
overlay::notifications::add(
|
||||||
|
overlay::notifications::Severity::Success,
|
||||||
|
fmt::format("API client connected ({})", client_address_str));
|
||||||
client_states_m.lock();
|
client_states_m.lock();
|
||||||
client_states.emplace_back(&client_state);
|
client_states.emplace_back(&client_state);
|
||||||
client_states_m.unlock();
|
client_states_m.unlock();
|
||||||
@@ -277,6 +282,9 @@ void Controller::connection_handler(api::ClientState client_state) {
|
|||||||
|
|
||||||
// log disconnect
|
// log disconnect
|
||||||
log_info("api", "client disconnected: {}", client_address_str);
|
log_info("api", "client disconnected: {}", client_address_str);
|
||||||
|
overlay::notifications::add(
|
||||||
|
overlay::notifications::Severity::Info,
|
||||||
|
fmt::format("API client disconnected ({})", client_address_str));
|
||||||
client_states_m.lock();
|
client_states_m.lock();
|
||||||
client_states.erase(std::remove(client_states.begin(), client_states.end(), &client_state));
|
client_states.erase(std::remove(client_states.begin(), client_states.end(), &client_state));
|
||||||
client_states_m.unlock();
|
client_states_m.unlock();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
#include "games/io.h"
|
#include "games/io.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
#include "acio/mdxf/mdxf_poll.h"
|
||||||
|
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
using namespace rapidjson;
|
using namespace rapidjson;
|
||||||
@@ -104,6 +105,7 @@ namespace api::modules {
|
|||||||
for (auto &analog : *this->analogs) {
|
for (auto &analog : *this->analogs) {
|
||||||
analog.override_enabled = false;
|
analog.override_enabled = false;
|
||||||
}
|
}
|
||||||
|
mdxf_poll(true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -148,6 +150,7 @@ namespace api::modules {
|
|||||||
if (analog.getName() == name) {
|
if (analog.getName() == name) {
|
||||||
analog.override_state = CLAMP(state, 0.f, 1.f);
|
analog.override_state = CLAMP(state, 0.f, 1.f);
|
||||||
analog.override_enabled = true;
|
analog.override_enabled = true;
|
||||||
|
mdxf_poll(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,6 +170,7 @@ namespace api::modules {
|
|||||||
for (auto &analog : *this->analogs) {
|
for (auto &analog : *this->analogs) {
|
||||||
if (analog.getName() == name) {
|
if (analog.getName() == name) {
|
||||||
analog.override_enabled = false;
|
analog.override_enabled = false;
|
||||||
|
mdxf_poll(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "launcher/launcher.h"
|
#include "launcher/launcher.h"
|
||||||
#include "games/io.h"
|
#include "games/io.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
#include "acio/mdxf/mdxf_poll.h"
|
||||||
|
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
using namespace rapidjson;
|
using namespace rapidjson;
|
||||||
@@ -107,6 +108,7 @@ namespace api::modules {
|
|||||||
for (auto &button : *this->buttons) {
|
for (auto &button : *this->buttons) {
|
||||||
button.override_enabled = false;
|
button.override_enabled = false;
|
||||||
}
|
}
|
||||||
|
mdxf_poll(true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -153,6 +155,7 @@ namespace api::modules {
|
|||||||
GameAPI::Buttons::BUTTON_PRESSED : GameAPI::Buttons::BUTTON_NOT_PRESSED;
|
GameAPI::Buttons::BUTTON_PRESSED : GameAPI::Buttons::BUTTON_NOT_PRESSED;
|
||||||
button.override_velocity = CLAMP(state, 0.f, 1.f);
|
button.override_velocity = CLAMP(state, 0.f, 1.f);
|
||||||
button.override_enabled = true;
|
button.override_enabled = true;
|
||||||
|
mdxf_poll(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +175,7 @@ namespace api::modules {
|
|||||||
for (auto &button : *this->buttons) {
|
for (auto &button : *this->buttons) {
|
||||||
if (button.getName() == name) {
|
if (button.getName() == name) {
|
||||||
button.override_enabled = false;
|
button.override_enabled = false;
|
||||||
|
mdxf_poll(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "capture.h"
|
#include "capture.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
#include "external/rapidjson/document.h"
|
#include "external/rapidjson/document.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "util/crypt.h"
|
#include "util/crypt.h"
|
||||||
@@ -14,6 +16,56 @@ namespace api::modules {
|
|||||||
|
|
||||||
static thread_local std::vector<uint8_t> CAPTURE_BUFFER;
|
static thread_local std::vector<uint8_t> CAPTURE_BUFFER;
|
||||||
|
|
||||||
|
struct CachedFrame {
|
||||||
|
std::vector<uint8_t> jpeg;
|
||||||
|
uint64_t timestamp = 0;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::mutex FRAME_CACHE_M;
|
||||||
|
static std::unordered_map<int, CachedFrame> FRAME_CACHE;
|
||||||
|
|
||||||
|
static void add_jpeg_response(
|
||||||
|
int screen,
|
||||||
|
uint64_t timestamp,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
const std::vector<uint8_t> &jpeg,
|
||||||
|
Response &res) {
|
||||||
|
|
||||||
|
auto encoded = crypt::base64_encode(jpeg.data(), jpeg.size());
|
||||||
|
|
||||||
|
Value data;
|
||||||
|
data.SetString(encoded.c_str(), encoded.length(), res.doc()->GetAllocator());
|
||||||
|
res.add_data(timestamp);
|
||||||
|
res.add_data(width);
|
||||||
|
res.add_data(height);
|
||||||
|
res.add_data(data);
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(FRAME_CACHE_M);
|
||||||
|
FRAME_CACHE[screen] = {jpeg, timestamp, width, height};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool try_cached_response(int screen, Response &res) {
|
||||||
|
std::lock_guard<std::mutex> lock(FRAME_CACHE_M);
|
||||||
|
const auto pos = FRAME_CACHE.find(screen);
|
||||||
|
if (pos == FRAME_CACHE.end() || pos->second.jpeg.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &cached = pos->second;
|
||||||
|
auto encoded = crypt::base64_encode(cached.jpeg.data(), cached.jpeg.size());
|
||||||
|
|
||||||
|
Value data;
|
||||||
|
data.SetString(encoded.c_str(), encoded.length(), res.doc()->GetAllocator());
|
||||||
|
res.add_data(cached.timestamp);
|
||||||
|
res.add_data(cached.width);
|
||||||
|
res.add_data(cached.height);
|
||||||
|
res.add_data(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Capture::Capture() : Module("capture") {
|
Capture::Capture() : Module("capture") {
|
||||||
functions["get_screens"] = std::bind(&Capture::get_screens, this, _1, _2);
|
functions["get_screens"] = std::bind(&Capture::get_screens, this, _1, _2);
|
||||||
functions["get_jpg"] = std::bind(&Capture::get_jpg, this, _1, _2);
|
functions["get_jpg"] = std::bind(&Capture::get_jpg, this, _1, _2);
|
||||||
@@ -41,6 +93,7 @@ namespace api::modules {
|
|||||||
* reduce: uint for dividing image size
|
* reduce: uint for dividing image size
|
||||||
*/
|
*/
|
||||||
void Capture::get_jpg(Request &req, Response &res) {
|
void Capture::get_jpg(Request &req, Response &res) {
|
||||||
|
CAPTURE_BUFFER.clear();
|
||||||
CAPTURE_BUFFER.reserve(1024 * 128);
|
CAPTURE_BUFFER.reserve(1024 * 128);
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
@@ -71,24 +124,15 @@ namespace api::modules {
|
|||||||
bool success = graphics_capture_receive_jpeg(screen, [] (uint8_t byte) {
|
bool success = graphics_capture_receive_jpeg(screen, [] (uint8_t byte) {
|
||||||
CAPTURE_BUFFER.push_back(byte);
|
CAPTURE_BUFFER.push_back(byte);
|
||||||
}, true, quality, true, divide, ×tamp, &width, &height);
|
}, true, quality, true, divide, ×tamp, &width, &height);
|
||||||
if (!success) {
|
|
||||||
|
if (success) {
|
||||||
|
add_jpeg_response(screen, timestamp, width, height, CAPTURE_BUFFER, res);
|
||||||
|
CAPTURE_BUFFER.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// encode to base64
|
// fall back to the last successful frame while the game is busy loading
|
||||||
auto encoded = crypt::base64_encode(
|
|
||||||
CAPTURE_BUFFER.data(),
|
|
||||||
CAPTURE_BUFFER.size());
|
|
||||||
|
|
||||||
// clear buffer
|
|
||||||
CAPTURE_BUFFER.clear();
|
CAPTURE_BUFFER.clear();
|
||||||
|
try_cached_response(screen, res);
|
||||||
// add data to response
|
|
||||||
Value data;
|
|
||||||
data.SetString(encoded.c_str(), encoded.length(), res.doc()->GetAllocator());
|
|
||||||
res.add_data(timestamp);
|
|
||||||
res.add_data(width);
|
|
||||||
res.add_data(height);
|
|
||||||
res.add_data(data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "external/rapidjson/document.h"
|
#include "external/rapidjson/document.h"
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
|
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
using namespace rapidjson;
|
using namespace rapidjson;
|
||||||
@@ -58,6 +59,7 @@ namespace api::modules {
|
|||||||
// get params
|
// get params
|
||||||
auto keypad = req.params[0].GetUint();
|
auto keypad = req.params[0].GetUint();
|
||||||
auto input = std::string(req.params[1].GetString());
|
auto input = std::string(req.params[1].GetString());
|
||||||
|
timeutils::PreciseSleepTimer timer;
|
||||||
|
|
||||||
// process all chars
|
// process all chars
|
||||||
for (auto c : input) {
|
for (auto c : input) {
|
||||||
@@ -91,11 +93,11 @@ namespace api::modules {
|
|||||||
|
|
||||||
// set
|
// set
|
||||||
eamuse_set_keypad_overrides(keypad, state);
|
eamuse_set_keypad_overrides(keypad, state);
|
||||||
Sleep(sleep_time);
|
timer.sleep(sleep_time);
|
||||||
|
|
||||||
// unset
|
// unset
|
||||||
eamuse_set_keypad_overrides(keypad, 0);
|
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
|
// check if file exists in modules
|
||||||
if (!fileutils::file_exists(dll_path)) {
|
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
|
// get module
|
||||||
@@ -118,7 +118,7 @@ namespace api::modules {
|
|||||||
|
|
||||||
// check if file exists in modules
|
// check if file exists in modules
|
||||||
if (!fileutils::file_exists(dll_path)) {
|
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
|
// get module
|
||||||
@@ -196,7 +196,7 @@ namespace api::modules {
|
|||||||
|
|
||||||
// check if file exists in modules
|
// check if file exists in modules
|
||||||
if (!fileutils::file_exists(dll_path)) {
|
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
|
// get module
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ namespace api {
|
|||||||
controller->init_state(this->state);
|
controller->init_state(this->state);
|
||||||
this->thread = new std::thread([this] () {
|
this->thread = new std::thread([this] () {
|
||||||
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
|
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
|
||||||
|
timeutils::PreciseSleepTimer timer;
|
||||||
|
|
||||||
// read buffer
|
// read buffer
|
||||||
uint8_t read_buffer[16*1024];
|
uint8_t read_buffer[16*1024];
|
||||||
@@ -160,7 +162,7 @@ namespace api {
|
|||||||
|
|
||||||
// slow down on reconnect
|
// slow down on reconnect
|
||||||
if (this->running) {
|
if (this->running) {
|
||||||
Sleep(retry_time);
|
timer.sleep(retry_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "util/rc4.h"
|
#include "util/rc4.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "overlay/notifications.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
|
||||||
using namespace headsocket;
|
using namespace headsocket;
|
||||||
@@ -91,12 +92,18 @@ namespace api {
|
|||||||
|
|
||||||
// log connection
|
// log connection
|
||||||
log_info("api::websocket", "client connected");
|
log_info("api::websocket", "client connected");
|
||||||
|
overlay::notifications::add(
|
||||||
|
overlay::notifications::Severity::Success,
|
||||||
|
"API websocket client connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketClient::on_disconnect() {
|
void WebSocketClient::on_disconnect() {
|
||||||
|
|
||||||
// log disconnection
|
// log disconnection
|
||||||
log_info("api::websocket", "client disconnected");
|
log_info("api::websocket", "client disconnected");
|
||||||
|
overlay::notifications::add(
|
||||||
|
overlay::notifications::Severity::Info,
|
||||||
|
"API websocket client disconnected");
|
||||||
|
|
||||||
// get pointer to server
|
// get pointer to server
|
||||||
auto srv = reinterpret_cast<WebSocketServer *>(server().get());
|
auto srv = reinterpret_cast<WebSocketServer *>(server().get());
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "external/robin_hood.h"
|
#include "external/robin_hood.h"
|
||||||
|
#include "games/popn/popn.h"
|
||||||
#include "launcher/logger.h"
|
#include "launcher/logger.h"
|
||||||
#include "launcher/signal.h"
|
#include "launcher/signal.h"
|
||||||
#include "util/deferlog.h"
|
#include "util/deferlog.h"
|
||||||
@@ -1179,6 +1180,15 @@ namespace avs {
|
|||||||
return;
|
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()) {
|
if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) {
|
||||||
auto old_size = HEAP_SIZE;
|
auto old_size = HEAP_SIZE;
|
||||||
|
|
||||||
@@ -1506,7 +1516,7 @@ namespace avs {
|
|||||||
" * It's also possible that you have incomplete game data\n"
|
" * 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"
|
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
|
||||||
"\n"
|
"\n"
|
||||||
, DLL_NAME, MODULE_PATH.string()) };
|
, DLL_NAME, MODULE_PATH) };
|
||||||
log_warning("avs-ea3", "{}", info_str);
|
log_warning("avs-ea3", "{}", info_str);
|
||||||
log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME);
|
log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME);
|
||||||
}
|
}
|
||||||
@@ -1747,8 +1757,8 @@ namespace avs {
|
|||||||
{
|
{
|
||||||
deferredlogs::defer_error_messages({
|
deferredlogs::defer_error_messages({
|
||||||
"AVS filesystem initialization failure was previously detected during boot!",
|
"AVS filesystem initialization failure was previously detected during boot!",
|
||||||
fmt::format(" ERROR: directory could not be created: {}", src_path.string().c_str()),
|
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.c_str()),
|
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"
|
" fix the XML file and try again"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1770,14 +1780,14 @@ namespace avs {
|
|||||||
auto created = std::filesystem::create_directories(real_path, err);
|
auto created = std::filesystem::create_directories(real_path, err);
|
||||||
|
|
||||||
if (created) {
|
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) {
|
if (err) {
|
||||||
avs_dir_err(real_path);
|
avs_dir_err(real_path);
|
||||||
log_warning("avs-core", "failed to create '{}' folder at '{}': {}",
|
log_warning("avs-core", "failed to create '{}' folder at '{}': {}",
|
||||||
avs_path,
|
avs_path,
|
||||||
real_path.string(),
|
real_path,
|
||||||
err.message());
|
err.message());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1911,6 +1921,15 @@ namespace avs {
|
|||||||
// create nvram and raw directories if possible for mounttable configurations
|
// create nvram and raw directories if possible for mounttable configurations
|
||||||
create_avs_config_fs_table(config, config_node);
|
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
|
// set log level
|
||||||
if (!LOG_LEVEL_CUSTOM.empty()) {
|
if (!LOG_LEVEL_CUSTOM.empty()) {
|
||||||
property_search_remove_safe(config, config_node, "/log/level");
|
property_search_remove_safe(config, config_node, "/log/level");
|
||||||
|
|||||||
+32
-6
@@ -151,7 +151,7 @@ namespace avs {
|
|||||||
" * It's also possible that you have incomplete game data\n"
|
" * 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"
|
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
|
||||||
"\n"
|
"\n"
|
||||||
, DLL_NAME, MODULE_PATH.string()) };
|
, DLL_NAME, MODULE_PATH) };
|
||||||
|
|
||||||
log_warning("avs-ea3", "{}", info_str);
|
log_warning("avs-ea3", "{}", info_str);
|
||||||
log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME);
|
log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME);
|
||||||
@@ -217,7 +217,25 @@ namespace avs {
|
|||||||
ea3_config_name = "prop/eamuse-config.xml";
|
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
|
// remember new config path
|
||||||
CFG_PATH = ea3_config_name;
|
CFG_PATH = ea3_config_name;
|
||||||
@@ -372,8 +390,8 @@ namespace avs {
|
|||||||
|
|
||||||
// fall back to default PCBID if node is not found
|
// fall back to default PCBID if node is not found
|
||||||
if (!EA3_PCBID[0] && PCBID_CUSTOM.empty()) {
|
if (!EA3_PCBID[0] && PCBID_CUSTOM.empty()) {
|
||||||
log_warning("avs-ea3", "no PCBID set, falling back to default PCBID value (04040000000000000000)");
|
log_warning("avs-ea3", "no PCBID set, falling back to default PCBID value (01201000000000010101)");
|
||||||
PCBID_CUSTOM = "04040000000000000000";
|
PCBID_CUSTOM = "01201000000000010101";
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom PCBID
|
// custom PCBID
|
||||||
@@ -554,9 +572,17 @@ namespace avs {
|
|||||||
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
|
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
|
||||||
|
|
||||||
// call the game init
|
// 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)) {
|
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
|
// accommodate changes to soft id code
|
||||||
|
|||||||
@@ -73,11 +73,10 @@ namespace avs {
|
|||||||
|
|
||||||
// load game instance
|
// load game instance
|
||||||
const auto dll_path = MODULE_PATH / DLL_NAME;
|
const auto dll_path = MODULE_PATH / DLL_NAME;
|
||||||
const auto dll_path_s = dll_path.string();
|
log_info("avs-game", "DLL path: {}", dll_path);
|
||||||
log_info("avs-game", "DLL path: {}", dll_path_s.c_str());
|
|
||||||
|
|
||||||
// MAX_PATH is 260
|
// 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(
|
log_warning(
|
||||||
"avs-game",
|
"avs-game",
|
||||||
"PATH TOO LONG WARNING\n\n"
|
"PATH TOO LONG WARNING\n\n"
|
||||||
@@ -93,7 +92,7 @@ namespace avs {
|
|||||||
"long, often resulting in random crashes. Move the game contents to\n"
|
"long, often resulting in random crashes. Move the game contents to\n"
|
||||||
"a directory with shorter path.\n"
|
"a directory with shorter path.\n"
|
||||||
"-------------------------------------------------------------------\n\n",
|
"-------------------------------------------------------------------\n\n",
|
||||||
dll_path_s, dll_path_s.length());
|
dll_path, dll_path_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ddr gamemdx.dll user error
|
// ddr gamemdx.dll user error
|
||||||
@@ -112,7 +111,7 @@ namespace avs {
|
|||||||
|
|
||||||
// file not found on disk
|
// file not found on disk
|
||||||
if (!fileutils::file_exists(dll_path)) {
|
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");
|
log_warning("avs-game", "double check -exec and -modules parameters; unless you know what you're doing, leave them blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,8 @@
|
|||||||
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" 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>
|
</buttons>
|
||||||
<analogs>
|
<analogs>
|
||||||
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
<analog name="Turntable 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="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>
|
</analogs>
|
||||||
<lights>
|
<lights>
|
||||||
<light name="P1 1" devid="Player 1" index="0"/>
|
<light name="P1 1" devid="Player 1" index="0"/>
|
||||||
@@ -117,8 +117,8 @@
|
|||||||
<keypad_buttons/>
|
<keypad_buttons/>
|
||||||
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
|
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
|
||||||
<analogs>
|
<analogs>
|
||||||
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
<analog name="Turntable 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="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>
|
</analogs>
|
||||||
<lights>
|
<lights>
|
||||||
<light name="P1 1" devid="arcin (1p)" index="0"/>
|
<light name="P1 1" devid="arcin (1p)" index="0"/>
|
||||||
@@ -144,34 +144,34 @@
|
|||||||
<buttons/>
|
<buttons/>
|
||||||
<keypad_buttons/>
|
<keypad_buttons/>
|
||||||
<analogs>
|
<analogs>
|
||||||
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
<analog name="Key 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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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="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>
|
</analogs>
|
||||||
<lights>
|
<lights>
|
||||||
<light name="Key 1 R" devid="Lights 01-14" index="0"/>
|
<light name="Key 1 R" devid="Lights 01-14" index="0"/>
|
||||||
@@ -277,9 +277,9 @@
|
|||||||
</buttons>
|
</buttons>
|
||||||
<keypad_buttons/>
|
<keypad_buttons/>
|
||||||
<analogs>
|
<analogs>
|
||||||
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
<analog name="Guitar P1 Wail 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="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="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>
|
</analogs>
|
||||||
<lights/>
|
<lights/>
|
||||||
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
|
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
|
||||||
@@ -296,8 +296,8 @@
|
|||||||
</buttons>
|
</buttons>
|
||||||
<keypad_buttons/>
|
<keypad_buttons/>
|
||||||
<analogs>
|
<analogs>
|
||||||
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
<analog name="VOL-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="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>
|
</analogs>
|
||||||
<lights>
|
<lights>
|
||||||
<light name="BT-A" devid="Lights (MI_02)" index="24"/>
|
<light name="BT-A" devid="Lights (MI_02)" index="24"/>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
<security>
|
<security>
|
||||||
<requestedPrivileges>
|
<requestedPrivileges>
|
||||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||||
</requestedPrivileges>
|
</requestedPrivileges>
|
||||||
</security>
|
</security>
|
||||||
</trustInfo>
|
</trustInfo>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+128
-18
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# set to EN-US for consistency
|
# set to EN-US for consistency
|
||||||
LANG=en_us_8859_1
|
LANG=en_us_8859_1
|
||||||
@@ -43,10 +43,16 @@ GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
|
|||||||
GIT_HEAD=$(git rev-parse HEAD || echo "none")
|
GIT_HEAD=$(git rev-parse HEAD || echo "none")
|
||||||
TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-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_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_RELEASE="./cmake-build-release-32"
|
||||||
BUILDDIR_32_DEBUG="./cmake-build-debug-32"
|
BUILDDIR_32_DEBUG="./cmake-build-debug-32"
|
||||||
BUILDDIR_64_RELEASE="./cmake-build-release-64"
|
BUILDDIR_64_RELEASE="./cmake-build-release-64"
|
||||||
BUILDDIR_64_DEBUG="./cmake-build-debug-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
|
DEBUG=0
|
||||||
OUTDIR="./bin/spice2x"
|
OUTDIR="./bin/spice2x"
|
||||||
OUTDIR_EXTRAS="./bin/spice2x/extras"
|
OUTDIR_EXTRAS="./bin/spice2x/extras"
|
||||||
@@ -62,21 +68,44 @@ DIST_FOLDER="./dist"
|
|||||||
DIST_NAME="spice2x-$(date +%y)-$(date +%m)-$(date +%d).zip"
|
DIST_NAME="spice2x-$(date +%y)-$(date +%m)-$(date +%d).zip"
|
||||||
DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.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.'
|
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_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"
|
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
|
# determine build type
|
||||||
BUILD_TYPE="Release"
|
BUILD_TYPE="Release"
|
||||||
BUILDDIR_32=${BUILDDIR_32_RELEASE}
|
BUILDDIR_32=${BUILDDIR_32_RELEASE}
|
||||||
BUILDDIR_64=${BUILDDIR_64_RELEASE}
|
BUILDDIR_64=${BUILDDIR_64_RELEASE}
|
||||||
|
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_RELEASE}
|
||||||
|
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_RELEASE}
|
||||||
if ((DEBUG > 0))
|
if ((DEBUG > 0))
|
||||||
then
|
then
|
||||||
BUILD_TYPE="Debug"
|
BUILD_TYPE="Debug"
|
||||||
BUILDDIR_32=${BUILDDIR_32_DEBUG}
|
BUILDDIR_32=${BUILDDIR_32_DEBUG}
|
||||||
BUILDDIR_64=${BUILDDIR_64_DEBUG}
|
BUILDDIR_64=${BUILDDIR_64_DEBUG}
|
||||||
|
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_DEBUG}
|
||||||
|
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_DEBUG}
|
||||||
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
|
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# is the XP-compatible toolchain installed?
|
||||||
|
XP_MUST_BUILD=0
|
||||||
|
# 64-bit WinXP builds are disabled by default; set to 1 to opt in
|
||||||
|
BUILD_XP_64_ENABLE=0
|
||||||
|
BUILD_XP_32=0
|
||||||
|
BUILD_XP_64=0
|
||||||
|
if [ -f "$TOOLCHAIN_WINXP_32" ]; then
|
||||||
|
BUILD_XP_32=1;
|
||||||
|
elif ((XP_MUST_BUILD > 0))
|
||||||
|
then
|
||||||
|
echo "WinXP 32bit toolchain not available, aborting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ((BUILD_XP_64_ENABLE > 0)) && [ -f "$TOOLCHAIN_WINXP_64" ]; then
|
||||||
|
BUILD_XP_64=1;
|
||||||
|
fi
|
||||||
|
|
||||||
# determine number of cores
|
# determine number of cores
|
||||||
CORES=$(nproc)
|
CORES=$(nproc)
|
||||||
|
|
||||||
@@ -90,6 +119,18 @@ echo "Git Branch: $GIT_BRANCH"
|
|||||||
echo "Git Head: $GIT_HEAD"
|
echo "Git Head: $GIT_HEAD"
|
||||||
echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
|
echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
|
||||||
echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
|
echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
|
||||||
|
if ((BUILD_XP_32 > 0))
|
||||||
|
then
|
||||||
|
echo "Toolchain for WinXP 32bit targets: $TOOLCHAIN_WINXP_32"
|
||||||
|
else
|
||||||
|
echo "WinXP 32bit toolchain not available, skipping WinXP 32bit builds"
|
||||||
|
fi
|
||||||
|
if ((BUILD_XP_64 > 0))
|
||||||
|
then
|
||||||
|
echo "Toolchain for WinXP 64bit targets: $TOOLCHAIN_WINXP_64"
|
||||||
|
else
|
||||||
|
echo "WinXP 64bit builds disabled, skipping WinXP 64bit builds"
|
||||||
|
fi
|
||||||
echo "Distribution Name: $DIST_NAME"
|
echo "Distribution Name: $DIST_NAME"
|
||||||
echo "Build Type: $BUILD_TYPE"
|
echo "Build Type: $BUILD_TYPE"
|
||||||
echo "Cores: $CORES"
|
echo "Cores: $CORES"
|
||||||
@@ -130,11 +171,71 @@ time (
|
|||||||
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
|
popd > /dev/null
|
||||||
|
|
||||||
|
if ((BUILD_XP_32 > 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
|
||||||
|
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DSPICE_XP=ON "$OLDPWD" && ninja ${TARGETS_XP32}
|
||||||
|
popd > /dev/null
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "Skipping WinXP 32bit builds, toolchain not specified"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((BUILD_XP_64 > 0))
|
||||||
|
then
|
||||||
|
# 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
|
||||||
|
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DSPICE_XP=ON "$OLDPWD" && ninja ${TARGETS_XP64}
|
||||||
|
popd > /dev/null
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "Skipping WinXP 64bit builds"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Compilation process done :)"
|
echo "Compilation process done :)"
|
||||||
echo "==========================="
|
echo "==========================="
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if ((BUILD_XP_32 > 0)) || ((BUILD_XP_64 > 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
|
||||||
|
if ((BUILD_XP_64 > 0))
|
||||||
|
then
|
||||||
|
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
|
||||||
|
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
|
||||||
|
fi
|
||||||
|
if ((BUILD_XP_32 > 0))
|
||||||
|
then
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
# generate PDBs
|
# generate PDBs
|
||||||
if false # ((DEBUG > 0))
|
if false # ((DEBUG > 0))
|
||||||
then
|
then
|
||||||
@@ -191,6 +292,12 @@ rm -rf ${OUTDIR_EXTRAS}
|
|||||||
mkdir -p ${OUTDIR_EXTRAS}
|
mkdir -p ${OUTDIR_EXTRAS}
|
||||||
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
|
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
|
||||||
mkdir -p ${OUTDIR_EXTRAS}/linux
|
mkdir -p ${OUTDIR_EXTRAS}/linux
|
||||||
|
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/32
|
||||||
|
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/64
|
||||||
|
if ((BUILD_XP_32 > 0)) || ((BUILD_XP_64 > 0))
|
||||||
|
then
|
||||||
|
mkdir -p ${OUTDIR_EXTRAS}/winxp
|
||||||
|
fi
|
||||||
|
|
||||||
if false # ((DEBUG > 0))
|
if false # ((DEBUG > 0))
|
||||||
then
|
then
|
||||||
@@ -199,16 +306,8 @@ then
|
|||||||
cp ${BUILDDIR_32}/spicetools/spicecfg-pdb.pdb ${OUTDIR} 2>/dev/null
|
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.exe ${OUTDIR} 2>/dev/null
|
||||||
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.pdb ${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.exe ${OUTDIR} 2>/dev/null
|
||||||
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.pdb ${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
|
else
|
||||||
# release files
|
# release files
|
||||||
cp ${BUILDDIR_32}/spicetools/spicecfg.exe ${OUTDIR} 2>/dev/null
|
cp ${BUILDDIR_32}/spicetools/spicecfg.exe ${OUTDIR} 2>/dev/null
|
||||||
@@ -216,16 +315,24 @@ else
|
|||||||
cp ${BUILDDIR_32}/spicetools/32/spice.exe ${OUTDIR} 2>/dev/null
|
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_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/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.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/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/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/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||||
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.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/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_32 > 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
|
||||||
|
fi
|
||||||
|
if ((BUILD_XP_64 > 0))
|
||||||
|
then
|
||||||
|
cp ${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# pack source files to output directory
|
# pack source files to output directory
|
||||||
@@ -238,6 +345,9 @@ then
|
|||||||
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
|
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# sdk headers
|
||||||
|
cp -r ./sdk/include ${OUTDIR_EXTRAS}/sdk
|
||||||
|
|
||||||
# copy resources
|
# copy resources
|
||||||
rm -rf ${OUTDIR_EXTRAS}/api
|
rm -rf ${OUTDIR_EXTRAS}/api
|
||||||
mkdir ${OUTDIR_EXTRAS}/api
|
mkdir ${OUTDIR_EXTRAS}/api
|
||||||
@@ -250,10 +360,10 @@ then
|
|||||||
echo "Building dist..."
|
echo "Building dist..."
|
||||||
mkdir -p ${DIST_FOLDER}
|
mkdir -p ${DIST_FOLDER}
|
||||||
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
|
rm -rf "${DIST_FOLDER}/${DIST_NAME}"
|
||||||
pushd ${OUTDIR}/.. > /dev/null
|
pushd ${OUTDIR} > /dev/null
|
||||||
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT"
|
zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" . -x "extras/*" -z <<< "$DIST_COMMENT"
|
||||||
echo "Building extras..."
|
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
|
popd > /dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
docker build --pull external/docker -t spicetools/deps
|
docker build --pull external/docker -t spicetools/deps
|
||||||
docker build --build-context gitroot=%cd%/../../.git . -t spicetools/spice
|
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 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\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\spice.exe bin\spice2x\spice-pdb.exe bin\spice2x\spice-pdb.pdb
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
|
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
|
||||||
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
|
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
|
||||||
docker run --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
|
|
||||||
|
# Interactive TTY if available, so docker build can be Ctrl+C'd
|
||||||
|
DOCKER_FLAGS=""
|
||||||
|
[ -t 0 ] && DOCKER_FLAGS="-it"
|
||||||
|
|
||||||
|
docker run $DOCKER_FLAGS --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
|
|||||||
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
|
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:
|
case rawinput::DESTROYED:
|
||||||
return "Device unplugged (" + indexString + ")";
|
return "Device unplugged (" + indexString + ")";
|
||||||
default:
|
default:
|
||||||
@@ -203,6 +207,7 @@ float Analog::applyMultiplier(float value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float Analog::normalizeAnalogValue(float value) {
|
float Analog::normalizeAnalogValue(float value) {
|
||||||
|
if (getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||||
// effectively the same as fmodf(value, 1.f)
|
// effectively the same as fmodf(value, 1.f)
|
||||||
// for small values, this is MUCH faster than fmodf.
|
// for small values, this is MUCH faster than fmodf.
|
||||||
float new_value = value;
|
float new_value = value;
|
||||||
@@ -213,12 +218,27 @@ float Analog::normalizeAnalogValue(float value) {
|
|||||||
new_value += 1.f;
|
new_value += 1.f;
|
||||||
}
|
}
|
||||||
return new_value;
|
return new_value;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// clamp to [0, 1] range
|
||||||
|
return std::clamp(value, 0.f, 1.f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float Analog::applyDeadzone(float raw_value) {
|
float Analog::applyDeadzone(float raw_value) {
|
||||||
float value = raw_value;
|
float value = raw_value;
|
||||||
const auto deadzone = this->getDeadzone();
|
auto deadzone = this->getDeadzone();
|
||||||
if (deadzone > 0) {
|
|
||||||
|
// 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
|
// calculate values
|
||||||
const auto delta = value - 0.5f;
|
const auto delta = value - 0.5f;
|
||||||
@@ -251,7 +271,7 @@ float Analog::applyDeadzone(float raw_value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (deadzone < 0) {
|
} else {
|
||||||
|
|
||||||
// invert for mirror
|
// invert for mirror
|
||||||
if (this->getDeadzoneMirror()) {
|
if (this->getDeadzoneMirror()) {
|
||||||
@@ -259,8 +279,8 @@ float Analog::applyDeadzone(float raw_value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deadzone from minimum value
|
// deadzone from minimum value
|
||||||
if (deadzone > -1 && value > -deadzone) {
|
if (deadzone < 1.f && deadzone < value) {
|
||||||
value = std::min(1.f, (value + deadzone) / (1.f + deadzone));
|
value = std::max(0.f, (value - deadzone) / (1.f - deadzone));
|
||||||
} else {
|
} else {
|
||||||
value = 0.f;
|
value = 0.f;
|
||||||
}
|
}
|
||||||
@@ -272,3 +292,74 @@ float Analog::applyDeadzone(float raw_value) {
|
|||||||
}
|
}
|
||||||
return 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;
|
||||||
|
}
|
||||||
|
|||||||
+46
-18
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -13,12 +14,32 @@ namespace rawinput {
|
|||||||
class RawInputManager;
|
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 {
|
struct AnalogMovingAverage {
|
||||||
double time_in_ms;
|
double time_in_ms;
|
||||||
float sine;
|
float sine;
|
||||||
float cosine;
|
float cosine;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AnalogDelayEntry {
|
||||||
|
double time_in_ms;
|
||||||
|
float value;
|
||||||
|
};
|
||||||
|
|
||||||
class Analog {
|
class Analog {
|
||||||
private:
|
private:
|
||||||
std::string name;
|
std::string name;
|
||||||
@@ -31,10 +52,11 @@ private:
|
|||||||
float last_state = 0.5f;
|
float last_state = 0.5f;
|
||||||
bool sensitivity_set = false;
|
bool sensitivity_set = false;
|
||||||
bool deadzone_set = false;
|
bool deadzone_set = false;
|
||||||
|
GameAPI::Analogs::AnalogType type = GameAPI::Analogs::AnalogType::Circular;
|
||||||
|
|
||||||
// smoothing function
|
// smoothing function
|
||||||
bool smoothing = false;
|
bool smoothing = false;
|
||||||
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history;
|
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history = {};
|
||||||
int vector_history_index = 0;
|
int vector_history_index = 0;
|
||||||
float smoothed_last_state = 0.f;
|
float smoothed_last_state = 0.f;
|
||||||
|
|
||||||
@@ -48,12 +70,13 @@ private:
|
|||||||
unsigned short divisor_region = 0;
|
unsigned short divisor_region = 0;
|
||||||
|
|
||||||
// relative input mode
|
// 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;
|
bool relative_mode = false;
|
||||||
|
|
||||||
// circular buffer (delayed input)
|
// delay
|
||||||
int delay_buffer_depth = 0;
|
uint32_t delay_ms = 0;
|
||||||
std::queue<float> delay_buffer;
|
std::queue<AnalogDelayEntry> delayed_inputs;
|
||||||
|
|
||||||
float calculateAngularDifference(float old_rads, float new_rads);
|
float calculateAngularDifference(float old_rads, float new_rads);
|
||||||
float normalizeAngle(float rads);
|
float normalizeAngle(float rads);
|
||||||
@@ -66,7 +89,8 @@ public:
|
|||||||
float override_state = 0.5f;
|
float override_state = 0.5f;
|
||||||
|
|
||||||
explicit Analog(std::string name) : name(std::move(name)) {
|
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);
|
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||||
@@ -74,6 +98,8 @@ public:
|
|||||||
float applyAngularSensitivity(float raw_rads);
|
float applyAngularSensitivity(float raw_rads);
|
||||||
float applyMultiplier(float raw_value);
|
float applyMultiplier(float raw_value);
|
||||||
float applyDeadzone(float raw_value);
|
float applyDeadzone(float raw_value);
|
||||||
|
float getRelativeModeValue(float raw_value);
|
||||||
|
float getDelayedValue(float raw_value);
|
||||||
|
|
||||||
inline bool isSet() {
|
inline bool isSet() {
|
||||||
if (this->override_enabled) {
|
if (this->override_enabled) {
|
||||||
@@ -90,8 +116,8 @@ public:
|
|||||||
deadzone_mirror = false;
|
deadzone_mirror = false;
|
||||||
setMultiplier(1);
|
setMultiplier(1);
|
||||||
setRelativeMode(false);
|
setRelativeMode(false);
|
||||||
setDelayBufferDepth(0);
|
|
||||||
setLastState(0.5f);
|
setLastState(0.5f);
|
||||||
|
setDelayMs(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void clearBindings() {
|
inline void clearBindings() {
|
||||||
@@ -193,25 +219,27 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void setRelativeMode(bool relative_mode) {
|
inline void setRelativeMode(bool relative_mode) {
|
||||||
|
if (relative_mode) {
|
||||||
|
this->smoothing = false;
|
||||||
|
}
|
||||||
this->relative_mode = relative_mode;
|
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) {
|
inline GameAPI::Analogs::AnalogType getType() const {
|
||||||
this->absolute_value_for_rel_mode =
|
return this->type;
|
||||||
normalizeAnalogValue(this->absolute_value_for_rel_mode + relative_delta);
|
|
||||||
return this->absolute_value_for_rel_mode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int getDelayBufferDepth() const {
|
inline void setType(GameAPI::Analogs::AnalogType type) {
|
||||||
return this->delay_buffer_depth;
|
this->type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void setDelayBufferDepth(int depth) {
|
inline uint32_t getDelayMs() const {
|
||||||
this->delay_buffer_depth = depth;
|
return this->delay_ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::queue<float> &getDelayBuffer() {
|
inline void setDelayMs(uint32_t delay_ms) {
|
||||||
return this->delay_buffer;
|
this->delay_ms = delay_ms;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+144
-46
@@ -1,5 +1,6 @@
|
|||||||
#include "api.h"
|
#include "api.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "launcher/superexit.h"
|
#include "launcher/superexit.h"
|
||||||
@@ -198,9 +199,19 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
|||||||
if (vKey < value_states->size()) {
|
if (vKey < value_states->size()) {
|
||||||
auto value = value_states->at(vKey);
|
auto value = value_states->at(vKey);
|
||||||
if (current_button->getAnalogType() == BAT_POSITIVE) {
|
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) {
|
} 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 {
|
} else {
|
||||||
state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||||
}
|
}
|
||||||
@@ -389,6 +400,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
|||||||
}
|
}
|
||||||
case rawinput::PIUIO_DEVICE: {
|
case rawinput::PIUIO_DEVICE: {
|
||||||
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -586,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;
|
float value = 0.5f;
|
||||||
if (!device) {
|
if (!device) {
|
||||||
return value;
|
return value;
|
||||||
@@ -658,30 +679,16 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deadzone
|
// 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);
|
value = analog.applyDeadzone(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (analog.getType() == AnalogType::Circular) {
|
||||||
|
|
||||||
if (analog.isRelativeMode()) {
|
if (analog.isRelativeMode()) {
|
||||||
float relative_delta = value - 0.5f;
|
value = analog.getRelativeModeValue(value);
|
||||||
// built-in scaling to make values reasonable
|
|
||||||
relative_delta /= 80.f;
|
|
||||||
|
|
||||||
// integer multiplier/divisor
|
|
||||||
const auto mult = analog.getMultiplier();
|
|
||||||
if (mult < -1) {
|
|
||||||
relative_delta /= -mult;
|
|
||||||
} else if (1 < mult) {
|
|
||||||
relative_delta *= mult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sensitivity (ranges from 0.0 to 4.0)
|
|
||||||
if (analog.isSensitivitySet()) {
|
|
||||||
relative_delta *= analog.getSensitivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
// translate relative movement to absolute value
|
|
||||||
value = analog.getAbsoluteValue(relative_delta);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// integer multiplier
|
// integer multiplier
|
||||||
@@ -712,27 +719,58 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
|||||||
value = rads * (float) M_1_TAU;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// delay
|
||||||
if (0 < analog.getDelayBufferDepth()) {
|
if (analog.getDelayMs() > 0) {
|
||||||
auto& queue = analog.getDelayBuffer();
|
value = analog.getDelayedValue(value);
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -767,6 +805,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -806,6 +856,42 @@ std::vector<Analog> GameAPI::Analogs::sortAnalogs(
|
|||||||
return sorted;
|
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) {
|
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
|
||||||
|
|
||||||
// check override
|
// check override
|
||||||
@@ -822,7 +908,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana
|
|||||||
return analog.getLastState();
|
return analog.getLastState();
|
||||||
}
|
}
|
||||||
|
|
||||||
float state = getState(device, analog);
|
float state = getState(manager, device, analog);
|
||||||
analog.setLastState(state);
|
analog.setLastState(state);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
@@ -904,7 +990,7 @@ void GameAPI::Lights::sortLightsWithCategory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) {
|
void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value) {
|
||||||
|
|
||||||
// check device
|
// check device
|
||||||
if (!device) {
|
if (!device) {
|
||||||
@@ -991,6 +1077,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1016,9 +1114,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh
|
|||||||
|
|
||||||
// write state
|
// write state
|
||||||
if (light.override_enabled) {
|
if (light.override_enabled) {
|
||||||
writeLight(device, light.getIndex(), light.override_state);
|
writeLight(manager, device, light.getIndex(), light.override_state);
|
||||||
} else {
|
} else {
|
||||||
writeLight(device, light.getIndex(), value);
|
writeLight(manager, device, light.getIndex(), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-9
@@ -85,14 +85,15 @@ namespace GameAPI {
|
|||||||
const std::vector<Analog> &analogs,
|
const std::vector<Analog> &analogs,
|
||||||
const std::vector<std::string> &analog_names);
|
const std::vector<std::string> &analog_names);
|
||||||
|
|
||||||
template<typename T>
|
struct AnalogWithType {
|
||||||
void sortAnalogs(std::vector<Analog> *analogs, T t) {
|
const std::string name;
|
||||||
const std::vector<std::string> analog_names { t };
|
const AnalogType type;
|
||||||
|
|
||||||
if (analogs) {
|
AnalogWithType(std::string name, AnalogType type) :
|
||||||
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names);
|
name(std::move(name)), type(type) {}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
void sortAnalogsWithType(std::vector<Analog> *analogs, const std::initializer_list<AnalogWithType> list);
|
||||||
|
|
||||||
template<typename T, typename... Rest>
|
template<typename T, typename... Rest>
|
||||||
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
|
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
|
||||||
@@ -103,7 +104,7 @@ namespace GameAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float getState(rawinput::Device *device, Analog &analog);
|
float getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog);
|
||||||
float getState(rawinput::RawInputManager *manager, Analog &analog);
|
float getState(rawinput::RawInputManager *manager, Analog &analog);
|
||||||
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
|
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
|
||||||
}
|
}
|
||||||
@@ -135,7 +136,7 @@ namespace GameAPI {
|
|||||||
|
|
||||||
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
|
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
|
||||||
|
|
||||||
void writeLight(rawinput::Device *device, int index, float value);
|
void writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value);
|
||||||
void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
|
void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
|
||||||
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
|
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
|
||||||
|
|
||||||
|
|||||||
@@ -278,6 +278,8 @@ std::string Button::getVKeyString() {
|
|||||||
return "Left Alt";
|
return "Left Alt";
|
||||||
case 0xA5:
|
case 0xA5:
|
||||||
return "Right Alt";
|
return "Right Alt";
|
||||||
|
case INVALID_VKEY:
|
||||||
|
return "Invalid";
|
||||||
default:
|
default:
|
||||||
|
|
||||||
// check win API
|
// check win API
|
||||||
@@ -304,7 +306,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
|||||||
std::string vKeyString = fmt::format("{:#x}", vKey);
|
std::string vKeyString = fmt::format("{:#x}", vKey);
|
||||||
|
|
||||||
// device must be existing
|
// 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 "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,6 +420,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
|||||||
}
|
}
|
||||||
case rawinput::PIUIO_DEVICE:
|
case rawinput::PIUIO_DEVICE:
|
||||||
return "PIUIO " + vKeyString;
|
return "PIUIO " + vKeyString;
|
||||||
|
case rawinput::XINPUT_GAMEPAD:
|
||||||
|
return fmt::format("{} ({})",
|
||||||
|
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
|
||||||
|
device->desc);
|
||||||
case rawinput::DESTROYED:
|
case rawinput::DESTROYED:
|
||||||
return "Device unplugged (" + vKeyString + ")";
|
return "Device unplugged (" + vKeyString + ")";
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ private:
|
|||||||
ButtonAnalogType analog_type = BAT_NONE;
|
ButtonAnalogType analog_type = BAT_NONE;
|
||||||
double debounce_up = 0.0;
|
double debounce_up = 0.0;
|
||||||
double debounce_down = 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 invert = false;
|
||||||
bool is_temporary = false;
|
bool is_temporary = false;
|
||||||
|
|
||||||
@@ -83,12 +84,20 @@ public:
|
|||||||
if (this->override_enabled) {
|
if (this->override_enabled) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// has a device
|
||||||
|
if (!this->device_identifier.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// naive, and has a valid vKey
|
||||||
if (this->vKey != INVALID_VKEY) {
|
if (this->vKey != INVALID_VKEY) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &alternative : this->alternatives) {
|
for (auto &alternative : this->alternatives) {
|
||||||
if (alternative.vKey != INVALID_VKEY) {
|
if (!alternative.getDeviceIdentifier().empty() ||
|
||||||
|
alternative.getVKey() != INVALID_VKEY) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,6 +110,7 @@ public:
|
|||||||
alternatives.clear();
|
alternatives.clear();
|
||||||
device_identifier = "";
|
device_identifier = "";
|
||||||
analog_type = BAT_NONE;
|
analog_type = BAT_NONE;
|
||||||
|
bat_threshold = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||||
@@ -161,6 +171,14 @@ public:
|
|||||||
this->debounce_down = debounce_time_down;
|
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 {
|
inline bool getInvert() const {
|
||||||
return this->invert;
|
return this->invert;
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-23
@@ -28,7 +28,7 @@ Config::Config() {
|
|||||||
this->status = false;
|
this->status = false;
|
||||||
if (CONFIG_PATH_OVERRIDE.length() > 0) {
|
if (CONFIG_PATH_OVERRIDE.length() > 0) {
|
||||||
this->configLocation = CONFIG_PATH_OVERRIDE;
|
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 {
|
} else {
|
||||||
this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml";
|
this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml";
|
||||||
// avoids logging the expanded appdata path as it contains user name
|
// avoids logging the expanded appdata path as it contains user name
|
||||||
@@ -57,7 +57,7 @@ Config::Config() {
|
|||||||
this->firstFillConfigFile();
|
this->firstFillConfigFile();
|
||||||
break;
|
break;
|
||||||
case tinyxml2::XMLError::XML_ERROR_FILE_COULD_NOT_BE_OPENED:
|
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;
|
break;
|
||||||
case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND:
|
case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND:
|
||||||
this->createConfigFile();
|
this->createConfigFile();
|
||||||
@@ -72,7 +72,7 @@ Config::Config() {
|
|||||||
case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN:
|
case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN:
|
||||||
case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT:
|
case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT:
|
||||||
case tinyxml2::XMLError::XML_ERROR_PARSING:
|
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->createConfigFile();
|
||||||
this->firstFillConfigFile();
|
this->firstFillConfigFile();
|
||||||
break;
|
break;
|
||||||
@@ -98,7 +98,7 @@ bool Config::getStatus() {
|
|||||||
return this->status;
|
return this->status;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Config::addGame(Game &game) {
|
bool Config::addGame(Game &game, bool save) {
|
||||||
tinyxml2::XMLNode *rootNode = this->configFile.LastChild();
|
tinyxml2::XMLNode *rootNode = this->configFile.LastChild();
|
||||||
tinyxml2::XMLElement *gameNodes = rootNode->FirstChildElement("game");
|
tinyxml2::XMLElement *gameNodes = rootNode->FirstChildElement("game");
|
||||||
|
|
||||||
@@ -162,6 +162,7 @@ bool Config::addGame(Game &game) {
|
|||||||
auto analogType = (int) BAT_NONE;
|
auto analogType = (int) BAT_NONE;
|
||||||
double debounce_up = 0.0;
|
double debounce_up = 0.0;
|
||||||
double debounce_down = 0.0;
|
double debounce_down = 0.0;
|
||||||
|
int bat_threshold = 0;
|
||||||
int velocity_threshold = 0;
|
int velocity_threshold = 0;
|
||||||
bool invert = false;
|
bool invert = false;
|
||||||
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||||
@@ -169,6 +170,7 @@ bool Config::addGame(Game &game) {
|
|||||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||||
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
||||||
|
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||||
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
||||||
@@ -180,6 +182,7 @@ bool Config::addGame(Game &game) {
|
|||||||
gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str());
|
||||||
gameButtonNode->SetAttribute("debounce_up", debounce_up);
|
gameButtonNode->SetAttribute("debounce_up", debounce_up);
|
||||||
gameButtonNode->SetAttribute("debounce_down", debounce_down);
|
gameButtonNode->SetAttribute("debounce_down", debounce_down);
|
||||||
|
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
||||||
gameButtonNode->SetAttribute("invert", invert);
|
gameButtonNode->SetAttribute("invert", invert);
|
||||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||||
@@ -188,6 +191,7 @@ bool Config::addGame(Game &game) {
|
|||||||
button->setAnalogType((ButtonAnalogType) analogType);
|
button->setAnalogType((ButtonAnalogType) analogType);
|
||||||
button->setDebounceUp(debounce_up);
|
button->setDebounceUp(debounce_up);
|
||||||
button->setDebounceDown(debounce_down);
|
button->setDebounceDown(debounce_down);
|
||||||
|
button->setBatThreshold(bat_threshold);
|
||||||
button->setVelocityThreshold(velocity_threshold);
|
button->setVelocityThreshold(velocity_threshold);
|
||||||
button->setInvert(invert);
|
button->setInvert(invert);
|
||||||
if (devid) {
|
if (devid) {
|
||||||
@@ -207,6 +211,7 @@ bool Config::addGame(Game &game) {
|
|||||||
gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType());
|
gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType());
|
||||||
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
||||||
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
||||||
|
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||||
@@ -248,7 +253,7 @@ bool Config::addGame(Game &game) {
|
|||||||
bool smoothing = false;
|
bool smoothing = false;
|
||||||
int multiplier = 1;
|
int multiplier = 1;
|
||||||
bool relative_mode = false;
|
bool relative_mode = false;
|
||||||
int delay_buffer_depth = 0;
|
uint32_t delay = 0;
|
||||||
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
|
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
|
||||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||||
@@ -257,7 +262,7 @@ bool Config::addGame(Game &game) {
|
|||||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||||
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
|
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
|
||||||
const char *devid = gameAnalogNode->Attribute("devid");
|
const char *devid = gameAnalogNode->Attribute("devid");
|
||||||
|
|
||||||
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
|
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
|
||||||
@@ -273,7 +278,7 @@ bool Config::addGame(Game &game) {
|
|||||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||||
} else {
|
} else {
|
||||||
it.setIndex(static_cast<unsigned short int>(index));
|
it.setIndex(static_cast<unsigned short int>(index));
|
||||||
@@ -285,7 +290,7 @@ bool Config::addGame(Game &game) {
|
|||||||
it.setSmoothing(smoothing);
|
it.setSmoothing(smoothing);
|
||||||
it.setMultiplier(multiplier);
|
it.setMultiplier(multiplier);
|
||||||
it.setRelativeMode(relative_mode);
|
it.setRelativeMode(relative_mode);
|
||||||
it.setDelayBufferDepth(delay_buffer_depth);
|
it.setDelayMs(delay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gameAnalogNode = this->configFile.NewElement("analog");
|
gameAnalogNode = this->configFile.NewElement("analog");
|
||||||
@@ -298,7 +303,7 @@ bool Config::addGame(Game &game) {
|
|||||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||||
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||||
}
|
}
|
||||||
@@ -432,6 +437,7 @@ bool Config::addGame(Game &game) {
|
|||||||
gameButtonNode->SetAttribute("analogtype", it.getAnalogType());
|
gameButtonNode->SetAttribute("analogtype", it.getAnalogType());
|
||||||
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
||||||
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
||||||
|
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||||
@@ -452,7 +458,7 @@ bool Config::addGame(Game &game) {
|
|||||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,13 +486,20 @@ bool Config::addGame(Game &game) {
|
|||||||
rootNode->InsertEndChild(gameNode);
|
rootNode->InsertEndChild(gameNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// save config
|
// save config (skipped when caller batches multiple addGame calls
|
||||||
|
// and flushes once at the end via Config::save())
|
||||||
|
if (save) {
|
||||||
this->saveConfigFile();
|
this->saveConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
// return success
|
// return success
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Config::save() {
|
||||||
|
this->saveConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
bool Config::updateBinding(const Game &game, const Button &button, int alternative) {
|
bool Config::updateBinding(const Game &game, const Button &button, int alternative) {
|
||||||
|
|
||||||
// get root node
|
// get root node
|
||||||
@@ -535,6 +548,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
|||||||
gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType());
|
gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType());
|
||||||
gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp());
|
gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp());
|
||||||
gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown());
|
gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown());
|
||||||
|
gameButtonNode->SetAttribute("bat_threshold", button.getBatThreshold());
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
|
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
|
||||||
gameButtonNode->SetAttribute("invert", button.getInvert());
|
gameButtonNode->SetAttribute("invert", button.getInvert());
|
||||||
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
||||||
@@ -551,6 +565,7 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
|||||||
gameButtonNode->SetAttribute("analogtype", 0);
|
gameButtonNode->SetAttribute("analogtype", 0);
|
||||||
gameButtonNode->SetAttribute("debounce_up", 0.0);
|
gameButtonNode->SetAttribute("debounce_up", 0.0);
|
||||||
gameButtonNode->SetAttribute("debounce_down", 0.0);
|
gameButtonNode->SetAttribute("debounce_down", 0.0);
|
||||||
|
gameButtonNode->SetAttribute("bat_threshold", 0);
|
||||||
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
||||||
gameButtonNode->SetAttribute("invert", false);
|
gameButtonNode->SetAttribute("invert", false);
|
||||||
gameButtonNode->SetAttribute("devid", "");
|
gameButtonNode->SetAttribute("devid", "");
|
||||||
@@ -684,7 +699,7 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
|||||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||||
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
|
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
|
||||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||||
} else {
|
} else {
|
||||||
gameAnalogNode = this->configFile.NewElement("analog");
|
gameAnalogNode = this->configFile.NewElement("analog");
|
||||||
@@ -696,7 +711,7 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
|||||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||||
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
|
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
|
||||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||||
}
|
}
|
||||||
@@ -946,12 +961,14 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
auto analogType = (int) BAT_NONE;
|
auto analogType = (int) BAT_NONE;
|
||||||
double debounce_up = 0.0;
|
double debounce_up = 0.0;
|
||||||
double debounce_down = 0.0;
|
double debounce_down = 0.0;
|
||||||
|
int bat_threshold = 0;
|
||||||
int velocity_threshold = 0;
|
int velocity_threshold = 0;
|
||||||
bool invert = false;
|
bool invert = false;
|
||||||
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||||
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
||||||
|
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||||
const char *devid = gameButtonNode->Attribute("devid");
|
const char *devid = gameButtonNode->Attribute("devid");
|
||||||
@@ -966,6 +983,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
alt.setAnalogType((ButtonAnalogType) analogType);
|
alt.setAnalogType((ButtonAnalogType) analogType);
|
||||||
alt.setDebounceUp(debounce_up);
|
alt.setDebounceUp(debounce_up);
|
||||||
alt.setDebounceDown(debounce_down);
|
alt.setDebounceDown(debounce_down);
|
||||||
|
alt.setBatThreshold(bat_threshold);
|
||||||
alt.setVelocityThreshold(velocity_threshold);
|
alt.setVelocityThreshold(velocity_threshold);
|
||||||
alt.setInvert(invert);
|
alt.setInvert(invert);
|
||||||
if (devid) {
|
if (devid) {
|
||||||
@@ -984,6 +1002,7 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
|||||||
button.setAnalogType((ButtonAnalogType) analogType);
|
button.setAnalogType((ButtonAnalogType) analogType);
|
||||||
button.setDebounceUp(debounce_up);
|
button.setDebounceUp(debounce_up);
|
||||||
button.setDebounceDown(debounce_down);
|
button.setDebounceDown(debounce_down);
|
||||||
|
button.setBatThreshold(bat_threshold);
|
||||||
button.setVelocityThreshold(velocity_threshold);
|
button.setVelocityThreshold(velocity_threshold);
|
||||||
button.setInvert(invert);
|
button.setInvert(invert);
|
||||||
if (devid) {
|
if (devid) {
|
||||||
@@ -1130,7 +1149,7 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
|||||||
bool smoothing = false;
|
bool smoothing = false;
|
||||||
int multiplier = 1;
|
int multiplier = 1;
|
||||||
bool relative_mode = false;
|
bool relative_mode = false;
|
||||||
int delay_buffer_depth = 0;
|
uint32_t delay = 0;
|
||||||
gameAnalogNode->QueryIntAttribute("index", &index);
|
gameAnalogNode->QueryIntAttribute("index", &index);
|
||||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||||
@@ -1139,7 +1158,7 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
|||||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||||
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
|
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
|
||||||
const char *devid = gameAnalogNode->Attribute("devid");
|
const char *devid = gameAnalogNode->Attribute("devid");
|
||||||
|
|
||||||
// create analog and add to list
|
// create analog and add to list
|
||||||
@@ -1152,7 +1171,7 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
|||||||
analog.setSmoothing(smoothing);
|
analog.setSmoothing(smoothing);
|
||||||
analog.setMultiplier(multiplier);
|
analog.setMultiplier(multiplier);
|
||||||
analog.setRelativeMode(relative_mode);
|
analog.setRelativeMode(relative_mode);
|
||||||
analog.setDelayBufferDepth(delay_buffer_depth);
|
analog.setDelayMs(delay);
|
||||||
if (devid) {
|
if (devid) {
|
||||||
analog.setDeviceIdentifier(devid);
|
analog.setDeviceIdentifier(devid);
|
||||||
}
|
}
|
||||||
@@ -1312,17 +1331,32 @@ bool Config::firstFillConfigFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Config::saveConfigFile() {
|
void Config::saveConfigFile() {
|
||||||
// create a .tmp file and write to it...
|
// write the new config to a .tmp file first...
|
||||||
const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false);
|
const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false);
|
||||||
if (xml_result != tinyxml2::XMLError::XML_SUCCESS) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// copy the .tmp file to the main file...
|
|
||||||
if (CopyFileW(this->configLocationTemp.c_str(), this->configLocation.c_str(), false) == 0) {
|
// ...flush the .tmp file to disk so a crash/power loss can't leave it half-written...
|
||||||
log_warning("cfg", "CopyFileA failed: 0x{:08x}", GetLastError());
|
HANDLE tmp_handle = CreateFileW(
|
||||||
|
this->configLocationTemp.c_str(),
|
||||||
|
GENERIC_WRITE, FILE_SHARE_READ, nullptr,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
|
if (tmp_handle != INVALID_HANDLE_VALUE) {
|
||||||
|
FlushFileBuffers(tmp_handle);
|
||||||
|
CloseHandle(tmp_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...then atomically replace the real config with the .tmp file.
|
||||||
|
// Unlike CopyFile (which truncates and rewrites the destination in place),
|
||||||
|
// an NTFS rename is atomic: the existing config is never left half-overwritten,
|
||||||
|
// so an interrupted save can't corrupt it. MoveFileEx also removes the .tmp on success.
|
||||||
|
if (MoveFileExW(
|
||||||
|
this->configLocationTemp.c_str(),
|
||||||
|
this->configLocation.c_str(),
|
||||||
|
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0) {
|
||||||
|
log_warning("cfg", "MoveFileExW failed: 0x{:08x}", GetLastError());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// delete the .tmp file (not critical if this fails)
|
|
||||||
DeleteFileW(this->configLocationTemp.c_str());
|
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,12 @@ public:
|
|||||||
bool getStatus();
|
bool getStatus();
|
||||||
bool createConfigFile();
|
bool createConfigFile();
|
||||||
|
|
||||||
bool addGame(Game &game);
|
bool addGame(Game &game, bool save = true);
|
||||||
|
|
||||||
|
// flush pending in-memory changes to disk; intended to be paired with
|
||||||
|
// addGame(game, false) in bulk-init paths so we write the XML once instead
|
||||||
|
// of once per game
|
||||||
|
void save();
|
||||||
|
|
||||||
bool updateBinding(const Game &game, const Button &button, int alternative);
|
bool updateBinding(const Game &game, const Button &button, int alternative);
|
||||||
bool updateBinding(const Game &game, const Analog &analog);
|
bool updateBinding(const Game &game, const Analog &analog);
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
#include "configurator.h"
|
#include "configurator.h"
|
||||||
|
|
||||||
|
#include <d3d9.h>
|
||||||
|
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
|
#include "util/libutils.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
namespace cfg {
|
namespace cfg {
|
||||||
|
|
||||||
// globals
|
// globals
|
||||||
bool CONFIGURATOR_STANDALONE = false;
|
bool CONFIGURATOR_STANDALONE = false;
|
||||||
|
bool CONFIGURATOR_FORCE_SOFTWARE_RENDER = false;
|
||||||
ConfigType CONFIGURATOR_TYPE = ConfigType::Config;
|
ConfigType CONFIGURATOR_TYPE = ConfigType::Config;
|
||||||
|
|
||||||
Configurator::Configurator() {
|
Configurator::Configurator() {
|
||||||
@@ -16,13 +21,98 @@ namespace cfg {
|
|||||||
CONFIGURATOR_STANDALONE = false;
|
CONFIGURATOR_STANDALONE = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt to bring up a D3D9 device backing the configurator's window so the
|
||||||
|
// overlay can use the hardware-accelerated imgui_impl_dx9 path instead of
|
||||||
|
// the CPU rasterizer. Returns true if both Direct3DCreate9 and CreateDevice
|
||||||
|
// succeed; the caller falls back to overlay::create_software() otherwise so
|
||||||
|
// that environments without D3D9 (Wine without dxvk, headless test boxes,
|
||||||
|
// GPUs whose drivers reject HAL) still get a working configurator.
|
||||||
|
static bool try_init_d3d9(ConfiguratorWindow &wnd) {
|
||||||
|
if (cfg::CONFIGURATOR_FORCE_SOFTWARE_RENDER) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wnd.hWnd == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load d3d9.dll dynamically rather than linking against it statically.
|
||||||
|
// a static import would force the system d3d9.dll to load at process
|
||||||
|
// startup for the main spice executable too (this file is shared with
|
||||||
|
// spice.exe), which loads system32\d3d9.dll before the modules directory
|
||||||
|
// is added to the DLL search path - preventing a user-supplied DXVK
|
||||||
|
// d3d9.dll in modules from ever loading for the game.
|
||||||
|
typedef IDirect3D9 *(WINAPI *Direct3DCreate9_t)(UINT);
|
||||||
|
HMODULE d3d9_module = libutils::try_library("d3d9.dll");
|
||||||
|
if (d3d9_module == nullptr) {
|
||||||
|
log_warning("configurator", "could not load d3d9.dll, falling back to software renderer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto Direct3DCreate9_fn =
|
||||||
|
reinterpret_cast<Direct3DCreate9_t>(libutils::try_proc(d3d9_module, "Direct3DCreate9"));
|
||||||
|
if (Direct3DCreate9_fn == nullptr) {
|
||||||
|
log_warning("configurator", "could not find Direct3DCreate9, falling back to software renderer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDirect3D9 *d3d = Direct3DCreate9_fn(D3D_SDK_VERSION);
|
||||||
|
if (d3d == nullptr) {
|
||||||
|
log_warning("configurator", "Direct3DCreate9 returned NULL, falling back to software renderer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3DPRESENT_PARAMETERS pp {};
|
||||||
|
pp.Windowed = TRUE;
|
||||||
|
pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||||
|
// D3DFMT_UNKNOWN -> driver picks the current desktop format
|
||||||
|
pp.BackBufferFormat = D3DFMT_UNKNOWN;
|
||||||
|
pp.BackBufferWidth = static_cast<UINT>(wnd.client_width);
|
||||||
|
pp.BackBufferHeight = static_cast<UINT>(wnd.client_height);
|
||||||
|
pp.hDeviceWindow = wnd.hWnd;
|
||||||
|
pp.EnableAutoDepthStencil = FALSE;
|
||||||
|
pp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
|
||||||
|
|
||||||
|
IDirect3DDevice9 *device = nullptr;
|
||||||
|
// SOFTWARE_VERTEXPROCESSING is the most compatible behavior flag; the UI
|
||||||
|
// is tiny so we don't need hardware T&L. FPU_PRESERVE keeps our floating
|
||||||
|
// point environment intact in case other spice code relies on it.
|
||||||
|
const DWORD behavior_flags = D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE;
|
||||||
|
HRESULT hr = d3d->CreateDevice(
|
||||||
|
D3DADAPTER_DEFAULT,
|
||||||
|
D3DDEVTYPE_HAL,
|
||||||
|
wnd.hWnd,
|
||||||
|
behavior_flags,
|
||||||
|
&pp,
|
||||||
|
&device);
|
||||||
|
if (FAILED(hr) || device == nullptr) {
|
||||||
|
log_warning("configurator",
|
||||||
|
"D3D9 CreateDevice failed (hr={:#x}), falling back to software renderer",
|
||||||
|
static_cast<unsigned int>(hr));
|
||||||
|
d3d->Release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wnd.d3d = d3d;
|
||||||
|
wnd.device = device;
|
||||||
|
wnd.pp = pp;
|
||||||
|
wnd.use_d3d9 = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Configurator::run() {
|
void Configurator::run() {
|
||||||
|
|
||||||
// create instance
|
// bring up the overlay against either a real D3D9 device or the software
|
||||||
|
// rasterizer. The choice is one-shot - the in-game overlay always uses
|
||||||
|
// the same renderer the configurator picked here.
|
||||||
overlay::ENABLED = true;
|
overlay::ENABLED = true;
|
||||||
|
if (try_init_d3d9(this->wnd)) {
|
||||||
|
log_info("configurator", "using D3D9 hardware-accelerated renderer");
|
||||||
|
overlay::create_d3d9(this->wnd.hWnd, this->wnd.d3d, this->wnd.device);
|
||||||
|
} else {
|
||||||
|
log_info("configurator", "using software renderer");
|
||||||
overlay::create_software(this->wnd.hWnd);
|
overlay::create_software(this->wnd.hWnd);
|
||||||
|
}
|
||||||
overlay::OVERLAY->set_active(true);
|
overlay::OVERLAY->set_active(true);
|
||||||
overlay::OVERLAY->hotkeys_enable = false;
|
|
||||||
ImGui::GetIO().MouseDrawCursor = false;
|
ImGui::GetIO().MouseDrawCursor = false;
|
||||||
|
|
||||||
// run window
|
// run window
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace cfg {
|
|||||||
// globals
|
// globals
|
||||||
extern bool CONFIGURATOR_STANDALONE;
|
extern bool CONFIGURATOR_STANDALONE;
|
||||||
extern ConfigType CONFIGURATOR_TYPE;
|
extern ConfigType CONFIGURATOR_TYPE;
|
||||||
|
extern bool CONFIGURATOR_FORCE_SOFTWARE_RENDER;
|
||||||
|
|
||||||
class Configurator {
|
class Configurator {
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
#include "configurator_wnd.h"
|
#include "configurator_wnd.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <windowsx.h>
|
||||||
|
|
||||||
#include "build/defs.h"
|
#include "build/defs.h"
|
||||||
|
#include "external/imgui/imgui.h"
|
||||||
#include "launcher/shutdown.h"
|
#include "launcher/shutdown.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
|
#include "util/utils.h"
|
||||||
#include "cfg/configurator.h"
|
#include "cfg/configurator.h"
|
||||||
|
|
||||||
#include "icon.h"
|
#include "icon.h"
|
||||||
@@ -14,8 +21,177 @@ static const char *CLASS_NAME = "ConfiguratorWindow";
|
|||||||
static std::string WINDOW_TITLE;
|
static std::string WINDOW_TITLE;
|
||||||
static int WINDOW_SIZE_X = 800;
|
static int WINDOW_SIZE_X = 800;
|
||||||
static int WINDOW_SIZE_Y = 600;
|
static int WINDOW_SIZE_Y = 600;
|
||||||
|
static const int WINDOW_MIN_SIZE_X = 540;
|
||||||
|
static const int WINDOW_MIN_SIZE_Y = 300;
|
||||||
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
||||||
|
|
||||||
|
static const UINT_PTR RENDER_TIMER_ID = 1;
|
||||||
|
|
||||||
|
// Map a virtual key code to the matching ImGuiKey. Mirrors the table used by
|
||||||
|
// the in-game ImGui spice backend so navigation keys behave identically when
|
||||||
|
// the configurator runs standalone and drives ImGui from Win32 messages.
|
||||||
|
static ImGuiKey vk_to_imgui_key(WPARAM vkey) {
|
||||||
|
switch (vkey) {
|
||||||
|
case VK_TAB: return ImGuiKey_Tab;
|
||||||
|
case VK_LEFT: return ImGuiKey_LeftArrow;
|
||||||
|
case VK_RIGHT: return ImGuiKey_RightArrow;
|
||||||
|
case VK_UP: return ImGuiKey_UpArrow;
|
||||||
|
case VK_DOWN: return ImGuiKey_DownArrow;
|
||||||
|
case VK_PRIOR: return ImGuiKey_PageUp;
|
||||||
|
case VK_NEXT: return ImGuiKey_PageDown;
|
||||||
|
case VK_HOME: return ImGuiKey_Home;
|
||||||
|
case VK_END: return ImGuiKey_End;
|
||||||
|
case VK_INSERT: return ImGuiKey_Insert;
|
||||||
|
case VK_DELETE: return ImGuiKey_Delete;
|
||||||
|
case VK_BACK: return ImGuiKey_Backspace;
|
||||||
|
case VK_SPACE: return ImGuiKey_Space;
|
||||||
|
case VK_RETURN: return ImGuiKey_Enter;
|
||||||
|
case VK_ESCAPE: return ImGuiKey_Escape;
|
||||||
|
case VK_LSHIFT: return ImGuiKey_LeftShift;
|
||||||
|
case VK_RSHIFT: return ImGuiKey_RightShift;
|
||||||
|
case VK_SHIFT: return ImGuiKey_LeftShift;
|
||||||
|
case VK_LCONTROL: return ImGuiKey_LeftCtrl;
|
||||||
|
case VK_RCONTROL: return ImGuiKey_RightCtrl;
|
||||||
|
case VK_CONTROL: return ImGuiKey_LeftCtrl;
|
||||||
|
case 'A': return ImGuiKey_A;
|
||||||
|
case 'C': return ImGuiKey_C;
|
||||||
|
case 'V': return ImGuiKey_V;
|
||||||
|
case 'X': return ImGuiKey_X;
|
||||||
|
case 'Y': return ImGuiKey_Y;
|
||||||
|
case 'Z': return ImGuiKey_Z;
|
||||||
|
default: return ImGuiKey_None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ImGuiKey vk_to_imgui_mod_key(WPARAM vkey) {
|
||||||
|
switch (vkey) {
|
||||||
|
case VK_SHIFT:
|
||||||
|
case VK_LSHIFT:
|
||||||
|
case VK_RSHIFT:
|
||||||
|
return ImGuiMod_Shift;
|
||||||
|
case VK_CONTROL:
|
||||||
|
case VK_LCONTROL:
|
||||||
|
case VK_RCONTROL:
|
||||||
|
return ImGuiMod_Ctrl;
|
||||||
|
case VK_MENU:
|
||||||
|
case VK_LMENU:
|
||||||
|
case VK_RMENU:
|
||||||
|
return ImGuiMod_Alt;
|
||||||
|
default:
|
||||||
|
return ImGuiMod_None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static cfg::ConfiguratorWindow *get_state(HWND hWnd) {
|
||||||
|
return reinterpret_cast<cfg::ConfiguratorWindow *>(
|
||||||
|
GetWindowLongPtrW(hWnd, GWLP_USERDATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
// computes the top-left position that centers a window of the given size on
|
||||||
|
// the primary monitor's work area (the desktop minus the taskbar); falls back
|
||||||
|
// to (0, 0) if the monitor info can't be queried.
|
||||||
|
static POINT center_on_primary_monitor(int width, int height) {
|
||||||
|
POINT pos = { 0, 0 };
|
||||||
|
HMONITOR mon = MonitorFromPoint(pos, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
MONITORINFO mi {};
|
||||||
|
mi.cbSize = sizeof(mi);
|
||||||
|
if (GetMonitorInfo(mon, &mi)) {
|
||||||
|
const int work_w = mi.rcWork.right - mi.rcWork.left;
|
||||||
|
const int work_h = mi.rcWork.bottom - mi.rcWork.top;
|
||||||
|
pos.x = mi.rcWork.left + (work_w - width) / 2;
|
||||||
|
pos.y = mi.rcWork.top + (work_h - height) / 2;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the refresh rate (Hz) of the monitor the window currently lives on,
|
||||||
|
// clamped to a sane range. Falls back to 60 if detection fails or the value
|
||||||
|
// looks invalid (DEVMODE may report 0 or 1 to mean "use hardware default").
|
||||||
|
static UINT detect_monitor_refresh_hz(HWND hWnd) {
|
||||||
|
UINT hz = 0;
|
||||||
|
HMONITOR mon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
|
||||||
|
if (mon) {
|
||||||
|
MONITORINFOEXW mi {};
|
||||||
|
mi.cbSize = sizeof(mi);
|
||||||
|
if (GetMonitorInfoW(mon, reinterpret_cast<LPMONITORINFO>(&mi))) {
|
||||||
|
DEVMODEW dm {};
|
||||||
|
dm.dmSize = sizeof(dm);
|
||||||
|
if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm)) {
|
||||||
|
hz = dm.dmDisplayFrequency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hz <= 1) {
|
||||||
|
HDC hdc = GetDC(hWnd);
|
||||||
|
if (hdc) {
|
||||||
|
int v = GetDeviceCaps(hdc, VREFRESH);
|
||||||
|
if (v > 1) {
|
||||||
|
hz = static_cast<UINT>(v);
|
||||||
|
}
|
||||||
|
ReleaseDC(hWnd, hdc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hz < 30 || hz > 240) {
|
||||||
|
hz = 60;
|
||||||
|
}
|
||||||
|
return hz;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Software-path WM_PAINT: blit the overlay's pixel buffer to the window using
|
||||||
|
// SetDIBitsToDevice. Avoids creating/destroying a GDI HBITMAP every frame.
|
||||||
|
static void paint_software(HWND hWnd) {
|
||||||
|
if (!overlay::OVERLAY) {
|
||||||
|
PAINTSTRUCT ps {};
|
||||||
|
BeginPaint(hWnd, &ps);
|
||||||
|
EndPaint(hWnd, &ps);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
uint32_t *pixel_data = overlay::OVERLAY->sw_get_pixel_data(&width, &height);
|
||||||
|
|
||||||
|
PAINTSTRUCT paint {};
|
||||||
|
HDC hdc = BeginPaint(hWnd, &paint);
|
||||||
|
|
||||||
|
if (pixel_data && width > 0 && height > 0) {
|
||||||
|
BITMAPINFO bmi {};
|
||||||
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmi.bmiHeader.biWidth = width;
|
||||||
|
// negative height -> top-down DIB, matching how ImGui packs pixels
|
||||||
|
bmi.bmiHeader.biHeight = -height;
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB;
|
||||||
|
|
||||||
|
SetDIBitsToDevice(
|
||||||
|
hdc,
|
||||||
|
0, 0,
|
||||||
|
width, height,
|
||||||
|
0, 0,
|
||||||
|
0, height,
|
||||||
|
pixel_data,
|
||||||
|
&bmi,
|
||||||
|
DIB_RGB_COLORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
EndPaint(hWnd, &paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cfg::ConfiguratorWindow::start_timer() {
|
||||||
|
if (!this->timer_running && this->hWnd) {
|
||||||
|
SetTimer(this->hWnd, RENDER_TIMER_ID, this->timer_interval_ms, nullptr);
|
||||||
|
this->timer_running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cfg::ConfiguratorWindow::stop_timer() {
|
||||||
|
if (this->timer_running && this->hWnd) {
|
||||||
|
KillTimer(this->hWnd, RENDER_TIMER_ID);
|
||||||
|
this->timer_running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cfg::ConfiguratorWindow::ConfiguratorWindow() {
|
cfg::ConfiguratorWindow::ConfiguratorWindow() {
|
||||||
|
|
||||||
// register the window class
|
// register the window class
|
||||||
@@ -27,12 +203,22 @@ cfg::ConfiguratorWindow::ConfiguratorWindow() {
|
|||||||
wc.hIcon = WINDOW_ICON;
|
wc.hIcon = WINDOW_ICON;
|
||||||
RegisterClass(&wc);
|
RegisterClass(&wc);
|
||||||
|
|
||||||
|
// raise SetTimer resolution so high refresh rates (120/144Hz) are actually
|
||||||
|
// achievable. Without this, USER timers quantize to ~15.6ms and cap us
|
||||||
|
// near ~64 FPS. Uses the shared helper so it respects -notimerhacks
|
||||||
|
// (Use Legacy Timers) and the Win11 timer-throttling opt-out. The helper
|
||||||
|
// intentionally never calls timeEndPeriod; the kernel releases the request
|
||||||
|
// when spicecfg exits.
|
||||||
|
timeutils::set_timer_resolution();
|
||||||
|
|
||||||
// determine window title
|
// determine window title
|
||||||
if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
|
if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
|
||||||
WINDOW_TITLE = "spice2x config (" + to_string(VERSION_STRING_CFG) + ")";
|
WINDOW_TITLE = "spice2x config (" + to_string(VERSION_STRING_CFG) + ")";
|
||||||
WINDOW_SIZE_X = 800;
|
WINDOW_SIZE_X = 800;
|
||||||
WINDOW_SIZE_Y = 600;
|
WINDOW_SIZE_Y = 600;
|
||||||
}
|
}
|
||||||
|
this->client_width = WINDOW_SIZE_X;
|
||||||
|
this->client_height = WINDOW_SIZE_Y;
|
||||||
|
|
||||||
// open window
|
// open window
|
||||||
this->hWnd = CreateWindowEx(
|
this->hWnd = CreateWindowEx(
|
||||||
@@ -48,27 +234,58 @@ cfg::ConfiguratorWindow::ConfiguratorWindow() {
|
|||||||
|
|
||||||
if (this->hWnd) {
|
if (this->hWnd) {
|
||||||
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
|
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
|
||||||
|
// force dark title bar
|
||||||
|
set_window_dark_titlebar(this->hWnd, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg::ConfiguratorWindow::~ConfiguratorWindow() {
|
cfg::ConfiguratorWindow::~ConfiguratorWindow() {
|
||||||
|
|
||||||
|
this->stop_timer();
|
||||||
|
|
||||||
// close window
|
// close window
|
||||||
DestroyWindow(this->hWnd);
|
DestroyWindow(this->hWnd);
|
||||||
|
|
||||||
// unregister class
|
// unregister class
|
||||||
UnregisterClass(CLASS_NAME, GetModuleHandle(NULL));
|
UnregisterClass(CLASS_NAME, GetModuleHandle(NULL));
|
||||||
|
|
||||||
|
// release D3D9 resources if any
|
||||||
|
if (this->device) {
|
||||||
|
this->device->Release();
|
||||||
|
this->device = nullptr;
|
||||||
|
}
|
||||||
|
if (this->d3d) {
|
||||||
|
this->d3d->Release();
|
||||||
|
this->d3d = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cfg::ConfiguratorWindow::run() {
|
void cfg::ConfiguratorWindow::run() {
|
||||||
|
|
||||||
|
const POINT pos = center_on_primary_monitor(WINDOW_SIZE_X, WINDOW_SIZE_Y);
|
||||||
|
SetWindowPos(this->hWnd, HWND_TOP, pos.x, pos.y, WINDOW_SIZE_X, WINDOW_SIZE_Y, 0);
|
||||||
|
|
||||||
// show window
|
// show window
|
||||||
SetWindowPos(this->hWnd, HWND_TOP, 0, 0, WINDOW_SIZE_X, WINDOW_SIZE_Y, 0);
|
|
||||||
ShowWindow(this->hWnd, SW_SHOWNORMAL);
|
ShowWindow(this->hWnd, SW_SHOWNORMAL);
|
||||||
UpdateWindow(this->hWnd);
|
UpdateWindow(this->hWnd);
|
||||||
|
|
||||||
// draw overlay in 60 FPS
|
// SW_SHOWNORMAL usually activates a top-level window, but not always (e.g.,
|
||||||
SetTimer(this->hWnd, 1, 1000 / 60, nullptr);
|
// when the launching process doesn't have foreground rights to transfer).
|
||||||
|
// Force foreground+focus explicitly so WM_MOUSEWHEEL is routed to us from
|
||||||
|
// the very first frame; the Win32 default routes wheel events to the
|
||||||
|
// keyboard-focused window.
|
||||||
|
SetForegroundWindow(this->hWnd);
|
||||||
|
SetFocus(this->hWnd);
|
||||||
|
|
||||||
|
// match the render timer to the monitor refresh rate so scrolling stays smooth
|
||||||
|
// on 60/120/144 Hz panels. Idle CPU is bounded by overlay::sw_pixels_dirty
|
||||||
|
// (software path) and overlay::d3d9_frame_dirty (D3D9 path); the timer is also
|
||||||
|
// paused entirely when the window is minimized (see WM_SIZE handler).
|
||||||
|
const UINT hz = detect_monitor_refresh_hz(this->hWnd);
|
||||||
|
this->timer_interval_ms = std::max<UINT>(1, 1000 / hz);
|
||||||
|
log_info("configurator", "render timer {} ms ({} Hz)",
|
||||||
|
this->timer_interval_ms, hz);
|
||||||
|
this->start_timer();
|
||||||
|
|
||||||
// window loop
|
// window loop
|
||||||
BOOL ret;
|
BOOL ret;
|
||||||
@@ -94,6 +311,22 @@ LRESULT CALLBACK cfg::ConfiguratorWindow::window_proc(HWND hWnd, UINT uMsg, WPAR
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case WM_GETMINMAXINFO: {
|
||||||
|
|
||||||
|
// enforce a minimum window size so the UI can't be shrunk to a
|
||||||
|
// point where the tabs/controls become unusable. The minimum is
|
||||||
|
// expressed in client-area pixels and converted to a full window
|
||||||
|
// size that accounts for the current frame/title-bar style.
|
||||||
|
RECT rc = { 0, 0, WINDOW_MIN_SIZE_X, WINDOW_MIN_SIZE_Y };
|
||||||
|
DWORD style = static_cast<DWORD>(GetWindowLongPtrW(hWnd, GWL_STYLE));
|
||||||
|
DWORD ex_style = static_cast<DWORD>(GetWindowLongPtrW(hWnd, GWL_EXSTYLE));
|
||||||
|
if (AdjustWindowRectEx(&rc, style, FALSE, ex_style)) {
|
||||||
|
auto *mmi = reinterpret_cast<MINMAXINFO *>(lParam);
|
||||||
|
mmi->ptMinTrackSize.x = rc.right - rc.left;
|
||||||
|
mmi->ptMinTrackSize.y = rc.bottom - rc.top;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case WM_CREATE: {
|
case WM_CREATE: {
|
||||||
|
|
||||||
// set user data of window to class pointer
|
// set user data of window to class pointer
|
||||||
@@ -102,6 +335,72 @@ LRESULT CALLBACK cfg::ConfiguratorWindow::window_proc(HWND hWnd, UINT uMsg, WPAR
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case WM_SIZE: {
|
||||||
|
|
||||||
|
// pause/resume the render timer based on visibility
|
||||||
|
auto *state = get_state(hWnd);
|
||||||
|
if (state) {
|
||||||
|
if (wParam == SIZE_MINIMIZED) {
|
||||||
|
state->window_minimized = true;
|
||||||
|
state->stop_timer();
|
||||||
|
} else {
|
||||||
|
if (state->window_minimized) {
|
||||||
|
state->window_minimized = false;
|
||||||
|
// force a full repaint on the next frame
|
||||||
|
state->has_valid_draw_hash = false;
|
||||||
|
state->start_timer();
|
||||||
|
}
|
||||||
|
const int new_w = LOWORD(lParam);
|
||||||
|
const int new_h = HIWORD(lParam);
|
||||||
|
if (new_w > 0 && new_h > 0
|
||||||
|
&& (new_w != state->client_width || new_h != state->client_height)) {
|
||||||
|
state->client_width = new_w;
|
||||||
|
state->client_height = new_h;
|
||||||
|
|
||||||
|
// reset the D3D9 device with the new back-buffer size so the
|
||||||
|
// hardware-accelerated path matches the window dimensions.
|
||||||
|
if (state->use_d3d9 && state->device) {
|
||||||
|
if (overlay::OVERLAY) {
|
||||||
|
overlay::OVERLAY->reset_invalidate();
|
||||||
|
}
|
||||||
|
state->pp.BackBufferWidth = static_cast<UINT>(new_w);
|
||||||
|
state->pp.BackBufferHeight = static_cast<UINT>(new_h);
|
||||||
|
HRESULT hr = state->device->Reset(&state->pp);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
log_warning("configurator", "D3D9 device Reset failed, hr={:#x}",
|
||||||
|
static_cast<unsigned int>(hr));
|
||||||
|
}
|
||||||
|
if (overlay::OVERLAY) {
|
||||||
|
overlay::OVERLAY->reset_recreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// force a full repaint after resize
|
||||||
|
state->has_valid_draw_hash = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_DISPLAYCHANGE: {
|
||||||
|
|
||||||
|
// monitor refresh rate may have changed (or the window moved to a
|
||||||
|
// different monitor); re-detect and re-arm the render timer.
|
||||||
|
auto *state = get_state(hWnd);
|
||||||
|
if (state && state->timer_running) {
|
||||||
|
const UINT hz = detect_monitor_refresh_hz(hWnd);
|
||||||
|
const UINT new_interval = std::max<UINT>(1, 1000 / hz);
|
||||||
|
if (new_interval != state->timer_interval_ms) {
|
||||||
|
state->stop_timer();
|
||||||
|
state->timer_interval_ms = new_interval;
|
||||||
|
state->start_timer();
|
||||||
|
log_info("configurator",
|
||||||
|
"WM_DISPLAYCHANGE: render timer {} ms ({} Hz)",
|
||||||
|
new_interval, hz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case WM_CLOSE:
|
case WM_CLOSE:
|
||||||
case WM_DESTROY: {
|
case WM_DESTROY: {
|
||||||
|
|
||||||
@@ -110,62 +409,192 @@ LRESULT CALLBACK cfg::ConfiguratorWindow::window_proc(HWND hWnd, UINT uMsg, WPAR
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WM_TIMER: {
|
case WM_TIMER: {
|
||||||
|
if (wParam != RENDER_TIMER_ID) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// update overlay
|
auto *state = get_state(hWnd);
|
||||||
|
|
||||||
|
// skip rendering when the window is minimized or hidden - the timer is
|
||||||
|
// already paused on minimize, but defensively skip here as well.
|
||||||
|
if (state && (state->window_minimized || !IsWindowVisible(hWnd))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool use_d3d9 = state && state->use_d3d9 && state->device;
|
||||||
|
|
||||||
|
// build the imgui frame (input is already buffered via WM_* messages)
|
||||||
if (overlay::OVERLAY) {
|
if (overlay::OVERLAY) {
|
||||||
overlay::OVERLAY->update();
|
overlay::OVERLAY->update();
|
||||||
overlay::OVERLAY->set_active(true);
|
overlay::OVERLAY->set_active(true);
|
||||||
overlay::OVERLAY->new_frame();
|
overlay::OVERLAY->new_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use_d3d9) {
|
||||||
|
const HRESULT cl = state->device->TestCooperativeLevel();
|
||||||
|
if (cl == D3DERR_DEVICELOST) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cl == D3DERR_DEVICENOTRESET) {
|
||||||
|
if (overlay::OVERLAY) {
|
||||||
|
overlay::OVERLAY->reset_invalidate();
|
||||||
|
}
|
||||||
|
const HRESULT reset_hr = state->device->Reset(&state->pp);
|
||||||
|
if (SUCCEEDED(reset_hr) && overlay::OVERLAY) {
|
||||||
|
overlay::OVERLAY->reset_recreate();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (FAILED(cl)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlay::OVERLAY) {
|
||||||
overlay::OVERLAY->render();
|
overlay::OVERLAY->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// repaint window
|
// skip Clear/Present when ImGui draw data is unchanged; the last
|
||||||
InvalidateRect(hWnd, nullptr, TRUE);
|
// presented frame stays visible (same fast path as sw_pixels_dirty).
|
||||||
|
const bool dirty = overlay::OVERLAY && overlay::OVERLAY->d3d9_frame_dirty;
|
||||||
|
const bool force_present = state && !state->has_valid_draw_hash;
|
||||||
|
if (dirty || force_present) {
|
||||||
|
if (state) {
|
||||||
|
state->has_valid_draw_hash = true;
|
||||||
|
}
|
||||||
|
state->device->Clear(0, nullptr, D3DCLEAR_TARGET,
|
||||||
|
D3DCOLOR_RGBA(20, 18, 18, 255), 1.0f, 0);
|
||||||
|
if (SUCCEEDED(state->device->BeginScene())) {
|
||||||
|
if (overlay::OVERLAY) {
|
||||||
|
overlay::OVERLAY->d3d9_render_draw(force_present);
|
||||||
|
}
|
||||||
|
state->device->EndScene();
|
||||||
|
}
|
||||||
|
const HRESULT hr = state->device->Present(nullptr, nullptr, nullptr, nullptr);
|
||||||
|
if (hr == D3DERR_DEVICELOST) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (overlay::OVERLAY) {
|
||||||
|
overlay::OVERLAY->render();
|
||||||
|
}
|
||||||
|
// software path: the overlay's renderer already skipped the costly
|
||||||
|
// paint_imgui call when the draw data was unchanged. Only invalidate
|
||||||
|
// the window when those pixels actually changed, or on the first
|
||||||
|
// frame / after a resize when we lost the previously cached state.
|
||||||
|
const bool dirty = overlay::OVERLAY && overlay::OVERLAY->sw_pixels_dirty;
|
||||||
|
const bool force_repaint = state && !state->has_valid_draw_hash;
|
||||||
|
if (dirty || force_repaint) {
|
||||||
|
if (state) {
|
||||||
|
state->has_valid_draw_hash = true;
|
||||||
|
}
|
||||||
|
// pass FALSE for bErase - WM_ERASEBKGND already short-circuits, so
|
||||||
|
// skipping the erase region avoids one extra Win32 round trip.
|
||||||
|
InvalidateRect(hWnd, nullptr, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WM_ERASEBKGND: {
|
case WM_ERASEBKGND: {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
case WM_PAINT: {
|
case WM_PAINT: {
|
||||||
|
paint_software(hWnd);
|
||||||
// render overlay
|
break;
|
||||||
if (overlay::OVERLAY) {
|
|
||||||
|
|
||||||
// get pixel data
|
|
||||||
int width, height;
|
|
||||||
uint32_t *pixel_data = overlay::OVERLAY->sw_get_pixel_data(&width, &height);
|
|
||||||
if (width > 0 && height > 0) {
|
|
||||||
|
|
||||||
// create bitmap
|
|
||||||
HBITMAP bitmap = CreateBitmap(width, height, 1, 8 * sizeof(uint32_t), pixel_data);
|
|
||||||
|
|
||||||
// prepare paint
|
|
||||||
PAINTSTRUCT paint{};
|
|
||||||
HDC hdc = BeginPaint(hWnd, &paint);
|
|
||||||
HDC hdcMem = CreateCompatibleDC(hdc);
|
|
||||||
SetBkMode(hdc, TRANSPARENT);
|
|
||||||
|
|
||||||
// draw bitmap
|
|
||||||
SelectObject(hdcMem, bitmap);
|
|
||||||
BitBlt(hdc, paint.rcPaint.left, paint.rcPaint.top,
|
|
||||||
paint.rcPaint.right - paint.rcPaint.left,
|
|
||||||
paint.rcPaint.bottom - paint.rcPaint.top,
|
|
||||||
hdcMem, paint.rcPaint.left, paint.rcPaint.top, SRCCOPY);
|
|
||||||
|
|
||||||
// delete bitmap
|
|
||||||
DeleteObject(bitmap);
|
|
||||||
|
|
||||||
// clean up
|
|
||||||
DeleteDC(hdcMem);
|
|
||||||
EndPaint(hWnd, &paint);
|
|
||||||
} else {
|
|
||||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
||||||
}
|
}
|
||||||
|
case WM_ACTIVATE: {
|
||||||
|
const WORD activation = LOWORD(wParam);
|
||||||
|
if (activation == WA_INACTIVE) {
|
||||||
|
// dropping any held mouse buttons so we don't get stuck in a
|
||||||
|
// "button still pressed" state when we come back.
|
||||||
|
auto &io = ImGui::GetIO();
|
||||||
|
io.AddMouseButtonEvent(ImGuiMouseButton_Left, false);
|
||||||
|
io.AddMouseButtonEvent(ImGuiMouseButton_Right, false);
|
||||||
|
io.AddMouseButtonEvent(ImGuiMouseButton_Middle, false);
|
||||||
} else {
|
} else {
|
||||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
// WA_ACTIVE or WA_CLICKACTIVE: make sure the keyboard focus is
|
||||||
|
// actually on this window so WM_MOUSEWHEEL gets routed to us
|
||||||
|
// again. Without this, scrolling silently stops working after
|
||||||
|
// the user alt-tabs back to spicecfg.
|
||||||
|
SetFocus(hWnd);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case WM_KILLFOCUS: {
|
||||||
|
// mirror the WA_INACTIVE mouse-button drop for the keyboard -
|
||||||
|
// without this, a key held during alt-tab stays "down" in ImGui
|
||||||
|
// state until the user presses and releases it again.
|
||||||
|
ImGui::GetIO().ClearInputKeys();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// input messages routed straight into ImGui IO. This replaces the previous
|
||||||
|
// per-frame 256-VK rawinput scan in ImGui_ImplSpice_NewFrame for the
|
||||||
|
// standalone configurator (see CONFIGURATOR_STANDALONE branches there).
|
||||||
|
case WM_KEYDOWN:
|
||||||
|
case WM_SYSKEYDOWN:
|
||||||
|
case WM_KEYUP:
|
||||||
|
case WM_SYSKEYUP: {
|
||||||
|
if (wParam == VK_F4) {
|
||||||
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
const bool down = (uMsg == WM_KEYDOWN) || (uMsg == WM_SYSKEYDOWN);
|
||||||
|
auto &io = ImGui::GetIO();
|
||||||
|
const ImGuiKey key = vk_to_imgui_key(wParam);
|
||||||
|
if (key != ImGuiKey_None) {
|
||||||
|
io.AddKeyEvent(key, down);
|
||||||
|
}
|
||||||
|
const ImGuiKey mod = vk_to_imgui_mod_key(wParam);
|
||||||
|
if (mod != ImGuiMod_None) {
|
||||||
|
io.AddKeyEvent(mod, down);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_MOUSEMOVE: {
|
||||||
|
auto &io = ImGui::GetIO();
|
||||||
|
io.AddMousePosEvent(static_cast<float>(GET_X_LPARAM(lParam)),
|
||||||
|
static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_LBUTTONDOWN:
|
||||||
|
case WM_LBUTTONUP:
|
||||||
|
case WM_RBUTTONDOWN:
|
||||||
|
case WM_RBUTTONUP:
|
||||||
|
case WM_MBUTTONDOWN:
|
||||||
|
case WM_MBUTTONUP: {
|
||||||
|
int button = 0;
|
||||||
|
bool down = false;
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_LBUTTONDOWN: button = ImGuiMouseButton_Left; down = true; break;
|
||||||
|
case WM_LBUTTONUP: button = ImGuiMouseButton_Left; down = false; break;
|
||||||
|
case WM_RBUTTONDOWN: button = ImGuiMouseButton_Right; down = true; break;
|
||||||
|
case WM_RBUTTONUP: button = ImGuiMouseButton_Right; down = false; break;
|
||||||
|
case WM_MBUTTONDOWN: button = ImGuiMouseButton_Middle; down = true; break;
|
||||||
|
case WM_MBUTTONUP: button = ImGuiMouseButton_Middle; down = false; break;
|
||||||
|
}
|
||||||
|
auto &io = ImGui::GetIO();
|
||||||
|
io.AddMouseButtonEvent(button, down);
|
||||||
|
if (down) {
|
||||||
|
SetCapture(hWnd);
|
||||||
|
} else {
|
||||||
|
ReleaseCapture();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_MOUSEWHEEL: {
|
||||||
|
const float delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wParam))
|
||||||
|
/ static_cast<float>(WHEEL_DELTA);
|
||||||
|
ImGui::GetIO().AddMouseWheelEvent(0.0f, delta);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#if !SPICE_XP
|
||||||
|
case WM_MOUSEHWHEEL: {
|
||||||
|
const float delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wParam))
|
||||||
|
/ static_cast<float>(WHEEL_DELTA);
|
||||||
|
ImGui::GetIO().AddMouseWheelEvent(delta, 0.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <d3d9.h>
|
||||||
|
|
||||||
namespace cfg {
|
namespace cfg {
|
||||||
|
|
||||||
@@ -9,11 +12,38 @@ namespace cfg {
|
|||||||
|
|
||||||
HWND hWnd;
|
HWND hWnd;
|
||||||
|
|
||||||
|
// optional D3D9 device backing the configurator window; nullptr when running
|
||||||
|
// the software-rendered path. Owned by ConfiguratorWindow when set.
|
||||||
|
IDirect3D9 *d3d = nullptr;
|
||||||
|
IDirect3DDevice9 *device = nullptr;
|
||||||
|
D3DPRESENT_PARAMETERS pp {};
|
||||||
|
bool use_d3d9 = false;
|
||||||
|
|
||||||
|
// throttling / pause state. timer_interval_ms is the default until run()
|
||||||
|
// refines it to the actual monitor refresh rate (see detect_monitor_refresh_hz).
|
||||||
|
UINT timer_interval_ms = 1000 / 60;
|
||||||
|
bool timer_running = false;
|
||||||
|
bool window_minimized = false;
|
||||||
|
|
||||||
|
// tracks whether we've issued at least one InvalidateRect since window
|
||||||
|
// creation/resize. The overlay's per-frame "pixels changed" flag suppresses
|
||||||
|
// idle blits; this flag forces the very first blit on startup or after
|
||||||
|
// a resize so the window doesn't show garbage until the user moves the mouse.
|
||||||
|
bool has_valid_draw_hash = false;
|
||||||
|
|
||||||
|
// dimensions of the configurator client area (kept in sync with WM_SIZE)
|
||||||
|
int client_width = 0;
|
||||||
|
int client_height = 0;
|
||||||
|
|
||||||
ConfiguratorWindow();
|
ConfiguratorWindow();
|
||||||
~ConfiguratorWindow();
|
~ConfiguratorWindow();
|
||||||
|
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
// start/stop the render timer based on visibility state
|
||||||
|
void start_timer();
|
||||||
|
void stop_timer();
|
||||||
|
|
||||||
static LRESULT CALLBACK window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
static LRESULT CALLBACK window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace overlay::windows {
|
|||||||
el->SetAttribute("invert", entry.invert);
|
el->SetAttribute("invert", entry.invert);
|
||||||
el->SetAttribute("debounce_up", entry.debounce_up);
|
el->SetAttribute("debounce_up", entry.debounce_up);
|
||||||
el->SetAttribute("debounce_down", entry.debounce_down);
|
el->SetAttribute("debounce_down", entry.debounce_down);
|
||||||
|
el->SetAttribute("bat_threshold", entry.bat_threshold);
|
||||||
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
||||||
parent->InsertEndChild(el);
|
parent->InsertEndChild(el);
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,10 @@ namespace overlay::windows {
|
|||||||
el->QueryIntAttribute("velocity_threshold", &vel);
|
el->QueryIntAttribute("velocity_threshold", &vel);
|
||||||
entry.velocity_threshold = (unsigned short)vel;
|
entry.velocity_threshold = (unsigned short)vel;
|
||||||
|
|
||||||
|
int bat = 0;
|
||||||
|
el->QueryIntAttribute("bat_threshold", &bat);
|
||||||
|
entry.bat_threshold = bat;
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +70,7 @@ namespace overlay::windows {
|
|||||||
el->SetAttribute("smoothing", analog.smoothing);
|
el->SetAttribute("smoothing", analog.smoothing);
|
||||||
el->SetAttribute("multiplier", analog.multiplier);
|
el->SetAttribute("multiplier", analog.multiplier);
|
||||||
el->SetAttribute("relative", analog.relative_mode);
|
el->SetAttribute("relative", analog.relative_mode);
|
||||||
el->SetAttribute("delay", analog.delay_buffer_depth);
|
el->SetAttribute("delay_ms", analog.delay_ms);
|
||||||
parent->InsertEndChild(el);
|
parent->InsertEndChild(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +93,7 @@ namespace overlay::windows {
|
|||||||
el->QueryBoolAttribute("smoothing", &a.smoothing);
|
el->QueryBoolAttribute("smoothing", &a.smoothing);
|
||||||
el->QueryIntAttribute("multiplier", &a.multiplier);
|
el->QueryIntAttribute("multiplier", &a.multiplier);
|
||||||
el->QueryBoolAttribute("relative", &a.relative_mode);
|
el->QueryBoolAttribute("relative", &a.relative_mode);
|
||||||
el->QueryIntAttribute("delay", &a.delay_buffer_depth);
|
el->QueryUnsignedAttribute("delay_ms", &a.delay_ms);
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
@@ -354,13 +359,12 @@ namespace overlay::windows {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::error_code ec;
|
if (MoveFileExW(path_tmp.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0) {
|
||||||
std::filesystem::rename(path_tmp, path, ec);
|
log_warning("templates", "failed to rename templates file: 0x{:08x}", GetLastError());
|
||||||
if (ec) {
|
|
||||||
log_warning("templates", "failed to rename templates file: {}", ec.message());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log_info("templates", "templates file saved successfully");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,8 +396,7 @@ namespace overlay::windows {
|
|||||||
auto path_tmp = path;
|
auto path_tmp = path;
|
||||||
path_tmp.replace_extension(L"tmp");
|
path_tmp.replace_extension(L"tmp");
|
||||||
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
|
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
|
||||||
std::error_code ec;
|
MoveFileExW(path_tmp.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
|
||||||
std::filesystem::rename(path_tmp, path, ec);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,8 +432,7 @@ namespace overlay::windows {
|
|||||||
auto path_tmp = path;
|
auto path_tmp = path;
|
||||||
path_tmp.replace_extension(L"tmp");
|
path_tmp.replace_extension(L"tmp");
|
||||||
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
|
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
|
||||||
std::error_code ec;
|
MoveFileExW(path_tmp.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
|
||||||
std::filesystem::rename(path_tmp, path, ec);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
|
|||||||
|
|
||||||
return "Invalid SMX Dedicab Light (" + index_string + ")";
|
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:
|
case rawinput::DESTROYED:
|
||||||
return "Unplugged device (" + index_string + ")";
|
return "Unplugged device (" + index_string + ")";
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
<security>
|
<security>
|
||||||
<requestedPrivileges>
|
<requestedPrivileges>
|
||||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||||
</requestedPrivileges>
|
</requestedPrivileges>
|
||||||
</security>
|
</security>
|
||||||
</trustInfo>
|
</trustInfo>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ enum class OptionPickerType {
|
|||||||
CpuAffinity,
|
CpuAffinity,
|
||||||
FilePath,
|
FilePath,
|
||||||
DirectoryPath,
|
DirectoryPath,
|
||||||
|
Monitor,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OptionDefinition {
|
struct OptionDefinition {
|
||||||
@@ -45,6 +46,8 @@ struct OptionDefinition {
|
|||||||
|
|
||||||
// for OptionPickerType::FilePath
|
// for OptionPickerType::FilePath
|
||||||
std::string file_extension = "";
|
std::string file_extension = "";
|
||||||
|
|
||||||
|
std::string quick_setting_category = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
class Option {
|
class Option {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace cfg {
|
|||||||
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
|
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
|
||||||
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
|
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
|
||||||
if (fileutils::file_exists(this->config_path)) {
|
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;
|
file_exists = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ namespace cfg {
|
|||||||
// window = rectangle including the frame
|
// window = rectangle including the frame
|
||||||
// client = just the content area without frames.
|
// client = just the content area without frames.
|
||||||
bool window_always_on_top = false;
|
bool window_always_on_top = false;
|
||||||
|
bool window_disable_round_corners = false;
|
||||||
bool client_keep_aspect_ratio = true;
|
bool client_keep_aspect_ratio = true;
|
||||||
bool enable_window_resize = false;
|
bool enable_window_resize = false;
|
||||||
int window_decoration = 0; // enum type WindowDecorationMode
|
int window_decoration = 0; // enum type WindowDecorationMode
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# fails the build if a PE binary statically imports a forbidden DLL.
|
||||||
|
#
|
||||||
|
# some DLLs must never end up in spice's static import table, for two reasons:
|
||||||
|
#
|
||||||
|
# 1. user-overridable DLLs (e.g. DXVK's d3d9.dll): users drop their own copy
|
||||||
|
# into the modules directory to replace the system one. a static import
|
||||||
|
# forces the loader to load the SYSTEM copy at process startup - before the
|
||||||
|
# modules directory is added to the DLL search path and before the game DLL
|
||||||
|
# loads - so the user-supplied override never takes effect (see issue #779).
|
||||||
|
#
|
||||||
|
# 2. DLLs that break games when present (e.g. Media Foundation: mf/mfplat/
|
||||||
|
# mfreadwrite): a static import loads them eagerly and breaks Unity games.
|
||||||
|
#
|
||||||
|
# in both cases the DLL must instead be loaded dynamically (libutils::try_library
|
||||||
|
# / GetProcAddress / delay load) so it is only pulled in when actually needed.
|
||||||
|
#
|
||||||
|
# invoked via `cmake -P` from a POST_BUILD step. required -D variables:
|
||||||
|
# OBJDUMP - path to objdump (CMAKE_OBJDUMP)
|
||||||
|
# TARGET_FILE - path to the PE binary to inspect
|
||||||
|
# FORBIDDEN - semicolon-separated list of lowercase DLL names to reject
|
||||||
|
|
||||||
|
if(NOT OBJDUMP OR NOT EXISTS "${OBJDUMP}")
|
||||||
|
message(WARNING
|
||||||
|
"check_no_static_dll_imports: objdump not found, skipping import check for ${TARGET_FILE}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${OBJDUMP}" -p "${TARGET_FILE}"
|
||||||
|
OUTPUT_VARIABLE dump_output
|
||||||
|
RESULT_VARIABLE dump_result
|
||||||
|
ERROR_VARIABLE dump_error)
|
||||||
|
|
||||||
|
if(NOT dump_result EQUAL 0)
|
||||||
|
message(WARNING
|
||||||
|
"check_no_static_dll_imports: objdump failed for ${TARGET_FILE}: ${dump_error}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# both GNU objdump and llvm-objdump print one "DLL Name: <name>" line per
|
||||||
|
# statically imported DLL in their PE private-header dump.
|
||||||
|
string(REGEX MATCHALL "DLL Name:[ \t]*[^\n\r]+" dll_lines "${dump_output}")
|
||||||
|
|
||||||
|
set(violations "")
|
||||||
|
foreach(line IN LISTS dll_lines)
|
||||||
|
string(REGEX REPLACE "DLL Name:[ \t]*" "" dll_name "${line}")
|
||||||
|
string(STRIP "${dll_name}" dll_name)
|
||||||
|
string(TOLOWER "${dll_name}" dll_name_lower)
|
||||||
|
if(dll_name_lower IN_LIST FORBIDDEN)
|
||||||
|
list(APPEND violations "${dll_name}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(violations)
|
||||||
|
list(REMOVE_DUPLICATES violations)
|
||||||
|
string(REPLACE ";" ", " violations_str "${violations}")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"static DLL import check FAILED for ${TARGET_FILE}\n"
|
||||||
|
" forbidden static imports found: ${violations_str}\n"
|
||||||
|
"\n"
|
||||||
|
" these DLLs must never be statically imported by spice:\n"
|
||||||
|
" * user-overridable DLLs (e.g. DXVK d3d9.dll) - a static import loads the\n"
|
||||||
|
" system copy at startup and preempts the modules override (issue #779).\n"
|
||||||
|
" * Media Foundation DLLs (mf/mfplat/mfreadwrite) - a static import breaks\n"
|
||||||
|
" Unity games.\n"
|
||||||
|
"\n"
|
||||||
|
" fix: load the DLL dynamically instead - replace the direct API call with a\n"
|
||||||
|
" libutils::try_library() + libutils::try_proc() lookup (or a delay load), then\n"
|
||||||
|
" call through the resolved function pointer.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "static DLL import check passed for ${TARGET_FILE}")
|
||||||
+8
@@ -15,3 +15,11 @@ RUN pacman --noconfirm -Syu git \
|
|||||||
ccache
|
ccache
|
||||||
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
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 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
|
||||||
|
|||||||
+552
@@ -0,0 +1,552 @@
|
|||||||
|
// This code comes from:
|
||||||
|
// https://github.com/dhbaird/easywsclient
|
||||||
|
//
|
||||||
|
// To get the latest version:
|
||||||
|
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp
|
||||||
|
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp
|
||||||
|
|
||||||
|
#include "easywsclient.hpp"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#pragma comment( lib, "ws2_32" )
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
typedef SOCKET socket_t;
|
||||||
|
#ifndef _SSIZE_T_DEFINED
|
||||||
|
typedef int ssize_t;
|
||||||
|
#define _SSIZE_T_DEFINED
|
||||||
|
#endif
|
||||||
|
#ifndef _SOCKET_T_DEFINED
|
||||||
|
typedef SOCKET socket_t;
|
||||||
|
#define _SOCKET_T_DEFINED
|
||||||
|
#endif
|
||||||
|
#if defined(_MSC_VER) && !defined(snprintf)
|
||||||
|
#define snprintf _snprintf_s
|
||||||
|
#endif
|
||||||
|
#include <stdint.h>
|
||||||
|
#define socketerrno WSAGetLastError()
|
||||||
|
#define SOCKET_EAGAIN_EINPROGRESS WSAEINPROGRESS
|
||||||
|
#define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK
|
||||||
|
#else
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <errno.h>
|
||||||
|
typedef int socket_t;
|
||||||
|
#ifndef INVALID_SOCKET
|
||||||
|
#define INVALID_SOCKET (-1)
|
||||||
|
#endif
|
||||||
|
#ifndef SOCKET_ERROR
|
||||||
|
#define SOCKET_ERROR (-1)
|
||||||
|
#endif
|
||||||
|
#define closesocket(s) ::close(s)
|
||||||
|
#include <errno.h>
|
||||||
|
#define socketerrno errno
|
||||||
|
#define SOCKET_EAGAIN_EINPROGRESS EAGAIN
|
||||||
|
#define SOCKET_EWOULDBLOCK EWOULDBLOCK
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
// When false, easywsclient's diagnostics are suppressed. The host application
|
||||||
|
// opts in by setting this flag (see obs_websocket.cpp).
|
||||||
|
bool EASYWSCLIENT_LOGGING_ENABLED = false;
|
||||||
|
|
||||||
|
// Route easywsclient's diagnostic output through the project logger instead of
|
||||||
|
// writing to stderr, without editing the upstream source lines below: these two
|
||||||
|
// macros redirect fprintf(stderr, ...) / fputs(..., stderr) to log_misc.
|
||||||
|
// Only emitted when EASYWSCLIENT_LOGGING_ENABLED is set.
|
||||||
|
static inline void easywsclient_logf(const char *fmt, ...) {
|
||||||
|
if (!EASYWSCLIENT_LOGGING_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char buf[1024];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
// trim trailing newline(s); the logger appends its own
|
||||||
|
size_t len = strlen(buf);
|
||||||
|
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) {
|
||||||
|
buf[--len] = '\0';
|
||||||
|
}
|
||||||
|
log_misc("easywsclient", "{}", buf);
|
||||||
|
}
|
||||||
|
#define fprintf(stream, ...) easywsclient_logf(__VA_ARGS__)
|
||||||
|
#define fputs(str, stream) easywsclient_logf("%s", str)
|
||||||
|
|
||||||
|
using namespace easywsclient;
|
||||||
|
|
||||||
|
namespace { // private module-only namespace
|
||||||
|
|
||||||
|
socket_t hostname_connect(const std::string& hostname, int port) {
|
||||||
|
struct addrinfo hints;
|
||||||
|
struct addrinfo *result;
|
||||||
|
struct addrinfo *p;
|
||||||
|
int ret;
|
||||||
|
socket_t sockfd = INVALID_SOCKET;
|
||||||
|
char sport[16];
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
snprintf(sport, 16, "%d", port);
|
||||||
|
if ((ret = getaddrinfo(hostname.c_str(), sport, &hints, &result)) != 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
for(p = result; p != NULL; p = p->ai_next)
|
||||||
|
{
|
||||||
|
sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||||
|
if (sockfd == INVALID_SOCKET) { continue; }
|
||||||
|
if (connect(sockfd, p->ai_addr, p->ai_addrlen) != SOCKET_ERROR) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
closesocket(sockfd);
|
||||||
|
sockfd = INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
freeaddrinfo(result);
|
||||||
|
return sockfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class _DummyWebSocket : public easywsclient::WebSocket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void poll(int timeout) { }
|
||||||
|
void send(const std::string& message) { }
|
||||||
|
void sendBinary(const std::string& message) { }
|
||||||
|
void sendBinary(const std::vector<uint8_t>& message) { }
|
||||||
|
void sendPing() { }
|
||||||
|
void close() { }
|
||||||
|
readyStateValues getReadyState() const { return CLOSED; }
|
||||||
|
void _dispatch(Callback_Imp & callable) { }
|
||||||
|
void _dispatchBinary(BytesCallback_Imp& callable) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class _RealWebSocket : public easywsclient::WebSocket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol
|
||||||
|
//
|
||||||
|
// 0 1 2 3
|
||||||
|
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
// +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||||
|
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||||
|
// |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||||
|
// |N|V|V|V| |S| | (if payload len==126/127) |
|
||||||
|
// | |1|2|3| |K| | |
|
||||||
|
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||||
|
// | Extended payload length continued, if payload len == 127 |
|
||||||
|
// + - - - - - - - - - - - - - - - +-------------------------------+
|
||||||
|
// | |Masking-key, if MASK set to 1 |
|
||||||
|
// +-------------------------------+-------------------------------+
|
||||||
|
// | Masking-key (continued) | Payload Data |
|
||||||
|
// +-------------------------------- - - - - - - - - - - - - - - - +
|
||||||
|
// : Payload Data continued ... :
|
||||||
|
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||||
|
// | Payload Data continued ... |
|
||||||
|
// +---------------------------------------------------------------+
|
||||||
|
|
||||||
|
struct wsheader_type {
|
||||||
|
unsigned header_size;
|
||||||
|
bool fin;
|
||||||
|
bool mask;
|
||||||
|
enum opcode_type {
|
||||||
|
CONTINUATION = 0x0,
|
||||||
|
TEXT_FRAME = 0x1,
|
||||||
|
BINARY_FRAME = 0x2,
|
||||||
|
CLOSE = 8,
|
||||||
|
PING = 9,
|
||||||
|
PONG = 0xa,
|
||||||
|
} opcode;
|
||||||
|
int N0;
|
||||||
|
uint64_t N;
|
||||||
|
uint8_t masking_key[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint8_t> rxbuf;
|
||||||
|
std::vector<uint8_t> txbuf;
|
||||||
|
std::vector<uint8_t> receivedData;
|
||||||
|
|
||||||
|
socket_t sockfd;
|
||||||
|
readyStateValues readyState;
|
||||||
|
bool useMask;
|
||||||
|
bool isRxBad;
|
||||||
|
|
||||||
|
_RealWebSocket(socket_t sockfd, bool useMask) : sockfd(sockfd), readyState(OPEN), useMask(useMask), isRxBad(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
readyStateValues getReadyState() const {
|
||||||
|
return readyState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void poll(int timeout) { // timeout in milliseconds
|
||||||
|
if (readyState == CLOSED) {
|
||||||
|
if (timeout > 0) {
|
||||||
|
timeval tv = { timeout/1000, (timeout%1000) * 1000 };
|
||||||
|
select(0, NULL, NULL, NULL, &tv);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (timeout != 0) {
|
||||||
|
fd_set rfds;
|
||||||
|
fd_set wfds;
|
||||||
|
timeval tv = { timeout/1000, (timeout%1000) * 1000 };
|
||||||
|
FD_ZERO(&rfds);
|
||||||
|
FD_ZERO(&wfds);
|
||||||
|
FD_SET(sockfd, &rfds);
|
||||||
|
if (txbuf.size()) { FD_SET(sockfd, &wfds); }
|
||||||
|
select(sockfd + 1, &rfds, &wfds, NULL, timeout > 0 ? &tv : NULL);
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
// FD_ISSET(0, &rfds) will be true
|
||||||
|
int N = rxbuf.size();
|
||||||
|
ssize_t ret;
|
||||||
|
rxbuf.resize(N + 1500);
|
||||||
|
ret = recv(sockfd, (char*)&rxbuf[0] + N, 1500, 0);
|
||||||
|
if (false) { }
|
||||||
|
else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
|
||||||
|
rxbuf.resize(N);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (ret <= 0) {
|
||||||
|
rxbuf.resize(N);
|
||||||
|
closesocket(sockfd);
|
||||||
|
readyState = CLOSED;
|
||||||
|
fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rxbuf.resize(N + ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (txbuf.size()) {
|
||||||
|
int ret = ::send(sockfd, (char*)&txbuf[0], txbuf.size(), 0);
|
||||||
|
if (false) { } // ??
|
||||||
|
else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (ret <= 0) {
|
||||||
|
closesocket(sockfd);
|
||||||
|
readyState = CLOSED;
|
||||||
|
fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
txbuf.erase(txbuf.begin(), txbuf.begin() + ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!txbuf.size() && readyState == CLOSING) {
|
||||||
|
closesocket(sockfd);
|
||||||
|
readyState = CLOSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void _dispatch(Callback_Imp & callable) {
|
||||||
|
struct CallbackAdapter : public BytesCallback_Imp
|
||||||
|
// Adapt void(const std::string<uint8_t>&) to void(const std::string&)
|
||||||
|
{
|
||||||
|
Callback_Imp& callable;
|
||||||
|
CallbackAdapter(Callback_Imp& callable) : callable(callable) { }
|
||||||
|
void operator()(const std::vector<uint8_t>& message) {
|
||||||
|
std::string stringMessage(message.begin(), message.end());
|
||||||
|
callable(stringMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
CallbackAdapter bytesCallback(callable);
|
||||||
|
_dispatchBinary(bytesCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void _dispatchBinary(BytesCallback_Imp& callable) {
|
||||||
|
// TODO: consider acquiring a lock on rxbuf...
|
||||||
|
while (true) {
|
||||||
|
wsheader_type ws;
|
||||||
|
if (rxbuf.size() < 2) { return; /* Need at least 2 */ }
|
||||||
|
const uint8_t * data = (uint8_t *) &rxbuf[0]; // peek, but don't consume
|
||||||
|
ws.fin = (data[0] & 0x80) == 0x80;
|
||||||
|
ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f);
|
||||||
|
ws.mask = (data[1] & 0x80) == 0x80;
|
||||||
|
ws.N0 = (data[1] & 0x7f);
|
||||||
|
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
|
||||||
|
if (rxbuf.size() < ws.header_size) { return; /* Need: ws.header_size - rxbuf.size() */ }
|
||||||
|
int i = 0;
|
||||||
|
if (ws.N0 < 126) {
|
||||||
|
ws.N = ws.N0;
|
||||||
|
i = 2;
|
||||||
|
}
|
||||||
|
else if (ws.N0 == 126) {
|
||||||
|
ws.N = 0;
|
||||||
|
ws.N |= ((uint64_t) data[2]) << 8;
|
||||||
|
ws.N |= ((uint64_t) data[3]) << 0;
|
||||||
|
i = 4;
|
||||||
|
}
|
||||||
|
else if (ws.N0 == 127) {
|
||||||
|
ws.N = 0;
|
||||||
|
ws.N |= ((uint64_t) data[2]) << 56;
|
||||||
|
ws.N |= ((uint64_t) data[3]) << 48;
|
||||||
|
ws.N |= ((uint64_t) data[4]) << 40;
|
||||||
|
ws.N |= ((uint64_t) data[5]) << 32;
|
||||||
|
ws.N |= ((uint64_t) data[6]) << 24;
|
||||||
|
ws.N |= ((uint64_t) data[7]) << 16;
|
||||||
|
ws.N |= ((uint64_t) data[8]) << 8;
|
||||||
|
ws.N |= ((uint64_t) data[9]) << 0;
|
||||||
|
i = 10;
|
||||||
|
}
|
||||||
|
if (ws.mask) {
|
||||||
|
ws.masking_key[0] = ((uint8_t) data[i+0]) << 0;
|
||||||
|
ws.masking_key[1] = ((uint8_t) data[i+1]) << 0;
|
||||||
|
ws.masking_key[2] = ((uint8_t) data[i+2]) << 0;
|
||||||
|
ws.masking_key[3] = ((uint8_t) data[i+3]) << 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ws.masking_key[0] = 0;
|
||||||
|
ws.masking_key[1] = 0;
|
||||||
|
ws.masking_key[2] = 0;
|
||||||
|
ws.masking_key[3] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: The checks above should hopefully ensure this addition
|
||||||
|
// cannot overflow:
|
||||||
|
if (rxbuf.size() < ws.header_size+ws.N) { return; /* Need: ws.header_size+ws.N - rxbuf.size() */ }
|
||||||
|
|
||||||
|
// We got a whole message, now do something with it:
|
||||||
|
if (false) { }
|
||||||
|
else if (
|
||||||
|
ws.opcode == wsheader_type::TEXT_FRAME
|
||||||
|
|| ws.opcode == wsheader_type::BINARY_FRAME
|
||||||
|
|| ws.opcode == wsheader_type::CONTINUATION
|
||||||
|
) {
|
||||||
|
if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } }
|
||||||
|
receivedData.insert(receivedData.end(), rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed
|
||||||
|
if (ws.fin) {
|
||||||
|
callable((const std::vector<uint8_t>) receivedData);
|
||||||
|
receivedData.erase(receivedData.begin(), receivedData.end());
|
||||||
|
std::vector<uint8_t> ().swap(receivedData);// free memory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ws.opcode == wsheader_type::PING) {
|
||||||
|
if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } }
|
||||||
|
std::string data(rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);
|
||||||
|
sendData(wsheader_type::PONG, data.size(), data.begin(), data.end());
|
||||||
|
}
|
||||||
|
else if (ws.opcode == wsheader_type::PONG) { }
|
||||||
|
else if (ws.opcode == wsheader_type::CLOSE) { close(); }
|
||||||
|
else { fprintf(stderr, "ERROR: Got unexpected WebSocket message.\n"); close(); }
|
||||||
|
|
||||||
|
rxbuf.erase(rxbuf.begin(), rxbuf.begin() + ws.header_size+(size_t)ws.N);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPing() {
|
||||||
|
std::string empty;
|
||||||
|
sendData(wsheader_type::PING, empty.size(), empty.begin(), empty.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(const std::string& message) {
|
||||||
|
sendData(wsheader_type::TEXT_FRAME, message.size(), message.begin(), message.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendBinary(const std::string& message) {
|
||||||
|
sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendBinary(const std::vector<uint8_t>& message) {
|
||||||
|
sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Iterator>
|
||||||
|
void sendData(wsheader_type::opcode_type type, uint64_t message_size, Iterator message_begin, Iterator message_end) {
|
||||||
|
// TODO:
|
||||||
|
// Masking key should (must) be derived from a high quality random
|
||||||
|
// number generator, to mitigate attacks on non-WebSocket friendly
|
||||||
|
// middleware:
|
||||||
|
const uint8_t masking_key[4] = { 0x12, 0x34, 0x56, 0x78 };
|
||||||
|
// TODO: consider acquiring a lock on txbuf...
|
||||||
|
if (readyState == CLOSING || readyState == CLOSED) { return; }
|
||||||
|
std::vector<uint8_t> header;
|
||||||
|
header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + (useMask ? 4 : 0), 0);
|
||||||
|
header[0] = 0x80 | type;
|
||||||
|
if (false) { }
|
||||||
|
else if (message_size < 126) {
|
||||||
|
header[1] = (message_size & 0xff) | (useMask ? 0x80 : 0);
|
||||||
|
if (useMask) {
|
||||||
|
header[2] = masking_key[0];
|
||||||
|
header[3] = masking_key[1];
|
||||||
|
header[4] = masking_key[2];
|
||||||
|
header[5] = masking_key[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message_size < 65536) {
|
||||||
|
header[1] = 126 | (useMask ? 0x80 : 0);
|
||||||
|
header[2] = (message_size >> 8) & 0xff;
|
||||||
|
header[3] = (message_size >> 0) & 0xff;
|
||||||
|
if (useMask) {
|
||||||
|
header[4] = masking_key[0];
|
||||||
|
header[5] = masking_key[1];
|
||||||
|
header[6] = masking_key[2];
|
||||||
|
header[7] = masking_key[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // TODO: run coverage testing here
|
||||||
|
header[1] = 127 | (useMask ? 0x80 : 0);
|
||||||
|
header[2] = (message_size >> 56) & 0xff;
|
||||||
|
header[3] = (message_size >> 48) & 0xff;
|
||||||
|
header[4] = (message_size >> 40) & 0xff;
|
||||||
|
header[5] = (message_size >> 32) & 0xff;
|
||||||
|
header[6] = (message_size >> 24) & 0xff;
|
||||||
|
header[7] = (message_size >> 16) & 0xff;
|
||||||
|
header[8] = (message_size >> 8) & 0xff;
|
||||||
|
header[9] = (message_size >> 0) & 0xff;
|
||||||
|
if (useMask) {
|
||||||
|
header[10] = masking_key[0];
|
||||||
|
header[11] = masking_key[1];
|
||||||
|
header[12] = masking_key[2];
|
||||||
|
header[13] = masking_key[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// N.B. - txbuf will keep growing until it can be transmitted over the socket:
|
||||||
|
txbuf.insert(txbuf.end(), header.begin(), header.end());
|
||||||
|
txbuf.insert(txbuf.end(), message_begin, message_end);
|
||||||
|
if (useMask) {
|
||||||
|
size_t message_offset = txbuf.size() - message_size;
|
||||||
|
for (size_t i = 0; i != message_size; ++i) {
|
||||||
|
txbuf[message_offset + i] ^= masking_key[i&0x3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
if(readyState == CLOSING || readyState == CLOSED) { return; }
|
||||||
|
readyState = CLOSING;
|
||||||
|
uint8_t closeFrame[6] = {0x88, 0x80, 0x00, 0x00, 0x00, 0x00}; // last 4 bytes are a masking key
|
||||||
|
std::vector<uint8_t> header(closeFrame, closeFrame+6);
|
||||||
|
txbuf.insert(txbuf.end(), header.begin(), header.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
easywsclient::WebSocket::pointer from_url(const std::string& url, bool useMask, const std::string& origin) {
|
||||||
|
char host[512];
|
||||||
|
int port;
|
||||||
|
char path[512];
|
||||||
|
if (url.size() >= 512) {
|
||||||
|
fprintf(stderr, "ERROR: url size limit exceeded: %s\n", url.c_str());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (origin.size() >= 200) {
|
||||||
|
fprintf(stderr, "ERROR: origin size limit exceeded: %s\n", origin.c_str());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (false) { }
|
||||||
|
else if (sscanf(url.c_str(), "ws://%[^:/]:%d/%s", host, &port, path) == 3) {
|
||||||
|
}
|
||||||
|
else if (sscanf(url.c_str(), "ws://%[^:/]/%s", host, path) == 2) {
|
||||||
|
port = 80;
|
||||||
|
}
|
||||||
|
else if (sscanf(url.c_str(), "ws://%[^:/]:%d", host, &port) == 2) {
|
||||||
|
path[0] = '\0';
|
||||||
|
}
|
||||||
|
else if (sscanf(url.c_str(), "ws://%[^:/]", host) == 1) {
|
||||||
|
port = 80;
|
||||||
|
path[0] = '\0';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fprintf(stderr, "ERROR: Could not parse WebSocket url: %s\n", url.c_str());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//fprintf(stderr, "easywsclient: connecting: host=%s port=%d path=/%s\n", host, port, path);
|
||||||
|
socket_t sockfd = hostname_connect(host, port);
|
||||||
|
if (sockfd == INVALID_SOCKET) {
|
||||||
|
fprintf(stderr, "Unable to connect to %s:%d\n", host, port);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// XXX: this should be done non-blocking,
|
||||||
|
char line[1024];
|
||||||
|
int status;
|
||||||
|
int i;
|
||||||
|
snprintf(line, 1024, "GET /%s HTTP/1.1\r\n", path); ::send(sockfd, line, strlen(line), 0);
|
||||||
|
if (port == 80) {
|
||||||
|
snprintf(line, 1024, "Host: %s\r\n", host); ::send(sockfd, line, strlen(line), 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snprintf(line, 1024, "Host: %s:%d\r\n", host, port); ::send(sockfd, line, strlen(line), 0);
|
||||||
|
}
|
||||||
|
snprintf(line, 1024, "Upgrade: websocket\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||||
|
snprintf(line, 1024, "Connection: Upgrade\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||||
|
if (!origin.empty()) {
|
||||||
|
snprintf(line, 1024, "Origin: %s\r\n", origin.c_str()); ::send(sockfd, line, strlen(line), 0);
|
||||||
|
}
|
||||||
|
snprintf(line, 1024, "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||||
|
snprintf(line, 1024, "Sec-WebSocket-Version: 13\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||||
|
snprintf(line, 1024, "\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||||
|
for (i = 0; i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } }
|
||||||
|
line[i] = 0;
|
||||||
|
if (i == 1023) { fprintf(stderr, "ERROR: Got invalid status line connecting to: %s\n", url.c_str()); return NULL; }
|
||||||
|
if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { fprintf(stderr, "ERROR: Got bad status connecting to %s: %s", url.c_str(), line); return NULL; }
|
||||||
|
// TODO: verify response headers,
|
||||||
|
while (true) {
|
||||||
|
for (i = 0; i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } }
|
||||||
|
if (line[0] == '\r' && line[1] == '\n') { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int flag = 1;
|
||||||
|
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag)); // Disable Nagle's algorithm
|
||||||
|
#ifdef _WIN32
|
||||||
|
u_long on = 1;
|
||||||
|
ioctlsocket(sockfd, FIONBIO, &on);
|
||||||
|
#else
|
||||||
|
fcntl(sockfd, F_SETFL, O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
//fprintf(stderr, "Connected to: %s\n", url.c_str());
|
||||||
|
return easywsclient::WebSocket::pointer(new _RealWebSocket(sockfd, useMask));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end of module-only namespace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace easywsclient {
|
||||||
|
|
||||||
|
WebSocket::pointer WebSocket::create_dummy() {
|
||||||
|
static pointer dummy = pointer(new _DummyWebSocket);
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WebSocket::pointer WebSocket::from_url(const std::string& url, const std::string& origin) {
|
||||||
|
return ::from_url(url, true, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocket::pointer WebSocket::from_url_no_mask(const std::string& url, const std::string& origin) {
|
||||||
|
return ::from_url(url, false, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace easywsclient
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#ifndef EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD
|
||||||
|
#define EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD
|
||||||
|
|
||||||
|
// This code comes from:
|
||||||
|
// https://github.com/dhbaird/easywsclient
|
||||||
|
//
|
||||||
|
// To get the latest version:
|
||||||
|
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp
|
||||||
|
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace easywsclient {
|
||||||
|
|
||||||
|
struct Callback_Imp { virtual void operator()(const std::string& message) = 0; };
|
||||||
|
struct BytesCallback_Imp { virtual void operator()(const std::vector<uint8_t>& message) = 0; };
|
||||||
|
|
||||||
|
class WebSocket {
|
||||||
|
public:
|
||||||
|
typedef WebSocket * pointer;
|
||||||
|
typedef enum readyStateValues { CLOSING, CLOSED, CONNECTING, OPEN } readyStateValues;
|
||||||
|
|
||||||
|
// Factories:
|
||||||
|
static pointer create_dummy();
|
||||||
|
static pointer from_url(const std::string& url, const std::string& origin = std::string());
|
||||||
|
static pointer from_url_no_mask(const std::string& url, const std::string& origin = std::string());
|
||||||
|
|
||||||
|
// Interfaces:
|
||||||
|
virtual ~WebSocket() { }
|
||||||
|
virtual void poll(int timeout = 0) = 0; // timeout in milliseconds
|
||||||
|
virtual void send(const std::string& message) = 0;
|
||||||
|
virtual void sendBinary(const std::string& message) = 0;
|
||||||
|
virtual void sendBinary(const std::vector<uint8_t>& message) = 0;
|
||||||
|
virtual void sendPing() = 0;
|
||||||
|
virtual void close() = 0;
|
||||||
|
virtual readyStateValues getReadyState() const = 0;
|
||||||
|
|
||||||
|
template<class Callable>
|
||||||
|
void dispatch(Callable callable)
|
||||||
|
// For callbacks that accept a string argument.
|
||||||
|
{ // N.B. this is compatible with both C++11 lambdas, functors and C function pointers
|
||||||
|
struct _Callback : public Callback_Imp {
|
||||||
|
Callable& callable;
|
||||||
|
_Callback(Callable& callable) : callable(callable) { }
|
||||||
|
void operator()(const std::string& message) { callable(message); }
|
||||||
|
};
|
||||||
|
_Callback callback(callable);
|
||||||
|
_dispatch(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Callable>
|
||||||
|
void dispatchBinary(Callable callable)
|
||||||
|
// For callbacks that accept a std::vector<uint8_t> argument.
|
||||||
|
{ // N.B. this is compatible with both C++11 lambdas, functors and C function pointers
|
||||||
|
struct _Callback : public BytesCallback_Imp {
|
||||||
|
Callable& callable;
|
||||||
|
_Callback(Callable& callable) : callable(callable) { }
|
||||||
|
void operator()(const std::vector<uint8_t>& message) { callable(message); }
|
||||||
|
};
|
||||||
|
_Callback callback(callable);
|
||||||
|
_dispatchBinary(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void _dispatch(Callback_Imp& callable) = 0;
|
||||||
|
virtual void _dispatchBinary(BytesCallback_Imp& callable) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easywsclient
|
||||||
|
|
||||||
|
#endif /* EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD */
|
||||||
Vendored
+1
@@ -20,6 +20,7 @@ Usage:
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|||||||
+6
@@ -21,5 +21,11 @@ set(IMGUI_SOURCES
|
|||||||
misc/cpp/imgui_stdlib.cpp
|
misc/cpp/imgui_stdlib.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# spice2x: DX11 backend is only built for non-XP toolchains
|
||||||
|
if(NOT SPICE_XP)
|
||||||
|
list(APPEND IMGUI_HEADERS backends/imgui_impl_dx11.h)
|
||||||
|
list(APPEND IMGUI_SOURCES backends/imgui_impl_dx11.cpp)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_library(imgui STATIC ${IMGUI_HEADERS} ${IMGUI_SOURCES})
|
add_library(imgui STATIC ${IMGUI_HEADERS} ${IMGUI_SOURCES})
|
||||||
target_include_directories(imgui PRIVATE ${PROJECT_SOURCE_DIR})
|
target_include_directories(imgui PRIVATE ${PROJECT_SOURCE_DIR})
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -0,0 +1,894 @@
|
|||||||
|
// dear imgui: Renderer Backend for DirectX11
|
||||||
|
// This needs to be used along with a Platform Backend (e.g. Win32)
|
||||||
|
|
||||||
|
// Implemented features:
|
||||||
|
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' 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: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
|
||||||
|
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
|
||||||
|
|
||||||
|
// 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-04-23: DirectX11: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. Obsoleting samplers from ImGui_ImplDX11_RenderState. (#9378)
|
||||||
|
// 2026-01-19: DirectX11: Added 'SamplerNearest' in ImGui_ImplDX11_RenderState. Renamed 'SamplerDefault' to 'SamplerLinear'.
|
||||||
|
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
|
||||||
|
// 2025-06-11: DirectX11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
|
||||||
|
// 2025-05-07: DirectX11: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows).
|
||||||
|
// 2025-02-24: [Docking] Added undocumented ImGui_ImplDX11_SetSwapChainDescs() to configure swap chain creation for secondary viewports.
|
||||||
|
// 2025-01-06: DirectX11: Expose VertexConstantBuffer in ImGui_ImplDX11_RenderState. Reset projection matrix in ImDrawCallback_ResetRenderState handler.
|
||||||
|
// 2024-10-07: DirectX11: Changed default texture sampler to Clamp instead of Repeat/Wrap.
|
||||||
|
// 2024-10-07: DirectX11: Expose selected render state in ImGui_ImplDX11_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
|
||||||
|
// 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-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
|
||||||
|
// 2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer.
|
||||||
|
// 2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled).
|
||||||
|
// 2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX11_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.
|
||||||
|
// 2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
|
||||||
|
// 2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
|
||||||
|
// 2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
|
||||||
|
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
|
||||||
|
// 2018-08-01: DirectX11: Querying for IDXGIFactory instead of IDXGIFactory1 to increase compatibility.
|
||||||
|
// 2018-07-13: DirectX11: Fixed unreleased resources in Init and Shutdown functions.
|
||||||
|
// 2018-06-08: Misc: Extracted imgui_impl_dx11.cpp/.h away from the old combined DX11+Win32 example.
|
||||||
|
// 2018-06-08: DirectX11: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
|
||||||
|
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX11_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.
|
||||||
|
// 2016-05-07: DirectX11: Disabling depth-write.
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
#ifndef IMGUI_DISABLE
|
||||||
|
#include "imgui_impl_dx11.h"
|
||||||
|
|
||||||
|
// DirectX
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <d3d11.h>
|
||||||
|
#include <d3dcompiler.h>
|
||||||
|
// #ifdef _MSC_VER
|
||||||
|
// #pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// spice2x: resolve D3DCompile at runtime rather than static-linking d3dcompiler,
|
||||||
|
// which would hardwire an import on d3dcompiler_47.dll (absent on stock Win7).
|
||||||
|
static pD3DCompile ImGui_ImplDX11_GetD3DCompile()
|
||||||
|
{
|
||||||
|
static pD3DCompile fn = []() -> pD3DCompile {
|
||||||
|
static const char *dlls[] = {
|
||||||
|
"d3dcompiler_47.dll",
|
||||||
|
"d3dcompiler_46.dll",
|
||||||
|
"d3dcompiler_43.dll" };
|
||||||
|
|
||||||
|
for (const char *dll : dlls) {
|
||||||
|
HMODULE mod = GetModuleHandleA(dll);
|
||||||
|
if (!mod) {
|
||||||
|
mod = LoadLibraryA(dll);
|
||||||
|
}
|
||||||
|
if (mod) {
|
||||||
|
if (auto p = (pD3DCompile) GetProcAddress(mod, "D3DCompile")) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}();
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// DirectX11 data
|
||||||
|
struct ImGui_ImplDX11_Texture
|
||||||
|
{
|
||||||
|
ID3D11Texture2D* pTexture;
|
||||||
|
ID3D11ShaderResourceView* pTextureView;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ImGui_ImplDX11_Data
|
||||||
|
{
|
||||||
|
ID3D11Device* pd3dDevice;
|
||||||
|
ID3D11DeviceContext* pd3dDeviceContext;
|
||||||
|
IDXGIFactory* pFactory;
|
||||||
|
ID3D11Buffer* pVB;
|
||||||
|
ID3D11Buffer* pIB;
|
||||||
|
ID3D11VertexShader* pVertexShader;
|
||||||
|
ID3D11InputLayout* pInputLayout;
|
||||||
|
ID3D11Buffer* pVertexConstantBuffer;
|
||||||
|
ID3D11PixelShader* pPixelShader;
|
||||||
|
ID3D11SamplerState* pTexSamplerLinear;
|
||||||
|
ID3D11SamplerState* pTexSamplerNearest;
|
||||||
|
ID3D11RasterizerState* pRasterizerState;
|
||||||
|
ID3D11BlendState* pBlendState;
|
||||||
|
ID3D11DepthStencilState* pDepthStencilState;
|
||||||
|
int VertexBufferSize;
|
||||||
|
int IndexBufferSize;
|
||||||
|
ImGui_ImplDX11_RenderState* RenderState; // == (ImGui_ImplDX11_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState during rendering.
|
||||||
|
ImVector<DXGI_SWAP_CHAIN_DESC> SwapChainDescsForViewports;
|
||||||
|
|
||||||
|
ImGui_ImplDX11_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VERTEX_CONSTANT_BUFFER_DX11
|
||||||
|
{
|
||||||
|
float mvp[4][4];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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_ImplDX11_Data* ImGui_ImplDX11_GetBackendData()
|
||||||
|
{
|
||||||
|
return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward Declarations
|
||||||
|
static void ImGui_ImplDX11_InitMultiViewportSupport();
|
||||||
|
static void ImGui_ImplDX11_ShutdownMultiViewportSupport();
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11DeviceContext* device_ctx)
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
|
||||||
|
// Setup viewport
|
||||||
|
D3D11_VIEWPORT vp = {};
|
||||||
|
vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x;
|
||||||
|
vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y;
|
||||||
|
vp.MinDepth = 0.0f;
|
||||||
|
vp.MaxDepth = 1.0f;
|
||||||
|
vp.TopLeftX = vp.TopLeftY = 0;
|
||||||
|
device_ctx->RSSetViewports(1, &vp);
|
||||||
|
|
||||||
|
// Setup orthographic projection matrix into our constant buffer
|
||||||
|
// 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.
|
||||||
|
D3D11_MAPPED_SUBRESOURCE mapped_resource;
|
||||||
|
if (device_ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) == S_OK)
|
||||||
|
{
|
||||||
|
VERTEX_CONSTANT_BUFFER_DX11* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX11*)mapped_resource.pData;
|
||||||
|
float L = draw_data->DisplayPos.x;
|
||||||
|
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
|
||||||
|
float T = draw_data->DisplayPos.y;
|
||||||
|
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
|
||||||
|
float mvp[4][4] =
|
||||||
|
{
|
||||||
|
{ 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 },
|
||||||
|
{ (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f },
|
||||||
|
};
|
||||||
|
memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));
|
||||||
|
device_ctx->Unmap(bd->pVertexConstantBuffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup shader and vertex buffers
|
||||||
|
unsigned int stride = sizeof(ImDrawVert);
|
||||||
|
unsigned int offset = 0;
|
||||||
|
device_ctx->IASetInputLayout(bd->pInputLayout);
|
||||||
|
device_ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
|
||||||
|
device_ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
|
||||||
|
device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||||
|
device_ctx->VSSetShader(bd->pVertexShader, nullptr, 0);
|
||||||
|
device_ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
|
||||||
|
device_ctx->PSSetShader(bd->pPixelShader, nullptr, 0);
|
||||||
|
device_ctx->PSSetSamplers(0, 1, &bd->pTexSamplerLinear);
|
||||||
|
device_ctx->GSSetShader(nullptr, nullptr, 0);
|
||||||
|
device_ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||||
|
device_ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||||
|
device_ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||||
|
|
||||||
|
// Setup render state
|
||||||
|
const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
|
||||||
|
device_ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
|
||||||
|
device_ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
|
||||||
|
device_ctx->RSSetState(bd->pRasterizerState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw callbacks
|
||||||
|
static void ImGui_ImplDX11_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way.
|
||||||
|
static void ImGui_ImplDX11_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); bd->RenderState->DeviceContext->PSSetSamplers(0, 1, &bd->pTexSamplerLinear); }
|
||||||
|
static void ImGui_ImplDX11_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); bd->RenderState->DeviceContext->PSSetSamplers(0, 1, &bd->pTexSamplerNearest); }
|
||||||
|
|
||||||
|
// Render function
|
||||||
|
void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
|
||||||
|
{
|
||||||
|
// Avoid rendering when minimized
|
||||||
|
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
ID3D11DeviceContext* device = bd->pd3dDeviceContext;
|
||||||
|
|
||||||
|
// 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_ImplDX11_UpdateTexture(tex);
|
||||||
|
|
||||||
|
// Create and grow vertex/index 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;
|
||||||
|
D3D11_BUFFER_DESC desc = {};
|
||||||
|
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||||
|
desc.ByteWidth = (UINT)bd->VertexBufferSize * sizeof(ImDrawVert);
|
||||||
|
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
||||||
|
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||||
|
desc.MiscFlags = 0;
|
||||||
|
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 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;
|
||||||
|
D3D11_BUFFER_DESC desc = {};
|
||||||
|
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||||
|
desc.ByteWidth = (UINT)bd->IndexBufferSize * sizeof(ImDrawIdx);
|
||||||
|
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
||||||
|
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||||
|
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload vertex/index data into a single contiguous GPU buffer
|
||||||
|
D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;
|
||||||
|
if (device->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)
|
||||||
|
return;
|
||||||
|
if (device->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)
|
||||||
|
return;
|
||||||
|
ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;
|
||||||
|
ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;
|
||||||
|
for (const ImDrawList* draw_list : draw_data->CmdLists)
|
||||||
|
{
|
||||||
|
memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||||
|
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||||
|
vtx_dst += draw_list->VtxBuffer.Size;
|
||||||
|
idx_dst += draw_list->IdxBuffer.Size;
|
||||||
|
}
|
||||||
|
device->Unmap(bd->pVB, 0);
|
||||||
|
device->Unmap(bd->pIB, 0);
|
||||||
|
|
||||||
|
// Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)
|
||||||
|
struct BACKUP_DX11_STATE
|
||||||
|
{
|
||||||
|
UINT ScissorRectsCount, ViewportsCount;
|
||||||
|
D3D11_RECT ScissorRects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
|
||||||
|
D3D11_VIEWPORT Viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
|
||||||
|
ID3D11RasterizerState* RS;
|
||||||
|
ID3D11BlendState* BlendState;
|
||||||
|
FLOAT BlendFactor[4];
|
||||||
|
UINT SampleMask;
|
||||||
|
UINT StencilRef;
|
||||||
|
ID3D11DepthStencilState* DepthStencilState;
|
||||||
|
ID3D11ShaderResourceView* PSShaderResource;
|
||||||
|
ID3D11SamplerState* PSSampler;
|
||||||
|
ID3D11PixelShader* PS;
|
||||||
|
ID3D11VertexShader* VS;
|
||||||
|
ID3D11GeometryShader* GS;
|
||||||
|
UINT PSInstancesCount, VSInstancesCount, GSInstancesCount;
|
||||||
|
ID3D11ClassInstance *PSInstances[256], *VSInstances[256], *GSInstances[256]; // 256 is max according to PSSetShader documentation
|
||||||
|
D3D11_PRIMITIVE_TOPOLOGY PrimitiveTopology;
|
||||||
|
ID3D11Buffer* IndexBuffer, *VertexBuffer, *VSConstantBuffer;
|
||||||
|
UINT IndexBufferOffset, VertexBufferStride, VertexBufferOffset;
|
||||||
|
DXGI_FORMAT IndexBufferFormat;
|
||||||
|
ID3D11InputLayout* InputLayout;
|
||||||
|
};
|
||||||
|
BACKUP_DX11_STATE old = {};
|
||||||
|
old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;
|
||||||
|
device->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
|
||||||
|
device->RSGetViewports(&old.ViewportsCount, old.Viewports);
|
||||||
|
device->RSGetState(&old.RS);
|
||||||
|
device->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
|
||||||
|
device->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
|
||||||
|
device->PSGetShaderResources(0, 1, &old.PSShaderResource);
|
||||||
|
device->PSGetSamplers(0, 1, &old.PSSampler);
|
||||||
|
old.PSInstancesCount = old.VSInstancesCount = old.GSInstancesCount = 256;
|
||||||
|
device->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);
|
||||||
|
device->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);
|
||||||
|
device->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
|
||||||
|
device->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);
|
||||||
|
|
||||||
|
device->IAGetPrimitiveTopology(&old.PrimitiveTopology);
|
||||||
|
device->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
|
||||||
|
device->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
|
||||||
|
device->IAGetInputLayout(&old.InputLayout);
|
||||||
|
|
||||||
|
// Setup desired DX state
|
||||||
|
ImGui_ImplDX11_SetupRenderState(draw_data, device);
|
||||||
|
|
||||||
|
// Setup render state structure (for callbacks and custom texture bindings)
|
||||||
|
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||||
|
ImGui_ImplDX11_RenderState render_state;
|
||||||
|
render_state.Device = bd->pd3dDevice;
|
||||||
|
render_state.DeviceContext = bd->pd3dDeviceContext;
|
||||||
|
render_state.VertexConstantBuffer = bd->pVertexConstantBuffer;
|
||||||
|
platform_io.Renderer_RenderState = bd->RenderState = &render_state;
|
||||||
|
|
||||||
|
// Render command lists
|
||||||
|
// (Because we merged all buffers into a single one, we maintain our own offset into them)
|
||||||
|
int global_idx_offset = 0;
|
||||||
|
int global_vtx_offset = 0;
|
||||||
|
ImVec2 clip_off = draw_data->DisplayPos;
|
||||||
|
ImVec2 clip_scale = draw_data->FramebufferScale;
|
||||||
|
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()
|
||||||
|
if (pcmd->UserCallback == ImGui_ImplDX11_DrawCallback_ResetRenderState)
|
||||||
|
ImGui_ImplDX11_SetupRenderState(draw_data, device);
|
||||||
|
else
|
||||||
|
pcmd->UserCallback(draw_list, pcmd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Project scissor/clipping rectangles into framebuffer space
|
||||||
|
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
|
||||||
|
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
|
||||||
|
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Apply scissor/clipping rectangle
|
||||||
|
const D3D11_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
|
||||||
|
device->RSSetScissorRects(1, &r);
|
||||||
|
|
||||||
|
// Bind texture, Draw
|
||||||
|
ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->GetTexID();
|
||||||
|
device->PSSetShaderResources(0, 1, &texture_srv);
|
||||||
|
device->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
global_idx_offset += draw_list->IdxBuffer.Size;
|
||||||
|
global_vtx_offset += draw_list->VtxBuffer.Size;
|
||||||
|
}
|
||||||
|
platform_io.Renderer_RenderState = bd->RenderState = nullptr;
|
||||||
|
|
||||||
|
// Restore modified DX state
|
||||||
|
device->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
|
||||||
|
device->RSSetViewports(old.ViewportsCount, old.Viewports);
|
||||||
|
device->RSSetState(old.RS); if (old.RS) old.RS->Release();
|
||||||
|
device->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
|
||||||
|
device->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
|
||||||
|
device->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
|
||||||
|
device->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
|
||||||
|
device->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();
|
||||||
|
for (UINT i = 0; i < old.PSInstancesCount; i++) if (old.PSInstances[i]) old.PSInstances[i]->Release();
|
||||||
|
device->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();
|
||||||
|
device->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
|
||||||
|
device->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();
|
||||||
|
for (UINT i = 0; i < old.VSInstancesCount; i++) if (old.VSInstances[i]) old.VSInstances[i]->Release();
|
||||||
|
device->IASetPrimitiveTopology(old.PrimitiveTopology);
|
||||||
|
device->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
|
||||||
|
device->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
|
||||||
|
device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplDX11_DestroyTexture(ImTextureData* tex)
|
||||||
|
{
|
||||||
|
if (ImGui_ImplDX11_Texture* backend_tex = (ImGui_ImplDX11_Texture*)tex->BackendUserData)
|
||||||
|
{
|
||||||
|
IM_ASSERT(backend_tex->pTextureView == (ID3D11ShaderResourceView*)(intptr_t)tex->TexID);
|
||||||
|
backend_tex->pTextureView->Release();
|
||||||
|
backend_tex->pTexture->Release();
|
||||||
|
IM_DELETE(backend_tex);
|
||||||
|
|
||||||
|
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
|
||||||
|
tex->SetTexID(ImTextureID_Invalid);
|
||||||
|
tex->BackendUserData = nullptr;
|
||||||
|
}
|
||||||
|
tex->SetStatus(ImTextureStatus_Destroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex)
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
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);
|
||||||
|
unsigned int* pixels = (unsigned int*)tex->GetPixels();
|
||||||
|
ImGui_ImplDX11_Texture* backend_tex = IM_NEW(ImGui_ImplDX11_Texture)();
|
||||||
|
|
||||||
|
// Create texture
|
||||||
|
D3D11_TEXTURE2D_DESC desc;
|
||||||
|
ZeroMemory(&desc, sizeof(desc));
|
||||||
|
desc.Width = (UINT)tex->Width;
|
||||||
|
desc.Height = (UINT)tex->Height;
|
||||||
|
desc.MipLevels = 1;
|
||||||
|
desc.ArraySize = 1;
|
||||||
|
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
|
||||||
|
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||||
|
#else
|
||||||
|
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
#endif
|
||||||
|
desc.SampleDesc.Count = 1;
|
||||||
|
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||||
|
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||||
|
desc.CPUAccessFlags = 0;
|
||||||
|
D3D11_SUBRESOURCE_DATA subResource;
|
||||||
|
subResource.pSysMem = pixels;
|
||||||
|
subResource.SysMemPitch = desc.Width * 4;
|
||||||
|
subResource.SysMemSlicePitch = 0;
|
||||||
|
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &backend_tex->pTexture);
|
||||||
|
IM_ASSERT(backend_tex->pTexture != nullptr && "Backend failed to create texture!");
|
||||||
|
|
||||||
|
// Create texture view
|
||||||
|
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
|
||||||
|
ZeroMemory(&srvDesc, sizeof(srvDesc));
|
||||||
|
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
|
||||||
|
srvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||||
|
#else
|
||||||
|
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
#endif
|
||||||
|
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
||||||
|
srvDesc.Texture2D.MipLevels = desc.MipLevels;
|
||||||
|
srvDesc.Texture2D.MostDetailedMip = 0;
|
||||||
|
bd->pd3dDevice->CreateShaderResourceView(backend_tex->pTexture, &srvDesc, &backend_tex->pTextureView);
|
||||||
|
IM_ASSERT(backend_tex->pTextureView != nullptr && "Backend failed to create texture!");
|
||||||
|
|
||||||
|
// Store identifiers
|
||||||
|
tex->SetTexID((ImTextureID)(intptr_t)backend_tex->pTextureView);
|
||||||
|
tex->SetStatus(ImTextureStatus_OK);
|
||||||
|
tex->BackendUserData = backend_tex;
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
ImGui_ImplDX11_Texture* backend_tex = (ImGui_ImplDX11_Texture*)tex->BackendUserData;
|
||||||
|
IM_ASSERT(backend_tex->pTextureView == (ID3D11ShaderResourceView*)(intptr_t)tex->TexID);
|
||||||
|
for (ImTextureRect& r : tex->Updates)
|
||||||
|
{
|
||||||
|
D3D11_BOX box = { (UINT)r.x, (UINT)r.y, (UINT)0, (UINT)(r.x + r.w), (UINT)(r.y + r .h), (UINT)1 };
|
||||||
|
bd->pd3dDeviceContext->UpdateSubresource(backend_tex->pTexture, 0, &box, tex->GetPixelsAt(r.x, r.y), (UINT)tex->GetPitch(), 0);
|
||||||
|
}
|
||||||
|
tex->SetStatus(ImTextureStatus_OK);
|
||||||
|
}
|
||||||
|
if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
|
||||||
|
ImGui_ImplDX11_DestroyTexture(tex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGui_ImplDX11_CreateDeviceObjects()
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
if (!bd->pd3dDevice)
|
||||||
|
return false;
|
||||||
|
ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||||
|
|
||||||
|
// By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
|
||||||
|
// If you would like to use this DX11 sample code but remove this dependency you can:
|
||||||
|
// 1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution]
|
||||||
|
// 2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL.
|
||||||
|
// See https://github.com/ocornut/imgui/pull/638 for sources and details.
|
||||||
|
|
||||||
|
// spice2x: dynamically resolved; see ImGui_ImplDX11_GetD3DCompile.
|
||||||
|
pD3DCompile D3DCompile_fn = ImGui_ImplDX11_GetD3DCompile();
|
||||||
|
if (!D3DCompile_fn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the vertex shader
|
||||||
|
{
|
||||||
|
static const char* vertexShader =
|
||||||
|
"cbuffer vertexBuffer : register(b0) \
|
||||||
|
{\
|
||||||
|
float4x4 ProjectionMatrix; \
|
||||||
|
};\
|
||||||
|
struct VS_INPUT\
|
||||||
|
{\
|
||||||
|
float2 pos : POSITION;\
|
||||||
|
float4 col : COLOR0;\
|
||||||
|
float2 uv : TEXCOORD0;\
|
||||||
|
};\
|
||||||
|
\
|
||||||
|
struct PS_INPUT\
|
||||||
|
{\
|
||||||
|
float4 pos : SV_POSITION;\
|
||||||
|
float4 col : COLOR0;\
|
||||||
|
float2 uv : TEXCOORD0;\
|
||||||
|
};\
|
||||||
|
\
|
||||||
|
PS_INPUT main(VS_INPUT input)\
|
||||||
|
{\
|
||||||
|
PS_INPUT output;\
|
||||||
|
output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
|
||||||
|
output.col = input.col;\
|
||||||
|
output.uv = input.uv;\
|
||||||
|
return output;\
|
||||||
|
}";
|
||||||
|
|
||||||
|
ID3DBlob* vertexShaderBlob;
|
||||||
|
if (FAILED(D3DCompile_fn(vertexShader, strlen(vertexShader), nullptr, nullptr, nullptr, "main", "vs_4_0", 0, 0, &vertexShaderBlob, nullptr)))
|
||||||
|
return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
|
||||||
|
if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), nullptr, &bd->pVertexShader) != S_OK)
|
||||||
|
{
|
||||||
|
vertexShaderBlob->Release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the input layout
|
||||||
|
D3D11_INPUT_ELEMENT_DESC local_layout[] =
|
||||||
|
{
|
||||||
|
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||||
|
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||||
|
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
|
||||||
|
{ "COLOR", 0, DXGI_FORMAT_B8G8R8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||||
|
#else
|
||||||
|
{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK)
|
||||||
|
{
|
||||||
|
vertexShaderBlob->Release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vertexShaderBlob->Release();
|
||||||
|
|
||||||
|
// Create the constant buffer
|
||||||
|
{
|
||||||
|
D3D11_BUFFER_DESC desc = {};
|
||||||
|
desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX11);
|
||||||
|
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||||
|
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
||||||
|
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||||
|
desc.MiscFlags = 0;
|
||||||
|
bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVertexConstantBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the pixel shader
|
||||||
|
{
|
||||||
|
static const char* pixelShader =
|
||||||
|
"struct PS_INPUT\
|
||||||
|
{\
|
||||||
|
float4 pos : SV_POSITION;\
|
||||||
|
float4 col : COLOR0;\
|
||||||
|
float2 uv : TEXCOORD0;\
|
||||||
|
};\
|
||||||
|
sampler sampler0;\
|
||||||
|
Texture2D texture0;\
|
||||||
|
\
|
||||||
|
float4 main(PS_INPUT input) : SV_Target\
|
||||||
|
{\
|
||||||
|
float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \
|
||||||
|
return out_col; \
|
||||||
|
}";
|
||||||
|
|
||||||
|
ID3DBlob* pixelShaderBlob;
|
||||||
|
if (FAILED(D3DCompile_fn(pixelShader, strlen(pixelShader), nullptr, nullptr, nullptr, "main", "ps_4_0", 0, 0, &pixelShaderBlob, nullptr)))
|
||||||
|
return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
|
||||||
|
if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), nullptr, &bd->pPixelShader) != S_OK)
|
||||||
|
{
|
||||||
|
pixelShaderBlob->Release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pixelShaderBlob->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the blending setup
|
||||||
|
{
|
||||||
|
D3D11_BLEND_DESC desc;
|
||||||
|
ZeroMemory(&desc, sizeof(desc));
|
||||||
|
desc.AlphaToCoverageEnable = false;
|
||||||
|
desc.RenderTarget[0].BlendEnable = true;
|
||||||
|
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
|
||||||
|
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
||||||
|
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
||||||
|
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
||||||
|
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
|
||||||
|
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
||||||
|
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
|
||||||
|
bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the rasterizer state
|
||||||
|
{
|
||||||
|
D3D11_RASTERIZER_DESC desc;
|
||||||
|
ZeroMemory(&desc, sizeof(desc));
|
||||||
|
desc.FillMode = D3D11_FILL_SOLID;
|
||||||
|
desc.CullMode = D3D11_CULL_NONE;
|
||||||
|
desc.ScissorEnable = true;
|
||||||
|
desc.DepthClipEnable = true;
|
||||||
|
bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create depth-stencil State
|
||||||
|
{
|
||||||
|
D3D11_DEPTH_STENCIL_DESC desc;
|
||||||
|
ZeroMemory(&desc, sizeof(desc));
|
||||||
|
desc.DepthEnable = false;
|
||||||
|
desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
|
||||||
|
desc.DepthFunc = D3D11_COMPARISON_ALWAYS;
|
||||||
|
desc.StencilEnable = false;
|
||||||
|
desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
|
||||||
|
desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
|
||||||
|
desc.BackFace = desc.FrontFace;
|
||||||
|
bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create texture sampler
|
||||||
|
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
|
||||||
|
{
|
||||||
|
D3D11_SAMPLER_DESC desc;
|
||||||
|
ZeroMemory(&desc, sizeof(desc));
|
||||||
|
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
||||||
|
desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||||
|
desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||||
|
desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||||
|
desc.MipLODBias = 0.f;
|
||||||
|
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
|
||||||
|
desc.MinLOD = 0.f;
|
||||||
|
desc.MaxLOD = 0.f;
|
||||||
|
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinear);
|
||||||
|
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
|
||||||
|
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerNearest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGui_ImplDX11_InvalidateDeviceObjects()
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
if (!bd->pd3dDevice)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Destroy all textures
|
||||||
|
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
|
||||||
|
if (tex->RefCount == 1)
|
||||||
|
ImGui_ImplDX11_DestroyTexture(tex);
|
||||||
|
|
||||||
|
if (bd->pTexSamplerLinear) { bd->pTexSamplerLinear->Release(); bd->pTexSamplerLinear = nullptr; }
|
||||||
|
if (bd->pTexSamplerNearest) { bd->pTexSamplerNearest->Release(); bd->pTexSamplerNearest = nullptr; }
|
||||||
|
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
|
||||||
|
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
|
||||||
|
if (bd->pBlendState) { bd->pBlendState->Release(); bd->pBlendState = nullptr; }
|
||||||
|
if (bd->pDepthStencilState) { bd->pDepthStencilState->Release(); bd->pDepthStencilState = nullptr; }
|
||||||
|
if (bd->pRasterizerState) { bd->pRasterizerState->Release(); bd->pRasterizerState = nullptr; }
|
||||||
|
if (bd->pPixelShader) { bd->pPixelShader->Release(); bd->pPixelShader = nullptr; }
|
||||||
|
if (bd->pVertexConstantBuffer) { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = nullptr; }
|
||||||
|
if (bd->pInputLayout) { bd->pInputLayout->Release(); bd->pInputLayout = nullptr; }
|
||||||
|
if (bd->pVertexShader) { bd->pVertexShader->Release(); bd->pVertexShader = nullptr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context)
|
||||||
|
{
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
|
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
|
||||||
|
|
||||||
|
// Setup backend capabilities flags
|
||||||
|
ImGui_ImplDX11_Data* bd = IM_NEW(ImGui_ImplDX11_Data)();
|
||||||
|
io.BackendRendererUserData = (void*)bd;
|
||||||
|
io.BackendRendererName = "imgui_impl_dx11";
|
||||||
|
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)
|
||||||
|
|
||||||
|
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||||
|
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||||
|
platform_io.DrawCallback_ResetRenderState = ImGui_ImplDX11_DrawCallback_ResetRenderState;
|
||||||
|
platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplDX11_DrawCallback_SetSamplerLinear;
|
||||||
|
platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplDX11_DrawCallback_SetSamplerNearest;
|
||||||
|
|
||||||
|
// Get factory from device
|
||||||
|
IDXGIDevice* pDXGIDevice = nullptr;
|
||||||
|
IDXGIAdapter* pDXGIAdapter = nullptr;
|
||||||
|
IDXGIFactory* pFactory = nullptr;
|
||||||
|
|
||||||
|
if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK)
|
||||||
|
if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK)
|
||||||
|
if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK)
|
||||||
|
{
|
||||||
|
bd->pd3dDevice = device;
|
||||||
|
bd->pd3dDeviceContext = device_context;
|
||||||
|
bd->pFactory = pFactory;
|
||||||
|
}
|
||||||
|
if (pDXGIDevice) pDXGIDevice->Release();
|
||||||
|
if (pDXGIAdapter) pDXGIAdapter->Release();
|
||||||
|
bd->pd3dDevice->AddRef();
|
||||||
|
bd->pd3dDeviceContext->AddRef();
|
||||||
|
|
||||||
|
ImGui_ImplDX11_InitMultiViewportSupport();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGui_ImplDX11_Shutdown()
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||||
|
|
||||||
|
ImGui_ImplDX11_ShutdownMultiViewportSupport();
|
||||||
|
ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||||
|
if (bd->pFactory) { bd->pFactory->Release(); }
|
||||||
|
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
|
||||||
|
if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); }
|
||||||
|
|
||||||
|
io.BackendRendererName = nullptr;
|
||||||
|
io.BackendRendererUserData = nullptr;
|
||||||
|
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
|
||||||
|
platform_io.ClearRendererHandlers();
|
||||||
|
IM_DELETE(bd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGui_ImplDX11_NewFrame()
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX11_Init()?");
|
||||||
|
|
||||||
|
if (!bd->pVertexShader)
|
||||||
|
if (!ImGui_ImplDX11_CreateDeviceObjects())
|
||||||
|
IM_ASSERT(0 && "ImGui_ImplDX11_CreateDeviceObjects() failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------------
|
||||||
|
// 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_ImplDX11_ViewportData
|
||||||
|
{
|
||||||
|
IDXGISwapChain* SwapChain;
|
||||||
|
ID3D11RenderTargetView* RTView;
|
||||||
|
|
||||||
|
ImGui_ImplDX11_ViewportData() { SwapChain = nullptr; RTView = nullptr; }
|
||||||
|
~ImGui_ImplDX11_ViewportData() { IM_ASSERT(SwapChain == nullptr && RTView == nullptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Multi-Viewports: configure templates used when creating swapchains for secondary viewports. Will try them in order.
|
||||||
|
// This is intentionally not declared in the .h file yet, so you will need to copy this declaration:
|
||||||
|
void ImGui_ImplDX11_SetSwapChainDescs(const DXGI_SWAP_CHAIN_DESC* desc_templates, int desc_templates_count);
|
||||||
|
void ImGui_ImplDX11_SetSwapChainDescs(const DXGI_SWAP_CHAIN_DESC* desc_templates, int desc_templates_count)
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
bd->SwapChainDescsForViewports.resize(desc_templates_count);
|
||||||
|
memcpy(bd->SwapChainDescsForViewports.Data, desc_templates, sizeof(DXGI_SWAP_CHAIN_DESC));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplDX11_CreateWindow(ImGuiViewport* viewport)
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
ImGui_ImplDX11_ViewportData* vd = IM_NEW(ImGui_ImplDX11_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);
|
||||||
|
IM_ASSERT(vd->SwapChain == nullptr && vd->RTView == nullptr);
|
||||||
|
|
||||||
|
// Create swap chain
|
||||||
|
HRESULT hr = DXGI_ERROR_UNSUPPORTED;
|
||||||
|
for (const DXGI_SWAP_CHAIN_DESC& sd_template : bd->SwapChainDescsForViewports)
|
||||||
|
{
|
||||||
|
IM_ASSERT(sd_template.BufferDesc.Width == 0 && sd_template.BufferDesc.Height == 0 && sd_template.OutputWindow == nullptr);
|
||||||
|
DXGI_SWAP_CHAIN_DESC sd = sd_template;
|
||||||
|
sd.BufferDesc.Width = (UINT)viewport->Size.x;
|
||||||
|
sd.BufferDesc.Height = (UINT)viewport->Size.y;
|
||||||
|
sd.OutputWindow = hwnd;
|
||||||
|
hr = bd->pFactory->CreateSwapChain(bd->pd3dDevice, &sd, &vd->SwapChain);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
IM_ASSERT(SUCCEEDED(hr));
|
||||||
|
bd->pFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES); // Disable e.g. Alt+Enter
|
||||||
|
|
||||||
|
// Create the render target
|
||||||
|
if (vd->SwapChain != nullptr)
|
||||||
|
{
|
||||||
|
ID3D11Texture2D* pBackBuffer;
|
||||||
|
vd->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
|
||||||
|
bd->pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &vd->RTView);
|
||||||
|
pBackBuffer->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplDX11_DestroyWindow(ImGuiViewport* viewport)
|
||||||
|
{
|
||||||
|
// The main viewport (owned by the application) will always have RendererUserData == nullptr since we didn't create the data for it.
|
||||||
|
if (ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData)
|
||||||
|
{
|
||||||
|
if (vd->SwapChain)
|
||||||
|
vd->SwapChain->Release();
|
||||||
|
vd->SwapChain = nullptr;
|
||||||
|
if (vd->RTView)
|
||||||
|
vd->RTView->Release();
|
||||||
|
vd->RTView = nullptr;
|
||||||
|
IM_DELETE(vd);
|
||||||
|
}
|
||||||
|
viewport->RendererUserData = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplDX11_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData;
|
||||||
|
if (vd->RTView)
|
||||||
|
{
|
||||||
|
vd->RTView->Release();
|
||||||
|
vd->RTView = nullptr;
|
||||||
|
}
|
||||||
|
if (vd->SwapChain)
|
||||||
|
{
|
||||||
|
ID3D11Texture2D* pBackBuffer = nullptr;
|
||||||
|
vd->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0);
|
||||||
|
vd->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
|
||||||
|
if (pBackBuffer == nullptr) { fprintf(stderr, "ImGui_ImplDX11_SetWindowSize() failed creating buffers.\n"); return; }
|
||||||
|
bd->pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &vd->RTView);
|
||||||
|
pBackBuffer->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplDX11_RenderWindow(ImGuiViewport* viewport, void*)
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||||
|
ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData;
|
||||||
|
ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
bd->pd3dDeviceContext->OMSetRenderTargets(1, &vd->RTView, nullptr);
|
||||||
|
if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
|
||||||
|
bd->pd3dDeviceContext->ClearRenderTargetView(vd->RTView, (float*)&clear_color);
|
||||||
|
ImGui_ImplDX11_RenderDrawData(viewport->DrawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplDX11_SwapBuffers(ImGuiViewport* viewport, void*)
|
||||||
|
{
|
||||||
|
ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData;
|
||||||
|
if (vd->SwapChain)
|
||||||
|
vd->SwapChain->Present(0, 0); // Present without vsync
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplDX11_InitMultiViewportSupport()
|
||||||
|
{
|
||||||
|
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||||
|
platform_io.Renderer_CreateWindow = ImGui_ImplDX11_CreateWindow;
|
||||||
|
platform_io.Renderer_DestroyWindow = ImGui_ImplDX11_DestroyWindow;
|
||||||
|
platform_io.Renderer_SetWindowSize = ImGui_ImplDX11_SetWindowSize;
|
||||||
|
platform_io.Renderer_RenderWindow = ImGui_ImplDX11_RenderWindow;
|
||||||
|
platform_io.Renderer_SwapBuffers = ImGui_ImplDX11_SwapBuffers;
|
||||||
|
|
||||||
|
// Default swapchain format
|
||||||
|
DXGI_SWAP_CHAIN_DESC sd;
|
||||||
|
ZeroMemory(&sd, sizeof(sd));
|
||||||
|
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
sd.SampleDesc.Count = 1;
|
||||||
|
sd.SampleDesc.Quality = 0;
|
||||||
|
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||||
|
sd.BufferCount = 1;
|
||||||
|
sd.Windowed = TRUE;
|
||||||
|
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
||||||
|
sd.Flags = 0;
|
||||||
|
ImGui_ImplDX11_SetSwapChainDescs(&sd, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ImGui_ImplDX11_ShutdownMultiViewportSupport()
|
||||||
|
{
|
||||||
|
ImGui::DestroyPlatformWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#endif // #ifndef IMGUI_DISABLE
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// dear imgui: Renderer Backend for DirectX11
|
||||||
|
// This needs to be used along with a Platform Backend (e.g. Win32)
|
||||||
|
|
||||||
|
// Implemented features:
|
||||||
|
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' 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: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
|
||||||
|
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
|
||||||
|
|
||||||
|
// 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" // IMGUI_IMPL_API
|
||||||
|
#ifndef IMGUI_DISABLE
|
||||||
|
|
||||||
|
struct ID3D11Device;
|
||||||
|
struct ID3D11DeviceContext;
|
||||||
|
struct ID3D11SamplerState;
|
||||||
|
struct ID3D11Buffer;
|
||||||
|
|
||||||
|
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
|
||||||
|
IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);
|
||||||
|
IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown();
|
||||||
|
IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame();
|
||||||
|
IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
|
||||||
|
|
||||||
|
// Use if you want to reset your rendering device without losing Dear ImGui state.
|
||||||
|
IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects();
|
||||||
|
IMGUI_IMPL_API void ImGui_ImplDX11_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_ImplDX11_UpdateTexture(ImTextureData* tex);
|
||||||
|
|
||||||
|
// [BETA] Selected render state data shared with callbacks.
|
||||||
|
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX11_RenderDrawData() call.
|
||||||
|
// (Please open an issue if you feel you need access to more data)
|
||||||
|
struct ImGui_ImplDX11_RenderState
|
||||||
|
{
|
||||||
|
ID3D11Device* Device;
|
||||||
|
ID3D11DeviceContext* DeviceContext;
|
||||||
|
ID3D11Buffer* VertexConstantBuffer;
|
||||||
|
//ID3D11SamplerState* SamplerLinear; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerLinear)
|
||||||
|
//ID3D11SamplerState* SamplerNearest; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerNearest)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // #ifndef IMGUI_DISABLE
|
||||||
+664
-255
File diff suppressed because it is too large
Load Diff
+25
-12
@@ -1,25 +1,38 @@
|
|||||||
// dear imgui: Renderer for DirectX9
|
// dear imgui: Renderer Backend for DirectX9
|
||||||
// This needs to be used along with a Platform Binding (e.g. Win32)
|
// This needs to be used along with a Platform Backend (e.g. Win32)
|
||||||
|
|
||||||
// Implemented features:
|
// Implemented features:
|
||||||
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
|
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||||
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
|
// [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.
|
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||||
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
|
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||||
// https://github.com/ocornut/imgui
|
// 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
|
#pragma once
|
||||||
|
#include "imgui.h" // IMGUI_IMPL_API
|
||||||
#include "imgui.h"
|
#ifndef IMGUI_DISABLE
|
||||||
|
|
||||||
struct IDirect3DDevice9;
|
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_Shutdown();
|
||||||
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
|
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 bool ImGui_ImplDX9_CreateDeviceObjects();
|
||||||
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
|
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
|
#pragma once
|
||||||
|
|
||||||
//---- Define assertion handler. Defaults to calling assert().
|
//---- 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) MyAssert(_EXPR)
|
||||||
#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
|
#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.
|
//---- 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_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.
|
//---- 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.
|
// 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_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_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_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
|
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
|
||||||
|
|
||||||
//---- Enable Test Engine / Automation features.
|
//---- Enable Test Engine / Automation features.
|
||||||
@@ -59,12 +60,15 @@
|
|||||||
//#define IMGUI_INCLUDE_IMGUI_USER_H
|
//#define IMGUI_INCLUDE_IMGUI_USER_H
|
||||||
//#define IMGUI_USER_H_FILENAME "my_folder/my_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
|
#define IMGUI_USE_BGRA_PACKED_COLOR
|
||||||
|
|
||||||
//---- Performance optimization for DX9
|
//---- Performance optimization for DX9
|
||||||
#define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
|
#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...)
|
//---- 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
|
//#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)
|
//---- 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).
|
// 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'.
|
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
|
||||||
//#define IMGUI_ENABLE_FREETYPE
|
//#define IMGUI_ENABLE_FREETYPE
|
||||||
|
|
||||||
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
|
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
|
||||||
// Only works in combination with IMGUI_ENABLE_FREETYPE.
|
// Only works in combination with IMGUI_ENABLE_FREETYPE.
|
||||||
// - lunasvg is currently easier to acquire/install, as e.g. it is part of vcpkg.
|
// - 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.
|
||||||
// - 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.
|
|
||||||
// - Both require headers to be available in the include path + program to be linked with the library code (not provided).
|
// - 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)
|
// - (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
|
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
|
||||||
@@ -130,6 +134,10 @@
|
|||||||
//#define IM_DEBUG_BREAK IM_ASSERT(0)
|
//#define IM_DEBUG_BREAK IM_ASSERT(0)
|
||||||
//#define IM_DEBUG_BREAK __debugbreak()
|
//#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
|
//---- Debug Tools: Enable slower asserts
|
||||||
//#define IMGUI_DEBUG_PARANOID
|
//#define IMGUI_DEBUG_PARANOID
|
||||||
|
|
||||||
|
|||||||
Vendored
+3481
-1901
File diff suppressed because it is too large
Load Diff
Vendored
+1062
-524
File diff suppressed because it is too large
Load Diff
+2940
-2069
File diff suppressed because it is too large
Load Diff
+3308
-1177
File diff suppressed because it is too large
Load Diff
+794
-335
File diff suppressed because it is too large
Load Diff
+255
-161
@@ -1,6 +1,7 @@
|
|||||||
// Mini memory editor for Dear ImGui (to embed in your game/tools)
|
// Mini memory editor for Dear ImGui (to embed in your game/tools)
|
||||||
// Get latest version at http://www.github.com/ocornut/imgui_club
|
// 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!
|
// Right-click anywhere to access the Options menu!
|
||||||
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
|
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
|
||||||
// The code assume a mono-space font for simplicity!
|
// 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.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.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.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.
|
// - 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.
|
// - 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.
|
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
|
||||||
|
|
||||||
@@ -51,15 +64,18 @@
|
|||||||
#include <stdio.h> // sprintf, scanf
|
#include <stdio.h> // sprintf, scanf
|
||||||
#include <stdint.h> // uint8_t, etc.
|
#include <stdint.h> // uint8_t, etc.
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#if defined(_MSC_VER) && !defined(snprintf)
|
||||||
#define _PRISizeT "I"
|
|
||||||
#define ImSnprintf _snprintf
|
#define ImSnprintf _snprintf
|
||||||
#else
|
#else
|
||||||
#define _PRISizeT "z"
|
|
||||||
#define ImSnprintf snprintf
|
#define ImSnprintf snprintf
|
||||||
#endif
|
#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 (push)
|
||||||
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
|
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
|
||||||
#endif
|
#endif
|
||||||
@@ -88,9 +104,17 @@ struct MemoryEditor
|
|||||||
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr).
|
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
|
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
|
||||||
ImU32 HighlightColor; // // background color of highlighted bytes.
|
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.
|
// Function handlers
|
||||||
bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
|
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]
|
// [Internal State]
|
||||||
bool ContentsWidthChanged;
|
bool ContentsWidthChanged;
|
||||||
@@ -101,7 +125,7 @@ struct MemoryEditor
|
|||||||
char AddrInputBuf[32];
|
char AddrInputBuf[32];
|
||||||
size_t GotoAddr;
|
size_t GotoAddr;
|
||||||
size_t HighlightMin, HighlightMax;
|
size_t HighlightMin, HighlightMax;
|
||||||
int PreviewEndianess;
|
int PreviewEndianness;
|
||||||
ImGuiDataType PreviewDataType;
|
ImGuiDataType PreviewDataType;
|
||||||
|
|
||||||
MemoryEditor()
|
MemoryEditor()
|
||||||
@@ -120,9 +144,11 @@ struct MemoryEditor
|
|||||||
OptAddrDigitsCount = 0;
|
OptAddrDigitsCount = 0;
|
||||||
OptFooterExtraHeight = 0.0f;
|
OptFooterExtraHeight = 0.0f;
|
||||||
HighlightColor = IM_COL32(255, 255, 255, 50);
|
HighlightColor = IM_COL32(255, 255, 255, 50);
|
||||||
ReadFn = NULL;
|
ReadFn = nullptr;
|
||||||
WriteFn = NULL;
|
WriteFn = nullptr;
|
||||||
HighlightFn = NULL;
|
HighlightFn = nullptr;
|
||||||
|
BgColorFn = nullptr;
|
||||||
|
UserData = nullptr;
|
||||||
|
|
||||||
// State/Internals
|
// State/Internals
|
||||||
ContentsWidthChanged = false;
|
ContentsWidthChanged = false;
|
||||||
@@ -131,8 +157,10 @@ struct MemoryEditor
|
|||||||
memset(DataInputBuf, 0, sizeof(DataInputBuf));
|
memset(DataInputBuf, 0, sizeof(DataInputBuf));
|
||||||
memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
|
memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
|
||||||
GotoAddr = (size_t)-1;
|
GotoAddr = (size_t)-1;
|
||||||
|
MouseHovered = false;
|
||||||
|
MouseHoveredAddr = 0;
|
||||||
HighlightMin = HighlightMax = (size_t)-1;
|
HighlightMin = HighlightMax = (size_t)-1;
|
||||||
PreviewEndianess = 0;
|
PreviewEndianness = 0;
|
||||||
PreviewDataType = ImGuiDataType_S32;
|
PreviewDataType = ImGuiDataType_S32;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,16 +173,16 @@ struct MemoryEditor
|
|||||||
|
|
||||||
struct Sizes
|
struct Sizes
|
||||||
{
|
{
|
||||||
int AddrDigitsCount;
|
int AddrDigitsCount; // Number of digits required to represent maximum address.
|
||||||
float LineHeight;
|
float LineHeight; // Height of each line (no spacing).
|
||||||
float GlyphWidth;
|
float GlyphWidth; // Glyph width (assume mono-space).
|
||||||
float HexCellWidth;
|
float HexCellWidth; // Width of a hex edit cell ~2.5f * GlypHWidth.
|
||||||
float SpacingBetweenMidCols;
|
float SpacingBetweenMidCols; // Spacing between each columns section (OptMidColsCount).
|
||||||
float PosHexStart;
|
float OffsetHexMinX;
|
||||||
float PosHexEnd;
|
float OffsetHexMaxX;
|
||||||
float PosAsciiStart;
|
float OffsetAsciiMinX;
|
||||||
float PosAsciiEnd;
|
float OffsetAsciiMaxX;
|
||||||
float WindowWidth;
|
float WindowWidth; // Ideal window width.
|
||||||
|
|
||||||
Sizes() { memset(this, 0, sizeof(*this)); }
|
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.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.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.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.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth;
|
||||||
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols);
|
s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * Cols);
|
||||||
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd;
|
s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX;
|
||||||
if (OptShowAscii)
|
if (OptShowAscii)
|
||||||
{
|
{
|
||||||
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
|
s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1;
|
||||||
if (OptMidColsCount > 0)
|
if (OptMidColsCount > 0)
|
||||||
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
|
s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
|
||||||
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
|
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
|
// Standalone Memory Editor window
|
||||||
@@ -194,8 +222,6 @@ struct MemoryEditor
|
|||||||
Open = true;
|
Open = true;
|
||||||
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar))
|
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);
|
DrawContents(mem_data, mem_size, base_display_addr);
|
||||||
if (ContentsWidthChanged)
|
if (ContentsWidthChanged)
|
||||||
{
|
{
|
||||||
@@ -217,6 +243,8 @@ struct MemoryEditor
|
|||||||
CalcSizes(s, mem_size, base_display_addr);
|
CalcSizes(s, mem_size, base_display_addr);
|
||||||
ImGuiStyle& style = ImGui::GetStyle();
|
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.
|
// 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.
|
// 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;
|
const float height_separator = style.ItemSpacing.y;
|
||||||
@@ -225,20 +253,21 @@ struct MemoryEditor
|
|||||||
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
|
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
|
||||||
if (OptShowDataPreview)
|
if (OptShowDataPreview)
|
||||||
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3;
|
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();
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, 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.
|
// 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);
|
const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
|
||||||
ImGuiListClipper clipper;
|
ImGuiListClipper clipper;
|
||||||
clipper.Begin(line_total_count, s.LineHeight);
|
clipper.Begin(line_total_count, s.LineHeight);
|
||||||
|
|
||||||
bool data_next = false;
|
bool data_next = false;
|
||||||
|
|
||||||
if (ReadOnly || DataEditingAddr >= mem_size)
|
if (DataEditingAddr >= mem_size)
|
||||||
DataEditingAddr = (size_t)-1;
|
DataEditingAddr = (size_t)-1;
|
||||||
if (DataPreviewAddr >= mem_size)
|
if (DataPreviewAddr >= mem_size)
|
||||||
DataPreviewAddr = (size_t)-1;
|
DataPreviewAddr = (size_t)-1;
|
||||||
@@ -249,16 +278,16 @@ struct MemoryEditor
|
|||||||
if (DataEditingAddr != (size_t)-1)
|
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)
|
// 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; }
|
if (ImGui::IsKeyPressed(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(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(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; }
|
else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw vertical separator
|
// Draw vertical separator
|
||||||
ImVec2 window_pos = ImGui::GetWindowPos();
|
ImVec2 window_pos = ImGui::GetWindowPos();
|
||||||
if (OptShowAscii)
|
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_text = ImGui::GetColorU32(ImGuiCol_Text);
|
||||||
const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_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 = OptUpperCaseHex ? "%02X" : "%02x";
|
||||||
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
|
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
|
||||||
|
|
||||||
|
MouseHovered = false;
|
||||||
|
MouseHoveredAddr = 0;
|
||||||
|
|
||||||
while (clipper.Step())
|
while (clipper.Step())
|
||||||
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
|
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);
|
ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
|
||||||
|
|
||||||
// Draw Hexadecimal
|
// Draw Hexadecimal
|
||||||
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
|
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)
|
if (OptMidColsCount > 0)
|
||||||
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
|
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
|
||||||
ImGui::SameLine(byte_pos_x);
|
ImGui::SameLine(byte_pos_x);
|
||||||
|
|
||||||
// Draw highlight
|
// Draw highlight or custom background color
|
||||||
bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
|
const bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
|
||||||
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr));
|
const bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, UserData));
|
||||||
bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
|
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)
|
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
|
||||||
{
|
{
|
||||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
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));
|
||||||
float highlight_width = s.GlyphWidth * 2;
|
bg_color = HighlightColor;
|
||||||
bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1)));
|
}
|
||||||
|
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))
|
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)
|
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)
|
if (DataEditingAddr == addr)
|
||||||
@@ -308,17 +352,21 @@ struct MemoryEditor
|
|||||||
if (DataEditingTakeFocus)
|
if (DataEditingTakeFocus)
|
||||||
{
|
{
|
||||||
ImGui::SetKeyboardFocusHere(0);
|
ImGui::SetKeyboardFocusHere(0);
|
||||||
sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr);
|
ImSnprintf(AddrInputBuf, 32, format_data, s.AddrDigitsCount, base_display_addr + addr);
|
||||||
sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[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.
|
// 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)
|
static int Callback(ImGuiInputTextCallbackData* data)
|
||||||
{
|
{
|
||||||
UserData* user_data = (UserData*)data->UserData;
|
InputTextUserData* user_data = (InputTextUserData*)data->UserData;
|
||||||
if (!data->HasSelection())
|
if (!data->HasSelection())
|
||||||
user_data->CursorPos = data->CursorPos;
|
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)
|
if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
|
||||||
{
|
{
|
||||||
// When not editing a byte, always refresh its InputText content pulled from underlying memory data
|
// 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
|
char CurrentBufOverwrite[3]; // Input
|
||||||
int CursorPos; // Output
|
int CursorPos; // Output
|
||||||
};
|
};
|
||||||
UserData user_data;
|
InputTextUserData input_text_user_data;
|
||||||
user_data.CursorPos = -1;
|
input_text_user_data.CursorPos = -1;
|
||||||
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
|
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;
|
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
|
||||||
#if IMGUI_VERSION_NUM >= 18104
|
if (ReadOnly)
|
||||||
flags |= ImGuiInputTextFlags_AlwaysOverwrite;
|
flags |= ImGuiInputTextFlags_ReadOnly;
|
||||||
#else
|
flags |= ImGuiInputTextFlags_AlwaysOverwrite; // was ImGuiInputTextFlags_AlwaysInsertMode
|
||||||
flags |= ImGuiInputTextFlags_AlwaysInsertMode;
|
|
||||||
#endif
|
|
||||||
ImGui::SetNextItemWidth(s.GlyphWidth * 2);
|
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;
|
data_write = data_next = true;
|
||||||
else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
|
else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
|
||||||
DataEditingAddr = data_editing_addr_next = (size_t)-1;
|
DataEditingAddr = data_editing_addr_next = (size_t)-1;
|
||||||
DataEditingTakeFocus = false;
|
DataEditingTakeFocus = false;
|
||||||
if (user_data.CursorPos >= 2)
|
if (input_text_user_data.CursorPos >= 2)
|
||||||
data_write = data_next = true;
|
data_write = data_next = true;
|
||||||
if (data_editing_addr_next != (size_t)-1)
|
if (data_editing_addr_next != (size_t)-1)
|
||||||
data_write = data_next = false;
|
data_write = data_next = false;
|
||||||
unsigned int data_input_value = 0;
|
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)
|
if (WriteFn)
|
||||||
WriteFn(mem_data, addr, (ImU8)data_input_value);
|
WriteFn(mem_data, addr, (ImU8)data_input_value, UserData);
|
||||||
else
|
else
|
||||||
mem_data[addr] = (ImU8)data_input_value;
|
mem_data[addr] = (ImU8)data_input_value;
|
||||||
}
|
}
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
{
|
||||||
|
MouseHovered = true;
|
||||||
|
MouseHoveredAddr = addr;
|
||||||
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on.
|
// 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)
|
if (OptShowHexII)
|
||||||
{
|
{
|
||||||
@@ -386,26 +437,40 @@ struct MemoryEditor
|
|||||||
else
|
else
|
||||||
ImGui::Text(format_byte_space, b);
|
ImGui::Text(format_byte_space, b);
|
||||||
}
|
}
|
||||||
if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0))
|
if (ImGui::IsItemHovered())
|
||||||
|
{
|
||||||
|
MouseHovered = true;
|
||||||
|
MouseHoveredAddr = addr;
|
||||||
|
if (ImGui::IsMouseClicked(0))
|
||||||
{
|
{
|
||||||
DataEditingTakeFocus = true;
|
DataEditingTakeFocus = true;
|
||||||
data_editing_addr_next = addr;
|
data_editing_addr_next = addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (OptShowAscii)
|
if (OptShowAscii)
|
||||||
{
|
{
|
||||||
// Draw ASCII values
|
// Draw ASCII values
|
||||||
ImGui::SameLine(s.PosAsciiStart);
|
ImGui::SameLine(s.OffsetAsciiMinX);
|
||||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
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);
|
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;
|
DataEditingTakeFocus = true;
|
||||||
}
|
}
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
{
|
||||||
|
MouseHovered = true;
|
||||||
|
MouseHoveredAddr = mouse_addr;
|
||||||
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
|
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_FrameBg));
|
||||||
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
|
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;
|
char display_c = (c < 32 || c >= 128) ? '.' : c;
|
||||||
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
|
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
|
||||||
pos.x += s.GlyphWidth;
|
pos.x += s.GlyphWidth;
|
||||||
@@ -422,10 +491,14 @@ struct MemoryEditor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar(2);
|
ImGui::PopStyleVar(2);
|
||||||
|
const float child_width = ImGui::GetWindowSize().x;
|
||||||
ImGui::EndChild();
|
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)
|
// 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::SetCursorPosX(s.WindowWidth);
|
||||||
|
ImGui::Dummy(ImVec2(0.0f, 0.0f));
|
||||||
|
ImGui::SetCursorScreenPos(backup_pos);
|
||||||
|
|
||||||
if (data_next && DataEditingAddr + 1 < mem_size)
|
if (data_next && DataEditingAddr + 1 < mem_size)
|
||||||
{
|
{
|
||||||
@@ -450,18 +523,28 @@ struct MemoryEditor
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
|
DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
|
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));
|
||||||
IM_UNUSED(mem_data);
|
if (OptShowOptions)
|
||||||
ImGuiStyle& style = ImGui::GetStyle();
|
if (ImGui::IsMouseHoveringRect(contents_pos_start, contents_pos_end))
|
||||||
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
|
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
|
||||||
|
ImGui::OpenPopup("OptionsPopup");
|
||||||
|
|
||||||
// Options menu
|
if (ImGui::BeginPopup("OptionsPopup"))
|
||||||
if (ImGui::Button("Options"))
|
|
||||||
ImGui::OpenPopup("context");
|
|
||||||
if (ImGui::BeginPopup("context"))
|
|
||||||
{
|
{
|
||||||
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
|
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; }
|
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();
|
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::SameLine();
|
||||||
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
|
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 (MouseHovered)
|
||||||
{
|
//{
|
||||||
if (GotoAddr < mem_size)
|
// ImGui::SameLine();
|
||||||
{
|
// ImGui::Text("Hovered: %p", MouseHoveredAddr);
|
||||||
ImGui::BeginChild("##scrolling");
|
//}
|
||||||
ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight());
|
|
||||||
ImGui::EndChild();
|
|
||||||
DataEditingAddr = DataPreviewAddr = GotoAddr;
|
|
||||||
DataEditingTakeFocus = true;
|
|
||||||
}
|
|
||||||
GotoAddr = (size_t)-1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr)
|
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::Text("Preview as:");
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
|
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))
|
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
|
||||||
{
|
{
|
||||||
for (int n = 0; n < ImGuiDataType_COUNT; n++)
|
for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++)
|
||||||
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n))
|
{
|
||||||
PreviewDataType = (ImGuiDataType)n;
|
ImGuiDataType data_type = supported_data_types[n];
|
||||||
|
if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
|
||||||
|
PreviewDataType = data_type;
|
||||||
|
}
|
||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
|
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] = "";
|
char buf[128] = "";
|
||||||
float x = s.GlyphWidth * 6.0f;
|
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");
|
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* DataTypeGetDesc(ImGuiDataType data_type) const
|
||||||
{
|
{
|
||||||
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" };
|
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];
|
return descs[data_type];
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t DataTypeGetSize(ImGuiDataType data_type) const
|
size_t DataTypeGetSize(ImGuiDataType data_type) const
|
||||||
{
|
{
|
||||||
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
|
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];
|
return sizes[data_type];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,18 +654,18 @@ struct MemoryEditor
|
|||||||
|
|
||||||
bool IsBigEndian() const
|
bool IsBigEndian() const
|
||||||
{
|
{
|
||||||
uint16_t x = 1;
|
ImU16 x = 1;
|
||||||
char c[2];
|
char c[2];
|
||||||
memcpy(c, &x, 2);
|
memcpy(c, &x, 2);
|
||||||
return c[0] != 0;
|
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)
|
if (is_little_endian)
|
||||||
{
|
{
|
||||||
uint8_t* dst = (uint8_t*)_dst;
|
ImU8* dst = (ImU8*)_dst;
|
||||||
uint8_t* src = (uint8_t*)_src + s - 1;
|
ImU8* src = (ImU8*)_src + s - 1;
|
||||||
for (int i = 0, n = (int)s; i < n; ++i)
|
for (int i = 0, n = (int)s; i < n; ++i)
|
||||||
memcpy(dst++, src--, 1);
|
memcpy(dst++, src--, 1);
|
||||||
return _dst;
|
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)
|
if (is_little_endian)
|
||||||
{
|
{
|
||||||
@@ -591,23 +684,23 @@ struct MemoryEditor
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uint8_t* dst = (uint8_t*)_dst;
|
ImU8* dst = (ImU8*)_dst;
|
||||||
uint8_t* src = (uint8_t*)_src + s - 1;
|
ImU8* src = (ImU8*)_src + s - 1;
|
||||||
for (int i = 0, n = (int)s; i < n; ++i)
|
for (int i = 0, n = (int)s; i < n; ++i)
|
||||||
memcpy(dst++, src--, 1);
|
memcpy(dst++, src--, 1);
|
||||||
return _dst;
|
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;
|
static void* (*fp)(void*, void*, size_t, int) = nullptr;
|
||||||
if (fp == NULL)
|
if (fp == nullptr)
|
||||||
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian;
|
fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
|
||||||
return fp(dst, src, size, PreviewEndianess);
|
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);
|
IM_ASSERT(width <= 64);
|
||||||
size_t out_n = 0;
|
size_t out_n = 0;
|
||||||
@@ -627,19 +720,19 @@ struct MemoryEditor
|
|||||||
// [Internal]
|
// [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
|
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 elem_size = DataTypeGetSize(data_type);
|
||||||
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
|
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
|
||||||
if (ReadFn)
|
if (ReadFn)
|
||||||
for (int i = 0, n = (int)size; i < n; ++i)
|
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
|
else
|
||||||
memcpy(buf, mem_data + addr, size);
|
memcpy(buf, mem_data + addr, size);
|
||||||
|
|
||||||
if (data_format == DataFormat_Bin)
|
if (data_format == DataFormat_Bin)
|
||||||
{
|
{
|
||||||
uint8_t binbuf[8];
|
ImU8 binbuf[8];
|
||||||
EndianessCopy(binbuf, buf, size);
|
EndiannessCopy(binbuf, buf, size);
|
||||||
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
|
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -649,84 +742,85 @@ struct MemoryEditor
|
|||||||
{
|
{
|
||||||
case ImGuiDataType_S8:
|
case ImGuiDataType_S8:
|
||||||
{
|
{
|
||||||
int8_t int8 = 0;
|
ImS8 data = 0;
|
||||||
EndianessCopy(&int8, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; }
|
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", int8 & 0xFF); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImGuiDataType_U8:
|
case ImGuiDataType_U8:
|
||||||
{
|
{
|
||||||
uint8_t uint8 = 0;
|
ImU8 data = 0;
|
||||||
EndianessCopy(&uint8, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; }
|
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", uint8 & 0XFF); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImGuiDataType_S16:
|
case ImGuiDataType_S16:
|
||||||
{
|
{
|
||||||
int16_t int16 = 0;
|
ImS16 data = 0;
|
||||||
EndianessCopy(&int16, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; }
|
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", int16 & 0xFFFF); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImGuiDataType_U16:
|
case ImGuiDataType_U16:
|
||||||
{
|
{
|
||||||
uint16_t uint16 = 0;
|
ImU16 data = 0;
|
||||||
EndianessCopy(&uint16, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; }
|
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", uint16 & 0xFFFF); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImGuiDataType_S32:
|
case ImGuiDataType_S32:
|
||||||
{
|
{
|
||||||
int32_t int32 = 0;
|
ImS32 data = 0;
|
||||||
EndianessCopy(&int32, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; }
|
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", int32); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImGuiDataType_U32:
|
case ImGuiDataType_U32:
|
||||||
{
|
{
|
||||||
uint32_t uint32 = 0;
|
ImU32 data = 0;
|
||||||
EndianessCopy(&uint32, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; }
|
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", uint32); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImGuiDataType_S64:
|
case ImGuiDataType_S64:
|
||||||
{
|
{
|
||||||
int64_t int64 = 0;
|
ImS64 data = 0;
|
||||||
EndianessCopy(&int64, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; }
|
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)int64); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImGuiDataType_U64:
|
case ImGuiDataType_U64:
|
||||||
{
|
{
|
||||||
uint64_t uint64 = 0;
|
ImU64 data = 0;
|
||||||
EndianessCopy(&uint64, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; }
|
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)uint64); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImGuiDataType_Float:
|
case ImGuiDataType_Float:
|
||||||
{
|
{
|
||||||
float float32 = 0.0f;
|
float data = 0.0f;
|
||||||
EndianessCopy(&float32, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; }
|
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", float32); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ImGuiDataType_Double:
|
case ImGuiDataType_Double:
|
||||||
{
|
{
|
||||||
double float64 = 0.0;
|
double data = 0.0;
|
||||||
EndianessCopy(&float64, buf, size);
|
EndiannessCopy(&data, buf, size);
|
||||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; }
|
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", float64); return; }
|
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
case ImGuiDataType_COUNT:
|
case ImGuiDataType_COUNT:
|
||||||
break;
|
break;
|
||||||
} // Switch
|
} // Switch
|
||||||
|
|||||||
+398
-199
File diff suppressed because it is too large
Load Diff
+1586
-900
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) {
|
if (node->y > min_y) {
|
||||||
// raise min_y higher.
|
// raise min_y higher.
|
||||||
// we've accounted for all waste up to min_y,
|
// 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);
|
waste_area += visited_width * (node->y - min_y);
|
||||||
min_y = node->y;
|
min_y = node->y;
|
||||||
// the first time through, visited_width might be reduced
|
// 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
|
// insert the new node into the right starting point, and
|
||||||
// let 'cur' point to the remaining nodes needing to be
|
// let 'cur' point to the remaining nodes needing to be
|
||||||
// stiched back in
|
// stitched back in
|
||||||
|
|
||||||
cur = *res.prev_link;
|
cur = *res.prev_link;
|
||||||
if (cur->x < res.x) {
|
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)
|
// - 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 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)
|
// - 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_*
|
// - 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
|
// stb_textedit.h - v1.14 - public domain - Sean Barrett
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
//
|
//
|
||||||
// VERSION HISTORY
|
// 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.14 (2021-07-11) page up/down, various fixes
|
||||||
// 1.13 (2019-02-07) fix bug in undo size management
|
// 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
|
// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
|
||||||
@@ -141,12 +143,14 @@
|
|||||||
// with previous char)
|
// with previous char)
|
||||||
// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
|
// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
|
||||||
// (return type is int, -1 means not valid to insert)
|
// (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_GETCHAR(obj,i) returns the i'th character of obj, 0-based
|
||||||
// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
|
// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
|
||||||
// as manually wordwrapping for end-of-line positioning
|
// as manually wordwrapping for end-of-line positioning
|
||||||
//
|
//
|
||||||
// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
|
// 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
|
// 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_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
|
// 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
|
// 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
|
// 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,
|
// 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
|
// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
|
||||||
// transformed into text and stb_textedit_text() is automatically called.
|
// transformed into text and stb_textedit_text() is automatically called.
|
||||||
//
|
//
|
||||||
// text: [DEAR IMGUI] added 2024-09
|
// text: (added 2025)
|
||||||
// call this to text inputs sent to the textfield.
|
// 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
|
// When rendering, you can read the cursor position and selection state from
|
||||||
@@ -400,6 +413,16 @@ typedef struct
|
|||||||
#define IMSTB_TEXTEDIT_memmove memmove
|
#define IMSTB_TEXTEDIT_memmove memmove
|
||||||
#endif
|
#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
|
// 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;
|
StbTexteditRow r;
|
||||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
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.x0 = r.x1 = 0;
|
||||||
r.ymin = r.ymax = 0;
|
r.ymin = r.ymax = 0;
|
||||||
r.num_chars = 0;
|
r.num_chars = 0;
|
||||||
|
*out_side_on_line = 0;
|
||||||
|
|
||||||
// search rows to find one that straddles 'y'
|
// search rows to find one that straddles 'y'
|
||||||
while (i < n) {
|
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
|
// below all text, return 'after' last character
|
||||||
if (i >= n)
|
if (i >= n)
|
||||||
|
{
|
||||||
|
*out_side_on_line = 1;
|
||||||
return n;
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
// check if it's before the beginning of the line
|
// check if it's before the beginning of the line
|
||||||
if (x < r.x0)
|
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) {
|
for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {
|
||||||
float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
|
float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
|
||||||
if (x < prev_x+w) {
|
if (x < prev_x+w) {
|
||||||
|
*out_side_on_line = (k == 0) ? 0 : 1;
|
||||||
if (x < prev_x+w/2)
|
if (x < prev_x+w/2)
|
||||||
return k+i;
|
return k+i;
|
||||||
else
|
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
|
// 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)
|
if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
|
||||||
return i+r.num_chars-1;
|
return i+r.num_chars-1;
|
||||||
else
|
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
|
// 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
|
// goes off the top or bottom of the text
|
||||||
|
int side_on_line;
|
||||||
if( state->single_line )
|
if( state->single_line )
|
||||||
{
|
{
|
||||||
StbTexteditRow r;
|
StbTexteditRow r;
|
||||||
@@ -478,16 +508,18 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st
|
|||||||
y = r.ymin;
|
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_start = state->cursor;
|
||||||
state->select_end = state->cursor;
|
state->select_end = state->cursor;
|
||||||
state->has_preferred_x = 0;
|
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
|
// 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)
|
static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
|
||||||
{
|
{
|
||||||
int p = 0;
|
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
|
// 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
|
// 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)
|
if (state->select_start == state->select_end)
|
||||||
state->select_start = state->cursor;
|
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;
|
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);
|
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
|
||||||
if (n < i + r.num_chars)
|
if (n < i + r.num_chars)
|
||||||
break;
|
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
|
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]
|
break; // [DEAR IMGUI]
|
||||||
prev_start = i;
|
prev_start = i;
|
||||||
@@ -648,15 +683,33 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [DEAR IMGUI]
|
// [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping.
|
||||||
// Functions must be implemented for UTF8 support
|
#ifndef STB_TEXTEDIT_MOVELINESTART
|
||||||
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
|
static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
|
||||||
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
|
{
|
||||||
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
|
if (state->single_line)
|
||||||
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1)
|
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
|
#endif
|
||||||
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
|
#ifndef STB_TEXTEDIT_MOVELINEEND
|
||||||
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1)
|
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
|
#endif
|
||||||
|
|
||||||
#ifdef STB_TEXTEDIT_IS_SPACE
|
#ifdef STB_TEXTEDIT_IS_SPACE
|
||||||
@@ -668,9 +721,9 @@ static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
|
|||||||
#ifndef STB_TEXTEDIT_MOVEWORDLEFT
|
#ifndef STB_TEXTEDIT_MOVEWORDLEFT
|
||||||
static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
|
static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
|
||||||
{
|
{
|
||||||
--c; // always move at least one character
|
c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character
|
||||||
while( c >= 0 && !is_word_boundary( str, c ) )
|
while (c >= 0 && !is_word_boundary(str, c))
|
||||||
--c;
|
c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c);
|
||||||
|
|
||||||
if( c < 0 )
|
if( c < 0 )
|
||||||
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 )
|
static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )
|
||||||
{
|
{
|
||||||
const int len = STB_TEXTEDIT_STRINGLEN(str);
|
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 ) )
|
while( c < len && !is_word_boundary( str, c ) )
|
||||||
++c;
|
c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c);
|
||||||
|
|
||||||
if( c > len )
|
if( c > len )
|
||||||
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_clamp(str, state);
|
||||||
stb_textedit_delete_selection(str,state);
|
stb_textedit_delete_selection(str,state);
|
||||||
// try to insert the characters
|
// 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);
|
stb_text_makeundo_insert(state, state->cursor, len);
|
||||||
state->cursor += len;
|
state->cursor += len;
|
||||||
state->has_preferred_x = 0;
|
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
|
#define STB_TEXTEDIT_KEYTYPE int
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// API key: process text input
|
||||||
// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
|
// [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)
|
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)) {
|
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_text_makeundo_replace(str, state, state->cursor, 1, 1);
|
||||||
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 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->cursor += text_len;
|
||||||
state->has_preferred_x = 0;
|
state->has_preferred_x = 0;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
stb_textedit_delete_selection(str, state); // implicitly clamps
|
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);
|
stb_text_makeundo_insert(state, state->cursor, text_len);
|
||||||
state->cursor += text_len;
|
state->cursor += text_len;
|
||||||
state->has_preferred_x = 0;
|
state->has_preferred_x = 0;
|
||||||
@@ -771,6 +827,7 @@ retry:
|
|||||||
switch (key) {
|
switch (key) {
|
||||||
default: {
|
default: {
|
||||||
#ifdef STB_TEXTEDIT_KEYTOTEXT
|
#ifdef STB_TEXTEDIT_KEYTOTEXT
|
||||||
|
// This is not suitable for UTF-8 support.
|
||||||
int c = STB_TEXTEDIT_KEYTOTEXT(key);
|
int c = STB_TEXTEDIT_KEYTOTEXT(key);
|
||||||
if (c > 0) {
|
if (c > 0) {
|
||||||
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
|
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
|
||||||
@@ -911,15 +968,16 @@ retry:
|
|||||||
|
|
||||||
// [DEAR IMGUI]
|
// [DEAR IMGUI]
|
||||||
// going down while being on the last line shouldn't bring us to that line end
|
// 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)
|
//if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
|
||||||
break;
|
// break;
|
||||||
|
|
||||||
// now find character position down a row
|
// now find character position down a row
|
||||||
state->cursor = start;
|
state->cursor = start;
|
||||||
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
|
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
|
||||||
x = row.x0;
|
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);
|
float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
|
||||||
|
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
|
||||||
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
|
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
|
||||||
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
|
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
|
||||||
break;
|
break;
|
||||||
@@ -927,10 +985,13 @@ retry:
|
|||||||
x += dx;
|
x += dx;
|
||||||
if (x > goal_x)
|
if (x > goal_x)
|
||||||
break;
|
break;
|
||||||
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
|
i += next - state->cursor;
|
||||||
|
state->cursor = next;
|
||||||
}
|
}
|
||||||
stb_textedit_clamp(str, state);
|
stb_textedit_clamp(str, state);
|
||||||
|
|
||||||
|
if (state->cursor == find.first_char + find.length)
|
||||||
|
str->LastMoveDirectionLR = ImGuiDir_Left;
|
||||||
state->has_preferred_x = 1;
|
state->has_preferred_x = 1;
|
||||||
state->preferred_x = goal_x;
|
state->preferred_x = goal_x;
|
||||||
|
|
||||||
@@ -980,8 +1041,9 @@ retry:
|
|||||||
state->cursor = find.prev_first;
|
state->cursor = find.prev_first;
|
||||||
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
|
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
|
||||||
x = row.x0;
|
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);
|
float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
|
||||||
|
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
|
||||||
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
|
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
|
||||||
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
|
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
|
||||||
break;
|
break;
|
||||||
@@ -989,10 +1051,15 @@ retry:
|
|||||||
x += dx;
|
x += dx;
|
||||||
if (x > goal_x)
|
if (x > goal_x)
|
||||||
break;
|
break;
|
||||||
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
|
i += next - state->cursor;
|
||||||
|
state->cursor = next;
|
||||||
}
|
}
|
||||||
stb_textedit_clamp(str, state);
|
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->has_preferred_x = 1;
|
||||||
state->preferred_x = goal_x;
|
state->preferred_x = goal_x;
|
||||||
|
|
||||||
@@ -1002,10 +1069,15 @@ retry:
|
|||||||
// go to previous line
|
// go to previous line
|
||||||
// (we need to scan previous line the hard way. maybe we could expose this as a new API function?)
|
// (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;
|
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)
|
while (prev_scan > 0)
|
||||||
--prev_scan;
|
{
|
||||||
|
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.first_char = find.prev_first;
|
||||||
find.prev_first = prev_scan;
|
find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1079,10 +1151,7 @@ retry:
|
|||||||
case STB_TEXTEDIT_K_LINESTART:
|
case STB_TEXTEDIT_K_LINESTART:
|
||||||
stb_textedit_clamp(str, state);
|
stb_textedit_clamp(str, state);
|
||||||
stb_textedit_move_to_first(state);
|
stb_textedit_move_to_first(state);
|
||||||
if (state->single_line)
|
state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
|
||||||
state->cursor = 0;
|
|
||||||
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
|
|
||||||
--state->cursor;
|
|
||||||
state->has_preferred_x = 0;
|
state->has_preferred_x = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -1090,13 +1159,9 @@ retry:
|
|||||||
case STB_TEXTEDIT_K_LINEEND2:
|
case STB_TEXTEDIT_K_LINEEND2:
|
||||||
#endif
|
#endif
|
||||||
case STB_TEXTEDIT_K_LINEEND: {
|
case STB_TEXTEDIT_K_LINEEND: {
|
||||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
|
||||||
stb_textedit_clamp(str, state);
|
stb_textedit_clamp(str, state);
|
||||||
stb_textedit_move_to_first(state);
|
stb_textedit_move_to_last(str, state);
|
||||||
if (state->single_line)
|
state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
|
||||||
state->cursor = n;
|
|
||||||
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
|
|
||||||
++state->cursor;
|
|
||||||
state->has_preferred_x = 0;
|
state->has_preferred_x = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1107,10 +1172,7 @@ retry:
|
|||||||
case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
|
case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
|
||||||
stb_textedit_clamp(str, state);
|
stb_textedit_clamp(str, state);
|
||||||
stb_textedit_prep_selection_at_cursor(state);
|
stb_textedit_prep_selection_at_cursor(state);
|
||||||
if (state->single_line)
|
state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
|
||||||
state->cursor = 0;
|
|
||||||
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
|
|
||||||
--state->cursor;
|
|
||||||
state->select_end = state->cursor;
|
state->select_end = state->cursor;
|
||||||
state->has_preferred_x = 0;
|
state->has_preferred_x = 0;
|
||||||
break;
|
break;
|
||||||
@@ -1119,13 +1181,9 @@ retry:
|
|||||||
case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
|
case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
|
||||||
#endif
|
#endif
|
||||||
case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
|
case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
|
||||||
int n = STB_TEXTEDIT_STRINGLEN(str);
|
|
||||||
stb_textedit_clamp(str, state);
|
stb_textedit_clamp(str, state);
|
||||||
stb_textedit_prep_selection_at_cursor(state);
|
stb_textedit_prep_selection_at_cursor(state);
|
||||||
if (state->single_line)
|
state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
|
||||||
state->cursor = n;
|
|
||||||
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
|
|
||||||
++state->cursor;
|
|
||||||
state->select_end = state->cursor;
|
state->select_end = state->cursor;
|
||||||
state->has_preferred_x = 0;
|
state->has_preferred_x = 0;
|
||||||
break;
|
break;
|
||||||
@@ -1300,7 +1358,7 @@ static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
|
|||||||
// check type of recorded action:
|
// check type of recorded action:
|
||||||
if (u.insert_length) {
|
if (u.insert_length) {
|
||||||
// easy case: was a deletion, so we need to insert n characters
|
// 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;
|
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) {
|
if (r.insert_length) {
|
||||||
// easy case: need to insert n characters
|
// 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;
|
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
|
// 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);
|
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
|
// 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);
|
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[0] = (float)x2;
|
||||||
q2[1] = (float)y2;
|
q2[1] = (float)y2;
|
||||||
if (equal(q0,q1) || equal(q1,q2)) {
|
if (equal(q0,q1) || equal(q1,q2)) {
|
||||||
x0 = (int)verts[i-1].x;
|
x0 = (int)verts[i-1].x; //-V1048
|
||||||
y0 = (int)verts[i-1].y;
|
y0 = (int)verts[i-1].y; //-V1048
|
||||||
x1 = (int)verts[i ].x;
|
x1 = (int)verts[i ].x;
|
||||||
y1 = (int)verts[i ].y;
|
y1 = (int)verts[i ].y;
|
||||||
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
|
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
|
#define ROBIN_HOOD_VERSION_PATCH 2 // for backwards-compatible bug fixes
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|||||||
@@ -38,11 +38,12 @@ std::vector<Analog> &games::bc::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
|
analogs = GameAPI::Analogs::getAnalogs("Busou Shinki: Armored Princess Battle Conductor");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Stick X",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Stick Y"
|
{ "Stick X", AnalogType::LinearCentered },
|
||||||
);
|
{ "Stick Y", AnalogType::LinearCentered }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ namespace games::ccj {
|
|||||||
libutils::load_library("libaio-iob2_video.dll");
|
libutils::load_library("libaio-iob2_video.dll");
|
||||||
libutils::load_library("win10actlog.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),
|
detour::trampoline_try("execexe.dll", MAKEINTRESOURCE(11),
|
||||||
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
|
(void*)execexe_CreateFileW_hook,(void**)&execexe_CreateFileW_orig);
|
||||||
|
|
||||||
|
|||||||
@@ -45,13 +45,14 @@ std::vector<Analog> &games::ccj::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
|
analogs = GameAPI::Analogs::getAnalogs("Chase Chase Jokers");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Joystick X",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Joystick Y",
|
{ "Joystick X", AnalogType::LinearCentered },
|
||||||
"Trackball DX",
|
{ "Joystick Y", AnalogType::LinearCentered },
|
||||||
"Trackball DY"
|
{ "Trackball DX", AnalogType::LinearCentered },
|
||||||
);
|
{ "Trackball DY", AnalogType::LinearCentered }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
+164
-101
@@ -6,6 +6,7 @@
|
|||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "games/io.h"
|
#include "games/io.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
|
||||||
@@ -32,13 +33,14 @@ namespace games::ccj {
|
|||||||
static UINT WINAPI GetRawInputDeviceList_hook(PRAWINPUTDEVICELIST pRawInputDeviceList, PUINT puiNumDevices,
|
static UINT WINAPI GetRawInputDeviceList_hook(PRAWINPUTDEVICELIST pRawInputDeviceList, PUINT puiNumDevices,
|
||||||
UINT cbSize) {
|
UINT cbSize) {
|
||||||
auto result = GetRawInputDeviceList_orig(pRawInputDeviceList, puiNumDevices, cbSize);
|
auto result = GetRawInputDeviceList_orig(pRawInputDeviceList, puiNumDevices, cbSize);
|
||||||
if (result == 0xFFFFFFFF)
|
if (result == 0xFFFFFFFF) {
|
||||||
return result;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
if (pRawInputDeviceList == NULL) {
|
if (pRawInputDeviceList == NULL) {
|
||||||
(*puiNumDevices)++;
|
(*puiNumDevices)++;
|
||||||
} else if (result < *puiNumDevices) {
|
} else if (result < *puiNumDevices) {
|
||||||
pRawInputDeviceList[result] = {fakeHandle, 0};
|
pRawInputDeviceList[result] = { fakeHandle, 0 };
|
||||||
result++;
|
result++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,8 +48,9 @@ namespace games::ccj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static UINT WINAPI GetRawInputDeviceInfoW_hook(HANDLE hDevice, UINT uiCommand, LPVOID pData, PUINT pcbSize) {
|
static UINT WINAPI GetRawInputDeviceInfoW_hook(HANDLE hDevice, UINT uiCommand, LPVOID pData, PUINT pcbSize) {
|
||||||
if (hDevice != fakeHandle || uiCommand != RIDI_DEVICENAME)
|
if (hDevice != fakeHandle || uiCommand != RIDI_DEVICENAME) {
|
||||||
return GetRawInputDeviceInfoW_orig(hDevice, uiCommand, pData, pcbSize);
|
return GetRawInputDeviceInfoW_orig(hDevice, uiCommand, pData, pcbSize);
|
||||||
|
}
|
||||||
|
|
||||||
const auto requiredLen = (wcslen(fakeDeviceName) + 1) * sizeof(wchar_t);
|
const auto requiredLen = (wcslen(fakeDeviceName) + 1) * sizeof(wchar_t);
|
||||||
|
|
||||||
@@ -67,23 +70,157 @@ namespace games::ccj {
|
|||||||
|
|
||||||
static LONG_PTR WINAPI SetWindowLongPtrW_hook(HWND _hWnd, int nIndex, LONG_PTR dwNewLong) {
|
static LONG_PTR WINAPI SetWindowLongPtrW_hook(HWND _hWnd, int nIndex, LONG_PTR dwNewLong) {
|
||||||
wchar_t buffer[256];
|
wchar_t buffer[256];
|
||||||
if (nIndex != GWLP_WNDPROC || GetWindowTextW(_hWnd, buffer, 256) == 0 || !wcswcs(buffer, windowName))
|
if (nIndex != GWLP_WNDPROC || GetWindowTextW(_hWnd, buffer, 256) == 0 || !wcswcs(buffer, windowName)) {
|
||||||
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
|
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
|
||||||
|
}
|
||||||
|
|
||||||
hWnd = _hWnd;
|
hWnd = _hWnd;
|
||||||
wndProc = (WNDPROC)dwNewLong;
|
wndProc = (WNDPROC)dwNewLong;
|
||||||
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
|
return SetWindowLongPtrW_orig(_hWnd, nIndex, dwNewLong);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compute the cursor wrap region in client coordinates. The cursor is confined to the
|
||||||
|
// monitor, so if the window's client area extends past a screen edge (window larger than /
|
||||||
|
// offset off the monitor) the cursor can never reach that far client edge. Clamp the region
|
||||||
|
// to the on-screen portion of the client area so the right/bottom edges wrap as reliably as
|
||||||
|
// the left/top edges.
|
||||||
|
static RECT trackball_wrap_bounds(HWND wnd, const RECT &client) {
|
||||||
|
RECT bounds = client;
|
||||||
|
|
||||||
|
POINT origin = { 0, 0 };
|
||||||
|
ClientToScreen(wnd, &origin);
|
||||||
|
RECT clientScreen = {
|
||||||
|
origin.x,
|
||||||
|
origin.y,
|
||||||
|
origin.x + client.right,
|
||||||
|
origin.y + client.bottom
|
||||||
|
};
|
||||||
|
|
||||||
|
MONITORINFO mi = {};
|
||||||
|
mi.cbSize = sizeof(mi);
|
||||||
|
RECT usable;
|
||||||
|
if (GetMonitorInfo(MonitorFromWindow(wnd, MONITOR_DEFAULTTONEAREST), &mi)
|
||||||
|
&& IntersectRect(&usable, &clientScreen, &mi.rcMonitor)) {
|
||||||
|
bounds.left = usable.left - origin.x;
|
||||||
|
bounds.top = usable.top - origin.y;
|
||||||
|
bounds.right = usable.right - origin.x;
|
||||||
|
bounds.bottom = usable.bottom - origin.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// drive the trackball from the physical mouse cursor, wrapping it at the window edges so it
|
||||||
|
// can spin indefinitely. gated by the secondary-mouse button (hold or debounced toggle).
|
||||||
|
static void trackball_mouse_input(RAWMOUSE &rawMouse) {
|
||||||
|
static bool active = false;
|
||||||
|
static bool lastState = false;
|
||||||
|
static std::chrono::steady_clock::time_point lastModified = std::chrono::steady_clock::now();
|
||||||
|
static const std::chrono::milliseconds debounceDuration(100);
|
||||||
|
|
||||||
|
const auto currentTime = std::chrono::steady_clock::now();
|
||||||
|
const bool pressed = get_async_secondary_mouse();
|
||||||
|
const bool focused = GetForegroundWindow() == hWnd;
|
||||||
|
|
||||||
|
if (focused && MOUSE_TRACKBALL_USE_TOGGLE && pressed && (currentTime - lastModified > debounceDuration)) {
|
||||||
|
active = !active;
|
||||||
|
lastModified = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool engaged = focused
|
||||||
|
&& ((MOUSE_TRACKBALL_USE_TOGGLE && active) || (!MOUSE_TRACKBALL_USE_TOGGLE && pressed));
|
||||||
|
if (!engaged) {
|
||||||
|
if (lastState && !active) {
|
||||||
|
lastState = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
POINT cursor;
|
||||||
|
RECT client;
|
||||||
|
GetClientRect(hWnd, &client);
|
||||||
|
GetCursorPos(&cursor);
|
||||||
|
ScreenToClient(hWnd, &cursor);
|
||||||
|
|
||||||
|
const RECT bounds = trackball_wrap_bounds(hWnd, client);
|
||||||
|
|
||||||
|
static int lastX = cursor.x;
|
||||||
|
static int lastY = cursor.y;
|
||||||
|
if (!lastState) {
|
||||||
|
lastX = cursor.x;
|
||||||
|
lastY = cursor.y;
|
||||||
|
lastState = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
|
||||||
|
rawMouse.lLastX = (int)((float)(cursor.x - lastX) * (float)TRACKBALL_SENSITIVITY / 20.0f);
|
||||||
|
rawMouse.lLastY = (int)((float)(lastY - cursor.y) * (float)TRACKBALL_SENSITIVITY / 20.0f);
|
||||||
|
|
||||||
|
// wrap the cursor to the opposite edge once it reaches a boundary, so the trackball
|
||||||
|
// can keep spinning past the screen edge.
|
||||||
|
bool updateCursor = false;
|
||||||
|
auto wrap = [&updateCursor](LONG value, LONG lo, LONG hi) -> LONG {
|
||||||
|
if (value <= lo) {
|
||||||
|
updateCursor = true;
|
||||||
|
return hi - 5;
|
||||||
|
}
|
||||||
|
if (value >= hi - 1) {
|
||||||
|
updateCursor = true;
|
||||||
|
return lo + 5;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
cursor.x = wrap(cursor.x, bounds.left, bounds.right);
|
||||||
|
cursor.y = wrap(cursor.y, bounds.top, bounds.bottom);
|
||||||
|
|
||||||
|
lastX = cursor.x;
|
||||||
|
lastY = cursor.y;
|
||||||
|
|
||||||
|
if (updateCursor) {
|
||||||
|
ClientToScreen(hWnd, &cursor);
|
||||||
|
SetCursorPos(cursor.x, cursor.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// drive the trackball from the configured analog axes / direction buttons.
|
||||||
|
static void trackball_mapped_input(RAWMOUSE &rawMouse) {
|
||||||
|
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
|
||||||
|
|
||||||
|
auto &analogs = get_analogs();
|
||||||
|
if (analogs[Analogs::Trackball_DX].isSet() || analogs[Analogs::Trackball_DY].isSet()) {
|
||||||
|
float x = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DX]) * 2.0f - 1.0f;
|
||||||
|
float y = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DY]) * 2.0f - 1.0f;
|
||||||
|
rawMouse.lLastX = (long) (x * (float) TRACKBALL_SENSITIVITY);
|
||||||
|
rawMouse.lLastY = (long) (-y * (float) TRACKBALL_SENSITIVITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &buttons = get_buttons();
|
||||||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Up])) {
|
||||||
|
rawMouse.lLastY = TRACKBALL_SENSITIVITY;
|
||||||
|
}
|
||||||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Down])) {
|
||||||
|
rawMouse.lLastY = -TRACKBALL_SENSITIVITY;
|
||||||
|
}
|
||||||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Left])) {
|
||||||
|
rawMouse.lLastX = -TRACKBALL_SENSITIVITY;
|
||||||
|
}
|
||||||
|
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Right])) {
|
||||||
|
rawMouse.lLastX = TRACKBALL_SENSITIVITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static UINT WINAPI GetRawInputData_hook(HRAWINPUT hRawInput, UINT uiCommand, LPVOID pData, PUINT pcbSize, UINT cbSizeHeader) {
|
static UINT WINAPI GetRawInputData_hook(HRAWINPUT hRawInput, UINT uiCommand, LPVOID pData, PUINT pcbSize, UINT cbSizeHeader) {
|
||||||
if (hRawInput != fakeHandle)
|
if (hRawInput != fakeHandle) {
|
||||||
return GetRawInputData_orig(hRawInput, uiCommand, pData, pcbSize, cbSizeHeader);
|
return GetRawInputData_orig(hRawInput, uiCommand, pData, pcbSize, cbSizeHeader);
|
||||||
|
}
|
||||||
|
|
||||||
if (pData == NULL) {
|
if (pData == NULL) {
|
||||||
if (uiCommand == RID_HEADER)
|
if (uiCommand == RID_HEADER) {
|
||||||
*pcbSize = sizeof(RAWINPUTHEADER);
|
*pcbSize = sizeof(RAWINPUTHEADER);
|
||||||
else
|
} else {
|
||||||
*pcbSize = sizeof(RAWINPUT);
|
*pcbSize = sizeof(RAWINPUT);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,92 +243,9 @@ namespace games::ccj {
|
|||||||
RAWMOUSE rawMouse {};
|
RAWMOUSE rawMouse {};
|
||||||
|
|
||||||
if (MOUSE_TRACKBALL) {
|
if (MOUSE_TRACKBALL) {
|
||||||
static bool active = false;
|
trackball_mouse_input(rawMouse);
|
||||||
static bool lastState = false;
|
|
||||||
|
|
||||||
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 focused = GetForegroundWindow() == hWnd;
|
|
||||||
|
|
||||||
if (focused && MOUSE_TRACKBALL_USE_TOGGLE && pressed && (currentTime - lastModified > debounceDuration)) {
|
|
||||||
active = !active;
|
|
||||||
lastModified = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (focused && ((MOUSE_TRACKBALL_USE_TOGGLE && active) || (!MOUSE_TRACKBALL_USE_TOGGLE && pressed))) {
|
|
||||||
POINT cursor;
|
|
||||||
RECT client;
|
|
||||||
|
|
||||||
GetClientRect(hWnd, &client);
|
|
||||||
int sx = client.right - client.left;
|
|
||||||
int sy = client.bottom - client.top;
|
|
||||||
|
|
||||||
GetCursorPos(&cursor);
|
|
||||||
ScreenToClient(hWnd, &cursor);
|
|
||||||
|
|
||||||
static int lastX = cursor.x;
|
|
||||||
static int lastY = cursor.y;
|
|
||||||
|
|
||||||
if (!lastState) {
|
|
||||||
lastX = cursor.x;
|
|
||||||
lastY = cursor.y;
|
|
||||||
lastState = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
|
|
||||||
rawMouse.lLastX = (int)((float)(cursor.x - lastX) * (float)TRACKBALL_SENSITIVITY / 20.0f);
|
|
||||||
rawMouse.lLastY = (int)((float)(lastY - cursor.y) * (float)TRACKBALL_SENSITIVITY / 20.0f);
|
|
||||||
|
|
||||||
bool updateCursor = false;
|
|
||||||
|
|
||||||
if (cursor.x <= 0) {
|
|
||||||
updateCursor = true;
|
|
||||||
cursor.x = sx - 5;
|
|
||||||
} else if (cursor.x >= sx - 1) {
|
|
||||||
updateCursor = true;
|
|
||||||
cursor.x = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursor.y <= 0) {
|
|
||||||
updateCursor = true;
|
|
||||||
cursor.y = sy - 5;
|
|
||||||
} else if (cursor.y >= sy - 1) {
|
|
||||||
updateCursor = true;
|
|
||||||
cursor.y = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastX = cursor.x;
|
|
||||||
lastY = cursor.y;
|
|
||||||
|
|
||||||
if (updateCursor) {
|
|
||||||
ClientToScreen(hWnd, &cursor);
|
|
||||||
SetCursorPos(cursor.x, cursor.y);
|
|
||||||
}
|
|
||||||
} else if (lastState && !active) {
|
|
||||||
lastState = false;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
rawMouse.usFlags = MOUSE_MOVE_RELATIVE;
|
trackball_mapped_input(rawMouse);
|
||||||
|
|
||||||
auto &analogs = get_analogs();
|
|
||||||
if (analogs[Analogs::Trackball_DX].isSet() || analogs[Analogs::Trackball_DY].isSet()) {
|
|
||||||
float x = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DX]) * 2.0f - 1.0f;
|
|
||||||
float y = GameAPI::Analogs::getState(RI_MGR, analogs[Analogs::Trackball_DY]) * 2.0f - 1.0f;
|
|
||||||
rawMouse.lLastX = (long) (x * (float) TRACKBALL_SENSITIVITY);
|
|
||||||
rawMouse.lLastY = (long) (-y * (float) TRACKBALL_SENSITIVITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &buttons = get_buttons();
|
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Up]))
|
|
||||||
rawMouse.lLastY = TRACKBALL_SENSITIVITY;
|
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Down]))
|
|
||||||
rawMouse.lLastY = -TRACKBALL_SENSITIVITY;
|
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Left]))
|
|
||||||
rawMouse.lLastX = -TRACKBALL_SENSITIVITY;
|
|
||||||
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::Trackball_Right]))
|
|
||||||
rawMouse.lLastX = TRACKBALL_SENSITIVITY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*((RAWINPUT*)pData) = { header, { rawMouse } };
|
*((RAWINPUT*)pData) = { header, { rawMouse } };
|
||||||
@@ -202,21 +256,29 @@ namespace games::ccj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static BOOL WINAPI RegisterRawInputDevices_hook(PCRAWINPUTDEVICE pRawInputDevices, UINT uiNumDevices, UINT cbSize) {
|
static BOOL WINAPI RegisterRawInputDevices_hook(PCRAWINPUTDEVICE pRawInputDevices, UINT uiNumDevices, UINT cbSize) {
|
||||||
if (uiNumDevices == 2 && pRawInputDevices[1].usUsage == HID_USAGE_GENERIC_GAMEPAD)
|
|
||||||
uiNumDevices = 1;
|
// if the caller is spice itself, then pass through.
|
||||||
|
if (pRawInputDevices &&
|
||||||
|
(uiNumDevices > 0) &&
|
||||||
|
(pRawInputDevices[0].hwndTarget == RI_MGR->input_hwnd)) {
|
||||||
|
|
||||||
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
|
return RegisterRawInputDevices_orig(pRawInputDevices, uiNumDevices, cbSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// otherwise, it must be the game; prevent the game from registering for raw input
|
||||||
|
// and hijacking WM_INPUT messages; we need that for rawinput to work.
|
||||||
|
// even if we drop this, trackball emulation and mouse-as-touch input still work
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void trackball_hook_init() {
|
void trackball_hook_init() {
|
||||||
// avoid double init
|
// avoid double init
|
||||||
static bool initialized = false;
|
static bool initialized = false;
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
initialized = true;
|
|
||||||
}
|
}
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
// announce
|
// announce
|
||||||
log_info("trackball", "init");
|
log_info("trackball", "init");
|
||||||
@@ -236,30 +298,31 @@ namespace games::ccj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void trackball_thread_start() {
|
void trackball_thread_start() {
|
||||||
using namespace std::chrono_literals;
|
|
||||||
|
|
||||||
tbThreadRunning = true;
|
tbThreadRunning = true;
|
||||||
|
|
||||||
log_info("trackball", "thread start, use mouse: {}, toggle: {}", MOUSE_TRACKBALL, MOUSE_TRACKBALL_USE_TOGGLE);
|
log_info("trackball", "thread start, use mouse: {}, toggle: {}", MOUSE_TRACKBALL, MOUSE_TRACKBALL_USE_TOGGLE);
|
||||||
|
|
||||||
tbThread = new std::thread([&] {
|
tbThread = new std::thread([&] {
|
||||||
|
timeutils::PreciseSleepTimer timer;
|
||||||
while (tbThreadRunning) {
|
while (tbThreadRunning) {
|
||||||
if (hWnd && wndProc) {
|
if (hWnd && wndProc) {
|
||||||
wndProc(hWnd, WM_INPUT, RIM_INPUT, (LPARAM)fakeHandle);
|
wndProc(hWnd, WM_INPUT, RIM_INPUT, (LPARAM)fakeHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tbThreadRunning)
|
if (!tbThreadRunning) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(10ms);
|
timer.sleep(10);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void trackball_thread_stop() {
|
void trackball_thread_stop() {
|
||||||
tbThreadRunning = false;
|
tbThreadRunning = false;
|
||||||
if (tbThread)
|
if (tbThread) {
|
||||||
tbThread->join();
|
tbThread->join();
|
||||||
|
}
|
||||||
|
|
||||||
log_info("trackball", "thread stop");
|
log_info("trackball", "thread stop");
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "p3io/usbmem.h"
|
#include "p3io/usbmem.h"
|
||||||
|
|
||||||
#include "p4io/p4io.h"
|
#include "p4io/p4io.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
using namespace acioemu;
|
using namespace acioemu;
|
||||||
|
|
||||||
@@ -53,9 +54,9 @@ namespace games::ddr {
|
|||||||
return SendMessage_real(hWnd, Msg, wParam, lParam);
|
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) {
|
for (auto c: str) {
|
||||||
if (static_cast<unsigned char>(c) > 127) {
|
if (c > 127) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,24 +81,24 @@ namespace games::ddr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fileutils::dir_exists(dir)) {
|
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 {
|
} else {
|
||||||
log_info("ddr", "codecs directory not found: {}", dir.string());
|
log_info("ddr", "codecs directory not found: {}", dir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &file : std::filesystem::directory_iterator(dir)) {
|
for (const auto &file : std::filesystem::directory_iterator(dir)) {
|
||||||
const auto &filename = file.path().filename();
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("ddr", "found DLL: {}, size: {} bytes", filename.string(), file.file_size());
|
log_info("ddr", "found DLL: {}, size: {} bytes", filename, file.file_size());
|
||||||
if (filename == "k-clvsd.dll" || filename.string().find("xactengine") == 0) {
|
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::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;
|
int result = 0;
|
||||||
std::thread t([wcmd, &result]() {
|
std::thread t([wcmd, &result]() {
|
||||||
@@ -111,12 +112,12 @@ namespace games::ddr {
|
|||||||
log_warning("ddr", "`{}` failed, returned {}", cmd, result);
|
log_warning("ddr", "`{}` failed, returned {}", cmd, result);
|
||||||
deferredlogs::defer_error_messages({
|
deferredlogs::defer_error_messages({
|
||||||
"DDR codec registration failure",
|
"DDR codec registration failure",
|
||||||
fmt::format(" {} failed to register with error {}", filename.string(), result)});
|
fmt::format(" {} failed to register with error {}", filename, result)});
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::once_flag printed;
|
static std::once_flag printed;
|
||||||
std::call_once(printed, [file]() {
|
std::call_once(printed, [file]() {
|
||||||
if (!contains_only_ascii(file.path().string())) {
|
if (!contains_only_ascii(file.path().wstring())) {
|
||||||
log_warning(
|
log_warning(
|
||||||
"ddr",
|
"ddr",
|
||||||
"BAD PATH ERROR\n\n"
|
"BAD PATH ERROR\n\n"
|
||||||
@@ -290,4 +291,50 @@ namespace games::ddr {
|
|||||||
// dispose device hook
|
// dispose device hook
|
||||||
devicehook_dispose();
|
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:
|
private:
|
||||||
void register_codecs();
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,50 @@ std::vector<Button> &games::ddr::get_buttons() {
|
|||||||
return analogs;
|
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() {
|
std::vector<Light> &games::ddr::get_lights() {
|
||||||
static std::vector<Light> lights;
|
static std::vector<Light> lights;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// all lights in correct order
|
||||||
namespace Lights {
|
namespace Lights {
|
||||||
enum {
|
enum {
|
||||||
@@ -177,5 +187,8 @@ namespace games::ddr {
|
|||||||
|
|
||||||
// getters
|
// getters
|
||||||
std::vector<Button> &get_buttons();
|
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();
|
std::vector<Light> &get_lights();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "cfg/api.h"
|
#include "cfg/api.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
#include "../ddr.h"
|
#include "../ddr.h"
|
||||||
@@ -459,7 +460,8 @@ int games::ddr::DDRP3IOHandle::device_io(
|
|||||||
if (nOutBufferSize >= 4) {
|
if (nOutBufferSize >= 4) {
|
||||||
|
|
||||||
// cool down
|
// cool down
|
||||||
Sleep(1);
|
static thread_local timeutils::PreciseSleepTimer timer;
|
||||||
|
timer.sleep(1);
|
||||||
|
|
||||||
// get controls as single variable (4 bytes)
|
// get controls as single variable (4 bytes)
|
||||||
auto &controls = *(uint32_t*) lpOutBuffer;
|
auto &controls = *(uint32_t*) lpOutBuffer;
|
||||||
@@ -498,32 +500,80 @@ int games::ddr::DDRP3IOHandle::device_io(
|
|||||||
|
|
||||||
// shift table
|
// shift table
|
||||||
static size_t 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[] = {
|
static size_t button_table[] = {
|
||||||
Buttons::SERVICE,
|
Buttons::SERVICE,
|
||||||
Buttons::TEST,
|
Buttons::TEST,
|
||||||
Buttons::COIN_MECH,
|
Buttons::COIN_MECH,
|
||||||
|
|
||||||
Buttons::P1_START,
|
Buttons::P1_START,
|
||||||
|
|
||||||
Buttons::P1_PANEL_UP,
|
Buttons::P1_PANEL_UP,
|
||||||
Buttons::P1_PANEL_DOWN,
|
Buttons::P1_PANEL_DOWN,
|
||||||
Buttons::P1_PANEL_LEFT,
|
Buttons::P1_PANEL_LEFT,
|
||||||
Buttons::P1_PANEL_RIGHT,
|
Buttons::P1_PANEL_RIGHT,
|
||||||
|
|
||||||
Buttons::P1_MENU_UP,
|
Buttons::P1_MENU_UP,
|
||||||
Buttons::P1_MENU_DOWN,
|
Buttons::P1_MENU_DOWN,
|
||||||
Buttons::P1_MENU_LEFT,
|
Buttons::P1_MENU_LEFT,
|
||||||
Buttons::P1_MENU_RIGHT,
|
Buttons::P1_MENU_RIGHT,
|
||||||
|
|
||||||
Buttons::P2_START,
|
Buttons::P2_START,
|
||||||
|
|
||||||
Buttons::P2_PANEL_UP,
|
Buttons::P2_PANEL_UP,
|
||||||
Buttons::P2_PANEL_DOWN,
|
Buttons::P2_PANEL_DOWN,
|
||||||
Buttons::P2_PANEL_LEFT,
|
Buttons::P2_PANEL_LEFT,
|
||||||
Buttons::P2_PANEL_RIGHT,
|
Buttons::P2_PANEL_RIGHT,
|
||||||
|
|
||||||
Buttons::P2_MENU_UP,
|
Buttons::P2_MENU_UP,
|
||||||
Buttons::P2_MENU_DOWN,
|
Buttons::P2_MENU_DOWN,
|
||||||
Buttons::P2_MENU_LEFT,
|
Buttons::P2_MENU_LEFT,
|
||||||
Buttons::P2_MENU_RIGHT,
|
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
|
// update states
|
||||||
auto &buttons = get_buttons();
|
auto &buttons = get_buttons();
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
#include "../io.h"
|
#include "../io.h"
|
||||||
@@ -255,7 +256,8 @@ namespace games::ddr {
|
|||||||
case P4IO_IOCTL_GET_INPUTS: {
|
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
|
// 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);
|
memset(lpOutBuffer, 0, 16);
|
||||||
auto controls = (uint32_t*) lpOutBuffer;
|
auto controls = (uint32_t*) lpOutBuffer;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "games/game.h"
|
#include "games/game.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/memutils.h"
|
#include "util/memutils.h"
|
||||||
#include "rgb_cam.h"
|
#include "rgb_cam.h"
|
||||||
|
|
||||||
@@ -221,10 +222,11 @@ namespace games::drs {
|
|||||||
void start_touch() {
|
void start_touch() {
|
||||||
std::thread t([] {
|
std::thread t([] {
|
||||||
log_info("drs", "starting touch input thread");
|
log_info("drs", "starting touch input thread");
|
||||||
|
timeutils::PreciseSleepTimer timer;
|
||||||
|
|
||||||
// main loop
|
// main loop
|
||||||
while (TRUE) {
|
while (TRUE) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
timer.sleep(1);
|
||||||
|
|
||||||
TOUCH_EVENTS.clear();
|
TOUCH_EVENTS.clear();
|
||||||
touch_get_events(TOUCH_EVENTS);
|
touch_get_events(TOUCH_EVENTS);
|
||||||
|
|||||||
@@ -35,13 +35,14 @@ std::vector<Analog> &games::ftt::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
|
analogs = GameAPI::Analogs::getAnalogs("FutureTomTom");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using GameAPI::Analogs::AnalogType;
|
||||||
&analogs,
|
|
||||||
"Pad 1",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Pad 2",
|
{ "Pad 1", AnalogType::LinearPositive },
|
||||||
"Pad 3",
|
{ "Pad 2", AnalogType::LinearPositive },
|
||||||
"Pad 4"
|
{ "Pad 3", AnalogType::LinearPositive },
|
||||||
);
|
{ "Pad 4", AnalogType::LinearPositive }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#include "asio.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "avs/game.h"
|
||||||
|
#include "gitadora.h"
|
||||||
|
#include "util/detour.h"
|
||||||
|
#include "util/logging.h"
|
||||||
|
|
||||||
|
namespace games::gitadora {
|
||||||
|
|
||||||
|
// Redirects the game's hard-coded "XONAR" ASIO driver lookup to the
|
||||||
|
// driver name in ASIO_DRIVER by intercepting registry calls to
|
||||||
|
// HKLM\SOFTWARE\ASIO. Sentinel HKEY values mark the redirected handles
|
||||||
|
// so we can recognise them on subsequent reg* calls.
|
||||||
|
|
||||||
|
static const HKEY PARENT_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x4001);
|
||||||
|
static const HKEY DEVICE_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x4002);
|
||||||
|
static const char *FAKE_ASIO_DEVICE_NAME = "XONAR";
|
||||||
|
|
||||||
|
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
|
||||||
|
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
|
||||||
|
static decltype(RegOpenKeyA) *RegOpenKeyA_orig = nullptr;
|
||||||
|
static decltype(RegOpenKeyExA) *RegOpenKeyExA_orig = nullptr;
|
||||||
|
static decltype(RegQueryValueExA) *RegQueryValueExA_orig = nullptr;
|
||||||
|
|
||||||
|
static HKEY real_asio_reg_handle = nullptr;
|
||||||
|
static HKEY real_asio_device_reg_handle = nullptr;
|
||||||
|
|
||||||
|
static LONG WINAPI RegOpenKeyExA_hook(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
|
||||||
|
PHKEY phkResult)
|
||||||
|
{
|
||||||
|
if (ASIO_DRIVER.has_value() &&
|
||||||
|
lpSubKey != nullptr &&
|
||||||
|
phkResult != nullptr &&
|
||||||
|
hKey == PARENT_ASIO_REG_HANDLE &&
|
||||||
|
_stricmp(lpSubKey, FAKE_ASIO_DEVICE_NAME) == 0) {
|
||||||
|
|
||||||
|
*phkResult = DEVICE_ASIO_REG_HANDLE;
|
||||||
|
|
||||||
|
log_info("gitadora::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
|
||||||
|
const auto result = RegOpenKeyExA_orig(
|
||||||
|
real_asio_reg_handle,
|
||||||
|
ASIO_DRIVER.value().c_str(),
|
||||||
|
ulOptions,
|
||||||
|
samDesired,
|
||||||
|
&real_asio_device_reg_handle);
|
||||||
|
|
||||||
|
if (result != ERROR_SUCCESS) {
|
||||||
|
log_warning(
|
||||||
|
"gitadora::asio",
|
||||||
|
"failed to open registry subkey '{}', error=0x{:x}",
|
||||||
|
ASIO_DRIVER.value(), result);
|
||||||
|
log_warning(
|
||||||
|
"gitadora::asio",
|
||||||
|
"due to improper ASIO setting, audio init will fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
|
||||||
|
if (ASIO_DRIVER.has_value() &&
|
||||||
|
lpSubKey != nullptr &&
|
||||||
|
phkResult != nullptr &&
|
||||||
|
hKey == HKEY_LOCAL_MACHINE &&
|
||||||
|
_stricmp(lpSubKey, "software\\asio") == 0)
|
||||||
|
{
|
||||||
|
*phkResult = PARENT_ASIO_REG_HANDLE;
|
||||||
|
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegOpenKeyA_orig(hKey, lpSubKey, phkResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LONG WINAPI RegEnumKeyA_hook(HKEY hKey, DWORD dwIndex, LPSTR lpName, DWORD cchName) {
|
||||||
|
if (hKey == PARENT_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
|
||||||
|
if (dwIndex == 0) {
|
||||||
|
// forward to real handle just to verify the key exists; we
|
||||||
|
// overwrite the name with our fake driver string regardless
|
||||||
|
auto ret = RegEnumKeyA_orig(real_asio_reg_handle, dwIndex, lpName, cchName);
|
||||||
|
if (ret == ERROR_SUCCESS && lpName != nullptr && cchName > 0) {
|
||||||
|
log_info("gitadora::asio", "stubbing '{}' with '{}'", lpName, FAKE_ASIO_DEVICE_NAME);
|
||||||
|
strncpy(lpName, FAKE_ASIO_DEVICE_NAME, cchName);
|
||||||
|
lpName[cchName - 1] = '\0';
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
return ERROR_NO_MORE_ITEMS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegEnumKeyA_orig(hKey, dwIndex, lpName, cchName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
|
||||||
|
LPBYTE lpData, LPDWORD lpcbData)
|
||||||
|
{
|
||||||
|
HKEY target = hKey;
|
||||||
|
|
||||||
|
if (ASIO_DRIVER.has_value() &&
|
||||||
|
lpValueName != nullptr &&
|
||||||
|
lpData != nullptr &&
|
||||||
|
lpcbData != nullptr &&
|
||||||
|
hKey == DEVICE_ASIO_REG_HANDLE) {
|
||||||
|
if (_stricmp(lpValueName, "Description") == 0) {
|
||||||
|
// engine may verify the driver name after open; ensure it still
|
||||||
|
// sees something containing "XONAR" so the substring check passes
|
||||||
|
const size_t len = strlen(FAKE_ASIO_DEVICE_NAME) + 1;
|
||||||
|
if (*lpcbData < len) {
|
||||||
|
*lpcbData = static_cast<DWORD>(len);
|
||||||
|
return ERROR_MORE_DATA;
|
||||||
|
}
|
||||||
|
memcpy(lpData, FAKE_ASIO_DEVICE_NAME, len);
|
||||||
|
*lpcbData = static_cast<DWORD>(len);
|
||||||
|
if (lpType != nullptr) {
|
||||||
|
*lpType = REG_SZ;
|
||||||
|
}
|
||||||
|
return ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for everything else (CLSID etc.) defer to the real driver subkey
|
||||||
|
target = real_asio_device_reg_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegQueryValueExA_orig(target, lpValueName, lpReserved, lpType, lpData, lpcbData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LONG WINAPI RegCloseKey_hook(HKEY hKey) {
|
||||||
|
if (hKey == PARENT_ASIO_REG_HANDLE) {
|
||||||
|
if (real_asio_reg_handle != nullptr) {
|
||||||
|
RegCloseKey_orig(real_asio_reg_handle);
|
||||||
|
real_asio_reg_handle = nullptr;
|
||||||
|
}
|
||||||
|
return ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hKey == DEVICE_ASIO_REG_HANDLE) {
|
||||||
|
if (real_asio_device_reg_handle != nullptr) {
|
||||||
|
RegCloseKey_orig(real_asio_device_reg_handle);
|
||||||
|
real_asio_device_reg_handle = nullptr;
|
||||||
|
}
|
||||||
|
return ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegCloseKey_orig(hKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void asio_hook_init() {
|
||||||
|
if (!ASIO_DRIVER.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("gitadora::asio", "installing ASIO driver redirect: XONAR -> {}", ASIO_DRIVER.value());
|
||||||
|
|
||||||
|
RegCloseKey_orig = detour::iat_try(
|
||||||
|
"RegCloseKey", RegCloseKey_hook, avs::game::DLL_INSTANCE);
|
||||||
|
RegEnumKeyA_orig = detour::iat_try(
|
||||||
|
"RegEnumKeyA", RegEnumKeyA_hook, avs::game::DLL_INSTANCE);
|
||||||
|
RegOpenKeyA_orig = detour::iat_try(
|
||||||
|
"RegOpenKeyA", RegOpenKeyA_hook, avs::game::DLL_INSTANCE);
|
||||||
|
RegOpenKeyExA_orig = detour::iat_try(
|
||||||
|
"RegOpenKeyExA", RegOpenKeyExA_hook, avs::game::DLL_INSTANCE);
|
||||||
|
RegQueryValueExA_orig = detour::iat_try(
|
||||||
|
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace games::gitadora {
|
||||||
|
|
||||||
|
// installs IAT registry hooks in gfdm.dll that redirect the game's
|
||||||
|
// ASIO driver lookup (hard-coded "XONAR" substring) to a user-chosen
|
||||||
|
// driver name read from games::gitadora::ASIO_DRIVER.
|
||||||
|
//
|
||||||
|
// safe to call unconditionally; if ASIO_DRIVER is unset the hooks
|
||||||
|
// forward every call straight through to advapi32.
|
||||||
|
void asio_hook_init();
|
||||||
|
}
|
||||||
@@ -174,6 +174,7 @@ namespace games::gitadora {
|
|||||||
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
|
static_assert(sizeof(AIO_IOB5_Y32D) == 0x2e8);
|
||||||
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
|
static_assert(sizeof(AIO_IOB2_BI2X_AC1) == 0x4570);
|
||||||
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
static_assert(sizeof(AIO_IOB2_BI2X_WRFIRM) == 0x20f48);
|
||||||
|
static_assert(sizeof(AIO_IOB2_BI2X_AC1__DEVSTATUS) == 0x2C8);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* typedefs
|
* typedefs
|
||||||
|
|||||||
@@ -1,18 +1,27 @@
|
|||||||
#include "gitadora.h"
|
#include "gitadora.h"
|
||||||
|
#include "asio.h"
|
||||||
#include "handle.h"
|
#include "handle.h"
|
||||||
#include "bi2x_hook.h"
|
#include "bi2x_hook.h"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <ks.h>
|
||||||
|
#include <ksmedia.h>
|
||||||
|
|
||||||
#include "cfg/configurator.h"
|
#include "cfg/configurator.h"
|
||||||
|
#include "hooks/audio/audio.h"
|
||||||
#include "hooks/audio/mme.h"
|
#include "hooks/audio/mme.h"
|
||||||
#include "hooks/graphics/graphics.h"
|
#include "hooks/graphics/graphics.h"
|
||||||
#include "misc/wintouchemu.h"
|
#include "misc/wintouchemu.h"
|
||||||
#include "overlay/overlay.h"
|
#include "overlay/overlay.h"
|
||||||
#include "util/cpuutils.h"
|
#include "util/cpuutils.h"
|
||||||
|
#include "util/deferlog.h"
|
||||||
#include "util/detour.h"
|
#include "util/detour.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
#include "util/sigscan.h"
|
#include "util/sigscan.h"
|
||||||
#include "util/socd_cleaner.h"
|
#include "util/socd_cleaner.h"
|
||||||
|
#include "util/sysutils.h"
|
||||||
|
#include "util/utils.h"
|
||||||
#include "hooks/setupapihook.h"
|
#include "hooks/setupapihook.h"
|
||||||
|
|
||||||
namespace games::gitadora {
|
namespace games::gitadora {
|
||||||
@@ -20,11 +29,13 @@ namespace games::gitadora {
|
|||||||
// settings
|
// settings
|
||||||
bool TWOCHANNEL = false;
|
bool TWOCHANNEL = false;
|
||||||
std::optional<unsigned int> CAB_TYPE = std::nullopt;
|
std::optional<unsigned int> CAB_TYPE = std::nullopt;
|
||||||
bool ARENA_SINGLE_WINDOW = false;
|
|
||||||
bool P1_LEFTY = false;
|
bool P1_LEFTY = false;
|
||||||
bool P2_LEFTY = false;
|
bool P2_LEFTY = false;
|
||||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||||
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
std::optional<socd::SocdAlgorithm> PICK_ALGO = socd::SocdAlgorithm::PreferRecent;
|
||||||
|
std::optional<uint8_t> ARENA_WINDOW_COUNT = std::nullopt;
|
||||||
|
std::optional<std::string> ASIO_DRIVER = std::nullopt;
|
||||||
|
bool ALLOW_REALTEK_AUDIO = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prevent GitaDora from creating folders on F drive
|
* Prevent GitaDora from creating folders on F drive
|
||||||
@@ -221,13 +232,6 @@ namespace games::gitadora {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// arena model launches a tiny window yet backbuffer at 4k, resulting in unusable overlay
|
|
||||||
// force scaling to make things usable
|
|
||||||
if (!overlay::UI_SCALE_PERCENT.has_value() && is_arena_model() &&
|
|
||||||
GRAPHICS_WINDOWED && !cfg::CONFIGURATOR_STANDALONE) {
|
|
||||||
overlay::UI_SCALE_PERCENT = 250;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for guitar wail SOCD cleaning
|
// for guitar wail SOCD cleaning
|
||||||
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
socd::ALGORITHM = socd::SocdAlgorithm::PreferRecent;
|
||||||
|
|
||||||
@@ -237,12 +241,334 @@ namespace games::gitadora {
|
|||||||
} else {
|
} else {
|
||||||
log_info("gitadora", "guitar pick SOCD algorithm: legacy");
|
log_info("gitadora", "guitar pick SOCD algorithm: legacy");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if SPICE64 && !SPICE_XP
|
||||||
|
|
||||||
|
if (is_arena_model()) {
|
||||||
|
// in full screen, if single-adapter option is checked, it's functionally
|
||||||
|
// the same as forcing a single monitor
|
||||||
|
if (!GRAPHICS_WINDOWED && GRAPHICS_FORCE_SINGLE_ADAPTER) {
|
||||||
|
ARENA_WINDOW_COUNT = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out default settings if user didn't provide one
|
||||||
|
if (!ARENA_WINDOW_COUNT.has_value()) {
|
||||||
|
if (!GRAPHICS_WINDOWED && sysutils::enumerate_monitors().size() < 4) {
|
||||||
|
log_info("gitadora", "arena model: <4 monitors, defaulting to single window mode");
|
||||||
|
ARENA_WINDOW_COUNT = 1;
|
||||||
|
} else {
|
||||||
|
ARENA_WINDOW_COUNT = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int count = ARENA_WINDOW_COUNT.value();
|
||||||
|
switch (count) {
|
||||||
|
case 1:
|
||||||
|
log_info("gitadora", "arena model: single-window mode");
|
||||||
|
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
|
||||||
|
GRAPHICS_PREVENT_SECONDARY_WINDOWS = true;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (!GRAPHICS_WINDOWED) {
|
||||||
|
log_fatal(
|
||||||
|
"gitadora",
|
||||||
|
"arena model: 2-window mode is not supported in fullscreen, choose 1 or 4");
|
||||||
|
}
|
||||||
|
log_info("gitadora", "arena model: two-window mode");
|
||||||
|
GRAPHICS_GITADORA_HIDE_SIDE_WINDOWS = true;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
log_info("gitadora", "arena model: four-window mode");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log_fatal(
|
||||||
|
"gitadora",
|
||||||
|
"arena model: unsupported window count: {}", count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#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() {
|
void GitaDoraGame::attach() {
|
||||||
Game::attach();
|
Game::attach();
|
||||||
|
|
||||||
|
// 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()) {
|
||||||
|
log_info("gitadora", "forcing UI scale to 250% for arena model");
|
||||||
|
overlay::UI_SCALE_PERCENT = 250;
|
||||||
|
}
|
||||||
|
|
||||||
// modules
|
// modules
|
||||||
HMODULE sharepj_module = libutils::try_module("libshare-pj.dll");
|
HMODULE sharepj_module = libutils::try_module("libshare-pj.dll");
|
||||||
HMODULE bmsd2_module = libutils::try_module("libbmsd2.dll");
|
HMODULE bmsd2_module = libutils::try_module("libbmsd2.dll");
|
||||||
@@ -269,6 +595,12 @@ namespace games::gitadora {
|
|||||||
#ifdef SPICE64
|
#ifdef SPICE64
|
||||||
// gitadora arena model
|
// gitadora arena model
|
||||||
auto aio = libutils::try_library("libaio.dll");
|
auto aio = libutils::try_library("libaio.dll");
|
||||||
|
|
||||||
|
// before we start patching and hooking things, detect invalid configuration
|
||||||
|
if (aio != nullptr && !is_arena_model()) {
|
||||||
|
log_fatal("gitadora", "arena model i/o (libaio.dll) detected, but <spec> is not an arena model - bad prop XML files?");
|
||||||
|
}
|
||||||
|
|
||||||
if (aio != nullptr) {
|
if (aio != nullptr) {
|
||||||
SETUPAPI_SETTINGS settings{};
|
SETUPAPI_SETTINGS settings{};
|
||||||
settings.class_guid[0] = 0x86E0D1E0;
|
settings.class_guid[0] = 0x86E0D1E0;
|
||||||
@@ -294,39 +626,76 @@ namespace games::gitadora {
|
|||||||
detour::iat_try("GetDriveTypeA", GetDriveTypeA_hook, avs::game::DLL_INSTANCE);
|
detour::iat_try("GetDriveTypeA", GetDriveTypeA_hook, avs::game::DLL_INSTANCE);
|
||||||
detour::iat_try("CreateDirectoryA", CreateDirectoryA_hook, avs::game::DLL_INSTANCE);
|
detour::iat_try("CreateDirectoryA", CreateDirectoryA_hook, avs::game::DLL_INSTANCE);
|
||||||
|
|
||||||
|
// ASIO driver redirect (XONAR -> user-configured driver)
|
||||||
|
asio_hook_init();
|
||||||
|
|
||||||
// volume change prevention
|
// volume change prevention
|
||||||
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
|
hooks::audio::mme::init(avs::game::DLL_INSTANCE);
|
||||||
|
|
||||||
// touch hook
|
// fake Realtek audio injection
|
||||||
if (ARENA_SINGLE_WINDOW) {
|
// if ASIO init succeeds, game tries to look for audio device with `Realtek` in friendly name
|
||||||
|
// if ASIO init fails, game opens default audio device
|
||||||
|
// therefore, it's safe to enable this hook by default regardless of ASIO preference
|
||||||
|
// (unless the user explicitly disables it, of course)
|
||||||
|
if (ALLOW_REALTEK_AUDIO) {
|
||||||
|
log_info(
|
||||||
|
"gitadora",
|
||||||
|
"fake Realtek audio injection disabled "
|
||||||
|
"(user's real Realtek audio may be used after successful ASIO init)");
|
||||||
|
} else {
|
||||||
|
log_info(
|
||||||
|
"gitadora",
|
||||||
|
"fake Realtek audio injection enabled "
|
||||||
|
"(create a fake Realtek audio device to prevent crashes after successful ASIO init)");
|
||||||
|
hooks::audio::INJECT_FAKE_REALTEK_AUDIO = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitor/touch hooks (windowed or full screen)
|
||||||
|
if (GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
|
||||||
|
// enable touch hook for subscreen overlay
|
||||||
wintouchemu::FORCE = true;
|
wintouchemu::FORCE = true;
|
||||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||||
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// window patch
|
||||||
|
if (!is_arena_model()) {
|
||||||
|
HMODULE gdme_module = libutils::try_module("libgdme.dll");
|
||||||
if (GRAPHICS_WINDOWED && !replace_pattern(
|
if (GRAPHICS_WINDOWED && !replace_pattern(
|
||||||
gdme_module,
|
gdme_module,
|
||||||
"754185ED753D8B4118BF0000CB02",
|
"754185ED753D8B4118BF0000CB02",
|
||||||
"9090????9090??????????????12", 0, 0))
|
"9090????9090??????????????12", 0, 0)) {
|
||||||
{
|
|
||||||
log_warning("gitadora", "windowed mode failed");
|
log_warning("gitadora", "windowed mode failed");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// two channel mod
|
||||||
|
if (TWOCHANNEL && !is_arena_model()) {
|
||||||
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
|
HMODULE bmsd_engine_module = libutils::try_module("libbmsd-engine.dll");
|
||||||
HMODULE bmsd_module = libutils::try_module("libbmsd.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);
|
bmsd2_boot_orig = detour::iat_try("bmsd2_boot", bmsd2_boot_hook, bmsd_module);
|
||||||
|
|
||||||
if (!(replace_pattern(bmsd_engine_module, "33000000488D", "03??????????", 0, 0) ||
|
if (!(replace_pattern(bmsd_engine_module, "33000000488D", "03??????????", 0, 0) ||
|
||||||
replace_pattern(bmsd_engine_module, "330000000F10", "03??????????", 0, 0)))
|
replace_pattern(bmsd_engine_module, "330000000F10", "03??????????", 0, 0))) {
|
||||||
{
|
|
||||||
log_warning("gitadora", "two channel mode failed");
|
log_warning("gitadora", "two channel mode failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,4 +703,18 @@ namespace games::gitadora {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fix_audio_channel_mask(WAVEFORMATEX *format) {
|
||||||
|
if (!format || format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ext = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(format);
|
||||||
|
|
||||||
|
// fix the legacy 7.1 channel mask to the modern surround layout
|
||||||
|
// makes it more compatible with modern audio cards
|
||||||
|
if (ext->dwChannelMask == KSAUDIO_SPEAKER_7POINT1) {
|
||||||
|
ext->dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <mmreg.h>
|
||||||
|
|
||||||
#include "avs/game.h"
|
#include "avs/game.h"
|
||||||
#include "games/game.h"
|
#include "games/game.h"
|
||||||
#include "util/socd_cleaner.h"
|
#include "util/socd_cleaner.h"
|
||||||
@@ -11,11 +14,13 @@ namespace games::gitadora {
|
|||||||
// settings
|
// settings
|
||||||
extern bool TWOCHANNEL;
|
extern bool TWOCHANNEL;
|
||||||
extern std::optional<unsigned int> CAB_TYPE;
|
extern std::optional<unsigned int> CAB_TYPE;
|
||||||
extern bool ARENA_SINGLE_WINDOW;
|
|
||||||
extern bool P1_LEFTY;
|
extern bool P1_LEFTY;
|
||||||
extern bool P2_LEFTY;
|
extern bool P2_LEFTY;
|
||||||
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
|
||||||
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
extern std::optional<socd::SocdAlgorithm> PICK_ALGO;
|
||||||
|
extern std::optional<uint8_t> ARENA_WINDOW_COUNT;
|
||||||
|
extern std::optional<std::string> ASIO_DRIVER;
|
||||||
|
extern bool ALLOW_REALTEK_AUDIO;
|
||||||
|
|
||||||
class GitaDoraGame : public games::Game {
|
class GitaDoraGame : public games::Game {
|
||||||
public:
|
public:
|
||||||
@@ -24,6 +29,8 @@ namespace games::gitadora {
|
|||||||
virtual void attach() override;
|
virtual void attach() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void fix_audio_channel_mask(WAVEFORMATEX *format);
|
||||||
|
|
||||||
static inline bool is_drum() {
|
static inline bool is_drum() {
|
||||||
return (
|
return (
|
||||||
avs::game::is_model({ "J32", "K32", "L32" }) ||
|
avs::game::is_model({ "J32", "K32", "L32" }) ||
|
||||||
|
|||||||
@@ -86,19 +86,21 @@ std::vector<Button> &games::gitadora::get_buttons() {
|
|||||||
std::vector<Analog> &games::gitadora::get_analogs() {
|
std::vector<Analog> &games::gitadora::get_analogs() {
|
||||||
static std::vector<Analog> analogs;
|
static std::vector<Analog> analogs;
|
||||||
|
|
||||||
|
using namespace GameAPI::Analogs;
|
||||||
|
|
||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("GitaDora");
|
analogs = GameAPI::Analogs::getAnalogs("GitaDora");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(&analogs,
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Guitar P1 Wail X",
|
{"Guitar P1 Wail X", AnalogType::LinearCentered},
|
||||||
"Guitar P1 Wail Y",
|
{"Guitar P1 Wail Y", AnalogType::LinearCentered},
|
||||||
"Guitar P1 Wail Z",
|
{"Guitar P1 Wail Z", AnalogType::LinearCentered},
|
||||||
"Guitar P1 Knob",
|
{"Guitar P1 Knob", AnalogType::Circular},
|
||||||
"Guitar P2 Wail X",
|
{"Guitar P2 Wail X", AnalogType::LinearCentered},
|
||||||
"Guitar P2 Wail Y",
|
{"Guitar P2 Wail Y", AnalogType::LinearCentered},
|
||||||
"Guitar P2 Wail Z",
|
{"Guitar P2 Wail Z", AnalogType::LinearCentered},
|
||||||
"Guitar P2 Knob"
|
{"Guitar P2 Knob", AnalogType::Circular}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
#include "iidx.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 - otherwise the IO thread will go too hard on the CPU
|
||||||
Sleep(1);
|
static thread_local timeutils::PreciseSleepTimer timer;
|
||||||
|
timer.sleep(1);
|
||||||
|
|
||||||
// generate message
|
// generate message
|
||||||
auto msg = this->create_msg(msg_in, 0x2E);
|
auto msg = this->create_msg(msg_in, 0x2E);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "rawinput/rawinput.h"
|
#include "rawinput/rawinput.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
|
|
||||||
#include "iidx.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 - otherwise the IO thread will go too hard on the CPU
|
||||||
Sleep(1);
|
static thread_local timeutils::PreciseSleepTimer timer;
|
||||||
|
timer.sleep(1);
|
||||||
|
|
||||||
// generate message
|
// generate message
|
||||||
auto msg = this->create_msg(msg_in, 0x2E);
|
auto msg = this->create_msg(msg_in, 0x2E);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
|
|
||||||
#if SPICE64
|
#if SPICE64 && !SPICE_XP
|
||||||
|
|
||||||
#include <d3d9.h>
|
#include <d3d9.h>
|
||||||
#include "mf_wrappers.h"
|
#include "mf_wrappers.h"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if SPICE64
|
#if SPICE64 && !SPICE_XP
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "games/iidx/local_camera.h"
|
#include "games/iidx/local_camera.h"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "util/memutils.h"
|
#include "util/memutils.h"
|
||||||
#include "util/sigscan.h"
|
#include "util/sigscan.h"
|
||||||
#include "util/socd_cleaner.h"
|
#include "util/socd_cleaner.h"
|
||||||
|
#include "util/sysutils.h"
|
||||||
#include "util/time.h"
|
#include "util/time.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "launcher/signal.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 = std::nullopt;
|
||||||
std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt;
|
std::optional<std::string> SOUND_OUTPUT_DEVICE_IN_EFFECT = std::nullopt;
|
||||||
std::optional<std::string> ASIO_DRIVER = 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;
|
uint8_t DIGITAL_TT_SENS = 4;
|
||||||
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt;
|
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt;
|
||||||
std::optional<std::string> SCREEN_MODE = std::nullopt;
|
std::optional<std::string> SCREEN_MODE = std::nullopt;
|
||||||
@@ -423,6 +426,14 @@ namespace games::iidx {
|
|||||||
init_legacy_camera_hook(FLIP_CAMS);
|
init_legacy_camera_hook(FLIP_CAMS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if SPICE64
|
||||||
|
|
||||||
|
if (TDJ_MODE) {
|
||||||
|
sysutils::hook_EnumDisplayDevicesA();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// init cfgmgr32 hooks
|
// init cfgmgr32 hooks
|
||||||
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
|
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
|
||||||
}
|
}
|
||||||
@@ -486,28 +497,28 @@ namespace games::iidx {
|
|||||||
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
|
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) {
|
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
|
||||||
IIDX_TDJ_MONITOR_WARNING = true;
|
IIDX_TDJ_MONITOR_WARNING = true;
|
||||||
log_warning(
|
log_warning(
|
||||||
"iidx",
|
"iidx",
|
||||||
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
|
"\n\n"
|
||||||
"!!! compatibility issues with the game !!!\n"
|
"!!! using -dxmainadapter option with TDJ is NOT recommended !!!\n"
|
||||||
|
"!!! due to known compatibility issues with the game !!!\n"
|
||||||
"!!! !!!\n"
|
"!!! !!!\n"
|
||||||
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
|
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
|
||||||
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
|
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
|
||||||
"!!! !!!\n"
|
"!!! !!!\n"
|
||||||
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
|
"!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n"
|
||||||
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
|
"!!! option instead of -dxmainadapter !!!\n\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
deferredlogs::defer_error_messages({
|
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:",
|
" due to known compatibility issues with the game:",
|
||||||
" * game may launch in wrong resolution or refresh rate",
|
" * game may launch in wrong resolution or refresh rate",
|
||||||
" * touch / mouse input may stop working in subscreen / overlay",
|
" * touch / mouse input may stop working in subscreen / overlay",
|
||||||
" recommended fix is to NOT use -monitor and instead set the primary",
|
" use Change Main Monitor (-mainmonitor) option instead"
|
||||||
" monitor in Windows settings before launching the game"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,11 +543,7 @@ namespace games::iidx {
|
|||||||
// if the user specified a value (other than auto), use it as the environment var
|
// if the user specified a value (other than auto), use it as the environment var
|
||||||
// probably "wasapi" or "asio", but it's not explicitly checked here for forward compat
|
// probably "wasapi" or "asio", but it's not explicitly checked here for forward compat
|
||||||
if (SOUND_OUTPUT_DEVICE.has_value() && SOUND_OUTPUT_DEVICE.value() != "auto") {
|
if (SOUND_OUTPUT_DEVICE.has_value() && SOUND_OUTPUT_DEVICE.value() != "auto") {
|
||||||
log_info(
|
// the environemnt variable was already set in launcher.cpp
|
||||||
"iidx",
|
|
||||||
"using user-supplied \"{}\" for SOUND_OUTPUT_DEVICE",
|
|
||||||
SOUND_OUTPUT_DEVICE.value());
|
|
||||||
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", SOUND_OUTPUT_DEVICE.value().c_str());
|
|
||||||
SOUND_OUTPUT_DEVICE_IN_EFFECT = SOUND_OUTPUT_DEVICE;
|
SOUND_OUTPUT_DEVICE_IN_EFFECT = SOUND_OUTPUT_DEVICE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -772,7 +779,7 @@ namespace games::iidx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return higher 8 bit
|
// return higher 8 bit
|
||||||
return (uint8_t) (ret_value >> 2);
|
return (uint8_t)(ret_value >> 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char get_slider(uint8_t slider) {
|
unsigned char get_slider(uint8_t slider) {
|
||||||
@@ -882,39 +889,10 @@ namespace games::iidx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// patch iidx32+ for asio compatibility
|
// note: the iidx32+ ASIO refcount bug (a duplicate AddRef on the ASIO instance with
|
||||||
// only do this if NOT wasapi (as opposed to checking if it's asio)
|
// no matching Release, which leaks the driver and can hang non-XONAR devices) is now
|
||||||
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
|
// handled transparently by the WrappedAsio proxy (see hooks/audio/asio_proxy.cpp),
|
||||||
// other devices as a XONAR, don't check for the exact string (common ASIO workaround for INF)
|
// so no game-DLL signature patch is needed here anymore
|
||||||
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]
|
|
||||||
// 48 8b 01 mov rax,QWORD PTR [rcx]
|
|
||||||
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
|
|
||||||
|
|
||||||
intptr_t result = replace_pattern(
|
|
||||||
avs::game::DLL_INSTANCE,
|
|
||||||
"FF50????????????????FF50??4533C94533C0418D51",
|
|
||||||
"????????????????????909090??????????????????",
|
|
||||||
0, 0);
|
|
||||||
|
|
||||||
if (result == 0) {
|
|
||||||
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());
|
|
||||||
} else {
|
|
||||||
log_info(
|
|
||||||
"iidx",
|
|
||||||
"Successfully applied ASIO compatibility fix for iidx32+ using signature matching @ 0x{:x}.",
|
|
||||||
result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -63,16 +63,17 @@ std::vector<Analog> &games::iidx::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
|
analogs = GameAPI::Analogs::getAnalogs("Beatmania IIDX");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using namespace GameAPI::Analogs;
|
||||||
&analogs,
|
|
||||||
"Turntable P1",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Turntable P2",
|
{ "Turntable P1", AnalogType::Circular },
|
||||||
"VEFX",
|
{ "Turntable P2", AnalogType::Circular },
|
||||||
"Low-EQ",
|
{ "VEFX", AnalogType::LinearPositive },
|
||||||
"Hi-EQ",
|
{ "Low-EQ", AnalogType::LinearPositive },
|
||||||
"Filter",
|
{ "Hi-EQ", AnalogType::LinearPositive },
|
||||||
"Play Volume"
|
{ "Filter", AnalogType::LinearPositive },
|
||||||
);
|
{ "Play Volume", AnalogType::LinearPositive }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return analogs;
|
return analogs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#include "games/iidx/local_camera.h"
|
#include "games/iidx/local_camera.h"
|
||||||
|
|
||||||
#if SPICE64
|
#if SPICE64 && !SPICE_XP
|
||||||
|
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
#include "util/utils.h"
|
#include "util/utils.h"
|
||||||
#include "mf_wrappers.h"
|
#include "mf_wrappers.h"
|
||||||
|
|
||||||
@@ -399,6 +400,7 @@ namespace games::iidx {
|
|||||||
void IIDXLocalCamera::CreateThread() {
|
void IIDXLocalCamera::CreateThread() {
|
||||||
// Create thread
|
// Create thread
|
||||||
m_drawThread = new std::thread([this]() {
|
m_drawThread = new std::thread([this]() {
|
||||||
|
timeutils::PreciseSleepTimer timer;
|
||||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||||
|
|
||||||
double accumulator = 0.0;
|
double accumulator = 0.0;
|
||||||
@@ -412,7 +414,7 @@ namespace games::iidx {
|
|||||||
accumulator -= 1.0;
|
accumulator -= 1.0;
|
||||||
floorFrameTimeMicroSec += 1;
|
floorFrameTimeMicroSec += 1;
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
|
timer.sleep(std::chrono::microseconds(floorFrameTimeMicroSec));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if SPICE64
|
#if SPICE64 && !SPICE_XP
|
||||||
|
|
||||||
#include <d3d9.h>
|
#include <d3d9.h>
|
||||||
#include <dxva2api.h>
|
#include <dxva2api.h>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "util/libutils.h"
|
#include "util/libutils.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
|
|
||||||
#define POKE_NATIVE_TOUCH 0
|
#define POKE_NATIVE_TOUCH 0
|
||||||
|
|
||||||
@@ -200,6 +201,7 @@ namespace games::iidx::poke {
|
|||||||
// create new thread
|
// create new thread
|
||||||
THREAD_RUNNING = true;
|
THREAD_RUNNING = true;
|
||||||
THREAD = new std::thread([] {
|
THREAD = new std::thread([] {
|
||||||
|
timeutils::PreciseSleepTimer timer;
|
||||||
|
|
||||||
// log
|
// log
|
||||||
log_info("poke", "enabled");
|
log_info("poke", "enabled");
|
||||||
@@ -319,7 +321,7 @@ namespace games::iidx::poke {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// slow down
|
// slow down
|
||||||
Sleep(50);
|
timer.sleep(50);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
+55
-47
@@ -51,7 +51,7 @@ namespace games {
|
|||||||
static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs;
|
static robin_hood::unordered_map<std::string, std::vector<Analog> &> analogs;
|
||||||
static robin_hood::unordered_map<std::string, std::vector<Light> &> lights;
|
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<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() {
|
static void initialize() {
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ namespace games {
|
|||||||
buttons_help.insert({ bbc, bbc::get_buttons_help() });
|
buttons_help.insert({ bbc, bbc::get_buttons_help() });
|
||||||
analogs.insert({ bbc, bbc::get_analogs() });
|
analogs.insert({ bbc, bbc::get_analogs() });
|
||||||
lights.insert({ bbc, bbc::get_lights() });
|
lights.insert({ bbc, bbc::get_lights() });
|
||||||
file_hints[bbc].emplace_back("bsch.dll");
|
file_hints[bbc].push_back({"bsch.dll"});
|
||||||
|
|
||||||
// hpm
|
// hpm
|
||||||
const std::string hpm("HELLO! Pop'n Music");
|
const std::string hpm("HELLO! Pop'n Music");
|
||||||
@@ -76,32 +76,35 @@ namespace games {
|
|||||||
buttons.insert({ hpm, hpm::get_buttons() });
|
buttons.insert({ hpm, hpm::get_buttons() });
|
||||||
buttons_help.insert({ hpm, hpm::get_buttons_help() });
|
buttons_help.insert({ hpm, hpm::get_buttons_help() });
|
||||||
lights.insert({ hpm, hpm::get_lights() });
|
lights.insert({ hpm, hpm::get_lights() });
|
||||||
file_hints[hpm].emplace_back("popn.dll");
|
file_hints[hpm].push_back({"popn.dll", "pdh.dll"});
|
||||||
|
|
||||||
// bs
|
// bs
|
||||||
const std::string bs("Beatstream");
|
const std::string bs("Beatstream");
|
||||||
games.push_back(bs);
|
games.push_back(bs);
|
||||||
buttons.insert({ bs, bs::get_buttons() });
|
buttons.insert({ bs, bs::get_buttons() });
|
||||||
lights.insert({ bs, bs::get_lights() });
|
lights.insert({ bs, bs::get_lights() });
|
||||||
file_hints[bs].emplace_back("beatstream.dll");
|
file_hints[bs].push_back({"beatstream.dll"});
|
||||||
file_hints[bs].emplace_back("beatstream1.dll");
|
file_hints[bs].push_back({"beatstream1.dll"});
|
||||||
file_hints[bs].emplace_back("beatstream2.dll");
|
file_hints[bs].push_back({"beatstream2.dll"});
|
||||||
|
|
||||||
// ddr
|
// ddr
|
||||||
const std::string ddr("Dance Dance Revolution");
|
const std::string ddr("Dance Dance Revolution");
|
||||||
games.push_back(ddr);
|
games.push_back(ddr);
|
||||||
buttons.insert({ ddr, ddr::get_buttons() });
|
buttons.insert({ ddr, ddr::get_buttons() });
|
||||||
lights.insert({ ddr, ddr::get_lights() });
|
lights.insert({ ddr, ddr::get_lights() });
|
||||||
file_hints[ddr].emplace_back("ddr.dll");
|
buttons_help.insert({ ddr, ddr::get_buttons_help() });
|
||||||
file_hints[ddr].emplace_back("mdxja_945.dll");
|
analogs_help.insert({ ddr, ddr::get_analogs_help() });
|
||||||
file_hints[ddr].emplace_back("arkmdxp3.dll");
|
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
|
// dea
|
||||||
const std::string dea("Dance Evolution");
|
const std::string dea("Dance Evolution");
|
||||||
games.push_back(dea);
|
games.push_back(dea);
|
||||||
buttons.insert({ dea, dea::get_buttons() });
|
buttons.insert({ dea, dea::get_buttons() });
|
||||||
lights.insert({ dea, dea::get_lights() });
|
lights.insert({ dea, dea::get_lights() });
|
||||||
file_hints[dea].emplace_back("arkkdm.dll");
|
file_hints[dea].push_back({"arkkdm.dll"});
|
||||||
|
|
||||||
// gitadora
|
// gitadora
|
||||||
const std::string gitadora("GitaDora");
|
const std::string gitadora("GitaDora");
|
||||||
@@ -111,7 +114,7 @@ namespace games {
|
|||||||
analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
|
analogs_help.insert({ gitadora, gitadora::get_analogs_help() });
|
||||||
analogs.insert({ gitadora, gitadora::get_analogs() });
|
analogs.insert({ gitadora, gitadora::get_analogs() });
|
||||||
lights.insert({ gitadora, gitadora::get_lights() });
|
lights.insert({ gitadora, gitadora::get_lights() });
|
||||||
file_hints[gitadora].emplace_back("gdxg.dll");
|
file_hints[gitadora].push_back({"gdxg.dll"});
|
||||||
|
|
||||||
// iidx
|
// iidx
|
||||||
const std::string iidx("Beatmania IIDX");
|
const std::string iidx("Beatmania IIDX");
|
||||||
@@ -120,7 +123,7 @@ namespace games {
|
|||||||
buttons_help.insert({ iidx, iidx::get_buttons_help() });
|
buttons_help.insert({ iidx, iidx::get_buttons_help() });
|
||||||
analogs.insert({ iidx, iidx::get_analogs() });
|
analogs.insert({ iidx, iidx::get_analogs() });
|
||||||
lights.insert({ iidx, iidx::get_lights() });
|
lights.insert({ iidx, iidx::get_lights() });
|
||||||
file_hints[iidx].emplace_back("bm2dx.dll");
|
file_hints[iidx].push_back({"bm2dx.dll"});
|
||||||
|
|
||||||
// jb
|
// jb
|
||||||
const std::string jb("Jubeat");
|
const std::string jb("Jubeat");
|
||||||
@@ -128,7 +131,7 @@ namespace games {
|
|||||||
buttons.insert({ jb, jb::get_buttons() });
|
buttons.insert({ jb, jb::get_buttons() });
|
||||||
buttons_help.insert({ jb, jb::get_buttons_help() });
|
buttons_help.insert({ jb, jb::get_buttons_help() });
|
||||||
lights.insert({ jb, jb::get_lights() });
|
lights.insert({ jb, jb::get_lights() });
|
||||||
file_hints[jb].emplace_back("jubeat.dll");
|
file_hints[jb].push_back({"jubeat.dll"});
|
||||||
|
|
||||||
// mga
|
// mga
|
||||||
const std::string mga("Metal Gear");
|
const std::string mga("Metal Gear");
|
||||||
@@ -136,7 +139,7 @@ namespace games {
|
|||||||
buttons.insert({ mga, mga::get_buttons() });
|
buttons.insert({ mga, mga::get_buttons() });
|
||||||
analogs.insert({ mga, mga::get_analogs() });
|
analogs.insert({ mga, mga::get_analogs() });
|
||||||
lights.insert({ mga, mga::get_lights() });
|
lights.insert({ mga, mga::get_lights() });
|
||||||
file_hints[mga].emplace_back("launch.dll");
|
file_hints[mga].push_back({"launch.dll"});
|
||||||
|
|
||||||
// museca
|
// museca
|
||||||
const std::string museca("Museca");
|
const std::string museca("Museca");
|
||||||
@@ -145,7 +148,7 @@ namespace games {
|
|||||||
buttons_help.insert({ museca, museca::get_buttons_help() });
|
buttons_help.insert({ museca, museca::get_buttons_help() });
|
||||||
analogs.insert({ museca, museca::get_analogs() });
|
analogs.insert({ museca, museca::get_analogs() });
|
||||||
lights.insert({ museca, museca::get_lights() });
|
lights.insert({ museca, museca::get_lights() });
|
||||||
file_hints[museca].emplace_back("museca.dll");
|
file_hints[museca].push_back({"museca.dll"});
|
||||||
|
|
||||||
// nost
|
// nost
|
||||||
const std::string nost("Nostalgia");
|
const std::string nost("Nostalgia");
|
||||||
@@ -154,7 +157,7 @@ namespace games {
|
|||||||
buttons_help.insert({ nost, nost::get_buttons_help() });
|
buttons_help.insert({ nost, nost::get_buttons_help() });
|
||||||
analogs.insert({ nost, nost::get_analogs() });
|
analogs.insert({ nost, nost::get_analogs() });
|
||||||
lights.insert({ nost, nost::get_lights() });
|
lights.insert({ nost, nost::get_lights() });
|
||||||
file_hints[nost].emplace_back("nostalgia.dll");
|
file_hints[nost].push_back({"nostalgia.dll"});
|
||||||
|
|
||||||
// popn
|
// popn
|
||||||
const std::string popn("Pop'n Music");
|
const std::string popn("Pop'n Music");
|
||||||
@@ -162,41 +165,42 @@ namespace games {
|
|||||||
buttons.insert({ popn, popn::get_buttons() });
|
buttons.insert({ popn, popn::get_buttons() });
|
||||||
buttons_help.insert({ popn, popn::get_buttons_help() });
|
buttons_help.insert({ popn, popn::get_buttons_help() });
|
||||||
lights.insert({ popn, popn::get_lights() });
|
lights.insert({ popn, popn::get_lights() });
|
||||||
file_hints[popn].emplace_back("popn19.dll");
|
file_hints[popn].push_back({"popn19.dll"});
|
||||||
file_hints[popn].emplace_back("popn20.dll");
|
file_hints[popn].push_back({"popn20.dll"});
|
||||||
file_hints[popn].emplace_back("popn21.dll");
|
file_hints[popn].push_back({"popn21.dll"});
|
||||||
file_hints[popn].emplace_back("popn22.dll");
|
file_hints[popn].push_back({"popn22.dll"});
|
||||||
file_hints[popn].emplace_back("popn23.dll");
|
file_hints[popn].push_back({"popn23.dll"});
|
||||||
file_hints[popn].emplace_back("popn24.dll");
|
file_hints[popn].push_back({"popn24.dll"});
|
||||||
file_hints[popn].emplace_back("popn25.dll");
|
file_hints[popn].push_back({"popn25.dll"});
|
||||||
|
file_hints[popn].push_back({"popn.dll", "libaio-iob2_video.dll"});
|
||||||
|
|
||||||
// qma
|
// qma
|
||||||
const std::string qma("Quiz Magic Academy");
|
const std::string qma("Quiz Magic Academy");
|
||||||
games.push_back(qma);
|
games.push_back(qma);
|
||||||
buttons.insert({ qma, qma::get_buttons() });
|
buttons.insert({ qma, qma::get_buttons() });
|
||||||
lights.insert({ qma, qma::get_lights() });
|
lights.insert({ qma, qma::get_lights() });
|
||||||
file_hints[qma].emplace_back("client.dll");
|
file_hints[qma].push_back({"client.dll"});
|
||||||
|
|
||||||
// rb
|
// rb
|
||||||
const std::string rb("Reflec Beat");
|
const std::string rb("Reflec Beat");
|
||||||
games.push_back(rb);
|
games.push_back(rb);
|
||||||
buttons.insert({ rb, rb::get_buttons() });
|
buttons.insert({ rb, rb::get_buttons() });
|
||||||
lights.insert({ rb, rb::get_lights() });
|
lights.insert({ rb, rb::get_lights() });
|
||||||
file_hints[rb].emplace_back("reflecbeat.dll");
|
file_hints[rb].push_back({"reflecbeat.dll"});
|
||||||
|
|
||||||
// shogikai
|
// shogikai
|
||||||
std::string shogikai("Tenkaichi Shogikai");
|
std::string shogikai("Tenkaichi Shogikai");
|
||||||
games.push_back(shogikai);
|
games.push_back(shogikai);
|
||||||
buttons.insert({ shogikai, shogikai::get_buttons() });
|
buttons.insert({ shogikai, shogikai::get_buttons() });
|
||||||
lights.insert({ shogikai, shogikai::get_lights() });
|
lights.insert({ shogikai, shogikai::get_lights() });
|
||||||
file_hints[shogikai].emplace_back("shogi_engine.dll");
|
file_hints[shogikai].push_back({"shogi_engine.dll"});
|
||||||
|
|
||||||
// rf3d
|
// rf3d
|
||||||
const std::string rf3d("Road Fighters 3D");
|
const std::string rf3d("Road Fighters 3D");
|
||||||
games.push_back(rf3d);
|
games.push_back(rf3d);
|
||||||
buttons.insert({ rf3d, rf3d::get_buttons() });
|
buttons.insert({ rf3d, rf3d::get_buttons() });
|
||||||
analogs.insert({ rf3d, rf3d::get_analogs() });
|
analogs.insert({ rf3d, rf3d::get_analogs() });
|
||||||
file_hints[rf3d].emplace_back("jgt.dll");
|
file_hints[rf3d].push_back({"jgt.dll"});
|
||||||
|
|
||||||
// sc
|
// sc
|
||||||
const std::string sc("Steel Chronicle");
|
const std::string sc("Steel Chronicle");
|
||||||
@@ -204,7 +208,7 @@ namespace games {
|
|||||||
buttons.insert({ sc, sc::get_buttons() });
|
buttons.insert({ sc, sc::get_buttons() });
|
||||||
analogs.insert({ sc, sc::get_analogs() });
|
analogs.insert({ sc, sc::get_analogs() });
|
||||||
lights.insert({ sc, sc::get_lights() });
|
lights.insert({ sc, sc::get_lights() });
|
||||||
file_hints[sc].emplace_back("gamekgg.dll");
|
file_hints[sc].push_back({"gamekgg.dll"});
|
||||||
|
|
||||||
// sdvx
|
// sdvx
|
||||||
const std::string sdvx("Sound Voltex");
|
const std::string sdvx("Sound Voltex");
|
||||||
@@ -213,13 +217,13 @@ namespace games {
|
|||||||
buttons_help.insert({ sdvx, sdvx::get_buttons_help() });
|
buttons_help.insert({ sdvx, sdvx::get_buttons_help() });
|
||||||
analogs.insert({ sdvx, sdvx::get_analogs() });
|
analogs.insert({ sdvx, sdvx::get_analogs() });
|
||||||
lights.insert({ sdvx, sdvx::get_lights() });
|
lights.insert({ sdvx, sdvx::get_lights() });
|
||||||
file_hints[sdvx].emplace_back("soundvoltex.dll");
|
file_hints[sdvx].push_back({"soundvoltex.dll"});
|
||||||
|
|
||||||
// mfc
|
// mfc
|
||||||
const std::string mfc("Mahjong Fight Club");
|
const std::string mfc("Mahjong Fight Club");
|
||||||
games.push_back(mfc);
|
games.push_back(mfc);
|
||||||
buttons.insert({ mfc, mfc::get_buttons() });
|
buttons.insert({ mfc, mfc::get_buttons() });
|
||||||
file_hints[mfc].emplace_back("allinone.dll");
|
file_hints[mfc].push_back({"allinone.dll"});
|
||||||
|
|
||||||
// ftt
|
// ftt
|
||||||
const std::string ftt("FutureTomTom");
|
const std::string ftt("FutureTomTom");
|
||||||
@@ -228,21 +232,21 @@ namespace games {
|
|||||||
buttons_help.insert({ ftt, ftt::get_buttons_help() });
|
buttons_help.insert({ ftt, ftt::get_buttons_help() });
|
||||||
analogs.insert({ ftt, ftt::get_analogs() });
|
analogs.insert({ ftt, ftt::get_analogs() });
|
||||||
lights.insert({ ftt, ftt::get_lights() });
|
lights.insert({ ftt, ftt::get_lights() });
|
||||||
file_hints[ftt].emplace_back("arkmmd.dll");
|
file_hints[ftt].push_back({"arkmmd.dll"});
|
||||||
|
|
||||||
// loveplus
|
// loveplus
|
||||||
const std::string loveplus("LovePlus");
|
const std::string loveplus("LovePlus");
|
||||||
games.push_back(loveplus);
|
games.push_back(loveplus);
|
||||||
buttons.insert({ loveplus, loveplus::get_buttons() });
|
buttons.insert({ loveplus, loveplus::get_buttons() });
|
||||||
lights.insert({ loveplus, loveplus::get_lights() });
|
lights.insert({ loveplus, loveplus::get_lights() });
|
||||||
file_hints[loveplus].emplace_back("arkklp.dll");
|
file_hints[loveplus].push_back({"arkklp.dll"});
|
||||||
|
|
||||||
// scotto
|
// scotto
|
||||||
const std::string scotto("Scotto");
|
const std::string scotto("Scotto");
|
||||||
games.push_back(scotto);
|
games.push_back(scotto);
|
||||||
buttons.insert({ scotto, scotto::get_buttons() });
|
buttons.insert({ scotto, scotto::get_buttons() });
|
||||||
lights.insert({ scotto, scotto::get_lights() });
|
lights.insert({ scotto, scotto::get_lights() });
|
||||||
file_hints[scotto].emplace_back("scotto.dll");
|
file_hints[scotto].push_back({"scotto.dll"});
|
||||||
|
|
||||||
// drs
|
// drs
|
||||||
const std::string drs("DANCERUSH");
|
const std::string drs("DANCERUSH");
|
||||||
@@ -250,14 +254,14 @@ namespace games {
|
|||||||
buttons.insert({ drs, drs::get_buttons() });
|
buttons.insert({ drs, drs::get_buttons() });
|
||||||
buttons_help.insert({ drs, drs::get_buttons_help() });
|
buttons_help.insert({ drs, drs::get_buttons_help() });
|
||||||
lights.insert({ drs, drs::get_lights() });
|
lights.insert({ drs, drs::get_lights() });
|
||||||
file_hints[drs].emplace_back("superstep.dll");
|
file_hints[drs].push_back({"superstep.dll"});
|
||||||
|
|
||||||
// otoca
|
// otoca
|
||||||
const std::string otoca("Otoca D'or");
|
const std::string otoca("Otoca D'or");
|
||||||
games.push_back(otoca);
|
games.push_back(otoca);
|
||||||
buttons.insert({ otoca, otoca::get_buttons() });
|
buttons.insert({ otoca, otoca::get_buttons() });
|
||||||
buttons_help.insert({ otoca, otoca::get_buttons_help() });
|
buttons_help.insert({ otoca, otoca::get_buttons_help() });
|
||||||
file_hints[otoca].emplace_back("arkkep.dll");
|
file_hints[otoca].push_back({"arkkep.dll"});
|
||||||
|
|
||||||
// winning eleven
|
// winning eleven
|
||||||
const std::string we("Winning Eleven");
|
const std::string we("Winning Eleven");
|
||||||
@@ -265,27 +269,27 @@ namespace games {
|
|||||||
buttons.insert({ we, we::get_buttons() });
|
buttons.insert({ we, we::get_buttons() });
|
||||||
analogs.insert({ we, we::get_analogs() });
|
analogs.insert({ we, we::get_analogs() });
|
||||||
lights.insert({ we, we::get_lights() });
|
lights.insert({ we, we::get_lights() });
|
||||||
file_hints[we].emplace_back("weac12_bootstrap_release.dll");
|
file_hints[we].push_back({"weac12_bootstrap_release.dll"});
|
||||||
file_hints[we].emplace_back("arknck.dll");
|
file_hints[we].push_back({"arknck.dll"});
|
||||||
|
|
||||||
// silent scope: bone eater
|
// silent scope: bone eater
|
||||||
const std::string silentscope("Silent Scope: Bone Eater");
|
const std::string silentscope("Silent Scope: Bone Eater");
|
||||||
games.push_back(silentscope);
|
games.push_back(silentscope);
|
||||||
buttons.insert({ silentscope, silentscope::get_buttons() });
|
buttons.insert({ silentscope, silentscope::get_buttons() });
|
||||||
analogs.insert({ silentscope, silentscope::get_analogs() });
|
analogs.insert({ silentscope, silentscope::get_analogs() });
|
||||||
file_hints[silentscope].emplace_back("arkndd.dll");
|
file_hints[silentscope].push_back({"arkndd.dll"});
|
||||||
|
|
||||||
// charge machine
|
// charge machine
|
||||||
const std::string pcm("Charge Machine");
|
const std::string pcm("Charge Machine");
|
||||||
games.push_back(pcm);
|
games.push_back(pcm);
|
||||||
buttons.insert({ pcm, pcm::get_buttons() });
|
buttons.insert({ pcm, pcm::get_buttons() });
|
||||||
file_hints[pcm].emplace_back("launch.dll");
|
file_hints[pcm].push_back({"launch.dll"});
|
||||||
|
|
||||||
// ongaku paradise
|
// ongaku paradise
|
||||||
const std::string op("Ongaku Paradise");
|
const std::string op("Ongaku Paradise");
|
||||||
games.push_back(op);
|
games.push_back(op);
|
||||||
buttons.insert({ op, onpara::get_buttons() });
|
buttons.insert({ op, onpara::get_buttons() });
|
||||||
file_hints[op].emplace_back("arkjc9.dll");
|
file_hints[op].push_back({"arkjc9.dll"});
|
||||||
|
|
||||||
// bc
|
// bc
|
||||||
const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
|
const std::string bc("Busou Shinki: Armored Princess Battle Conductor");
|
||||||
@@ -293,7 +297,7 @@ namespace games {
|
|||||||
buttons.insert({ bc, bc::get_buttons() });
|
buttons.insert({ bc, bc::get_buttons() });
|
||||||
buttons_help.insert({ bc, bc::get_buttons_help() });
|
buttons_help.insert({ bc, bc::get_buttons_help() });
|
||||||
analogs.insert({ bc, bc::get_analogs() });
|
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
|
// ccj
|
||||||
const std::string ccj("Chase Chase Jokers");
|
const std::string ccj("Chase Chase Jokers");
|
||||||
@@ -302,21 +306,21 @@ namespace games {
|
|||||||
buttons_help.insert({ ccj, ccj::get_buttons_help() });
|
buttons_help.insert({ ccj, ccj::get_buttons_help() });
|
||||||
analogs.insert({ ccj, ccj::get_analogs() });
|
analogs.insert({ ccj, ccj::get_analogs() });
|
||||||
lights.insert({ ccj, ccj::get_lights() });
|
lights.insert({ ccj, ccj::get_lights() });
|
||||||
file_hints[ccj].emplace_back("game/chaseproject.exe");
|
file_hints[ccj].push_back({"game/chaseproject.exe"});
|
||||||
|
|
||||||
// QuizKnock STADIUM
|
// QuizKnock STADIUM
|
||||||
const std::string qks("QuizKnock STADIUM");
|
const std::string qks("QuizKnock STADIUM");
|
||||||
games.push_back(qks);
|
games.push_back(qks);
|
||||||
buttons.insert({ qks, qks::get_buttons() });
|
buttons.insert({ qks, qks::get_buttons() });
|
||||||
buttons_help.insert({ qks, qks::get_buttons_help() });
|
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
|
// Mahjong Fight Girl
|
||||||
const std::string mfg("Mahjong Fight Girl");
|
const std::string mfg("Mahjong Fight Girl");
|
||||||
games.push_back(mfg);
|
games.push_back(mfg);
|
||||||
buttons.insert({ mfg, mfg::get_buttons() });
|
buttons.insert({ mfg, mfg::get_buttons() });
|
||||||
buttons_help.insert({ mfg, mfg::get_buttons_help() });
|
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
|
// Polaris Chord
|
||||||
const std::string pc("Polaris Chord");
|
const std::string pc("Polaris Chord");
|
||||||
@@ -325,7 +329,7 @@ namespace games {
|
|||||||
buttons_help.insert({ pc, pc::get_buttons_help() });
|
buttons_help.insert({ pc, pc::get_buttons_help() });
|
||||||
analogs.insert({ pc, pc::get_analogs() });
|
analogs.insert({ pc, pc::get_analogs() });
|
||||||
lights.insert({ pc, pc::get_lights() });
|
lights.insert({ pc, pc::get_lights() });
|
||||||
file_hints[pc].emplace_back("game/svm.exe");
|
file_hints[pc].push_back({"game/svm.exe"});
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::string> &get_games() {
|
const std::vector<std::string> &get_games() {
|
||||||
@@ -440,6 +444,8 @@ namespace games {
|
|||||||
// overlay button definitions
|
// overlay button definitions
|
||||||
names.emplace_back("Screenshot");
|
names.emplace_back("Screenshot");
|
||||||
vkey_defaults.push_back(VK_SNAPSHOT);
|
vkey_defaults.push_back(VK_SNAPSHOT);
|
||||||
|
names.emplace_back("Toggle All Windows");
|
||||||
|
vkey_defaults.push_back(VK_OEM_3); // backtick `
|
||||||
names.emplace_back("Toggle Main Menu");
|
names.emplace_back("Toggle Main Menu");
|
||||||
vkey_defaults.push_back(VK_ESCAPE);
|
vkey_defaults.push_back(VK_ESCAPE);
|
||||||
names.emplace_back("Toggle Sub Screen");
|
names.emplace_back("Toggle Sub Screen");
|
||||||
@@ -464,10 +470,12 @@ namespace games {
|
|||||||
vkey_defaults.push_back(VK_F10);
|
vkey_defaults.push_back(VK_F10);
|
||||||
names.emplace_back("Toggle Screen Resize");
|
names.emplace_back("Toggle Screen Resize");
|
||||||
vkey_defaults.push_back(VK_F11);
|
vkey_defaults.push_back(VK_F11);
|
||||||
names.emplace_back("Toggle Overlay");
|
names.emplace_back("Toggle FPS");
|
||||||
vkey_defaults.push_back(VK_F12);
|
vkey_defaults.push_back(VK_F12);
|
||||||
names.emplace_back("Toggle Camera Control");
|
names.emplace_back("Toggle Camera Control");
|
||||||
vkey_defaults.push_back(0xFF);
|
vkey_defaults.push_back(0xFF);
|
||||||
|
names.emplace_back("Toggle OBS Control");
|
||||||
|
vkey_defaults.push_back(0xFF);
|
||||||
names.emplace_back("Player 1 PIN Macro");
|
names.emplace_back("Player 1 PIN Macro");
|
||||||
vkey_defaults.push_back(0xFF);
|
vkey_defaults.push_back(0xFF);
|
||||||
names.emplace_back("Player 2 PIN Macro");
|
names.emplace_back("Player 2 PIN Macro");
|
||||||
@@ -559,7 +567,7 @@ namespace games {
|
|||||||
return &it->second;
|
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();
|
initialize();
|
||||||
auto it = file_hints.find(game);
|
auto it = file_hints.find(game);
|
||||||
if (it == file_hints.end()) {
|
if (it == file_hints.end()) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace games {
|
|||||||
namespace OverlayButtons {
|
namespace OverlayButtons {
|
||||||
enum {
|
enum {
|
||||||
Screenshot,
|
Screenshot,
|
||||||
|
ToggleAllWindows,
|
||||||
ToggleMainMenu,
|
ToggleMainMenu,
|
||||||
ToggleSubScreen,
|
ToggleSubScreen,
|
||||||
InsertCoin,
|
InsertCoin,
|
||||||
@@ -20,8 +21,9 @@ namespace games {
|
|||||||
ToggleControl,
|
ToggleControl,
|
||||||
TogglePatchManager,
|
TogglePatchManager,
|
||||||
ToggleScreenResize,
|
ToggleScreenResize,
|
||||||
ToggleOverlay,
|
ToggleFps,
|
||||||
ToggleCameraControl,
|
ToggleCameraControl,
|
||||||
|
ToggleOBSControl,
|
||||||
TriggerPinMacroP1,
|
TriggerPinMacroP1,
|
||||||
TriggerPinMacroP2,
|
TriggerPinMacroP2,
|
||||||
ScreenResize,
|
ScreenResize,
|
||||||
@@ -64,5 +66,5 @@ namespace games {
|
|||||||
std::vector<Analog> *get_analogs(const std::string &game);
|
std::vector<Analog> *get_analogs(const std::string &game);
|
||||||
std::vector<Light> *get_lights(const std::string &game);
|
std::vector<Light> *get_lights(const std::string &game);
|
||||||
std::vector<Option> *get_options(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);
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-11
@@ -19,7 +19,7 @@
|
|||||||
namespace games::jb {
|
namespace games::jb {
|
||||||
|
|
||||||
// touch stuff
|
// touch stuff
|
||||||
bool TOUCH_LEGACY_BOX = false;
|
JubeatTouchAlgorithm TOUCH_ALGORITHM = Improved;
|
||||||
static bool TOUCH_ENABLE = false;
|
static bool TOUCH_ENABLE = false;
|
||||||
static bool TOUCH_ATTACHED = false;
|
static bool TOUCH_ATTACHED = false;
|
||||||
static bool IS_PORTRAIT = true;
|
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.
|
// note that the IO code in device.cpp will correctly compensate for orientation, depending on the model.
|
||||||
TOUCH_POINTS.clear();
|
TOUCH_POINTS.clear();
|
||||||
touch_get_points(TOUCH_POINTS);
|
touch_get_points(TOUCH_POINTS);
|
||||||
if (TOUCH_LEGACY_BOX) {
|
if (TOUCH_ALGORITHM == Legacy) {
|
||||||
auto offset = IS_PORTRAIT ? 580 : 0;
|
auto offset = IS_PORTRAIT ? 580 : 0;
|
||||||
for (auto &tp : TOUCH_POINTS) {
|
for (auto &tp : TOUCH_POINTS) {
|
||||||
|
|
||||||
@@ -94,6 +94,17 @@ namespace games::jb {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (auto &tp : TOUCH_POINTS) {
|
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 x_relative = tp.x;
|
||||||
int y_relative = tp.y;
|
int y_relative = tp.y;
|
||||||
|
|
||||||
@@ -110,21 +121,68 @@ namespace games::jb {
|
|||||||
y_relative -= 8;
|
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;
|
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
|
// check if the gap was touched
|
||||||
|
if (TOUCH_ALGORITHM == AcAccurate) {
|
||||||
|
// in ac-accurate mode, touching the gap is ignored
|
||||||
if (x_hitbox > JB_BUTTON_SIZE || y_hitbox > JB_BUTTON_SIZE) {
|
if (x_hitbox > JB_BUTTON_SIZE || y_hitbox > JB_BUTTON_SIZE) {
|
||||||
continue;
|
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
|
// set the corresponding state
|
||||||
int index = y_index * 4 + x_index;
|
int index = y_index * 4 + x_index;
|
||||||
if (0 <= index && index < 16) {
|
if (0 <= index && index < 16) {
|
||||||
@@ -167,7 +225,7 @@ namespace games::jb {
|
|||||||
void JBGame::pre_attach() {
|
void JBGame::pre_attach() {
|
||||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||||
const auto current_path = std::filesystem::current_path();
|
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()) {
|
if (current_path.parent_path() == current_path.root_path()) {
|
||||||
log_warning(
|
log_warning(
|
||||||
"jubeat",
|
"jubeat",
|
||||||
@@ -178,7 +236,7 @@ namespace games::jb {
|
|||||||
" c:\\jubeat\\contents\\spice.exe <- OK\n\n"
|
" c:\\jubeat\\contents\\spice.exe <- OK\n\n"
|
||||||
"To fix this, create a new directory and move ALL game files there.\n\n"
|
"To fix this, create a new directory and move ALL game files there.\n\n"
|
||||||
"Your current working directory: {}\n",
|
"Your current working directory: {}\n",
|
||||||
current_path.string());
|
current_path);
|
||||||
|
|
||||||
log_fatal(
|
log_fatal(
|
||||||
"jubeat",
|
"jubeat",
|
||||||
@@ -193,6 +251,21 @@ namespace games::jb {
|
|||||||
// enable touch
|
// enable touch
|
||||||
TOUCH_ENABLE = true;
|
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
|
// enable debug logging of gftools
|
||||||
HMODULE gftools = libutils::try_module("gftools.dll");
|
HMODULE gftools = libutils::try_module("gftools.dll");
|
||||||
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
detour::inline_hook((void *) GFDbgSetReportFunc, libutils::try_proc(
|
||||||
|
|||||||
@@ -4,8 +4,14 @@
|
|||||||
|
|
||||||
namespace games::jb {
|
namespace games::jb {
|
||||||
|
|
||||||
|
enum JubeatTouchAlgorithm {
|
||||||
|
Legacy,
|
||||||
|
Improved,
|
||||||
|
AcAccurate
|
||||||
|
};
|
||||||
|
|
||||||
// touch stuff
|
// touch stuff
|
||||||
extern bool TOUCH_LEGACY_BOX;
|
extern JubeatTouchAlgorithm TOUCH_ALGORITHM;
|
||||||
extern bool TOUCH_STATE[16];
|
extern bool TOUCH_STATE[16];
|
||||||
void touch_update();
|
void touch_update();
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ std::vector<Analog> &games::mga::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
|
analogs = GameAPI::Analogs::getAnalogs("Metal Gear");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using namespace GameAPI::Analogs;
|
||||||
&analogs,
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Joy X",
|
{ "Joy X", AnalogType::LinearCentered },
|
||||||
"Joy Y"
|
{ "Joy Y", AnalogType::LinearCentered }
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return analogs;
|
return analogs;
|
||||||
|
|||||||
@@ -165,37 +165,38 @@ std::vector<Analog> &games::nost::get_analogs() {
|
|||||||
if (analogs.empty()) {
|
if (analogs.empty()) {
|
||||||
analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
|
analogs = GameAPI::Analogs::getAnalogs("Nostalgia");
|
||||||
|
|
||||||
GameAPI::Analogs::sortAnalogs(
|
using namespace GameAPI::Analogs;
|
||||||
&analogs,
|
|
||||||
"Key 1",
|
GameAPI::Analogs::sortAnalogsWithType(&analogs, {
|
||||||
"Key 2",
|
{ "Key 1", AnalogType::LinearPositive },
|
||||||
"Key 3",
|
{ "Key 2", AnalogType::LinearPositive },
|
||||||
"Key 4",
|
{ "Key 3", AnalogType::LinearPositive },
|
||||||
"Key 5",
|
{ "Key 4", AnalogType::LinearPositive },
|
||||||
"Key 6",
|
{ "Key 5", AnalogType::LinearPositive },
|
||||||
"Key 7",
|
{ "Key 6", AnalogType::LinearPositive },
|
||||||
"Key 8",
|
{ "Key 7", AnalogType::LinearPositive },
|
||||||
"Key 9",
|
{ "Key 8", AnalogType::LinearPositive },
|
||||||
"Key 10",
|
{ "Key 9", AnalogType::LinearPositive },
|
||||||
"Key 11",
|
{ "Key 10", AnalogType::LinearPositive },
|
||||||
"Key 12",
|
{ "Key 11", AnalogType::LinearPositive },
|
||||||
"Key 13",
|
{ "Key 12", AnalogType::LinearPositive },
|
||||||
"Key 14",
|
{ "Key 13", AnalogType::LinearPositive },
|
||||||
"Key 15",
|
{ "Key 14", AnalogType::LinearPositive },
|
||||||
"Key 16",
|
{ "Key 15", AnalogType::LinearPositive },
|
||||||
"Key 17",
|
{ "Key 16", AnalogType::LinearPositive },
|
||||||
"Key 18",
|
{ "Key 17", AnalogType::LinearPositive },
|
||||||
"Key 19",
|
{ "Key 18", AnalogType::LinearPositive },
|
||||||
"Key 20",
|
{ "Key 19", AnalogType::LinearPositive },
|
||||||
"Key 21",
|
{ "Key 20", AnalogType::LinearPositive },
|
||||||
"Key 22",
|
{ "Key 21", AnalogType::LinearPositive },
|
||||||
"Key 23",
|
{ "Key 22", AnalogType::LinearPositive },
|
||||||
"Key 24",
|
{ "Key 23", AnalogType::LinearPositive },
|
||||||
"Key 25",
|
{ "Key 24", AnalogType::LinearPositive },
|
||||||
"Key 26",
|
{ "Key 25", AnalogType::LinearPositive },
|
||||||
"Key 27",
|
{ "Key 26", AnalogType::LinearPositive },
|
||||||
"Key 28"
|
{ "Key 27", AnalogType::LinearPositive },
|
||||||
);
|
{ "Key 28", AnalogType::LinearPositive }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return analogs;
|
return analogs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "misc/eamuse.h"
|
#include "misc/eamuse.h"
|
||||||
#include "touch/touch.h"
|
#include "touch/touch.h"
|
||||||
#include "util/logging.h"
|
#include "util/logging.h"
|
||||||
|
#include "util/precise_timer.h"
|
||||||
|
|
||||||
namespace games::nost::poke {
|
namespace games::nost::poke {
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ namespace games::nost::poke {
|
|||||||
// create new thread
|
// create new thread
|
||||||
THREAD_RUNNING = true;
|
THREAD_RUNNING = true;
|
||||||
THREAD = new std::thread([] {
|
THREAD = new std::thread([] {
|
||||||
|
timeutils::PreciseSleepTimer timer;
|
||||||
const DWORD touch_id = (DWORD)(0xFFFFFFFE);
|
const DWORD touch_id = (DWORD)(0xFFFFFFFE);
|
||||||
|
|
||||||
const int swipe_anim_total_frames = 6;
|
const int swipe_anim_total_frames = 6;
|
||||||
@@ -178,7 +180,7 @@ namespace games::nost::poke {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// slow down
|
// slow down
|
||||||
Sleep(30);
|
timer.sleep(30);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user