commit caa9e02285d97f8729ed56630769c58f919d381c Author: Scan Date: Wed Aug 28 11:10:34 2024 -0400 Initial re-upload of spice2x-24-08-24 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..852f8ce --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +bin/** +dist/** +docker/** +cmake-build* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4f6b25 --- /dev/null +++ b/.gitignore @@ -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/* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fa18ba2 --- /dev/null +++ b/CMakeLists.txt @@ -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$<$: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("$<$:/ZI>") + add_link_options("$<$:/SAFESEH:NO>") + + # enable fast pdb generation in debug + add_link_options("$<$:/DEBUG:FASTLINK>") + + # enable pdb generation for release builds + add_compile_options("$<$:/Zi>") + add_link_options("$<$:/DEBUG:FULL>") + + # enable COMDAT folding for even smaller release builds + add_link_options("$<$:/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$<$: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") diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0300cf9 --- /dev/null +++ b/Dockerfile @@ -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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce350ec --- /dev/null +++ b/README.md @@ -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. diff --git a/acio/acio.cpp b/acio/acio.cpp new file mode 100644 index 0000000..2ae28f4 --- /dev/null +++ b/acio/acio.cpp @@ -0,0 +1,140 @@ +#include "acio.h" + +#include +#include + +#include + +#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 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(); + } +} diff --git a/acio/acio.h b/acio/acio.h new file mode 100644 index 0000000..b2f1838 --- /dev/null +++ b/acio/acio.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include + +#include "module.h" + +namespace acio { + + // globals + extern HINSTANCE DLL_INSTANCE; + extern std::vector MODULES; + + void attach(); + void attach_icca(); + void detach(); +} diff --git a/acio/bi2a/bi2a.cpp b/acio/bi2a/bi2a.cpp new file mode 100644 index 0000000..2d8d9ba --- /dev/null +++ b/acio/bi2a/bi2a.cpp @@ -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); +} diff --git a/acio/bi2a/bi2a.h b/acio/bi2a/bi2a.h new file mode 100644 index 0000000..9aef074 --- /dev/null +++ b/acio/bi2a/bi2a.h @@ -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; + }; +} diff --git a/acio/bmpu/bmpu.cpp b/acio/bmpu/bmpu.cpp new file mode 100644 index 0000000..c3e1f16 --- /dev/null +++ b/acio/bmpu/bmpu.cpp @@ -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(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(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(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(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); +} diff --git a/acio/bmpu/bmpu.h b/acio/bmpu/bmpu.h new file mode 100644 index 0000000..8ef0b94 --- /dev/null +++ b/acio/bmpu/bmpu.h @@ -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; + }; +} diff --git a/acio/core/core.cpp b/acio/core/core.cpp new file mode 100644 index 0000000..5e6313c --- /dev/null +++ b/acio/core/core.cpp @@ -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); +} diff --git a/acio/core/core.h b/acio/core/core.h new file mode 100644 index 0000000..d5571c0 --- /dev/null +++ b/acio/core/core.h @@ -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; + }; +} diff --git a/acio/hbhi/hbhi.cpp b/acio/hbhi/hbhi.cpp new file mode 100644 index 0000000..56a0b85 --- /dev/null +++ b/acio/hbhi/hbhi.cpp @@ -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); +} diff --git a/acio/hbhi/hbhi.h b/acio/hbhi/hbhi.h new file mode 100644 index 0000000..607805c --- /dev/null +++ b/acio/hbhi/hbhi.h @@ -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; + }; +} diff --git a/acio/hdxs/hdxs.cpp b/acio/hdxs/hdxs.cpp new file mode 100644 index 0000000..d1f2484 --- /dev/null +++ b/acio/hdxs/hdxs.cpp @@ -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); +} diff --git a/acio/hdxs/hdxs.h b/acio/hdxs/hdxs.h new file mode 100644 index 0000000..7ac27de --- /dev/null +++ b/acio/hdxs/hdxs.h @@ -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; + }; +} diff --git a/acio/hgth/hgth.cpp b/acio/hgth/hgth.cpp new file mode 100644 index 0000000..b6d7036 --- /dev/null +++ b/acio/hgth/hgth.cpp @@ -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); +} diff --git a/acio/hgth/hgth.h b/acio/hgth/hgth.h new file mode 100644 index 0000000..3233ac1 --- /dev/null +++ b/acio/hgth/hgth.h @@ -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; + }; +} diff --git a/acio/i36g/i36g.cpp b/acio/i36g/i36g.cpp new file mode 100644 index 0000000..336d69d --- /dev/null +++ b/acio/i36g/i36g.cpp @@ -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"); +} diff --git a/acio/i36g/i36g.h b/acio/i36g/i36g.h new file mode 100644 index 0000000..7d22628 --- /dev/null +++ b/acio/i36g/i36g.h @@ -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; + }; +} diff --git a/acio/i36i/i36i.cpp b/acio/i36i/i36i.cpp new file mode 100644 index 0000000..6b2cdaf --- /dev/null +++ b/acio/i36i/i36i.cpp @@ -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); +} diff --git a/acio/i36i/i36i.h b/acio/i36i/i36i.h new file mode 100644 index 0000000..514fa8c --- /dev/null +++ b/acio/i36i/i36i.h @@ -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; + }; +} diff --git a/acio/icca/icca.cpp b/acio/icca/icca.cpp new file mode 100644 index 0000000..3fb98ae --- /dev/null +++ b/acio/icca/icca.cpp @@ -0,0 +1,472 @@ +#include +#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(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(reinterpret_cast(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); +} diff --git a/acio/icca/icca.h b/acio/icca/icca.h new file mode 100644 index 0000000..b9722cc --- /dev/null +++ b/acio/icca/icca.h @@ -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; +} diff --git a/acio/j32d/j32d.cpp b/acio/j32d/j32d.cpp new file mode 100644 index 0000000..fab58cf --- /dev/null +++ b/acio/j32d/j32d.cpp @@ -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); +} diff --git a/acio/j32d/j32d.h b/acio/j32d/j32d.h new file mode 100644 index 0000000..1acb591 --- /dev/null +++ b/acio/j32d/j32d.h @@ -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; + }; +} diff --git a/acio/kfca/kfca.cpp b/acio/kfca/kfca.cpp new file mode 100644 index 0000000..079208c --- /dev/null +++ b/acio/kfca/kfca.cpp @@ -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); +} diff --git a/acio/kfca/kfca.h b/acio/kfca/kfca.h new file mode 100644 index 0000000..2fb464a --- /dev/null +++ b/acio/kfca/kfca.h @@ -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; + }; +} diff --git a/acio/klpa/klpa.cpp b/acio/klpa/klpa.cpp new file mode 100644 index 0000000..dd39d90 --- /dev/null +++ b/acio/klpa/klpa.cpp @@ -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); +} diff --git a/acio/klpa/klpa.h b/acio/klpa/klpa.h new file mode 100644 index 0000000..79ca4a5 --- /dev/null +++ b/acio/klpa/klpa.h @@ -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; + }; +} diff --git a/acio/la9a/la9a.cpp b/acio/la9a/la9a.cpp new file mode 100644 index 0000000..bc90f01 --- /dev/null +++ b/acio/la9a/la9a.cpp @@ -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 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::max()) + CONTROL_STATUS.touch_x = static_cast(touch_point.x); + CONTROL_STATUS.touch_y = static_cast(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); + } +} diff --git a/acio/la9a/la9a.h b/acio/la9a/la9a.h new file mode 100644 index 0000000..a179915 --- /dev/null +++ b/acio/la9a/la9a.h @@ -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; + }; +} \ No newline at end of file diff --git a/acio/mdxf/mdxf.cpp b/acio/mdxf/mdxf.cpp new file mode 100644 index 0000000..8fe81d2 --- /dev/null +++ b/acio/mdxf/mdxf.cpp @@ -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); +} diff --git a/acio/mdxf/mdxf.h b/acio/mdxf/mdxf.h new file mode 100644 index 0000000..cf22516 --- /dev/null +++ b/acio/mdxf/mdxf.h @@ -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; + }; +} diff --git a/acio/module.cpp b/acio/module.cpp new file mode 100644 index 0000000..4144f68 --- /dev/null +++ b/acio/module.cpp @@ -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; +} diff --git a/acio/module.h b/acio/module.h new file mode 100644 index 0000000..2d35175 --- /dev/null +++ b/acio/module.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include + +// macro for lazy typing of hooks +#define ACIO_MODULE_HOOK(f) this->hook(reinterpret_cast(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; + }; +} diff --git a/acio/nddb/nddb.cpp b/acio/nddb/nddb.cpp new file mode 100644 index 0000000..2b0910a --- /dev/null +++ b/acio/nddb/nddb.cpp @@ -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); +} diff --git a/acio/nddb/nddb.h b/acio/nddb/nddb.h new file mode 100644 index 0000000..92e18ba --- /dev/null +++ b/acio/nddb/nddb.h @@ -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; + }; +} diff --git a/acio/panb/panb.cpp b/acio/panb/panb.cpp new file mode 100644 index 0000000..b499660 --- /dev/null +++ b/acio/panb/panb.cpp @@ -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); +} diff --git a/acio/panb/panb.h b/acio/panb/panb.h new file mode 100644 index 0000000..ebbc067 --- /dev/null +++ b/acio/panb/panb.h @@ -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; + }; +} diff --git a/acio/pix/pix.cpp b/acio/pix/pix.cpp new file mode 100644 index 0000000..511f716 --- /dev/null +++ b/acio/pix/pix.cpp @@ -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); +} diff --git a/acio/pix/pix.h b/acio/pix/pix.h new file mode 100644 index 0000000..f547ed5 --- /dev/null +++ b/acio/pix/pix.h @@ -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; + }; +} diff --git a/acio/pjec/pjec.cpp b/acio/pjec/pjec.cpp new file mode 100644 index 0000000..12c0ffe --- /dev/null +++ b/acio/pjec/pjec.cpp @@ -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); +} diff --git a/acio/pjec/pjec.h b/acio/pjec/pjec.h new file mode 100644 index 0000000..78e38a0 --- /dev/null +++ b/acio/pjec/pjec.h @@ -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; + }; +} diff --git a/acio/pjei/pjei.cpp b/acio/pjei/pjei.cpp new file mode 100644 index 0000000..101380a --- /dev/null +++ b/acio/pjei/pjei.cpp @@ -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); +} diff --git a/acio/pjei/pjei.h b/acio/pjei/pjei.h new file mode 100644 index 0000000..e14787d --- /dev/null +++ b/acio/pjei/pjei.h @@ -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; + }; +} diff --git a/acio2emu/firmware/bi2x.cpp b/acio2emu/firmware/bi2x.cpp new file mode 100644 index 0000000..bd287f6 --- /dev/null +++ b/acio2emu/firmware/bi2x.cpp @@ -0,0 +1,67 @@ +#include "bi2x.h" + +namespace acio2emu::firmware { + bool BI2XNode::handle_packet(const acio2emu::Packet &in, std::vector &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(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; + } +} \ No newline at end of file diff --git a/acio2emu/firmware/bi2x.h b/acio2emu/firmware/bi2x.h new file mode 100644 index 0000000..bd589d2 --- /dev/null +++ b/acio2emu/firmware/bi2x.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +#include "acio2emu/node.h" +#include "util/logging.h" + +namespace acio2emu::firmware { + class BI2XNode : public Node { + virtual void read_firmware_version(std::vector &buffer) = 0; + + virtual bool read_input(std::vector &buffer) = 0; + virtual int write_output(std::span buffer) = 0; + + /* + * acio2emu::Node + */ + bool handle_packet(const acio2emu::Packet &in, std::vector &out) override; + }; +} \ No newline at end of file diff --git a/acio2emu/handle.cpp b/acio2emu/handle.cpp new file mode 100644 index 0000000..2e6a86e --- /dev/null +++ b/acio2emu/handle.cpp @@ -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 &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(this); + } + + bool IOBHandle::register_node(std::unique_ptr 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 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(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(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; + } +} \ No newline at end of file diff --git a/acio2emu/handle.h b/acio2emu/handle.h new file mode 100644 index 0000000..5939d54 --- /dev/null +++ b/acio2emu/handle.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include // std::unique_ptr +#include + +#include "acio2emu/packet.h" +#include "acio2emu/node.h" + +#include "hooks/devicehook.h" + +namespace acio2emu { + class IOBHandle : public CustomHandle { + private: + std::wstring device_; + + std::array, 17> nodes_; + // the first node is reserved for the "master" node + int number_of_nodes_ = 1; + + PacketDecoder decoder_; + std::queue output_; + + void forward_packet_(const Packet &packet); + + public: + IOBHandle(std::wstring device); + + bool register_node(std::unique_ptr 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; + }; +} \ No newline at end of file diff --git a/acio2emu/internal/crc.h b/acio2emu/internal/crc.h new file mode 100644 index 0000000..a35c647 --- /dev/null +++ b/acio2emu/internal/crc.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +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; + } +} \ No newline at end of file diff --git a/acio2emu/internal/lz.h b/acio2emu/internal/lz.h new file mode 100644 index 0000000..85f2f43 --- /dev/null +++ b/acio2emu/internal/lz.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include + +namespace acio2emu::detail { + class InflateTransformer { + private: + std::queue 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; + } + }; +} \ No newline at end of file diff --git a/acio2emu/node.h b/acio2emu/node.h new file mode 100644 index 0000000..3acefe7 --- /dev/null +++ b/acio2emu/node.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "acio2emu/packet.h" + +namespace acio2emu { + class Node { + public: + virtual ~Node() {} + + virtual bool handle_packet(const Packet &in, std::vector &out) = 0; + }; +} \ No newline at end of file diff --git a/acio2emu/packet.cpp b/acio2emu/packet.cpp new file mode 100644 index 0000000..a9af536 --- /dev/null +++ b/acio2emu/packet.cpp @@ -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 &out, const std::vector &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 &out, uint8_t node, uint8_t tag, const std::vector &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(node * 3), + tag, + static_cast(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(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_; + } +} \ No newline at end of file diff --git a/acio2emu/packet.h b/acio2emu/packet.h new file mode 100644 index 0000000..ecefc94 --- /dev/null +++ b/acio2emu/packet.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include // std::linear_congruential_engine +#include + +#include "acio2emu/internal/lz.h" + +namespace acio2emu { + struct Packet { + uint8_t node; + uint8_t tag; + std::vector 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 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 &out, uint8_t node, uint8_t tag, const std::vector &payload); +} \ No newline at end of file diff --git a/acioemu/acioemu.cpp b/acioemu/acioemu.cpp new file mode 100644 index 0000000..658cbc3 --- /dev/null +++ b/acioemu/acioemu.cpp @@ -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(); + this->response_buffer = new circular_buffer(4096); + this->read_buffer = new circular_buffer(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 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 +} diff --git a/acioemu/acioemu.h b/acioemu/acioemu.h new file mode 100644 index 0000000..57328e0 --- /dev/null +++ b/acioemu/acioemu.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "util/circular_buffer.h" + +#include "device.h" +#include "icca.h" + +namespace acioemu { + + class ACIOEmu { + private: + std::vector *devices; + circular_buffer *response_buffer; + circular_buffer *read_buffer; + bool invert = false; + + void msg_parse(); + + public: + + explicit ACIOEmu(); + ~ACIOEmu(); + + void add_device(ACIODeviceEmu *device); + + void write(uint8_t byte); + std::optional read(); + size_t bytes_available(); + }; +} diff --git a/acioemu/bi2a.h b/acioemu/bi2a.h new file mode 100644 index 0000000..53669a6 --- /dev/null +++ b/acioemu/bi2a.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include +#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 *response_buffer) override; + + void update_card(int unit); + void update_keypad(int unit, bool update_edge); + void update_status(int unit); + }; +} diff --git a/acioemu/device.cpp b/acioemu/device.cpp new file mode 100644 index 0000000..6e8dacf --- /dev/null +++ b/acioemu/device.cpp @@ -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 *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 *response_buffer) { + auto data = reinterpret_cast(msg); + write_msg(data, MSG_HEADER_SIZE + msg->cmd.data_size, response_buffer); +} diff --git a/acioemu/device.h b/acioemu/device.h new file mode 100644 index 0000000..3bd0fcc --- /dev/null +++ b/acioemu/device.h @@ -0,0 +1,103 @@ +#pragma once + +#include + +#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 *response_buffer) = 0; + static void write_msg(const uint8_t *data, size_t size, circular_buffer *response_buffer); + static void write_msg(MessageData *msg, circular_buffer *response_buffer); + }; +} diff --git a/acioemu/handle.cpp b/acioemu/handle.cpp new file mode 100644 index 0000000..7ed2e8c --- /dev/null +++ b/acioemu/handle.cpp @@ -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(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(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; +} diff --git a/acioemu/handle.h b/acioemu/handle.h new file mode 100644 index 0000000..acff798 --- /dev/null +++ b/acioemu/handle.h @@ -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; + }; +} diff --git a/acioemu/icca.cpp b/acioemu/icca.cpp new file mode 100644 index 0000000..1f75a10 --- /dev/null +++ b/acioemu/icca.cpp @@ -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[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 *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 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); +} diff --git a/acioemu/icca.h b/acioemu/icca.h new file mode 100644 index 0000000..2ca83be --- /dev/null +++ b/acioemu/icca.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include + +#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; + 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 *response_buffer) override; + + void update_card(int unit); + void update_keypad(int unit, bool update_edge); + void update_status(int unit); + }; +} diff --git a/api/controller.cpp b/api/controller.cpp new file mode 100644 index 0000000..300923a --- /dev/null +++ b/api/controller.cpp @@ -0,0 +1,459 @@ +#include +#include + +#include "controller.h" + +#include + +#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(&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(&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 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 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 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 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 *in, std::vector *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 *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 *vec) { + std::lock_guard 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()) + ")"; + } + } + } +} diff --git a/api/controller.h b/api/controller.h new file mode 100644 index 0000000..5413f44 --- /dev/null +++ b/api/controller.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#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 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 serial; + std::vector server_workers; + std::vector server_handlers; + std::mutex server_handlers_m; + std::vector 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 *in, std::vector *out); + bool process_request(ClientState *state, const char *in, size_t in_size, std::vector *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 *output); + + std::string get_ip_address(sockaddr_in addr); + + inline const std::string &get_password() const { + return this->password; + } + }; +} diff --git a/api/module.cpp b/api/module.cpp new file mode 100644 index 0000000..700a562 --- /dev/null +++ b/api/module.cpp @@ -0,0 +1,43 @@ +#include + +#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); + } +} diff --git a/api/module.h b/api/module.h new file mode 100644 index 0000000..d41b787 --- /dev/null +++ b/api/module.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "response.h" +#include "request.h" + +namespace api { + + // logging setting + extern bool LOGGING; + + // callback + typedef std::function ModuleFunctionCallback; + + class Module { + protected: + + // map of available functions + robin_hood::unordered_map 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 + }; +} diff --git a/api/modules/analogs.cpp b/api/modules/analogs.cpp new file mode 100644 index 0000000..3cb2be1 --- /dev/null +++ b/api/modules/analogs.cpp @@ -0,0 +1,177 @@ +#include "analogs.h" +#include +#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 ¶m : 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 ¶m : 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; + } +} diff --git a/api/modules/analogs.h b/api/modules/analogs.h new file mode 100644 index 0000000..c6f93c7 --- /dev/null +++ b/api/modules/analogs.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#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 *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); + }; +} diff --git a/api/modules/buttons.cpp b/api/modules/buttons.cpp new file mode 100644 index 0000000..2f28345 --- /dev/null +++ b/api/modules/buttons.cpp @@ -0,0 +1,182 @@ +#include "buttons.h" +#include +#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 ¶m : 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 ¶m : 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; + } +} diff --git a/api/modules/buttons.h b/api/modules/buttons.h new file mode 100644 index 0000000..1a9a8a7 --- /dev/null +++ b/api/modules/buttons.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#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