Initial re-upload of spice2x-24-08-24

This commit is contained in:
Hazel 2024-08-28 11:10:34 -04:00
commit caa9e02285
1181 changed files with 380065 additions and 0 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
bin/**
dist/**
docker/**
cmake-build*

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
.vscode/*
# JetBrains IDEs
.idea
.idea_modules
*.iws
# CMake
cmake-build*
# user config
build_all.local.sh
build_docker.local.sh
# output from build script
bin/*
dist/*
external/cv2pdb/*

768
CMakeLists.txt Normal file
View File

@ -0,0 +1,768 @@
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>")
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")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000")
# 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")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections,--disable-dynamicbase,--image-base=0x400000")
# 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")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--disable-dynamicbase,--image-base=0x400000")
# 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
OPENVR_BUILD_STATIC
)
# 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})
# 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_compile_definitions(IMGUI_DISABLE_DEMO_WINDOWS)
add_compile_definitions(IMGUI_DISABLE_DEBUG_TOOLS)
add_subdirectory(external/imgui EXCLUDE_FROM_ALL)
add_subdirectory(external/minhook EXCLUDE_FROM_ALL)
add_subdirectory(external/openvr EXCLUDE_FROM_ALL)
add_subdirectory(external/lua EXCLUDE_FROM_ALL)
add_subdirectory(external/cpu_features EXCLUDE_FROM_ALL)
add_subdirectory(external/stepmaniax-sdk 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)
# 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
# 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 layeredfs
external/layeredfs/config.cpp
external/layeredfs/hook.cpp
external/layeredfs/modpath_handler.cpp
external/layeredfs/texture_packer.cpp
external/layeredfs/utils.cpp
external/layeredfs/3rd_party/GuillotineBinPack.cpp
external/layeredfs/3rd_party/lodepng.cpp
external/layeredfs/3rd_party/Rect.cpp
external/layeredfs/3rd_party/stb_dxt.cpp
# external cardio
external/cardio/cardio_hid.cpp
external/cardio/cardio_window.cpp
external/cardio/cardio_runner.cpp
# external misc
external/scard/scard.cpp
external/stackwalker/stackwalker.cpp
external/tinyxml2/tinyxml2.cpp
external/http-parser/http_parser.c
external/usbhidusage/usb-hid-usage.c
external/toojpeg/toojpeg.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/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/gitadora/gitadora.cpp
games/gitadora/io.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/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
# hooks
hooks/audio/audio.cpp
hooks/audio/buffer.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_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/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/sciunit.cpp
misc/sde.cpp
misc/vrutil.cpp
misc/wintouchemu.cpp
# nvapi
nvapi/nvapi.cpp
# overlay
overlay/overlay.cpp
overlay/window.cpp
overlay/imgui/extensions.cpp
overlay/imgui/impl_dx9.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/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/kfcontrol.cpp
overlay/windows/log.cpp
overlay/windows/midi.cpp
overlay/windows/patch_manager.cpp
overlay/windows/vr.cpp
overlay/windows/wnd_manager.cpp
# rawinput
rawinput/rawinput.cpp
rawinput/sextet.cpp
rawinput/piuio.cpp
rawinput/touch.cpp
rawinput/hotplug.cpp
rawinput/smxstage.cpp
rawinput/smxstage.h
# reader
reader/reader.cpp
reader/message.cpp
reader/structuredmessage.cpp
reader/crypt.cpp
# script
script/api/analogs.cpp
script/api/buttons.cpp
script/api/capture.cpp
script/api/card.cpp
script/api/coin.cpp
script/api/control.cpp
script/api/drs.cpp
script/api/iidx.cpp
script/api/info.cpp
script/api/keypads.cpp
script/api/lcd.cpp
script/api/lights.cpp
script/api/memory.cpp
script/api/touch.cpp
script/instance.cpp
script/lib.cpp
script/manager.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/utils.cpp
util/memutils.cpp
util/rc4.cpp
util/crypt.cpp
util/time.cpp
util/cpuutils.cpp
util/netutils.cpp
util/lz77.cpp
util/tapeled.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)
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 winscard winhttp
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook openvr_api lua_static imm32 dwmapi CpuFeatures::cpu_features smx)
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()
# spice64.exe
#############
set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg/Win32D.rc)
add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice64
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard winhttp mfplat mf mfreadwrite mfuuid strmiids dxva2
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook openvr_api64 lua_static imm32 dwmapi CpuFeatures::cpu_features smx)
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()
# 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 winscard winhttp strmiids
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook openvr_api lua_static imm32 dwmapi CpuFeatures::cpu_features smx)
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()
# 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-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-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-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-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()
# output directories
####################
# output config
set_target_properties(spicetools_cfg
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
# output 32bit
set_target_properties(spicetools_spice spicetools_stubs_kbt spicetools_stubs_kld
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_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")

6
Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM spicetools/deps
WORKDIR /src
RUN chown user:user /src
USER user
COPY --chown=user:user . /src
CMD ./build_all.sh

674
LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

261
README.md Normal file
View File

@ -0,0 +1,261 @@
SpiceTools
==========
This is a loader for various arcade games developed by 573.
The project is using CMake as it's build system, with a custom build
script for making packages ready for distribution and to keep it easy
for people not knowing how to use CMake.
## Building/Distribution
We're currently using Arch Linux for building the binaries.
You'll need:
- MinGW-64 packages (can be found in the AUR)
- bash
- git
- zip
- upx (optional)
For any other GNU/Linux distributions (or Windows lol), you're on your
own.
To build the project, run:
$ ./build_all.sh
## Build Configuration
You can tweak some settings at the beginning of the build script. You
might want to modify the paths of the toolchains if yours differ. It's
also possible to disable UPX compression and source distribution for
example.
If you're not using an IDE and want to run the build script from command
line manually each time you make a change, you can set CLEAN_BUILD to 0
so it will only compile what's needed. When modifying the resources you
should build from scratch, since CMake isn't able to track changes of
those files (e.g. changelog.txt, licenses.txt).
You can put your custom build script under "build_all.local.sh" if you
don't want git to track the changes you made to the build settings.
## Debug Build
To get proper stackstraces, you need to build with the DEBUG setting
enabled. This will set the CMake build type to debug and the script
will try to make use of cv2pdb.
Check external/cv2pdb/README.md for details on how to set it up.
It is required to use cv2pdb to generate PDB output, because at time of
writing, MinGW still didn't support Microsoft's proprietary file format.
## API
SpiceTools is providing a TCP/JSON based API. You can enable it with the
`-api [PORT]` option. It's recommended to also set a password with
`-apipass [PASS]`.
The protocol is meant to be simple, easy to use and with few overhead.
To connect to the API, just open a TCP connection to the host computer
at the specified port.
To make a request you write the UTF-8 encoded JSON contents to the
socket. To mark the end of the request, you need to terminate the JSON
string with `0x00`. The server returns its answer on the same way,
UTF-8 encoded JSON terminated with NULL.
To save space, by default the JSONs are without whitespace. To enable
pretty printing, use `-apipretty`.
If you want to test the API server without running a game, you can
run it headless via `-apidebug`.
If a password is specified, both request and response are encrypted
using RC4 with the key being the password encoded in UTF-8.
While only providing weak security when the password is static,
it's simple to implement and requires no authentication protocol.
The password is also able to change dynamically, see
`control.session_refresh()` for details.
If you don't want to worry about all the details, you can just use one
of the supplied libraries (check /api/resources). There's also a little
remote control GUI available for python.
### WebSocket
SpiceTools opens a WebSocket server on the specified port plus one.
That means if the API is on 1337, the WebSocket server will be on 1338.
The protocol is the very same as for the normal API, but instead of
directly sending the data over TCP you need to send binary datapackets.
The included dart spiceapi library also has a WebSocket implementation.
### Example Call
This example inserts the a card into P1's reader slot.
If you need more examples, you can check the example code.
#### Request
```JSON
{
"id": 1,
"module": "card",
"function": "insert",
"params": [0, "E004010000000000"]
}
```
#### Response
```JSON
{
"id": 1,
"errors": [],
"data": []
}
```
### Modules
For the sake of simplifying this documentation, I will describe functions
as if they were called via a normal programming language. That means,
besides `module`/`function` needing to be set accordingly, the `params` field
must contain all the parameters described below. Some of them are optional.
The returning data will always be in the `data` field of the response.
Errors will be reported as strings in the `errors` field. Sometimes, you can
receive more than one error. The error messages may be changed/improved in the
future, so you shouldn't rely on them. If the error list is empty, the function
executed successfully.
The `id` of the response will always match the `id` of the request. The API
server itself doesn't expect any logic of your ID choices, however you can use
them to make sure your answers aren't arriving out of order. Currently it
doesn't matter since the TCP protocol doesn't allow for out of order data,
however this may change when/if support for UDP is being introduced. The only
restriction is that the ID has to be a valid 64-bit unsigned integer.
#### Card
- insert(index: uint, card_id: hex)
- inserts a card which gets read by the emulated card readers for the game
- index has to be either 0 (for P1) or 1 (for P2)
- card_id has to be a valid 16-character hex string
#### Coin
- get()
- returns the amount of unprocessed coins in queue
- not equal to the amount of coins shown ingame since the game may drain it
- set(amount: int)
- sets the amount of coins in queue for the game to process
- insert(amount: int)
- adds the amount to the coins in queue
- amount is optional and defaults to 1
- blocker_get()
- returns the current coin blocker state (false: open, true: closed)
#### Info
- avs()
- returns a dict including the AVS model, dest, spec, rev and ext
- launcher()
- returns a dict including the version, compile date/time, system time and
the arguments used to start the process
- memory()
- returns a dict including total, total used and bytes used by the process
for both physical and virtual RAM
#### Keypads
For all functions in this module, the keypad parameter must be
either 0 (for P1) or 1 (for P2). Accepted keypad characters are "0" to "9" for
the single digit numbers, "A" for the double zero and "D" for the decimal key.
- write(keypad: uint, input: str)
- writes all characters in input sequentially to the keypad
- this should be used for things like PIN input
- set(keypad: uint, key: char, ...)
- clears all overrides, then applies each given key as override
- if a key is overridden, it shows up as pressed in-game
- get(keypad: uint)
- returns all pressed keys of the specified keypad
#### Analogs/Buttons/Lights
All of those three modules have equally named methods for you to call.
- read()
- returns an array of state objects containing name, state and a bool
- the bool indicates if the object is active (e.g. the button was overridden)
- write([name: str, state: float], ...)
- applies the states as an override value
- the device binding via the config will be ignored while an override is set
- if an override value is set, the object will be marked as active
- write_reset([name: str], ...)
- removes the override value from the objects specified by name
- if no names were passed, all overrides will be removed
#### Touch
- read()
- returns an array of state objects containing id, x and y
- write([id: uint, x: int, y: int], ...)
- adds the given touch points having an unknown ID
- overrides old touch points when the ID matches
- write_reset(id: uint, ...)
- removes the touch points matching one of the given IDs
#### Control
This module only functions if a password is being used to communicate,
with the single exception of session_refresh().
- raise(signal: str)
- raises a signal to the current process
- signal is a string and must be one of the following values:
"SIGABRT", "SIGFPE", "SIGILL", "SIGINT", "SIGSEGV", "SIGTERM"
- the signal will be raised while the message is being processed
- exit(code: int)
- code is optional and defaults to 0
- the process will end while the message is being processed
- restart()
- spawns a new instance with the same arguments as passed to the main executable
- session_refresh()
- generates a new secure password and applies it after the response
- can be used to increase the security by using the password as some kind of
session token
- recommended to call directly after connecting and once in a while for
persistent connections
- shutdown()
- tries to force shutdown the computer
- reboot()
- tries to force reboot the computer
#### Memory
This module only functions if a password is being used to communicate.
All offsets are specified in file offsets of the corresponding `dll_name`,
which also means that your hex edits are applicable directly.
- write(dll_name: str, data: hex, offset: uint)
- writes the bytes in data to the specified offset
- read(dll_name: str, offset: uint, size: uint)
- reads bytes starting from offset and returns it
- signature(dll_name: str, signature: hex, replacement: hex, offset: uint,
usage: uint)
- tries to find the signature in the module's memory
- unknown bytes are masked out via "??" (e.g. "75??90????74")
- masking out bytes works for both the signature and the replacement
- offset is the byte difference between the offset of the found signature and
the offset where the replacement data gets written to
- usage is the number of the result being used for replacement, starting at 0
- returns the offset where the replacement data gets written to
- the replacement string can be empty, so you can only get the address of the
signature while not actually replacing anything
#### IIDX
- ticker_get()
- returns a string of the characters displayed on the 16 segment display
- ticker_set(text: str)
- sets the contents of the 16 segment display and disables writes from game
- ticker_reset()
- re-enables writes from game
#### LCD
- info()
- returns information about the serial LCD controller some games use
## License
Unless otherwise noted, all files are licensed under the GPLv3.
See the LICENSE file for the full license text.
Files not originating from this project should be placed in "external",
they each have their own license.
###### What that means for you
- If you want to distribute the binaries, you'll have to include the
sources, otherwise you're violating the GPL.
- If you want to merge some of this project's code into similar
software, that software has to be put under the GPL as well.
You'll probably have to modify a lot anyways, so you can just use the
source as some kind of documentation.

140
acio/acio.cpp Normal file
View File

@ -0,0 +1,140 @@
#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;
}
/*
* 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");
// 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();
}
}
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();
}
}

18
acio/acio.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <vector>
#include <windows.h>
#include "module.h"
namespace acio {
// globals
extern HINSTANCE DLL_INSTANCE;
extern std::vector<acio::ACIOModule *> MODULES;
void attach();
void attach_icca();
void detach();
}

763
acio/bi2a/bi2a.cpp Normal file
View File

@ -0,0 +1,763 @@
#include "bi2a.h"
#include "avs/game.h"
#include "games/ddr/io.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/utils.h"
#include "util/tapeled.h"
using namespace GameAPI;
// 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
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
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);
}
// 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 != 38 || y_sz != 49) {
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
*/
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
acio/bi2a/bi2a.h Normal file
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;
};
}

