Align nixAC to spice2x folder structure

This commit is contained in:
2026-08-06 16:03:22 +02:00
parent 3ddd34d51d
commit 64260623bf
1060 changed files with 0 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
bin/**
dist/**
docker/**
cmake-build*
.ccache/**
+879
View File
@@ -0,0 +1,879 @@
cmake_minimum_required(VERSION 3.12)
cmake_policy(SET CMP0069 NEW)
project(spicetools)
include(CheckIPOSupported)
# set language level
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
# niceities for vscode
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
# for RapidJSON
add_compile_definitions(RAPIDJSON_HAS_STDSTRING)
if(MSVC)
# disable intermediate manifest
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /manifest:no")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /manifest:no")
# disable warnings about using non _s variants like strncpy
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
# disable warnings about using deprecated winsock2 functions
add_compile_definitions(_WINSOCK_DEPRECATED_NO_WARNINGS)
# RapidJSON does this
add_compile_definitions(_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING)
# define M_PI
add_compile_definitions(_USE_MATH_DEFINES)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DYNAMICBASE:NO")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DYNAMICBASE:NO")
set(USE_STATIC_MSVCRT ON CACHE BOOL "If enabled, will force the use of static crt instead of dynamic")
if(USE_STATIC_MSVCRT)
# use statically linked runtime
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set(CompilerFlags
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_RELEASE
)
foreach(CompilerFlag ${CompilerFlags})
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()
endif()
# disable C4996 "The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name" warning
add_compile_options("/wd4996")
# cleanup windows.h includes
add_compile_options("/DNOMINMAX")
# cleanup weird types
add_compile_options("/DWINBOOL=BOOL")
# add support for the deprecated std::result_of in c++20
add_compile_options("/D_HAS_DEPRECATED_RESULT_OF")
# enable build paralellization
add_compile_options("/MP")
# enable edit and continue in Debug
add_compile_options("$<$<CONFIG:DEBUG>:/ZI>")
add_link_options("$<$<CONFIG:DEBUG>:/SAFESEH:NO>")
# enable fast pdb generation in debug
add_link_options("$<$<CONFIG:DEBUG>:/DEBUG:FASTLINK>")
# enable pdb generation for release builds
add_compile_options("$<$<CONFIG:RELEASE,MINSIZEREL>:/Zi>")
add_link_options("$<$<CONFIG:RELEASE,MINSIZEREL>:/DEBUG:FULL>")
# enable COMDAT folding for even smaller release builds
add_link_options("$<$<CONFIG:RELEASE,MINSIZEREL>:/OPT:ICF>")
# always use UTF-8 (fix 4819)
add_compile_options("/utf-8")
# spectre mitigation warning
add_compile_options("/wd5045")
# implicit type convert warnings
add_compile_options("/wd4244")
add_compile_options("/wd4267")
add_compile_options("/wd4305")
# unreferenced local variable
add_compile_options("/wd4101")
# warning in winbase.h??
add_compile_options("/wd5039")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# disable warnings about using non _s variants like strncpy
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
# disable warnings about using deprecated winsock2 functions
add_compile_definitions(_WINSOCK_DEPRECATED_NO_WARNINGS)
# RapidJSON does this
add_compile_definitions(_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING)
# warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunknown-warning-option")
# static linking
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
else()
# warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith") # but we love pointer arithmetic :)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas") # since CLion does clang pragmas
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-address") # to allow checking function pointers for null
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-cast-function-type") # we actually do this a lot
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-class-memaccess") # RapidJSON does this
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") # RapidJSON issue
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter") # for all those stubs
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-parameter") # for all those stubs
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-stringop-truncation") # since we do that from time to time
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-local-typedefs") # for our logging system
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes") # fmtlib workaround
# release flags
if(CMAKE_BUILD_TYPE MATCHES "Release")
# hide ident strings
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fno-ident -ffunction-sections -fdata-sections")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-ident -ffunction-sections -fdata-sections")
# a change in the linker caused the executable to be loaded above 4GB base virtual address
# https://github.com/msys2/MINGW-packages/pull/6880
# some games crash if some DLLS load above 4GB VA, so manually set base address to standard 32-bit VA,
# and might as well double make sure ASLR is disabled here
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
# set visibility to hidden
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fvisibility=hidden")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fvisibility=hidden -fvisibility-inlines-hidden")
# remove symbol table and relocation information
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s")
# performance
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2 -pipe")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -pipe")
# ensure frame pointers are enabled
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fno-omit-frame-pointer")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-omit-frame-pointer")
# no debug
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -DNDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
# file prefix map for relative working directory
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
endif()
# release with debug info flags
if(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
# hide ident strings
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-fno-ident -ffunction-sections -fdata-sections")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-fno-ident -ffunction-sections -fdata-sections")
# linker fix to load below 4GB
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
# set visibility to hidden
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -fvisibility=hidden")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fvisibility=hidden -fvisibility-inlines-hidden")
# generate dwarf
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -gdwarf")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -gdwarf")
# ensure frame pointers are enabled
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer")
endif()
# debug flags
if(CMAKE_BUILD_TYPE MATCHES "Debug")
# generate dwarf
set(CMAKE_C_FLAGS_DEBUG "-gdwarf")
set(CMAKE_CXX_FLAGS_DEBUG "-gdwarf")
# linker fix to load below 4GB
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--disable-dynamicbase,--image-base=0x400000,--enable-stdcall-fixup")
# enable debug symbols on level 3 and keep frame pointers
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g3 -fno-omit-frame-pointer")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g3 -fno-omit-frame-pointer")
# optimize for debugging
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og -pipe")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -pipe")
endif()
# static linking
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static -static-libgcc")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static -static-libgcc -static-libstdc++")
endif()
# default defines
add_compile_definitions(
WIN32_LEAN_AND_MEAN
_WIN32_IE=0x0400
)
# acioemu log
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DACIOEMU_LOG")
# add project directory to include path so we can comfortably import
include_directories(${spicetools_SOURCE_DIR} ${spicetools_SOURCE_DIR}/external/imgui)
# add external libraries
add_subdirectory(external/fmt EXCLUDE_FROM_ALL)
add_subdirectory(external/discord-rpc EXCLUDE_FROM_ALL)
add_subdirectory(external/hash-library EXCLUDE_FROM_ALL)
add_subdirectory(external/imgui EXCLUDE_FROM_ALL)
add_subdirectory(external/minhook EXCLUDE_FROM_ALL)
add_subdirectory(external/cpu_features EXCLUDE_FROM_ALL)
# set link time optimizations (disabled for Debug builds for speed, disabled
# for RelWithDebInfo builds due to "lto1: error: two or more sections for"
# errors)
check_ipo_supported()
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUG OFF)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO OFF)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL ON)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
# resources
###########
set_source_files_properties(build/manifest.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/icon.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/manifest.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/blob.rc PROPERTIES LANGUAGE RC)
add_custom_command(
OUTPUT build/dsdmo.i686.dll
COMMAND sh -c "wget -qO- 'https://web.archive.org/web/20260212204718if_/https://download.zip.dll-files.com/d01cab97ad497201c4ad99eb6e4182b3/dsdmo.zip?token=f7cVie5KOSz-hnFUYC37Ow&expires=1770974229' | bsdtar -xvf - -C '${CMAKE_SOURCE_DIR}/build' -s '/dsdmo.dll/dsdmo.i686.dll/' dsdmo.dll"
COMMENT "Fetching dsdmo.dll blob (i686)"
VERBATIM
)
add_custom_command(
OUTPUT build/dsdmo.x86_64.dll
COMMAND sh -c "wget -qO- 'https://web.archive.org/web/20260212203516if_/https://download.zip.dll-files.com/2627c69b89807078f5fdf63ee104dddf/dsdmo.zip?token=1rLcDAA2LFA6YRyinX0GXg&expires=1770973374' | bsdtar -xvf - -C '${CMAKE_SOURCE_DIR}/build' -s '/dsdmo.dll/dsdmo.x86_64.dll/' dsdmo.dll"
COMMENT "Fetching dsdmo.dll blob (x86_64)"
VERBATIM
)
# sources
#########
set(SOURCE_FILES ${SOURCE_FILES}
# acio
acio/acio.cpp
acio/module.cpp
acio/pix/pix.cpp
acio/core/core.cpp
acio/hgth/hgth.cpp
acio/bmpu/bmpu.cpp
acio/hbhi/hbhi.cpp
acio/hdxs/hdxs.cpp
acio/kfca/kfca.cpp
acio/i36g/i36g.cpp
acio/panb/panb.cpp
acio/icca/icca.cpp
acio/j32d/j32d.cpp
acio/bi2a/bi2a.cpp
acio/klpa/klpa.cpp
acio/mdxf/mdxf.cpp
acio/pjei/pjei.cpp
acio/pjec/pjec.cpp
acio/i36i/i36i.cpp
acio/nddb/nddb.cpp
acio/la9a/la9a.cpp
# acioemu
acioemu/acioemu.cpp
acioemu/device.cpp
acioemu/handle.cpp
acioemu/icca.cpp
# acio2emu
acio2emu/handle.cpp
acio2emu/packet.cpp
acio2emu/firmware/bi2x.cpp
# api
api/controller.cpp
api/websocket.cpp
api/request.cpp
api/response.cpp
api/module.cpp
api/modules/card.cpp
api/modules/buttons.cpp
api/modules/capture.cpp
api/modules/analogs.cpp
api/modules/lights.cpp
api/modules/memory.cpp
api/modules/coin.cpp
api/modules/info.cpp
api/modules/keypads.cpp
api/modules/control.cpp
api/modules/touch.cpp
api/modules/iidx.cpp
api/serial.cpp
api/modules/drs.cpp
api/modules/lcd.cpp
api/modules/ddr.cpp
api/modules/resize.cpp
# avs
avs/core.cpp
avs/ea3.cpp
avs/game.cpp
avs/automap.cpp
avs/ssl.cpp
# build
build/defs.cpp
# cfg
cfg/spicecfg.cpp
cfg/analog.cpp
cfg/game.cpp
cfg/button.cpp
cfg/config.cpp
cfg/api.cpp
cfg/option.cpp
cfg/light.cpp
cfg/configurator.cpp
cfg/configurator_wnd.cpp
cfg/screen_resize.cpp
# easrv
easrv/easrv.cpp
easrv/smartea.cpp
# external asio
external/asio/asiolist.cpp
# external cardio
external/cardio/cardio_hid.cpp
external/cardio/cardio_window.cpp
external/cardio/cardio_runner.cpp
# external misc
external/stackwalker/stackwalker.cpp
external/tinyxml2/tinyxml2.cpp
external/http-parser/http_parser.c
external/usbhidusage/usb-hid-usage.c
external/toojpeg/toojpeg.cpp
external/scard/scard.cpp
# games
games/game.cpp
games/io.cpp
games/shared/lcdhandle.cpp
games/shared/printer.cpp
games/shared/twtouch.cpp
games/popn/popn.cpp
games/popn/io.cpp
games/bbc/bbc.cpp
games/bbc/io.cpp
games/hpm/hpm.cpp
games/hpm/io.cpp
games/iidx/iidx.cpp
games/iidx/io.cpp
games/iidx/poke.cpp
games/iidx/bi2a.cpp
games/iidx/bi2x.cpp
games/iidx/bi2x_hook.cpp
games/iidx/ezusb.cpp
games/iidx/legacy_camera.cpp
games/iidx/local_camera.cpp
games/iidx/camera.cpp
games/iidx/mf_wrappers.cpp
games/sdvx/bi2x_hook.cpp
games/sdvx/sdvx.cpp
games/sdvx/io.cpp
games/sdvx/camera.cpp
games/jb/jb.cpp
games/jb/io.cpp
games/nost/nost.cpp
games/nost/io.cpp
games/nost/poke.cpp
games/gitadora/gitadora.cpp
games/gitadora/io.cpp
games/gitadora/handle.cpp
games/gitadora/j32d.cpp
games/gitadora/j33i.cpp
games/gitadora/bi2x_hook.cpp
games/mga/mga.cpp
games/mga/io.cpp
games/mga/gunio.cpp
games/sc/sc.cpp
games/sc/io.cpp
games/rb/rb.cpp
games/rb/io.cpp
games/rb/touch.cpp
games/bs/bs.cpp
games/bs/io.cpp
games/rf3d/rf3d.cpp
games/rf3d/io.cpp
games/museca/io.cpp
games/museca/museca.cpp
games/dea/dea.cpp
games/dea/io.cpp
games/qma/qma.cpp
games/qma/io.cpp games/qma/ezusb.cpp
games/ddr/ddr.cpp
games/ddr/io.cpp
games/ddr/p3io/foot.cpp
games/ddr/p3io/p3io.cpp
games/ddr/p3io/sate.cpp
games/ddr/p3io/usbmem.cpp
games/ddr/p4io/p4io.cpp
games/ddr/p4io/p4io.h
games/mfc/mfc.cpp
games/mfc/io.cpp
games/ftt/ftt.cpp
games/ftt/io.cpp
games/loveplus/loveplus.cpp
games/loveplus/io.cpp
games/scotto/scotto.cpp
games/scotto/io.cpp
games/drs/drs.cpp
games/drs/io.cpp
games/drs/rgb_cam.cpp
games/we/we.cpp
games/we/io.cpp
games/we/touchpanel.cpp
games/shogikai/shogikai.cpp
games/shogikai/io.cpp
games/otoca/otoca.cpp
games/otoca/io.cpp
games/otoca/p4io.cpp
games/silentscope/silentscope.cpp
games/silentscope/io.cpp
games/pcm/pcm.cpp
games/pcm/io.cpp
games/onpara/onpara.cpp
games/onpara/io.cpp
games/onpara/westboard.cpp
games/onpara/touchpanel.cpp
games/bc/bc.cpp
games/bc/io.cpp
games/ccj/ccj.cpp
games/ccj/io.cpp
games/ccj/bi2x_hook.cpp
games/ccj/trackball.cpp
games/qks/qks.cpp
games/qks/io.cpp
games/qks/bi2x_hook.cpp
games/mfg/mfg.cpp
games/mfg/io.cpp
games/mfg/bi2a_hook.cpp
games/pc/pc.cpp
games/pc/io.cpp
games/pc/bi2x_hook.cpp
# hooks
hooks/audio/acm.cpp
hooks/audio/audio.cpp
hooks/audio/buffer.cpp
hooks/audio/mme.cpp
hooks/audio/util.cpp
hooks/audio/backends/dsound/dsound_backend.cpp
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
hooks/audio/backends/mmdevice/device.cpp
hooks/audio/backends/mmdevice/device_collection.cpp
hooks/audio/backends/mmdevice/device_enumerator.cpp
hooks/audio/backends/wasapi/audio_client.cpp
hooks/audio/backends/wasapi/audio_render_client.cpp
hooks/audio/backends/wasapi/dummy_audio_client.cpp
hooks/audio/backends/wasapi/dummy_audio_clock.cpp
hooks/audio/backends/wasapi/dummy_audio_render_client.cpp
hooks/audio/backends/wasapi/dummy_audio_session_control.cpp
hooks/audio/backends/wasapi/low_latency_client.cpp
hooks/audio/backends/wasapi/util.cpp
hooks/audio/implementations/asio.cpp
hooks/audio/implementations/wave_out.cpp
hooks/audio/implementations/none.cpp
hooks/audio/implementations/pipewire.cpp
hooks/develhook.cpp
hooks/sndbhook.cpp
hooks/wmischook.cpp
hooks/avshook.cpp
hooks/cfgmgr32hook.cpp
hooks/debughook.cpp
hooks/devicehook.cpp
hooks/graphics/graphics.cpp
hooks/graphics/graphics_windowed.cpp
hooks/graphics/nvapi_hook.cpp
hooks/graphics/nvenc_hook.cpp
hooks/graphics/backends/d3d9/d3d9_backend.cpp
hooks/graphics/backends/d3d9/d3d9_device.cpp
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp
hooks/graphics/backends/d3d9/d3d9_swapchain.cpp
hooks/graphics/backends/d3d9/d3d9_texture.cpp
hooks/input/dinput8/fake_backend.cpp
hooks/input/dinput8/fake_device.cpp
hooks/input/dinput8/hook.cpp
hooks/lang.cpp
hooks/libraryhook.cpp
hooks/networkhook.cpp
hooks/powrprof.cpp
#hooks/rom.cpp
hooks/setupapihook.cpp
hooks/sleephook.cpp
hooks/unisintrhook.cpp
hooks/winuser.cpp
# launcher
launcher/launcher.cpp
launcher/signal.cpp
launcher/superexit.cpp
launcher/logger.cpp
launcher/richpresence.cpp
launcher/shutdown.cpp
launcher/options.cpp
# misc
misc/bt5api.cpp
misc/clipboard.cpp
misc/device.cpp
misc/eamuse.cpp
misc/extdev.cpp
misc/nativetouchhook.cpp
misc/sciunit.cpp
misc/sde.cpp
misc/wintouchemu.cpp
misc/ami2000.cpp
# nvapi
nvapi/nvapi.cpp
# overlay
overlay/overlay.cpp
overlay/window.cpp
overlay/imgui/extensions.cpp
overlay/imgui/impl_spice.cpp
overlay/imgui/impl_sw.cpp
overlay/windows/acio_status_buffers.cpp
overlay/windows/camera_control.cpp
overlay/windows/card_manager.cpp
overlay/windows/drs_dancefloor.cpp
overlay/windows/gfdm_sub.cpp
overlay/windows/screen_resize.cpp
overlay/windows/sdvx_sub.cpp
overlay/windows/config.cpp
overlay/windows/control.cpp
overlay/windows/eadev.cpp
overlay/windows/fps.cpp
overlay/windows/generic_sub.cpp
overlay/windows/iidx_seg.cpp
overlay/windows/iidx_sub.cpp
overlay/windows/iopanel.cpp
overlay/windows/iopanel_ddr.cpp
overlay/windows/iopanel_gfdm.cpp
overlay/windows/iopanel_iidx.cpp
overlay/windows/keypad.cpp
overlay/windows/log.cpp
overlay/windows/midi.cpp
overlay/windows/patch_manager.cpp
overlay/windows/wnd_manager.cpp
# rawinput
rawinput/rawinput.cpp
rawinput/sextet.cpp
rawinput/piuio.cpp
rawinput/touch.cpp
rawinput/hotplug.cpp
rawinput/smx.cpp
rawinput/smx.h
rawinput/smxstage.cpp
rawinput/smxstage.h
rawinput/smxdedicab.cpp
rawinput/smxdedicab.h
# reader
reader/reader.cpp
reader/message.cpp
reader/structuredmessage.cpp
reader/crypt.cpp
# stubs
stubs/stubs.cpp
# touch
touch/touch.cpp
touch/touch_indicators.cpp
touch/win7.cpp
touch/win8.cpp
# util
util/sigscan.cpp
util/detour.cpp
util/logging.cpp
util/detour.cpp
util/peb.cpp
util/libutils.cpp
util/fileutils.cpp
util/resutils.cpp
util/unity_player.cpp
util/utils.cpp
util/memutils.cpp
util/rc4.cpp
util/crypt.cpp
util/time.cpp
util/cpuutils.cpp
util/netutils.cpp
util/sysutils.cpp
util/lz77.cpp
util/tapeled.cpp
util/execexe.cpp
util/dependencies.cpp
util/deferlog.cpp
util/socd_cleaner.cpp
)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
# spice.exe
###########
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc build/blob.rc build/dsdmo.i686.dll)
add_executable(spicetools_spice ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice
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)
set_target_properties(spicetools_spice PROPERTIES PREFIX "")
set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice")
IF(NOT MSVC)
set_target_properties(spicetools_spice PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# spice_laa.exe
###########
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc build/blob.rc build/dsdmo.i686.dll)
add_executable(spicetools_spice_laa ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice_laa
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)
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
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)
set_target_properties(spicetools_spice_laa PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32 -Wl,--large-address-aware")
endif()
# spice_linux.exe
###########
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice_linux ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice_linux
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)
set_target_properties(spicetools_spice_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_spice_linux PROPERTIES OUTPUT_NAME "spice_linux")
target_compile_definitions(spicetools_spice_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
IF(NOT MSVC)
set_target_properties(spicetools_spice_linux PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# spice64.exe
#############
set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg/Win32D.rc build/blob.rc build/dsdmo.x86_64.dll)
add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
target_link_libraries(spicetools_spice64
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_spice64 PROPERTIES PREFIX "")
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
IF(NOT MSVC)
set_target_properties(spicetools_spice64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# spice64_linux.exe
#############
set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice64_linux ${SOURCE_FILES} ${RESOURCE_FILES})
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
target_link_libraries(spicetools_spice64_linux
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "")
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 NO_SCARD=1 PRIVATE SPICE_LINUX=1)
IF(NOT MSVC)
set_target_properties(spicetools_spice64_linux PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# spicecfg.exe
##############
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)
add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_cfg
PUBLIC 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)
set_target_properties(spicetools_cfg PROPERTIES PREFIX "")
set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg")
target_compile_definitions(spicetools_cfg PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1)
if(NOT MSVC)
set_target_properties(spicetools_cfg PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# spicecfg_linux.exe
##############
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)
add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_cfg_linux
PUBLIC 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)
set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux")
target_compile_definitions(spicetools_cfg_linux PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1)
target_compile_definitions(spicetools_cfg_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
if(NOT MSVC)
set_target_properties(spicetools_cfg_linux PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# stubs
#######
# kbt.dll
set(SOURCE_FILES stubs/stubs.cpp)
add_library(spicetools_stubs_kbt SHARED ${SOURCE_FILES} stubs/stubs.def)
target_link_libraries(spicetools_stubs_kbt PRIVATE fmt::fmt-header-only)
set_target_properties(spicetools_stubs_kbt PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_kbt PROPERTIES OUTPUT_NAME "kbt")
target_compile_definitions(spicetools_stubs_kbt PRIVATE STUB=1)
if(NOT MSVC)
set_target_properties(spicetools_stubs_kbt PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# kbt.dll 64bit
add_library(spicetools_stubs_kbt64 SHARED ${SOURCE_FILES} stubs/stubs.def)
target_link_libraries(spicetools_stubs_kbt64 PRIVATE fmt::fmt-header-only)
set_target_properties(spicetools_stubs_kbt64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_kbt64 PROPERTIES OUTPUT_NAME "kbt")
target_compile_definitions(spicetools_stubs_kbt64 PRIVATE STUB=1)
if(NOT MSVC)
set_target_properties(spicetools_stubs_kbt64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# kld.dll
set(SOURCE_FILES stubs/stubs.cpp)
add_library(spicetools_stubs_kld SHARED ${SOURCE_FILES} stubs/stubs.def)
target_link_libraries(spicetools_stubs_kld PRIVATE fmt::fmt-header-only)
set_target_properties(spicetools_stubs_kld PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_kld PROPERTIES OUTPUT_NAME "kld")
target_compile_definitions(spicetools_stubs_kld PRIVATE STUB=1)
if(NOT MSVC)
set_target_properties(spicetools_stubs_kld PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# kld.dll 64bit
add_library(spicetools_stubs_kld64 SHARED ${SOURCE_FILES} stubs/stubs.def)
target_link_libraries(spicetools_stubs_kld64 PRIVATE fmt::fmt-header-only)
set_target_properties(spicetools_stubs_kld64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_kld64 PROPERTIES OUTPUT_NAME "kld")
target_compile_definitions(spicetools_stubs_kld64 PRIVATE STUB=1)
if(NOT MSVC)
set_target_properties(spicetools_stubs_kld64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# nvcuda.dll
set(SOURCE_FILES stubs/nvcuda.cpp)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} stubs/nvcuda.def)
set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
if(NOT MSVC)
set_target_properties(spicetools_stubs_nvcuda PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# nvcuvid.dll
set(SOURCE_FILES stubs/nvcuvid.cpp)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} stubs/nvcuvid.def)
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
if(NOT MSVC)
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# nvEncodeAPI64.dll
set(SOURCE_FILES stubs/nvEncodeAPI64.cpp)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} stubs/nvEncodeAPI64.def)
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
if(NOT MSVC)
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
endif()
# cpusbxpkm.dll (32 bit)
set(SOURCE_FILES stubs/cpusbxpkm.cpp)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} stubs/cpusbxpkm.def)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
if(NOT MSVC)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
# output directories
####################
# output config
set_target_properties(spicetools_cfg spicetools_cfg_linux
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
# output 32bit
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
# output 64bit
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64")
+7
View File
@@ -0,0 +1,7 @@
FROM spicetools/deps
WORKDIR /src
COPY --from=gitroot . /src/.git
COPY . /src/src/spice2x
WORKDIR /src/src/spice2x
ENTRYPOINT ["./build_all.sh"]
+144
View File
@@ -0,0 +1,144 @@
#include "acio.h"
#include <fstream>
#include <iostream>
#include <windows.h>
#include "avs/game.h"
#include "cfg/config.h"
#include "cfg/api.h"
#include "hooks/libraryhook.h"
#include "misc/eamuse.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
#include "bi2a/bi2a.h"
#include "bmpu/bmpu.h"
#include "core/core.h"
#include "hbhi/hbhi.h"
#include "hdxs/hdxs.h"
#include "hgth/hgth.h"
#include "i36g/i36g.h"
#include "i36i/i36i.h"
#include "icca/icca.h"
#include "j32d/j32d.h"
#include "kfca/kfca.h"
#include "klpa/klpa.h"
#include "mdxf/mdxf.h"
#include "nddb/nddb.h"
#include "panb/panb.h"
#include "pix/pix.h"
#include "pjec/pjec.h"
#include "pjei/pjei.h"
#include "la9a/la9a.h"
#include "module.h"
// globals
namespace acio {
HINSTANCE DLL_INSTANCE = nullptr;
std::vector<acio::ACIOModule *> MODULES;
std::atomic<bool> IO_INIT_IN_PROGRESS = false;
}
/*
* decide on hook mode used
* libacio compiled using ICC64 sometimes doesn't leave enough space to insert the inline hooks
* in this case, we want to use IAT instead
*/
static inline acio::HookMode get_hookmode() {
#ifdef SPICE64
return acio::HookMode::IAT;
#else
return acio::HookMode::INLINE;
#endif
}
void acio::attach() {
log_info("acio", "SpiceTools ACIO");
IO_INIT_IN_PROGRESS = true;
// load settings and instance
acio::DLL_INSTANCE = LoadLibraryA("libacio.dll");
/*
* library hook
* some games have a second DLL laying around which gets loaded dynamically
* we just give it the same instance as the normal one so the hooks still work
*/
libraryhook_hook_library("libacioex.dll", acio::DLL_INSTANCE);
libraryhook_hook_library("libacio_ex.dll", acio::DLL_INSTANCE);
libraryhook_hook_library("libacio_old.dll", acio::DLL_INSTANCE);
// libacioEx.dll for Road Fighters 3D
// needed as comparisons in LoadLibrary hooks are case-sensitive
libraryhook_hook_library("libacioEx.dll", acio::DLL_INSTANCE);
libraryhook_enable(avs::game::DLL_INSTANCE);
// get hook mode
acio::HookMode hook_mode = get_hookmode();
// load modules
MODULES.push_back(new acio::BI2AModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::BMPUModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::CoreModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::HBHIModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::HDXSModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::HGTHModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::I36GModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::I36IModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::ICCAModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::J32DModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::KFCAModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::KLPAModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::MDXFModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::NDDBModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::PANBModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::PJECModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::PJEIModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::LA9AModule(acio::DLL_INSTANCE, hook_mode));
/*
* PIX is special and needs another DLL.
* we load that module only if the file exists.
*/
if (fileutils::file_exists(MODULE_PATH / "libacio_pix.dll")) {
HINSTANCE pix_instance = libutils::load_library(MODULE_PATH / "libacio_pix.dll");
MODULES.push_back(new acio::PIXModule(pix_instance, hook_mode));
}
// apply modules
for (auto &module : MODULES) {
module->attach();
}
IO_INIT_IN_PROGRESS = false;
}
void acio::attach_icca() {
log_info("acio", "SpiceTools ACIO ICCA");
// load instance if needed
if (!acio::DLL_INSTANCE) {
acio::DLL_INSTANCE = LoadLibraryA("libacio.dll");
}
// get hook mode
acio::HookMode hook_mode = get_hookmode();
// load single module
auto icca_module = new acio::ICCAModule(acio::DLL_INSTANCE, hook_mode);
icca_module->attach();
MODULES.push_back(icca_module);
}
void acio::detach() {
// clear modules
while (!MODULES.empty()) {
delete MODULES.back();
MODULES.pop_back();
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <atomic>
#include <vector>
#include <windows.h>
#include "module.h"
namespace acio {
// globals
extern HINSTANCE DLL_INSTANCE;
extern std::vector<acio::ACIOModule *> MODULES;
extern std::atomic<bool> IO_INIT_IN_PROGRESS;
void attach();
void attach_icca();
void detach();
}
+821
View File
@@ -0,0 +1,821 @@
#include "bi2a.h"
#include "avs/game.h"
#include "games/ddr/io.h"
#include "games/ddr/ddr.h"
#include "games/sdvx/sdvx.h"
#include "games/sdvx/io.h"
#include "games/drs/io.h"
#include "games/drs/drs.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
#include "util/tapeled.h"
using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// state
static uint8_t STATUS_BUFFER[272] {};
static bool STATUS_BUFFER_FREEZE = false;
static unsigned int BI2A_VOLL = 0;
static unsigned int BI2A_VOLR = 0;
static bool __cdecl ac_io_bi2a_init_is_finished() {
return true;
}
static bool __cdecl ac_io_bi2a_get_control_status_buffer(void *buffer) {
// copy buffer
memcpy(buffer, STATUS_BUFFER, std::size(STATUS_BUFFER));
return true;
}
static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// Sound Voltex
if (avs::game::is_model("KFC")) {
// clear buffer
memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
STATUS_BUFFER[0] = 1;
/*
* Unmapped Buttons
*
* Control Bit
* EX BUTTON 1 93
* EX BUTTON 2 92
* EX ANALOG 1 170-183
* EX ANALOG 2 186-199
*/
// get buttons
auto &buttons = games::sdvx::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Test))) {
ARRAY_SETB(STATUS_BUFFER, 19);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Service))) {
ARRAY_SETB(STATUS_BUFFER, 18);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::CoinMech))) {
ARRAY_SETB(STATUS_BUFFER, 17);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Start))) {
ARRAY_SETB(STATUS_BUFFER, 85);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_A))) {
ARRAY_SETB(STATUS_BUFFER, 84);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_B))) {
ARRAY_SETB(STATUS_BUFFER, 83);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_C))) {
ARRAY_SETB(STATUS_BUFFER, 82);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_D))) {
ARRAY_SETB(STATUS_BUFFER, 81);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_L))) {
ARRAY_SETB(STATUS_BUFFER, 80);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_R))) {
ARRAY_SETB(STATUS_BUFFER, 95);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Headphone))) {
ARRAY_SETB(STATUS_BUFFER, 87);
}
// volume left
const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_l_state == socd::SocdCW) {
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_r_state == socd::SocdCW) {
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// update volumes
auto &analogs = games::sdvx::get_analogs();
auto vol_left = BI2A_VOLL;
auto vol_right = BI2A_VOLR;
if (analogs.at(0).isSet() || analogs.at(1).isSet()) {
vol_left += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_L)) * 1023.99f);
vol_right += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_R)) * 1023.99f);
}
// proper loops
vol_left %= 1024;
vol_right %= 1024;
// save volumes in buffer
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
log_debug(
"bi2a",
"knobs = {} {}",
*((uint16_t*) &STATUS_BUFFER[17]),
*((uint16_t*) &STATUS_BUFFER[19]));
}
// DanceDanceRevolution
if (avs::game::is_model("MDX")) {
// clear buffer
memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
STATUS_BUFFER[0] = 1;
// get buttons
auto &buttons = games::ddr::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::COIN_MECH)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[2] |= 1 << 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::SERVICE)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[2] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::TEST)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[2] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_START)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 7;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_UP)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 6;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_DOWN)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_LEFT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_RIGHT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_START)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_UP)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_DOWN)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_LEFT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_RIGHT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 1;
}
}
// DANCERUSH
if (avs::game::is_model("REC")) {
// clear buffer
memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
STATUS_BUFFER[0] = 1;
// get buttons
auto &buttons = games::drs::get_buttons();
// test
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::Test)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 19);
}
// service
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::Service)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 18);
}
// coin
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::CoinMech)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 17);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Start)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 87);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Up)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 86);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Down)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 85);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Left)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 84);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Right)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 83);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Start)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 93);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Up)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 92);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Down)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 91);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Left)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 90);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Right)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 89);
}
}
return true;
}
static bool __cdecl ac_io_bi2a_current_coinstock(size_t index, DWORD *coins) {
// check index
if (index > 1)
return false;
// get coins and return success
*coins = (DWORD) eamuse_coin_get_stock();
return true;
}
static bool __cdecl ac_io_bi2a_consume_coinstock(size_t index, int amount) {
// check index
if (index > 1)
return false;
// calculate new stock
auto stock = eamuse_coin_get_stock();
auto stock_new = stock - amount;
// check new stock
if (stock_new < 0)
return false;
// apply new stock
eamuse_coin_set_stock(stock_new);
return true;
}
static bool __cdecl ac_io_bi2a_lock_coincounter(size_t index) {
// check index
if (index > 1)
return false;
// enable coin blocker
eamuse_coin_set_block(true);
return true;
}
static bool __cdecl ac_io_bi2a_unlock_coincounter(size_t index) {
// check index
if (index > 1)
return false;
// disable coin blocker
eamuse_coin_set_block(false);
return true;
}
static void __cdecl ac_io_bi2a_control_coin_blocker_close(size_t index) {
// check index
if (index > 1)
return;
// enable coin blocker
eamuse_coin_set_block(true);
}
static void __cdecl ac_io_bi2a_control_coin_blocker_open(size_t index) {
// check index
if (index > 1)
return;
// disable coin blocker
eamuse_coin_set_block(false);
}
static long __cdecl ac_io_bi2a_control_led_bright(size_t index, uint8_t brightness) {
// Sound Voltex
if (avs::game::is_model("KFC")) {
/*
* Control R G B
* =======================
* WING UP 28 29 30
* WING LOW 31 32 33
* WOOFER 0 1 3
* CONTROLLER 4 5 6
*
* Values go up to 255.
*
*
* Control Index
* ==================
* START BUTTON 8
* A BUTTON 9
* B BUTTON 10
* C BUTTON 11
* D BUTTON 12
* FX L BUTTON 13
* FX R BUTTON 14
* POP 24
* TITLE LEFT 25
* TITLE RIGHT 26
*
* Values go up to 127.
*/
static const struct {
int light1, light2;
float max;
} mapping[] = {
{ games::sdvx::Lights::WOOFER_R, -1, 255 },
{ games::sdvx::Lights::WOOFER_G, -1, 255 },
{ -1, -1, 0 },
{ games::sdvx::Lights::WOOFER_B, -1, 255 },
{ games::sdvx::Lights::CONTROLLER_R, -1, 255 },
{ games::sdvx::Lights::CONTROLLER_G, -1, 255 },
{ games::sdvx::Lights::CONTROLLER_B, -1, 255 },
{ -1, -1, 0 },
{ games::sdvx::Lights::START, -1, 127 },
{ games::sdvx::Lights::BT_A, -1, 127 },
{ games::sdvx::Lights::BT_B, -1, 127 },
{ games::sdvx::Lights::BT_C, -1, 127 },
{ games::sdvx::Lights::BT_D, -1, 127 },
{ games::sdvx::Lights::FX_L, -1, 127 },
{ games::sdvx::Lights::FX_R, -1, 127 },
{ -1, -1, 0 }, { -1, -1, 0 }, { -1, -1, 0 },
{ games::sdvx::Lights::GENERATOR_R, -1, 255 },
{ games::sdvx::Lights::GENERATOR_G, -1, 255 },
{ games::sdvx::Lights::GENERATOR_B, -1, 255 },
{ -1, -1, 0 }, { -1, -1, 0 }, { -1, -1, 0 },
{ games::sdvx::Lights::POP, -1, 127 },
{ games::sdvx::Lights::TITLE_LEFT, -1, 127 },
{ games::sdvx::Lights::TITLE_RIGHT, -1, 127 },
{ -1, -1, 0 },
{ games::sdvx::Lights::WING_RIGHT_UP_R, games::sdvx::Lights::WING_LEFT_UP_R, 255 },
{ games::sdvx::Lights::WING_RIGHT_UP_G, games::sdvx::Lights::WING_LEFT_UP_G, 255 },
{ games::sdvx::Lights::WING_RIGHT_UP_B, games::sdvx::Lights::WING_LEFT_UP_B, 255 },
{ games::sdvx::Lights::WING_RIGHT_LOW_R, games::sdvx::Lights::WING_LEFT_LOW_R, 255 },
{ games::sdvx::Lights::WING_RIGHT_LOW_G, games::sdvx::Lights::WING_LEFT_LOW_G, 255 },
{ games::sdvx::Lights::WING_RIGHT_LOW_B, games::sdvx::Lights::WING_LEFT_LOW_B, 255 },
};
// ignore index out of range
if (index > std::size(mapping)) {
return true;
}
// get lights
auto &lights = games::sdvx::get_lights();
// get light from mapping
auto light = mapping[index];
// write lights
if (light.light1 >= 0) {
Lights::writeLight(RI_MGR, lights[light.light1], brightness / light.max);
} else {
log_warning("sdvx", "light unset {} {}", index, (int) brightness);
}
if (light.light2 >= 0) {
Lights::writeLight(RI_MGR, lights[light.light2], brightness / light.max);
}
// DANCERUSH
} else if (avs::game::is_model("REC")) {
/*
* Control R G B
* ==============================
* CARD UNIT 13 14 15
* TITLE PANEL 28 29 30
* MONITOR SIDE LEFT (tape LED - see ac_io_bi2a_control_tapeled_bright)
* MONITOR SIDE RIGHT (tape LED - see ac_io_bi2a_control_tapeled_bright)
*
* Values go up to 127.
*
* Control Index
* ==================
* 1P LEFT 11
* 1P RIGHT 12
* 1P UP 9
* 1P DOWN 10
* 1P START 8
* 2P LEFT 19
* 2P RIGHT 20
* 2P UP 17
* 2P DOWN 18
* 2P START 16
*
* Values go up to 127.
*/
static const struct {
int light;
float max;
} mapping[] = {
{ -1, 0 }, // 0
{ -1, 0 }, // 1
{ -1, 0 }, // 2
{ -1, 0 }, // 3
{ -1, 0 }, // 4
{ -1, 0 }, // 5
{ -1, 0 }, // 6
{ -1, 0 }, // 7
{ games::drs::Lights::P1_START, 127 }, // 8
{ games::drs::Lights::P1_MENU_UP, 127 }, // 9
{ games::drs::Lights::P1_MENU_DOWN, 127 }, // 10
{ games::drs::Lights::P1_MENU_LEFT, 127 }, // 11
{ games::drs::Lights::P1_MENU_RIGHT, 127 }, // 12
{ games::drs::Lights::CARD_READER_R, 127 }, // 13
{ games::drs::Lights::CARD_READER_G, 127 }, // 14
{ games::drs::Lights::CARD_READER_B, 127 }, // 15
{ games::drs::Lights::P2_START, 127 }, // 16
{ games::drs::Lights::P2_MENU_UP, 127 }, // 17
{ games::drs::Lights::P2_MENU_DOWN, 127 }, // 18
{ games::drs::Lights::P2_MENU_LEFT, 127 }, // 19
{ games::drs::Lights::P2_MENU_RIGHT, 127 }, // 20
{ -1, 0 }, // 21
{ -1, 0 }, // 22
{ -1, 0 }, // 23
{ -1, 0 }, // 24
{ -1, 0 }, // 25
{ -1, 0 }, // 26
{ -1, 0 }, // 27
{ games::drs::Lights::TITLE_PANEL_R, 127 }, // 28
{ games::drs::Lights::TITLE_PANEL_G, 127 }, // 29
{ games::drs::Lights::TITLE_PANEL_B, 127 }, // 30
};
// ignore index out of range
if (index > std::size(mapping)) {
return true;
}
// get lights
auto &lights = games::drs::get_lights();
// get light from mapping
auto light = mapping[index];
// write lights
if (light.light >= 0) {
Lights::writeLight(RI_MGR, lights[light.light], brightness / light.max);
} else {
log_warning("drs", "light unset {} {}", index, (int) brightness);
}
// DanceDanceRevolution
} else if (avs::game::is_model("MDX")) {
static const struct {
int light;
float max;
} mapping[] = {
{ -1, 0 }, // 0
{ -1, 0 }, // 1
{ -1, 0 }, // 2
{ -1, 0 }, // 3
{ -1, 0 }, // 4
{ -1, 0 }, // 5
{ -1, 0 }, // 6
{ -1, 0 }, // 7
{ games::ddr::Lights::GOLD_P1_MENU_START, 127 }, // 8
{ games::ddr::Lights::GOLD_P1_MENU_UP, 127 }, // 9
{ games::ddr::Lights::GOLD_P1_MENU_DOWN, 127 }, // 10
{ games::ddr::Lights::GOLD_P1_MENU_LEFT, 127 }, // 11
{ games::ddr::Lights::GOLD_P1_MENU_RIGHT, 127 }, // 12
{ games::ddr::Lights::GOLD_P1_CARD_UNIT_R, 127 }, // 13
{ games::ddr::Lights::GOLD_P1_CARD_UNIT_G, 127 }, // 14
{ games::ddr::Lights::GOLD_P1_CARD_UNIT_B, 127 }, // 15
{ games::ddr::Lights::GOLD_P2_MENU_START, 127 }, // 16
{ games::ddr::Lights::GOLD_P2_MENU_UP, 127 }, // 17
{ games::ddr::Lights::GOLD_P2_MENU_DOWN, 127 }, // 18
{ games::ddr::Lights::GOLD_P2_MENU_LEFT, 127 }, // 19
{ games::ddr::Lights::GOLD_P2_MENU_RIGHT, 127 }, // 20
{ games::ddr::Lights::GOLD_P2_CARD_UNIT_R, 0 }, // 21
{ games::ddr::Lights::GOLD_P2_CARD_UNIT_G, 0 }, // 22
{ games::ddr::Lights::GOLD_P2_CARD_UNIT_B, 0 }, // 23
{ -1, 0 }, // 24
{ -1, 0 }, // 25
{ -1, 0 }, // 26
{ -1, 0 }, // 27
{ games::ddr::Lights::GOLD_TITLE_PANEL_LEFT, 0 }, // 28
{ games::ddr::Lights::GOLD_TITLE_PANEL_CENTER, 0 }, // 29
{ games::ddr::Lights::GOLD_TITLE_PANEL_RIGHT, 0 }, // 30
{ games::ddr::Lights::GOLD_P1_WOOFER_CORNER, 0 }, // 31
{ games::ddr::Lights::GOLD_P2_WOOFER_CORNER, 0 } // 32
};
// ignore index out of range
if (index > std::size(mapping)) {
return true;
}
// get lights
auto &lights = games::ddr::get_lights();
// get light from mapping
auto light = mapping[index];
// write lights
if (light.light >= 0) {
Lights::writeLight(RI_MGR, lights[light.light], brightness / light.max);
}
}
// return success
return true;
}
static long __cdecl ac_io_bi2a_get_watchdog_time_min() {
return -1;
}
static long __cdecl ac_io_bi2a_get_watchdog_time_now() {
return -1;
}
static void __cdecl ac_io_bi2a_watchdog_off() {
}
static bool __cdecl ac_io_bi2a_init(uint8_t param) {
return true;
}
static bool __cdecl ac_io_bi2a_set_watchdog_time(uint16_t time) {
return true;
}
static bool __cdecl ac_io_bi2a_get_watchdog_status() {
return true;
}
static bool __cdecl ac_io_bi2a_set_amp_volume(uint8_t a1, uint8_t a2) {
return true;
}
static bool __cdecl ac_io_bi2a_tapeled_init(uint8_t a1, uint8_t a2) {
return true;
}
static bool __cdecl ac_io_bi2a_tapeled_init_is_finished() {
return true;
}
static bool __cdecl ac_io_bi2a_control_tapeled_rec_set(uint8_t* data, size_t x_sz, size_t y_sz) {
// check dimensions
if (x_sz != DRS_TAPELED_COLS || y_sz != DRS_TAPELED_ROWS) {
log_fatal("drs", "DRS tapeled wrong dimensions");
}
// copy data into our buffer - 4 bytes per pixel BGR
for (size_t i = 0; i < x_sz * y_sz; i++) {
games::drs::DRS_TAPELED[i][0] = data[i*4+2];
games::drs::DRS_TAPELED[i][1] = data[i*4+1];
games::drs::DRS_TAPELED[i][2] = data[i*4];
}
// success
return true;
}
// TODO: DRS tape lights
static bool __cdecl ac_io_bi2a_control_tapeled_bright(size_t off1, size_t off2,
uint8_t r, uint8_t g, uint8_t b, uint8_t bank) {
if (!tapeledutils::is_enabled()) {
return true;
}
if (avs::game::is_model("MDX")) {
/*
* r, g, b values range from [0-255]
* bank always seems to be [0]
*
* [off1.off2] [LEDs] [tape name]
* 0.0 25 P1 Foot Up (0.0 to 0.24, inclusive)
* 0.25 25 P1 Foot Right
* 1.0 25 P1 Foot Left
* 1.25 25 P1 Foot Down
*
* 2.0 25 P2 Foot Up
* 2.25 25 P2 Foot Right
* 3.0 25 P2 Foot Left
* 3.25 25 P2 Foot Down
*
* 5.0 50 Top Panel
* 6.0 50 Monitor side left
* 7.0 50 Monitor side right
*/
// In order to set the data that can be output via Spice API, we first
// need to figure out which device in our buffers this data belongs to
int device = -1;
if (off1 == 0) {
if (off2 < 25) device = 0; // P1 Foot Up
else device = 1; // P1 Foot Right
} else if (off1 == 1) {
if (off2 < 25) device = 2; // P1 Foot Left
else device = 3; // P1 Foot Down
} else if (off1 == 2) {
if (off2 < 25) device = 4; // P2 Foot Up
else device = 5; // P2 Foot Right
} else if (off1 == 3) {
if (off2 < 25) device = 6; // P2 Foot Left
else device = 7; // P2 Foot Down
} else if (off1 >= 5 && off1 <= 7) {
device = off1 + 3; // Top Panel / Monitor Side Left / Monitor Side Right
}
if (device != -1) {
// We subtract 25 from off2 to get the device's LED index, if it's for one of the
// arrow panels that is on the latter half of the logical strip
size_t subtractor = 0;
if (off1 <= 3 && off2 >= 25) {
subtractor = 25;
}
size_t led_index = off2 - subtractor;
games::ddr::DDR_TAPELEDS[device][led_index][0] = r;
games::ddr::DDR_TAPELEDS[device][led_index][1] = g;
games::ddr::DDR_TAPELEDS[device][led_index][2] = b;
}
static struct TapeLedMapping {
bool split; // true == 50 LEDs for one light, false == 25 for two lights
uint8_t index_r0, index_g0, index_b0;
uint8_t index_r1, index_g1, index_b1;
size_t index_for_avg0 = UINT8_MAX;
size_t index_for_avg1 = UINT8_MAX;
TapeLedMapping(
uint8_t index_r0, uint8_t index_g0, uint8_t index_b0,
uint8_t index_r1, uint8_t index_g1, uint8_t index_b1)
: index_r0(index_r0), index_g0(index_g0), index_b0(index_b0),
index_r1(index_r1), index_g1(index_g1), index_b1(index_b1) {
split = (index_r1 != UINT8_MAX);
if (split) {
index_for_avg0 = tapeledutils::get_led_index_using_avg_algo(25);
index_for_avg1 = index_for_avg0 + 25;
} else {
index_for_avg0 = tapeledutils::get_led_index_using_avg_algo(50);
index_for_avg1 = -1;
}
}
} mapping[] = {
{
games::ddr::Lights::GOLD_P1_FOOT_UP_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_UP_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_UP_AVG_B,
games::ddr::Lights::GOLD_P1_FOOT_RIGHT_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_RIGHT_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_RIGHT_AVG_B
},
{
games::ddr::Lights::GOLD_P1_FOOT_LEFT_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_LEFT_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_LEFT_AVG_B,
games::ddr::Lights::GOLD_P1_FOOT_DOWN_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_DOWN_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_DOWN_AVG_B
},
{
games::ddr::Lights::GOLD_P2_FOOT_UP_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_UP_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_UP_AVG_B,
games::ddr::Lights::GOLD_P2_FOOT_RIGHT_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_RIGHT_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_RIGHT_AVG_B
},
{
games::ddr::Lights::GOLD_P2_FOOT_LEFT_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_LEFT_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_LEFT_AVG_B,
games::ddr::Lights::GOLD_P2_FOOT_DOWN_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_DOWN_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_DOWN_AVG_B
},
{
games::ddr::Lights::GOLD_TOP_PANEL_AVG_R, games::ddr::Lights::GOLD_TOP_PANEL_AVG_G, games::ddr::Lights::GOLD_TOP_PANEL_AVG_B,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
{
UINT8_MAX, UINT8_MAX, UINT8_MAX,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
{
games::ddr::Lights::GOLD_MONITOR_SIDE_LEFT_AVG_R, games::ddr::Lights::GOLD_MONITOR_SIDE_LEFT_AVG_G, games::ddr::Lights::GOLD_MONITOR_SIDE_LEFT_AVG_B,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
{
games::ddr::Lights::GOLD_MONITOR_SIDE_RIGHT_AVG_R, games::ddr::Lights::GOLD_MONITOR_SIDE_RIGHT_AVG_G, games::ddr::Lights::GOLD_MONITOR_SIDE_RIGHT_AVG_B,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
};
if (off1 < std::size(mapping)) {
auto &map = mapping[off1];
size_t off2_match = -1;
if (!map.split || off2 < 25) {
off2_match = map.index_for_avg0;
} else {
off2_match = map.index_for_avg1;
}
if (off2_match == off2 && map.index_r0 != UINT8_MAX) {
auto &lights = games::ddr::get_lights();
if (!map.split || off2 < 25) {
Lights::writeLight(RI_MGR, lights[map.index_r0], r / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_g0], g / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_b0], b / 255.f);
} else {
Lights::writeLight(RI_MGR, lights[map.index_r1], r / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_g1], g / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_b1], b / 255.f);
}
}
}
}
return true;
}
static bool __cdecl ac_io_bi2a_tapeled_send() {
return true;
}
static int __cdecl ac_io_bi2a_get_exbio2_status(uint8_t *info) {
// surely this meme never gets old
info[5] = 5;
info[6] = 7;
info[7] = 3;
return 0;
}
acio::BI2AModule::BI2AModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("BI2A", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::BI2AModule::attach() {
ACIOModule::attach();
ACIO_MODULE_HOOK(ac_io_bi2a_init_is_finished);
ACIO_MODULE_HOOK(ac_io_bi2a_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bi2a_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bi2a_current_coinstock);
ACIO_MODULE_HOOK(ac_io_bi2a_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_bi2a_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_bi2a_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_bi2a_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_bi2a_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_bi2a_control_led_bright);
ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_bi2a_watchdog_off);
ACIO_MODULE_HOOK(ac_io_bi2a_init);
ACIO_MODULE_HOOK(ac_io_bi2a_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_status);
ACIO_MODULE_HOOK(ac_io_bi2a_set_amp_volume);
ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_init);
ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_init_is_finished);
ACIO_MODULE_HOOK(ac_io_bi2a_get_exbio2_status);
ACIO_MODULE_HOOK(ac_io_bi2a_control_tapeled_rec_set);
ACIO_MODULE_HOOK(ac_io_bi2a_control_tapeled_bright);
ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_send);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class BI2AModule : public ACIOModule {
public:
BI2AModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+616
View File
@@ -0,0 +1,616 @@
#include "bmpu.h"
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "cfg/api.h"
#include "cfg/light.h"
#include "games/bbc/io.h"
#include "games/dea/io.h"
#include "games/ftt/io.h"
#include "games/museca/io.h"
#include "games/silentscope/io.h"
#include "launcher/launcher.h"
#include "misc/eamuse.h"
using namespace GameAPI;
// state
static uint8_t STATUS_BUFFER[64] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static char __cdecl ac_io_bmpu_consume_coinstock(int a1, int a2) {
eamuse_coin_consume_stock();
return 1;
}
static int __cdecl ac_io_bmpu_control_1p_start_led_off() {
// dance evolution
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P1Start), 0.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Start), 0.f);
}
return 1;
}
static int __cdecl ac_io_bmpu_control_1p_start_led_on() {
// dance evolution
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P1Start), 1.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Start), 1.f);
}
return 1;
}
static int __cdecl ac_io_bmpu_control_2p_start_led_off() {
// dance evolution
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P2Start), 0.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Keypad), 0.f);
}
return 1;
}
static int __cdecl ac_io_bmpu_control_2p_start_led_on() {
// dance evolution
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P2Start), 1.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Keypad), 1.f);
}
return 1;
}
static int __cdecl ac_io_bmpu_control_coin_blocker_close() {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_bmpu_control_coin_blocker_open() {
eamuse_coin_set_block(false);
return 1;
}
static bool __cdecl ac_io_bmpu_control_led_bright(uint32_t led_field, uint8_t brightness) {
// MUSECA
if (avs::game::is_model("PIX")) {
// get lights
auto &lights = games::museca::get_lights();
// control mapping
static const int mapping[] = {
games::museca::Lights::UnderLED3G,
games::museca::Lights::UnderLED3R,
games::museca::Lights::UnderLED2B,
games::museca::Lights::UnderLED2G,
games::museca::Lights::UnderLED2R,
games::museca::Lights::UnderLED1B,
games::museca::Lights::UnderLED1G,
games::museca::Lights::UnderLED1R,
-1, -1, -1, -1,
games::museca::Lights::SideB,
games::museca::Lights::SideG,
games::museca::Lights::SideR,
games::museca::Lights::UnderLED3B,
};
// write light
float value = brightness > 127.f ? 1.f : brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// BISHI BASHI CHANNEL
if (avs::game::is_model("R66")) {
// get lights
auto &lights = games::bbc::get_lights();
// control mapping
static int mapping[] = {
games::bbc::Lights::UNDER_LED3_G,
games::bbc::Lights::UNDER_LED3_R,
games::bbc::Lights::UNDER_LED2_B,
games::bbc::Lights::UNDER_LED2_G,
games::bbc::Lights::UNDER_LED2_R,
games::bbc::Lights::UNDER_LED1_B,
games::bbc::Lights::UNDER_LED1_G,
games::bbc::Lights::UNDER_LED1_R,
-1, -1, -1, -1,
games::bbc::Lights::IC_CARD_B,
games::bbc::Lights::IC_CARD_G,
games::bbc::Lights::IC_CARD_R,
games::bbc::Lights::UNDER_LED3_B,
};
// write light
float value = brightness > 127.f ? 1.f : brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// FutureTomTom
if (avs::game::is_model("MMD")) {
// get lights
auto &lights = games::ftt::get_lights();
// control mapping
static int mapping[] = {
games::ftt::Lights::Pad3_G,
games::ftt::Lights::Pad3_R,
games::ftt::Lights::Pad2_B,
games::ftt::Lights::Pad2_G,
games::ftt::Lights::Pad2_R,
games::ftt::Lights::Pad1_B,
games::ftt::Lights::Pad1_G,
games::ftt::Lights::Pad1_R,
-1, -1, -1, -1,
games::ftt::Lights::Pad4_B,
games::ftt::Lights::Pad4_G,
games::ftt::Lights::Pad4_R,
games::ftt::Lights::Pad3_B,
};
// write light
float value = brightness > 127.f ? 1.f : brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// Dance Evolution
if (avs::game::is_model("KDM")) {
// get lights
auto &lights = games::dea::get_lights();
// control mapping
static int mapping[] = {
-1,
-1,
-1,
-1,
-1,
-1,
games::dea::Lights::P2LRButton,
games::dea::Lights::P1LRButton,
-1,
games::dea::Lights::TitleB,
games::dea::Lights::TitleR,
games::dea::Lights::TitleG,
-1,
};
// write light
float value = brightness > 128.f ? 1.f : brightness / 128.f;
for (size_t i = 0; i < std::size(mapping); i++)
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
// return success
return true;
}
static bool __cdecl ac_io_bmpu_control_led_bright_pack(int a1, int a2, int a3) {
// TODO(felix): NDD lights
return true;
}
static bool __cdecl ac_io_bmpu_create_get_status_thread() {
return true;
}
static char __cdecl ac_io_bmpu_current_coinstock(int a1, int *a2) {
*a2 = eamuse_coin_get_stock();
return 1;
}
static bool __cdecl ac_io_bmpu_destroy_get_status_thread() {
return true;
}
static char __cdecl ac_io_bmpu_get_control_status_buffer(void *buffer) {
size_t buffer_len = 0;
if (avs::game::is_model({ "KDM", "MMD" })) {
buffer_len = sizeof(STATUS_BUFFER);
} else if (avs::game::is_model("PIX")) {
buffer_len = 16;
} else if (avs::game::is_model("R66")) {
buffer_len = 56;
} else if (avs::game::is_model("NDD")) {
buffer_len = 56;
}
if (buffer_len > 0) {
memcpy(buffer, &STATUS_BUFFER, buffer_len);
}
// success
return true;
}
static char *__cdecl ac_io_bmpu_get_softwareid(char *a1) {
*a1 = 0;
return a1;
}
static char *__cdecl ac_io_bmpu_get_systemid(char *a1) {
*a1 = 0;
return a1;
}
static char __cdecl ac_io_bmpu_init_outport() {
return 1;
}
static char __cdecl ac_io_bmpu_lock_coincounter(signed int a1) {
return 1;
}
static char __cdecl ac_io_bmpu_req_secplug_check_isfinished(DWORD *a1) {
return 1;
}
static char __cdecl ac_io_bmpu_req_secplug_check_softwareplug(char *a1) {
return 1;
}
static char __cdecl ac_io_bmpu_req_secplug_check_systemplug() {
return 1;
}
static char __cdecl ac_io_bmpu_req_secplug_missing_check() {
return 1;
}
static int __cdecl ac_io_bmpu_req_secplug_missing_check_isfinished(DWORD *a1) {
return 1;
}
static int __cdecl ac_io_bmpu_set_outport_led(uint8_t *data1, uint8_t *data2) {
// dance evolution
if (avs::game::is_model("KDM")) {
// get lights
auto &lights = games::dea::get_lights();
// mapping
static const size_t mapping[] {
games::dea::Lights::SideUpperLeftR,
games::dea::Lights::SideUpperLeftG,
games::dea::Lights::SideUpperLeftB,
games::dea::Lights::SideLowerLeft1R,
games::dea::Lights::SideLowerLeft1G,
games::dea::Lights::SideLowerLeft1B,
games::dea::Lights::SideLowerLeft2R,
games::dea::Lights::SideLowerLeft2G,
games::dea::Lights::SideLowerLeft2B,
games::dea::Lights::SideLowerLeft3R,
games::dea::Lights::SideLowerLeft3G,
games::dea::Lights::SideLowerLeft3B,
games::dea::Lights::SideUpperRightR,
games::dea::Lights::SideUpperRightG,
games::dea::Lights::SideUpperRightB,
games::dea::Lights::SideLowerRight1R,
games::dea::Lights::SideLowerRight1G,
games::dea::Lights::SideLowerRight1B,
games::dea::Lights::SideLowerRight2R,
games::dea::Lights::SideLowerRight2G,
games::dea::Lights::SideLowerRight2B,
games::dea::Lights::SideLowerRight3R,
games::dea::Lights::SideLowerRight3G,
games::dea::Lights::SideLowerRight3B,
};
// write lights
for (size_t i = 0; i < std::size(mapping); i++) {
float brightness = data1[i * 2] / 255.f;
Lights::writeLight(RI_MGR, lights.at(mapping[i]), brightness);
}
}
// success
return true;
}
static int __cdecl ac_io_bmpu_set_output_mode(__int16 a1) {
return 1;
}
static char __cdecl ac_io_bmpu_unlock_coincounter(int a1) {
return 1;
}
static bool __cdecl ac_io_bmpu_update_control_status_buffer() {
unsigned int control_data = 0;
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// DEA
if (avs::game::is_model("KDM")) {
// keypad mirror fix
acio::ICCA_FLIP_ROWS = true;
// get buttons
auto &buttons = games::dea::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::Test))) {
control_data |= 0xF0000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::Service))) {
control_data |= 0x0F000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P1Start))) {
control_data |= 0x00000001;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P1Left))) {
control_data |= 0x00000008;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P1Right))) {
control_data |= 0x00000010;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P2Start))) {
control_data |= 0x00000100;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P2Left))) {
control_data |= 0x00000800;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P2Right))) {
control_data |= 0x00001000;
}
// set control data
auto buffer = reinterpret_cast<unsigned int *>(STATUS_BUFFER);
for (size_t i = 0; i < 16; i++) {
buffer[i] = control_data;
}
}
// FutureTomTom
if (avs::game::is_model("MMD")) {
// keypad mirror fix
acio::ICCA_FLIP_ROWS = true;
// get buttons
auto &buttons = games::ftt::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Service))) {
control_data |= 0x0F000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Test))) {
control_data |= 0xF0000000;
}
// set control data
auto buffer = reinterpret_cast<unsigned int *>(STATUS_BUFFER);
for (size_t i = 0; i < 16; i++) {
buffer[i] = control_data;
}
}
// MUSECA
if (avs::game::is_model("PIX")) {
// get buttons
auto &buttons = games::museca::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Service))) {
control_data |= 0x0F000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Test))) {
control_data |= 0xF0000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Start))) {
control_data |= 0x00000001;
}
// set control data
auto buffer = reinterpret_cast<unsigned int *>(STATUS_BUFFER);
for (size_t i = 0; i < 4; i++) {
buffer[i] = control_data;
}
}
// BISHI BASHI CHANNEL
if (avs::game::is_model("R66")) {
// get buttons
auto &buttons = games::bbc::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::Service))) {
control_data |= 0x0F000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::Test))) {
control_data |= 0xF0000000;
}
// set control data
auto buffer = reinterpret_cast<unsigned int *>(STATUS_BUFFER);
for (size_t i = 0; i < 4; i++) {
buffer[i] = control_data;
}
}
// Silent Scope Bone Eater
if (avs::game::is_model("NDD")) {
// clear state
memset(STATUS_BUFFER, 0, 56);
// get buttons
auto &buttons = games::silentscope::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::TEST))) {
STATUS_BUFFER[7] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::SERVICE))) {
STATUS_BUFFER[7] |= 0x2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::COIN_MECH))) {
STATUS_BUFFER[7] |= 0x1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::START))) {
STATUS_BUFFER[5] |= 0x1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::UP))) {
STATUS_BUFFER[5] |= 0x2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::DOWN))) {
STATUS_BUFFER[5] |= 0x4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::LEFT))) {
STATUS_BUFFER[5] |= 0x8;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::RIGHT))) {
STATUS_BUFFER[5] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::SCOPE_RIGHT))) {
STATUS_BUFFER[4] |= 0x80;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::SCOPE_LEFT))) {
STATUS_BUFFER[4] |= 0x40;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::GUN_PRESSED))) {
STATUS_BUFFER[4] |= 0x20;
}
// joy stick raw input
auto &analogs = games::silentscope::get_analogs();
unsigned short joy_x = 0x7FFF;
unsigned short joy_y = 0x7FFF;
if (analogs.at(games::silentscope::Analogs::GUN_X).isSet()) {
joy_x = (unsigned short) (Analogs::getState(RI_MGR, analogs.at(games::silentscope::Analogs::GUN_X)) * USHRT_MAX);
}
if (analogs.at(games::silentscope::Analogs::GUN_Y).isSet()) {
joy_y = (unsigned short) (Analogs::getState(RI_MGR, analogs.at(games::silentscope::Analogs::GUN_Y)) * USHRT_MAX);
}
// invert X axis
joy_x = USHRT_MAX - joy_x;
STATUS_BUFFER[8] = HIBYTE(joy_x);
STATUS_BUFFER[9] = LOBYTE(joy_x);
STATUS_BUFFER[10] = HIBYTE(joy_y);
STATUS_BUFFER[11] = LOBYTE(joy_y);
}
// success
return true;
}
static bool __cdecl ac_io_bmpu_set_watchdog_time(char a1) {
return true;
}
static char __cdecl ac_io_bmpu_get_watchdog_time_min() {
return 0;
}
static char __cdecl ac_io_bmpu_get_watchdog_time_now() {
return 0;
}
static void __cdecl ac_io_bmpu_watchdog_off() {
}
/*
* Module stuff
*/
acio::BMPUModule::BMPUModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("BMPU", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::BMPUModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_bmpu_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_bmpu_control_1p_start_led_off);
ACIO_MODULE_HOOK(ac_io_bmpu_control_1p_start_led_on);
ACIO_MODULE_HOOK(ac_io_bmpu_control_2p_start_led_off);
ACIO_MODULE_HOOK(ac_io_bmpu_control_2p_start_led_on);
ACIO_MODULE_HOOK(ac_io_bmpu_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_bmpu_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_bmpu_control_led_bright);
ACIO_MODULE_HOOK(ac_io_bmpu_control_led_bright_pack);
ACIO_MODULE_HOOK(ac_io_bmpu_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_bmpu_current_coinstock);
ACIO_MODULE_HOOK(ac_io_bmpu_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_bmpu_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bmpu_get_softwareid);
ACIO_MODULE_HOOK(ac_io_bmpu_get_systemid);
ACIO_MODULE_HOOK(ac_io_bmpu_init_outport);
ACIO_MODULE_HOOK(ac_io_bmpu_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_check_isfinished);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_check_softwareplug);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_check_systemplug);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_missing_check);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_missing_check_isfinished);
ACIO_MODULE_HOOK(ac_io_bmpu_set_outport_led);
ACIO_MODULE_HOOK(ac_io_bmpu_set_output_mode);
ACIO_MODULE_HOOK(ac_io_bmpu_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_bmpu_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bmpu_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_bmpu_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_bmpu_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_bmpu_watchdog_off);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class BMPUModule : public ACIOModule {
public:
BMPUModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+190
View File
@@ -0,0 +1,190 @@
#include "core.h"
#include "avs/game.h"
#include "launcher/launcher.h"
#include "misc/wintouchemu.h"
#include "rawinput/rawinput.h"
// static stuff
static int ACIO_WARMUP = 0;
static HHOOK ACIO_KB_HOOK = nullptr;
/*
* Implementations
*/
// needed for some games to make GetAsyncKeyState() working
static LRESULT CALLBACK ac_io_kb_hook_callback(int nCode, WPARAM wParam, LPARAM lParam) {
CallNextHookEx(ACIO_KB_HOOK, nCode, wParam, lParam);
return 0;
}
static char __cdecl ac_io_begin(
size_t dev,
const char *ver,
unsigned int *val,
size_t flags,
void *ptr,
size_t baud)
{
if (ACIO_KB_HOOK == nullptr) {
ACIO_KB_HOOK = SetWindowsHookEx(WH_KEYBOARD_LL, ac_io_kb_hook_callback, GetModuleHandle(nullptr), 0);
}
// always return success
if (val && avs::game::is_model("KFC")) {
*val = 2;
}
return 1;
}
static char __cdecl ac_io_begin_get_status() {
return 1;
}
static int __cdecl ac_io_end(int a1) {
return 1;
}
static int __cdecl ac_io_end_get_status(int a1) {
return 1;
}
static void *__cdecl ac_io_get_rs232c_status(char *a1, int a2) {
return memset(a1, 0, 88);
}
static char __cdecl ac_io_get_version(uint8_t *a1, int a2) {
// some games have version checks
// pop'n music only accepts versions bigger than 1.X.X (check yourself), anything starting with 2 works though
memset(a1 + 5, 2, 1);
memset(a1 + 6, 0, 1);
memset(a1 + 7, 0, 1);
return 1;
}
static const char *__cdecl ac_io_get_version_string() {
static const char *version = "1.25.0";
return version;
}
static char __cdecl ac_io_is_active(int a1, int a2) {
if (a1 == 1 && avs::game::is_model("JMA")) {
return 1;
}
return (char) (++ACIO_WARMUP > 601 ? 1 : 0);
}
static int __cdecl ac_io_is_active2(int a1, int *a2, int a3) {
ACIO_WARMUP = 601;
*a2 = 6;
return 1;
}
static char __cdecl ac_io_is_active_device(int index, int a2) {
// for scotto
static bool CHECKED_24 = false;
// dance evolution
if (avs::game::is_model("KDM")) {
// disable mysterious LED devices
if (index >= 12 && index <= 15)
return false;
}
// scotto
if (avs::game::is_model("NSC") && index == 24) {
// scotto expects device index 24 to come online after
// it initializes device index 22
if (!CHECKED_24) {
CHECKED_24 = true;
return false;
}
return true;
}
// dunno for what game we did this again
return (char) (index != 5);
}
static int __cdecl ac_io_reset(int a1) {
return a1;
}
static int __cdecl ac_io_secplug_set_encodedpasswd(void *a1, int a2) {
return 1;
}
static int __cdecl ac_io_set_soft_watch_dog(int a1, int a2) {
return 1;
}
static int __cdecl ac_io_soft_watch_dog_on(int a1) {
return 1;
}
static int __cdecl ac_io_soft_watch_dog_off() {
return 1;
}
static int __cdecl ac_io_update(int a1) {
// flush device output
RI_MGR->devices_flush_output();
// update wintouchemu
wintouchemu::update();
return 1;
}
static int __cdecl ac_io_get_firmware_update_device_index() {
return 0xFF;
}
static void __cdecl ac_io_go_firmware_update() {
}
static int __cdecl ac_io_set_get_status_device(int a1) {
return a1;
}
/*
* Module stuff
*/
acio::CoreModule::CoreModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("Core", module, hookMode) {
}
void acio::CoreModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_begin);
ACIO_MODULE_HOOK(ac_io_begin_get_status);
ACIO_MODULE_HOOK(ac_io_end);
ACIO_MODULE_HOOK(ac_io_end_get_status);
ACIO_MODULE_HOOK(ac_io_get_rs232c_status);
ACIO_MODULE_HOOK(ac_io_get_version);
ACIO_MODULE_HOOK(ac_io_get_version_string);
ACIO_MODULE_HOOK(ac_io_is_active);
ACIO_MODULE_HOOK(ac_io_is_active2);
ACIO_MODULE_HOOK(ac_io_is_active_device);
ACIO_MODULE_HOOK(ac_io_reset);
ACIO_MODULE_HOOK(ac_io_secplug_set_encodedpasswd);
ACIO_MODULE_HOOK(ac_io_set_soft_watch_dog);
ACIO_MODULE_HOOK(ac_io_soft_watch_dog_on);
ACIO_MODULE_HOOK(ac_io_soft_watch_dog_off);
ACIO_MODULE_HOOK(ac_io_update);
ACIO_MODULE_HOOK(ac_io_get_firmware_update_device_index);
ACIO_MODULE_HOOK(ac_io_go_firmware_update);
ACIO_MODULE_HOOK(ac_io_set_get_status_device);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class CoreModule : public ACIOModule {
public:
CoreModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+565
View File
@@ -0,0 +1,565 @@
#include "hbhi.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/rf3d/io.h"
#include "games/sc/io.h"
#include "games/hpm/io.h"
#include "avs/game.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace GameAPI;
// state
static uint8_t STATUS_BUFFER[64] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static int __cdecl ac_io_hbhi_add_coin(int a1, int a2) {
eamuse_coin_add();
return 1;
}
static char __cdecl ac_io_hbhi_consume_coinstock(int a1, int a2) {
eamuse_coin_consume_stock();
return 1;
}
static int __cdecl ac_io_hbhi_control_coin_blocker_close(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_hbhi_control_coin_blocker_open(int a1) {
eamuse_coin_set_block(0);
return 1;
}
/*
* Helper method, not a real ACIO one
*/
static inline int __cdecl ac_io_hbhi_control_lamp_set(uint32_t lamp_bits, float value) {
// steel chronicle
if (avs::game::is_model("KGG")) {
// get lights
auto &lights = games::sc::get_lights();
// write lights
if (lamp_bits & 0x01) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::SideRed), value);
}
if (lamp_bits & 0x02) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::SideGreen), value);
}
if (lamp_bits & 0x04) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::SideBlue), value);
}
if (lamp_bits & 0x08) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::CenterRed), value);
}
if (lamp_bits & 0x10) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::CenterGreen), value);
}
if (lamp_bits & 0x20) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::CenterBlue), value);
}
if (lamp_bits & 0x40) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::ControllerRed), value);
}
if (lamp_bits & 0x80) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::ControllerBlue), value);
}
}
// hello popn music
if (avs::game::is_model("JMP")) {
// get lights
auto &lights = games::hpm::get_lights();
// write lights
if (lamp_bits & 0x01) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_RED_P2_GREEN), value);
}
if (lamp_bits & 0x02) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_BLUE), value);
}
if (lamp_bits & 0x04) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_YELLOW), value);
}
if (lamp_bits & 0x08) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_GREEN), value);
}
if (lamp_bits & 0x10) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P2_RED), value);
}
if (lamp_bits & 0x20) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P2_BLUE), value);
}
if (lamp_bits & 0x40) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P2_YELLOW), value);
}
if (lamp_bits & 0x80) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P2_START), value);
}
}
// return success
return 1;
}
static bool __cdecl ac_io_hbhi_control_lamp_bright(uint32_t lamp_bits, uint8_t value) {
ac_io_hbhi_control_lamp_set(lamp_bits, value / 31.f);
return true;
}
static int __cdecl ac_io_hbhi_control_lamp_mode(uint32_t mode) {
return 1;
}
static int __cdecl ac_io_hbhi_control_lamp_off(uint8_t lamp_bits) {
return ac_io_hbhi_control_lamp_set(lamp_bits, 0.f);
}
static int __cdecl ac_io_hbhi_control_lamp_on(uint8_t lamp_bits) {
return ac_io_hbhi_control_lamp_set(lamp_bits, 1.f);
}
/*
* Helper method, not a real ACIO one
*/
static inline int __cdecl ac_io_hbhi_control_parallel_set(uint8_t lamp_bits, float value) {
// hello popn music
if (avs::game::is_model("JMP")) {
// get lights
auto &lights = games::hpm::get_lights();
// write lights
if (lamp_bits & 0x01) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_START), value);
}
if (lamp_bits & 0x02) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::SPEAKER_BLUE), value);
}
if (lamp_bits & 0x04) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::SPEAKER_ORANGE), value);
}
if (lamp_bits & 0x08) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::SPEAKER_RED), value);
}
}
// return success
return 1;
}
static int __cdecl ac_io_hbhi_control_parallel_off(uint8_t lamp_bits) {
return ac_io_hbhi_control_parallel_set(lamp_bits, 0.f);
}
static int __cdecl ac_io_hbhi_control_parallel_on(uint8_t lamp_bits) {
return ac_io_hbhi_control_parallel_set(lamp_bits, 1.f);
}
static int __cdecl ac_io_hbhi_control_reset() {
return 1;
}
static bool __cdecl ac_io_hbhi_create_get_status_thread(void *a1) {
return true;
}
static char __cdecl ac_io_hbhi_current_coinstock(int a1, int *coinstock) {
*coinstock = eamuse_coin_get_stock();
return 1;
}
static int __cdecl ac_io_hbhi_destroy_get_status_thread() {
return 1;
}
static char __cdecl ac_io_hbhi_get_coin_input_wave_buffer(int *a1) {
return 1;
}
static void *__cdecl ac_io_hbhi_get_control_status_buffer(uint8_t *buffer) {
// return buffer
memcpy(buffer, STATUS_BUFFER, std::size(STATUS_BUFFER));
return buffer;
}
static char __cdecl ac_io_hbhi_get_softwareid(char *a1) {
memset(a1, 'F', 16);
return 1;
}
static char __cdecl ac_io_hbhi_get_systemid(char *a1) {
memset(a1, 'F', 16);
return 1;
}
static bool __cdecl ac_io_hbhi_get_watchdog_status() {
return true;
}
static short __cdecl ac_io_hbhi_get_watchdog_time_min() {
return 0;
}
static short __cdecl ac_io_hbhi_get_watchdog_time_now() {
return 0;
}
static char __cdecl ac_io_hbhi_lock_coincounter(int a1) {
return 1;
}
static char __cdecl ac_io_hbhi_req_carddispenser_disburse() {
return 1;
}
static bool __cdecl ac_io_hbhi_req_carddispenser_disburse_isfinished(int *a1) {
*a1 += 1;
return true;
}
static char __cdecl ac_io_hbhi_req_carddispenser_get_status() {
return 1;
}
static int __cdecl ac_io_hbhi_req_carddispenser_get_status_isfinished(int *a1) {
*a1 += 1;
return 2;
}
static char __cdecl ac_io_hbhi_req_carddispenser_init() {
return 1;
}
static bool __cdecl ac_io_hbhi_req_carddispenser_init_isfinished(int *a1) {
*a1 += 1;
return true;
}
static char __cdecl ac_io_hbhi_req_coin_input_wave() {
return 1;
}
static char __cdecl ac_io_hbhi_req_get_control_status(int *a1) {
return 1;
}
static char __cdecl ac_io_hbhi_req_secplug_check(char *a1) {
return 1;
}
static bool __cdecl ac_io_hbhi_req_secplug_check_isfinished(int *a1) {
return true;
}
static char __cdecl ac_io_hbhi_req_secplug_check_softwareplug(char *a1) {
return 1;
}
static char __cdecl ac_io_hbhi_req_secplug_check_systemplug() {
return 1;
}
static char __cdecl ac_io_hbhi_req_secplug_missing_check() {
return 1;
}
static bool __cdecl ac_io_hbhi_req_secplug_missing_check_isfinished(int *a1) {
return true;
}
static bool __cdecl ac_io_hbhi_req_volume_control(char a1, char a2) {
return true;
}
static bool __cdecl ac_io_hbhi_req_volume_control_isfinished(int *a1) {
return true;
}
static int __cdecl ac_io_hbhi_reset_coin_slot_noise_flag(int a1) {
return 1;
}
static int __cdecl ac_io_hbhi_set_framing_err_packet_send_interval(int a1) {
return 1;
}
static bool __cdecl ac_io_hbhi_set_watchdog_time(short a1) {
return true;
}
static char __cdecl ac_io_hbhi_unlock_coincounter(int a1) {
return 1;
}
static char __cdecl ac_io_hbhi_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// steel chronicle
if (avs::game::is_model("KGG")) {
// get buttons
auto &buttons = games::sc::get_buttons();
// reset
memset(STATUS_BUFFER, 0, 64);
// check buttons
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::Service))) {
STATUS_BUFFER[5] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::Test))) {
STATUS_BUFFER[5] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::LButton))) {
STATUS_BUFFER[12] |= 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::L1))) {
STATUS_BUFFER[12] |= 1 << 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::L2))) {
STATUS_BUFFER[12] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::JogLeft))) {
STATUS_BUFFER[12] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::JogRight))) {
STATUS_BUFFER[12] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::RButton))) {
STATUS_BUFFER[12] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::R1))) {
STATUS_BUFFER[12] |= 1 << 6;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::R2))) {
STATUS_BUFFER[12] |= 1 << 7;
}
// get analogs
auto &analogs = games::sc::get_analogs();
auto joy_left_x = Analogs::getState(RI_MGR, analogs.at(games::sc::Analogs::LEFT_X)) * USHRT_MAX;
auto joy_left_y = Analogs::getState(RI_MGR, analogs.at(games::sc::Analogs::LEFT_Y)) * USHRT_MAX;
auto joy_right_x = Analogs::getState(RI_MGR, analogs.at(games::sc::Analogs::RIGHT_X)) * USHRT_MAX;
auto joy_right_y = Analogs::getState(RI_MGR, analogs.at(games::sc::Analogs::RIGHT_Y)) * USHRT_MAX;
// because these are flight sticks, the X axis is inverted
*((uint16_t *) &STATUS_BUFFER[20]) = USHRT_MAX - (uint16_t) joy_left_x;
*((uint16_t *) &STATUS_BUFFER[22]) = (uint16_t) joy_left_y;
*((uint16_t *) &STATUS_BUFFER[24]) = USHRT_MAX - (uint16_t) joy_right_x;
*((uint16_t *) &STATUS_BUFFER[26]) = (uint16_t) joy_right_y;
}
// hello popn music
if (avs::game::is_model("JMP")) {
// get buttons
auto &buttons = games::hpm::get_buttons();
// reset
memset(STATUS_BUFFER, 0x00, 64);
// check buttons
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::Service))) {
STATUS_BUFFER[5] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::Test))) {
STATUS_BUFFER[5] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::CoinMech))) {
STATUS_BUFFER[5] |= 1 << 2;
}
if (!Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_Start))) {
STATUS_BUFFER[4] |= 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_1))) {
STATUS_BUFFER[12] |= 1 << 0;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_2))) {
STATUS_BUFFER[12] |= 1 << 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_3))) {
STATUS_BUFFER[12] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_4))) {
STATUS_BUFFER[12] |= 1 << 3;
}
if (!Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_Start))) {
STATUS_BUFFER[6] |= 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_1))) {
STATUS_BUFFER[12] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_2))) {
STATUS_BUFFER[12] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_3))) {
STATUS_BUFFER[12] |= 1 << 6;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_4))) {
STATUS_BUFFER[12] |= 1 << 7;
}
}
// road fighters 3D
if (avs::game::is_model("JGT")) {
static int lever_state = 0;
// get buttons
auto &buttons = games::rf3d::get_buttons();
// reset
memset(STATUS_BUFFER, 0x00, 64);
// check buttons
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Service))) {
STATUS_BUFFER[5] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Test))) {
STATUS_BUFFER[5] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::CoinMech))) {
STATUS_BUFFER[5] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::View))) {
STATUS_BUFFER[12] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Toggle2D3D))) {
STATUS_BUFFER[12] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::LeverUp))) {
STATUS_BUFFER[12] |= 1 << 4;
lever_state = 0;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::LeverDown))) {
STATUS_BUFFER[12] |= 1 << 5;
lever_state = 0;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::LeverLeft))) {
STATUS_BUFFER[12] |= 1 << 6;
lever_state = 0;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::LeverRight))) {
STATUS_BUFFER[12] |= 1 << 7;
lever_state = 0;
}
// auto lever buttons
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::AutoLeverUp)) && lever_state < 6) {
lever_state++;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::AutoLeverDown)) && lever_state > 0) {
lever_state--;
}
// auto lever logic
switch (lever_state) {
case 1:
STATUS_BUFFER[12] |= 1 << 4 | 1 << 6;
break;
case 2:
STATUS_BUFFER[12] |= 1 << 4 | 1 << 6;
break;
case 3:
STATUS_BUFFER[12] |= 1 << 4;
break;
case 4:
STATUS_BUFFER[12] |= 1 << 5;
break;
case 5:
STATUS_BUFFER[12] |= 1 << 4 | 1 << 7;
break;
case 6:
STATUS_BUFFER[12] |= 1 << 5 | 1 << 7;
break;
default:
lever_state = 0;
break;
}
}
// success
return true;
}
static void __cdecl ac_io_hbhi_watchdog_off() {
}
/*
* Module stuff
*/
acio::HBHIModule::HBHIModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("HBHI", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::HBHIModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_hbhi_add_coin);
ACIO_MODULE_HOOK(ac_io_hbhi_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_hbhi_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_hbhi_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_hbhi_control_lamp_bright);
ACIO_MODULE_HOOK(ac_io_hbhi_control_lamp_mode);
ACIO_MODULE_HOOK(ac_io_hbhi_control_lamp_off);
ACIO_MODULE_HOOK(ac_io_hbhi_control_lamp_on);
ACIO_MODULE_HOOK(ac_io_hbhi_control_parallel_off);
ACIO_MODULE_HOOK(ac_io_hbhi_control_parallel_on);
ACIO_MODULE_HOOK(ac_io_hbhi_control_reset);
ACIO_MODULE_HOOK(ac_io_hbhi_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_hbhi_current_coinstock);
ACIO_MODULE_HOOK(ac_io_hbhi_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_hbhi_get_coin_input_wave_buffer);
ACIO_MODULE_HOOK(ac_io_hbhi_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_hbhi_get_softwareid);
ACIO_MODULE_HOOK(ac_io_hbhi_get_systemid);
ACIO_MODULE_HOOK(ac_io_hbhi_get_watchdog_status);
ACIO_MODULE_HOOK(ac_io_hbhi_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_hbhi_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_hbhi_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_disburse);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_disburse_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_get_status);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_get_status_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_init);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_init_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_coin_input_wave);
ACIO_MODULE_HOOK(ac_io_hbhi_req_get_control_status);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_check);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_check_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_check_softwareplug);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_check_systemplug);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_missing_check);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_missing_check_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_volume_control);
ACIO_MODULE_HOOK(ac_io_hbhi_req_volume_control_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_reset_coin_slot_noise_flag);
ACIO_MODULE_HOOK(ac_io_hbhi_set_framing_err_packet_send_interval);
ACIO_MODULE_HOOK(ac_io_hbhi_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_hbhi_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_hbhi_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_hbhi_watchdog_off);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class HBHIModule : public ACIOModule {
public:
HBHIModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+200
View File
@@ -0,0 +1,200 @@
#include "hdxs.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "games/popn/io.h"
#include "games/rb/io.h"
#include "games/dea/io.h"
#include "avs/game.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace GameAPI;
// state
static uint8_t STATUS_BUFFER[32] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static int __cdecl ac_io_hdxs_get_control_status_buffer(int a1, void *a2) {
// copy buffer
memcpy(a2, STATUS_BUFFER, sizeof(STATUS_BUFFER));
return true;
}
static int __cdecl ac_io_hdxs_led_scroll(int a1, char a2, char a3, char a4, char a5, char a6, char a7, char a8, char a9,
char a10, char a11, char a12, char a13) {
return 1;
}
static int __cdecl ac_io_hdxs_led_set_pattern(int index, char r, char g, char b, uint64_t led_bits) {
// reflec beat
if (avs::game::is_model({"KBR", "LBR", "MBR"})) {
// get lights
auto &lights = games::rb::get_lights();
// set values
Lights::writeLight(RI_MGR, lights.at(games::rb::Lights::PoleR), r / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::rb::Lights::PoleG), g / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::rb::Lights::PoleB), b / 127.f);
}
// dance evolution
if (avs::game::is_model("KDM")) {
// get lights
auto &lights = games::dea::get_lights();
// decide on index
switch (index) {
case 12:
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperLeftR), r / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperLeftG), g / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperLeftB), b / 127.f);
break;
case 14:
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperRightR), r / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperRightG), g / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperRightB), b / 127.f);
}
}
// popn
if (avs::game::is_model("M39")) {
// mappings
static const uint64_t top_led_bits[] = {
0x80000000000000,
0x40000000000000,
0x20000000000000,
0x10000000000000,
0x8000000000000,
0x4000000000000,
0x2000000000000,
0x1000000000000,
0x800000000000,
0x400000000000,
0x200000000000,
0x100000000000,
0x80000000000,
0x40000000000,
0x20000000000,
0x10000000000,
0x8000000000,
0x4000000000,
0x2000000000,
0x1000000000,
0x800000000,
0x400000000,
0x200000000,
0x100000000,
0x80000000,
0x40000000,
0x20000000,
0x10000000,
0x8000000,
0x4000000,
0x2000000,
0x1000000,
};
static const size_t light_mapping[] {
games::popn::Lights::TopLED1,
games::popn::Lights::TopLED2,
games::popn::Lights::TopLED3,
games::popn::Lights::TopLED4,
games::popn::Lights::TopLED5,
games::popn::Lights::TopLED6,
games::popn::Lights::TopLED7,
games::popn::Lights::TopLED8,
games::popn::Lights::TopLED9,
games::popn::Lights::TopLED10,
games::popn::Lights::TopLED11,
games::popn::Lights::TopLED12,
games::popn::Lights::TopLED13,
games::popn::Lights::TopLED14,
games::popn::Lights::TopLED15,
games::popn::Lights::TopLED16,
games::popn::Lights::TopLED17,
games::popn::Lights::TopLED18,
games::popn::Lights::TopLED19,
games::popn::Lights::TopLED20,
games::popn::Lights::TopLED21,
games::popn::Lights::TopLED22,
games::popn::Lights::TopLED23,
games::popn::Lights::TopLED24,
games::popn::Lights::TopLED25,
games::popn::Lights::TopLED26,
games::popn::Lights::TopLED27,
games::popn::Lights::TopLED28,
games::popn::Lights::TopLED29,
games::popn::Lights::TopLED30,
games::popn::Lights::TopLED31,
games::popn::Lights::TopLED32,
};
// get lights
auto &lights = games::popn::get_lights();
// bit scan
for (int i = 0; i < 32; i++) {
bool value = (led_bits & top_led_bits[i]) > 0;
Lights::writeLight(RI_MGR, lights.at(light_mapping[i]), value ? 1.f : 0.f);
}
// write RGB
auto value_r = r / 127.f;
auto value_g = g / 127.f;
auto value_b = b / 127.f;
Lights::writeLight(RI_MGR, lights.at(games::popn::Lights::TopLED_R), value_r);
Lights::writeLight(RI_MGR, lights.at(games::popn::Lights::TopLED_G), value_g);
Lights::writeLight(RI_MGR, lights.at(games::popn::Lights::TopLED_B), value_b);
}
return 1;
}
static int __cdecl ac_io_hdxs_led_set_rgb_mask(int a1, char a2, char a3, long a4) {
return 1;
}
static char __cdecl ac_io_hdxs_update_control_status_buffer(int a1) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// success
return true;
}
static int __cdecl ac_io_hdxs_set_framing_err_packet_send_interval(int a1) {
return a1;
}
/*
* Module stuff
*/
acio::HDXSModule::HDXSModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("HDXS", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::HDXSModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_hdxs_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_hdxs_led_scroll);
ACIO_MODULE_HOOK(ac_io_hdxs_led_set_pattern);
ACIO_MODULE_HOOK(ac_io_hdxs_led_set_rgb_mask);
ACIO_MODULE_HOOK(ac_io_hdxs_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_hdxs_set_framing_err_packet_send_interval);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class HDXSModule : public ACIOModule {
public:
HDXSModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+122
View File
@@ -0,0 +1,122 @@
#include "hgth.h"
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "cfg/api.h"
#include "games/rf3d/io.h"
#include "launcher/launcher.h"
using namespace GameAPI;
// state
static uint8_t STATUS_BUFFER[32] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static int __cdecl ac_io_hgth_set_senddata(int a1) {
return 1;
}
static char __cdecl ac_io_hgth_update_recvdata() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// Road Fighters 3D
if (avs::game::is_model("JGT")) {
// keypad mirror fix
acio::ICCA_FLIP_ROWS = true;
// variables
uint16_t wheel = 0x7FFF;
uint16_t accelerator = 0x00;
uint16_t brake = 0x00;
// get buttons
auto &buttons = games::rf3d::get_buttons();
// check buttons
bool wheel_button = false;
bool accelerate_button = false;
bool brake_button = false;
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::WheelLeft))) {
wheel -= 0x7FFF;
wheel_button = true;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::WheelRight))) {
wheel += 0x8000;
wheel_button = true;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Accelerate))) {
accelerator = 0xFFFF;
accelerate_button = true;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Brake))) {
brake = 0xFFFF;
brake_button = true;
}
// analogs
auto &analogs = games::rf3d::get_analogs();
if (!wheel_button && analogs.at(games::rf3d::Analogs::Wheel).isSet()) {
wheel = (uint16_t) (Analogs::getState(RI_MGR, analogs.at(games::rf3d::Analogs::Wheel)) * 0xFFFF);
}
if (!accelerate_button && analogs.at(games::rf3d::Analogs::Accelerate).isSet()) {
accelerator = (uint16_t) (Analogs::getState(RI_MGR, analogs.at(games::rf3d::Analogs::Accelerate)) * 0xFFFF);
}
if (!brake_button && analogs.at(games::rf3d::Analogs::Brake).isSet()) {
brake = (uint16_t) (Analogs::getState(RI_MGR, analogs.at(games::rf3d::Analogs::Brake)) * 0xFFFF);
}
// write values
*((uint16_t *) STATUS_BUFFER + 1) = wheel;
*((uint16_t *) STATUS_BUFFER + 2) = accelerator;
*((uint16_t *) STATUS_BUFFER + 3) = brake;
}
// success
return true;
}
static void __cdecl ac_io_hgth_get_recvdata(void *buffer) {
// copy buffer
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static char __cdecl ac_io_hgth_directreq_set_handle_limit(char a1, int *a2) {
*a2 = 1;
return 1;
}
static bool __cdecl ac_io_hgth_directreq_set_handle_limit_isfinished(int *a1) {
*a1 = 2;
return true;
}
/*
* Module stuff
*/
acio::HGTHModule::HGTHModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("HGTH", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::HGTHModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_hgth_set_senddata);
ACIO_MODULE_HOOK(ac_io_hgth_update_recvdata);
ACIO_MODULE_HOOK(ac_io_hgth_get_recvdata);
ACIO_MODULE_HOOK(ac_io_hgth_directreq_set_handle_limit);
ACIO_MODULE_HOOK(ac_io_hgth_directreq_set_handle_limit_isfinished);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class HGTHModule : public ACIOModule {
public:
HGTHModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+375
View File
@@ -0,0 +1,375 @@
#include "i36g.h"
#include "launcher/launcher.h"
#include "avs/game.h"
#include "rawinput/rawinput.h"
#include "games/mga/io.h"
#include "misc/eamuse.h"
#include "util/utils.h"
using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[88 * 2] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static int __cdecl ac_io_i36g_add_coin(int a1, int a2, int a3) {
// not so sure we want to add coins
return 1;
}
static char __cdecl ac_io_i36g_consume_coinstock(int a1, int a2, int a3) {
eamuse_coin_consume_stock();
return 1;
}
static int __cdecl ac_io_i36g_control_coin_blocker_close(int a1, int a2) {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_i36g_control_coin_blocker_open(int a1, int a2) {
eamuse_coin_set_block(false);
return 1;
}
static int __cdecl ac_io_i36g_control_lamp_bright(uint32_t device, uint32_t lamp_bits, uint8_t brightness) {
// calculate value
float value = (float) brightness / 255.f;
// get lights
auto &lights = games::mga::get_lights();
// cabinet device
if (device == 21) {
if (lamp_bits & 1) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::LeftR], value);
}
if (lamp_bits & 2) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::LeftG], value);
}
if (lamp_bits & 4) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::LeftB], value);
}
if (lamp_bits & 8) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::RightR], value);
}
if (lamp_bits & 16) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::RightG], value);
}
if (lamp_bits & 32) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::RightB], value);
}
if (lamp_bits & 512) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::Start], value);
}
}
// gun device
if (device == 22) {
if (lamp_bits & 1) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::GunR], value);
}
if (lamp_bits & 2) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::GunG], value);
}
if (lamp_bits & 4) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::GunB], value);
}
}
// success
return 1;
}
static int __cdecl ac_io_i36g_control_motor_power(int device, uint8_t strength) {
// gun device
if (device == 22) {
float value = (float) strength / 255.f;
auto &lights = games::mga::get_lights();
Lights::writeLight(RI_MGR, lights[games::mga::Lights::GunVibration], value);
}
// success
return 1;
}
static char __cdecl ac_io_i36g_current_coinstock(int a1, int a2, int *a3) {
// get coinstock
*a3 = eamuse_coin_get_stock();
return 1;
}
static char __cdecl ac_io_i36g_get_coin_input_wave_buffer(int a1, char *a2) {
return 1;
}
static void __cdecl ac_io_i36g_get_control_status_buffer(int device, void *buffer) {
// cabinet buffer
if (device == 21) {
memcpy(buffer, &STATUS_BUFFER[0], 88);
}
// gun buffer
if (device == 22) {
memcpy(buffer, &STATUS_BUFFER[88], 88);
}
}
static char __cdecl ac_io_i36g_get_softwareid(int a1, int a2) {
return 1;
}
static char __cdecl ac_io_i36g_get_systemid(int a1, int a2) {
return 1;
}
static bool __cdecl ac_io_i36g_get_watchdog_status(int a1) {
return false;
}
static short __cdecl ac_io_i36g_get_watchdog_time_min(int a1) {
return 0;
}
static short __cdecl ac_io_i36g_get_watchdog_time_now(int a1) {
return 0;
}
static char __cdecl ac_io_i36g_lock_coincounter(int a1, int a2) {
return 1;
}
static char __cdecl ac_io_i36g_req_coin_input_wave(int a1) {
return 1;
}
static char __cdecl ac_io_i36g_req_get_control_status(int a1, int *a2) {
return 1;
}
static char __cdecl ac_io_i36g_req_secplug_check(int a1, char *a2) {
return 1;
}
static bool __cdecl ac_io_i36g_req_secplug_check_isfinished(int a1, int *a2) {
return true;
}
static char __cdecl ac_io_i36g_req_secplug_check_softwareplug(int a1, char *a2) {
return 1;
}
static char __cdecl ac_io_i36g_req_secplug_check_systemplug(int a1) {
return 1;
}
static char __cdecl ac_io_i36g_req_secplug_missing_check(int a1) {
return 1;
}
static bool __cdecl ac_io_i36g_req_secplug_missing_check_isfinished(int a1, int *a2) {
return true;
}
static bool __cdecl ac_io_i36g_req_volume_control(int a1, char a2, char a3, char a4, char a5) {
return true;
}
static bool __cdecl ac_io_i36g_req_volume_control_isfinished(int a1, int *ret_state) {
*ret_state = 3;
return true;
}
static int __cdecl ac_io_i36g_set_cmdmode(int a1, int a2) {
return 1;
}
static int __cdecl ac_io_i36g_set_framing_err_packet_send_interval(int a1) {
return 1;
}
static bool __cdecl ac_io_i36g_set_watchdog_time(int a1, short a2) {
return true;
}
static char __cdecl ac_io_i36g_unlock_coincounter(int a1, int a2) {
return 1;
}
static bool __cdecl ac_io_i36g_update_control_status_buffer(int node) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// Metal Gear Arcade
if (avs::game::is_model("I36")) {
// get buttons
auto &buttons = games::mga::get_buttons();
// cabinet device
if (node == 21) {
// clear status buffer
memset(&STATUS_BUFFER[0], 0, 88);
// update buttons
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::Service))) {
ARRAY_SETB(&STATUS_BUFFER[0], 44);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::Test))) {
ARRAY_SETB(&STATUS_BUFFER[0], 45);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::CoinMech))) {
ARRAY_SETB(&STATUS_BUFFER[0], 42);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::Start))) {
ARRAY_SETB(&STATUS_BUFFER[0], 124);
}
}
// gun device
if (node == 22) {
// clear status buffer
memset(&STATUS_BUFFER[88], 0, 88);
// update buttons
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::TriggerButton))
|| (GetKeyState(VK_LBUTTON) & 0x100) != 0) { // mouse button
ARRAY_SETB(&STATUS_BUFFER[88], 109);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::FrontTop))) {
ARRAY_SETB(&STATUS_BUFFER[88], 108);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::FrontBottom))) {
ARRAY_SETB(&STATUS_BUFFER[88], 106);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::SideLeft))) {
ARRAY_SETB(&STATUS_BUFFER[88], 107);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::SideRight))) {
ARRAY_SETB(&STATUS_BUFFER[88], 105);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::SideLever))) {
ARRAY_SETB(&STATUS_BUFFER[88], 104);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::SwitchButton))) {
ARRAY_SETB(&STATUS_BUFFER[88], 125);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::Top))) {
ARRAY_SETB(&STATUS_BUFFER[88], 124);
}
// joy stick
unsigned short joy_x = 0x7FFF;
unsigned short joy_y = 0x7FFF;
bool joy_x_pressed = false;
bool joy_y_pressed = false;
if (Buttons::getState(RI_MGR, buttons[games::mga::Buttons::JoyForwards])) {
joy_y -= 0x7FFF;
joy_y_pressed = true;
}
if (Buttons::getState(RI_MGR, buttons[games::mga::Buttons::JoyBackwards])) {
joy_y += 0x7FFF;
joy_y_pressed = true;
}
if (Buttons::getState(RI_MGR, buttons[games::mga::Buttons::JoyLeft])) {
joy_x -= 0x7FFF;
joy_x_pressed = true;
}
if (Buttons::getState(RI_MGR, buttons[games::mga::Buttons::JoyRight])) {
joy_x += 0x7FFF;
joy_x_pressed = true;
}
// joy stick raw input
auto &analogs = games::mga::get_analogs();
if (!joy_x_pressed && analogs[games::mga::Analogs::JoyX].isSet()) {
joy_x = (unsigned short) (Analogs::getState(RI_MGR, analogs[games::mga::Analogs::JoyX]) * 0xFFFF);
}
if (!joy_y_pressed && analogs[games::mga::Analogs::JoyY].isSet()) {
joy_y = (unsigned short) (Analogs::getState(RI_MGR, analogs[games::mga::Analogs::JoyY]) * 0xFFFF);
}
// save joy stick
STATUS_BUFFER[88 + 42] = LOBYTE(joy_y);
STATUS_BUFFER[88 + 43] = HIBYTE(joy_y);
STATUS_BUFFER[88 + 44] = LOBYTE(joy_x);
STATUS_BUFFER[88 + 45] = HIBYTE(joy_x);
}
}
// return success
return true;
}
static int __cdecl ac_io_i36g_watchdog_off(int a1) {
return 1;
}
/*
* Module stuff
*/
acio::I36GModule::I36GModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("I36G", module, hookMode) {
this->status_buffer = &STATUS_BUFFER[0];
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::I36GModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_i36g_add_coin);
ACIO_MODULE_HOOK(ac_io_i36g_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_i36g_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_i36g_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_i36g_control_lamp_bright);
ACIO_MODULE_HOOK(ac_io_i36g_control_motor_power);
ACIO_MODULE_HOOK(ac_io_i36g_current_coinstock);
ACIO_MODULE_HOOK(ac_io_i36g_get_coin_input_wave_buffer);
ACIO_MODULE_HOOK(ac_io_i36g_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_i36g_get_softwareid);
ACIO_MODULE_HOOK(ac_io_i36g_get_systemid);
ACIO_MODULE_HOOK(ac_io_i36g_get_watchdog_status);
ACIO_MODULE_HOOK(ac_io_i36g_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_i36g_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_i36g_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_i36g_req_coin_input_wave);
ACIO_MODULE_HOOK(ac_io_i36g_req_get_control_status);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_check);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_check_isfinished);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_check_softwareplug);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_check_systemplug);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_missing_check);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_missing_check_isfinished);
ACIO_MODULE_HOOK(ac_io_i36g_req_volume_control);
ACIO_MODULE_HOOK(ac_io_i36g_req_volume_control_isfinished);
ACIO_MODULE_HOOK(ac_io_i36g_set_cmdmode);
ACIO_MODULE_HOOK(ac_io_i36g_set_framing_err_packet_send_interval);
ACIO_MODULE_HOOK(ac_io_i36g_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_i36g_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_i36g_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_i36g_watchdog_off);
// I36S links
this->hook((void *) ac_io_i36g_update_control_status_buffer,
"ac_io_i36s_update_control_status_buffer");
this->hook((void *) ac_io_i36g_get_control_status_buffer,
"ac_io_i36s_get_control_status_buffer");
this->hook((void *) ac_io_i36g_set_cmdmode,
"ac_io_i36s_set_cmdmode");
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class I36GModule : public ACIOModule {
public:
I36GModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+125
View File
@@ -0,0 +1,125 @@
#include "i36i.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "misc/eamuse.h"
#include "avs/game.h"
//using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[48];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static bool __cdecl ac_io_i36i_ps3_controller_pwr_on() {
return true;
}
static bool __cdecl ac_io_i36i_ps3_controller_pwr_off() {
return true;
}
static bool __cdecl ac_io_i36i_create_get_status_thread() {
return true;
}
static bool __cdecl ac_io_i36i_destroy_get_status_thread() {
return true;
}
static bool __cdecl ac_io_i36i_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// Winning Eleven
if (avs::game::is_model({ "KCK", "NCK" })) {
// TODO
}
// success
return true;
}
static bool __cdecl ac_io_i36i_get_control_status_buffer(uint8_t *buffer) {
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
return true;
}
static bool __cdecl ac_io_i36i_usb_controller_bus_IO() {
return true;
}
static bool __cdecl ac_io_i36i_usb_controller_bus_PC() {
return true;
}
static bool __cdecl ac_io_i36i_req_get_usb_desc(int a1) {
return true;
}
static bool __cdecl ac_io_i36i_req_get_usb_desc_isfinished(
int a1, uint32_t *a2, int a3, uint32_t *out_size, uint8_t *in_data, unsigned int in_size) {
// DualShock 3 device descriptor
static uint8_t DS3_DESC[] {
0x12, // bLength
0x01, // bDescriptorType (Device)
0x00, 0x02, // bcdUSB 2.00
0x00, // bDeviceClass (Use class information in the Interface Descriptors)
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
0x40, // bMaxPacketSize0 64
0x4C, 0x05, // idVendor 0x054C
0x68, 0x02, // idProduct 0x0268
0x00, 0x01, // bcdDevice 1.00
0x01, // iManufacturer (String Index)
0x02, // iProduct (String Index)
0x00, // iSerialNumber (String Index)
0x01, // bNumConfigurations 1
};
// copy descriptor to buffer
*out_size = MIN(sizeof(DS3_DESC), in_size);
memcpy(in_data, DS3_DESC, *out_size);
// we apparently need this too
*a2 = 3;
// return success
return true;
}
/*
* Module stuff
*/
acio::I36IModule::I36IModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("I36I", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::I36IModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_i36i_ps3_controller_pwr_on);
ACIO_MODULE_HOOK(ac_io_i36i_ps3_controller_pwr_off);
ACIO_MODULE_HOOK(ac_io_i36i_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_i36i_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_i36i_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_i36i_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_i36i_usb_controller_bus_IO);
ACIO_MODULE_HOOK(ac_io_i36i_usb_controller_bus_PC);
ACIO_MODULE_HOOK(ac_io_i36i_req_get_usb_desc);
ACIO_MODULE_HOOK(ac_io_i36i_req_get_usb_desc_isfinished);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class I36IModule : public ACIOModule {
public:
I36IModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+488
View File
@@ -0,0 +1,488 @@
#include <cmath>
#include "icca.h"
#include "avs/game.h"
#include "misc/eamuse.h"
#include "util/time.h"
// settings
namespace acio {
bool ICCA_FLIP_ROWS = false;
bool ICCA_COMPAT_ACTIVE = false;
}
/*
* Helpers
*/
struct ICCA_STATUS {
uint8_t status_code;
uint8_t solenoid;
uint8_t front_sensor;
uint8_t rear_sensor;
uint8_t uid[8];
int32_t error;
uint32_t key_edge;
uint32_t key_level;
};
struct ICCA_STATUS_LA9 {
uint8_t status_code;
uint8_t card_in;
uint8_t uid[8];
uint8_t error;
uint8_t uid2[8];
};
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
enum ICCA_WORKFLOW {
STEP,
SLEEP,
START,
INIT,
READY,
GET_USERID,
ACTIVE,
EJECT,
EJECT_CHECK,
END,
CLOSE_EJECT,
CLOSE_E_CHK,
CLOSE_END,
ERR_GETUID = -2
};
struct ICCA_UNIT {
struct ICCA_STATUS status {};
enum ICCA_WORKFLOW state = STEP;
bool card_cmd_pressed = false;
bool card_in = false;
double card_in_time = 0.0;
char key_serial = 0;
bool uid_skip = false;
bool initialized = false;
int felica_retries = 0;
};
static ICCA_UNIT ICCA_UNITS[2] {};
static bool IS_LAST_CARD_FELICA = false;
static bool STATUS_BUFFER_FREEZE = false;
static double CARD_TIMEOUT = 2.0;
static inline int icca_get_active_count() {
int active_count = 0;
for (auto unit : ICCA_UNITS) {
active_count += unit.initialized;
}
return active_count;
}
static inline int icca_get_unit_id(int unit_id) {
if (icca_get_active_count() < 2)
return 1;
else {
if (unit_id > 1) {
return 1;
} else {
return 0;
}
}
}
static inline void update_card(int unit_id) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return;
}
// eamio keypress
int index = unit_id > 0 && icca_get_active_count() > 1 ? 1 : 0;
bool kb_insert_press = (eamuse_get_keypad_state(index) & (1 << EAM_IO_INSERT)) > 0;
static bool kb_insert_press_old[2] = {false, false};
// get unit
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
const bool card_presented = eamuse_card_insert_consume(icca_get_active_count(), unit_id);
const bool key_pressed = (kb_insert_press && !kb_insert_press_old[unit_id]);
// beatstream and nostalgia have logic that requires ac_io_icca_get_uid_felica to return the
// exact same card number multiple times in a row in order for the card number to be read...
// for whatever reason setting this to 3-4 doesn't work the very first time the game boots up
// so we use 10 just to be safe
const bool need_felica_retries = avs::game::is_model({"NBT", "PAN"});
if (need_felica_retries && (card_presented || key_pressed)) {
unit->felica_retries = 10;
}
// check for card insert
if (card_presented || key_pressed || (0 < unit->felica_retries)) {
if (!unit->card_cmd_pressed) {
unit->card_cmd_pressed = true;
if (unit->state == GET_USERID || unit->state == CLOSE_EJECT || unit->state == STEP) {
if (unit->uid_skip || eamuse_get_card(icca_get_active_count(), unit_id, unit->status.uid)) {
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->status.error = 0;
} else {
unit->state = ERR_GETUID;
memset(unit->status.uid, 0, 8);
}
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
} else if (unit->state == EJECT_CHECK) {
unit->state = SLEEP;
unit->card_in = false;
}
} else {
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->status.error = 0;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
}
} else {
unit->card_cmd_pressed = false;
unit->state = CLOSE_EJECT;
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
unit->card_in = false;
}
}
// save state
kb_insert_press_old[unit_id] = kb_insert_press;
}
static bool KEYPAD_LAST[2][12];
static uint32_t KEYPAD_EAMUSE_MAPPING[] = {
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
static uint32_t KEYPAD_KEY_CODES[] = {
0x100,
0x200,
0x2000,
2,
0x400,
0x4000,
4,
0x800,
0x8000,
8,
1,
0x1000
};
static uint32_t KEYPAD_KEY_CODE_NUMS[] = {
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
static inline void keypad_update(int unit_id) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return;
}
// reset unit
struct ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->status.key_level = 0;
unit->status.error = 0;
// get eamu state
int index = unit_id > 0 && icca_get_active_count() > 1 ? 1 : 0;
uint16_t eamu_state = eamuse_get_keypad_state(index);
// iterate keypad
for (int n = 0; n < 12; n++) {
int i = n;
// flip 123 with 789
if (acio::ICCA_FLIP_ROWS) {
if (!n)
i = 11;
else if (n < 4)
i = n + 6;
else if (n > 6 && n < 10)
i = n - 6;
else if (n == 11)
i = 0;
}
// check if pressed
if ((eamu_state & (1 << KEYPAD_EAMUSE_MAPPING[i])))
{
unit->status.key_level |= KEYPAD_KEY_CODES[i];
if (!KEYPAD_LAST[unit_id][i]) {
unit->status.key_edge = KEYPAD_KEY_CODES[n] << 16;
unit->status.key_edge |= 0x80 | (unit->key_serial << 4) | KEYPAD_KEY_CODE_NUMS[n];
unit->key_serial += 1;
unit->key_serial &= 0x07;
}
KEYPAD_LAST[unit_id][i] = true;
} else {
unit->status.key_edge &= ~(KEYPAD_KEY_CODES[n] << 16);
KEYPAD_LAST[unit_id][i] = false;
}
}
}
/*
* Implementations
*/
static bool __cdecl ac_io_icca_cardunit_init(int unit_id) {
unit_id = icca_get_unit_id(unit_id);
// dirty workaround code
if (icca_get_active_count() < 1)
ICCA_UNITS[unit_id].initialized = true;
else {
ICCA_UNITS[0].initialized = true;
ICCA_UNITS[1].initialized = true;
}
// initial poll
eamuse_get_keypad_state(unit_id);
// return success
return true;
}
static char __cdecl ac_io_icca_cardunit_init_isfinished(int unit_id, DWORD *status) {
*status = READY;
return 1;
}
static char __cdecl ac_io_icca_crypt_init(int unit_id) {
return 1;
}
static char __cdecl ac_io_icca_device_control_iccard_power_supply_off(int unit_id) {
return 1;
}
static char __cdecl ac_io_icca_device_control_iccard_power_supply_on(int unit_id) {
return 1;
}
static bool __cdecl ac_io_icca_device_control_isfinished(int unit_id, DWORD *a2) {
if (a2 && avs::game::is_model("KFC")) {
*a2 = 6;
}
return true;
}
static bool __cdecl ac_io_icca_get_keep_alive_error(int unit_id, DWORD *a2) {
*a2 = 0;
return false;
}
static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
// Metal Gear Arcade and Charge Machine had the args swapped so we need to check for valid pointers!
if (reinterpret_cast<uintptr_t>(a2) > 2) {
std::swap(a1, a2);
// honestly this could be used to detect if compat mode should be active
// but we are too lazy to check if all games still work with this change
//acio::ICCA_COMPAT_ACTIVE = true;
}
// and best just leave this casting mess alone unless something is wrong with it.
// long long is required because casting to int loses precision on 64-bit
void *status = a1;
int unit_id = static_cast<int>(reinterpret_cast<long long>(a2));
// update state
unit_id = icca_get_unit_id(unit_id);
keypad_update(unit_id);
update_card(unit_id);
// copy state to output buffer
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->status.status_code = unit->state;
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
// funny workaround
if (acio::ICCA_COMPAT_ACTIVE) {
if (avs::game::is_model("LA9")) {
auto p = (ICCA_STATUS*) status;
ICCA_STATUS_LA9 p_la9;
p_la9.status_code = unit->state;
p_la9.card_in = unit->card_in;
memcpy(p_la9.uid, p->uid, sizeof(p_la9.uid));
p_la9.error = p->error;
memcpy(p_la9.uid2, p->uid, sizeof(p_la9.uid));
memcpy(status, &p_la9, sizeof(ICCA_STATUS_LA9));
} else {
// the struct is different (28 bytes instead of 24) but nobody ain't got time for that
auto p = (ICCA_STATUS*) status;
p->error = p->key_level << 16;
p->front_sensor = p->uid[0];
p->rear_sensor = p->uid[1];
for (size_t i = 2; i < sizeof(p->uid); i++) {
p->uid[i - 2] = p->uid[i];
}
p->uid[sizeof(p->uid) - 2] = 0;
p->uid[sizeof(p->uid) - 1] = 0;
}
}
return 1;
}
static char __cdecl ac_io_icca_get_uid(int unit_id, char *card) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
// copy card
memcpy(card, unit->status.uid, 8);
// set felica flag
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
// check for error
return unit->state != ERR_GETUID;
}
static char __cdecl ac_io_icca_get_uid_felica(int unit_id, char *card) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
// copy card
memcpy(card, unit->status.uid, 8);
// set felica flag
bool felica = is_card_uid_felica(unit->status.uid);
card[8] = (char) (felica ? 1 : 0);
IS_LAST_CARD_FELICA = felica;
if (0 < unit->felica_retries) {
unit->felica_retries--;
}
// check for error
return unit->state != ERR_GETUID;
}
static bool __cdecl ac_io_icca_is_felica() {
return IS_LAST_CARD_FELICA;
}
static char __cdecl ac_io_icca_req_uid(int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->state = GET_USERID;
update_card(unit_id);
return 1;
}
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (unit->card_in) {
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
unit->state = END;
} else {
unit->state = ERR_GETUID;
}
unit->card_in = false;
}
*read_state = unit->state;
return 1;
}
static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
return 0;
}
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
case STEP:
if (avs::game::is_model("JDZ"))
unit->state = SLEEP;
else
unit->state = STEP;
break;
case SLEEP:
unit->state = SLEEP;
break;
case INIT:
unit->state = READY;
break;
case START:
if (unit->card_in)
unit->state = ACTIVE;
else
unit->state = READY;
break;
case EJECT:
unit->card_in = false;
break;
case CLOSE_EJECT:
unit->state = unit->card_in ? EJECT_CHECK : SLEEP;
break;
case CLOSE_END:
unit->state = SLEEP;
break;
case GET_USERID:
unit->state = GET_USERID;
break;
default:
break;
}
return unit->state;
}
static char __cdecl ac_io_icca_req_status(int a1, char a2) {
return 1;
}
static bool __cdecl ac_io_icca_req_status_isfinished(int a1, int *a2) {
*a2 = 11;
return true;
}
/*
* Module stuff
*/
acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("ICCA", module, hookMode) {
this->status_buffer = (uint8_t*) &ICCA_UNITS[0];
this->status_buffer_size = sizeof(ICCA_UNITS);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::ICCAModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
ACIO_MODULE_HOOK(ac_io_icca_crypt_init);
ACIO_MODULE_HOOK(ac_io_icca_device_control_iccard_power_supply_off);
ACIO_MODULE_HOOK(ac_io_icca_device_control_iccard_power_supply_on);
ACIO_MODULE_HOOK(ac_io_icca_device_control_isfinished);
ACIO_MODULE_HOOK(ac_io_icca_get_keep_alive_error);
ACIO_MODULE_HOOK(ac_io_icca_get_status);
ACIO_MODULE_HOOK(ac_io_icca_get_uid);
ACIO_MODULE_HOOK(ac_io_icca_get_uid_felica);
ACIO_MODULE_HOOK(ac_io_icca_is_felica);
ACIO_MODULE_HOOK(ac_io_icca_req_uid);
ACIO_MODULE_HOOK(ac_io_icca_req_uid_isfinished);
ACIO_MODULE_HOOK(ac_io_icca_send_keep_alive_packet);
ACIO_MODULE_HOOK(ac_io_icca_workflow);
ACIO_MODULE_HOOK(ac_io_icca_req_status);
ACIO_MODULE_HOOK(ac_io_icca_req_status_isfinished);
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "../module.h"
namespace acio {
// settings
extern bool ICCA_FLIP_ROWS;
extern bool ICCA_COMPAT_ACTIVE;
class ICCAModule : public ACIOModule {
public:
ICCAModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
static inline bool is_card_uid_felica(uint8_t *uid) {
return uid[0] != 0xE0 && uid[1] != 0x04;
}
+158
View File
@@ -0,0 +1,158 @@
#include "j32d.h"
#include "avs/game.h"
#include "games/ftt/io.h"
#include "games/scotto/io.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace GameAPI;
// static stuff
static uint32_t STATUS_BUFFER[20] {};
static bool STATUS_BUFFER_FREEZE = false;
static uint32_t STATUS_BUFFER_COUNTER = 1;
/*
* Implementations
*/
static bool __cdecl ac_io_j32d_get_control_status_buffer(size_t a1, void* buffer, int a3) {
// set counter
STATUS_BUFFER[14] = STATUS_BUFFER_COUNTER++;
// copy buffer
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
// return success
return true;
}
static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// FutureTomTom
if (avs::game::is_model("MMD")) {
// process buttons
auto &buttons = games::ftt::get_buttons();
float pad1_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad1));
float pad2_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad2));
float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3));
float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4));
// FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad1))) {
STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) {
STATUS_BUFFER[7] = (int) (51.f * pad2_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad3))) {
STATUS_BUFFER[8] = (int) (51.f * pad3_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad4))) {
STATUS_BUFFER[9] = (int) (51.f * pad4_vel + 0.5f);
}
// process analogs
auto &analogs = games::ftt::get_analogs();
auto &pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
auto &pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
auto &pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
auto &pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
if (pad1_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f);
STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val);
}
if (pad2_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad2_analog) + 0.5f);
STATUS_BUFFER[7] = MAX(STATUS_BUFFER[7], val);
}
if (pad3_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad3_analog) + 0.5f);
STATUS_BUFFER[8] = MAX(STATUS_BUFFER[8], val);
}
if (pad4_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad4_analog) + 0.5f);
STATUS_BUFFER[9] = MAX(STATUS_BUFFER[9], val);
}
}
// Scotto
if (avs::game::is_model("NSC")) {
// get buttons
auto &buttons = games::scotto::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Cup1))) {
STATUS_BUFFER[5] |= 0x1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Cup2))) {
STATUS_BUFFER[5] |= 0x2;
}
// process button emulation for pads
float first_pad_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::FirstPad));
float pad_a_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadA));
float pad_b_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadB));
float pad_c_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadC));
float pad_d_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadD));
float pad_e_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadE));
float pad_f_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadF));
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::FirstPad))) {
STATUS_BUFFER[6] = (int) (191.f * first_pad_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadA))) {
STATUS_BUFFER[7] = (int) (51.f * pad_a_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadB))) {
STATUS_BUFFER[8] = (int) (51.f * pad_b_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadC))) {
STATUS_BUFFER[9] = (int) (51.f * pad_c_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadD))) {
STATUS_BUFFER[10] = (int) (51.f * pad_d_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadE))) {
STATUS_BUFFER[11] = (int) (51.f * pad_e_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadF))) {
STATUS_BUFFER[12] = (int) (51.f * pad_f_vel + 0.5f);
}
// TODO(felix): analogs
}
// success
return true;
}
/*
* Module stuff
*/
acio::J32DModule::J32DModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("J32D", module, hookMode) {
this->status_buffer = (uint8_t*) &STATUS_BUFFER[0];
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::J32DModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_j32d_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_j32d_update_control_status_buffer);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class J32DModule : public ACIOModule {
public:
J32DModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+504
View File
@@ -0,0 +1,504 @@
#include "kfca.h"
#include "avs/game.h"
#include "games/bs/io.h"
#include "games/nost/io.h"
#include "games/scotto/io.h"
#include "games/sdvx/sdvx.h"
#include "games/sdvx/io.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// globals
uint8_t KFCA_VOL_SOUND = 96;
uint8_t KFCA_VOL_HEADPHONE = 96;
uint8_t KFCA_VOL_EXTERNAL = 96;
uint8_t KFCA_VOL_WOOFER = 96;
// static stuff
static uint8_t STATUS_BUFFER[64] {};
static bool STATUS_BUFFER_FREEZE = false;
static unsigned int KFCA_VOLL = 0;
static unsigned int KFCA_VOLR = 0;
/*
* Implementations
*/
static int __cdecl ac_io_kfca_control_button_led(unsigned int button, bool state) {
// Sound Voltex
if (avs::game::is_model("KFC")) {
// control mapping
static const size_t mapping[] = {
games::sdvx::Lights::BT_A,
games::sdvx::Lights::BT_B,
games::sdvx::Lights::BT_C,
games::sdvx::Lights::BT_D,
games::sdvx::Lights::FX_L,
games::sdvx::Lights::FX_R,
games::sdvx::Lights::START,
games::sdvx::Lights::GENERATOR_B,
};
// check if button is mapped
if (button < 8) {
// get lights
auto &lights = games::sdvx::get_lights();
// write light
float value = state ? 1.f : 0.f;
Lights::writeLight(RI_MGR, lights.at(mapping[button]), value);
}
}
// Scotto
if (avs::game::is_model("NSC")) {
// control mapping
static const size_t mapping[] = {
games::scotto::Lights::PAD_F_B,
games::scotto::Lights::PAD_E_R,
games::scotto::Lights::PAD_E_B,
~0u,
~0u,
~0u,
games::scotto::Lights::PAD_F_R,
games::scotto::Lights::BUTTON,
};
// check if button is mapped
if (button < std::size(mapping) && button[mapping] != ~0u) {
// get lights
auto &lights = games::scotto::get_lights();
// write light
float value = state ? 1.f : 0.f;
Lights::writeLight(RI_MGR, lights.at(mapping[button]), value);
}
}
// return success
return 1;
}
static int __cdecl ac_io_kfca_control_coin_blocker_close(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_kfca_control_coin_blocker_open(int a1) {
eamuse_coin_set_block(false);
return 1;
}
static int __cdecl ac_io_kfca_control_led_bright(uint32_t led_field, uint8_t brightness) {
// Sound Voltex
if (avs::game::is_model("KFC")) {
// get lights
auto &lights = games::sdvx::get_lights();
// control mapping
static const size_t mapping[] {
games::sdvx::Lights::WING_LEFT_UP_R,
games::sdvx::Lights::WING_LEFT_UP_G,
games::sdvx::Lights::WING_LEFT_UP_B,
games::sdvx::Lights::WING_RIGHT_UP_R,
games::sdvx::Lights::WING_RIGHT_UP_G,
games::sdvx::Lights::WING_RIGHT_UP_B,
games::sdvx::Lights::WING_LEFT_LOW_R,
games::sdvx::Lights::WING_LEFT_LOW_G,
games::sdvx::Lights::WING_LEFT_LOW_B,
games::sdvx::Lights::WING_RIGHT_LOW_R,
games::sdvx::Lights::WING_RIGHT_LOW_G,
games::sdvx::Lights::WING_RIGHT_LOW_B,
games::sdvx::Lights::WOOFER_R,
games::sdvx::Lights::WOOFER_G,
games::sdvx::Lights::WOOFER_B,
games::sdvx::Lights::CONTROLLER_R,
games::sdvx::Lights::CONTROLLER_G,
games::sdvx::Lights::CONTROLLER_B,
games::sdvx::Lights::GENERATOR_R,
games::sdvx::Lights::GENERATOR_G,
};
// write light
float value = brightness / 255.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
}
// BeatStream
if (avs::game::is_model("NBT")) {
// get lights
auto &lights = games::bs::get_lights();
// mapping
static const size_t mapping[] {
~0u, ~0u, ~0u,
games::bs::Lights::RightR,
games::bs::Lights::RightG,
games::bs::Lights::RightB,
games::bs::Lights::LeftR,
games::bs::Lights::LeftG,
games::bs::Lights::LeftB,
games::bs::Lights::BottomR,
games::bs::Lights::BottomG,
games::bs::Lights::BottomB,
};
// write light
float value = brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] != ~0u && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
}
// Nostalgia
if (avs::game::is_model("PAN")) {
// get lights
auto &lights = games::nost::get_lights();
// mapping
static const size_t mapping[] {
~0u, ~0u, ~0u,
games::nost::Lights::TitleR,
games::nost::Lights::TitleG,
games::nost::Lights::TitleB,
~0u, ~0u, ~0u,
games::nost::Lights::BottomR,
games::nost::Lights::BottomG,
games::nost::Lights::BottomB,
};
// write light
float value = brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] != ~0u && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
}
// Scotto
if (avs::game::is_model("NSC")) {
// get lights
auto &lights = games::scotto::get_lights();
// mapping
static const size_t mapping[] {
games::scotto::Lights::CUP_R,
games::scotto::Lights::CUP_G,
games::scotto::Lights::CUP_B,
games::scotto::Lights::PAD_A_R,
games::scotto::Lights::PAD_A_G,
games::scotto::Lights::PAD_A_B,
games::scotto::Lights::PAD_B_R,
games::scotto::Lights::PAD_B_G,
games::scotto::Lights::PAD_B_B,
games::scotto::Lights::PAD_C_R,
games::scotto::Lights::PAD_C_G,
games::scotto::Lights::PAD_C_B,
games::scotto::Lights::PAD_D_R,
games::scotto::Lights::PAD_D_G,
games::scotto::Lights::PAD_D_B,
games::scotto::Lights::FIRST_PAD_R,
games::scotto::Lights::FIRST_PAD_G,
games::scotto::Lights::FIRST_PAD_B,
games::scotto::Lights::PAD_F_G,
games::scotto::Lights::PAD_E_G,
};
// write light
float value = brightness / 255.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
}
// return success
return 1;
}
static char __cdecl ac_io_kfca_current_coinstock(int a1, DWORD *a2) {
*a2 = (DWORD) eamuse_coin_get_stock();
return 1;
}
static void *__cdecl ac_io_kfca_get_control_status_buffer(void *target_buffer) {
// copy buffer
return memcpy(target_buffer, STATUS_BUFFER, 64);
}
static int __cdecl ac_io_kfca_lock_coincounter(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static bool __cdecl ac_io_kfca_req_volume_control(
uint8_t vol_sound, uint8_t vol_headphone, uint8_t vol_external, uint8_t vol_woofer) {
// update globals
KFCA_VOL_SOUND = vol_sound;
KFCA_VOL_HEADPHONE = vol_headphone;
KFCA_VOL_EXTERNAL = vol_external;
KFCA_VOL_WOOFER = vol_woofer;
// Sound Voltex
if (avs::game::is_model("KFC")) {
// get lights
auto &lights = games::sdvx::get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_SOUND],
(100 - vol_sound) / 100.f);
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_HEADPHONE],
(100 - vol_headphone) / 100.f);
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_EXTERNAL],
(100 - vol_external) / 100.f);
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_WOOFER],
(100 - vol_woofer) / 100.f);
}
return true;
}
static bool __cdecl ac_io_kfca_set_watchdog_time(short a1) {
return true;
}
static char __cdecl ac_io_kfca_unlock_coincounter(int a1) {
eamuse_coin_set_block(false);
return 1;
}
static char __cdecl ac_io_kfca_update_control_status_buffer() {
static const int input_offset = 4;
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, 64);
// SDVX
if (avs::game::is_model("KFC")) {
// get buttons
auto &buttons = games::sdvx::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Test))) {
STATUS_BUFFER[input_offset + 1] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Service))) {
STATUS_BUFFER[input_offset + 1] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::CoinMech))) {
STATUS_BUFFER[input_offset + 1] |= 0x04;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Start))) {
STATUS_BUFFER[input_offset + 9] |= 0x08;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_A))) {
STATUS_BUFFER[input_offset + 9] |= 0x04;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_B))) {
STATUS_BUFFER[input_offset + 9] |= 0x02;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_C))) {
STATUS_BUFFER[input_offset + 9] |= 0x01;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_D))) {
STATUS_BUFFER[input_offset + 11] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_L))) {
STATUS_BUFFER[input_offset + 11] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_R))) {
STATUS_BUFFER[input_offset + 11] |= 0x08;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Headphone))) {
STATUS_BUFFER[input_offset + 9] |= 0x20;
}
// volume left
const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_l_state == socd::SocdCW) {
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_r_state == socd::SocdCW) {
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// update volumes
auto &analogs = games::sdvx::get_analogs();
auto vol_left = KFCA_VOLL;
auto vol_right = KFCA_VOLR;
if (analogs.at(0).isSet() || analogs.at(1).isSet()) {
vol_left += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_L)) * 1023.99f);
vol_right += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_R)) * 1023.99f);
}
// proper loops
vol_left %= 1024;
vol_right %= 1024;
log_debug("kfca", "knobs = {} {}", vol_left, vol_right);
// save volumes in buffer
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
STATUS_BUFFER[input_offset + 16 + 1] |= (unsigned char) ((vol_left >> 2) & 0xFF);
STATUS_BUFFER[input_offset + 16 + 2] |= (unsigned char) ((vol_right << 6) & 0xFF);
STATUS_BUFFER[input_offset + 16 + 3] |= (unsigned char) ((vol_right >> 2) & 0xFF);
}
// Beatstream
if (avs::game::is_model("NBT")) {
// get buttons
auto &buttons = games::bs::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::bs::Buttons::Test))) {
STATUS_BUFFER[input_offset + 1] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bs::Buttons::Service))) {
STATUS_BUFFER[input_offset + 1] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bs::Buttons::CoinMech))) {
STATUS_BUFFER[input_offset + 1] |= 0x04;
}
}
// Nostalgia
if (avs::game::is_model("PAN")) {
// get buttons
auto &buttons = games::nost::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::Service))) {
STATUS_BUFFER[input_offset + 1] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::Test))) {
STATUS_BUFFER[input_offset + 1] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::CoinMech))) {
STATUS_BUFFER[input_offset + 1] |= 0x04;
}
}
// Scotto
if (avs::game::is_model("NSC")) {
// get buttons
auto &buttons = games::scotto::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Test)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 1] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Service)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 1] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::CoinMech)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 1] |= 0x04;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Start)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 9] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Up)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 9] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Down)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 9] |= 0x08;
}
// the code also checks `input_offset + 9` for 0x01 but that does not trigger any response
// in the "I/O CHECK" scene
}
// success
return true;
}
static void __cdecl ac_io_kfca_watchdog_off() {
}
// yes this is spelled "marge" instead of "merge"
static int __cdecl ac_io_kfca_set_status_marge_func(void *cb) {
return 1;
}
/*
* Module stuff
*/
acio::KFCAModule::KFCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("KFCA", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::KFCAModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_kfca_control_button_led);
ACIO_MODULE_HOOK(ac_io_kfca_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_kfca_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_kfca_control_led_bright);
ACIO_MODULE_HOOK(ac_io_kfca_current_coinstock);
ACIO_MODULE_HOOK(ac_io_kfca_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_kfca_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_kfca_req_volume_control);
ACIO_MODULE_HOOK(ac_io_kfca_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_kfca_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_kfca_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_kfca_watchdog_off);
ACIO_MODULE_HOOK(ac_io_kfca_set_status_marge_func);
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "../module.h"
namespace acio {
extern uint8_t KFCA_VOL_SOUND;
extern uint8_t KFCA_VOL_HEADPHONE;
extern uint8_t KFCA_VOL_EXTERNAL;
extern uint8_t KFCA_VOL_WOOFER;
class KFCAModule : public ACIOModule {
public:
KFCAModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+238
View File
@@ -0,0 +1,238 @@
#include "klpa.h"
#include "avs/game.h"
#include "games/loveplus/io.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
using namespace GameAPI;
static uint8_t STATUS_BUFFER[48];
static bool STATUS_BUFFER_FREEZE = false;
static const size_t LOVEPLUS_LIGHTS_MAPPING[] = {
games::loveplus::Lights::Red,
games::loveplus::Lights::Green,
games::loveplus::Lights::Blue,
SIZE_MAX,
games::loveplus::Lights::Right,
games::loveplus::Lights::Left,
};
static char __cdecl ac_io_klpa_consume_coinstock(int a1, DWORD *a2) {
*a2 = (DWORD) eamuse_coin_get_stock();
return 1;
}
static int __cdecl ac_io_klpa_control_coin_blocker_close(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_klpa_control_coin_blocker_open(int a1) {
eamuse_coin_set_block(false);
return 1;
}
static int __cdecl ac_io_klpa_control_led_off(size_t index) {
// LovePlus
if (avs::game::is_model("KLP") && index < std::size(LOVEPLUS_LIGHTS_MAPPING)) {
// get lights
auto &lights = games::loveplus::get_lights();
if (LOVEPLUS_LIGHTS_MAPPING[index] != SIZE_MAX) {
Lights::writeLight(RI_MGR, lights.at(LOVEPLUS_LIGHTS_MAPPING[index]), 0.f);
}
}
// return success
return 1;
}
static int __cdecl ac_io_klpa_control_led_on(size_t index) {
// LovePlus
if (avs::game::is_model("KLP") && index < std::size(LOVEPLUS_LIGHTS_MAPPING)) {
// get lights
auto &lights = games::loveplus::get_lights();
if (LOVEPLUS_LIGHTS_MAPPING[index] != SIZE_MAX) {
Lights::writeLight(RI_MGR, lights.at(LOVEPLUS_LIGHTS_MAPPING[index]), 1.f);
}
}
// return success
return 1;
}
static bool __cdecl ac_io_klpa_create_get_status_thread() {
return 1;
}
static char __cdecl ac_io_klpa_current_coinstock(int a1, DWORD *a2) {
// check bounds
if (a1 < 0 || a1 >= 2) {
return 0;
}
*a2 = (DWORD) eamuse_coin_get_stock();
// return success
return 1;
}
static bool __cdecl ac_io_klpa_destroy_get_status_thread() {
return 1;
}
static void* __cdecl ac_io_klpa_get_control_status_buffer(void *a1) {
// copy buffer
return memcpy(a1, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static void __cdecl ac_io_klpa_get_io_command_mode(void *a1) {
memset(a1, 0, 4);
}
static int __cdecl ac_io_klpa_led_reset() {
if (avs::game::is_model("KLP")) {
// get lights
auto &lights = games::loveplus::get_lights();
for (const auto &mapping : LOVEPLUS_LIGHTS_MAPPING) {
if (mapping != SIZE_MAX) {
Lights::writeLight(RI_MGR, lights.at(mapping), 0.f);
}
}
}
return 1;
}
static int __cdecl ac_io_klpa_lock_coincounter(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static bool __cdecl ac_io_klpa_set_io_command_mode(int a1) {
return true;
}
static bool __cdecl ac_io_klpa_set_io_command_mode_is_finished(uint8_t *a1) {
*a1 = 0;
return true;
}
static int __cdecl ac_io_klpa_set_led_bright(size_t index, uint8_t brightness) {
// LovePlus
if (avs::game::is_model("KLP") && index < std::size(LOVEPLUS_LIGHTS_MAPPING)) {
// get lights
auto &lights = games::loveplus::get_lights();
if (LOVEPLUS_LIGHTS_MAPPING[index] != SIZE_MAX) {
Lights::writeLight(RI_MGR, lights.at(LOVEPLUS_LIGHTS_MAPPING[index]), brightness / 127.f);
}
}
return 1;
}
static bool __cdecl ac_io_klpa_set_sound_mute(int a1) {
return true;
}
static bool __cdecl ac_io_klpa_set_sound_mute_is_finished(int a1) {
return true;
}
static bool __cdecl ac_io_klpa_set_watchdog_time(short a1) {
return true;
}
static char __cdecl ac_io_klpa_unlock_coincounter(int a1) {
eamuse_coin_set_block(false);
return 1;
}
static bool __cdecl ac_io_klpa_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// reset buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// LovePlus
if (avs::game::is_model("KLP")) {
// get buttons
auto &buttons = games::loveplus::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::loveplus::Buttons::Test)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[5] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::loveplus::Buttons::Service)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[5] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::loveplus::Buttons::Left)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[12] |= 1 << 6;
}
if (Buttons::getState(RI_MGR, buttons.at(games::loveplus::Buttons::Right)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[12] |= 1 << 7;
}
// x[9] & 0x3F) = volume output level?
// x[11] & 0x3F) = volume output level?
// x[12] |= (1 << 4) = headphone jack
}
// success
return true;
}
/*
* Module stuff
*/
acio::KLPAModule::KLPAModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("KLPA", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::KLPAModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_klpa_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_klpa_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_klpa_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_klpa_control_led_off);
ACIO_MODULE_HOOK(ac_io_klpa_control_led_on);
ACIO_MODULE_HOOK(ac_io_klpa_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_klpa_current_coinstock);
ACIO_MODULE_HOOK(ac_io_klpa_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_klpa_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_klpa_get_io_command_mode);
ACIO_MODULE_HOOK(ac_io_klpa_led_reset);
ACIO_MODULE_HOOK(ac_io_klpa_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_klpa_set_io_command_mode);
ACIO_MODULE_HOOK(ac_io_klpa_set_io_command_mode_is_finished);
ACIO_MODULE_HOOK(ac_io_klpa_set_led_bright);
ACIO_MODULE_HOOK(ac_io_klpa_set_sound_mute);
ACIO_MODULE_HOOK(ac_io_klpa_set_sound_mute_is_finished);
ACIO_MODULE_HOOK(ac_io_klpa_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_klpa_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_klpa_update_control_status_buffer);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class KLPAModule : public ACIOModule {
public:
KLPAModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+113
View File
@@ -0,0 +1,113 @@
#include "la9a.h"
#include "games/pcm/io.h"
#include "hooks/graphics/graphics.h"
#include "touch/touch.h"
#include "util/utils.h"
#ifdef max
#undef max
#endif
namespace acio {
#pragma pack(push, 1)
struct la9a_control_status {
uint8_t p1 : 6;
uint8_t service_button : 1;
uint8_t test_button : 1;
uint8_t p2[9];
uint8_t lcd_counter;
uint8_t p3[5];
uint16_t touch_x;
uint16_t touch_y;
uint16_t touch_z;
uint8_t p4[26];
};
#pragma pack(pop)
static struct la9a_control_status CONTROL_STATUS {};
static bool TOUCH_ATTACHED = false;
static bool __cdecl ac_io_la9a_set_error_message(int, unsigned int, int) {
return true;
}
static bool __cdecl ac_io_la9a_update_control_status_buffer() {
CONTROL_STATUS.touch_z = 0xFF;
CONTROL_STATUS.test_button = 0;
CONTROL_STATUS.service_button = 0;
// attach touch handler on the first call to this function
if (!TOUCH_ATTACHED) {
log_misc("la9a", "attach touch handler");
HWND hwnd = FindWindowBeginsWith("LA9");
if (!hwnd) {
log_fatal("la9a", "LA9 window not found");
}
touch_create_wnd(hwnd);
graphics_hook_window(hwnd, nullptr);
if (GRAPHICS_SHOW_CURSOR) {
ShowCursor(1);
}
TOUCH_ATTACHED = true;
}
// update touch
std::vector<TouchPoint> touch_points;
touch_get_points(touch_points);
if (!touch_points.empty()) {
auto &touch_point = touch_points[0];
// TODO: `x` and `y` should be clamped [0, std::numeric_limits<uint16_t>::max())
CONTROL_STATUS.touch_x = static_cast<uint16_t>(touch_point.x);
CONTROL_STATUS.touch_y = static_cast<uint16_t>(touch_point.y);
CONTROL_STATUS.touch_z = 0;
}
CONTROL_STATUS.lcd_counter++;
// update buttons
auto &buttons = games::pcm::get_buttons();
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::pcm::Buttons::Test])) {
CONTROL_STATUS.test_button = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::pcm::Buttons::Service])) {
CONTROL_STATUS.service_button = 1;
}
return true;
}
static int __cdecl ac_io_la9a_update_counter(int, int) {
return 0;
}
static int __cdecl ac_io_la9a_update_lcd(int) {
return 1;
}
static void __cdecl ac_io_la9a_get_control_status_buffer(struct la9a_control_status *control_status) {
*control_status = CONTROL_STATUS;
}
LA9AModule::LA9AModule(HMODULE module, HookMode hookMode) : ACIOModule("LA9A", module, hookMode) {
//this->status_buffer = STATUS_BUFFER;
//this->status_buffer_size = sizeof(STATUS_BUFFER);
//this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void LA9AModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_la9a_set_error_message);
ACIO_MODULE_HOOK(ac_io_la9a_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_la9a_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_la9a_update_lcd);
ACIO_MODULE_HOOK(ac_io_la9a_update_counter);
}
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class LA9AModule : public ACIOModule {
public:
LA9AModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+503
View File
@@ -0,0 +1,503 @@
#include "mdxf.h"
#include "mdxf_poll.h"
#include "avs/game.h"
#include "games/ddr/io.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/utils.h"
#include <mutex>
#define MDFX_DEBUG_VERBOSE 0
#if MDFX_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// constants
const size_t STATUS_BUFFER_SIZE = 32;
const size_t STATUS_BUFFER_NUM_ENTRIES = 16;
// static stuff
static uint8_t HEAD_P1 = 0;
static uint8_t HEAD_P2 = 0;
static uint16_t PREV_STATE_P1 = 0;
static uint16_t PREV_STATE_P2 = 0;
static uint64_t PREV_TIME_P1 = 0;
static uint64_t PREV_TIME_P2 = 0;
static std::mutex MUTEX_P1;
static std::mutex MUTEX_P2;
static bool IS_MDXF_ACTIVE = false;
static const uint8_t BACKFILL_INTERVAL_MS = 4;
static const uint8_t BACKFILL_PADDING_MS = 2;
// These are used to determine if a thread needs to be spun up to keep pad state ring buffers populated with enough recent polls
static uint64_t START_TIME = 0;
static int CALL_COUNT = 0;
static const int THRESHOLD_REFRESH_RATE = 120;
static std::atomic<bool> IS_REFRESH_RATE_MEASUREMENT_STARTED{false};
static std::atomic<bool> IS_REFRESH_RATE_DETERMINED{false};
static std::atomic<bool> IS_THREAD_NEEDED{false};
static std::atomic<bool> MDXF_THREAD_RUNNING{false};
static std::thread MDXF_THREAD;
static constexpr int THREAD_REFRESH_RATE_HZ = 125;
static constexpr auto THREAD_PERIOD = std::chrono::milliseconds(1000 / THREAD_REFRESH_RATE_HZ);
// buffers
#pragma pack(push, 1)
static struct {
uint8_t STATUS_BUFFER_P1[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {};
uint8_t STATUS_BUFFER_P2[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {};
} BUFFERS {};
#pragma pack(pop)
static bool STATUS_BUFFER_FREEZE = false;
// Decides which method to use for populating ring buffer entries for "padding".
// Overwritten in spicecfg using P4IO Buffer Algorithm option.
// THREAD_MODE: Spins a thread running at THREAD_REFRESH_RATE_HZ which periodically fills the ring
// buffer with auxiliary entries. Falls back on BACKFILL_MODE
// BACKFILL_MODE: On every update cycle, fill the ring buffer with entries for the last known state
// BACKFILL_INTERVAL_MS apart from each other from the time of the last entry to the
// current time before adding the entry for the current state.
// AUTO_MODE: thread mode if <120Hz, backfill if >=120Hz
acio::MDXFBufferFillMode acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::AUTO_MODE;
typedef enum {
ARKMDXP4_POLL = 0,
INTERNAL_POLL = 1,
EXTERNAL_POLL = 2
} MDXFPollSource;
typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)();
static uint64_t arkGetTickTime64() {
static ARK_GET_TICK_TIME64_T getTickTime64 = nullptr;
if (!getTickTime64) {
HMODULE h = avs::game::DLL_INSTANCE;
if (h) {
getTickTime64 = (ARK_GET_TICK_TIME64_T)GetProcAddress(h, "arkGetTickTime64");
}
}
// this works on 32-bit versions of avs, but not on 64.
// it's better than nothing though.
return getTickTime64 ? getTickTime64() : timeGetTime();
}
// Used to keep the ring buffer populated with steady updates. 60Hz interval is too slow
static void mdxf_thread_start() {
bool expected = false;
if (!MDXF_THREAD_RUNNING.compare_exchange_strong(expected, true)) {
return;
}
log_info("mdxf", "starting poll thread");
MDXF_THREAD = std::thread([] {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
mdxf_poll(false);
std::this_thread::sleep_for(THREAD_PERIOD);
}
});
}
static void mdxf_thread_stop() {
if (!MDXF_THREAD_RUNNING.exchange(false)) {
return;
}
if (MDXF_THREAD.joinable()) {
MDXF_THREAD.join();
}
}
// Snaps measured refresh rate to best fit
static int snap_refresh_rate(int measured_hz) {
static constexpr std::array<int, 6> rates = {
60, 120, 144, 165, 180, 240
};
int best = rates[0];
int best_err = std::fabs(measured_hz - best);
for (int r : rates) {
int err = std::fabs(measured_hz - r);
if (err < best_err) {
best = r;
best_err = err;
}
}
return best;
}
// Increments the number of times the update function was called,
// then calculates the current refresh rate of the game
// (20 seconds after the game starts, until 25 seconds)
static void count_calls_from_game() {
if (IS_REFRESH_RATE_DETERMINED) {
return;
}
const uint64_t current_time = arkGetTickTime64();
if (!IS_REFRESH_RATE_MEASUREMENT_STARTED) {
if (START_TIME == 0) {
START_TIME = current_time;
}
// boot screen takes about 10 seconds, so let's wait for double that
if ((current_time - START_TIME) < 20000) {
// too early, do nothing
return;
} else {
// 20s has passed for the first time, start measuring on next call
IS_REFRESH_RATE_MEASUREMENT_STARTED = true;
START_TIME = current_time;
log_debug("mdxf", "measurement begin");
return;
}
}
const uint64_t elapsed_time = current_time - START_TIME;
CALL_COUNT++;
if (elapsed_time >= 5000) {
double measured_hz = static_cast<double>(CALL_COUNT) * 1000.0 / static_cast<double>(elapsed_time);
// Account for the main loop calling this twice per iteration
measured_hz *= 0.5;
const int snapped_hz = snap_refresh_rate(static_cast<int>(measured_hz));
IS_REFRESH_RATE_DETERMINED = true;
IS_THREAD_NEEDED = (snapped_hz < THRESHOLD_REFRESH_RATE);
log_info(
"mdxf",
"detected: {} Hz, best fit: {} Hz",
static_cast<int>(measured_hz),
snapped_hz);
if (IS_THREAD_NEEDED) {
mdxf_thread_start();
}
}
}
/*
* Implementations
*/
static uint64_t __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *out, uint8_t index, uint8_t head_in) {
// Default error value (matches original mask behavior)
auto error_ret = static_cast<uint64_t>(node - 0x11) & 0xFFFFFFFFFFFFFF00;
// Dance Dance Revolution
if (avs::game::is_model("MDX")) {
// Select player-specific state
std::mutex* mutex = nullptr;
uint8_t* head = nullptr;
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
size_t size = STATUS_BUFFER_NUM_ENTRIES;
if (node == 17 || node == 25) {
mutex = &MUTEX_P1;
head = &HEAD_P1;
buffer = BUFFERS.STATUS_BUFFER_P1;
} else if (node == 18 || node == 26) {
mutex = &MUTEX_P2;
head = &HEAD_P2;
buffer = BUFFERS.STATUS_BUFFER_P2;
} else {
memset(out, 0, STATUS_BUFFER_SIZE);
return error_ret;
}
std::lock_guard<std::mutex> lock(*mutex);
const uint8_t start_index = (head_in == 0xFF) ? *head : head_in;
// Compute ring index: walk backwards from start_index as index increases
// Assumes ring buffer size is a power of two
const size_t mask = size - 1;
const size_t offset = static_cast<size_t>(index) & mask;
const size_t i = (static_cast<size_t>(start_index) - offset + size) & mask;
// Copy the chosen entry
memcpy(out, buffer[i], STATUS_BUFFER_SIZE);
// Return the start value actually used
return static_cast<uint64_t>(start_index);
}
return error_ret;
}
static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2, uint8_t value) {
if (avs::game::is_model("MDX")) {
static const struct {
int a2[4];
} mapping[] = {
{
// a1 == 17
{
games::ddr::Lights::GOLD_P1_STAGE_UP_RIGHT,
games::ddr::Lights::GOLD_P1_STAGE_DOWN_LEFT,
games::ddr::Lights::GOLD_P1_STAGE_UP_LEFT,
games::ddr::Lights::GOLD_P1_STAGE_DOWN_RIGHT
}
},
{
// a1 == 18
{
games::ddr::Lights::GOLD_P2_STAGE_UP_RIGHT,
games::ddr::Lights::GOLD_P2_STAGE_DOWN_LEFT,
games::ddr::Lights::GOLD_P2_STAGE_UP_LEFT,
games::ddr::Lights::GOLD_P2_STAGE_DOWN_RIGHT
}
}
};
if ((a1 == 17 || a1 == 18) && (a2 < 4)) {
// get light from mapping
const auto light = mapping[a1 - 17].a2[a2];
// get lights
auto &lights = games::ddr::get_lights();
// write lights
GameAPI::Lights::writeLight(RI_MGR, lights[light], value / 128.f);
}
}
return true;
}
static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFPollSource source, uint64_t current_time) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// Dance Dance Revolution
if (avs::game::is_model("MDX")) {
// Marks this module as actively being used, allowing this function to be called from other sources
if (source == ARKMDXP4_POLL) {
if (!IS_MDXF_ACTIVE) {
log_debug("mdxf", "initializing mdxf I/O support");
IS_MDXF_ACTIVE = true;
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::THREAD_MODE) {
IS_THREAD_NEEDED = true;
mdxf_thread_start();
}
}
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::AUTO_MODE) {
count_calls_from_game();
}
}
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
uint8_t *head = nullptr;
uint16_t *prev_state = nullptr;
uint64_t *prev_time = nullptr;
std::mutex* mutex = nullptr;
switch (node) {
case 17:
case 25:
mutex = &MUTEX_P1;
head = &HEAD_P1;
prev_state = &PREV_STATE_P1;
prev_time = &PREV_TIME_P1;
buffer = BUFFERS.STATUS_BUFFER_P1;
break;
case 18:
case 26:
mutex = &MUTEX_P2;
head = &HEAD_P2;
prev_state = &PREV_STATE_P2;
prev_time = &PREV_TIME_P2;
buffer = BUFFERS.STATUS_BUFFER_P2;
break;
default:
// return failure on unknown node
return false;
}
// Sensor Map (LDUR):
// FOOT DOWN = bit 32-35 = byte 4, bit 0-3
// FOOT UP = bit 36-39 = byte 4, bit 4-7
// FOOT RIGHT = bit 40-43 = byte 5, bit 0-3
// FOOT LEFT = bit 44-47 = byte 5, bit 4-7
static const size_t buttons_p1[] = {
games::ddr::Buttons::P1_PANEL_UP,
games::ddr::Buttons::P1_PANEL_DOWN,
games::ddr::Buttons::P1_PANEL_LEFT,
games::ddr::Buttons::P1_PANEL_RIGHT,
};
static const size_t buttons_p2[] = {
games::ddr::Buttons::P2_PANEL_UP,
games::ddr::Buttons::P2_PANEL_DOWN,
games::ddr::Buttons::P2_PANEL_LEFT,
games::ddr::Buttons::P2_PANEL_RIGHT,
};
// decide on button map
const size_t *button_map = nullptr;
switch (node) {
case 17:
case 25:
button_map = &buttons_p1[0];
break;
case 18:
case 26:
button_map = &buttons_p2[0];
break;
}
uint16_t current_state;
// Only call getState() if called externally when actual input events happen, otherwise use previous known state
if (source == EXTERNAL_POLL) {
// get buttons
auto &buttons = games::ddr::get_buttons();
uint8_t up_down = 0;
uint8_t left_right = 0;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
up_down |= 0xF0;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
up_down |= 0x0F;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
left_right |= 0xF0;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
left_right |= 0x0F;
}
current_state = (uint16_t(up_down) << 8) | left_right;
} else {
current_state = *prev_state;
}
std::lock_guard<std::mutex> lock(*mutex);
const bool has_state_changed = *prev_state != current_state;
const bool has_time_changed = *prev_time < current_time;
// If state hasn't changed and either the update was triggered externally or the time hasn't changed, then don't advance head pointer or write a new entry
if (!has_state_changed && (source == EXTERNAL_POLL || !has_time_changed)) {
return true;
}
// The start and stop time cutoffs for backfilling entries. Min(..) ensures times aren't negative.
// The stop time is just before the current_time, set by BACKFILL_PADDING_MS, which avoids the last backfilled entry being too close to current_time.
uint64_t start_time = *prev_time;
const uint64_t stop_time = current_time - std::min<uint64_t>(current_time, BACKFILL_PADDING_MS);
// Ensures the first iteration will write the first entry at current_time and not backfill to time 0ms.
if (start_time == 0) {
start_time = current_time - std::min<uint64_t>(current_time, BACKFILL_INTERVAL_MS);
}
// Ensures only STATUS_BUFFER_NUM_ENTRIES entries at most are backfilled
const uint64_t max_backfill = BACKFILL_INTERVAL_MS * STATUS_BUFFER_NUM_ENTRIES;
const uint64_t min_time = current_time - std::min<uint64_t>(current_time, max_backfill);
if (start_time < min_time) {
start_time = min_time;
}
// Don't backfill entries if called externally or if a separate thread is being used to fill auxiliary entries
if (source == EXTERNAL_POLL || IS_THREAD_NEEDED) {
start_time = stop_time - 1;
}
uint64_t time = start_time;
uint16_t state = *prev_state;
// Backfill entries a fixed interval apart from each other between prev_time and current_time
while (time < stop_time) {
// Advance head pointer
*head = (*head + 1) % STATUS_BUFFER_NUM_ENTRIES;
uint8_t* buffer_entry = buffer[*head];
// Clear buffer
memset(buffer_entry, 0, STATUS_BUFFER_SIZE);
time += BACKFILL_INTERVAL_MS;
// If the stop time is reached, then write current_time and current_state instead for this final iteration
const bool isEdge = (time >= stop_time);
if (isEdge) {
state = current_state;
time = current_time;
}
// Write button state
buffer_entry[4] = (state >> 8) & 0xFF;
buffer_entry[5] = state & 0xFF;
// Write game time
*(uint64_t*)&buffer_entry[0x18] = time;
}
*prev_state = current_state;
*prev_time = current_time;
}
// return success
return true;
}
static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
return ac_io_mdxf_update_control_status_buffer_impl(node, ARKMDXP4_POLL, arkGetTickTime64());
}
// Used for triggering updates of the controller states from outside arkmdxp4.dll main refresh loop (i.e. within rawinput.cpp on controller events)
void mdxf_poll(bool isExternal) {
if (IS_MDXF_ACTIVE) {
const MDXFPollSource source = isExternal ? EXTERNAL_POLL : INTERNAL_POLL;
const uint64_t call_time_ms = arkGetTickTime64();
ac_io_mdxf_update_control_status_buffer_impl(17, source, call_time_ms);
ac_io_mdxf_update_control_status_buffer_impl(18, source, call_time_ms);
}
}
/*
* Module stuff
*/
acio::MDXFModule::MDXFModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("MDXF", module, hookMode) {
this->status_buffer = (uint8_t*) &BUFFERS;
this->status_buffer_size = sizeof(BUFFERS);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::MDXFModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_mdxf_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_mdxf_set_output_level);
ACIO_MODULE_HOOK(ac_io_mdxf_update_control_status_buffer);
}
acio::MDXFModule::~MDXFModule() {
if (IS_THREAD_NEEDED) {
mdxf_thread_stop();
}
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "../module.h"
namespace acio {
enum class MDXFBufferFillMode {
// backfill mode, but if <120Hz, enable poll thread
AUTO_MODE,
// forces poll thread
THREAD_MODE,
// forces backfill mode (no poll thread)
BACKFILL_MODE
};
extern MDXFBufferFillMode MDXF_BUFFER_FILL_MODE;
class MDXFModule : public ACIOModule {
public:
MDXFModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
~MDXFModule() override;
};
}
+5
View File
@@ -0,0 +1,5 @@
// mdxf_poll.h
#pragma once
// Called from rawinput thread whenever inputs have just been updated
void mdxf_poll(bool isExternal);
+39
View File
@@ -0,0 +1,39 @@
#include "module.h"
#include "util/logging.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "avs/game.h"
const char *acio::hook_mode_str(acio::HookMode hook_mode) {
switch (hook_mode) {
case HookMode::INLINE:
return "Inline";
case HookMode::IAT:
return "IAT";
default:
return "Unknown";
}
}
/*
* Hook functions depending on the specified mode.
* We don't care about errors here since different versions of libacio contain different feature sets,
* which means that not all hooks must/can succeed.
*/
void acio::ACIOModule::hook(void *func, const char *func_name) {
switch (this->hook_mode) {
case HookMode::INLINE:
detour::inline_hook(func, libutils::try_proc(this->module, func_name));
break;
case HookMode::IAT:
detour::iat_try(func_name, func);
break;
default:
log_warning("acio", "unable to hook using mode {}", hook_mode_str(this->hook_mode));
}
}
void acio::ACIOModule::attach() {
log_info("acio", "module attach: {} {}", this->name, hook_mode_str(this->hook_mode));
this->attached = true;
}
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include <cstdint>
#include <string>
#include <windows.h>
// macro for lazy typing of hooks
#define ACIO_MODULE_HOOK(f) this->hook(reinterpret_cast<void *>(f), #f)
namespace acio {
/*
* Hook Modes
* Since some versions can't handle inline hooking
*/
enum class HookMode {
INLINE,
IAT
};
// this makes logging easier
const char *hook_mode_str(HookMode hook_mode);
/*
* The ACIO module itself
* Inherit this for extending our libacio implementation
*/
class ACIOModule {
protected:
// the magic
void hook(void* func, const char *func_name);
public:
ACIOModule(std::string name, HMODULE module, HookMode hook_mode) :
name(std::move(name)),
module(module),
hook_mode(hook_mode) {};
virtual ~ACIOModule() = default;
virtual void attach();
// settings
std::string name;
HMODULE module;
HookMode hook_mode;
bool attached = false;
// buffer state (optional)
uint8_t *status_buffer = nullptr;
size_t status_buffer_size = 0;
bool *status_buffer_freeze = nullptr;
};
}
+64
View File
@@ -0,0 +1,64 @@
#include "nddb.h"
#include "avs/game.h"
#include "misc/eamuse.h"
#include "util/utils.h"
// static stuff
static uint8_t STATUS_BUFFER[4] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static void __cdecl ac_io_nddb_control_pwm(int a1, int a2) {
log_misc("acio::nddb", "ac_io_nddb_control_pwm({}, {})", a1, a2);
}
static void __cdecl ac_io_nddb_control_solenoide(int a1, int a2) {
log_misc("acio::nddb", "ac_io_nddb_control_solenoide({}, {})", a1, a2);
}
static bool __cdecl ac_io_nddb_create_get_status_thread() {
return true;
}
static bool __cdecl ac_io_nddb_destroy_get_status_thread() {
return true;
}
static void __cdecl ac_io_nddb_get_control_status_buffer(void *buffer) {
}
static bool __cdecl ac_io_nddb_req_solenoide_control(uint8_t *buffer) {
log_misc("acio::nddb", "ac_io_nddb_req_solenoide_control");
return true;
}
static bool __cdecl ac_io_nddb_update_control_status_buffer() {
return true;
}
/*
* Module stuff
*/
acio::NDDBModule::NDDBModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("NDDB", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::NDDBModule::attach() {
ACIOModule::attach();
ACIO_MODULE_HOOK(ac_io_nddb_control_pwm);
ACIO_MODULE_HOOK(ac_io_nddb_control_solenoide);
ACIO_MODULE_HOOK(ac_io_nddb_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_nddb_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_nddb_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_nddb_req_solenoide_control);
ACIO_MODULE_HOOK(ac_io_nddb_update_control_status_buffer);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class NDDBModule : public ACIOModule {
public:
NDDBModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+304
View File
@@ -0,0 +1,304 @@
#include "panb.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "games/nost/io.h"
#include "games/nost/nost.h"
#include "util/logging.h"
#include "avs/game.h"
// std::min
#ifdef min
#undef min
#endif
using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[277];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static long __cdecl ac_io_panb_control_led_bright(size_t index, uint8_t value) {
// nostalgia
if (avs::game::is_model("PAN")) {
// get lights
auto &lights = games::nost::get_lights();
// mapping
static const size_t mapping[] {
games::nost::Lights::Key1R, games::nost::Lights::Key1G, games::nost::Lights::Key1B,
games::nost::Lights::Key2R, games::nost::Lights::Key2G, games::nost::Lights::Key2B,
games::nost::Lights::Key3R, games::nost::Lights::Key3G, games::nost::Lights::Key3B,
games::nost::Lights::Key4R, games::nost::Lights::Key4G, games::nost::Lights::Key4B,
games::nost::Lights::Key5R, games::nost::Lights::Key5G, games::nost::Lights::Key5B,
games::nost::Lights::Key6R, games::nost::Lights::Key6G, games::nost::Lights::Key6B,
games::nost::Lights::Key7R, games::nost::Lights::Key7G, games::nost::Lights::Key7B,
games::nost::Lights::Key8R, games::nost::Lights::Key8G, games::nost::Lights::Key8B,
games::nost::Lights::Key9R, games::nost::Lights::Key9G, games::nost::Lights::Key9B,
games::nost::Lights::Key10R, games::nost::Lights::Key10G, games::nost::Lights::Key10B,
games::nost::Lights::Key11R, games::nost::Lights::Key11G, games::nost::Lights::Key11B,
games::nost::Lights::Key12R, games::nost::Lights::Key12G, games::nost::Lights::Key12B,
games::nost::Lights::Key13R, games::nost::Lights::Key13G, games::nost::Lights::Key13B,
games::nost::Lights::Key14R, games::nost::Lights::Key14G, games::nost::Lights::Key14B,
games::nost::Lights::Key15R, games::nost::Lights::Key15G, games::nost::Lights::Key15B,
games::nost::Lights::Key16R, games::nost::Lights::Key16G, games::nost::Lights::Key16B,
games::nost::Lights::Key17R, games::nost::Lights::Key17G, games::nost::Lights::Key17B,
games::nost::Lights::Key18R, games::nost::Lights::Key18G, games::nost::Lights::Key18B,
games::nost::Lights::Key19R, games::nost::Lights::Key19G, games::nost::Lights::Key19B,
games::nost::Lights::Key20R, games::nost::Lights::Key20G, games::nost::Lights::Key20B,
games::nost::Lights::Key21R, games::nost::Lights::Key21G, games::nost::Lights::Key21B,
games::nost::Lights::Key22R, games::nost::Lights::Key22G, games::nost::Lights::Key22B,
games::nost::Lights::Key23R, games::nost::Lights::Key23G, games::nost::Lights::Key23B,
games::nost::Lights::Key24R, games::nost::Lights::Key24G, games::nost::Lights::Key24B,
games::nost::Lights::Key25R, games::nost::Lights::Key25G, games::nost::Lights::Key25B,
games::nost::Lights::Key26R, games::nost::Lights::Key26G, games::nost::Lights::Key26B,
games::nost::Lights::Key27R, games::nost::Lights::Key27G, games::nost::Lights::Key27B,
games::nost::Lights::Key28R, games::nost::Lights::Key28G, games::nost::Lights::Key28B,
};
// write light
if (index < std::size(mapping)) {
Lights::writeLight(RI_MGR, lights.at(mapping[index]), value / 127.f);
}
}
return 1;
}
static long __cdecl ac_io_panb_control_reset() {
return 0;
}
static void* __cdecl ac_io_panb_get_control_status_buffer(uint8_t* buffer) {
// copy buffer
return memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static bool __cdecl ac_io_panb_start_auto_input() {
return true;
}
static uint8_t panb_get_button_velocity(Button& button, Button& button_soft, Button& button_medium, Button& button_hard) {
const auto velocity = Buttons::getVelocity(RI_MGR, button);
const auto velocity_soft = Buttons::getVelocity(RI_MGR, button_soft);
const auto velocity_medium = Buttons::getVelocity(RI_MGR, button_medium);
const auto velocity_hard = Buttons::getVelocity(RI_MGR, button_hard);
// note that the digital values have been obtained via trial-and-error in recital mode
// based on Op3:
// * soft presses glow blue, should trigger Elegant in blue sections
// * hard presses glow red/orange, should trigger Elegant in yellow sections
// * default (medium) presses glow green, triggers Elegant in both blue and yellow sections
// digital-only values
if (velocity_hard > 0.f) {
// do NOT use 15 here!! 14 properly registers as a hard press, but 15 does not
return 14;
} else if (velocity_medium > 0.f) {
return 11;
} else if (velocity_soft > 0.f) {
return 1;
}
// digital or midi (velocity-sensitive) values
return std::min((uint8_t)(velocity * 15.999f), (uint8_t)14);
}
static bool __cdecl ac_io_panb_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, 277);
/*
* first byte is number of input data
* when it's set to 0 the game will not update it's key states
* setting it too high will make the game read over the buffer
*
* unsure why you would send more than one set of data, so
* we just set it to 1 and provide our current status
*/
STATUS_BUFFER[0] = 1;
// Nostalgia
if (avs::game::is_model("PAN")) {
// get buttons/analogs
auto &buttons = games::nost::get_buttons();
auto &analogs = games::nost::get_analogs();
// mappings
// "normal" buttons - these are velocity sensitive (digital or MIDI)
static const size_t button_mapping[] = {
games::nost::Buttons::Key1, games::nost::Buttons::Key2,
games::nost::Buttons::Key3, games::nost::Buttons::Key4,
games::nost::Buttons::Key5, games::nost::Buttons::Key6,
games::nost::Buttons::Key7, games::nost::Buttons::Key8,
games::nost::Buttons::Key9, games::nost::Buttons::Key10,
games::nost::Buttons::Key11, games::nost::Buttons::Key12,
games::nost::Buttons::Key13, games::nost::Buttons::Key14,
games::nost::Buttons::Key15, games::nost::Buttons::Key16,
games::nost::Buttons::Key17, games::nost::Buttons::Key18,
games::nost::Buttons::Key19, games::nost::Buttons::Key20,
games::nost::Buttons::Key21, games::nost::Buttons::Key22,
games::nost::Buttons::Key23, games::nost::Buttons::Key24,
games::nost::Buttons::Key25, games::nost::Buttons::Key26,
games::nost::Buttons::Key27, games::nost::Buttons::Key28,
};
// soft (digital) button - always registers as soft press
static const size_t soft_button_mapping[] = {
games::nost::Buttons::Key1Soft, games::nost::Buttons::Key2Soft,
games::nost::Buttons::Key3Soft, games::nost::Buttons::Key4Soft,
games::nost::Buttons::Key5Soft, games::nost::Buttons::Key6Soft,
games::nost::Buttons::Key7Soft, games::nost::Buttons::Key8Soft,
games::nost::Buttons::Key9Soft, games::nost::Buttons::Key10Soft,
games::nost::Buttons::Key11Soft, games::nost::Buttons::Key12Soft,
games::nost::Buttons::Key13Soft, games::nost::Buttons::Key14Soft,
games::nost::Buttons::Key15Soft, games::nost::Buttons::Key16Soft,
games::nost::Buttons::Key17Soft, games::nost::Buttons::Key18Soft,
games::nost::Buttons::Key19Soft, games::nost::Buttons::Key20Soft,
games::nost::Buttons::Key21Soft, games::nost::Buttons::Key22Soft,
games::nost::Buttons::Key23Soft, games::nost::Buttons::Key24Soft,
games::nost::Buttons::Key25Soft, games::nost::Buttons::Key26Soft,
games::nost::Buttons::Key27Soft, games::nost::Buttons::Key28Soft,
};
// medium (digital) button - always registers as medium press
static const size_t medium_button_mapping[] = {
games::nost::Buttons::Key1Medium, games::nost::Buttons::Key2Medium,
games::nost::Buttons::Key3Medium, games::nost::Buttons::Key4Medium,
games::nost::Buttons::Key5Medium, games::nost::Buttons::Key6Medium,
games::nost::Buttons::Key7Medium, games::nost::Buttons::Key8Medium,
games::nost::Buttons::Key9Medium, games::nost::Buttons::Key10Medium,
games::nost::Buttons::Key11Medium, games::nost::Buttons::Key12Medium,
games::nost::Buttons::Key13Medium, games::nost::Buttons::Key14Medium,
games::nost::Buttons::Key15Medium, games::nost::Buttons::Key16Medium,
games::nost::Buttons::Key17Medium, games::nost::Buttons::Key18Medium,
games::nost::Buttons::Key19Medium, games::nost::Buttons::Key20Medium,
games::nost::Buttons::Key21Medium, games::nost::Buttons::Key22Medium,
games::nost::Buttons::Key23Medium, games::nost::Buttons::Key24Medium,
games::nost::Buttons::Key25Medium, games::nost::Buttons::Key26Medium,
games::nost::Buttons::Key27Medium, games::nost::Buttons::Key28Medium,
};
// hard (digital) button - always registers as hard press
static const size_t hard_button_mapping[] = {
games::nost::Buttons::Key1Hard, games::nost::Buttons::Key2Hard,
games::nost::Buttons::Key3Hard, games::nost::Buttons::Key4Hard,
games::nost::Buttons::Key5Hard, games::nost::Buttons::Key6Hard,
games::nost::Buttons::Key7Hard, games::nost::Buttons::Key8Hard,
games::nost::Buttons::Key9Hard, games::nost::Buttons::Key10Hard,
games::nost::Buttons::Key11Hard, games::nost::Buttons::Key12Hard,
games::nost::Buttons::Key13Hard, games::nost::Buttons::Key14Hard,
games::nost::Buttons::Key15Hard, games::nost::Buttons::Key16Hard,
games::nost::Buttons::Key17Hard, games::nost::Buttons::Key18Hard,
games::nost::Buttons::Key19Hard, games::nost::Buttons::Key20Hard,
games::nost::Buttons::Key21Hard, games::nost::Buttons::Key22Hard,
games::nost::Buttons::Key23Hard, games::nost::Buttons::Key24Hard,
games::nost::Buttons::Key25Hard, games::nost::Buttons::Key26Hard,
games::nost::Buttons::Key27Hard, games::nost::Buttons::Key28Hard,
};
static const size_t analog_mapping[] = {
games::nost::Analogs::Key1, games::nost::Analogs::Key2,
games::nost::Analogs::Key3, games::nost::Analogs::Key4,
games::nost::Analogs::Key5, games::nost::Analogs::Key6,
games::nost::Analogs::Key7, games::nost::Analogs::Key8,
games::nost::Analogs::Key9, games::nost::Analogs::Key10,
games::nost::Analogs::Key11, games::nost::Analogs::Key12,
games::nost::Analogs::Key13, games::nost::Analogs::Key14,
games::nost::Analogs::Key15, games::nost::Analogs::Key16,
games::nost::Analogs::Key17, games::nost::Analogs::Key18,
games::nost::Analogs::Key19, games::nost::Analogs::Key20,
games::nost::Analogs::Key21, games::nost::Analogs::Key22,
games::nost::Analogs::Key23, games::nost::Analogs::Key24,
games::nost::Analogs::Key25, games::nost::Analogs::Key26,
games::nost::Analogs::Key27, games::nost::Analogs::Key28,
};
// iterate pairs of keys
for (size_t key_pair = 0; key_pair < 28 / 2; key_pair++) {
// default states
uint8_t state0 = 0;
uint8_t state1 = 0;
// check analogs
//
// while 15 is technically allowed by the I/O board & is recognized correctly in test
// menu, when you play recital mode, 15 fails to register Elegant in yellow sections.
// therefore, cap the value at 14. tested with Nostroller.
auto &analog0 = analogs.at(analog_mapping[key_pair * 2 + 0]);
auto &analog1 = analogs.at(analog_mapping[key_pair * 2 + 1]);
if (analog0.isSet()) {
state0 = std::min((uint8_t)(Analogs::getState(RI_MGR, analog0) * 15.999f), (uint8_t)14);
}
if (analog1.isSet()) {
state1 = std::min((uint8_t)(Analogs::getState(RI_MGR, analog1) * 15.999f), (uint8_t)14);
}
// check digital buttons
const auto button0 = panb_get_button_velocity(
buttons.at(button_mapping[key_pair * 2 + 0]),
buttons.at(soft_button_mapping[key_pair * 2 + 0]),
buttons.at(medium_button_mapping[key_pair * 2 + 0]),
buttons.at(hard_button_mapping[key_pair * 2 + 0])
);
if (button0 > 0) {
state0 = button0;
}
const auto button1 = panb_get_button_velocity(
buttons.at(button_mapping[key_pair * 2 + 1]),
buttons.at(soft_button_mapping[key_pair * 2 + 1]),
buttons.at(medium_button_mapping[key_pair * 2 + 1]),
buttons.at(hard_button_mapping[key_pair * 2 + 1])
);
if (button1 > 0) {
state1 = button1;
}
// build value
uint8_t value = 0;
value |= state0 << 4;
value |= state1 & 0xF;
// set value
STATUS_BUFFER[key_pair + 3] = value;
}
}
// return success
return true;
}
/*
* Module stuff
*/
acio::PANBModule::PANBModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("PANB", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::PANBModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_panb_control_led_bright);
ACIO_MODULE_HOOK(ac_io_panb_control_reset);
ACIO_MODULE_HOOK(ac_io_panb_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_panb_start_auto_input);
ACIO_MODULE_HOOK(ac_io_panb_update_control_status_buffer);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class PANBModule : public ACIOModule {
public:
PANBModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+520
View File
@@ -0,0 +1,520 @@
#include "pix.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "games/museca/io.h"
#include "games/bbc/io.h"
#include "util/utils.h"
#include "avs/game.h"
using namespace GameAPI;
// static stuff
static int ACIO_PIX_WARMUP = 0;
static uint8_t STATUS_BUFFER[60];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static char __cdecl ac_io_pix_begin(char a1, long long a2, int a3, int a4, int a5, int a6) {
return 1;
}
static char __cdecl ac_io_pix_begin_get_status(int a1, int a2) {
return 1;
}
static char __cdecl ac_io_pix_end(int a1) {
return 1;
}
static char __cdecl ac_io_pix_end_get_status(int a1) {
return 1;
}
static char __cdecl ac_io_pix_get_firmware_update_device_index(int a1) {
return 1;
}
static char __cdecl ac_io_pix_get_node_no(int a1, int a2) {
return 1;
}
static void *__cdecl ac_io_pix_get_recv_log(long long a1, void *a2, int a3) {
return a2;
}
static void *__cdecl ac_io_pix_get_rs232c_status(void *a1, int a2) {
return a1;
}
static void *__cdecl ac_io_pix_get_send_log(long long a1, void *a2, int a3) {
return a2;
}
static char __cdecl ac_io_pix_get_version(void *a1, int a2, int a3) {
return 1;
}
static const char *__cdecl ac_io_pix_get_version_string() {
static const char *version = "1.25.0";
return version;
}
static char __cdecl ac_io_pix_go_firmware_update(int a1) {
return 1;
}
static char __cdecl ac_io_pix_is_active(int a1, int a2) {
return (char) (++ACIO_PIX_WARMUP > 601 ? 1 : 0);
}
static char __cdecl ac_io_pix_is_active2(int a1, int *a2, int a3) {
ACIO_PIX_WARMUP = 601;
*a2 = 6;
return 1;
}
static char __cdecl ac_io_pix_is_active_device(int a1, int a2) {
return (char) (a1 != 5);
}
static long long __cdecl ac_io_pix_reset(int a1) {
return a1;
}
static bool __cdecl ac_io_pix_rvol_change_expand_mode(char a1) {
return true;
}
static long long __cdecl ac_io_pix_rvol_control_led_bright(uint32_t led_field, uint8_t brightness) {
// MUSECA
if (avs::game::is_model("PIX")) {
// get lights
auto &lights = games::museca::get_lights();
// control mapping
static int mapping[] = {
games::museca::Lights::Spinner1R,
games::museca::Lights::Spinner1G,
games::museca::Lights::Spinner1B,
games::museca::Lights::Spinner2R,
games::museca::Lights::Spinner2G,
games::museca::Lights::Spinner2B,
games::museca::Lights::Spinner3R,
games::museca::Lights::Spinner3G,
games::museca::Lights::Spinner3B,
games::museca::Lights::Spinner4R,
games::museca::Lights::Spinner4G,
games::museca::Lights::Spinner4B,
games::museca::Lights::Spinner5R,
games::museca::Lights::Spinner5G,
games::museca::Lights::Spinner5B,
games::museca::Lights::TitleR,
games::museca::Lights::TitleG,
games::museca::Lights::TitleB
};
// write light
float value = brightness > 127.f ? 1.f : brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// BISHI BASHI CHANNEL
if (avs::game::is_model("R66")) {
// get lights
auto &lights = games::bbc::get_lights();
// control mapping
static int mapping[] = {
games::bbc::Lights::P1_DISC_R,
games::bbc::Lights::P1_DISC_G,
games::bbc::Lights::P1_DISC_B,
games::bbc::Lights::P3_DISC_R,
games::bbc::Lights::P3_DISC_G,
games::bbc::Lights::P3_DISC_B,
games::bbc::Lights::P2_DISC_R,
games::bbc::Lights::P2_DISC_G,
games::bbc::Lights::P2_DISC_B,
games::bbc::Lights::P4_DISC_R,
games::bbc::Lights::P4_DISC_G,
games::bbc::Lights::P4_DISC_B,
games::bbc::Lights::P1_R,
games::bbc::Lights::P1_B,
-1, -1, -1, -1, -1, -1,
games::bbc::Lights::P2_R,
games::bbc::Lights::P2_B,
games::bbc::Lights::P3_R,
games::bbc::Lights::P3_B,
games::bbc::Lights::P4_R,
games::bbc::Lights::P4_B,
};
// write light
float value = brightness / 255.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// return success
return 1;
}
static long long __cdecl ac_io_pix_rvol_control_reset() {
return 1;
}
static bool __cdecl ac_io_pix_rvol_create_get_status_thread() {
return true;
}
static long long __cdecl ac_io_pix_rvol_destroy_get_status_thread() {
return 1;
}
static void *__cdecl ac_io_pix_rvol_get_control_status_buffer(void *a1) {
// copy buffer
return memcpy(a1, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static bool __cdecl ac_io_pix_rvol_get_watchdog_status() {
return true;
}
static short __cdecl ac_io_pix_rvol_get_watchdog_time_min() {
return 0;
}
static short __cdecl ac_io_pix_rvol_get_watchdog_time_now() {
return 0;
}
static bool __cdecl ac_io_pix_rvol_modify_auto_input_get(long long a1, long long a2) {
return true;
}
static char __cdecl ac_io_pix_rvol_req_get_control_status(DWORD *a1) {
*a1 = 1;
return 1;
}
static bool __cdecl ac_io_pix_rvol_req_volume_control(char a1, char a2, char a3, char a4) {
return true;
}
static bool __cdecl ac_io_pix_rvol_req_volume_control_isfinished(DWORD *a1) {
*a1 = 5;
return true;
}
static long long __cdecl ac_io_pix_rvol_set_framing_err_packet_send_interval(long long a1) {
return a1;
}
static bool __cdecl ac_io_pix_rvol_set_watchdog_time(short a1) {
return true;
}
static bool __cdecl ac_io_pix_rvol_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// MUSECA
if (avs::game::is_model("PIX")) {
// get input
auto &buttons = games::museca::get_buttons();
// get slowdown status
bool slowdown = Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::AnalogSlowdown));
// update disk buttons
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk1Press)))
ARRAY_SETB(STATUS_BUFFER, 107);
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk2Press)))
ARRAY_SETB(STATUS_BUFFER, 104);
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk3Press)))
ARRAY_SETB(STATUS_BUFFER, 123);
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk4Press)))
ARRAY_SETB(STATUS_BUFFER, 42);
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk5Press)))
ARRAY_SETB(STATUS_BUFFER, 44);
// foot pedal (inverted)
if (!Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::FootPedal)))
ARRAY_SETB(STATUS_BUFFER, 43);
// update analogs
static uint8_t analogs[5] = { 0, 0, 0, 0, 0 };
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk1Minus))) {
analogs[0] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk1Plus))) {
analogs[0] += slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk2Minus))) {
analogs[1] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk2Plus))) {
analogs[1] += slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk3Minus))) {
analogs[2] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk3Plus))) {
analogs[2] += slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk4Minus))) {
analogs[3] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk4Plus))) {
analogs[3] += slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk5Minus))) {
analogs[4] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk5Plus))) {
analogs[4] += slowdown ? 3 : 12;
}
// raw input analogs
auto &analog_list = games::museca::get_analogs();
size_t analog_mapping[] = {
games::museca::Analogs::Disk1,
games::museca::Analogs::Disk2,
games::museca::Analogs::Disk3,
games::museca::Analogs::Disk4,
games::museca::Analogs::Disk5,
};
uint8_t set_values[5];
std::copy(std::begin(analogs), std::end(analogs), std::begin(set_values));
for (size_t i = 0; i < 5; i++) {
auto &analog_item = analog_list.at(analog_mapping[i]);
if (analog_item.isSet()) {
set_values[i] = analogs[i] + (uint8_t) (Analogs::getState(RI_MGR, analog_item) * 255.99f);
}
}
// set analogs
for (int i = 0; i < 5; i++)
STATUS_BUFFER[20 + i] = set_values[i];
}
// BISHI BASHI CHANNEL
if (avs::game::is_model("R66")) {
// get input
auto &buttons = games::bbc::get_buttons();
auto &analogs = games::bbc::get_analogs();
// get slowdown status
bool slowdown1 = Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_DiskSlowdown));
bool slowdown2 = Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_DiskSlowdown));
bool slowdown3 = Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_DiskSlowdown));
bool slowdown4 = Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_DiskSlowdown));
// update buttons
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_R)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 44);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_G)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 107);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_B)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 41);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_R)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 39);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_G)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 123);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_B)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 55);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_R)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 71);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_G)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 104);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_B)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 87);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_R)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 103);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_G)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 42);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_B)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 119);
}
// update analogs
static uint8_t analog_states[4] = { 0, 0, 0, 0 };
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_DiskMinus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[0] -= slowdown1 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_DiskPlus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[0] += slowdown1 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_DiskMinus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[1] -= slowdown2 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_DiskPlus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[1] += slowdown2 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_DiskMinus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[2] -= slowdown3 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_DiskPlus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[2] += slowdown3 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_DiskMinus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[3] -= slowdown4 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_DiskPlus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[3] += slowdown4 ? 3 : 12;
}
// raw input analogs
uint8_t set_values[4];
size_t analog_mappings[] = {
games::bbc::Analogs::P1_Disk,
games::bbc::Analogs::P2_Disk,
games::bbc::Analogs::P3_Disk,
games::bbc::Analogs::P4_Disk,
};
std::copy(std::begin(analog_states), std::end(analog_states), std::begin(set_values));
for (size_t i = 0; i < 4; i++) {
auto &analog_item = analogs.at(analog_mappings[i]);
if (analog_item.isSet()) {
set_values[i] = analog_states[i] + (uint8_t) (Analogs::getState(RI_MGR, analog_item) * 255.99f);
}
}
// flip disk 2/3
set_values[1] ^= set_values[2];
set_values[2] ^= set_values[1];
set_values[1] ^= set_values[2];
// set analogs
for (int i = 0; i < 4; i++) {
STATUS_BUFFER[20 + i] = set_values[i];
}
}
// success
return true;
}
static void __cdecl ac_io_pix_rvol_watchdog_off() {
}
static void *__cdecl ac_io_pix_secplug_set_encodedpasswd(void *a1, unsigned int a2) {
return a1;
}
static void *__cdecl ac_io_pix_set_get_status_device(void *a1, int a2) {
return a1;
}
static void *__cdecl ac_io_pix_set_soft_watch_dog(void *a1, int a2) {
return a1;
}
static char __cdecl ac_io_pix_soft_watch_dog_off(int a1) {
return 1;
}
static char __cdecl ac_io_pix_soft_watch_dog_on(int a1) {
return 1;
}
static char __cdecl ac_io_pix_update(long long a1) {
// flush outputs
RI_MGR->devices_flush_output();
return 1;
}
static const char* __cdecl ac_io_pix_version() {
static const char *version = "Version: 1.25.0\nBuild Date: Sep 20 2016 15:16:13\nBuild Host: DEMETER\n";
return version;
}
/*
* Module stuff
*/
acio::PIXModule::PIXModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("PIX", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::PIXModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_pix_begin);
ACIO_MODULE_HOOK(ac_io_pix_begin_get_status);
ACIO_MODULE_HOOK(ac_io_pix_end);
ACIO_MODULE_HOOK(ac_io_pix_end_get_status);
ACIO_MODULE_HOOK(ac_io_pix_get_firmware_update_device_index);
ACIO_MODULE_HOOK(ac_io_pix_get_node_no);
ACIO_MODULE_HOOK(ac_io_pix_get_recv_log);
ACIO_MODULE_HOOK(ac_io_pix_get_rs232c_status);
ACIO_MODULE_HOOK(ac_io_pix_get_send_log);
ACIO_MODULE_HOOK(ac_io_pix_get_version);
ACIO_MODULE_HOOK(ac_io_pix_get_version_string);
ACIO_MODULE_HOOK(ac_io_pix_go_firmware_update);
ACIO_MODULE_HOOK(ac_io_pix_is_active);
ACIO_MODULE_HOOK(ac_io_pix_is_active2);
ACIO_MODULE_HOOK(ac_io_pix_is_active_device);
ACIO_MODULE_HOOK(ac_io_pix_reset);
ACIO_MODULE_HOOK(ac_io_pix_rvol_change_expand_mode);
ACIO_MODULE_HOOK(ac_io_pix_rvol_control_led_bright);
ACIO_MODULE_HOOK(ac_io_pix_rvol_control_reset);
ACIO_MODULE_HOOK(ac_io_pix_rvol_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_pix_rvol_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_pix_rvol_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pix_rvol_get_watchdog_status);
ACIO_MODULE_HOOK(ac_io_pix_rvol_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_pix_rvol_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_pix_rvol_modify_auto_input_get);
ACIO_MODULE_HOOK(ac_io_pix_rvol_req_get_control_status);
ACIO_MODULE_HOOK(ac_io_pix_rvol_req_volume_control);
ACIO_MODULE_HOOK(ac_io_pix_rvol_req_volume_control_isfinished);
ACIO_MODULE_HOOK(ac_io_pix_rvol_set_framing_err_packet_send_interval);
ACIO_MODULE_HOOK(ac_io_pix_rvol_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_pix_rvol_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pix_rvol_watchdog_off);
ACIO_MODULE_HOOK(ac_io_pix_secplug_set_encodedpasswd);
ACIO_MODULE_HOOK(ac_io_pix_set_get_status_device);
ACIO_MODULE_HOOK(ac_io_pix_set_soft_watch_dog);
ACIO_MODULE_HOOK(ac_io_pix_soft_watch_dog_off);
ACIO_MODULE_HOOK(ac_io_pix_soft_watch_dog_on);
ACIO_MODULE_HOOK(ac_io_pix_update);
ACIO_MODULE_HOOK(ac_io_pix_version);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class PIXModule : public ACIOModule {
public:
PIXModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+156
View File
@@ -0,0 +1,156 @@
#include "pjec.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "avs/game.h"
#include "games/we/io.h"
//using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[72];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static bool __cdecl ac_io_pjec_get_ps2() {
return true;
}
static void __cdecl ac_io_pjec_get_control_status_buffer(uint8_t *buffer) {
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static bool __cdecl ac_io_pjec_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// Winning Eleven
if (avs::game::is_model({ "KCK", "NCK" })) {
auto &buttons = games::we::get_buttons();
auto &analogs = games::we::get_analogs();
/*
* Device Types
* 0x00 - Unknown Device
* 0x01 - Mouse
* 0x02 - Rotate Controller
* 0x03 - Gun Controller K
* 0x04 - Digital Controller <- Accepted
* 0x05 - Analog Joystick
* 0x06 - Gun Controller N
* 0x07 - Analog Controller <- Accepted
* 0x08 - USB Analog Controller
*/
// set device type
STATUS_BUFFER[0] = 0x07;
// set device present
STATUS_BUFFER[2] = 0x5A;
// reset analogs to center
STATUS_BUFFER[8] = 0x7F;
STATUS_BUFFER[9] = 0x7F;
STATUS_BUFFER[10] = 0x7F;
STATUS_BUFFER[11] = 0x7F;
// apply analogs
if (analogs[games::we::Analogs::PadStickLeftX].isSet()) {
STATUS_BUFFER[8] = (uint8_t) (GameAPI::Analogs::getState(RI_MGR,
analogs[games::we::Analogs::PadStickLeftX]) * 255.9999f);
}
if (analogs[games::we::Analogs::PadStickLeftY].isSet()) {
STATUS_BUFFER[9] = (uint8_t) (GameAPI::Analogs::getState(RI_MGR,
analogs[games::we::Analogs::PadStickLeftY]) * 255.9999f);
}
if (analogs[games::we::Analogs::PadStickRightX].isSet()) {
STATUS_BUFFER[10] = (uint8_t) (GameAPI::Analogs::getState(RI_MGR,
analogs[games::we::Analogs::PadStickRightX]) * 255.9999f);
}
if (analogs[games::we::Analogs::PadStickRightY].isSet()) {
STATUS_BUFFER[11] = (uint8_t) (GameAPI::Analogs::getState(RI_MGR,
analogs[games::we::Analogs::PadStickRightY]) * 255.9999f);
}
// apply buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadStart])) {
STATUS_BUFFER[4] |= 0x08;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadSelect])) {
STATUS_BUFFER[4] |= 0x01;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadUp])) {
STATUS_BUFFER[4] |= 0x10;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadDown])) {
STATUS_BUFFER[4] |= 0x40;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadLeft])) {
STATUS_BUFFER[4] |= 0x80;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadRight])) {
STATUS_BUFFER[4] |= 0x20;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadTriangle])) {
STATUS_BUFFER[5] |= 0x10;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadCross])) {
STATUS_BUFFER[5] |= 0x40;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadSquare])) {
STATUS_BUFFER[5] |= 0x80;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadCircle])) {
STATUS_BUFFER[5] |= 0x20;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadL1])) {
STATUS_BUFFER[5] |= 0x04;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadL2])) {
STATUS_BUFFER[5] |= 0x01;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadL3])) {
STATUS_BUFFER[4] |= 0x02;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadR1])) {
STATUS_BUFFER[5] |= 0x08;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadR2])) {
STATUS_BUFFER[5] |= 0x02;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadR3])) {
STATUS_BUFFER[4] |= 0x04;
}
}
// success
return true;
}
/*
* Module stuff
*/
acio::PJECModule::PJECModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("PJEC", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::PJECModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_pjec_get_ps2);
ACIO_MODULE_HOOK(ac_io_pjec_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pjec_update_control_status_buffer);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class PJECModule : public ACIOModule {
public:
PJECModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+223
View File
@@ -0,0 +1,223 @@
#include "pjei.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "misc/eamuse.h"
#include "games/we/io.h"
#include "avs/game.h"
//using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[40];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static bool __cdecl ac_io_pjei_current_coinstock(int a1, uint32_t *coinstock) {
*coinstock = eamuse_coin_get_stock();
return true;
}
static bool __cdecl ac_io_pjei_consume_coinstock(int a1, uint32_t amount) {
return eamuse_coin_consume(amount);
}
static bool __cdecl ac_io_pjei_get_softwareid(char *dst) {
static char DATA[] = "0140FFFFFFFFFFFFFFFF";
memcpy(dst, DATA, sizeof(DATA));
return true;
}
static bool __cdecl ac_io_pjei_get_systemid(char *dst) {
static char DATA[] = "0140FFFFFFFFFFFFFFFF";
memcpy(dst, DATA, sizeof(DATA));
return true;
}
static bool __cdecl ac_io_pjei_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// Winning Eleven
if (avs::game::is_model({ "KCK", "NCK" })) {
// get buttons
auto &buttons = games::we::get_buttons();
// apply buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Service])) {
STATUS_BUFFER[16] |= 0x10;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Test])) {
STATUS_BUFFER[16] |= 0x20;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::CoinMech])) {
STATUS_BUFFER[16] |= 0x04;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Start])) {
STATUS_BUFFER[4] |= 0x80;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Up])) {
STATUS_BUFFER[4] |= 0x40;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Down])) {
STATUS_BUFFER[4] |= 0x20;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Left])) {
STATUS_BUFFER[4] |= 0x10;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Right])) {
STATUS_BUFFER[4] |= 0x08;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonA])) {
STATUS_BUFFER[4] |= 0x04;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonB])) {
STATUS_BUFFER[4] |= 0x02;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonC])) {
STATUS_BUFFER[4] |= 0x01;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonD])) {
STATUS_BUFFER[6] |= 0x80;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonE])) {
STATUS_BUFFER[6] |= 0x40;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonF])) {
STATUS_BUFFER[6] |= 0x20;
}
}
// success
return true;
}
static bool ac_io_pjei_get_control_status_buffer(uint8_t *buffer) {
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
return true;
}
static bool __cdecl ac_io_pjei_req_secplug_check() {
return true;
}
static bool __cdecl ac_io_pjei_req_secplug_check_isfinished() {
return true;
}
static bool __cdecl ac_io_pjei_req_secplug_missing_check() {
return true;
}
static bool __cdecl ac_io_pjei_req_secplug_missing_check_isfinished() {
return true;
}
static bool __cdecl ac_io_pjei_lock_coincounter(int a1) {
eamuse_coin_set_block(true);
return true;
}
static bool __cdecl ac_io_pjei_unlock_coincounter(int a1) {
eamuse_coin_set_block(false);
return true;
}
static bool __cdecl ac_io_pjei_control_coin_blocker_on(bool a1) {
eamuse_coin_set_block(true);
return true;
}
static bool __cdecl ac_io_pjei_control_coin_blocker_off(bool a1) {
eamuse_coin_set_block(false);
return true;
}
/*
* Helper method for easily setting the light values
*/
static void ac_io_pjei_control_lamp_set(uint8_t lamp_bits, uint8_t brightness) {
auto &lights = games::we::get_lights();
float value = CLAMP(brightness / 31.f, 0.f, 1.f);
if (lamp_bits & 0x20) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::LeftRed], value);
}
if (lamp_bits & 0x10) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::LeftGreen], value);
}
if (lamp_bits & 0x08) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::LeftBlue], value);
}
if (lamp_bits & 0x04) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::RightRed], value);
}
if (lamp_bits & 0x02) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::RightGreen], value);
}
if (lamp_bits & 0x01) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::RightBlue], value);
}
}
static bool __cdecl ac_io_pjei_control_lamp_on(uint8_t lamp_bits) {
ac_io_pjei_control_lamp_set(lamp_bits, 31);
return true;
}
static bool __cdecl ac_io_pjei_control_lamp_off(uint8_t lamp_bits) {
ac_io_pjei_control_lamp_set(lamp_bits, 0);
return true;
}
static bool __cdecl ac_io_pjei_control_lamp_bright(uint8_t lamp_bit, uint8_t brightness) {
ac_io_pjei_control_lamp_set(lamp_bit, brightness);
return true;
}
static bool __cdecl ac_io_pjei_control_lamp_mode(int mode) {
// mode -> [0,1] (0 is static, 1 is brightness?)
return true;
}
/*
* Module stuff
*/
acio::PJEIModule::PJEIModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("PJEI", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::PJEIModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_pjei_current_coinstock);
ACIO_MODULE_HOOK(ac_io_pjei_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_pjei_get_softwareid);
ACIO_MODULE_HOOK(ac_io_pjei_get_systemid);
ACIO_MODULE_HOOK(ac_io_pjei_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pjei_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pjei_req_secplug_check);
ACIO_MODULE_HOOK(ac_io_pjei_req_secplug_check_isfinished);
ACIO_MODULE_HOOK(ac_io_pjei_req_secplug_missing_check);
ACIO_MODULE_HOOK(ac_io_pjei_req_secplug_missing_check_isfinished);
ACIO_MODULE_HOOK(ac_io_pjei_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_pjei_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_pjei_control_coin_blocker_on);
ACIO_MODULE_HOOK(ac_io_pjei_control_coin_blocker_off);
ACIO_MODULE_HOOK(ac_io_pjei_control_lamp_on);
ACIO_MODULE_HOOK(ac_io_pjei_control_lamp_off);
ACIO_MODULE_HOOK(ac_io_pjei_control_lamp_bright);
ACIO_MODULE_HOOK(ac_io_pjei_control_lamp_mode);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class PJEIModule : public ACIOModule {
public:
PJEIModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
+67
View File
@@ -0,0 +1,67 @@
#include "bi2x.h"
namespace acio2emu::firmware {
bool BI2XNode::handle_packet(const acio2emu::Packet &in, std::vector<uint8_t> &out) {
auto cur = in.payload.begin();
while ((cur + 1) < in.payload.end()) {
auto cmd = (cur[0] << 8) | cur[1];
out.push_back(*cur++);
out.push_back(*cur++);
out.push_back(0);
switch (cmd) {
case 2: // query firmware version
read_firmware_version(out);
cur = in.payload.end();
break;
case 16:
out.push_back(2);
cur = in.payload.end();
break;
case 800:
case 802:
case 19:
cur = in.payload.end();
break;
case 120:
out.push_back(3);
cur = in.payload.end();
break;
case 801:
out.push_back(33);
out.push_back(0);
cur = in.payload.end();
break;
case 784: // poll input
if (!read_input(out)) {
return false;
}
break;
case 785: { // write output
auto count = write_output(std::span{&*cur, static_cast<size_t>(in.payload.end() - cur)});
if (count < 0) {
return false;
}
cur += count;
break;
}
case 786:
cur += 4;
break;
default:
log_warning("bi2x", "unknown command: {}", cmd);
return false;
}
}
return true;
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <vector>
#include <span>
#include <cstdint>
#include "acio2emu/node.h"
#include "util/logging.h"
namespace acio2emu::firmware {
class BI2XNode : public Node {
virtual void read_firmware_version(std::vector<uint8_t> &buffer) = 0;
virtual bool read_input(std::vector<uint8_t> &buffer) = 0;
virtual int write_output(std::span<const uint8_t> buffer) = 0;
/*
* acio2emu::Node
*/
bool handle_packet(const acio2emu::Packet &in, std::vector<uint8_t> &out) override;
};
}
+120
View File
@@ -0,0 +1,120 @@
#include "handle.h"
#include "util/logging.h"
#include "util/utils.h" // ws2s
namespace acio2emu {
class MasterNode : public Node {
private:
const IOBHandle *iob_;
public:
MasterNode(const IOBHandle *iob) : iob_(iob) { }
bool handle_packet(const Packet &in, std::vector<uint8_t> &out) {
// were we sent a command?
if (in.payload.size() >= 2) {
if (in.payload[0] != 0 || in.payload[1] != 1) {
// unknown command
return false;
}
// assign node ids
out.push_back(0);
out.push_back(1);
for (int i = 0; i < iob_->number_of_nodes(); i++) {
out.push_back(i * 16);
}
}
return true;
}
};
IOBHandle::IOBHandle(LPCWSTR device) : device_(device) {
nodes_[0] = std::make_unique<MasterNode>(this);
}
bool IOBHandle::register_node(std::unique_ptr<Node> node) {
if ((number_of_nodes_ - 1) >= 16) {
// too many nodes
return false;
}
nodes_[number_of_nodes_++] = std::move(node);
return true;
}
int IOBHandle::number_of_nodes() const {
// don't include the master node
return number_of_nodes_ - 1;
}
void IOBHandle::forward_packet_(const Packet &packet) {
// clear the output queue
output_ = {};
auto node = packet.node / 2;
if (node >= number_of_nodes_) {
log_warning("acio2emu", "cannot forward packet: node out of range: {} >= {}", node, number_of_nodes_);
return;
}
// forward the packet to the node
std::vector<uint8_t> payload;
if (!nodes_[node]->handle_packet(packet, payload)) {
// error in handler
return;
}
// encode the response
encode_packet(output_, node, packet.tag, payload);
}
/*
* CustomHandle
*/
bool IOBHandle::open(LPCWSTR lpFileName) {
if (device_ != lpFileName) {
return false;
}
log_info("acio2emu", "Opened {} (ACIO2)", ws2s(device_));
return true;
}
bool IOBHandle::close() {
log_info("acio2emu", "Closed {} (ACIO2)", ws2s(device_));
return true;
}
int IOBHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
DWORD i = 0;
while (!output_.empty() && i < nNumberOfBytesToRead) {
buffer[i++] = output_.front();
output_.pop();
}
return i;
}
int IOBHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
if (decoder_.update(buffer[i])) {
// forward the packet to a node
forward_packet_(decoder_.packet());
}
}
return nNumberOfBytesToWrite;
}
int IOBHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <string>
#include <array>
#include <queue>
#include <memory> // std::unique_ptr
#include <cstdint>
#include "acio2emu/packet.h"
#include "acio2emu/node.h"
#include "hooks/devicehook.h"
namespace acio2emu {
class IOBHandle : public CustomHandle {
private:
std::wstring device_;
std::array<std::unique_ptr<Node>, 17> nodes_;
// the first node is reserved for the "master" node
int number_of_nodes_ = 1;
PacketDecoder decoder_;
std::queue<uint8_t> output_;
void forward_packet_(const Packet &packet);
public:
IOBHandle(LPCWSTR device);
bool register_node(std::unique_ptr<Node> node);
int number_of_nodes() const;
/*
* CustomHandle
*/
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) override;
bool close() override;
};
}
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <cstddef>
namespace acio2emu::detail {
inline uint8_t crc4_lgp_c(uint8_t crc, const uint8_t *data, size_t len) {
static constexpr uint8_t tbl[] = {
0x00, 0x0D, 0x03, 0x0E,
0x06, 0x0B, 0x05, 0x08,
0x0C, 0x01, 0x0F, 0x02,
0x0A, 0x07, 0x09, 0x04,
};
crc &= 15;
for (size_t i = 0; i < len; i++) {
auto b = data[i];
crc = (((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) >> 4) ^ tbl[(((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) ^ (b >> 4)) & 0x0F];
}
return crc;
}
inline uint8_t crc7_lgp_48(uint8_t crc, const uint8_t *data, size_t len) {
static constexpr uint8_t tbl[] = {
0x00, 0x09, 0x12, 0x1B,
0x24, 0x2D, 0x36, 0x3F,
0x48, 0x41, 0x5A, 0x53,
0x6C, 0x65, 0x7E, 0x77
};
crc &= 127;
for (size_t i = 0; i < len; i++) {
auto b = data[i];
crc = (((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) >> 4) ^ tbl[(((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) ^ (b >> 4)) & 0x0F];
}
return crc;
}
}
+140
View File
@@ -0,0 +1,140 @@
#pragma once
#include <queue>
#include <cstdint>
namespace acio2emu::detail {
class InflateTransformer {
private:
std::queue<uint8_t> output_;
uint8_t flags_ = 0, flag_shift_ = 0;
uint8_t window_[85] = {};
int window_offset_ = 81;
enum class inflateStep {
readFlags,
processFlags,
copyStored,
copyFromWindow,
} step_ = inflateStep::readFlags;
void window_put_(uint8_t b) {
window_[window_offset_++] = b;
window_offset_ %= sizeof(window_);
}
uint8_t window_get_(int offset) {
return window_[offset % sizeof(window_)];
}
public:
void put(uint8_t b) {
auto consumed = false;
while (true) {
switch (step_) {
case inflateStep::readFlags:
if (consumed) {
// need more data
return;
}
consumed = true;
flags_ = b;
flag_shift_ = 0;
step_ = inflateStep::processFlags;
break;
case inflateStep::processFlags:
// have we processed every flag?
if (flag_shift_ > 6) {
step_ = inflateStep::readFlags;
break;
}
if (flags_ & (1 << flag_shift_)) {
flag_shift_++;
if (flags_ & (1 << flag_shift_)) {
// emit 0xAA when both bits are set
output_.push(0xAA);
}
else {
// copy from the window when only the lower bit is set
step_ = inflateStep::copyFromWindow;
}
}
else {
step_ = inflateStep::copyStored;
}
flag_shift_++;
break;
case inflateStep::copyFromWindow: {
if (consumed) {
// need more data
return;
}
consumed = true;
// determine the match size, default is 2-bytes
auto offset = b;
auto size = 2;
if (offset >= 0xAA) {
// 4-byte match
size = 4;
offset -= 0xAB;
}
else if (offset >= 0x55) {
// 3-byte match
size = 3;
offset -= 0x55;
}
for (auto i = 0; i < size; i ++) {
auto cur = window_get_(offset + i);
window_put_(cur);
output_.push(cur);
}
// continue processing flags
step_ = inflateStep::processFlags;
break;
}
case inflateStep::copyStored:
if (consumed) {
// need more data
return;
}
consumed = true;
window_put_(b);
output_.push(b);
// continue processing flags
step_ = inflateStep::processFlags;
break;
}
}
}
int get() {
if (output_.empty()) {
// output queue is empty
return -1;
}
auto b = output_.front();
output_.pop();
return b;
}
};
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <vector>
#include "acio2emu/packet.h"
namespace acio2emu {
class Node {
public:
virtual ~Node() {}
virtual bool handle_packet(const Packet &in, std::vector<uint8_t> &out) = 0;
};
}
+266
View File
@@ -0,0 +1,266 @@
#include "packet.h"
#include "util/logging.h"
#include "acio2emu/internal/crc.h"
namespace acio2emu {
static constexpr uint8_t SOF = 0xAA;
static constexpr uint8_t ESC = 0xFF;
static void encode_payload_(std::queue<uint8_t> &out, const std::vector<uint8_t> &payload) {
for (auto b : payload) {
if (b == SOF || b == ESC) {
out.push(ESC);
b = ~b;
}
out.push(b);
}
// compute and write the payload's CRC
out.push(detail::crc7_lgp_48(0x7F, payload.data(), payload.size()) ^ 0x7F);
}
bool encode_packet(std::queue<uint8_t> &out, uint8_t node, uint8_t tag, const std::vector<uint8_t> &payload) {
auto size = payload.size();
if (size > 127) {
log_warning("acio2emu", "cannot encode packet: payload too large: {} > 127", payload.size());
return false;
}
// build the header
uint8_t header[5] = {
SOF,
static_cast<uint8_t>(node * 3),
tag,
static_cast<uint8_t>(size),
0,
};
// compute the header's CRC
header[4] = detail::crc4_lgp_c(0x0F, &header[1], sizeof(header) - 1) ^ 0x0F;
// push the header to the output queue
for (size_t i = 0; i < sizeof(header); i++) {
out.push(header[i]);
}
encode_payload_(out, payload);
return true;
}
void PacketDecoder::set_step_(readStep s) {
#ifndef NDEBUG
auto valid = true;
switch (s) {
case readStep::idle:
case readStep::readNode:
// transition from any step/state allowed
break;
case readStep::readTag:
if (step_ != readStep::readNode) {
valid = false;
}
break;
case readStep::readPayloadSize:
if (step_ != readStep::readTag) {
valid = false;
}
break;
case readStep::readPayloadFlags:
if (step_ != readStep::readPayloadSize) {
valid = false;
}
break;
case readStep::readReplacementByte:
if (step_ != readStep::readPayloadFlags) {
valid = false;
}
break;
case readStep::readPayload:
if (step_ != readStep::readPayloadFlags &&
step_ != readStep::readReplacementByte &&
step_ != readStep::readEscaped
) {
valid = false;
}
break;
case readStep::readEscaped:
if (step_ != readStep::readPayload) {
valid = false;
}
break;
default:
log_fatal(
"acio2emu",
"cannot set step: unknown value: {}",
static_cast<uint32_t>(s));
break;
}
if (!valid) {
log_fatal(
"acio2emu",
"illegal transition detected: {} -> {}",
static_cast<uint32_t>(step_),
static_cast<uint32_t>(s));
}
#endif
step_ = s;
}
int PacketDecoder::update_payload_size_(uint8_t b) {
if ((b & 0x80) == 0) {
payload_size_ = (payload_size_ << 7) | (b & 0x7F);
// finished
return 0;
}
else if ((b & 0x40) != 0 && payload_size_count_ < 5) {
payload_size_count_++;
payload_size_count_ = (payload_size_count_ << 6) | (b & 0x3F);
// continuation required
return 1;
}
else {
// invalid value or invalid state
return -1;
}
}
uint8_t PacketDecoder::deobfuscate_(uint8_t b) {
if ((b ^ 0xAA) == 0) {
return b;
}
auto mask = 0x55;
if ((b & 0x80) == 0) {
mask = 0x7F;
}
return (b ^ lcg_()) & mask;
}
void PacketDecoder::reset_(readStep s) {
set_step_(s);
packet_ = {};
payload_size_ = 0;
payload_size_count_ = 0;
}
bool PacketDecoder::update(uint8_t b) {
// is this the start of a packet?
if (b == SOF) {
reset_(readStep::readNode);
return false;
}
switch (step_) {
case readStep::readNode:
packet_.node = b;
set_step_(readStep::readTag);
break;
case readStep::readTag:
packet_.tag = b;
set_step_(readStep::readPayloadSize);
break;
case readStep::readPayloadSize: {
auto status = update_payload_size_(b);
if (status == 0) {
// finished reading payload size
packet_.payload.reserve(payload_size_);
set_step_(readStep::readPayloadFlags);
}
else if (status == -1) {
// reset on error
reset_(readStep::idle);
}
break;
}
case readStep::readPayloadFlags:
obfuscated_ = (b & (1 << 4)) != 0;
encoding_ = static_cast<payloadEncoding>(b >> 5);
if (obfuscated_) {
lcg_.seed(packet_.tag ^ 0x55);
}
if (encoding_ == payloadEncoding::replace) {
set_step_(readStep::readReplacementByte);
}
else {
set_step_(readStep::readPayload);
if (encoding_ == payloadEncoding::lz) {
// reset the InflateTransformer
inflate_ = {};
}
}
break;
case readStep::readReplacementByte:
substitute_ = b;
set_step_(readStep::readPayload);
break;
case readStep::readPayload:
// do we need to deobfuscate?
if (obfuscated_) {
b = deobfuscate_(b);
}
if (encoding_ == payloadEncoding::lz) {
inflate_.put(b);
for (int i = inflate_.get(); i >= 0; i = inflate_.get()) {
packet_.payload.push_back(i);
}
}
else if (encoding_ == payloadEncoding::replace && b == substitute_) {
packet_.payload.push_back(SOF);
}
else if (encoding_ == payloadEncoding::byteStuffing && b == ESC) {
set_step_(readStep::readEscaped);
break;
}
else {
packet_.payload.push_back(b);
}
break;
case readStep::readEscaped:
b = ~b;
if (obfuscated_) {
b = deobfuscate_(b);
}
packet_.payload.push_back(b);
set_step_(readStep::readPayload);
break;
default:
break;
}
if ((step_ == readStep::readPayload || step_ == readStep::readPayloadFlags) &&
(packet_.payload.size() >= payload_size_)) {
set_step_(readStep::idle);
// finished reading packet
return true;
}
return false;
}
const Packet &PacketDecoder::packet() {
return packet_;
}
}
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include <vector>
#include <queue>
#include <random> // std::linear_congruential_engine
#include <cstdint>
#include "acio2emu/internal/lz.h"
namespace acio2emu {
struct Packet {
uint8_t node;
uint8_t tag;
std::vector<uint8_t> payload;
};
class PacketDecoder {
private:
Packet packet_ = {};
// order matters, don't change this enum!
enum payloadEncoding {
byteStuffing,
raw,
unknown,
replace,
lz,
} encoding_;
uint32_t payload_size_ = 0, payload_size_count_ = 0;
// payloadEncoding::replace state
uint8_t substitute_;
// payloadEncoding::lz state
detail::InflateTransformer inflate_;
// deobfuscation state
bool obfuscated_;
std::linear_congruential_engine<uint32_t, 1103515245, 12345, 0> lcg_;
enum class readStep {
idle,
readNode,
readTag,
readPayloadSize,
readPayloadFlags,
readReplacementByte,
readPayload,
readEscaped,
} step_ = readStep::idle;
void set_step_(readStep s);
void reset_(readStep s);
int update_payload_size_(uint8_t b);
uint8_t deobfuscate_(uint8_t b);
public:
bool update(uint8_t b);
const Packet &packet();
};
bool encode_packet(std::queue<uint8_t> &out, uint8_t node, uint8_t tag, const std::vector<uint8_t> &payload);
}
+209
View File
@@ -0,0 +1,209 @@
#include "acioemu.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace acioemu;
ACIOEmu::ACIOEmu() {
this->devices = new std::vector<ACIODeviceEmu *>();
this->response_buffer = new circular_buffer<uint8_t>(4096);
this->read_buffer = new circular_buffer<uint8_t>(1024);
}
ACIOEmu::~ACIOEmu() {
// delete devices
for (auto device : *this->devices) {
delete device;
}
delete this->devices;
// delete buffers
delete this->response_buffer;
delete this->read_buffer;
}
void ACIOEmu::add_device(ACIODeviceEmu *device) {
this->devices->push_back(device);
}
void ACIOEmu::write(uint8_t byte) {
// insert into buffer
if (!invert) {
if (byte == ACIO_ESCAPE) {
invert = true;
} else {
this->read_buffer->put(byte);
}
} else {
byte = ~byte;
invert = false;
this->read_buffer->put(byte);
}
// clean garbage
while (!this->read_buffer->empty() && this->read_buffer->peek() != 0xAA) {
this->read_buffer->get();
}
while (this->read_buffer->size() > 1 && this->read_buffer->peek(1) == 0xAA) {
this->read_buffer->get();
}
// handshake counter
static unsigned int handshake_counter = 0;
if (byte == 0xAA) {
handshake_counter++;
} else {
handshake_counter = 0;
}
// check for handshake
if (handshake_counter > 1) {
/*
* small hack - BIO2 seems to expect more bytes here - sending two bytes each time fixes it
* TODO replace this handshake code with something better
*/
this->response_buffer->put(ACIO_SOF);
this->response_buffer->put(ACIO_SOF);
handshake_counter--;
return;
}
// parse
if (!this->read_buffer->empty() && this->read_buffer->size() >= 6) {
bool is_complete = false;
// check if broadcast
if (this->read_buffer->peek(1) == ACIO_BROADCAST) {
// check msg data size
auto data_size = this->read_buffer->peek(2);
// check if msg is complete (SOF + checksum + broadcast header + data_size)
is_complete = this->read_buffer->size() >= 2u + 2u + data_size;
} else {
// check msg data size
auto data_size = this->read_buffer->peek(5);
// check if msg is complete (SOF + checksum + command header + data_size)
is_complete = this->read_buffer->size() >= 2u + MSG_HEADER_SIZE + data_size;
}
// parse message if complete
if (is_complete) {
this->msg_parse();
this->read_buffer->reset();
}
}
}
std::optional<uint8_t> ACIOEmu::read() {
if (this->response_buffer->empty()) {
return std::nullopt;
}
return this->response_buffer->get();
}
size_t ACIOEmu::bytes_available() {
return this->response_buffer->size();
}
void ACIOEmu::msg_parse() {
#ifdef ACIOEMU_LOG
log_info("acioemu", "MSG RECV: {}", bin2hex(*this->read_buffer));
#endif
// calculate checksum
uint8_t chk = 0;
size_t max = this->read_buffer->size() - 1;
for (size_t i = 1; i < max; i++) {
chk += this->read_buffer->peek(i);
}
// check checksum
uint8_t chk_receive = this->read_buffer->peek(this->read_buffer->size() - 1);
if (chk != chk_receive) {
#ifdef ACIOEMU_LOG
log_info("acioemu", "detected wrong checksum: {}/{}", chk, chk_receive);
#endif
return;
}
// get message data
auto msg_data = this->read_buffer->peek_all();
auto msg_in = (MessageData *) &msg_data[1];
// correct cmd code endianness if this is not a broadcast
if (msg_in->addr != ACIO_BROADCAST) {
msg_in->cmd.code = acio_u16(msg_in->cmd.code);
}
// pass to applicable device
uint8_t node_offset = 0;
for (auto device : *this->devices) {
if (device->is_applicable(node_offset, msg_in->addr)) {
auto cur_offset = msg_in->addr - node_offset - 1;
if (cur_offset < 0) {
break;
}
if (device->parse_msg(msg_in, this->response_buffer)) {
return;
} else {
break;
}
}
node_offset += device->node_count;
}
// ignore broadcast messages by default
if (msg_in->addr == ACIO_BROADCAST) {
return;
}
/*
* Default Behavior
* If you want to do anything different, just handle the
* commands in your own device implementation.
*/
switch (msg_in->cmd.code) {
// node count report
case ACIO_CMD_ASSIGN_ADDRS: {
if (msg_in->addr == 0x00 && node_offset > 0) {
auto msg = ACIODeviceEmu::create_msg(msg_in, 1, &node_offset);
ACIODeviceEmu::write_msg(msg, this->response_buffer);
delete msg;
return;
}
break;
}
// status 0 defaults
case ACIO_CMD_CLEAR:
case ACIO_CMD_STARTUP:
case 0x80: // KEEPALIVE
case 0xFF: // BROADCAST
{
// send status 0
auto msg = ACIODeviceEmu::create_msg_status(msg_in, 0);
ACIODeviceEmu::write_msg(msg, response_buffer);
delete msg;
return;
}
default:
break;
}
#ifdef ACIOEMU_LOG
log_info("acioemu", "UNHANDLED MSG FOR ADDR: {}, CMD: 0x{:x}), DATA: {}",
msg_in->addr,
msg_in->cmd.code,
bin2hex(*this->read_buffer));
#endif
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
#include <vector>
#include "util/circular_buffer.h"
#include "device.h"
#include "icca.h"
namespace acioemu {
class ACIOEmu {
private:
std::vector<ACIODeviceEmu *> *devices;
circular_buffer<uint8_t> *response_buffer;
circular_buffer<uint8_t> *read_buffer;
bool invert = false;
void msg_parse();
public:
explicit ACIOEmu();
~ACIOEmu();
void add_device(ACIODeviceEmu *device);
void write(uint8_t byte);
std::optional<uint8_t> read();
size_t bytes_available();
};
}
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <ctime>
#include <thread>
#include <mutex>
#include <cstring>
#include "device.h"
#include "hooks/sleephook.h"
namespace acioemu {
#pragma pack(push, 1)
struct bio2_bi2a_state_in {
uint8_t pad0[3];
uint8_t panel[4];
uint8_t deck_switch[14];
uint8_t pad21[2];
uint8_t led_ticker[9];
uint8_t spot_light_1[4];
uint8_t neon_light;
uint8_t spot_light_2[4];
uint8_t pad41[7];
};
struct bio2_bi2a_status {
uint8_t slider_1;
uint8_t system;
uint8_t slider_2;
uint8_t pad3;
uint8_t slider_3;
uint8_t pad5;
uint8_t slider_4;
uint8_t slider_5;
uint8_t pad8;
uint8_t panel;
uint8_t pad10[6];
uint8_t tt_p1;
uint8_t tt_p2;
uint8_t p1_s1;
uint8_t pad20;
uint8_t p1_s2;
uint8_t pad22;
uint8_t p1_s3;
uint8_t pad24;
uint8_t p1_s4;
uint8_t pad26;
uint8_t p1_s5;
uint8_t pad28;
uint8_t p1_s6;
uint8_t pad30;
uint8_t p1_s7;
uint8_t pad32;
uint8_t p2_s1;
uint8_t pad34;
uint8_t p2_s2;
uint8_t pad36;
uint8_t p2_s3;
uint8_t pad38;
uint8_t p2_s4;
uint8_t pad40;
};
#pragma pack(pop)
class BI2A : public ACIODeviceEmu {
private:
uint8_t coin_counter = 0;
public:
explicit BI2A(bool type_new, bool flip_order, bool keypad_thread, uint8_t node_count);
~BI2A() override;
bool parse_msg(unsigned int node_offset,
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer) override;
void update_card(int unit);
void update_keypad(int unit, bool update_edge);
void update_status(int unit);
};
}
+118
View File
@@ -0,0 +1,118 @@
#include "device.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace acioemu;
void ACIODeviceEmu::set_header(MessageData* data, uint8_t addr, uint16_t code, uint8_t pid,
uint8_t data_size)
{
// flag as response
if (addr != 0) {
addr |= ACIO_RESPONSE_FLAG;
}
// set header data
data->addr = addr;
data->cmd.code = acio_u16(code);
data->cmd.pid = pid;
data->cmd.data_size = data_size;
}
void ACIODeviceEmu::set_version(MessageData* data, uint32_t type, uint8_t flag,
uint8_t ver_major, uint8_t ver_minor, uint8_t ver_rev, std::string code)
{
// set version data
auto data_version = &data->cmd.data_version;
data_version->type = type;
data_version->flag = flag;
data_version->ver_major = ver_major;
data_version->ver_minor = ver_minor;
data_version->ver_rev = ver_rev;
strncpy(data_version->code, code.c_str(), sizeof(data_version->code));
strncpy(data_version->date, __DATE__, sizeof(data_version->date));
strncpy(data_version->time, __TIME__, sizeof(data_version->time));
}
MessageData *ACIODeviceEmu::create_msg(uint8_t addr, uint16_t code, uint8_t pid, size_t data_size,
uint8_t *data)
{
// check data size
if (data_size > 0xFF) {
log_warning("acio", "data size > 255: {}", data_size);
data_size = 0xFF;
}
// allocate data
auto data_raw = new uint8_t[MSG_HEADER_SIZE + data_size];
// set header
auto msg = (MessageData *) &data_raw[0];
set_header(msg, addr, code, pid, (uint8_t) data_size);
// set data
if (data) {
memcpy(data_raw + MSG_HEADER_SIZE, data, data_size);
} else {
memset(data_raw + MSG_HEADER_SIZE, 0, data_size);
}
// return prepared message
return msg;
}
MessageData *ACIODeviceEmu::create_msg(MessageData *msg_in, size_t data_size, uint8_t *data) {
return create_msg(msg_in->addr, msg_in->cmd.code, msg_in->cmd.pid, data_size, data);
}
MessageData *ACIODeviceEmu::create_msg_status(uint8_t addr, uint16_t code, uint8_t pid, uint8_t status) {
return create_msg(addr, code, pid, 1, &status);
}
MessageData *ACIODeviceEmu::create_msg_status(MessageData *msg_in, uint8_t status) {
return create_msg_status(msg_in->addr, msg_in->cmd.code, msg_in->cmd.pid, status);
}
bool ACIODeviceEmu::is_applicable(uint8_t node_offset, uint8_t node) {
return node > node_offset && node <= node_offset + this->node_count;
}
void ACIODeviceEmu::write_msg(const uint8_t *data, size_t size, circular_buffer<uint8_t> *response_buffer) {
// header
for (int i = 0; i < 2; i++) {
response_buffer->put(ACIO_SOF);
}
// msg data and checksum
uint8_t b, chk = 0;
for (size_t i = 0; i <= size; i++) {
// set byte to data or checksum
if (i < size) {
b = data[i];
chk += b;
} else {
b = chk;
}
// check for escape
if (b == ACIO_SOF || b == ACIO_ESCAPE) {
response_buffer->put(ACIO_ESCAPE);
response_buffer->put(~b);
} else {
response_buffer->put(b);
}
}
#ifdef ACIOEMU_LOG
log_info("acioemu", "ACIO MSG OUT: AA{}{:02X}", bin2hex(data, size), chk);
#endif
}
void ACIODeviceEmu::write_msg(MessageData *msg, circular_buffer<uint8_t> *response_buffer) {
auto data = reinterpret_cast<const uint8_t *>(msg);
write_msg(data, MSG_HEADER_SIZE + msg->cmd.data_size, response_buffer);
}
+103
View File
@@ -0,0 +1,103 @@
#pragma once
#include <string>
#include "util/circular_buffer.h"
// convert big-endian to little-endian
#define acio_u16 _byteswap_ushort
#define acio_u32 _byteswap_ulong
namespace acioemu {
constexpr uint8_t ACIO_SOF = 0xAA;
constexpr uint8_t ACIO_ESCAPE = 0xFF;
constexpr uint8_t ACIO_BROADCAST = 0x70;
constexpr uint8_t ACIO_RESPONSE_FLAG = 0x80;
// general command codes
enum acio_cmd_codes {
ACIO_CMD_ASSIGN_ADDRS = 0x0001,
ACIO_CMD_GET_VERSION = 0x0002,
ACIO_CMD_STARTUP = 0x0003,
ACIO_CMD_KEEPALIVE = 0x0080,
ACIO_CMD_CLEAR = 0x0100,
};
// message structs
#pragma pack(push, 1)
struct VersionData {
uint32_t type;
uint8_t flag;
uint8_t ver_major;
uint8_t ver_minor;
uint8_t ver_rev;
char code[4];
char date[16];
char time[16];
};
struct MessageData {
uint8_t addr;
union {
struct {
uint16_t code;
uint8_t pid;
uint8_t data_size;
union {
uint8_t raw[0xFF];
uint8_t status;
VersionData data_version;
};
} cmd;
struct {
uint8_t data_size;
uint8_t raw[0xFF];
} broadcast;
};
};
#pragma pack(pop)
// message sizes
constexpr size_t MSG_HEADER_SIZE = 5;
constexpr size_t MSG_VERSION_SIZE = sizeof(VersionData);
class ACIODeviceEmu {
public:
// attributes
uint8_t node_count = 0;
/*
* Helper functions for getting/setting the message contents
*/
static void set_header(MessageData* data, uint8_t addr, uint16_t code, uint8_t pid, uint8_t data_size);
static void set_version(MessageData* data, uint32_t type, uint8_t flag,
uint8_t ver_major, uint8_t ver_minor, uint8_t ver_rev,
std::string code);
/*
* This function creates a basic message with optional parameter data.
* If data is set to null, the parameter data will be initialized with 0x00
*/
static MessageData* create_msg(uint8_t addr, uint16_t cmd, uint8_t pid,
size_t data_size, uint8_t *data = nullptr);
static MessageData* create_msg(MessageData* msg_in, size_t data_size, uint8_t *data = nullptr);
/*
* Helper functions for generating messages
*/
static MessageData* create_msg_status(uint8_t addr, uint16_t code, uint8_t pid, uint8_t status);
static MessageData* create_msg_status(MessageData* msg_in, uint8_t status);
virtual ~ACIODeviceEmu() = default;
virtual bool is_applicable(uint8_t node_offset, uint8_t node);
virtual bool parse_msg(MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) = 0;
static void write_msg(const uint8_t *data, size_t size, circular_buffer<uint8_t> *response_buffer);
static void write_msg(MessageData *msg, circular_buffer<uint8_t> *response_buffer);
};
}
+74
View File
@@ -0,0 +1,74 @@
#include "handle.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount) {
this->com_port = lpCOMPort;
this->icca_node_count = iccaNodeCount;
}
bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, com_port) != 0) {
return false;
}
log_info("acioemu", "Opened {} (ACIO)", ws2s(com_port));
// ACIO device
acio_emu.add_device(new acioemu::ICCADevice(false, true, icca_node_count));
return true;
}
int acioemu::ACIOHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// read from emu
DWORD bytes_read = 0;
while (bytes_read < nNumberOfBytesToRead) {
auto cur_byte = acio_emu.read();
if (cur_byte.has_value()) {
buffer[bytes_read++] = cur_byte.value();
} else {
break;
}
}
// return amount of bytes read
return (int) bytes_read;
}
int acioemu::ACIOHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
// write to emu
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu.write(buffer[i]);
}
// return all data written
return (int) nNumberOfBytesToWrite;
}
int acioemu::ACIOHandle::device_io(
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize
) {
return -1;
}
size_t acioemu::ACIOHandle::bytes_available() {
return acio_emu.bytes_available();
}
bool acioemu::ACIOHandle::close() {
log_info("acioemu", "Closed {} (ACIO)", ws2s(com_port));
return true;
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace acioemu {
class ACIOHandle : public CustomHandle {
private:
LPCWSTR com_port;
uint8_t icca_node_count;
acioemu::ACIOEmu acio_emu;
public:
ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount = 2);
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
size_t bytes_available() override;
bool close() override;
};
}
+578
View File
@@ -0,0 +1,578 @@
#include "icca.h"
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "games/sdvx/sdvx.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace acioemu;
namespace acioemu {
bool ICCA_DEVICE_HACK = false;
}
ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count) {
// init defaults
this->type_new = false;
this->flip_order = flip_order;
this->node_count = node_count;
this->cards = new uint8_t *[node_count] {};
this->cards_time = new time_t[node_count] {};
this->status = new uint8_t[node_count * 16] {};
this->accept = new bool[node_count] {};
for (int i = 0; i < node_count; i++) {
this->accept[i] = true;
}
this->hold = new bool[node_count] {};
this->keydown = new uint8_t[node_count] {};
this->keypad = new uint16_t[node_count] {};
this->keypad_last = new bool*[node_count] {};
for (int i = 0; i < node_count; i++) {
this->keypad_last[i] = new bool[12] {};
}
this->keypad_capture = new uint8_t[node_count] {};
for (int i = 0; i < node_count; i++) {
this->keypad_capture[i] = 0x08;
}
this->crypt = new std::optional<Crypt>[node_count] {};
this->counter = new uint8_t[node_count] {};
for (int i = 0; i < node_count; i++) {
this->counter[i] = 2;
}
// keypad thread for faster polling
if (keypad_thread) {
this->keypad_thread = new std::thread([this]() {
while (this->cards) {
for (int unit = 0; unit < this->node_count; unit++) {
this->update_keypad(unit, false);
}
Sleep(7);
}
});
}
}
ICCADevice::~ICCADevice() {
// stop thread
delete keypad_thread;
// delete cards in array
for (int i = 0; i < node_count; i++) {
delete cards[i];
}
// delete the rest
delete[] cards;
delete[] cards_time;
delete[] status;
delete[] accept;
delete[] hold;
delete[] keydown;
delete[] keypad;
delete[] keypad_last;
delete[] keypad_capture;
delete[] crypt;
delete[] counter;
}
bool ICCADevice::parse_msg(MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer) {
// get unit
int unit = msg_in->addr - 1;
if (this->flip_order) {
unit = this->node_count - unit - 1;
}
if (unit != 0 && unit != 1) {
log_fatal("icca", "invalid unit: {}", unit);
}
#ifdef ACIOEMU_LOG
log_info("acioemu", "ICCA ADDR: {}, CMD: 0x{:04x}", unit, msg_in->cmd.code);
#endif
// check command
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
if (avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
games::sdvx::is_valkyrie_model()) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} else if (avs::game::is_model("VFG")) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
} else {
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
}
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0130: { // REINITIALIZE
// send status 0
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0131: { // READ CARD UID
// build data array
auto msg = this->create_msg(msg_in, 16);
// update things
update_card(unit);
update_keypad(unit, true);
update_status(unit);
// copy status
memcpy(msg->cmd.raw, &status[unit * 16], 16);
// explicitly set no card since this is just read
msg->cmd.raw[0] = 0x01;
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0135: { // SET ACTION
// check for data
if (msg_in->cmd.data_size >= 2) {
// subcommand
switch (msg_in->cmd.raw[1]) {
case 0x00: // ACCEPT DISABLE
this->accept[unit] = false;
break;
case 0x11: // ACCEPT ENABLE
this->accept[unit] = true;
break;
case 0x12: // EJECT
if (this->cards[unit] != nullptr) {
delete this->cards[unit];
}
this->cards[unit] = nullptr;
this->hold[unit] = false;
default:
break;
}
}
// no break, return status
[[fallthrough]];
}
case 0x0134: { // GET STATUS
// build data array
auto msg = this->create_msg(msg_in, 16);
// update things
update_card(unit);
update_keypad(unit, true);
update_status(unit);
// copy status
memcpy(msg->cmd.raw, &status[unit * 16], 16);
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0160: { // KEY EXCHANGE
// if this cmd is called, the reader type must be new
this->type_new = true;
// build data array
auto msg = this->create_msg(msg_in, 4);
// set key
msg->cmd.raw[0] = 0xBE;
msg->cmd.raw[1] = 0xEF;
msg->cmd.raw[2] = 0xCA;
msg->cmd.raw[3] = 0xFE;
// convert keys
uint32_t game_key =
msg_in->cmd.raw[0] << 24 |
msg_in->cmd.raw[1] << 16 |
msg_in->cmd.raw[2] << 8 |
msg_in->cmd.raw[3];
uint32_t reader_key =
msg->cmd.raw[0] << 24 |
msg->cmd.raw[1] << 16 |
msg->cmd.raw[2] << 8 |
msg->cmd.raw[3];
log_info("icca", "client key: {:08x}", game_key);
log_info("icca", "reader key: {:08x}", reader_key);
this->crypt[unit].emplace();
this->crypt[unit]->set_keys(reader_key, game_key);
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0161: { // READ CARD UID NEW
// if this cmd is called, the reader type must be new
this->type_new = true;
// decide on answer
int answer_type = 0;
//if (avs::game::is_model("LDJ"))
//answer_type = 1;
// SDVX Old cabinet mode
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
answer_type = 1;
if (avs::game::is_model("L44"))
answer_type = 2;
// check answer type
switch (answer_type) {
case 1: {
// send status 1
auto msg = this->create_msg_status(msg_in, 1);
write_msg(msg, response_buffer);
delete msg;
break;
}
case 2: {
// build data array
auto msg = this->create_msg(msg_in, 16);
// update card
update_card(unit);
// check for card
if (this->cards[unit] != nullptr) {
// copy into data buffer
memcpy(msg->cmd.raw, this->cards[unit], 8);
// delete card
delete this->cards[unit];
this->cards[unit] = nullptr;
this->hold[unit] = false;
}
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
default: {
// send response with no data
auto msg = this->create_msg(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
}
break;
}
case 0x0164: { // GET STATUS ENC
// build data array
auto msg = this->create_msg(msg_in, 18);
// update things
update_card(unit);
update_keypad(unit, true);
update_status(unit);
// copy status
memcpy(msg->cmd.raw, &status[unit * 16], 16);
if (this->crypt[unit].has_value()) {
auto &crypt = this->crypt[unit];
uint16_t crc = crypt->crc(msg->cmd.raw, 16);
msg->cmd.raw[16] = (uint8_t) (crc >> 8);
msg->cmd.raw[17] = (uint8_t) crc;
crypt->crypt(msg->cmd.raw, 18);
} else {
log_warning("icca", "'GET STATUS ENC' message received with no crypt keys initialized");
}
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x013A: { // POWER CONTROL (tentative name, used in 1.7 firmware)
// TODO(felix): isolate this logic to LDJ and/or firmware 1.7 emulation
if (this->counter[unit] > 0) {
this->counter[unit]--;
}
//log_info("icca", "counter[{}] = {}", unit, this->counter[unit]);
auto msg = this->create_msg_status(msg_in, this->counter[unit]);
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_STARTUP:
case ACIO_CMD_CLEAR:
case 0x30: // GetBoardProductNumber
case 0x31: // GetMicomInfo
case 0x3A: // ???
case 0x0116: // ???
case 0x0120: // ???
case 0xFF: // BROADCAST
{
// send status 0
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
return false;
}
// mark as handled
return true;
}
void ICCADevice::update_card(int unit) {
// wavepass timeout after 10s
if (this->cards[unit] != nullptr) {
time_t t_now;
time(&t_now);
if (difftime(t_now, this->cards_time[unit]) >= 10.f) {
if (this->cards[unit] != nullptr) {
delete this->cards[unit];
}
this->cards[unit] = nullptr;
this->hold[unit] = false;
}
}
bool kb_insert_press = false;
// eamio keypress
kb_insert_press |= static_cast<bool>(eamuse_get_keypad_state((size_t) unit) & (1 << EAM_IO_INSERT));
// check for card
if (this->cards[unit] == nullptr && (eamuse_card_insert_consume(this->node_count, unit) || kb_insert_press)) {
auto card = new uint8_t[8];
if (!eamuse_get_card(this->node_count, unit, card)) {
// invalid card found
delete[] card;
} else {
this->cards[unit] = card;
time(&this->cards_time[unit]);
}
}
}
static int KEYPAD_EAMUSE_MAPPING[] = {
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
// map for KEYPAD_KEY_CODES:
// 7 8 9 | 800 8000 8
// 4 5 6 | 400 4000 4
// 1 2 3 | 200 2000 2
// 0 00 . | 100 1000 1
static int KEYPAD_KEY_CODES[]{
0x100, // 0
0x200, // 1
0x2000, // 2
2, // 3
0x400, // 4
0x4000, // 5
4, // 6
0x800, // 7
0x8000, // 8
8, // 9
1, // .
0x1000 // 00
};
// map for KEYPAD_KEY_CODES_ALT:
// 7 8 9 | 8 80 800
// 4 5 6 | 4 40 400
// 1 2 3 | 2 20 200
// 0 00 . | 1 10 100
//
// note that the only game that needs this (SDVX VM) does not accept decimal,
// so that key is untested
static int KEYPAD_KEY_CODES_ALT[]{
1, // 0
2, // 1
0x20, // 2
0x200, // 3
4, // 4
0x40, // 5
0x400, // 6
8, // 7
0x80, // 8
0x800, // 9
0x100, // .
0x10 // 00
};
static uint8_t KEYPAD_KEY_CODE_NUMS[]{
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
void ICCADevice::update_keypad(int unit, bool update_edge) {
// lock keypad so threads can't interfere
std::lock_guard<std::mutex> lock(this->keypad_mutex);
// reset unit
this->keypad[unit] = 0;
// get eamu key states
uint16_t eamu_state = eamuse_get_keypad_state((size_t) unit);
// iterate keypad
bool edge = false;
for (int n = 0; n < 12; n++) {
int i = n;
// check if pressed
if (eamu_state & (1 << KEYPAD_EAMUSE_MAPPING[i])) {
if (ICCA_DEVICE_HACK) {
this->keypad[unit] |= KEYPAD_KEY_CODES_ALT[i];
} else {
this->keypad[unit] |= KEYPAD_KEY_CODES[i];
}
if (!this->keypad_last[unit][i] && update_edge) {
this->keydown[unit] = (this->keypad_capture[unit] << 4) | KEYPAD_KEY_CODE_NUMS[n];
this->keypad_last[unit][i] = true;
edge = true;
}
} else {
this->keypad_last[unit][i] = false;
}
}
// update keypad capture
if (update_edge && edge) {
this->keypad_capture[unit]++;
this->keypad_capture[unit] |= 0x08;
} else {
this->keydown[unit] = 0;
}
}
void ICCADevice::update_status(int unit) {
// get buffer
uint8_t *buffer = &this->status[unit * 16];
// clear buffer
memset(buffer, 0x00, 16);
// check for card
bool card = false;
if (this->cards[unit] != nullptr) {
// copy card into buffer
memcpy(buffer + 2, this->cards[unit], 8);
card = true;
}
// check for reader type
if (this->type_new) {
// check for card
if (card) {
// set status to card present
buffer[0] = 0x02;
/*
* set card type
* 0x00 - ISO15696
* 0x01 - FELICA
*/
bool felica = buffer[2] != 0xE0 && buffer[3] != 0x04;
buffer[1] = felica ? 0x01 : 0x00;
buffer[10] = felica ? 0x01 : 0x00;
} else if (avs::game::is_model({"LDJ", "TBS", "XIF"}) || games::sdvx::is_valkyrie_model()) {
// set status to 0 otherwise reader power on fails
buffer[0] = 0x00;
} else {
// set status to no card present (1 or 4)
buffer[0] = 0x04;
}
} else { // old reader
// check for card
if (card && accept[unit]) {
this->hold[unit] = true;
}
// check for hold
if (this->hold[unit]) {
// set status to card present
buffer[0] = 0x02;
/*
* sensors
* 0x10 - OLD READER FRONT
* 0x20 - OLD READER BACK
*/
// activate both sensors
buffer[1] = 0x30;
} else {
// card present but reader isn't accepting it
if (card) {
// set card present
buffer[0] = 0x02;
// set front sensor
buffer[1] = 0x10;
} else {
// no card present
buffer[0] = 0x01;
}
}
// card type not present for old reader
buffer[10] = 0x00;
}
// other flags
buffer[11] = 0x03;
buffer[12] = keydown[unit];
buffer[13] = 0x00;
buffer[14] = (uint8_t) (keypad[unit] >> 8);
buffer[15] = (uint8_t) (keypad[unit] & 0xFF);
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <cstring>
#include <ctime>
#include <mutex>
#include <optional>
#include <thread>
#include "device.h"
#include "hooks/sleephook.h"
#include "reader/crypt.h"
namespace acioemu {
extern bool ICCA_DEVICE_HACK;
class ICCADevice : public ACIODeviceEmu {
private:
bool type_new;
bool flip_order;
std::thread *keypad_thread;
std::mutex keypad_mutex;
uint8_t **cards;
time_t *cards_time;
uint8_t *status;
bool *accept;
bool *hold;
uint8_t *keydown;
uint16_t *keypad;
bool **keypad_last;
uint8_t *keypad_capture;
std::optional<Crypt> *crypt;
uint8_t *counter;
public:
explicit ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count);
~ICCADevice() override;
bool parse_msg(MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) override;
void update_card(int unit);
void update_keypad(int unit, bool update_edge);
void update_status(int unit);
};
}
+463
View File
@@ -0,0 +1,463 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include "controller.h"
#include <utility>
#include "cfg/configurator.h"
#include "external/rapidjson/document.h"
#include "util/crypt.h"
#include "util/logging.h"
#include "util/utils.h"
#include "module.h"
#include "modules/analogs.h"
#include "modules/buttons.h"
#include "modules/card.h"
#include "modules/capture.h"
#include "modules/coin.h"
#include "modules/control.h"
#include "modules/ddr.h"
#include "modules/drs.h"
#include "modules/iidx.h"
#include "modules/info.h"
#include "modules/keypads.h"
#include "modules/lcd.h"
#include "modules/lights.h"
#include "modules/memory.h"
#include "modules/touch.h"
#include "modules/resize.h"
#include "request.h"
#include "response.h"
using namespace rapidjson;
using namespace api;
Controller::Controller(unsigned short port, std::string password, bool pretty)
: port(port), password(std::move(password)), pretty(pretty)
{
if (!crypt::INITIALIZED && !this->password.empty()) {
log_fatal("api", "API server with password cannot be used without crypt module");
}
// WSA startup
WSADATA wsa_data;
int error;
if ((error = WSAStartup(MAKEWORD(2, 2), &wsa_data)) != 0) {
log_warning("api", "WSAStartup() returned {}", error);
this->server = INVALID_SOCKET;
if (!cfg::CONFIGURATOR_STANDALONE) {
log_fatal("api", "failed to start server");
}
return;
}
// create socket
this->server = socket(AF_INET, SOCK_STREAM, 0);
if (this->server == INVALID_SOCKET) {
log_warning("api", "could not create listener socket: {}", get_last_error_string());
if (!cfg::CONFIGURATOR_STANDALONE) {
log_fatal("api", "failed to start server");
}
return;
}
// configure socket
int opt_enable = 1;
if (setsockopt(this->server, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char *>(&opt_enable), sizeof(int)) == -1)
{
log_warning("api", "could not set socket option SO_REUSEADDR: {}", get_last_error_string());
}
if (setsockopt(this->server, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<const char *>(&opt_enable), sizeof(int)) == -1)
{
log_warning("api", "could not set socket option TCP_NODELAY: {}", get_last_error_string());
}
// create address
sockaddr_in server_address{};
server_address.sin_family = AF_INET;
server_address.sin_port = htons(this->port);
server_address.sin_addr.s_addr = INADDR_ANY;
memset(&server_address.sin_zero, 0, sizeof(server_address.sin_zero));
// bind socket to address
if (bind(this->server, (sockaddr *) &server_address, sizeof(sockaddr)) == -1) {
log_warning("api", "could not bind socket on port {}: {}", port, get_last_error_string());
this->server = INVALID_SOCKET;
if (!cfg::CONFIGURATOR_STANDALONE) {
log_fatal("api", "failed to start server");
}
return;
}
// set socket to listen
if (listen(this->server, server_backlog) == -1) {
log_warning("api", "could not listen to socket on port {}: {}", port, get_last_error_string());
this->server = INVALID_SOCKET;
if (!cfg::CONFIGURATOR_STANDALONE) {
log_fatal("api", "failed to start server");
}
return;
}
// start workers
this->server_running = true;
for (int i = 0; i < server_worker_count; i++) {
this->server_workers.emplace_back(std::thread([this] {
this->server_worker();
}));
}
// log success
log_info("api", "API server is listening on port: {}", this->port);
log_info("api", "Using password: {}", this->password.empty() ? "no" : "yes");
// start websocket on next port
this->websocket = new WebSocketController(this, port + 1);
}
Controller::~Controller() {
// stop websocket
delete this->websocket;
// stop serial controllers
for (auto &s : this->serial) {
delete s;
}
// mark server stop
this->server_running = false;
// close socket
if (this->server != INVALID_SOCKET) {
closesocket(this->server);
}
// lock handlers
std::lock_guard<std::mutex> handlers_guard(this->server_handlers_m);
// join threads
for (auto &worker : this->server_workers) {
worker.join();
}
for (auto &handler : this->server_handlers) {
handler.join();
}
// cleanup WSA
WSACleanup();
}
void Controller::listen_serial(std::string port, DWORD baud) {
this->serial.push_back(new SerialController(this, port, baud));
}
void Controller::server_worker() {
// connection loop
while (this->server_running) {
// create client state
ClientState client_state {};
// accept connection
int socket_in_size = sizeof(sockaddr_in);
client_state.socket = accept(this->server, (sockaddr *) &client_state.address, &socket_in_size);
if (client_state.socket == INVALID_SOCKET) {
continue;
}
// lock handlers
std::lock_guard<std::mutex> handlers_guard(this->server_handlers_m);
// check connection limit
if (this->server_handlers.size() >= server_connection_limit) {
log_warning("api", "connection limit hit");
closesocket(client_state.socket);
continue;
}
// handle connection
this->server_handlers.emplace_back(std::thread([this, client_state] {
this->connection_handler(client_state);
}));
}
}
void Controller::connection_handler(api::ClientState client_state) {
// get address string
char client_address_str_data[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_state.address.sin_addr, client_address_str_data, INET_ADDRSTRLEN);
std::string client_address_str(client_address_str_data);
// log connection
log_info("api", "client connected: {}", client_address_str);
client_states_m.lock();
client_states.emplace_back(&client_state);
client_states_m.unlock();
// init state
init_state(&client_state);
// listen loop
std::vector<char> message_buffer;
char receive_buffer[server_receive_buffer_size];
while (this->server_running && !client_state.close) {
// receive data
int received_length = recv(client_state.socket, receive_buffer, server_receive_buffer_size, 0);
if (received_length < 0) {
// if the received length is < 0, we've got an error
log_warning("api", "receive error: {}", WSAGetLastError());
break;
} else if (received_length == 0) {
// if the received length is 0, the connection is closed
break;
}
// cipher
if (client_state.cipher != nullptr) {
client_state.cipher->crypt(
(uint8_t *) receive_buffer,
(size_t) received_length
);
}
// put into buffer
for (int i = 0; i < received_length; i++) {
// check for escape byte
if (receive_buffer[i] == 0) {
// get response
std::vector<char> send_buffer;
this->process_request(&client_state, &message_buffer, &send_buffer);
// clear message buffer
message_buffer.clear();
// check send buffer for content
if (!send_buffer.empty()) {
// cipher
if (client_state.cipher != nullptr) {
client_state.cipher->crypt(
(uint8_t *) send_buffer.data(),
(size_t) send_buffer.size()
);
}
// send data
send(client_state.socket, send_buffer.data(), (int) send_buffer.size(), 0);
// check for password change
process_password_change(&client_state);
}
} else {
// append to message
message_buffer.push_back(receive_buffer[i]);
// check buffer size
if (message_buffer.size() > server_message_buffer_max_size) {
message_buffer.clear();
client_state.close = true;
break;
}
}
}
}
// log disconnect
log_info("api", "client disconnected: {}", client_address_str);
client_states_m.lock();
client_states.erase(std::remove(client_states.begin(), client_states.end(), &client_state));
client_states_m.unlock();
// close connection
closesocket(client_state.socket);
// free state
free_state(&client_state);
}
bool Controller::process_request(ClientState *state, std::vector<char> *in, std::vector<char> *out) {
return this->process_request(state, &(*in)[0], in->size(), out);
}
bool Controller::process_request(ClientState *state, const char *in, size_t in_size, std::vector<char> *out) {
// parse document
Document document;
document.Parse(in, in_size);
// check for parse error
if (document.HasParseError()) {
// return empty response and close connection
out->push_back(0);
state->close = true;
return false;
}
// build request and response
Request request(document);
Response response(request.id);
bool success = true;
// check if request has parse error
if (request.parse_error) {
Value module_error("Request parse error (invalid message format?).");
response.add_error(module_error);
success = false;
} else {
// find module
bool module_found = false;
for (auto module : state->modules) {
if (module->name == request.module) {
module_found = true;
// check password force
if (module->password_force && this->password.empty() && request.function != "session_refresh") {
Value err("Module requires the password to be set.");
response.add_error(err);
break;
}
// handle request
module->handle(request, response);
break;
}
}
// check if module wasn't found
if (!module_found) {
Value module_error("Unknown module.");
response.add_error(module_error);
}
// check for password change
if (response.password_changed) {
state->password = response.password;
state->password_change = true;
}
}
// write response
auto response_out = response.get_string(this->pretty);
out->insert(out->end(), response_out.begin(), response_out.end());
out->push_back(0);
return success;
}
void Controller::process_password_change(api::ClientState *state) {
// check for password change
if (state->password_change) {
state->password_change = false;
delete state->cipher;
if (state->password.empty()) {
state->cipher = nullptr;
} else {
state->cipher = new util::RC4(
(uint8_t *) state->password.c_str(),
state->password.size());
}
}
}
void Controller::init_state(api::ClientState *state) {
// check if already initialized
if (!state->modules.empty()) {
log_fatal("api", "client state double initialization");
}
// cipher
state->cipher = nullptr;
state->password = this->password;
if (!this->password.empty()) {
state->cipher = new util::RC4((uint8_t *) this->password.c_str(), this->password.size());
}
// create module instances
state->modules.push_back(new modules::Analogs());
state->modules.push_back(new modules::Buttons());
state->modules.push_back(new modules::Card());
state->modules.push_back(new modules::Capture());
state->modules.push_back(new modules::Coin());
state->modules.push_back(new modules::Control());
state->modules.push_back(new modules::DDR());
state->modules.push_back(new modules::DRS());
state->modules.push_back(new modules::IIDX());
state->modules.push_back(new modules::Info());
state->modules.push_back(new modules::Keypads());
state->modules.push_back(new modules::LCD());
state->modules.push_back(new modules::Lights());
state->modules.push_back(new modules::Memory());
state->modules.push_back(new modules::Touch());
state->modules.push_back(new modules::Resize());
}
void Controller::free_state(api::ClientState *state) {
// free modules
for (auto module : state->modules) {
delete module;
}
// free cipher
delete state->cipher;
}
void Controller::free_socket() {
if (this->server != INVALID_SOCKET) {
closesocket(this->server);
this->server = INVALID_SOCKET;
}
this->websocket->free_socket();
for (auto &s : this->serial) {
s->free_port();
}
}
void Controller::obtain_client_states(std::vector<ClientState> *vec) {
std::lock_guard<std::mutex> lock(this->client_states_m);
for (auto &state : this->client_states) {
vec->push_back(*state);
}
}
std::string Controller::get_ip_address(sockaddr_in addr) {
switch (addr.sin_family) {
default:
case AF_INET: {
char buf[INET_ADDRSTRLEN];
auto ret = inet_ntop(AF_INET, &(addr.sin_addr), buf, sizeof(buf));
if (ret != nullptr) {
return std::string(ret);
} else {
return "unknown (" + to_string(WSAGetLastError()) + ")";
}
}
case AF_INET6: {
char buf[INET6_ADDRSTRLEN];
auto ret = inet_ntop(AF_INET6, &(addr.sin_addr), buf, sizeof(buf));
if (ret != nullptr) {
return std::string(ret);
} else {
return "unknown (" + to_string(WSAGetLastError()) + ")";
}
}
}
}
+82
View File
@@ -0,0 +1,82 @@
#pragma once
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <winsock2.h>
#include "util/rc4.h"
#include "module.h"
#include "websocket.h"
#include "serial.h"
namespace api {
struct ClientState {
SOCKADDR_IN address;
SOCKET socket;
bool close = false;
std::vector<Module*> modules;
std::string password;
bool password_change = false;
util::RC4 *cipher = nullptr;
};
class Controller {
private:
// configuration
const static int server_backlog = 16;
const static int server_receive_buffer_size = 64 * 1024;
const static int server_message_buffer_max_size = 64 * 1024;
const static int server_worker_count = 2;
const static int server_connection_limit = 4096;
// settings
unsigned short port;
std::string password;
bool pretty;
// server
WebSocketController *websocket;
std::vector<SerialController *> serial;
std::vector<std::thread> server_workers;
std::vector<std::thread> server_handlers;
std::mutex server_handlers_m;
std::vector<api::ClientState *> client_states;
std::mutex client_states_m;
SOCKET server;
void server_worker();
void connection_handler(ClientState client_state);
public:
// state
bool server_running;
// constructor / destructor
Controller(unsigned short port, std::string password, bool pretty);
~Controller();
void listen_serial(std::string port, DWORD baud);
bool process_request(ClientState *state, std::vector<char> *in, std::vector<char> *out);
bool process_request(ClientState *state, const char *in, size_t in_size, std::vector<char> *out);
static void process_password_change(ClientState *state);
void init_state(ClientState *state);
static void free_state(ClientState *state);
void free_socket();
void obtain_client_states(std::vector<ClientState> *output);
std::string get_ip_address(sockaddr_in addr);
inline const std::string &get_password() const {
return this->password;
}
};
}
+43
View File
@@ -0,0 +1,43 @@
#include <utility>
#include "util/logging.h"
#include "module.h"
using namespace rapidjson;
namespace api {
// logging setting
bool LOGGING = false;
Module::Module(std::string name, bool password_force) {
this->name = std::move(name);
this->password_force = password_force;
}
void Module::handle(Request &req, Response &res) {
// log module access
if (LOGGING)
log_info("api::" + this->name, "handling request");
// find function
auto pos = functions.find(req.function);
if (pos == functions.end())
return error_function_unknown(res);
// call function
pos->second(req, res);
}
void Module::error(Response &res, std::string err) {
// log the warning
log_warning("api::" + this->name, "error: {}", err);
// add error to response
Value val(err.c_str(), res.doc()->GetAllocator());
res.add_error(val);
}
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include <functional>
#include <map>
#include <string>
#include <sstream>
#include <external/robin_hood.h>
#include "response.h"
#include "request.h"
namespace api {
// logging setting
extern bool LOGGING;
// callback
typedef std::function<void(Request &, Response &)> ModuleFunctionCallback;
class Module {
protected:
// map of available functions
robin_hood::unordered_map<std::string, ModuleFunctionCallback> functions;
// default constructor
explicit Module(std::string name, bool password_force=false);
public:
// virtual deconstructor
virtual ~Module() = default;
// name of the module (should match namespace)
std::string name;
bool password_force;
// the magic
void handle(Request &req, Response &res);
/*
* Error definitions.
*/
void error(Response &res, std::string err);
void error_type(Response &res, const std::string &field, const std::string &type) {
std::ostringstream s;
s << field << " must be a " << type;
error(res, s.str());
};
void error_size(Response &res, const std::string &field, size_t size) {
std::ostringstream s;
s << field << " must be of size " << size;
error(res, s.str());
}
void error_unknown(Response &res, const std::string &field, const std::string &name) {
std::ostringstream s;
s << "Unknown " << field << ": " << name;
error(res, s.str());
}
#define ERR(name, err) void error_##name(Response &res) { error(res, err); }
ERR(function_unknown, "Unknown function.");
ERR(params_insufficient, "Insufficient number of parameters.");
#undef ERR
};
}
+177
View File
@@ -0,0 +1,177 @@
#include "analogs.h"
#include <functional>
#include "external/rapidjson/document.h"
#include "misc/eamuse.h"
#include "cfg/analog.h"
#include "launcher/launcher.h"
#include "games/io.h"
#include "util/utils.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
Analogs::Analogs() : Module("analogs") {
functions["read"] = std::bind(&Analogs::read, this, _1, _2);
functions["write"] = std::bind(&Analogs::write, this, _1, _2);
functions["write_reset"] = std::bind(&Analogs::write_reset, this, _1, _2);
analogs = games::get_analogs(eamuse_get_game());
}
/**
* read()
*/
void Analogs::read(api::Request &req, Response &res) {
// check analog cache
if (!analogs) {
return;
}
// add state for each analog
for (auto &analog : *this->analogs) {
Value state(kArrayType);
Value analog_name(analog.getName().c_str(), res.doc()->GetAllocator());
Value analog_state(GameAPI::Analogs::getState(RI_MGR, analog));
Value analog_enabled(analog.override_enabled);
state.PushBack(analog_name, res.doc()->GetAllocator());
state.PushBack(analog_state, res.doc()->GetAllocator());
state.PushBack(analog_enabled, res.doc()->GetAllocator());
res.add_data(state);
}
}
/**
* write([name: str, state: float], ...)
*/
void Analogs::write(Request &req, Response &res) {
// check analog cache
if (!analogs)
return;
// loop parameters
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsArray()) {
error(res, "parameters must be arrays");
return;
}
if (param.Size() < 2) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
if (!param[1].IsFloat() && !param[1].IsInt()) {
error_type(res, "state", "float");
continue;
}
// get params
auto analog_name = param[0].GetString();
auto analog_state = param[1].GetFloat();
// write analog state
if (!this->write_analog(analog_name, analog_state)) {
error_unknown(res, "analog", analog_name);
continue;
}
}
}
/**
* write_reset()
* write_reset([name: str], ...)
*/
void Analogs::write_reset(Request &req, Response &res) {
// check analog cache
if (!analogs)
return;
// get params
auto params = req.params.GetArray();
// write_reset()
if (params.Size() == 0) {
if (analogs != nullptr) {
for (auto &analog : *this->analogs) {
analog.override_enabled = false;
}
}
return;
}
// loop parameters
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsArray()) {
error(res, "parameters must be arrays");
return;
}
if (param.Size() < 1) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
// get params
auto analog_name = param[0].GetString();
// write analog state
if (!this->write_analog_reset(analog_name)) {
error_unknown(res, "analog", analog_name);
continue;
}
}
}
bool Analogs::write_analog(std::string name, float state) {
// check analog cache
if (!this->analogs) {
return false;
}
// find analog
for (auto &analog : *this->analogs) {
if (analog.getName() == name) {
analog.override_state = CLAMP(state, 0.f, 1.f);
analog.override_enabled = true;
return true;
}
}
// unknown analog
return false;
}
bool Analogs::write_analog_reset(std::string name) {
// check analog cache
if (!analogs) {
return false;
}
// find analog
for (auto &analog : *this->analogs) {
if (analog.getName() == name) {
analog.override_enabled = false;
return true;
}
}
// unknown analog
return false;
}
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <vector>
#include "api/module.h"
#include "api/request.h"
#include "cfg/api.h"
namespace api::modules {
class Analogs : public Module {
public:
Analogs();
private:
// state
std::vector<Analog> *analogs;
// function definitions
void read(Request &req, Response &res);
void write(Request &req, Response &res);
void write_reset(Request &req, Response &res);
// helper
bool write_analog(std::string name, float state);
bool write_analog_reset(std::string name);
};
}
+182
View File
@@ -0,0 +1,182 @@
#include "buttons.h"
#include <functional>
#include "external/rapidjson/document.h"
#include "misc/eamuse.h"
#include "cfg/button.h"
#include "launcher/launcher.h"
#include "games/io.h"
#include "util/utils.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
Buttons::Buttons() : Module("buttons") {
functions["read"] = std::bind(&Buttons::read, this, _1, _2);
functions["write"] = std::bind(&Buttons::write, this, _1, _2);
functions["write_reset"] = std::bind(&Buttons::write_reset, this, _1, _2);
buttons = games::get_buttons(eamuse_get_game());
}
/**
* read()
*/
void Buttons::read(api::Request &req, Response &res) {
// check button cache
if (!this->buttons) {
return;
}
// add state for each button
for (auto &button : *this->buttons) {
Value state(kArrayType);
Value button_name(button.getName().c_str(), res.doc()->GetAllocator());
Value button_state(GameAPI::Buttons::getVelocity(RI_MGR, button));
Value button_enabled(button.override_enabled);
state.PushBack(button_name, res.doc()->GetAllocator());
state.PushBack(button_state, res.doc()->GetAllocator());
state.PushBack(button_enabled, res.doc()->GetAllocator());
res.add_data(state);
}
}
/**
* write([name: str, state: bool/float], ...)
*/
void Buttons::write(Request &req, Response &res) {
// check button cache
if (!buttons) {
return;
}
// loop parameters
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsArray()) {
error(res, "parameters must be arrays");
return;
}
if (param.Size() < 2) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
if (!param[1].IsBool() && !param[1].IsFloat() && !param[1].IsInt()) {
error_type(res, "state", "bool or float");
continue;
}
// get params
auto button_name = param[0].GetString();
auto button_state = param[1].IsBool() ? param[1].GetBool() : param[1].GetFloat() > 0;
auto button_velocity = param[1].IsFloat() ? param[1].GetFloat() : (button_state ? 1.f : 0.f);
// write button state
if (!this->write_button(button_name, button_velocity)) {
error_unknown(res, "button", button_name);
continue;
}
}
}
/**
* write_reset()
* write_reset([name: str], ...)
*/
void Buttons::write_reset(Request &req, Response &res) {
// check button cache
if (!this->buttons) {
return;
}
// get params
auto params = req.params.GetArray();
// write_reset()
if (params.Size() == 0) {
if (buttons != nullptr) {
for (auto &button : *this->buttons) {
button.override_enabled = false;
}
}
return;
}
// loop parameters
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsArray()) {
error(res, "parameters must be arrays");
return;
}
if (param.Size() < 1) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
// get params
auto button_name = param[0].GetString();
// write button state
if (!this->write_button_reset(button_name)) {
error_unknown(res, "button", button_name);
continue;
}
}
}
bool Buttons::write_button(std::string name, float state) {
// check button cache
if (!this->buttons) {
return false;
}
// find button
for (auto &button : *this->buttons) {
if (button.getName() == name) {
button.override_state = state > 0.f ?
GameAPI::Buttons::BUTTON_PRESSED : GameAPI::Buttons::BUTTON_NOT_PRESSED;
button.override_velocity = CLAMP(state, 0.f, 1.f);
button.override_enabled = true;
return true;
}
}
// unknown button
return false;
}
bool Buttons::write_button_reset(std::string name) {
// check button cache
if (!this->buttons) {
return false;
}
// find button
for (auto &button : *this->buttons) {
if (button.getName() == name) {
button.override_enabled = false;
return true;
}
}
// unknown button
return false;
}
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <vector>
#include "api/module.h"
#include "api/request.h"
#include "cfg/api.h"
namespace api::modules {
class Buttons : public Module {
public:
Buttons();
private:
// state
std::vector<Button> *buttons;
// function definitions
void read(Request &req, Response &res);
void write(Request &req, Response &res);
void write_reset(Request &req, Response &res);
// helper
bool write_button(std::string name, float state);
bool write_button_reset(std::string name);
};
}
+94
View File
@@ -0,0 +1,94 @@
#include "capture.h"
#include <functional>
#include "external/rapidjson/document.h"
#include "hooks/graphics/graphics.h"
#include "util/crypt.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
std::optional<uint32_t> CAPTURE_QUALITY;
std::optional<uint32_t> CAPTURE_DIVIDE;
static thread_local std::vector<uint8_t> CAPTURE_BUFFER;
Capture::Capture() : Module("capture") {
functions["get_screens"] = std::bind(&Capture::get_screens, this, _1, _2);
functions["get_jpg"] = std::bind(&Capture::get_jpg, this, _1, _2);
}
/**
* get_screens()
*/
void Capture::get_screens(Request &req, Response &res) {
// aquire screens
std::vector<int> screens;
graphics_screens_get(screens);
// add screens to response
for (auto &screen : screens) {
res.add_data(screen);
}
}
/**
* get_jpg([screen=0, quality=70, downscale=0, divide=1])
* screen: uint specifying the window
* quality: uint in range [0, 100]
* reduce: uint for dividing image size
*/
void Capture::get_jpg(Request &req, Response &res) {
CAPTURE_BUFFER.reserve(1024 * 128);
// settings
int screen = 0;
int quality = 70;
int divide = 1;
if (req.params.Size() > 0 && req.params[0].IsUint()) {
screen = req.params[0].GetUint();
}
if (CAPTURE_QUALITY.has_value()) {
quality = CAPTURE_QUALITY.value();
} else if (req.params.Size() > 1 && req.params[1].IsUint()) {
quality = req.params[1].GetUint();
}
if (CAPTURE_DIVIDE.has_value()) {
divide = CAPTURE_DIVIDE.value();
} else if (req.params.Size() > 2 && req.params[2].IsUint()) {
divide = req.params[2].GetUint();
}
// receive JPEG data
uint64_t timestamp = 0;
int width = 0;
int height = 0;
graphics_capture_trigger(screen);
bool success = graphics_capture_receive_jpeg(screen, [] (uint8_t byte) {
CAPTURE_BUFFER.push_back(byte);
}, true, quality, true, divide, &timestamp, &width, &height);
if (!success) {
return;
}
// encode to base64
auto encoded = crypt::base64_encode(
CAPTURE_BUFFER.data(),
CAPTURE_BUFFER.size());
// clear buffer
CAPTURE_BUFFER.clear();
// 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);
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <optional>
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
extern std::optional<uint32_t> CAPTURE_QUALITY;
extern std::optional<uint32_t> CAPTURE_DIVIDE;
class Capture : public Module {
public:
Capture();
private:
// function definitions
void get_screens(Request &req, Response &res);
void get_jpg(Request &req, Response &res);
};
}
+53
View File
@@ -0,0 +1,53 @@
#include "card.h"
#include <functional>
#include "external/rapidjson/document.h"
#include "util/logging.h"
#include "util/utils.h"
#include "misc/eamuse.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
Card::Card() : Module("card") {
functions["insert"] = std::bind(&Card::insert, this, _1, _2);
}
/**
* insert(index, card_id)
* index: uint in range [0, 1]
* card_id: hex string of length 16
*/
void Card::insert(Request &req, Response &res) {
// check params
if (req.params.Size() < 2)
return error_params_insufficient(res);
if (!req.params[0].IsUint())
return error_type(res, "index", "uint");
if (!req.params[1].IsString())
return error_type(res, "card_id", "hex string");
if (req.params[1].GetStringLength() != 16)
return error_size(res, "card_id", 16);
// get params
auto index = req.params[0].GetUint();
auto card_hex = req.params[1].GetString();
// convert to binary
uint8_t card_bin[8] {};
if (!hex2bin(card_hex, card_bin)) {
return error_type(res, "card_id", "hex string");
}
// log
if (LOGGING) {
log_info("api::card", "inserting card: {}", card_hex);
}
// insert card
eamuse_card_insert(index & 1, card_bin);
}
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Card : public Module {
public:
Card();
private:
// function definitions
void insert(Request &req, Response &res);
};
}
+79
View File
@@ -0,0 +1,79 @@
#include "coin.h"
#include <functional>
#include "external/rapidjson/document.h"
#include "misc/eamuse.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
Coin::Coin() : Module("coin") {
functions["get"] = std::bind(&Coin::get, this, _1, _2);
functions["set"] = std::bind(&Coin::set, this, _1, _2);
functions["insert"] = std::bind(&Coin::insert, this, _1, _2);
functions["blocker_get"] = std::bind(&Coin::blocker_get, this, _1, _2);
}
/**
* get()
*/
void Coin::get(api::Request &req, api::Response &res) {
// get coin stock
auto coin_stock = eamuse_coin_get_stock();
// insert value
Value coin_stock_val(coin_stock);
res.add_data(coin_stock_val);
}
/**
* set(amount: int)
*/
void Coin::set(api::Request &req, api::Response &res) {
// check params
if (req.params.Size() < 1)
return error_params_insufficient(res);
if (!req.params[0].IsInt())
return error_type(res, "amount", "int");
// set coin stock
eamuse_coin_set_stock(req.params[0].GetInt());
}
/**
* insert()
* insert(amount: int)
*/
void Coin::insert(api::Request &req, api::Response &res) {
// insert()
if (req.params.Size() == 0) {
eamuse_coin_add();
return;
}
// check params
if (!req.params[0].IsInt())
return error_type(res, "amount", "int");
// add to coin stock
eamuse_coin_set_stock(eamuse_coin_get_stock() + std::max(0, req.params[0].GetInt()));
}
/*
* blocker_get()
*/
void Coin::blocker_get(api::Request &req, api::Response &res) {
// get block status
auto block_status = eamuse_coin_get_block();
// insert value
Value block_val(block_status);
res.add_data(block_val);
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Coin : public Module {
public:
Coin();
private:
// function definitions
void get(Request &req, Response &res);
void set(Request &req, Response &res);
void insert(Request &req, Response &res);
void blocker_get(Request &req, Response &res);
};
}
+152
View File
@@ -0,0 +1,152 @@
#include "control.h"
#include <csignal>
#include <functional>
#include "external/rapidjson/document.h"
#include "launcher/shutdown.h"
#include "util/logging.h"
#include "util/crypt.h"
#include "util/utils.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
struct SignalMapping {
int signum;
const char* name;
};
static SignalMapping SIGNAL_MAPPINGS[] = {
{ SIGABRT, "SIGABRT" },
{ SIGFPE, "SIGFPE" },
{ SIGILL, "SIGILL" },
{ SIGINT, "SIGINT" },
{ SIGSEGV, "SIGSEGV" },
{ SIGTERM, "SIGTERM" },
};
Control::Control() : Module("control", true) {
functions["raise"] = std::bind(&Control::raise, this, _1, _2);
functions["exit"] = std::bind(&Control::exit, this, _1, _2);
functions["restart"] = std::bind(&Control::restart, this, _1, _2);
functions["session_refresh"] = std::bind(&Control::session_refresh, this, _1, _2);
functions["shutdown"] = std::bind(&Control::shutdown, this, _1, _2);
functions["reboot"] = std::bind(&Control::reboot, this, _1, _2);
}
/**
* raise(signal: str)
*/
void Control::raise(Request &req, Response &res) {
// check args
if (req.params.Size() < 1)
return error_params_insufficient(res);
if (!req.params[0].IsString())
return error_type(res, "signal", "string");
// get signal
auto signal_str = req.params[0].GetString();
int signal_val = -1;
for (auto mapping : SIGNAL_MAPPINGS) {
if (_stricmp(mapping.name, signal_str) == 0) {
signal_val = mapping.signum;
break;
}
}
// check if not found
if (signal_val < 0)
return error_unknown(res, "signal", signal_str);
// raise signal
if (::raise(signal_val))
return error(res, "Failed to raise signo " + to_string(signal_val));
}
/**
* exit()
* exit(code: int)
*/
void Control::exit(Request &req, Response &res) {
// exit()
if (req.params.Size() == 0) {
launcher::shutdown();
}
// check code
if (!req.params[0].IsInt())
return error_type(res, "code", "int");
// exit
launcher::shutdown(req.params[0].GetInt());
}
/**
* restart()
*/
void Control::restart(Request &req, Response &res) {
// restart launcher
launcher::restart();
}
/**
* session_refresh()
*/
void Control::session_refresh(Request &req, Response &res) {
// generate new password
uint8_t password_bin[128];
crypt::random_bytes(password_bin, std::size(password_bin));
std::string password = bin2hex(&password_bin[0], std::size(password_bin));
// add to response
Value password_val(password.c_str(), res.doc()->GetAllocator());
res.add_data(password_val);
// change password
res.password_change(password);
}
/**
* shutdown()
*/
void Control::shutdown(Request &req, Response &res) {
// acquire privileges
if (!acquire_shutdown_privs())
return error(res, "Unable to acquire shutdown privileges");
// exit windows
if (!ExitWindowsEx(EWX_SHUTDOWN | EWX_HYBRID_SHUTDOWN | EWX_FORCE,
SHTDN_REASON_MAJOR_APPLICATION |
SHTDN_REASON_MINOR_MAINTENANCE))
return error(res, "Unable to shutdown system");
// terminate this process
launcher::shutdown(0);
}
/**
* reboot()
*/
void Control::reboot(Request &req, Response &res) {
// acquire privileges
if (!acquire_shutdown_privs())
return error(res, "Unable to acquire shutdown privileges");
// exit windows
if (!ExitWindowsEx(EWX_REBOOT | EWX_FORCE,
SHTDN_REASON_MAJOR_APPLICATION |
SHTDN_REASON_MINOR_MAINTENANCE))
return error(res, "Unable to reboot system");
// terminate this process
launcher::shutdown(0);
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Control : public Module {
public:
Control();
private:
// function definitions
void raise(Request &req, Response &res);
void exit(Request &req, Response &res);
void restart(Request &req, Response &res);
void session_refresh(Request &req, Response &res);
void shutdown(Request &req, Response &res);
void reboot(Request &req, Response &res);
};
}
+54
View File
@@ -0,0 +1,54 @@
#include "ddr.h"
#include <functional>
#include "external/rapidjson/document.h"
#include "games/ddr/ddr.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
DDR::DDR() : Module("ddr") {
functions["tapeled_get"] = std::bind(&DDR::tapeled_get, this, _1, _2);
}
/**
* Allows fetching of the RGB LED strips that are gold cabinets, via SpiceAPI
*/
void DDR::tapeled_get(Request &req, Response &res) {
static const char* device_names[11] = {
"p1_foot_up",
"p1_foot_right",
"p1_foot_left",
"p1_foot_down",
"p2_foot_up",
"p2_foot_right",
"p2_foot_left",
"p2_foot_down",
"top_panel",
"monitor_left",
"monitor_right"
};
Value response_object(kObjectType);
// Iterate through each device and dump its lights data into the response
for (size_t device = 0; device < 11; device++) {
size_t num_leds = 25;
if (device > 7)
num_leds = 50;
Value light_state(kArrayType);
light_state.Reserve(num_leds * 3, res.doc()->GetAllocator());
for (size_t led = 0; led < num_leds; led++) {
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][0], res.doc()->GetAllocator());
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][1], res.doc()->GetAllocator());
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][2], res.doc()->GetAllocator());
}
response_object.AddMember(StringRef(device_names[device]), light_state, res.doc()->GetAllocator());
}
res.add_data(response_object);
}
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <vector>
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class DDR : public Module {
public:
DDR();
private:
// function definitions
void tapeled_get(Request &req, Response &res);
};
}
+91
View File
@@ -0,0 +1,91 @@
#include "drs.h"
#include <functional>
#include "external/rapidjson/document.h"
#include "games/drs/drs.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
DRS::DRS() : Module("drs") {
functions["tapeled_get"] = std::bind(&DRS::tapeled_get, this, _1, _2);
functions["touch_set"] = std::bind(&DRS::touch_set, this, _1, _2);
}
/**
* ticker_get()
*/
void DRS::tapeled_get(Request &req, Response &res) {
// copy data to array
Value tapeled(kArrayType);
const size_t tape_len = sizeof(games::drs::DRS_TAPELED);
const uint8_t *tape_raw = (uint8_t*) games::drs::DRS_TAPELED;
tapeled.Reserve(tape_len, res.doc()->GetAllocator());
for (size_t i = 0; i < tape_len; i++) {
tapeled.PushBack(tape_raw[i], res.doc()->GetAllocator());
}
// add to response
res.add_data(tapeled);
}
void DRS::touch_set(Request &req, Response &res) {
// get all touch points
games::drs::drs_touch_t touches[16];
size_t i = 0;
for (Value &param : req.params.GetArray()) {
// check params
if (param.Size() < 6) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsUint()) {
error_type(res, "type", "uint");
continue;
}
if (!param[1].IsUint()) {
error_type(res, "id", "uint");
continue;
}
if (!param[2].IsDouble()) {
error_type(res, "x", "double");
continue;
}
if (!param[3].IsDouble()) {
error_type(res, "y", "double");
continue;
}
if (!param[4].IsDouble()) {
error_type(res, "width", "double");
continue;
}
if (!param[5].IsDouble()) {
error_type(res, "height", "double");
continue;
}
// get params
auto touch_type = param[0].GetUint();
auto touch_id = param[1].GetUint();
auto touch_x = param[2].GetDouble();
auto touch_y = param[3].GetDouble();
auto width = param[4].GetDouble();
auto height = param[5].GetDouble();
touches[i].type = touch_type;
touches[i].id = touch_id;
touches[i].x = touch_x;
touches[i].y = touch_y;
touches[i].width = width;
touches[i].height = height;
i++;
}
// apply touch points
games::drs::fire_touches(touches, i);
}
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <vector>
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class DRS : public Module {
public:
DRS();
private:
// function definitions
void tapeled_get(Request &req, Response &res);
void touch_set(Request &req, Response &res);
};
}
+120
View File
@@ -0,0 +1,120 @@
#include "iidx.h"
#include <functional>
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
// settings
static const size_t TICKER_SIZE = 9;
IIDX::IIDX() : Module("iidx") {
functions["ticker_get"] = std::bind(&IIDX::ticker_get, this, _1, _2);
functions["ticker_set"] = std::bind(&IIDX::ticker_set, this, _1, _2);
functions["ticker_reset"] = std::bind(&IIDX::ticker_reset, this, _1, _2);
functions["tapeled_get"] = std::bind(&IIDX::tapeled_get, this, _1, _2);
for (auto &light : games::iidx::TAPELED_MAPPING) {
this->lights_by_names.emplace(light.lightName, light);
}
}
/**
* ticker_get()
*/
void IIDX::ticker_get(api::Request &req, Response &res) {
// get led ticker
games::iidx::IIDX_LED_TICKER_LOCK.lock();
Value led_ticker(StringRef(games::iidx::IIDXIO_LED_TICKER, TICKER_SIZE), res.doc()->GetAllocator());
games::iidx::IIDX_LED_TICKER_LOCK.unlock();
// add to response
res.add_data(led_ticker);
}
/**
* ticker_set(text: str)
*/
void IIDX::ticker_set(api::Request &req, api::Response &res) {
// check param
if (req.params.Size() < 1)
return error_params_insufficient(res);
if (!req.params[0].IsString())
return error_type(res, "text", "str");
// get param
auto text = req.params[0].GetString();
auto text_len = req.params[0].GetStringLength();
// lock
std::lock_guard<std::mutex> ticker_lock(games::iidx::IIDX_LED_TICKER_LOCK);
// set to read only
games::iidx::IIDXIO_LED_TICKER_READONLY = true;
// set led ticker
memset(games::iidx::IIDXIO_LED_TICKER, ' ', TICKER_SIZE);
for (size_t i = 0; i < TICKER_SIZE && i < text_len; i++) {
games::iidx::IIDXIO_LED_TICKER[i] = text[i];
}
}
void IIDX::ticker_reset(api::Request &req, api::Response &res) {
// lock
std::lock_guard<std::mutex> ticker_lock(games::iidx::IIDX_LED_TICKER_LOCK);
// disable read only
games::iidx::IIDXIO_LED_TICKER_READONLY = false;
}
/**
* tapeled_get()
* tapeled_get(name: str, ...)
*/
void IIDX::tapeled_get(Request &req, Response &res) {
Value response_object(kObjectType);
// all tape leds
if (req.params.Size() == 0) {
// Iterate through each device and dump its lights data into the response
for (const auto &mapping : games::iidx::TAPELED_MAPPING) {
copy_tapeled_data(res, response_object, mapping);
}
} else {
// specified light names
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsString()) {
error_type(res, "name", "string");
return;
}
const auto name = param.GetString();
if (const auto &it = lights_by_names.find(name); it != lights_by_names.end()) {
const auto mapping = it->second.get();
copy_tapeled_data(res, response_object, mapping);
}
}
}
res.add_data(response_object);
}
void IIDX::copy_tapeled_data(Response &res, Value &response_object, const tapeledutils::tape_led &mapping) {
// Create an array for the light state
Value light_state(kArrayType);
light_state.Reserve(mapping.data.capacity() * 3, res.doc()->GetAllocator());
for (const auto [r, g, b] : mapping.data) {
light_state.PushBack(r, res.doc()->GetAllocator());
light_state.PushBack(g, res.doc()->GetAllocator());
light_state.PushBack(b, res.doc()->GetAllocator());
}
// Can't use StringRef here, turns some strings partially into null bytes for some reason
Value light_name(mapping.lightName.c_str(), res.doc()->GetAllocator());
response_object.AddMember(light_name, light_state, res.doc()->GetAllocator());
}
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
#include "games/iidx/iidx.h"
namespace api::modules {
class IIDX : public Module {
public:
IIDX();
private:
// state
robin_hood::unordered_map<std::string, std::reference_wrapper<tapeledutils::tape_led>> lights_by_names;
// function definitions
void ticker_get(Request &req, Response &res);
void ticker_set(Request &req, Response &res);
void ticker_reset(Request &req, Response &res);
void tapeled_get(Request &req, Response &res);
// helper
void copy_tapeled_data(Response &res, rapidjson::Value &response_object, const tapeledutils::tape_led &mapping);
};
}
+98
View File
@@ -0,0 +1,98 @@
#include "info.h"
#include <functional>
#include <iomanip>
#include "external/rapidjson/document.h"
#include "avs/game.h"
#include "avs/ea3.h"
#include "util/logging.h"
#include "util/utils.h"
#include "util/memutils.h"
#include "build/defs.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
Info::Info() : Module("info") {
functions["avs"] = std::bind(&Info::avs, this, _1, _2);
functions["launcher"] = std::bind(&Info::launcher, this, _1, _2);
functions["memory"] = std::bind(&Info::memory, this, _1, _2);
}
/**
* avs()
*/
void Info::avs(Request &req, Response &res) {
// get allocator
auto &alloc = res.doc()->GetAllocator();
// build info object
Value info(kObjectType);
info.AddMember("model", StringRef(avs::game::MODEL, 3), alloc);
info.AddMember("dest", StringRef(avs::game::DEST, 1), alloc);
info.AddMember("spec", StringRef(avs::game::SPEC, 1), alloc);
info.AddMember("rev", StringRef(avs::game::REV, 1), alloc);
info.AddMember("ext", StringRef(avs::game::EXT, 10), alloc);
info.AddMember("services", StringRef(avs::ea3::EA3_BOOT_URL.c_str()), alloc);
// add info object
res.add_data(info);
}
/**
* launcher()
*/
void Info::launcher(Request &req, Response &res) {
// get allocator
auto &alloc = res.doc()->GetAllocator();
// build args
Value args(kArrayType);
for (int count = 0; count < LAUNCHER_ARGC; count++) {
auto arg = LAUNCHER_ARGV[count];
args.PushBack(StringRef(arg), alloc);
}
// get system time
auto t_now = std::time(nullptr);
auto tm_now = *std::gmtime(&t_now);
auto tm_str = to_string(std::put_time(&tm_now, "%Y-%m-%dT%H:%M:%SZ"));
Value system_time(tm_str.c_str(), alloc);
// build info object
Value info(kObjectType);
info.AddMember("version", StringRef(VERSION_STRING), alloc);
info.AddMember("compile_date", StringRef(__DATE__), alloc);
info.AddMember("compile_time", StringRef(__TIME__), alloc);
info.AddMember("system_time", system_time, alloc);
info.AddMember("args", args, alloc);
// add info object
res.add_data(info);
}
/**
* memory()
*/
void Info::memory(Request &req, Response &res) {
// get allocator
auto &alloc = res.doc()->GetAllocator();
// build info object
Value info(kObjectType);
info.AddMember("mem_total", memutils::mem_total(), alloc);
info.AddMember("mem_total_used", memutils::mem_total_used(), alloc);
info.AddMember("mem_used", memutils::mem_used(), alloc);
info.AddMember("vmem_total", memutils::vmem_total(), alloc);
info.AddMember("vmem_total_used", memutils::vmem_total_used(), alloc);
info.AddMember("vmem_used", memutils::vmem_used(), alloc);
// add info object
res.add_data(info);
}
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Info : public Module {
public:
Info();
private:
// function definitions
void avs(Request &req, Response &res);
void launcher(Request &req, Response &res);
void memory(Request &req, Response &res);
};
}
+176
View File
@@ -0,0 +1,176 @@
#include "keypads.h"
#include <functional>
#include <windows.h>
#include "avs/game.h"
#include "external/rapidjson/document.h"
#include "misc/eamuse.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
struct KeypadMapping {
char character;
uint16_t state;
};
static KeypadMapping KEYPAD_MAPPINGS[] = {
{ '0', 1 << EAM_IO_KEYPAD_0 },
{ '1', 1 << EAM_IO_KEYPAD_1 },
{ '2', 1 << EAM_IO_KEYPAD_2 },
{ '3', 1 << EAM_IO_KEYPAD_3 },
{ '4', 1 << EAM_IO_KEYPAD_4 },
{ '5', 1 << EAM_IO_KEYPAD_5 },
{ '6', 1 << EAM_IO_KEYPAD_6 },
{ '7', 1 << EAM_IO_KEYPAD_7 },
{ '8', 1 << EAM_IO_KEYPAD_8 },
{ '9', 1 << EAM_IO_KEYPAD_9 },
{ 'A', 1 << EAM_IO_KEYPAD_00 },
{ 'D', 1 << EAM_IO_KEYPAD_DECIMAL },
};
Keypads::Keypads() : Module("keypads") {
functions["write"] = std::bind(&Keypads::write, this, _1, _2);
functions["set"] = std::bind(&Keypads::set, this, _1, _2);
functions["get"] = std::bind(&Keypads::get, this, _1, _2);
}
/**
* write(keypad: uint, input: str)
*/
void Keypads::write(Request &req, Response &res) {
// check params
if (req.params.Size() < 2) {
return error_params_insufficient(res);
}
if (!req.params[0].IsUint()) {
return error_type(res, "keypad", "uint");
}
if (!req.params[1].IsString()) {
return error_type(res, "input", "string");
}
// get params
auto keypad = req.params[0].GetUint();
auto input = std::string(req.params[1].GetString());
// process all chars
for (auto c : input) {
uint16_t state = 0;
// find mapping
bool mapping_found = false;
for (auto &mapping : KEYPAD_MAPPINGS) {
if (_strnicmp(&mapping.character, &c, 1) == 0) {
state |= mapping.state;
mapping_found = true;
break;
}
}
// check for error
if (!mapping_found) {
return error_unknown(res, "char", std::string("") + c);
}
/*
* Write input to keypad.
* We try to make sure it was accepted by waiting a bit more than two frames.
*/
DWORD sleep_time = 70;
if (avs::game::is_model("MDX")) {
// cuz fuck DDR
sleep_time = 150;
}
// set
eamuse_set_keypad_overrides(keypad, state);
Sleep(sleep_time);
// unset
eamuse_set_keypad_overrides(keypad, 0);
Sleep(sleep_time);
}
}
/**
* set(keypad: uint, key: char, ...)
*/
void Keypads::set(Request &req, Response &res) {
// check keypad
if (req.params.Size() < 1) {
return error_params_insufficient(res);
}
if (!req.params[0].IsUint()) {
return error_type(res, "keypad", "uint");
}
auto keypad = req.params[0].GetUint();
// iterate params
uint16_t state = 0;
auto params = req.params.GetArray();
for (size_t i = 1; i < params.Size(); i++) {
auto &param = params[i];
// check key
if (!param.IsString()) {
error_type(res, "key", "char");
}
if (param.GetStringLength() < 1) {
error_size(res, "key", 1);
}
// find mapping
auto key = param.GetString();
bool mapping_found = false;
for (auto &mapping : KEYPAD_MAPPINGS) {
if (_strnicmp(&mapping.character, key, 1) == 0) {
state |= mapping.state;
mapping_found = true;
break;
}
}
// check for error
if (!mapping_found) {
return error_unknown(res, "key", key);
}
}
// set keypad state
eamuse_set_keypad_overrides(keypad, state);
}
/**
* get(keypad: uint)
*/
void Keypads::get(Request &req, Response &res) {
// check keypad
if (req.params.Size() < 1) {
return error_params_insufficient(res);
}
if (!req.params[0].IsUint()) {
return error_type(res, "keypad", "uint");
}
auto keypad = req.params[0].GetUint();
// get keypad state
auto state = eamuse_get_keypad_state(keypad);
// add keys to response
for (auto &mapping : KEYPAD_MAPPINGS) {
if (state & mapping.state) {
Value val(&mapping.character, 1);
res.add_data(val);
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Keypads : public Module {
public:
Keypads();
private:
// function definitions
void write(Request &req, Response &res);
void set(Request &req, Response &res);
void get(Request &req, Response &res);
};
}
+36
View File
@@ -0,0 +1,36 @@
#include "lcd.h"
#include "external/rapidjson/document.h"
#include "games/shared/lcdhandle.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
LCD::LCD() : Module("lcd") {
functions["info"] = std::bind(&LCD::info, this, _1, _2);
}
/*
* info()
*/
void LCD::info(Request &req, Response &res) {
// get allocator
auto &alloc = res.doc()->GetAllocator();
// build info object
Value info(kObjectType);
info.AddMember("enabled", games::shared::LCD_ENABLED, alloc);
info.AddMember("csm", StringRef(games::shared::LCD_CSM.c_str()), alloc);
info.AddMember("bri", games::shared::LCD_BRI, alloc);
info.AddMember("con", games::shared::LCD_CON, alloc);
info.AddMember("bl", games::shared::LCD_BL, alloc);
info.AddMember("red", games::shared::LCD_RED, alloc);
info.AddMember("green", games::shared::LCD_GREEN, alloc);
info.AddMember("blue", games::shared::LCD_BLUE, alloc);
// add info object
res.add_data(info);
}
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class LCD : public Module {
public:
LCD();
private:
// function definitions
void info(Request &req, Response &res);
};
}
+219
View File
@@ -0,0 +1,219 @@
#include "lights.h"
#include <functional>
#include <cfg/configurator.h>
#include "external/rapidjson/document.h"
#include "misc/eamuse.h"
#include "cfg/light.h"
#include "launcher/launcher.h"
#include "games/io.h"
#include "util/utils.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
Lights::Lights() : Module("lights") {
functions["read"] = std::bind(&Lights::read, this, _1, _2);
functions["write"] = std::bind(&Lights::write, this, _1, _2);
functions["write_reset"] = std::bind(&Lights::write_reset, this, _1, _2);
this->lights = games::get_lights(eamuse_get_game());
for (auto &light : *this->lights) {
this->lights_by_names.emplace(light.getName(), light);
}
}
/**
* read()
* read(name: str, ...)
*/
void Lights::read(api::Request &req, Response &res) {
// check light cache
if (!this->lights) {
return;
}
// all lights for this game
if (req.params.Size() == 0) {
// add state for each light
for (auto &light : *this->lights) {
get_light(light, res);
}
return;
}
// specified light names
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsString()) {
error_type(res, "name", "string");
return;
}
const auto name = param.GetString();
if (this->lights_by_names.contains(name)) {
get_light(this->lights_by_names.at(name).get(), res);
}
}
}
void Lights::get_light(Light &light, Response &res) {
Value state(kArrayType);
Value light_name(light.getName().c_str(), res.doc()->GetAllocator());
Value light_state(GameAPI::Lights::readLight(RI_MGR, light));
Value light_enabled(light.override_enabled);
state.PushBack(light_name, res.doc()->GetAllocator());
state.PushBack(light_state, res.doc()->GetAllocator());
state.PushBack(light_enabled, res.doc()->GetAllocator());
res.add_data(state);
}
/**
* write([name: str, state: float], ...)
*/
void Lights::write(Request &req, Response &res) {
// check light cache
if (!this->lights) {
return;
}
// loop parameters
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsArray()) {
error(res, "parameters must be arrays");
return;
}
if (param.Size() < 2) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
if (!param[1].IsFloat() && !param[1].IsInt()) {
error_type(res, "state", "float");
continue;
}
// get params
auto light_name = param[0].GetString();
auto light_state = param[1].GetFloat();
// write light state
if (!this->write_light(light_name, light_state)) {
error_unknown(res, "light", light_name);
continue;
}
}
}
/**
* write_reset()
* write_reset(name: str, ...)
* write_reset([name: str], ...)
*/
void Lights::write_reset(Request &req, Response &res) {
// check light cache
if (!this->lights) {
return;
}
// get params
auto params = req.params.GetArray();
// write_reset()
if (params.Size() == 0) {
if (lights != nullptr) {
for (auto &light : *this->lights) {
if (light.override_enabled) {
if (cfg::CONFIGURATOR_STANDALONE) {
GameAPI::Lights::writeLight(RI_MGR, light, light.last_state);
}
light.override_enabled = false;
}
}
}
return;
}
// loop parameters
for (Value &param : req.params.GetArray()) {
const char* light_name = nullptr;
// check params
if (param.IsArray()) {
if (param.Size() < 1) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
// get params
light_name = param[0].GetString();
} else if (param.IsString()) {
light_name = param.GetString();
} else {
error(res, "parameters must be arrays or strings");
}
// write analog state
if (light_name && !this->write_light_reset(light_name)) {
error_unknown(res, "analog", light_name);
continue;
}
}
}
bool Lights::write_light(std::string name, float state) {
// check light cache
if (!this->lights) {
return false;
}
// find light
if (this->lights_by_names.contains(name)) {
auto &light = this->lights_by_names.at(name).get();
light.override_state = CLAMP(state, 0.f, 1.f);
light.override_enabled = true;
if (cfg::CONFIGURATOR_STANDALONE) {
GameAPI::Lights::writeLight(RI_MGR, light, state);
}
return true;
} else {
// unknown light
return false;
}
}
bool Lights::write_light_reset(std::string name) {
// check light cache
if (!this->lights) {
return false;
}
// find light
if (this->lights_by_names.contains(name)) {
auto &light = this->lights_by_names.at(name).get();
light.override_enabled = false;
return true;
} else {
// unknown light
return false;
}
}
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include <vector>
#include <external/robin_hood.h>
#include "api/module.h"
#include "api/request.h"
#include "cfg/api.h"
namespace api::modules {
class Lights : public Module {
public:
Lights();
private:
// state
std::vector<Light> *lights;
robin_hood::unordered_map<std::string, std::reference_wrapper<Light>> lights_by_names;
// function definitions
void read(Request &req, Response &res);
void write(Request &req, Response &res);
void write_reset(Request &req, Response &res);
// helper
void get_light(Light &light, Response &res);
bool write_light(std::string name, float state);
bool write_light_reset(std::string name);
};
}
+233
View File
@@ -0,0 +1,233 @@
#include "memory.h"
#include <functional>
#include <mutex>
#include "external/rapidjson/document.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/utils.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
// global lock to prevent simultaneous access to memory
static std::mutex MEMORY_LOCK;
Memory::Memory() : Module("memory", true) {
functions["write"] = std::bind(&Memory::write, this, _1, _2);
functions["read"] = std::bind(&Memory::read, this, _1, _2);
functions["signature"] = std::bind(&Memory::signature, this, _1, _2);
}
/**
* write(dll_name: str, data: hex, offset: uint)
*/
void Memory::write(Request &req, Response &res) {
std::lock_guard<std::mutex> lock(MEMORY_LOCK);
// check params
if (req.params.Size() < 3) {
return error_params_insufficient(res);
}
if (!req.params[0].IsString()) {
return error_type(res, "dll_name", "str");
}
if (!req.params[1].IsString() || (req.params[1].GetStringLength() & 1)) {
return error_type(res, "data", "hex string");
}
if (!req.params[2].IsUint()) {
return error_type(res, "offset", "uint");
}
// get params
auto dll_name = req.params[0].GetString();
auto dll_path = MODULE_PATH / dll_name;
auto data = req.params[1].GetString();
intptr_t offset = req.params[2].GetUint();
// convert data to bin
size_t data_bin_size = strlen(data) / 2;
auto data_bin = std::make_unique<uint8_t[]>(data_bin_size);
hex2bin(data, data_bin.get());
// check if file exists in modules
if (!fileutils::file_exists(dll_path)) {
return error(res, "Couldn't find " + dll_path.string());
}
// get module
auto module = libutils::try_module(dll_name);
if (!module) {
return error(res, "Couldn't find module.");
}
// convert offset to RVA
offset = libutils::offset2rva(dll_path, offset);
if (offset == ~0) {
return error(res, "Couldn't convert offset to RVA.");
}
// get module information
MODULEINFO module_info {};
if (!GetModuleInformation(GetCurrentProcess(), module, &module_info, sizeof(MODULEINFO))) {
return error(res, "Couldn't get module information.");
}
// check bounds
if (offset + data_bin_size >= (size_t) module_info.lpBaseOfDll + module_info.SizeOfImage) {
return error(res, "Data out of bounds.");
}
auto data_pos = reinterpret_cast<uint8_t *>(module_info.lpBaseOfDll) + offset;
// replace data
memutils::VProtectGuard guard(data_pos, data_bin_size);
memcpy(data_pos, data_bin.get(), data_bin_size);
}
/**
* read(dll_name: str, offset: uint, size: uint)
*/
void Memory::read(Request &req, Response &res) {
std::lock_guard<std::mutex> lock(MEMORY_LOCK);
// check params
if (req.params.Size() < 3) {
return error_params_insufficient(res);
}
if (!req.params[0].IsString()) {
return error_type(res, "dll_name", "str");
}
if (!req.params[1].IsUint()) {
return error_type(res, "offset", "uint");
}
if (!req.params[2].IsUint()) {
return error_type(res, "size", "uint");
}
// get params
auto dll_name = req.params[0].GetString();
auto dll_path = MODULE_PATH / dll_name;
intptr_t offset = req.params[1].GetUint();
auto size = req.params[2].GetUint();
// check if file exists in modules
if (!fileutils::file_exists(dll_path)) {
return error(res, "Couldn't find " + dll_path.string());
}
// get module
auto module = libutils::try_module(dll_name);
if (!module) {
return error(res, "Couldn't find module.");
}
// convert offset to RVA
offset = libutils::offset2rva(dll_path, offset);
if (offset == ~0) {
return error(res, "Couldn't convert offset to RVA.");
}
// get module information
MODULEINFO module_info {};
if (!GetModuleInformation(GetCurrentProcess(), module, &module_info, sizeof(MODULEINFO))) {
return error(res, "Couldn't get module information.");
}
// check bounds
auto max = offset + size;
if ((size_t) max >= (size_t) module_info.lpBaseOfDll + module_info.SizeOfImage) {
return error(res, "Data out of bounds.");
}
// read memory to hex (without virtual protect)
std::string hex = bin2hex((uint8_t*) module_info.lpBaseOfDll + offset, size);
Value hex_val(hex.c_str(), res.doc()->GetAllocator());
res.add_data(hex_val);
}
/**
* signature(
* dll_name: str,
* signature: hex,
* replacement: hex,
* offset: uint,
* usage: uint)
*
* Both signature and replacement will ignore bytes specified as "??" in the hex string.
* The offset specifies the offset between the found signature and the position to write the replacement to.
* The resulting integer is the file offset where the replacement was written to.
*/
void Memory::signature(Request &req, Response &res) {
std::lock_guard<std::mutex> lock(MEMORY_LOCK);
// check params
if (req.params.Size() < 5) {
return error_params_insufficient(res);
}
if (!req.params[0].IsString()) {
return error_type(res, "dll_name", "string");
}
if (!req.params[1].IsString() || (req.params[1].GetStringLength() & 1)) {
return error_type(res, "signature", "hex string");
}
if (!req.params[2].IsString() || (req.params[2].GetStringLength() & 1)) {
return error_type(res, "replacement", "hex string");
}
if (!req.params[3].IsUint()) {
return error_type(res, "offset", "uint");
}
if (!req.params[4].IsUint()) {
return error_type(res, "usage", "uint");
}
// get params
auto dll_name = req.params[0].GetString();
auto dll_path = MODULE_PATH / dll_name;
auto signature = req.params[1].GetString();
auto replacement = req.params[2].GetString();
auto offset = req.params[3].GetUint();
auto usage = req.params[4].GetUint();
// check if file exists in modules
if (!fileutils::file_exists(dll_path)) {
return error(res, "Couldn't find " + dll_path.string());
}
// get module
auto module = libutils::try_module(dll_name);
if (!module) {
return error(res, "Couldn't find module.");
}
// execute
auto result = replace_pattern(
module,
signature,
replacement,
offset,
usage
);
// check result
if (!result) {
return error(res, std::string("Pattern not found in memory of ") + dll_name);
}
// convert to offset
auto rva = result - reinterpret_cast<intptr_t>(module);
result = libutils::rva2offset(dll_path, rva);
if (result == -1) {
return error(res, "Couldn't convert RVA to file offset.");
}
// add result
Value result_val(result);
res.add_data(result_val);
}
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Memory : public Module {
public:
Memory();
private:
// function definitions
void write(Request &req, Response &res);
void read(Request &req, Response &res);
void signature(Request &req, Response &res);
};
}
+53
View File
@@ -0,0 +1,53 @@
#include "resize.h"
#include "external/rapidjson/document.h"
#include "cfg/screen_resize.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
static thread_local std::vector<uint8_t> CAPTURE_BUFFER;
Resize::Resize() : Module("resize") {
functions["image_resize_enable"] = std::bind(&Resize::image_resize_enable, this, _1, _2);
functions["image_resize_set_scene"] = std::bind(&Resize::image_resize_set_scene, this, _1, _2);
}
/**
* image_resize_enable(enable: bool)
*/
void Resize::image_resize_enable(Request &req, Response &res) {
if (req.params.Size() < 1) {
return error_params_insufficient(res);
}
if (!req.params[0].IsBool()) {
return error_type(res, "enable", "bool");
}
cfg::SCREENRESIZE->enable_screen_resize = req.params[0].GetBool();
}
/**
* image_resize_set_scene(scene: int)
*/
void Resize::image_resize_set_scene(Request &req, Response &res) {
if (req.params.Size() < 1) {
return error_params_insufficient(res);
}
if (!req.params[0].IsInt()) {
return error_type(res, "scene", "int");
}
const auto scene = req.params[0].GetInt();
if (scene < 0 || (int)std::size(cfg::SCREENRESIZE->scene_settings) < scene) {
return error(res, "invalid scene number");
}
if (scene == 0) {
cfg::SCREENRESIZE->enable_screen_resize = false;
} else {
cfg::SCREENRESIZE->enable_screen_resize = true;
cfg::SCREENRESIZE->screen_resize_current_scene = scene - 1;
}
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Resize : public Module {
public:
Resize();
private:
// function definitions
void image_resize_enable(Request &req, Response &res);
void image_resize_set_scene(Request &req, Response &res);
};
}
+145
View File
@@ -0,0 +1,145 @@
#include "touch.h"
#include <functional>
#include "external/rapidjson/document.h"
#include "avs/game.h"
#include "hooks/graphics/graphics.h"
#include "misc/eamuse.h"
#include "launcher/launcher.h"
#include "touch/touch.h"
#include "util/utils.h"
#include "games/iidx/iidx.h"
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
Touch::Touch() : Module("touch") {
is_sdvx = avs::game::is_model("KFC");
is_tdj_fhd = (avs::game::is_model("LDJ") && games::iidx::is_tdj_fhd());
// special case: when windowed subscreen is in use, use the original coords
if (GRAPHICS_IIDX_WSUB) {
is_tdj_fhd = false;
}
functions["read"] = std::bind(&Touch::read, this, _1, _2);
functions["write"] = std::bind(&Touch::write, this, _1, _2);
functions["write_reset"] = std::bind(&Touch::write_reset, this, _1, _2);
}
/**
* read()
*/
void Touch::read(api::Request &req, Response &res) {
// get touch points
std::vector<TouchPoint> touch_points;
touch_get_points(touch_points);
// add state for each touch point
for (auto &touch : touch_points) {
Value state(kArrayType);
Value id((uint64_t) touch.id);
Value x((int64_t) touch.x);
Value y((int64_t) touch.y);
Value mouse((bool) touch.mouse);
state.PushBack(id, res.doc()->GetAllocator());
state.PushBack(x, res.doc()->GetAllocator());
state.PushBack(y, res.doc()->GetAllocator());
state.PushBack(mouse, res.doc()->GetAllocator());
res.add_data(state);
}
}
/**
* write([id: uint, x: int, y: int], ...)
*/
void Touch::write(Request &req, Response &res) {
// get all touch points
std::vector<TouchPoint> touch_points;
for (Value &param : req.params.GetArray()) {
// check params
if (param.Size() < 3) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsUint()) {
error_type(res, "id", "uint");
continue;
}
if (!param[1].IsInt()) {
error_type(res, "x", "int");
continue;
}
if (!param[2].IsInt()) {
error_type(res, "y", "int");
continue;
}
// TODO: optional mouse parameter
// get params
auto touch_id = param[0].GetUint();
auto touch_x = param[1].GetInt();
auto touch_y = param[2].GetInt();
apply_touch_errata(touch_x, touch_y);
touch_points.emplace_back(TouchPoint {
.id = touch_id,
.x = touch_x,
.y = touch_y,
.mouse = false,
});
}
// apply touch points
touch_write_points(&touch_points);
}
/**
* write_reset(id: uint, ...)
*/
void Touch::write_reset(Request &req, Response &res) {
// get all IDs
std::vector<DWORD> touch_point_ids;
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsUint()) {
error_type(res, "id", "uint");
continue;
}
// remember touch ID
auto touch_id = param.GetUint();
touch_point_ids.emplace_back(touch_id);
}
// remove all IDs
touch_remove_points(&touch_point_ids);
}
void Touch::apply_touch_errata(int &x, int &y) {
int x_raw = x;
int y_raw = y;
if (is_tdj_fhd) {
// deal with TDJ FHD resolution mismatch (upgrade 720p to 1080p)
// we don't know what screen is being shown on the companion and the API doesn't specify
// the target of the touch events so just assume it's the sub screen
x = x_raw * 1920 / 1280;
y = y_raw * 1080 / 720;
} else if (is_sdvx) {
// for exceed gear, they are both 1080p screens, but need to apply transformation
x = 1080 - y_raw;
y = x_raw;
}
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <vector>
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Touch : public Module {
public:
Touch();
private:
bool is_sdvx;
bool is_tdj_fhd;
// function definitions
void read(Request &req, Response &res);
void write(Request &req, Response &res);
void write_reset(Request &req, Response &res);
void apply_touch_errata(int &x, int &y);
};
}

Some files were not shown because too many files have changed in this diff Show More