596
acio/bmpu/bmpu.cpp Normal file
View File

@ -0,0 +1,596 @@
#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);
}
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);
}
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);
}
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);
}
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
acio/bmpu/bmpu.h Normal file
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
acio/core/core.cpp Normal file
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
acio/core/core.h Normal file
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
acio/hbhi/hbhi.cpp Normal file
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
acio/hbhi/hbhi.h Normal file
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
acio/hdxs/hdxs.cpp Normal file
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
acio/hdxs/hdxs.h Normal file
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
acio/hgth/hgth.cpp Normal file
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
acio/hgth/hgth.h Normal file
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
acio/i36g/i36g.cpp Normal file
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
acio/i36g/i36g.h Normal file
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
acio/i36i/i36i.cpp Normal file
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
acio/i36i/i36i.h Normal file
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;
};
}

472
acio/icca/icca.cpp Normal file
View File

@ -0,0 +1,472 @@
#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;
};
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;
// get unit
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
// check for card insert
static bool kb_insert_press_old[2] = {false, false};
if (eamuse_card_insert_consume(icca_get_active_count(), unit_id) ||
(kb_insert_press && !kb_insert_press_old[unit_id])) {
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;
// 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
acio/icca/icca.h Normal file
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
acio/j32d/j32d.cpp Normal file
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 (pad1_vel > 0.f) {
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
acio/j32d/j32d.h Normal file
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;
};
}

484
acio/kfca/kfca.cpp Normal file
View File

@ -0,0 +1,484 @@
#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/utils.h"
using namespace GameAPI;
// 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
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
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;
// 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
acio/kfca/kfca.h Normal file
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
acio/klpa/klpa.cpp Normal file
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
acio/klpa/klpa.h Normal file
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
acio/la9a/la9a.cpp Normal file
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
acio/la9a/la9a.h Normal file
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;
};
}

209
acio/mdxf/mdxf.cpp Normal file
View File

@ -0,0 +1,209 @@
#include "mdxf.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"
// constants
const size_t STATUS_BUFFER_SIZE = 32;
// static stuff
static uint8_t COUNTER = 0;
// buffers
#pragma pack(push, 1)
static struct {
uint8_t STATUS_BUFFER_P1[7][STATUS_BUFFER_SIZE] {};
uint8_t STATUS_BUFFER_P2[7][STATUS_BUFFER_SIZE] {};
} BUFFERS {};
#pragma pack(pop)
static bool STATUS_BUFFER_FREEZE = false;
typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)();
static uint64_t arkGetTickTime64() {
static ARK_GET_TICK_TIME64_T getTickTime64 =
(ARK_GET_TICK_TIME64_T)GetProcAddress(avs::game::DLL_INSTANCE, "arkGetTickTime64");
if (getTickTime64 == nullptr) {
// this works on 32-bit versions of avs, but not on 64.
// it's better than nothing though.
return timeGetTime();
}
return getTickTime64();
}
/*
* Implementations
*/
static bool __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *buffer, uint8_t a3, uint8_t a4) {
// Dance Dance Revolution
if (avs::game::is_model("MDX")) {
// get buffer index
auto i = (COUNTER + a3) % std::size(BUFFERS.STATUS_BUFFER_P1);
// copy buffer
if (node == 17 || node == 25) {
memcpy(buffer, BUFFERS.STATUS_BUFFER_P1[i], STATUS_BUFFER_SIZE);
} else if (node == 18 || node == 26) {
memcpy(buffer, BUFFERS.STATUS_BUFFER_P2[i], STATUS_BUFFER_SIZE);
} else {
// fill with zeros on unknown node
memset(buffer, 0, STATUS_BUFFER_SIZE);
return false;
}
}
// return success
return true;
}
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(int node) {
// increase counter
COUNTER = (COUNTER + 1) % std::size(BUFFERS.STATUS_BUFFER_P1);
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
uint8_t *buffer = nullptr;
switch (node) {
case 17:
case 25:
buffer = BUFFERS.STATUS_BUFFER_P1[COUNTER];
break;
case 18:
case 26:
buffer = BUFFERS.STATUS_BUFFER_P2[COUNTER];
break;
default:
// return failure on unknown node
return false;
}
memset(buffer, 0, STATUS_BUFFER_SIZE);
// Dance Dance Revolution
if (avs::game::is_model("MDX")) {
// 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;
}
*(uint64_t*)&buffer[0x18] = arkGetTickTime64();
// get buttons
auto &buttons = games::ddr::get_buttons();
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
buffer[4] |= 0xF0;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
buffer[4] |= 0x0F;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
buffer[5] |= 0xF0;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
buffer[5] |= 0x0F;
}
}
// return success
return true;
}
/*
* 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);
}

13
acio/mdxf/mdxf.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include "../module.h"
namespace acio {
class MDXFModule : public ACIOModule {
public:
MDXFModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}

39
acio/module.cpp Normal file
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
acio/module.h Normal file
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
acio/nddb/nddb.cpp Normal file
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
acio/nddb/nddb.h Normal file
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;
};
}

203
acio/panb/panb.cpp Normal file
View File

@ -0,0 +1,203 @@
#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"
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 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
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,
};
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
auto &analog0 = analogs.at(analog_mapping[key_pair * 2 + 0]);
auto &analog1 = analogs.at(analog_mapping[key_pair * 2 + 1]);
if (analog0.isSet()) {
state0 = (uint8_t) (Analogs::getState(RI_MGR, analog0) * 15.999f);
}
if (analog1.isSet()) {
state1 = (uint8_t) (Analogs::getState(RI_MGR, analog1) * 15.999f);
}
// check buttons
auto velocity0 = Buttons::getVelocity(RI_MGR, buttons.at(button_mapping[key_pair * 2 + 0]));
auto velocity1 = Buttons::getVelocity(RI_MGR, buttons.at(button_mapping[key_pair * 2 + 1]));
if (velocity0 > 0.f) {
state0 = (uint8_t) (velocity0 * 15.999f);
}
if (velocity1 > 0.f) {
state1 = (uint8_t) (velocity1 * 15.999f);
}
// 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
acio/panb/panb.h Normal file
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
acio/pix/pix.cpp Normal file
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
acio/pix/pix.h Normal file
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
acio/pjec/pjec.cpp Normal file
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
acio/pjec/pjec.h Normal file
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
acio/pjei/pjei.cpp Normal file
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
acio/pjei/pjei.h Normal file
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;
};
}

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
acio2emu/firmware/bi2x.h Normal file
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
acio2emu/handle.cpp Normal file
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(std::wstring 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
acio2emu/handle.h Normal file
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(std::wstring 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
acio2emu/internal/crc.h Normal file
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
acio2emu/internal/lz.h Normal file
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
acio2emu/node.h Normal file
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;
};
}

259
acio2emu/packet.cpp Normal file
View File

@ -0,0 +1,259 @@
#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: {}", s);
break;
}
if (!valid) {
log_fatal("acio2emu", "illegal transition detected: {} -> {}", step_, 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
acio2emu/packet.h Normal file
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
acioemu/acioemu.cpp Normal file
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
acioemu/acioemu.h Normal file
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
acioemu/bi2a.h Normal file
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
acioemu/device.cpp Normal file
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
acioemu/device.h Normal file
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);
};
}

73
acioemu/handle.cpp Normal file
View File

@ -0,0 +1,73 @@
#include "handle.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort) {
this->com_port = lpCOMPort;
}
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, 2));
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;
}

30
acioemu/handle.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace acioemu {
class ACIOHandle : public CustomHandle {
private:
LPCWSTR com_port;
acioemu::ACIOEmu acio_emu;
public:
ACIOHandle(LPCWSTR lpCOMPort);
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;
};
}

581
acioemu/icca.cpp Normal file
View File

@ -0,0 +1,581 @@
#include "icca.h"
#include "acio/icca/icca.h"
#include "avs/game.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"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} 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 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 |= 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"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
// 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
acioemu/icca.h Normal file
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);
};
}

459
api/controller.cpp Normal file
View File

@ -0,0 +1,459 @@
#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/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 "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::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());
}
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
api/controller.h Normal file
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
api/module.cpp Normal file
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
api/module.h Normal file
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
api/modules/analogs.cpp Normal file
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
api/modules/analogs.h Normal file
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
api/modules/buttons.cpp Normal file
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
api/modules/buttons.h Normal file
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);
};
}

82
api/modules/capture.cpp Normal file
View File

@ -0,0 +1,82 @@
#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 {
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 (req.params.Size() > 1 && req.params[1].IsUint())
quality = req.params[1].GetUint();
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);
}
}

18
api/modules/capture.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class Capture : public Module {
public:
Capture();
private:
// function definitions
void get_screens(Request &req, Response &res);
void get_jpg(Request &req, Response &res);
};
}

53
api/modules/card.cpp Normal file
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
api/modules/card.h Normal file
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
api/modules/coin.cpp Normal file
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
api/modules/coin.h Normal file
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
api/modules/control.cpp Normal file
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
api/modules/control.h Normal file
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);
};
}

91
api/modules/drs.cpp Normal file
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
api/modules/drs.h Normal file
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);
};
}

72
api/modules/iidx.cpp Normal file
View File

@ -0,0 +1,72 @@
#include "iidx.h"
#include <functional>
#include <vector>
#include "games/iidx/iidx.h"
#include "external/rapidjson/document.h"
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);
}
/**
* 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;
}
}

19
api/modules/iidx.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include "api/module.h"
#include "api/request.h"
namespace api::modules {
class IIDX : public Module {
public:
IIDX();
private:
// function definitions
void ticker_get(Request &req, Response &res);
void ticker_set(Request &req, Response &res);
void ticker_reset(Request &req, Response &res);
};
}

98
api/modules/info.cpp Normal file
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
api/modules/info.h Normal file
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
api/modules/keypads.cpp Normal file
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
api/modules/keypads.h Normal file
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
api/modules/lcd.cpp Normal file
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
api/modules/lcd.h Normal file
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);
};
}

191
api/modules/lights.cpp Normal file
View File

@ -0,0 +1,191 @@
#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);
lights = games::get_lights(eamuse_get_game());
}
/**
* read()
*/
void Lights::read(api::Request &req, Response &res) {
// check light cache
if (!this->lights) {
return;
}
// add state for each light
for (auto &light : *this->lights) {
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], ...)
*/
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()) {
// 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 light_name = param[0].GetString();
// write analog state
if (!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
for (auto &light : *this->lights) {
if (light.getName() == name) {
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;
}
}
// unknown light
return false;
}
bool Lights::write_light_reset(std::string name) {
// check light cache
if (!this->lights) {
return false;
}
// find light
for (auto &light : *this->lights) {
if (light.getName() == name) {
light.override_enabled = false;
return true;
}
}
// unknown light
return false;
}
}

28
api/modules/lights.h Normal file
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 Lights : public Module {
public:
Lights();
private:
// state
std::vector<Light> *lights;
// function definitions
void read(Request &req, Response &res);
void write(Request &req, Response &res);
void write_reset(Request &req, Response &res);
// helper
bool write_light(std::string name, float state);
bool write_light_reset(std::string name);
};
}

233
api/modules/memory.cpp Normal file
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
api/modules/memory.h Normal file
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);
};
}

145
api/modules/touch.cpp Normal file
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
api/modules/touch.h Normal file
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);
};
}

55
api/request.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "request.h"
#include "../util/logging.h"
#include "module.h"
using namespace rapidjson;
namespace api {
Request::Request(rapidjson::Document &document) {
Value::MemberIterator it;
this->parse_error = false;
// get ID
it = document.FindMember("id");
if (it == document.MemberEnd() || !(*it).value.IsUint64()) {
log_warning("api", "Request ID is invalid");
this->parse_error = true;
return;
}
this->id = (*it).value.GetUint64();
// get module
it = document.FindMember("module");
if (it == document.MemberEnd() || !(*it).value.IsString()) {
log_warning("api", "Request module is invalid");
this->parse_error = true;
return;
}
this->module = (*it).value.GetString();
// get function
it = document.FindMember("function");
if (it == document.MemberEnd() || !(*it).value.IsString()) {
log_warning("api", "Request function is invalid");
this->parse_error = true;
return;
}
this->function = (*it).value.GetString();
// get params
it = document.FindMember("params");
if (it == document.MemberEnd() || !(*it).value.IsArray()) {
log_warning("api", "Request params is invalid");
this->parse_error = true;
return;
}
this->params = document["params"];
// log request
if (LOGGING) {
log_info("api", "new request > id: {}, module: {}, function: {}",
this->id, this->module, this->function);
}
}
}

21
api/request.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <stdint.h>
#include "external/rapidjson/document.h"
namespace api {
class Request {
public:
uint64_t id;
std::string module;
std::string function;
rapidjson::Value params;
bool parse_error;
Request(rapidjson::Document &document);
};
}

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