commit cbd7720349a51a8a067a939e2f8104f9e2e691bf Author: icex2 Date: Fri Sep 27 22:36:50 2019 +0200 Bemanitools v5.26 release diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..98f3758 --- /dev/null +++ b/.clang-format @@ -0,0 +1,45 @@ +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: 'false' +AlignConsecutiveDeclarations: 'false' +AlignEscapedNewlines: Left +AlignOperands: 'false' +AlignTrailingComments: 'false' +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: 'false' +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterDefinitionReturnType: All +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'false' +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Linux +BreakBeforeTernaryOperators: 'false' +BreakStringLiterals: 'true' +ColumnLimit: '120' +ContinuationIndentWidth: '8' +IncludeBlocks: Preserve +IndentCaseLabels: 'false' +IndentPPDirectives: None +IndentWidth: '4' +IndentWrappedFunctionNames: 'false' +KeepEmptyLinesAtTheStartOfBlocks: 'false' +Language: Cpp +MaxEmptyLinesToKeep: '1' +PointerAlignment: Right +ReflowComments: 'true' +SortIncludes: 'true' +SpaceAfterCStyleCast: 'true' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: 'false' +SpacesBeforeTrailingComments: '1' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'false' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +TabWidth: '4' +UseTab: Never diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02dfb39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ +.*.swp +.idea +.vscode +.vs +version \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7d29d4c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,177 @@ +# Release history +## 5.26 +* iidxio-ezusb: Reduce sleep time to Sleep(1) to avoid framerate issues on some versions of iidx. +* Bugfix: iidx d3d9 games, mainly the newer ones iidx 20-25, freezing. Happened on boot, during song selection or during the song. +* Bugfix: log_server_init deadlock on iidxhook4-7. This caused games like iidx24 to hang before even showing a render window. +* iidxhook: Add feature to allow GPU based up-/downscaling of rendered frame. This gives +you the possibility to upscale the resolution of old SD (640x480) games to your monitor's/TV's +native resolution which can have a few advantages: better image quality if the monitor's upscaler +is not doing a good job, especially on resolutions that are not a multiple of its native resolution; +Reduce display latency if the upscaler is slow, avoid over-/underscan which cannot always be fixed +entirely or at all (depending on your GPU and monitor model). See the iidxhook configuration file +for the new parameters available. +* Major readme cleanup. Add development documentation like style guide, guidelines, development setup. + +## 5.25 +* Bugfix: iidx14 and 15 crashing on Windows 10 +* Bugfix: IO2 driver not using correct package sizes on reads/write -> iidxio-ezusb2.dll now working +* Improve ezusb2-boot.bat script to handle flashing of IO2 firmware +* Remove broken x64 builds of ezusb1/2 tools -> Just use the x86 tool versions instead +(use the x64 versions of iidxio-ezusb.dll and iidxio-ezusb2.dll with iidx25) +* iidxhook1-8: Allow floating point values for frame rate limiting, e.g. 59.95 (hz). +* Improve timing with ezusb (C02) driver + +## v5.24 +* Bugfix: iidxhook8 hangs very early on startup (race condition in log-server module) + +## v5.23 +* Refactored configurion (file) handling for iidxhooks, RE-READ THE DOCUMENTATION. +This gets rid of the "short cmd parameters", e.g. -w for windowed mode, and replaces +them with full name parameters, e.g. -p gfx.windowed=true, which improves handling +of configuration files/values. +* Add a lot of unit tests to the codebase +* Refactored round plug security infrastructure, shared with IIDX and jubeat (1) +* Move shared utility modules between iidxhook modules to a separate utilty module 'iidxhook-util' +* Add experimental jubeat (1) support (buggy IO emulation) +* Various fixes to improve all iidxhooks when running on Windows 10 +* Bugfix: iidxio-ezusb getting stuck on newer Windows platforms +* Update iidxhook docs, e.g. how to get old IIDX versions sync on Windows 10 +* Various documentation updates +* Various code cleanup +* Various other minor bugfixes + +## v5.22 +* Added a lot of documentation and readme stuff (READ IT!!11) +* Re-numbering iidxhook implementations to make room for missing games +* Support for IIDX 18 -> iidxhook4 +* Support for IIDX 19 -> iidxhook5 +* Support for IIDX 20 -> iidxhook6 +* A lot of code refactoring and cleanup +* Refactor p3io emulation +* Remove obsolete BT4 DDR stuff +* iidxhook: Refactored software monitor check and bugfixes. You can use the +auto monitor check to determine your machine's refresh rate or (new) set the +refresh rate yourself in the configuration file, e.g. when you already have +determined it using one of the newer IIDX games and want to skip that monitor +check or if BT5's software monitor check doesn't work properly (e.g. on Win 7). +* iidxhook1-3: Check if eamuse server is reachable and log a warning otherwise. +This should make debugging invalid URLs or connection issues easier. +* iidxhook: Revised ezusb and ezusb2 emulation layer. Translucent support +removed, all IO emulation goes through BT5's iidxio interface. +* iidxio: Add implementations for ezusb (C02 IO) and ezusb2 (IO2) hardware. This +allows you to run _ANY_ IIDX game supported by BT5 either with real C02 or IO2 +hardware. +* iidxhook: Remove translucent card reader feature. Again, to create a unified +interface for _ALL_ versions, use the eamio-icca.dll if you want to run on +real ICCA (slotted or wavepass) readers. +* Various other bugfixes + +Again, read the various markdown (.md) readme files. We tried to document +everything to the best of our knowledge. If you are missing something, please +contribute by adding that information and submitting a patch to us. + +## v5.21 +* Camera hook for IIDX 25 (use any UVC webcam in-game), by Xyen +* *deep breath* Source code release + +## v5.20 +* Support for the new IIDX 25 IO board (xyen) +* New IO hook system with better multi-threading behavior +* Add a replaceable "vefxio" backend dll for IIDX (xyen) +* Card reader emulation can now be disabled in iidxhook (xyen) +* Add jbhook (xyen, mon) +* Add ddrhook (ported from Bemanitools 4 by mon) +* Various ICCA emulation improvements (xyen, mon) +* QoL improvements to config.exe (xyen, mon) +* Other bug fixes (various contributors) + +## a19 +* iidx 17 support +* Bugfix: forums.php?action=viewthread&threadid=51257&postid=1425861#post1425861 +* iidx 14-17: Improved monitor check and new monitor check screen which shows +the current frame rate instead of just a white screen +* iidx 09-13: Improved monitor check (but still white screen when in progress. +d3d8 doesn't offer any text render out of the box) +* iidxfx(2)-exit-hook: Switch off lights on shutdown +* Various other bugfixes + +## a18 +* Bugfix: forums.php?action=viewthread&threadid=51063 +* Various other bugfixes + +## a17 +* IIDX 16 support +* Fix broken debug output to file (for iidxhook1-3 and all games using launcher) +* Improve debug output +* Various minor bugfixes + +## a16 +* Add tools.zip which contains various tools for development: ezusb IO related, +bemanitools API testing, acio related +* Add documentation (.md files) for tools +* Add iidxfx(2)-exit-hook.dll: Hook this using either inject or launcher +(depending on the game version) and exit the game by pressing Start P1 + Start +P2 + VEFX + Effect simultaneously +* Bugfix iidxio API: 16seg not working on IIDX games with FX2 emulation +* Bugfix iidxio API: return value of init call not getting checked in hook +libraries +* SDVX input emulation fixes +* Various other bugfixes + +## a15 +* Select best network adapter if having multiple +* Add Felica card detection +* Fix SDVX HID lighting +* Fix IIDX FX2 deck lighting + +## a13 +* iidx 15 (DJ Troopers) support +* Fix BG video triangle seam on old games +* New options handling: cmd args and options file + +## a11 +* Fix nVidia crash on GOLD + +## a10 +* Adds IO2 emulation for Gold +* Add IO2 translucent mode for Gold ONLY atm (untested due to lack of hardware) +* Random input bug resolved (also kinda untested, so maybe?) + +## a09 +* Add IIDX 14 support + +## a08 +* Add experimental KFCA (SDVX PCB) support +* Add Sound Voltex and BeatStream builds + +## a07 +* Add IIDX 13 DistorteD support +* Add option to use real card readers with IIDX 13 + +## a06 +* Fixes bug in chart data loader interception code + +## a05 +* Fix broken card reader emulation on Copula +* Add monitor check/auto timebase for old IIDX games (9-12) -> refer to the +readme file on how to use it + +## a04 +* Add software frame rate limiter for all D3D8 based games (-g option). + +## a03 +* Add support for IIDX 9-12 +* Translucent mode: Use real C02 EZUSB IO hardware +* eamio-real.dll: Use real slotted or wave pass card readers +* Setup guide, advanced features and FAQ: see readme file iidxhook1.md + +## a02 +* Fonts are always correct irrespective of system locale! (Make sure you +install East Asian fonts tho) +* No longer crashes shitty gaming mice that don't follow the USB spec! (will +backport this to bt4) +* launcher.exe now has a UAC manifest! (because 2006 called and told me to get +with the fucking program) + +## a01 +* Initial Alpha, only supports IIDX 21 and 22 for now. \ No newline at end of file diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..640e3ee --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,220 @@ +# vim: noexpandtab sts=8 sw=8 ts=8 + +# +# Overridable variables +# + +V ?= @ +BUILDDIR ?= build + +# +# Internal variables +# + +depdir := $(BUILDDIR)/dep +objdir := $(BUILDDIR)/obj +bindir := $(BUILDDIR)/bin + +toolchain_32 := i686-w64-mingw32- +toolchain_64 := x86_64-w64-mingw32- + +gitrev := $(shell git rev-parse HEAD) +cppflags := -I src -I src/main -I src/test -DGITREV=$(gitrev) +cflags := -O2 -pipe -ffunction-sections -fdata-sections \ + -Wall -std=c99 +ldflags := -Wl,--gc-sections -static-libgcc + +# +# The first target that GNU Make encounters becomes the default target. +# Define our ultimate target (`all') here, and also some helpers +# + +all: + +# Generate a version file to identify the build +version: + @echo "$(gitrev)" > version + +.PHONY: clean + +clean: + $(V)rm -rf $(BUILDDIR) + +# +# Pull in module definitions +# + +deps := + +dlls := +exes := +imps := +libs := + +avsdlls := +avsexes := + +testexes := + +include Module.mk + +modules := $(dlls) $(exes) $(libs) $(avsdlls) $(avsexes) $(testexes) + +# +# $1: Bitness +# $2: AVS2 minor version +# $3: Module +# + +define t_moddefs + +cppflags_$3 += $(cppflags) -DBUILD_MODULE=$3 +cflags_$3 += $(cflags) +ldflags_$3 += $(ldflags) +srcdir_$3 ?= src/main/$3 + +endef + +$(eval $(foreach module,$(modules),$(call t_moddefs,_,_,$(module)))) + +############################################################################## + +define t_bitness + +subdir_$1_indep := indep-$1 +bindir_$1_indep := $(bindir)/$$(subdir_$1_indep) + +$$(bindir_$1_indep): + $(V)mkdir -p $$@ + +$$(eval $$(foreach imp,$(imps),$$(call t_import,$1,indep,$$(imp)))) +$$(eval $$(foreach dll,$(dlls),$$(call t_linkdll,$1,indep,$$(dll)))) +$$(eval $$(foreach exe,$(exes),$$(call t_linkexe,$1,indep,$$(exe)))) +$$(eval $$(foreach lib,$(libs),$$(call t_archive,$1,indep,$$(lib)))) + +$$(eval $$(foreach avsver,$$(avsvers_$1),$$(call t_avsver,$1,$$(avsver)))) + +$$(eval $$(foreach exe,$(testexes),$$(call t_linkexe,$1,indep,$$(exe)))) + +endef + +############################################################################## + +define t_avsver + +subdir_$1_$2 := avs2_$2-$1 +bindir_$1_$2 := $(bindir)/$$(subdir_$1_$2) + +$$(bindir_$1_$2): + $(V)mkdir -p $$@ + +$$(eval $$(foreach imp,$(imps),$$(call t_import,$1,$2,$$(imp)))) +$$(eval $$(foreach dll,$(avsdlls),$$(call t_linkdll,$1,$2,$$(dll)))) +$$(eval $$(foreach exe,$(avsexes),$$(call t_linkexe,$1,$2,$$(exe)))) + +endef + +############################################################################## + +define t_compile + +depdir_$1_$2_$3 := $(depdir)/$$(subdir_$1_$2)/$3 +abslib_$1_$2_$3 := $$(libs_$3:%=$$(bindir_$1_indep)/lib%.a) +absdpl_$1_$2_$3 := $$(deplibs_$3:%=$$(bindir_$1_$2)/lib%.a) +objdir_$1_$2_$3 := $(objdir)/$$(subdir_$1_$2)/$3 +obj_$1_$2_$3 := $$(src_$3:%.c=$$(objdir_$1_$2_$3)/%.o) \ + $$(rc_$3:%.rc=$$(objdir_$1_$2_$3)/%_rc.o) + +deps += $$(src_$3:%.c=$$(depdir_$1_$2_$3)/%.d) + +$$(depdir_$1_$2_$3): + $(V)mkdir -p $$@ + +$$(objdir_$1_$2_$3): + $(V)mkdir -p $$@ + +$$(objdir_$1_$2_$3)/%.o: $$(srcdir_$3)/%.c \ + | $$(depdir_$1_$2_$3) $$(objdir_$1_$2_$3) + $(V)echo ... $$@ + $(V)$$(toolchain_$1)gcc $$(cflags_$3) $$(cppflags_$3) \ + -MMD -MF $$(depdir_$1_$2_$3)/$$*.d -MT $$@ -MP \ + -DAVS_VERSION=$2 -c -o $$@ $$< + +$$(objdir_$1_$2_$3)/%_rc.o: $$(srcdir_$3)/%.rc + $(V)echo ... $$@ [windres] + $(V)$$(toolchain_$1)windres $$(cppflags_$3) $$< $$@ + +endef + +############################################################################## + +define t_archive + +$(t_compile) + +$$(bindir_$1_$2)/lib$3.a: $$(obj_$1_$2_$3) | $$(bindir_$1_$2) + $(V)echo ... $$@ + $(V)$$(toolchain_$1)ar r $$@ $$^ 2> /dev/null + $(V)$$(toolchain_$1)ranlib $$@ + +endef + +############################################################################## + +define t_linkdll + +$(t_compile) + +dll_$1_$2_$3 := $$(bindir_$1_$2)/$3.dll +implib_$1_$2_$3 := $$(bindir_$1_$2)/lib$3.a + +$$(dll_$1_$2_$3) $$(implib_$1_$2_$3): $$(obj_$1_$2_$3) $$(abslib_$1_$2_$3) \ + $$(absdpl_$1_$2_$3) \ + $$(srcdir_$3)/$3.def | $$(bindir_$1_$2) + $(V)echo ... $$(dll_$1_$2_$3) + $(V)$$(toolchain_$1)gcc -shared \ + -o $$(dll_$1_$2_$3) -Wl,--out-implib,$$(implib_$1_$2_$3) \ + $$^ $$(ldflags_$3) + $(V)$$(toolchain_$1)strip $$(dll_$1_$2_$3) + $(V)$$(toolchain_$1)ranlib $$(implib_$1_$2_$3) + +endef + +############################################################################## + +define t_linkexe + +$(t_compile) + +exe_$1_$2_$3 := $$(bindir_$1_$2)/$3.exe + +$$(exe_$1_$2_$3): $$(obj_$1_$2_$3) $$(abslib_$1_$2_$3) $$(absdpl_$1_$2_$3) \ + | $$(bindir_$1_$2) + $(V)echo ... $$@ + $(V)$$(toolchain_$1)gcc -o $$@ $$^ $$(ldflags_$3) + $(V)$$(toolchain_$1)strip $$@ + +endef + +############################################################################## + +define t_import + +impdef_$1_$2_$3 ?= src/imports/import_$1_$2_$3.def + +$$(bindir_$1_$2)/lib$3.a: $$(impdef_$1_$2_$3) | $$(bindir_$1_$2) + $(V)echo ... $$@ [dlltool] + $(V)$$(toolchain_$1)dlltool -l $$@ -d $$< + +endef + +############################################################################## + +$(eval $(foreach bitness,32 64,$(call t_bitness,$(bitness)))) + +# +# Pull in GCC-generated dependency files +# + +-include $(deps) + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/Module.mk b/Module.mk new file mode 100644 index 0000000..ef2710d --- /dev/null +++ b/Module.mk @@ -0,0 +1,502 @@ +# Example AVS version: 2.13.4 (use std terminology: major.minor.patch) +# +# AVS major version has been 2 since forever +# AVS patch versions appear to maintain API and ABI compatibility. +# Or, they did until 2.16.7 reared its fucking head. +# +# So "AVS version" NOW equals minor version * 100 + patch version. +# 2.16.7 is encoded as 1607 under this scheme. +# +# Games with no AVS version (like IIDX 9th to Happy Sky) are treated as +# version 0. +# +# List of known versions: +# +# None (0): beatmania IIDX 9th Style +# beatmania IIDX 10th Style +# beatmania IIDX 11 RED +# beatmania IIDX 12 Happy Sky +# +# 1: Patch 4: beatmania IIDX 13 DistorteD +# +# 4: Patch 2: beatmania IIDX 14 GOLD +# +# 6: Patch 5: beatmania IIDX 15 DJ Troopers +# +# 8: Patch 3: beatmania IIDX 16 Empress +# jubeat +# +# 10: Patch 4: DanceDanceRevolution X2 +# +# 11: Patch 1: beatmania IIDX 18 Resort Anthem +# pop'n music 19 Tune Street +# +# 12: Patch 1: LovePlus Arcade (not a Bemani, w/e) +# +# 13: Patch 4: beatmania IIDX 19 Lincle +# Patch 6: DanceDanceRevolution X3 +# pop'n music 20 Fantasia +# +# 14: Patch 3: Guitar Freaks & Drum Mania XG3 +# +# 15: Patch 8: DanceDanceRevolution (2013) +# beatmania IIDX 20 tricoro +# pop'n music 21 Sunny Park +# SOUND VOLTEX (all known versions) +# jubeat prop +# +# 16: Patch 1: beatmania IIDX 21 SPADA +# beatmania IIDX 22 PENDUAL +# beatmania IIDX 23 copula +# beatmania IIDX 24 SINOBUZ +# "Patch" 3: Silent Scope Bone Eater (compatible with p7) +# "Patch" 7: Steel Chronicle VicTroopers (not a Bemani, w/e) +# +# 17: Patch 0: beatmania IIDX 25 CANNON BALLERS +# + +cflags += \ + -DWIN32_LEAN_AND_MEAN \ + -DWINVER=0x0601 \ + -D_WIN32_WINNT=0x0601 \ + -DCOBJMACROS \ + -Wno-attributes \ + +# List only the AVS versions that are meaningfully distinct here. +# Each AVS-dependent project should consume the earliest AVS import definition +# that is still ABI-compatible with the real build its target links against. + +avsvers_32 := 1700 1603 1601 1508 1403 1304 1101 803 0 +avsvers_64 := 1700 1603 1601 1508 + +imps += avs avs-ea3 + +include src/main/aciodrv/Module.mk +include src/main/acioemu/Module.mk +include src/main/aciotest/Module.mk +include src/main/bsthook/Module.mk +include src/main/bstio/Module.mk +include src/main/cconfig/Module.mk +include src/main/config/Module.mk +include src/main/ddrhook/Module.mk +include src/main/ddrio/Module.mk +include src/main/ddrio-smx/Module.mk +include src/main/ddrio-mm/Module.mk +include src/main/eamio/Module.mk +include src/main/eamio-icca/Module.mk +include src/main/eamiotest/Module.mk +include src/main/ezusb/Module.mk +include src/main/ezusb-emu/Module.mk +include src/main/ezusb-iidx/Module.mk +include src/main/ezusb-iidx-fpga-flash/Module.mk +include src/main/ezusb-iidx-sram-flash/Module.mk +include src/main/ezusb-tool/Module.mk +include src/main/ezusb2/Module.mk +include src/main/ezusb2-dbg-hook/Module.mk +include src/main/ezusb2-emu/Module.mk +include src/main/ezusb2-iidx/Module.mk +include src/main/ezusb2-iidx-emu/Module.mk +include src/main/ezusb2-tool/Module.mk +include src/main/ezusb-iidx-emu/Module.mk +include src/main/geninput/Module.mk +include src/main/hook/Module.mk +include src/main/hooklib/Module.mk +include src/main/iidx-ezusb-exit-hook/Module.mk +include src/main/iidx-ezusb2-exit-hook/Module.mk +include src/main/iidx-irbeat-patch/Module.mk +include src/main/iidxhook-util/Module.mk +include src/main/iidxhook1/Module.mk +include src/main/iidxhook2/Module.mk +include src/main/iidxhook3/Module.mk +include src/main/iidxhook4/Module.mk +include src/main/iidxhook5/Module.mk +include src/main/iidxhook6/Module.mk +include src/main/iidxhook7/Module.mk +include src/main/iidxhook8/Module.mk +include src/main/iidxio/Module.mk +include src/main/iidxio-ezusb/Module.mk +include src/main/iidxio-ezusb2/Module.mk +include src/main/iidxiotest/Module.mk +include src/main/inject/Module.mk +include src/main/jbio/Module.mk +include src/main/jbhook/Module.mk +include src/main/jbhook1/Module.mk +include src/main/launcher/Module.mk +include src/main/mempatch-hook/Module.mk +include src/main/mm/Module.mk +include src/main/p3io/Module.mk +include src/main/p3ioemu/Module.mk +include src/main/p4ioemu/Module.mk +include src/main/pcbidgen/Module.mk +include src/main/sdvxhook/Module.mk +include src/main/sdvxio/Module.mk +include src/main/security/Module.mk +include src/main/unicorntail/Module.mk +include src/main/util/Module.mk +include src/main/vefxio/Module.mk + +include src/test/cconfig/Module.mk +include src/test/iidxhook-util/Module.mk +include src/test/security/Module.mk +include src/test/test/Module.mk +include src/test/util/Module.mk + +# +# Distribution build rules +# + +zipdir := $(BUILDDIR)/zip + +$(zipdir)/: + $(V)mkdir -p $@ + +$(zipdir)/tools.zip: \ + build/bin/indep-32/aciotest.exe \ + build/bin/indep-32/eamiotest.exe \ + build/bin/indep-32/ezusb-iidx-fpga-flash.exe \ + build/bin/indep-32/ezusb-iidx-sram-flash.exe \ + build/bin/indep-32/iidxiotest.exe \ + build/bin/indep-32/iidx-ezusb-exit-hook.dll \ + build/bin/indep-32/iidx-ezusb2-exit-hook.dll \ + build/bin/indep-32/pcbidgen.exe \ + dist/iidx/ezusb-boot.bat \ + dist/iidx/ezusb2-boot.bat \ + build/bin/indep-32/mempatch-hook.dll \ + build/bin/indep-32/ezusb2-dbg-hook.dll \ + build/bin/indep-32/ezusb2-tool.exe \ + build/bin/indep-32/ezusb-tool.exe \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/tools-x64.zip: \ + build/bin/indep-64/eamiotest.exe \ + build/bin/indep-64/iidxiotest.exe \ + build/bin/indep-64/iidx-ezusb-exit-hook.dll \ + build/bin/indep-64/iidx-ezusb2-exit-hook.dll \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/src.zip: .git/HEAD $(zipdir)/ + $(V)echo ... $@ + $(V)git archive -o $@ HEAD + +$(zipdir)/iidx-09-to-12.zip: \ + build/bin/indep-32/iidxhook1.dll \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/eamio-icca.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/iidxio.dll \ + build/bin/indep-32/iidxio-ezusb.dll \ + build/bin/indep-32/iidxio-ezusb2.dll \ + build/bin/indep-32/vefxio.dll \ + build/bin/indep-32/inject.exe \ + dist/iidx/config.bat \ + dist/iidx/gamestart-09.bat \ + dist/iidx/gamestart-10.bat \ + dist/iidx/gamestart-11.bat \ + dist/iidx/gamestart-12.bat \ + dist/iidx/iidxhook-09.conf \ + dist/iidx/iidxhook-10.conf \ + dist/iidx/iidxhook-11.conf \ + dist/iidx/iidxhook-12.conf \ + dist/iidx/vefx.txt \ + build/bin/indep-32/iidx-irbeat-patch.exe \ + dist/iidx/iidx-irbeat-patch-09.bat \ + dist/iidx/iidx-irbeat-patch-10.bat \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/iidx-13.zip: \ + build/bin/avs2_0-32/iidxhook2.dll \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/eamio-icca.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/iidxio.dll \ + build/bin/indep-32/iidxio-ezusb.dll \ + build/bin/indep-32/iidxio-ezusb2.dll \ + build/bin/indep-32/vefxio.dll \ + build/bin/indep-32/inject.exe \ + dist/iidx/config.bat \ + dist/iidx/gamestart-13.bat \ + dist/iidx/iidxhook-13.conf \ + dist/iidx/vefx.txt \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/iidx-14-to-17.zip: \ + build/bin/avs2_0-32/iidxhook3.dll \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/eamio-icca.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/iidxio.dll \ + build/bin/indep-32/iidxio-ezusb.dll \ + build/bin/indep-32/iidxio-ezusb2.dll \ + build/bin/indep-32/vefxio.dll \ + build/bin/indep-32/inject.exe \ + dist/iidx/config.bat \ + dist/iidx/gamestart-14.bat \ + dist/iidx/gamestart-15.bat \ + dist/iidx/gamestart-16.bat \ + dist/iidx/gamestart-17.bat \ + dist/iidx/iidxhook-14.conf \ + dist/iidx/iidxhook-15.conf \ + dist/iidx/iidxhook-16.conf \ + dist/iidx/iidxhook-17.conf \ + dist/iidx/vefx.txt \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/iidx-18.zip: \ + build/bin/avs2_1101-32/iidxhook4.dll \ + build/bin/avs2_1101-32/launcher.exe \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/eamio-icca.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/iidxio.dll \ + build/bin/indep-32/iidxio-ezusb.dll \ + build/bin/indep-32/iidxio-ezusb2.dll \ + build/bin/indep-32/vefxio.dll \ + dist/iidx/config.bat \ + dist/iidx/gamestart-18.bat \ + dist/iidx/iidxhook-18.conf \ + dist/iidx/vefx.txt \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/iidx-19.zip: \ + build/bin/avs2_1304-32/iidxhook5.dll \ + build/bin/avs2_1304-32/launcher.exe \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/eamio-icca.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/iidxio.dll \ + build/bin/indep-32/iidxio-ezusb.dll \ + build/bin/indep-32/iidxio-ezusb2.dll \ + build/bin/indep-32/vefxio.dll \ + dist/iidx/config.bat \ + dist/iidx/gamestart-19.bat \ + dist/iidx/iidxhook-19.conf \ + dist/iidx/vefx.txt \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/iidx-20.zip: \ + build/bin/avs2_1508-32/iidxhook6.dll \ + build/bin/avs2_1508-32/launcher.exe \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/eamio-icca.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/iidxio.dll \ + build/bin/indep-32/iidxio-ezusb.dll \ + build/bin/indep-32/iidxio-ezusb2.dll \ + build/bin/indep-32/vefxio.dll \ + dist/iidx/config.bat \ + dist/iidx/gamestart-20.bat \ + dist/iidx/iidxhook-20.conf \ + dist/iidx/vefx.txt \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/iidx-21-to-24.zip: \ + build/bin/avs2_1601-32/iidxhook7.dll \ + build/bin/avs2_1601-32/launcher.exe \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/eamio-icca.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/iidxio.dll \ + build/bin/indep-32/iidxio-ezusb.dll \ + build/bin/indep-32/iidxio-ezusb2.dll \ + build/bin/indep-32/vefxio.dll \ + dist/iidx/config.bat \ + dist/iidx/gamestart-21.bat \ + dist/iidx/gamestart-22.bat \ + dist/iidx/gamestart-23.bat \ + dist/iidx/gamestart-24.bat \ + dist/iidx/iidxhook-21.conf \ + dist/iidx/iidxhook-22.conf \ + dist/iidx/iidxhook-23.conf \ + dist/iidx/iidxhook-24.conf \ + dist/iidx/vefx.txt \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/iidx-25.zip: \ + build/bin/avs2_1700-64/iidxhook8.dll \ + build/bin/avs2_1700-64/launcher.exe \ + build/bin/indep-64/config.exe \ + build/bin/indep-64/eamio.dll \ + build/bin/indep-64/eamio-icca.dll \ + build/bin/indep-64/geninput.dll \ + build/bin/indep-64/iidxio.dll \ + build/bin/indep-64/iidxio-ezusb.dll \ + build/bin/indep-64/iidxio-ezusb2.dll \ + build/bin/indep-64/vefxio.dll \ + dist/iidx/config.bat \ + dist/iidx/gamestart-25.bat \ + dist/iidx/iidxhook-25.conf \ + dist/iidx/vefx.txt \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/jb-01.zip: \ + build/bin/avs2_803-32/jbhook1.dll \ + build/bin/indep-32/inject.exe \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/jbio.dll \ + dist/jb/config.bat \ + dist/jb/gamestart-01.bat \ + dist/jb/jbhook-01.conf \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/jb-05-to-07.zip: \ + build/bin/avs2_1508-32/jbhook.dll \ + build/bin/avs2_1508-32/launcher.exe \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/jbio.dll \ + dist/jb/config.bat \ + dist/jb/gamestart.bat \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/jb-08.zip: \ + build/bin/avs2_1700-32/jbhook.dll \ + build/bin/avs2_1700-32/launcher.exe \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/jbio.dll \ + dist/jb/config.bat \ + dist/jb/gamestart.bat \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/sdvx.zip: \ + build/bin/avs2_1508-32/launcher.exe \ + build/bin/avs2_1508-32/sdvxhook.dll \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/geninput.dll \ + build/bin/indep-32/sdvxio.dll \ + dist/sdvx/config.bat \ + dist/sdvx/gamestart.bat \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/ddr-12-to-16.zip: \ + build/bin/avs2_1508-32/launcher.exe \ + build/bin/avs2_1508-32/ddrhook.dll \ + build/bin/avs2_1508-32/unicorntail.dll \ + build/bin/indep-32/config.exe \ + build/bin/indep-32/ddrio.dll \ + build/bin/indep-32/ddrio-mm.dll \ + build/bin/indep-32/ddrio-smx.dll \ + build/bin/indep-32/eamio.dll \ + build/bin/indep-32/geninput.dll \ + dist/ddr/config.bat \ + dist/ddr/gamestart-12.bat \ + dist/ddr/gamestart-13.bat \ + dist/ddr/gamestart-14.bat \ + dist/ddr/gamestart-15.bat \ + dist/ddr/gamestart-16.bat \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/bst.zip: \ + build/bin/avs2_1603-64/bsthook.dll \ + build/bin/avs2_1603-64/launcher.exe \ + build/bin/indep-64/bstio.dll \ + build/bin/indep-64/config.exe \ + build/bin/indep-64/eamio.dll \ + build/bin/indep-64/geninput.dll \ + dist/bst/config.bat \ + dist/bst/gamestart1.bat \ + dist/bst/gamestart2.bat \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/doc.zip: \ + doc/iidxhook \ + doc/jbhook \ + doc/tools \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -r $@ $^ + +$(BUILDDIR)/tests.zip: \ + build/bin/indep-32/iidxhook1.dll \ + build/bin/avs2_0-32/iidxhook2.dll \ + build/bin/indep-32/cconfig-test.exe \ + build/bin/indep-32/cconfig-util-test.exe \ + build/bin/indep-32/cconfig-cmd-test.exe \ + build/bin/indep-32/iidxhook-util-config-eamuse-test.exe \ + build/bin/indep-32/iidxhook-util-config-gfx-test.exe \ + build/bin/indep-32/iidxhook-util-config-misc-test.exe \ + build/bin/indep-32/iidxhook-util-config-sec-test.exe \ + build/bin/indep-32/security-id-test.exe \ + build/bin/indep-32/security-mcode-test.exe \ + build/bin/indep-32/security-rp-test.exe \ + build/bin/indep-32/security-rp2-test.exe \ + build/bin/indep-32/security-rp3-test.exe \ + build/bin/indep-32/security-util-test.exe \ + build/bin/indep-32/util-net-test.exe \ + dist/test/run-tests.sh \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(BUILDDIR)/bemanitools.zip: \ + $(zipdir)/bst.zip \ + $(zipdir)/ddr-12-to-16.zip \ + $(zipdir)/doc.zip \ + $(zipdir)/iidx-09-to-12.zip \ + $(zipdir)/iidx-13.zip \ + $(zipdir)/iidx-14-to-17.zip \ + $(zipdir)/iidx-18.zip \ + $(zipdir)/iidx-19.zip \ + $(zipdir)/iidx-20.zip \ + $(zipdir)/iidx-21-to-24.zip \ + $(zipdir)/iidx-25.zip \ + $(zipdir)/jb-01.zip \ + $(zipdir)/jb-05-to-07.zip \ + $(zipdir)/jb-08.zip \ + $(zipdir)/src.zip \ + $(zipdir)/sdvx.zip \ + $(zipdir)/tools.zip \ + $(zipdir)/tools-x64.zip \ + CHANGELOG.md \ + LICENSE \ + README.md \ + version \ + + $(V)echo ... $@ + $(V)zip -j $@ $^ + +all: $(BUILDDIR)/bemanitools.zip $(BUILDDIR)/tests.zip diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fd98d3 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# Bemanitools 5 +Version: 5.26
+[Release history](CHANGELOG.md) + +A collection of tools to run [various Bemani arcade games](#list-of-supported-games). + +Bemanitools 5 (BT5) is the successor to Bemanitools 4 which introduces a big code cleanup and support for newer games. +BT5 uses a cleaner approach than BT4 did; specifically, all input and lighting is handled by emulating the protocols +spoken by the real IO PCBs, instead of replacing chunks of game code like BT4. The benefits of this approach are a more +authentic gameplay experience, and easier support for a broader range of releases from each game series. + +# List of supported games +* BeatStream + * BeatStream (bst.zip) + * BeatStream アニムトライヴ (bst.zip) +* Dance Dance Revolution + * Dance Dance Revolution X2 (ddr-12-to-16.zip) + * Dance Dance Revolution X3 vs. 2ndMIX (ddr-12-to-16.zip) + * Dance Dance Revolution 2013 (ddr-12-to-16.zip) + * Dance Dance Revolution 2014 (ddr-12-to-16.zip) + * Dance Dance Revolution A (ddr-12-to-16.zip) +* Beatmania IIDX + * Beatmania IIDX 9th Style (iidx-09-to-12.zip) + * Beatmania IIDX 10th Style (iidx-09-to-12.zip) + * Beatmania IIDX 11 IIDX RED (iidx-09-to-12.zip) + * Beatmania IIDX 12 HAPPY SKY (iidx-09-to-12.zip) + * Beatmania IIDX 13 DistorteD (iidx-13.zip) + * Beatmania IIDX 14 GOLD (iidx-14-to-17.zip) + * Beatmania IIDX 15 DJ TROOPERS (iidx-14-to-17.zip) + * Beatmania IIDX 16 EMPRESS (iidx-14-to-17.zip) + * Beatmania IIDX 17 SIRIUS (iidx-14-to-17.zip) + * Beatmania IIDX 18 Resort Anthem (iidx-18.zip) + * Beatmania IIDX 19 Lincle (iidx-19.zip) + * Beatmania IIDX 20 Tricoro (iidx-20.zip) + * Beatmania IIDX 21 SPADA (iidx-21-to-24.zip) + * Beatmania IIDX 22 PENDUAL (iidx-21-to-24.zip) + * Beatmania IIDX 23 copula (iidx-21-to-24.zip) + * Beatmania IIDX 24 SINOBUZ (iidx-21-to-24.zip) + * Beatmania IIDX 25 CANNON BALLERS (iidx-25.zip) +* jubeat + * jubeat (experimental/buggy) (jb-01.zip) + * jubeat saucer (fulfill) (jb-05-to-07.zip) + * jubeat prop (jb-05-to-07.zip) + * jubeat Qubell (jb-05-to-07.zip) + * jubeat clan (jb-08.zip) +* SOUND VOLTEX + * SOUND VOLTEX BOOTH (sdvx.zip) + * SOUND VOLTEX II -infinite infection- (sdvx.zip) + * SOUND VOLTEX III GRAVITY WARS (sdvx.zip) + * SOUND VOLTEX IV HEAVENLY HAVEN (sdvx.zip) + +# Supported platforms +Our main platforms are currently Windows XP and Windows 7 which are also the target platforms on the original hardware +of those games. However, as it gets more difficult to get and maintain hardware comptible with Windows XP, this might +change in the future. Many games also run on very recent Windows 10 builds but bear with us that it's hard to keep up +with Windows updates breaking legacy software. + +# Distribution contents +Check the [list of supported games](#list-of-supported-games) to grab the right files for your game. BT5 also includes +a *tools* subpackage (tools.zip) as well as the full source code (src.zip). + +You will find *.md files in various sub-packages that give you further instructions for setup, usage, error information +or FAQ. We advice you to read them as your questions and concerns might already be answered by them. If not, let us +know if there is any information that you consider helpful or important to know and should be added. + +# Development +## API +Please refer to the [API documentation](doc/api.md). + +## Source Code +The source code is included with this distribution package (src.zip). Please refer to the +[development document](doc/development.md) for further details. + +## Bugs and TODOs +We have our own issue tracker for this. If you want to contribute or have any bugs to report, please reach out to us on +the various channels we are available on. Please help us by providing a detailed description of your concern including: +* The version of bemanitools you are using +* The games affected including version +* Log output of bemanitools and the game +* The APIs you have been using with bemanitools, e.g. iidxio-keyboard, eamio-keyboard. +* The OS version you are running this on +* Specs of your hardware including CPU, RAM, GPU +* A detailed description of your issue. Describe the symptoms and the steps to trigger and reproduce them. Videos and +screenshots might be helpful depending on the issue. + +## Contributions +Patches are welcome! Let us know if you have any contributions, e.g. bugfixes, and send us a patch file. Please read +our [development guidelines](doc/development.md) as they contain valuable information that your contribution meets our +standards. + +Once submitted, we will review your contribution and get back to you about any changes or when we merge them to our +upstream repository. Your changes, once approved, will be included in the next release. + +## Roadmap +No concrete roadmap or timeline exists. We want to continue adding support for new games as well as old games (some of +the old games supported by BT4 are not supported, yet). However, our time and workforce is limited. If you are +interested in contributing, please check the [contribution section](#contributions). + +# License +Source code license is the Unlicense; you are permitted to do with this as thou wilt. For details, please refer to the +[LICENSE file](LICENSE) included with the source code. + + + + + + diff --git a/dist/bst/config.bat b/dist/bst/config.bat new file mode 100644 index 0000000..202689d --- /dev/null +++ b/dist/bst/config.bat @@ -0,0 +1 @@ +@start config bst diff --git a/dist/bst/gamestart1.bat b/dist/bst/gamestart1.bat new file mode 100644 index 0000000..5a3a915 --- /dev/null +++ b/dist/bst/gamestart1.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K bsthook.dll -E prop/ea3-config-1.xml beatstream1.dll %* diff --git a/dist/bst/gamestart2.bat b/dist/bst/gamestart2.bat new file mode 100644 index 0000000..8b6f53f --- /dev/null +++ b/dist/bst/gamestart2.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K bsthook.dll -E prop/ea3-config-2.xml beatstream2.dll %* diff --git a/dist/ddr/config.bat b/dist/ddr/config.bat new file mode 100644 index 0000000..643331d --- /dev/null +++ b/dist/ddr/config.bat @@ -0,0 +1 @@ +@start config ddr diff --git a/dist/ddr/gamestart-12.bat b/dist/ddr/gamestart-12.bat new file mode 100644 index 0000000..334d131 --- /dev/null +++ b/dist/ddr/gamestart-12.bat @@ -0,0 +1,11 @@ +@echo off + +cd /d %~dp0 + +if not exist conf\nvram mkdir conf\nvram +if not exist conf\raw mkdir conf\raw + +regsvr32 /s k-clvsd.dll +regsvr32 /s xactengine2_10.dll + +.\launcher.exe -K .\ddrhook.dll .\ddr.dll %* diff --git a/dist/ddr/gamestart-13.bat b/dist/ddr/gamestart-13.bat new file mode 100644 index 0000000..334d131 --- /dev/null +++ b/dist/ddr/gamestart-13.bat @@ -0,0 +1,11 @@ +@echo off + +cd /d %~dp0 + +if not exist conf\nvram mkdir conf\nvram +if not exist conf\raw mkdir conf\raw + +regsvr32 /s k-clvsd.dll +regsvr32 /s xactengine2_10.dll + +.\launcher.exe -K .\ddrhook.dll .\ddr.dll %* diff --git a/dist/ddr/gamestart-14.bat b/dist/ddr/gamestart-14.bat new file mode 100644 index 0000000..ae834e3 --- /dev/null +++ b/dist/ddr/gamestart-14.bat @@ -0,0 +1,14 @@ +@echo off + +cd /d %~dp0 + +if not exist conf\nvram mkdir conf\nvram +if not exist conf\nvram\coin.xml copy prop\coin.xml conf\nvram\coin.xml +if not exist conf\nvram\eacoin.xml copy prop\eacoin.xml conf\nvram\eacoin.xml +if not exist conf\nvram\share-config.xml copy prop\share-config.xml conf\nvram\share-config.xml +if not exist conf\raw mkdir conf\raw + +regsvr32 /s k-clvsd.dll +regsvr32 /s xactengine2_10.dll + +.\launcher.exe -K .\ddrhook.dll .\mdxja_945.dll %* diff --git a/dist/ddr/gamestart-15.bat b/dist/ddr/gamestart-15.bat new file mode 100644 index 0000000..ae834e3 --- /dev/null +++ b/dist/ddr/gamestart-15.bat @@ -0,0 +1,14 @@ +@echo off + +cd /d %~dp0 + +if not exist conf\nvram mkdir conf\nvram +if not exist conf\nvram\coin.xml copy prop\coin.xml conf\nvram\coin.xml +if not exist conf\nvram\eacoin.xml copy prop\eacoin.xml conf\nvram\eacoin.xml +if not exist conf\nvram\share-config.xml copy prop\share-config.xml conf\nvram\share-config.xml +if not exist conf\raw mkdir conf\raw + +regsvr32 /s k-clvsd.dll +regsvr32 /s xactengine2_10.dll + +.\launcher.exe -K .\ddrhook.dll .\mdxja_945.dll %* diff --git a/dist/ddr/gamestart-16.bat b/dist/ddr/gamestart-16.bat new file mode 100644 index 0000000..2e2336c --- /dev/null +++ b/dist/ddr/gamestart-16.bat @@ -0,0 +1,15 @@ +@echo off + +cd /d %~dp0 + +if not exist conf\nvram mkdir conf\nvram +if not exist conf\nvram\ea3-config.xml copy prop\eamuse-config.xml conf\nvram\ea3-config.xml +if not exist conf\nvram\coin.xml copy prop\coin.xml conf\nvram\coin.xml +if not exist conf\nvram\eacoin.xml copy prop\eacoin.xml conf\nvram\eacoin.xml +if not exist conf\nvram\testmode-v.xml copy prop\testmode-v.xml conf\nvram\testmode-v.xml +if not exist conf\raw mkdir conf\raw + +regsvr32 /s com\k-clvsd.dll +regsvr32 /s com\xactengine2_10.dll + +.\launcher.exe -H 33554432 -K .\ddrhook.dll .\arkmdxp3.dll %* diff --git a/dist/iidx/config.bat b/dist/iidx/config.bat new file mode 100644 index 0000000..488210d --- /dev/null +++ b/dist/iidx/config.bat @@ -0,0 +1 @@ +@start config iidx diff --git a/dist/iidx/ezusb-boot.bat b/dist/iidx/ezusb-boot.bat new file mode 100644 index 0000000..a4f04c8 --- /dev/null +++ b/dist/iidx/ezusb-boot.bat @@ -0,0 +1,28 @@ +@echo off + +if "%2"=="" goto USAGE + +echo Flashing ezusb firmware (%1)... +ezusb-tool.exe flash %1 + +if %ERRORLEVEL% neq 0 ( + exit 1 +) + +:: Wait a moment for the ezusb to re-enumerate properly +ping 127.0.0.1 -n 8 > nul +echo Writing FPGA data (%2)... +ezusb-iidx-fpga-flash.exe v1 %2 + +if %ERRORLEVEL% neq 0 ( + exit 1 +) + +ping 127.0.0.1 -n 3 > nul + +goto END + +:USAGE + echo Usage: ezusb-boot.bat ^ ^ + +:END \ No newline at end of file diff --git a/dist/iidx/ezusb2-boot.bat b/dist/iidx/ezusb2-boot.bat new file mode 100755 index 0000000..9828491 --- /dev/null +++ b/dist/iidx/ezusb2-boot.bat @@ -0,0 +1,30 @@ +@echo off + +if "%1"=="" goto USAGE + +echo Scanning for ezusb2 board... + +:: Yeah, this is one ugly way to pipe stdout to a variable... +for /f %%i in ('ezusb2-tool.exe scan') do set EZUSBDEV=%%i + +if %ERRORLEVEL% neq 0 ( + echo Error, could not find a connected ezusb2 device + exit 1 +) + +echo Found ezusb2 device at path: "%EZUSBDEV%" + +echo Flashing ezusb2 firmware (%1)... +ezusb2-tool.exe flash "%EZUSBDEV%" %1 + +if %ERRORLEVEL% neq 0 ( + echo Error, flashing ezusb2 board + exit 1 +) + +goto END + +:USAGE + echo Usage: ezusb2-boot.bat ^ + +:END \ No newline at end of file diff --git a/dist/iidx/gamestart-09.bat b/dist/iidx/gamestart-09.bat new file mode 100755 index 0000000..0913f2f --- /dev/null +++ b/dist/iidx/gamestart-09.bat @@ -0,0 +1,6 @@ +@echo off + +cd /d %~dp0 + +inject iidxhook1.dll bm2dx.exe -D --config iidxhook-09.conf %* + diff --git a/dist/iidx/gamestart-10.bat b/dist/iidx/gamestart-10.bat new file mode 100755 index 0000000..37736ae --- /dev/null +++ b/dist/iidx/gamestart-10.bat @@ -0,0 +1,6 @@ +@echo off + +cd /d %~dp0 + +inject iidxhook1.dll bm2dx.exe -D --config iidxhook-10.conf %* + diff --git a/dist/iidx/gamestart-11.bat b/dist/iidx/gamestart-11.bat new file mode 100755 index 0000000..a4516f7 --- /dev/null +++ b/dist/iidx/gamestart-11.bat @@ -0,0 +1,6 @@ +@echo off + +cd /d %~dp0 + +inject iidxhook1.dll bm2dx.exe -D --config iidxhook-11.conf %* + diff --git a/dist/iidx/gamestart-12.bat b/dist/iidx/gamestart-12.bat new file mode 100755 index 0000000..91be7f3 --- /dev/null +++ b/dist/iidx/gamestart-12.bat @@ -0,0 +1,5 @@ +@echo off + +cd /d %~dp0 + +inject iidxhook1.dll bm2dx.exe -D --config iidxhook-12.conf %* diff --git a/dist/iidx/gamestart-13.bat b/dist/iidx/gamestart-13.bat new file mode 100755 index 0000000..199fc5a --- /dev/null +++ b/dist/iidx/gamestart-13.bat @@ -0,0 +1,15 @@ +@echo off + +cd /d %~dp0 + +if not exist d mkdir d +if not exist e mkdir e +if not exist f mkdir f + +if not exist e\avs_conf mkdir e\avs_conf +if not exist e\avs_conf\CONF mkdir e\avs_conf\CONF +if not exist e\avs_conf\CONF\NVRAM mkdir e\avs_conf\CONF\NVRAM +if not exist e\avs_conf\CONF\RAW mkdir e\avs_conf\CONF\RAW + +inject iidxhook2.dll bm2dx.exe -D --config iidxhook-13.conf %* + diff --git a/dist/iidx/gamestart-14.bat b/dist/iidx/gamestart-14.bat new file mode 100755 index 0000000..387ef66 --- /dev/null +++ b/dist/iidx/gamestart-14.bat @@ -0,0 +1,15 @@ +@echo off + +cd /d %~dp0 + +if not exist d mkdir d +if not exist e mkdir e +if not exist f mkdir f + +if not exist e\avs_conf mkdir e\avs_conf +if not exist e\avs_conf\CONF mkdir e\avs_conf\CONF +if not exist e\avs_conf\CONF\NVRAM mkdir e\avs_conf\CONF\NVRAM +if not exist e\avs_conf\CONF\RAW mkdir e\avs_conf\CONF\RAW + +inject iidxhook3.dll bm2dx.exe -D --config iidxhook-14.conf %* + diff --git a/dist/iidx/gamestart-15.bat b/dist/iidx/gamestart-15.bat new file mode 100755 index 0000000..7bac4ec --- /dev/null +++ b/dist/iidx/gamestart-15.bat @@ -0,0 +1,15 @@ +@echo off + +cd /d %~dp0 + +if not exist d mkdir d +if not exist e mkdir e +if not exist f mkdir f + +if not exist e\avs_conf mkdir e\avs_conf +if not exist e\avs_conf\CONF mkdir e\avs_conf\CONF +if not exist e\avs_conf\CONF\NVRAM mkdir e\avs_conf\CONF\NVRAM +if not exist e\avs_conf\CONF\RAW mkdir e\avs_conf\CONF\RAW + +inject iidxhook3.dll bm2dx.exe -D --config iidxhook-15.conf %* + diff --git a/dist/iidx/gamestart-16.bat b/dist/iidx/gamestart-16.bat new file mode 100755 index 0000000..9df8f27 --- /dev/null +++ b/dist/iidx/gamestart-16.bat @@ -0,0 +1,15 @@ +@echo off + +cd /d %~dp0 + +if not exist d mkdir d +if not exist e mkdir e +if not exist f mkdir f + +if not exist e\avs_conf mkdir e\avs_conf +if not exist e\avs_conf\CONF mkdir e\avs_conf\CONF +if not exist e\avs_conf\CONF\NVRAM mkdir e\avs_conf\CONF\NVRAM +if not exist e\avs_conf\CONF\RAW mkdir e\avs_conf\CONF\RAW + +inject iidxhook3.dll bm2dx.exe -D --config iidxhook-16.conf %* + diff --git a/dist/iidx/gamestart-17.bat b/dist/iidx/gamestart-17.bat new file mode 100755 index 0000000..027dbfd --- /dev/null +++ b/dist/iidx/gamestart-17.bat @@ -0,0 +1,15 @@ +@echo off + +cd /d %~dp0 + +if not exist d mkdir d +if not exist e mkdir e +if not exist f mkdir f + +if not exist e\avs_conf mkdir e\avs_conf +if not exist e\avs_conf\CONF mkdir e\avs_conf\CONF +if not exist e\avs_conf\CONF\NVRAM mkdir e\avs_conf\CONF\NVRAM +if not exist e\avs_conf\CONF\RAW mkdir e\avs_conf\CONF\RAW + +inject iidxhook3.dll bm2dx.exe -D --config iidxhook-17.conf %* + diff --git a/dist/iidx/gamestart-18.bat b/dist/iidx/gamestart-18.bat new file mode 100755 index 0000000..cce99b0 --- /dev/null +++ b/dist/iidx/gamestart-18.bat @@ -0,0 +1,14 @@ +@echo off + +cd /d %~dp0 + +if not exist d mkdir d +if not exist e mkdir e +if not exist f mkdir f + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K iidxhook4.dll bm2dx.dll --config iidxhook-18.conf %* diff --git a/dist/iidx/gamestart-19.bat b/dist/iidx/gamestart-19.bat new file mode 100755 index 0000000..8bfd580 --- /dev/null +++ b/dist/iidx/gamestart-19.bat @@ -0,0 +1,14 @@ +@echo off + +cd /d %~dp0 + +if not exist d mkdir d +if not exist e mkdir e +if not exist f mkdir f + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K iidxhook5.dll bm2dx.dll --config iidxhook-19.conf %* diff --git a/dist/iidx/gamestart-20.bat b/dist/iidx/gamestart-20.bat new file mode 100755 index 0000000..2dac80e --- /dev/null +++ b/dist/iidx/gamestart-20.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K iidxhook6.dll bm2dx.dll --config iidxhook-20.conf %* diff --git a/dist/iidx/gamestart-21.bat b/dist/iidx/gamestart-21.bat new file mode 100755 index 0000000..b79d641 --- /dev/null +++ b/dist/iidx/gamestart-21.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K iidxhook7.dll bm2dx.dll --config iidxhook-21.conf %* diff --git a/dist/iidx/gamestart-22.bat b/dist/iidx/gamestart-22.bat new file mode 100755 index 0000000..d10a945 --- /dev/null +++ b/dist/iidx/gamestart-22.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K iidxhook7.dll bm2dx.dll --config iidxhook-22.conf %* diff --git a/dist/iidx/gamestart-23.bat b/dist/iidx/gamestart-23.bat new file mode 100755 index 0000000..656e1fe --- /dev/null +++ b/dist/iidx/gamestart-23.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K iidxhook7.dll bm2dx.dll --config iidxhook-23.conf %* diff --git a/dist/iidx/gamestart-24.bat b/dist/iidx/gamestart-24.bat new file mode 100755 index 0000000..283020c --- /dev/null +++ b/dist/iidx/gamestart-24.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K iidxhook7.dll bm2dx.dll --config iidxhook-24.conf %* diff --git a/dist/iidx/gamestart-25.bat b/dist/iidx/gamestart-25.bat new file mode 100644 index 0000000..edff294 --- /dev/null +++ b/dist/iidx/gamestart-25.bat @@ -0,0 +1,16 @@ +@echo off +cd /d %~dp0 + +if not exist dev mkdir dev +if not exist dev\e mkdir dev\e +if not exist dev\g mkdir dev\g +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw +if not exist dev\raw\log mkdir dev\raw\log +if not exist dev\raw\fscache mkdir dev\raw\fscache + +for /R prop\defaults %%D in (*.*) do ( + if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram +) + +launcher -H 134217728 -K iidxhook8.dll bm2dx.dll --config iidxhook-25.conf %* diff --git a/dist/iidx/iidx-irbeat-patch-09.bat b/dist/iidx/iidx-irbeat-patch-09.bat new file mode 100755 index 0000000..859c968 --- /dev/null +++ b/dist/iidx/iidx-irbeat-patch-09.bat @@ -0,0 +1,12 @@ +@echo off + +IF "%1"=="" GOTO USAGE + +iidx-irbeat-patch.exe 9 %1 e\\settings.bin.0 +iidx-irbeat-patch.exe 9 %1 f\\settings.bin.1 +GOTO END + +:USAGE + ECHO "Usage: iidx-irbeat-patch.bat " + +:END diff --git a/dist/iidx/iidx-irbeat-patch-10.bat b/dist/iidx/iidx-irbeat-patch-10.bat new file mode 100755 index 0000000..0133ab3 --- /dev/null +++ b/dist/iidx/iidx-irbeat-patch-10.bat @@ -0,0 +1,12 @@ +@echo off + +IF "%1"=="" GOTO USAGE + +iidx-irbeat-patch.exe 10 %1 e\\settings.bin.0 +iidx-irbeat-patch.exe 10 %1 f\\settings.bin.1 +GOTO END + +:USAGE + ECHO "Usage: iidx-irbeat-patch.bat " + +:END diff --git a/dist/iidx/iidxhook-09.conf b/dist/iidx/iidxhook-09.conf new file mode 100755 index 0000000..eca0eee --- /dev/null +++ b/dist/iidx/iidxhook-09.conf @@ -0,0 +1,59 @@ +# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO) +eamuse.card_type=C02 + +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Disable operator clock setting system clock time +misc.disable_clock_set=false + +# Use d3d9 hooks instead of d3d8 to enable d3d9 hook features not available on d3d8 (e.g. upscaling). Requires d3d8to9 wrapper library to be used with this game. +misc.use_d3d9_hooks=false + +# Stub calls to rteffect.dll (10th to DistorteD) +misc.rteffect_stub=false + +# Security boot version (e.g. GEC02). +sec.boot_version=GEC02 + +# Security boot seeds for ezusb, format: X:X:X where X is a number of 0-9 (e.g. 0:0:0). +sec.boot_seeds=0:0:0 + +# Security black plug mcode id string (e.g. GQC02JAA). +sec.black_plug_mcode=GQC02JAA \ No newline at end of file diff --git a/dist/iidx/iidxhook-10.conf b/dist/iidx/iidxhook-10.conf new file mode 100755 index 0000000..2aebc02 --- /dev/null +++ b/dist/iidx/iidxhook-10.conf @@ -0,0 +1,59 @@ +# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO) +eamuse.card_type=D01 + +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Disable operator clock setting system clock time +misc.disable_clock_set=false + +# Use d3d9 hooks instead of d3d8 to enable d3d9 hook features not available on d3d8 (e.g. upscaling). Requires d3d8to9 wrapper library to be used with this game. +misc.use_d3d9_hooks=false + +# Stub calls to rteffect.dll (10th to DistorteD) +misc.rteffect_stub=false + +# Security boot version (e.g. GEC02). +sec.boot_version=GEC02 + +# Security boot seeds for ezusb, format: X:X:X where X is a number of 0-9 (e.g. 0:0:0). +sec.boot_seeds=0:1:1 + +# Security black plug mcode id string (e.g. GQC02JAA). +sec.black_plug_mcode=GQD01JAA \ No newline at end of file diff --git a/dist/iidx/iidxhook-11.conf b/dist/iidx/iidxhook-11.conf new file mode 100755 index 0000000..23b055c --- /dev/null +++ b/dist/iidx/iidxhook-11.conf @@ -0,0 +1,59 @@ +# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO) +eamuse.card_type=E11 + +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Disable operator clock setting system clock time +misc.disable_clock_set=false + +# Use d3d9 hooks instead of d3d8 to enable d3d9 hook features not available on d3d8 (e.g. upscaling). Requires d3d8to9 wrapper library to be used with this game. +misc.use_d3d9_hooks=false + +# Stub calls to rteffect.dll (10th to DistorteD) +misc.rteffect_stub=false + +# Security boot version (e.g. GEC02). +sec.boot_version=GEC02 + +# Security boot seeds for ezusb, format: X:X:X where X is a number of 0-9 (e.g. 0:0:0). +sec.boot_seeds=0:2:2 + +# Security black plug mcode id string (e.g. GQC02JAA). +sec.black_plug_mcode=GQE11JAA \ No newline at end of file diff --git a/dist/iidx/iidxhook-12.conf b/dist/iidx/iidxhook-12.conf new file mode 100755 index 0000000..f8d91b9 --- /dev/null +++ b/dist/iidx/iidxhook-12.conf @@ -0,0 +1,62 @@ +# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO) +eamuse.card_type=ECO + +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Fix broken 3D background on Happy Sky's music select (if appearing completely white) +misc.happy_sky_ms_bg_fix=false + +# Use d3d9 hooks instead of d3d8 to enable d3d9 hook features not available on d3d8 (e.g. upscaling). Requires d3d8to9 wrapper library to be used with this game. +misc.use_d3d9_hooks=false + +# Disable operator clock setting system clock time +misc.disable_clock_set=false + +# Stub calls to rteffect.dll (10th to DistorteD) +misc.rteffect_stub=false + +# Security boot version (e.g. GEC02). +sec.boot_version=GEC02 + +# Security boot seeds for ezusb, format: X:X:X where X is a number of 0-9 (e.g. 0:0:0). +sec.boot_seeds=0:3:3 + +# Security black plug mcode id string (e.g. GQC02JAA). +sec.black_plug_mcode=GQECOJAA \ No newline at end of file diff --git a/dist/iidx/iidxhook-13.conf b/dist/iidx/iidxhook-13.conf new file mode 100755 index 0000000..791aee5 --- /dev/null +++ b/dist/iidx/iidxhook-13.conf @@ -0,0 +1,62 @@ +# Magnetic card type, format XXX, 3 digit string (supports: C02, D01, E11, ECO) +eamuse.card_type=C02 + +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Fix broken 3D background on DistorteD's music select (if appearing completely black) +misc.distorted_ms_bg_fix=false + +# Use d3d9 hooks instead of d3d8 to enable d3d9 hook features not available on d3d8 (e.g. upscaling). Requires d3d8to9 wrapper library to be used with this game. +misc.use_d3d9_hooks=false + +# Disable operator clock setting system clock time +misc.disable_clock_set=false + +# Stub calls to rteffect.dll (10th to DistorteD) +misc.rteffect_stub=false + +# Security boot version (e.g. GEC02). +sec.boot_version=GEC02 + +# Security boot seeds for ezusb, format: X:X:X where X is a number of 0-9 (e.g. 0:0:0). +sec.boot_seeds=0:4:4 + +# Security black plug mcode id string (e.g. GQC02JAA). +sec.black_plug_mcode=GQFDDJAA \ No newline at end of file diff --git a/dist/iidx/iidxhook-14.conf b/dist/iidx/iidxhook-14.conf new file mode 100755 index 0000000..6a7d793 --- /dev/null +++ b/dist/iidx/iidxhook-14.conf @@ -0,0 +1,47 @@ +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Disable operator clock setting system clock time +misc.disable_clock_set=false + +# Security boot version (e.g. GEC02). +sec.boot_version=GQGLDJAA + +# Security black plug mcode id string (e.g. GQC02JAA). +sec.black_plug_mcode=GQGLDJA \ No newline at end of file diff --git a/dist/iidx/iidxhook-15.conf b/dist/iidx/iidxhook-15.conf new file mode 100755 index 0000000..023b2cf --- /dev/null +++ b/dist/iidx/iidxhook-15.conf @@ -0,0 +1,47 @@ +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Disable operator clock setting system clock time +misc.disable_clock_set=false + +# Security boot version (e.g. GEC02). +sec.boot_version=GQHDDJAA + +# Security black plug mcode id string (e.g. GQC02JAA). +sec.black_plug_mcode=GQHDDJA \ No newline at end of file diff --git a/dist/iidx/iidxhook-16.conf b/dist/iidx/iidxhook-16.conf new file mode 100755 index 0000000..cdfe866 --- /dev/null +++ b/dist/iidx/iidxhook-16.conf @@ -0,0 +1,50 @@ +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Disable operator clock setting system clock time +misc.disable_clock_set=false + +# Security boot version (e.g. GEC02). +sec.boot_version=GQI00JAA + +# Security black plug mcode id string (e.g. GQC02JAA). +sec.black_plug_mcode=GQI00JA \ No newline at end of file diff --git a/dist/iidx/iidxhook-17.conf b/dist/iidx/iidxhook-17.conf new file mode 100755 index 0000000..8e76d2a --- /dev/null +++ b/dist/iidx/iidxhook-17.conf @@ -0,0 +1,50 @@ +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Disable operator clock setting system clock time +misc.disable_clock_set=false + +# Security boot version (e.g. GEC02). +sec.boot_version=GCJDJJAA + +# Security black plug mcode id string (e.g. GQC02JAA). +sec.black_plug_mcode=GCJDJJA \ No newline at end of file diff --git a/dist/iidx/iidxhook-18.conf b/dist/iidx/iidxhook-18.conf new file mode 100755 index 0000000..0ddb19d --- /dev/null +++ b/dist/iidx/iidxhook-18.conf @@ -0,0 +1,32 @@ +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Enable/disable software monitor check/auto timebase or set a pre-determined refresh value. -1 disables this feature. 0 enables auto detecting the current refresh rate on startup. Setting any positive value > 0 allows you to set a pre-determined refresh rate (e.g. retrieved from the monitor check on newer IIDX games). Either the auto detected value or pre-determined value is used to patch any chart files in-memory to fix song synchronization issues. Requires constant refresh rate!!! +gfx.monitor_check=-1.000000 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none diff --git a/dist/iidx/iidxhook-19.conf b/dist/iidx/iidxhook-19.conf new file mode 100755 index 0000000..cd929cd --- /dev/null +++ b/dist/iidx/iidxhook-19.conf @@ -0,0 +1,29 @@ +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none diff --git a/dist/iidx/iidxhook-20.conf b/dist/iidx/iidxhook-20.conf new file mode 100755 index 0000000..cd929cd --- /dev/null +++ b/dist/iidx/iidxhook-20.conf @@ -0,0 +1,29 @@ +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none diff --git a/dist/iidx/iidxhook-21.conf b/dist/iidx/iidxhook-21.conf new file mode 100755 index 0000000..cd929cd --- /dev/null +++ b/dist/iidx/iidxhook-21.conf @@ -0,0 +1,29 @@ +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none diff --git a/dist/iidx/iidxhook-22.conf b/dist/iidx/iidxhook-22.conf new file mode 100755 index 0000000..cd929cd --- /dev/null +++ b/dist/iidx/iidxhook-22.conf @@ -0,0 +1,29 @@ +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none diff --git a/dist/iidx/iidxhook-23.conf b/dist/iidx/iidxhook-23.conf new file mode 100755 index 0000000..cd929cd --- /dev/null +++ b/dist/iidx/iidxhook-23.conf @@ -0,0 +1,29 @@ +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none diff --git a/dist/iidx/iidxhook-24.conf b/dist/iidx/iidxhook-24.conf new file mode 100755 index 0000000..cd929cd --- /dev/null +++ b/dist/iidx/iidxhook-24.conf @@ -0,0 +1,29 @@ +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none diff --git a/dist/iidx/iidxhook-25.conf b/dist/iidx/iidxhook-25.conf new file mode 100644 index 0000000..cbaa9fc --- /dev/null +++ b/dist/iidx/iidxhook-25.conf @@ -0,0 +1,47 @@ +# Fix stretched BG videos on newer GPUs. Might appear on Red and newer +gfx.bgvideo_uv_fix=false + +# Run the game in a framed window (requires windowed option) +gfx.framed=false + +# Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit) +gfx.frame_rate_limit=0.0 + +# Patch the GPU device ID detection (leave empty to disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid). Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE, Radeon HD3450) +gfx.pci_id=1002:7146 + +# Run the game windowed +gfx.windowed=false + +# Windowed width, -1 for default size +gfx.window_width=-1 + +# Windowed height, -1 for default size +gfx.window_height=-1 + +# Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding height parameter. +gfx.scale_back_buffer_width=0 + +# Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in combination with the corresponding width parameter. +gfx.scale_back_buffer_height=0 + +# Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE for explanation). +gfx.scale_back_buffer_filter=none + +# Disables the camera emulation +cam.disable_emu=false + +# Override camera device ID 1 detection (copy from device manager, do not escape) +cam.device_id1= + +# Override camera device ID 2 detection (copy from device manager, do not escape) +cam.device_id2= + +# Disable card reader emulation and enable usage of real card reader hardware on COM0 (for games supporting slotted readers) +io.disable_card_reader_emu=false + +# Disable BIO2 emulation and enable usage of real BIO2 hardware +io.disable_bio2_emu=false + +# Disables the poll limiter, warning very high CPU usage may arise +io.disable_poll_limiter=false \ No newline at end of file diff --git a/dist/iidx/vefx.txt b/dist/iidx/vefx.txt new file mode 100644 index 0000000..5302f77 --- /dev/null +++ b/dist/iidx/vefx.txt @@ -0,0 +1 @@ +-1 -1 -1 -1 -1 diff --git a/dist/jb/config.bat b/dist/jb/config.bat new file mode 100644 index 0000000..6ebe8ff --- /dev/null +++ b/dist/jb/config.bat @@ -0,0 +1 @@ +@start config jb diff --git a/dist/jb/gamestart-01.bat b/dist/jb/gamestart-01.bat new file mode 100644 index 0000000..60d1ca8 --- /dev/null +++ b/dist/jb/gamestart-01.bat @@ -0,0 +1,5 @@ +@echo off + +cd /d %~dp0 + +inject jbhook1.dll jubeat.exe --config jbhook-01.conf %* diff --git a/dist/jb/gamestart.bat b/dist/jb/gamestart.bat new file mode 100644 index 0000000..c25cf6e --- /dev/null +++ b/dist/jb/gamestart.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K jbhook.dll jubeat.dll diff --git a/dist/jb/jbhook-01.conf b/dist/jb/jbhook-01.conf new file mode 100755 index 0000000..4e26beb --- /dev/null +++ b/dist/jb/jbhook-01.conf @@ -0,0 +1,15 @@ +# Run the game windowed +gfx.windowed=false + +# URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 (e.g. 127.0.0.1:80) of the target eamuse server. The port is optional but defaults to 80. +eamuse.server=localhost:80 + +# PCBID +eamuse.pcbid=0101020304050607086F + +# EAMID +eamuse.eamid=0101020304050607086F + +# Mcode of the game to run. +security.mcode=GCH44JAB + diff --git a/dist/old/config-dm.bat b/dist/old/config-dm.bat new file mode 100644 index 0000000..b7c3862 --- /dev/null +++ b/dist/old/config-dm.bat @@ -0,0 +1 @@ +@start config dm diff --git a/dist/old/config-gf.bat b/dist/old/config-gf.bat new file mode 100644 index 0000000..266c055 --- /dev/null +++ b/dist/old/config-gf.bat @@ -0,0 +1 @@ +@start config gf diff --git a/dist/old/config-pnm.bat b/dist/old/config-pnm.bat new file mode 100755 index 0000000..b775440 --- /dev/null +++ b/dist/old/config-pnm.bat @@ -0,0 +1 @@ +@start config pnm diff --git a/dist/old/config-sdvx.bat b/dist/old/config-sdvx.bat new file mode 100644 index 0000000..2f3e17a --- /dev/null +++ b/dist/old/config-sdvx.bat @@ -0,0 +1 @@ +@start config sdvx diff --git a/dist/old/gamestart-JDXJAA.bat b/dist/old/gamestart-JDXJAA.bat new file mode 100755 index 0000000..a031ce6 --- /dev/null +++ b/dist/old/gamestart-JDXJAA.bat @@ -0,0 +1,8 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw + +launcher -k ddrhook.dll ddr.dll %* diff --git a/dist/old/gamestart-JDXJBA.bat b/dist/old/gamestart-JDXJBA.bat new file mode 100755 index 0000000..e97ecdd --- /dev/null +++ b/dist/old/gamestart-JDXJBA.bat @@ -0,0 +1,8 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw + +launcher -k ddrhook.dll ddr.dll -o %* diff --git a/dist/old/gamestart-JDZ.bat b/dist/old/gamestart-JDZ.bat new file mode 100755 index 0000000..a39e3f9 --- /dev/null +++ b/dist/old/gamestart-JDZ.bat @@ -0,0 +1,14 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw + +subst /D E: +subst /D F: + +subst E: dev\E +subst F: dev\F + +launcher -k iidxhook.dll bm2dx.dll -u -i 1002:7146 %* diff --git a/dist/old/gamestart-K39.bat b/dist/old/gamestart-K39.bat new file mode 100755 index 0000000..a0428fc --- /dev/null +++ b/dist/old/gamestart-K39.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw\bookkeeping mkdir dev\raw\bookkeeping +if not exist dev\raw\ranking mkdir dev\raw\ranking +if not exist dev\raw\settings mkdir dev\raw\settings + +launcher popn19.dll diff --git a/dist/old/gamestart-KDXJAA.bat b/dist/old/gamestart-KDXJAA.bat new file mode 100755 index 0000000..a031ce6 --- /dev/null +++ b/dist/old/gamestart-KDXJAA.bat @@ -0,0 +1,8 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw + +launcher -k ddrhook.dll ddr.dll %* diff --git a/dist/old/gamestart-KDXJBA.bat b/dist/old/gamestart-KDXJBA.bat new file mode 100755 index 0000000..e97ecdd --- /dev/null +++ b/dist/old/gamestart-KDXJBA.bat @@ -0,0 +1,8 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw + +launcher -k ddrhook.dll ddr.dll -o %* diff --git a/dist/old/gamestart-KDZ.bat b/dist/old/gamestart-KDZ.bat new file mode 100755 index 0000000..a39e3f9 --- /dev/null +++ b/dist/old/gamestart-KDZ.bat @@ -0,0 +1,14 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw + +subst /D E: +subst /D F: + +subst E: dev\E +subst F: dev\F + +launcher -k iidxhook.dll bm2dx.dll -u -i 1002:7146 %* diff --git a/dist/old/gamestart-KFC.bat b/dist/old/gamestart-KFC.bat new file mode 100644 index 0000000..a699482 --- /dev/null +++ b/dist/old/gamestart-KFC.bat @@ -0,0 +1 @@ +@launcher -k sdvxhook.dll soundvoltex.dll -w diff --git a/dist/old/gamestart-L39.bat b/dist/old/gamestart-L39.bat new file mode 100755 index 0000000..ef8fca6 --- /dev/null +++ b/dist/old/gamestart-L39.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw\bookkeeping mkdir dev\raw\bookkeeping +if not exist dev\raw\ranking mkdir dev\raw\ranking +if not exist dev\raw\settings mkdir dev\raw\settings + +launcher popn20.dll diff --git a/dist/old/gamestart-L44.bat b/dist/old/gamestart-L44.bat new file mode 100755 index 0000000..6fb8b1d --- /dev/null +++ b/dist/old/gamestart-L44.bat @@ -0,0 +1,8 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw + +launcher jubeat.dll \ No newline at end of file diff --git a/dist/old/gamestart-M39.bat b/dist/old/gamestart-M39.bat new file mode 100755 index 0000000..fefb36c --- /dev/null +++ b/dist/old/gamestart-M39.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw\bookkeeping mkdir dev\raw\bookkeeping +if not exist dev\raw\ranking mkdir dev\raw\ranking +if not exist dev\raw\settings mkdir dev\raw\settings + +launcher popn21.dll diff --git a/dist/old/gamestart-dm.bat b/dist/old/gamestart-dm.bat new file mode 100644 index 0000000..9310ff6 --- /dev/null +++ b/dist/old/gamestart-dm.bat @@ -0,0 +1,3 @@ +@echo off + +for %%x in (gdv?.exe) do start %%x -d diff --git a/dist/old/gamestart-gf.bat b/dist/old/gamestart-gf.bat new file mode 100644 index 0000000..81f3a09 --- /dev/null +++ b/dist/old/gamestart-gf.bat @@ -0,0 +1,3 @@ +@echo off + +for %%x in (gdv?.exe) do start %%x -g diff --git a/dist/old/gamestart-ju.bat b/dist/old/gamestart-ju.bat new file mode 100644 index 0000000..2749750 --- /dev/null +++ b/dist/old/gamestart-ju.bat @@ -0,0 +1,8 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw + +launcher jubeat.dll diff --git a/dist/old/gamestart-refl.bat b/dist/old/gamestart-refl.bat new file mode 100644 index 0000000..175f37b --- /dev/null +++ b/dist/old/gamestart-refl.bat @@ -0,0 +1,8 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\raw mkdir dev\raw + +launcher reflecbeat.dll \ No newline at end of file diff --git a/dist/sdvx/config.bat b/dist/sdvx/config.bat new file mode 100644 index 0000000..2f3e17a --- /dev/null +++ b/dist/sdvx/config.bat @@ -0,0 +1 @@ +@start config sdvx diff --git a/dist/sdvx/gamestart.bat b/dist/sdvx/gamestart.bat new file mode 100644 index 0000000..a286325 --- /dev/null +++ b/dist/sdvx/gamestart.bat @@ -0,0 +1,10 @@ +@echo off + +cd /d %~dp0 + +if not exist dev\nvram mkdir dev\nvram +if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml +if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml +if not exist dev\raw mkdir dev\raw + +launcher -K sdvxhook.dll soundvoltex.dll %* diff --git a/dist/test/run-tests.sh b/dist/test/run-tests.sh new file mode 100644 index 0000000..8628a4f --- /dev/null +++ b/dist/test/run-tests.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +set -e + +cd $DIR + +echo "Running tests..." + +wine ./cconfig-test.exe +wine ./cconfig-util-test.exe +wine ./cconfig-cmd-test.exe +wine ./iidxhook-util-config-eamuse-test.exe +wine ./iidxhook-util-config-gfx-test.exe +# wine ./iidxhook-config-iidxhook1-test.exe +# wine ./iidxhook-config-iidxhook2-test.exe +wine ./iidxhook-util-config-misc-test.exe +wine ./iidxhook-util-config-sec-test.exe +wine ./security-id-test.exe +wine ./security-mcode-test.exe +wine ./security-util-test.exe +wine ./security-rp-test.exe +wine ./security-rp2-test.exe +wine ./security-rp3-test.exe +wine ./util-net-test.exe + +echo "All tests successful." \ No newline at end of file diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 0000000..0af6370 --- /dev/null +++ b/doc/api.md @@ -0,0 +1,43 @@ +# Bemanitools API +Bemanitools introduces interfaces abstracting the IO hardware of many games. This is used to implement support for +non-intended IO devices from simple keyboard support, standard gamecontrollers to custom IO boards or using real +hardware with the games (e.g. support for real legacy hardware). + +For a list of already supported and included hardware by game, see the next section. + +The BT5 API separates main game IO hardware like buttons, turn tables, spinners, lights etc. (bstio, iidxio, ...) from +eamuse hardware like 10-key pads and card readers (eamio). + +If you want to write an implementation for your own custom piece of hardware, check out the SDK (*bemanitools* +sub-folder) in the source code (src.zip). + +## Implementations +The following implementations are already shipped with BT5. + +* BeatStream + * bstio.dll (default): Keyboard, joystick and mouse input +* Dance Dance Revolution + * ddrio.dll (default): Keyboard, joystick and mouse input + * ddrio-mm.dll: Minimaid hardware + * ddrio-smx.dll: StepManiaX platforms +* Beatmania IIDX + * iidxio.dll (default): Keyboard, joystick and mouse input + * iidxio-ezusb.dll: Ezusb (C02 IO) driver + * iidxio-ezusb2.dll: Ezusb FX2 (IO2) driver +* jubeat + * jbio.dll (default): Keyboard, joystick and mouse input +* SOUND VOLTEX + * sdvxio.dll (default): Keyboard, joystick and mouse input + +Eamuse hardware support is implemented separately: +* eamio.dll (default): Keyboard and joystick input + +## Development notes +A DEF file for geninput.dll is included. To convert the DEF into an import library suitable for use with Visual C++, run +``` +lib /machine:i386 /def:geninput.def +``` +from the Visual C++ command line. If you're using mingw then use dlltool: +``` +dlltool -d geninput.def -l geninput.a +``` \ No newline at end of file diff --git a/doc/dev/c02-driver-on-w7-crash.md b/doc/dev/c02-driver-on-w7-crash.md new file mode 100644 index 0000000..e8c0e87 --- /dev/null +++ b/doc/dev/c02-driver-on-w7-crash.md @@ -0,0 +1,103 @@ +# Follow-up (14th August 2019) +After publishing this post-mortem, I got messaged by a user on sows who was able to shed some more light on this issue. +The user was experiencing the same symptoms on a Win7 setup: blue screen once the firmware was flashed to the C02 IO. +This user's solutions was to use the USB2 ports on the PC instead of the USB3 ones. This is good to know and kinda +aligns with the weird things happening in the driver (see below). + +# Post-mortem: C02 IO kernel module crash on Windows 7 (29th July 2019) by icex2 +## Background +The original ezusbsys.sys kernel module, which is required to run the C02 IO, was compiled for Windows XP 32-bit, only. +There is a newer driver by Cypress, cyusb3.sys, which could be used to IO2 boards on newer Windows platforms, but does +not work with the C02 IO in combination with Konami's propriatery firmware. Thus, it was not possible to run the C02 IO +on anything than Windows XP 32-bit. But, with newer IIDX games running on Windows 7 64-bit, the C02 IO wasn't usable +anymore. Leaving aside, that the newer games actually require a BIO2 board and do not support C02 nor IO2 boards +anymore. + +## The goal +I still wanted to use my cabinet with a C02 board on newer games which is possible with BT5 adding an emulation layer +and an interface (iidxio). This IO interface can be used to implement a driver that talks to a real IO again. Thus, +implementing a ezusb iidxio driver library, we can run newer games with an C02 IO as well. + +However, there was no ezusbsys.sys driver that works on newer platforms required to run the newer games. But, Cypress +was nice and included the source code of the ezusbsys kernel module. With a few tweaks and a very recent version of +visual studio, it was quite easy to build this driver for newer platforms, including Windows 7, 8 and 10 in both +32-bit and 64-bit variants. + +## The problem +But, when using this driver on certain combinations of newer hardware (max. 1-2 years old) and Windows 7, the kernel +module might crash after the Konami C02 firmware got flashed to the ezusb board. The result was a bluescreen and reboot. + +However, the hardware was fine and the kernel module worked fine on another piece of hardware, the stock PC that was +used with iidx 20 to 24. However, this hardware is not powerful enough to run iidx 25 and newer without stuttering +issues. + +## The analysis/debugging +Note: The full source code can be found in the bemanitools-supplement package. + +Setup: +* Native hardware with Windows 7 that was crashing +* Vmware with Windows 10 and Visual Studio 2019 to compile the kernel module. Target platform Windows 7 64-bit +* Booting Windows 7 in test mode to allow unsigned kernel modules to run and with debug output turned on +* dbgview on Windows 7 machine to get local kernel dbg output + +Because I wanted to stick to Windows 7 in the beginning (refer to the solution section), I started debugging the kernel +module by enabling the debug message output that was already available in the code. However, since kernel debug message +printing can be very delayed, the kernel could not print various messages before the kernel crashed. + +Thus, I started stripping the kernel module step by step to narrow down the possible spots causing the crash. After a +few hours, I got the (first) issue tracked down: + +After the firmware was flashed, the device had to re-enumerate. When this happens, the function *Ezusb_PnPAddDevice* +is called to create a new instance of the device. Since this kernel module is acting as a filter driver, it has to +trap this call, and add a filter device before the real device in the device stack. Thus, each call to the ezusb device +hits the filter device first and the filter device calls the real device after doing some magic. + +*Ezusb_PnPAddDevice* calls *Ezusb_CreateDeviceObject*. Afterwards, it checks the status of the call to +*Ezusb_CreateDeviceObject* and if successful, it tries attaching the device to the device stack. However, instead of +using *IoAttachDeviceToDeviceStackSafe* it uses the unsafe variant *IoAttachDeviceToDeviceStack* which can lead to a +race condition on newer Windows Systems. Furthermore, all initialization of further variables of the *deviceObject* +needs to happen BEFORE doing that. Again, this is a race condition. + +Next issue: Once the kernel calls *Ezusb_StartDevice* -> *Ezusb_ConfigureDevice* -> *Ezusb_SelectInterfaces*, it tries +to use *USBD_ParseConfigurationDescriptorEx* to get the interface from the configuration descriptor. However, that +fails for some unknown reason. I checked the data structure and it is perfectly fine and everything is there. Thus, +I wrote my own version *Ezusb_GetInterfaceFromConfigurationDescriptor* which does all the magic required to get this +part fixed: +``` +PUSB_INTERFACE_DESCRIPTOR Ezusb_GetInterfaceFromConfigurationDescriptor( + IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor + ) +{ + if (!ConfigurationDescriptor) { + Ezusb_KdPrint(("ERROR Ezusb_GetInterfaceFromConfigurationDescriptor NULL configuration desc")); + return NULL; + } + + if (ConfigurationDescriptor->wTotalLength < sizeof(USB_CONFIGURATION_DESCRIPTOR) + sizeof(USB_CONFIGURATION_DESCRIPTOR)) { + Ezusb_KdPrint(("ERROR Ezusb_GetInterfaceFromConfigurationDescriptor configuration descriptor too small to have space for interface descriptor")); + return NULL; + } + + // hardcoding this to a single interface because we only care about the ezusb used with IIDX (C02 IO) + if (ConfigurationDescriptor->bNumInterfaces < 1) { + Ezusb_KdPrint(("ERROR Ezusb_GetInterfaceFromConfigurationDescriptor num interfaces 0")); + return NULL; + } + + // when retrieving the configuration descriptor from the usb device, the interface is located right next to it + return (PUSB_INTERFACE_DESCRIPTOR) (((unsigned char*) ConfigurationDescriptor) + sizeof(USB_CONFIGURATION_DESCRIPTOR)); +} +``` + +And next issue is just up ahead: Following the above, we have to call *Ezusb_USBD_CreateConfigurationRequestEx* to +create a USB configuration request to set the interface we want to use. This is executed with a *Ezusb_CallUSBD* call +which sends request to the real hardware. However, this request always fails. The call *IoCallDriver* inside +*Ezusb_CallUSBD* always returns an NTSTATUS code that is not documented anywhere (can't find the exact status code +anymore, but once you get it, try to find it in the header file). + +At this point, I had to give up. I already wasted too many hours and this is clearly a dead end. + +## The solution +Once I realized that I got stuck with Windows 7 and I didn't want to buy (more) new hardware, I gave Windows 10 a try. +Surprisingly, this solved all the issues and the kernel module runs fine. The C02 board is flashable without crashing +and works with newer IIDX games. \ No newline at end of file diff --git a/doc/dev/device-list.md b/doc/dev/device-list.md new file mode 100644 index 0000000..2cfb921 --- /dev/null +++ b/doc/dev/device-list.md @@ -0,0 +1,26 @@ +# H44B + +(RGB) LED output board for jubeat cabinets. + +# ICCA + +Separate boxed unit containing with one card reader slot and pin key pad + +- BeatmaniaIIDX DistorteD to Lincle: grey slotted readers hanging below the +side speakers next to the monitor +- DDR SN 1/2: slotted readers red/black supernova cover mounted to the side of +the cabinet next to the monitor (left and right) + +# ICCB + +Single card reader slot (no separate pin pad) built into the cabinet. +Pin entry using game controls. + +- (First gen) Jubeat cabinets before replaced with wave pass readers + +# ICCC + +Single card reader wave pass unit without separate pin pad. Pin entry using +game controls. Still supported by newer versions. + +- (Second gen) Jubeat cabinets with wave pass readers \ No newline at end of file diff --git a/doc/dev/logging-breakdown-avs.md b/doc/dev/logging-breakdown-avs.md new file mode 100644 index 0000000..ed4ea7a --- /dev/null +++ b/doc/dev/logging-breakdown-avs.md @@ -0,0 +1,76 @@ +Copy/pasted from chat with tau (2018/02/10): + +alright then. so for modern iidx. + +we want to do logging inside iidxhook and we also want to pass AVS-style log functions to iidxio which in turn passes them on to geninput in order for those to do logging too + +so iidxhook connects to the AVS log API: log_body_misc and friends, which I assume are invoked using a log_misc() macro in Konami's source code that adds some sort of module tag. + +anyway yeah this we already know. + +libutil has four function ptrs: log_impl_misc and co. These are static variables which are statically initialized to some no-op functions. Except log_impl_fatal, whose implementation just calls libc abort() + +at startup you call log_to_external(), supplying four function ptrs to wire these up to. As the name suggests, this causes Bemanitools libutil to talk to something that is compatible with the AVS log sink API. + +alternatively you can log_to_writer(), which initializes Bemanitools to use its own, internal logging system, and you give it a log writer function that takes strings and writes them somewhere. + +So you have log sinks and you have log writers. The path is [application code] -> [log sink] -> [logging engine] -> [log writer] +19:29 + +inside config.exe (or generally outside of modern AVS games) this path looks like [bemanitools application code] -> [log sinks passed across dlls] -> [bemanitools logging engine] -> [bemanitools log writer] + +inside modern AVS game the path looks like [bt hook dll / bt iodev dll] -> [avs log_body_whatever log sinks] -> [avs logging engine] -> [launcher.exe log writer] + +note that I tried to keep the log writer API consistent with the AVS log writer API but then Konami went and broke it repeatedly so now Bemanitools has its own stable log writer API. Launcher tracks the AVS log writer API, which breaks constantly, so that's not the same thing. + +anyway that's the background story. Now for the details about IIDX in particular. + +up until about iidx19 we did things the obvious way: iidxhook would log_to_external() to hook into the AVS log sinks and then call those directly and all was well. Then one fine day I was given a IIDX19 data dump and tried running iidxhook and it crashed with a stack overflow. hmm. + +the problem boils down to this: iidx19 AVS added those log timestamps. And for for whatever reason the AVS logging engine needs to access some mutexes and condition variables to make this work properly + +but AVS of course in grand Konami tradition has its own threading and concurrency primitive API which wraps the Win32 API. tbf this is kind of understandable in some sense, because win32 actually did not have condition variables until Windows Vista! in 2006! seriously, I'm not kidding. + +there's all sorts of articles out there describing in fine detail how to use Win32's event objects to implement your own condition variables and the multitudinous pitfalls that this entails + +but anyway one fun thing about the AVS concurrency API is that you can't actually use concurrency primitives unless you're calling that API from a thread launched using the AVS threading API + +and that's a problem in the case of iidxhook, because iidx is old as balls relatively speaking and its EZUSB driver code has I think two worker threads, which it launches using the MS libc's _beginthreadex() function + +this in turn is a wrapper around the win32 CreateThread function, but it also boots up stdio on whatever new thread gets launched and basically is responsible for guaranteeing that the libc will operate correctly on the newly launched thread + +so yeah when you do windows programming, never call CreateThread, always call _beginthreadex. otherwise stuff will break. maybe. + +point is, IIDX predates modern AVS so it just uses Windows threading directly. So, IIDX worker thread starts up, iidxhook does its thing, writes a log message, calls into AVS logging, which in turn grabs an AVS mutex, the implementation of which says "omg this isn't an AVS thread aaaaaa" and ... attempts to call back into the logging system to log this fact. whereupon a stack overflow condition proceeds in a predictable manner. + +so, there are a few ways to deal with this problem. bemanitools 4 dealt with it in a fairly stupid way. + +and very elaborate way too + +bt4 intercepted IIDX's calls to create Windows threads and then redirected those calls to go via the AVS threading API + +so now the worker threads are AVS threads and logging works as expected + +which is all well and good but the problem is that these threads are quite timing critical. it probably worked fine, but i didn't want to risk affecting those threads in a weird way and introducing latency and jitter. i wanted to keep the threading pristine and not mess with it just for the sake of diagnostic messages + +so bemanitools 5 uses the log server approach + +at an appropriate time, it creates its own AVS thread, the logging server. Since it is an AVS thread, it can call the AVS logging API + +and then we have log_post_misc() and friends, implemented in log-server.c + +we initialize the Bemanitools logging system to log "externally" to those funcs and we also propagate those to iidxio.dll and eamio.dll which in turn pass them to geninput.dll + +so what do log_post_misc and friends do + +they lock a "mailbox" using the win32 concurrency primitives and write the log severity and a pointer to the string to be logged into the mailbox, then signal the log server thread, again using win32 concurrency primitives + +then they do a synchronous wait for an acknowledgement from the logging server: since we're holding a string pointer, we cannot return until that string pointer has been consumed or it may be concurrently invalidated + +so the log server wakes up, locks the mailbox, calls AVS log_body_misc() to write the log message, then once that returns it asserts a signal in the mailbox (again, win32 event object because lol what are condition variables) and releases the lock. + +the caller gets the signal, wakes up, and returns to whatever Bemanitools code is running on the IIDX IO worker thread that wanted to write a log message. + +end of essay. + + diff --git a/doc/development.md b/doc/development.md new file mode 100644 index 0000000..781c4be --- /dev/null +++ b/doc/development.md @@ -0,0 +1,316 @@ +# Development +This document is intended for developers interested in contributing to Bemanitools. Please read this document before +you start developing. + +## Goals +We want you to understand what this project is about and its goals. The following list serves as a guidance for all +developers to identify valuable contributions for this project. As the project evolves, these gaols might do as well. + +* Allow running Konami arcade rhythm games, i.e. games of the Bemani series, on arbitrary hardware. + * Emulate required software and hardware features. + * Provide means to cope with incompatibility issues resulting from using a different software platform (e.g. version + of Windows). +* Provide an API for custom interfaces and configuring fundamental application features. + +## Development environment +The following tooling is required in order to build this project. + +### Tooling +#### Linux / MacOSX +* git +* make +* mingw-w64 +* clang-format +* wine (optional, for running tests or some quick testing without requiring a VM) + +On MacOSX, you can use homebrew or macports to install these packages. + +#### Windows +TODO + +### IDE +Ultimately, you are free to use whatever you feel comfortable with for development. The following is our preferred +development environment which we run on a Linux distribution of our choice: +* Visual Studio Code with the following extensions + * C/C++ + * C++ Intellisense + * Clang-Format + +## Building +Simply run make in the root folder: +``` +make +``` + +All output is located in the *build* folder including the final *bemanitools.zip* package. + +## Testing +This still needs to be improved/implemented properly to run the unit-tests easily. Currently, you have to be on either +a Linux/MacOSX system to run *run-test-wine.sh* from the root folder. This executes all currently available unit-tests +and reports to the terminal any errors. This requires wine to be installed. + +## Project structure +Now that your setup is ready to go, here is brief big picture of what you find in this project. + +* build: This folder will be generated once you have run the build process. +* dist: Distribution related files such as (default) configuration files, shell scripts, etc. +* doc: Documentation for the tools and hooks as well as some development related docs. +* src: The source code + * imports: Provides headers and import definitions for AVS libs and a few other dependencies. + * main: The main source code with game specific hook libraries, hardware emulation and application fixes. + * test: Unit tests for modules that are not required for hooking or the presence of a piece of hardware. +* .clang-format: Code style for clang-format. +* GNUmakefile: Our makefile to build this project. +* Module.mk: Defines the various libraries and exe files to build for the distribution packages. + +## Code style and guidelines +Please follow these guidelines to keep a consistent style for the code base. Furthermore, we provide some best practices +that have shown to be helpful. Please read them and reach out to us if you have any concerns or valuable +additions/changes. + +### Clang-format +The style we agreed on is provided as a clang-format file. Therefore, we use clang-format for autoformatting our code. + +You can use clang-format from your terminal but when using Visual Studio Code, just install the extension. Apply +formatting manually at the end or enable the "reformat on save" feature. + +However, clang-format cannot provide guidance to cover all our style rules. Therefore, we ask you to stick to the +"additional" guidelines in the following sections. + +### Additional code style guidelines +#### No trailing comments +``` +// NOPE +int var = 1; // this is a variable + +// OK +// this is a variable +int var = 1; +``` + +#### Comment style +* Use either // or /* ... */ for single line. +* Use /* ... */ for multiline comments. +* Use /* ... */ for documentation. + +Examples: +``` +// single line comment +int var = 1; + +/* another single line comment */ +int var2 = 2; + +/* multi + line + comment */ + +/** + * This is a function. + */ +int func(int a, int b); +``` + +#### Include guards +Provide include guards for every header file. The naming follows the namespacing of the module and the module name. + +Example for bsthook/acio.h file: +``` +#ifndef BSTHOOK_ACIO_H +#define BSTHOOK_ACIO_H + +// ... + +#endif +``` + +#### Empty line before and after control blocks +Control blocks include: if, if-else, for, while, do-while, switch + +Makes the code more readible with control blocks being easily visible. + +Example +``` +int var = 1; + +if (var == 2) { + // ... +} + +printf("%d\n", var); +``` + +#### Includes +* Always keep all includes at the top of a header and source file. Exception: Documentation before include guards on +header file. +* Use *< >* for system-based includes, e.g. . +* Use *" "* for project-based includes, e.g. "util/log.h". +* For project-based includes, always use the full path relative to the root folder (src/main, src/test), e.g. +"util/log.h" and not "log.h" when being in another module in the "util" namespace. +* Sorting + * System-based includes before project-based includes + * Block group them by different namespaces + * Lex sort block groups + * Because windows header files are a mess, the sorting on system-based includes is not always applicable. Please add + a comment when applicable and apply the necessary order. + +Example for sorting +``` +#include +#include +#include + +#include "iidxhook-util/acio.h" +#include "iidxhook-util/d3d9.h" + +#include "util/log.h" +#include "util/mem.h" +``` + +### Documentation +In general, add comments where required to explain a certain piece of code. If is not self-explanatory about: +* Why is it implemented like this +* Very important details to understand how and why it is working that only you know +* A complex algorithm/logic + +Make sure to add some comments or even an extended document. Avoid comments that explain trivial and obvious things +like "enable feature X" before an if-block with a feature switch. + +Especially if it comes to reverse-engineering efforts, comments or even a separate document is crucial to allow others +to understand the depths you dived into. + +Any extended notes about documentation some hardware, protocol, reverse-engineering a feature etc. can be stored in +the *doc/dev* folder and stay with the repository. Make good use of that! + +#### Header files +Document any enum, struct or function exposed by a header file. Documentation of static functions or variables in +source modules is not required. Also provide documentation for the module. + +Example for my-namespace/my-module.h +``` +/** + * Some example module to show you where documentation is expected. + */ +#ifndef MY_NAMESPACE_MY_MODULE_H +#define MY_NAMESPACE_MY_MODULE_H + +/** + * Very useful enum for things. + */ +enum my_enum { + MY_NAMESPACE_MY_MODULE_MY_ENUM_VAL_1 = 1, + MY_NAMESPACE_MY_MODULE_MY_ENUM_VAL_2 = 2, +} + +/** + * Some cool data structure. + */ +struct my_struct { + int a; + float b; +}; + +/** + * This is my awesome function doing great things. + * + * Here are some details about it: + * - Detail 1 + * - Detail 2 + * + * @param a If > 0, something happens. + * @param b Only positive values valid, makes sure fancy things happen. + * @return Result of the computation X which is always > 0. -1 on error. + */ +int my_namespace_my_module_func(int a, int b); + +#endif +``` + +### Naming conventions +In general, try to keep names short but don't overdo it by using abbrevations of things you created. Sometimes this is +not possible and we accept exceptions if there are no proper alternatives. + +#### Namespacing +The folder names use lower-case names with dashes *-* as seperators, e.g. *my-namespace*. + +#### Modules +Header and source files of modules use lower-case names with dashes *-* as seperators, e.g. *my-module.c*, *my-module.h. + +The include guards contain the name of the namespace and module, see [here](#### Include guards). + +Variables, functions, structs, enums and macros are namespace accordingly. + +#### Variables +Snake-case with proper namespacing to namespace and module. Namespacing applies to static and global variables. Local +variables are not namespaced. +``` +// For namespace "ezusb", module "device", static variable in module +static HANDLE ezusb_device_handle; + +// Local variable in some function +int buffer_size = 256; +``` + +#### Functions +Snake-case with proper namespacing to namespace and module for all functions that are not hook functions. +``` +// For namespace "ezusb", module "device", static variable in module, init function +void ezusb_device_init(...); + +// CreateFileA hook function inside module +HANDLE my_CreateFileA(...) +{ + // ... +} +``` + +#### Structs +Snake-case with proper namespacing to namespace and module. +``` +// For namespace "ezusb", module "device" ctx struct +struct ezusb_device_ctx { + // ... +} +``` + +#### Enums +Snake-case with proper namespacing to namespace and module. Upper-case for enum entries +``` +// For namespace "ezusb", module "device" state enum +struct EZUSB_DEVICE_STATE { + EZUSB_DEVICE_STATE_INIT = 0, + EZUSB_DEVICE_STATE_RUNNING = 1, + // ... +} +``` + +#### Macros +Upper-case with underscore as spacing, proper namespacing to namespace and module. +``` +// For namespace "ezusb", module "device" vid +#define EZUSB_DEVICE_VID 0xFFFF +``` + +### Testing +We advice you to write unit tests for all modules that allow proper unit testing. This applies to modules that are not +part of the actual hooking process and do not rely on external devices to be available. Add these tests to the +*src/test* sub-folder. + +This does not only speed up your own development but hardens the code base and avoids having to test these things by +running the real applications, hence saving a lot of time and trouble. + +### Further best practices +* Avoid external dependencies like additional libraries. Bemanitools is extremely self-contained which enables high +portability and control which is important for implementing various "hacks" to just make things work. +* If you see some module/function lacking documentation that you have to use/understand, add documentation once you +figured out what the function/module is doing. This does not only help your future you but others reading the code. +* Keep documentation and readme files up-to-date. When introducing changes/adding new features, review existing +documentation and apply necessary changes. + +## Misc +The core API interception code was ripped out, cleaned up a tiny bit and released on GitHub. BT5 will eventually be +ported to use this external library in order to avoid maintaining the same code in two places at once. + +https://github.com/decafcode/capnhook + +This too is a little rudimentary; it doesn't come with any examples or even a README yet. \ No newline at end of file diff --git a/doc/iidxhook/README.md b/doc/iidxhook/README.md new file mode 100644 index 0000000..fa9e3db --- /dev/null +++ b/doc/iidxhook/README.md @@ -0,0 +1,39 @@ +# iidxhook + +iidxhook is a collection of hook libraries for BeatmaniaIIDX providing +emulation and various patches to run these games on non BemaniPC hardware and +newer Windows versions. + +# Versions + +iidxhook comes in a few different flavors. The game and its engine changed over +the years. Some game versions might require patches/parameters enabled which +others don't need or have different AVS versions. Here is the list of supported +games: +* [iidxhook1](iidxhook1.md): 9th, 10th, RED, HAPPY SKY +* [iidxhook2](iidxhook2.md): DistorteD +* [iidxhook3](iidxhook3.md): GOLD, DJ TROOPERS, EMPRESS, SIRIUS +* [iidxhook4](iidxhook4.md): Resort Anthem +* [iidxhook5](iidxhook5.md): Lincle +* [iidxhook6](iidxhook6.md): Tricoro +* [iidxhook7](iidxhook7.md): SPADA, PENDUAL, copula, SINOBUZ +* [iidxhook8](../iidxhook8/iidxhook8.md): CANNON BALLERS + +When building kactools, independent packages are created for each set of games +which are ready to be dropped on top of vanilla AC data dumps. We recommend +using prestine dumps to avoid any conflicts with other hardcoded hacks or +binary patches. + +# How to run + +To run your game with iidxhook, you have to use the inject tool to inject the +DLL to the game process. *dist/iidx* contains bat scripts with all the +important parameters configured. Further parameters can be added but might not +be required to run the game with default settings. +Further information on how to setup the data for each specific version are +elaborated in their dedicated readme files. + +# Command line options + +Add the argument *-h* when running inject with iidxhook to print help/usage +information with a list of parameters you can apply to tweak various things. diff --git a/doc/iidxhook/iidxhook1.md b/doc/iidxhook/iidxhook1.md new file mode 100644 index 0000000..2294eb8 --- /dev/null +++ b/doc/iidxhook/iidxhook1.md @@ -0,0 +1,216 @@ +# Game list + +The following games are compatible with this version of iidxhook: +* 9th Style +* 10th Style +* RED +* HAPPY SKY + +# Data setup and running the game + +Ensure your folder with your unpacked data looks like this: +- JAx (Game binary revision folder where 'x' can be A, B, C, D, E, F, G) +- data +- sidcode.txt + +Any further files are optional and not required to run the game. + +Unpack the package containing iidxhook1 into the revision folder of your choice. +Most likely, you want to target the latest revision you have to run the latest +binary of the game with any bugfixes by developers. + +If you don't run this on old hardware that uses an analog version of a Realtek +integrated sound chip, you have to replace RtEffect.dll with a stubbed/patched +version (RtEffect_patched.dll). Otherwise, the game might crash instantly when +trying to start it. + +Run the appropriate gamestart-XX.bat file as admin, where XX is either +09, 10, 11, 12. + +# Configuring iidxhook + +The hook library can be configured via cmd arguments or a configuration file. +The latter is generated (*iidxhook.conf* in the same directory) on the first +start of the game using the gamestart-XX.bat file. It contains default values +for all available parameters and comments explaining each parameter. Please +follow the comments when configuring your setup. + +Add the argument *-h* when running gamestart-XX.bat +(e.g. *gamestart-XX.bat -h*) to print help/usage information with a list of +all available parameters. Every parameter can be either set as command line +argument or using a configuration file. + +To set a parameter from the command line, just add it as an argument after +the bat file like this +``` +gamestart-09.bat -p gfx.windowed=true -p gfx.framed=true +``` + +The syntax for the "key=value" is the same as in the config file. Make sure +to have a pre-ceeding "-p" for every parameter added. + +However, if a parameter is specifed in the configuration file and as a command +line argument, the command line argument overrides the config file's value. + +# Eamuse network setup + +If you want to run the games online, you have to set a valid PCBID in the +configuration file or as a command line argument. You also have to set the url +of the eamuse server you want to connect to. + +Run the game with the gamestart-XX.bat file and enable network on the operator +menu. When enabled, the game seems to hang and expects you to power +cycle the machine (i.e. quit the game and restart it). + +# Switching beat phases 9th and 10th Style + +9th Style offers internet ranking phases 1 and 2, 10th Style phases 1, 2 and 3. +On both games, the phases are not controlled by the eamuse server the game is +connected to (this started with RED). Thus, the game was unlocked by binary +updates back then. +The higher the beat phase, the more expert courses and songs got unlocked. +Furthermore, the "real" ES and OMES are only available on beat#1. +Use the iidx-irbeat-patch-XX.bat to patch to a different beat phase if you want +to play on a different beat phase. The default phase is beat#1. +To unlock everything, patch the game to beat#3. +Example: "iidx-irbeat-patch-10.bat 2" to patch to beat#3 on 10th Style + +# Real hardware support + +## USB IO (ezusb) + +Use the specific iidxio API implementations, e.g. iidxio-ezusb.dll to use +an old C02 EZUSB IO board, to run the game on real hardware. Thanks to a common +abstraction layer, you can also use more modern IO, e.g. IO2 boards with +iidxio-ezusb2.dll, even with old games that do not support them. + +## Slotted/Wave pass card readers + +Replace the default *eamio.dll* with the *eamio-icca.dll* and have either your +slotted (IIDX, DDR Supernova or GF/DM type) or new wave pass card readers +conencted and and assigned to *COM1*. + +### ICCA device settings (device manager) +* Port: COM1 +* BAUD rate: 57600 +* Data bits: 8 +* Parity: None +* Stop bits: 1 +* Flow control: None + +If you encounter issues after the game opened the device, e.g. application +stuck, try a USB <-> COM dongle instead of using one of the COM ports of the +mainboard. + +# Known bugs +## avs00000.bin file on D drive +All other settings data is remapped to the local folders d, e and f. But, for +the avs00000.bin file that's not possible. Once the avs.dll is initialized +(DllMain called), it creates that file if it doesn't exist. Currently, we can't +fix this because iidxhook is injected after avs.dll is loaded and can't be +injected before it is loaded to patch the path for the file. + +## USBIO (FM-DL TIMEOUT) +IIDX occasionally fails to boot with a "USBIO (FM-DL TIMEOUT)" error. If this +happens, run the game again. + +# Troubleshooting and FAQ + +## The game does not run "well" (frame drops, drifting offsync etc) +This can be related to various issues: +* Make sure to run the game as (true) Administrator especially on Windows 7 and +newer. This will also get rid of various other errors (see below) that are +related to permission issues. +* Run the game's process with a higher priority: +``` +start "" /relatime "gamestart.bat" +``` +* Enforce v-sync enabled in your GPU settings. +* Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) +that is not jumping around. Use the timebase feature of one of the newer games to +check that or enable iidxhook's timebase and check the log output for the +determined value. Run this a few times and check if the results differ. +* Use iidxhook's frame rate limiter feature (see further below) to software lock +the refresh rate. This might be necessary on Windows 7 and newer for D3D8 games, +e.g. iidx 9 to 12, which seem to ignore GPU side v-sync. +* Use iidxhook's auto timebase feature (see further below) or set a pre-determined +value to cut down start-up times. + +### The game still stutters (randomly) and drifts off-sync +If this concerns a d3d8 based game, i.e. IIDX 9 to 13, use the d3d8to9 wrapper from +the bemanitools-supplement package (follow the included instructions). + +## "NETWORK WARNING" instead of "NETWORK OK" +This can be caused by: +* Invalid PCBID +* Firewall blocking connections +* Invalid eamuse url or port specified +* Game is not run using the Administrator account +Make sure to check these things first + +## My songs are offsync +* Make sure your machine's refresh rate is stable +* If you don't get a close to 59.94hz refresh rate, use the software monitor +check/auto timebase that's built into iidxhook (refer to help/config file) + +## The game crashes instantly (10th, RED, HAPPY SKY) +Replace the original RtEffects.dll with the patched version +RtEffects_patched.dll from utils (for explanation see above). + +## The game errors with "PROG CHECKSUM" on boot (10th Style only) +10th Style does some checksum tests on boot that have to be removed in order +to boot it with iidxhook injected. Use a patched executable that removed the +checksum tests. + +## My game runs too fast +iidxhook can limit the frame rate for you (refer to help/config file) + +## My game crashes when I try fullscreen +Use dxwnd and set settings like "Acquire admin caps" and "Fullscreen only" + +## 10key input (card reader keyboard) seems unresponsive +10key pad emulation for the old magnetic card readers is quite a mess and +can't be refreshed very often to make it feel unresponsive. Solution: +hit your 10key/numpad slower than normal + +## Background videos aren't working. When starting a song, windows is playing the error sound and a message box appears (RED, HAPPY SKY) +You are missing a codec to decode and play the videos. There are different +methods available to get background videos working. Probably, the easiest +solution: grab the CLVSD.ax file and go to Start -> Run -> regsvr32 clvsd.ax +Make sure to run cmd.exe as Administrator, otherwise you will get errors caused +by invalid permissions. + +## All background videos are looking streched (starting with HAPPY SKY) +The game requires a hardware feature that is not present on newer GPUs. +Refer to the help/config file and turn on the UV fix. + +## I installed the CLVSD.ax codec but the game crashes or displays a message box that tells me to disable my debugger +If songs keep crashing upon start and you get an error message that says +``` +DirectShow Texture3D Sample +Could not create source filter to graph! hr=0x80040266 +``` +despite having the codec (CLVSD.ax) installed, turn off debugging output +(refer to the help/config file) or use a CLVSD.ax codec which has the debugger +checks removed. + +## I used the auto timebase option and/or limited my refresh rate but the songs are still going offsync +There aren't many options left. The old games were developed for specific +hardware and are not guaranteed to work well on (especially) newer hardware. +Multiple monitor setups can also have a bad impact on a stable refresh rate. +Try a setup with just a single monitor you want to use for gameplay physically +connected. Furthermore, dedicated and tested/verified hardware by other users +is recommended if you want to save yourself a lot of fiddling. + +## Over-/underscan, bad image quality or latency caused by my monitor's/TV's upscaler +Many modern monitors/TVs cannot upscale 640x480 output properly. This can lead to +over-/underscan, bad image quality or even latency caused by the upscaler of the device +you are using. +If one or multiple of these issues apply, use the built in scaling options by setting +*gfx.scale_back_buffer_width* and *gfx.scale_back_buffer_height* to a target resolution +to scale to. Usually, you want to set this to the monitor's native resolution, e.g. +1920x1080 for full HD. You can play around with a few different filters using +*gfx.scale_back_buffer_filter* which impacts image quality/blurriness on upscaling. +If you want to use this with old d3d8 games (IIDX 9-13), you have to use the d3d8to9 +library from bemanitools-supplement because the d3d8 hook module cannot support this +upscaling feature. Make sure to set *misc.use_d3d9_hooks=true*. diff --git a/doc/iidxhook/iidxhook2.md b/doc/iidxhook/iidxhook2.md new file mode 100644 index 0000000..b2c37ff --- /dev/null +++ b/doc/iidxhook/iidxhook2.md @@ -0,0 +1,181 @@ +# Game list + +The following games are compatible with this version of iidxhook: +* DistorteD + +# Data setup and running the game + +Ensure your folder with your unpacked data looks like this: +- JAx (Game binary revision folder where 'x' can be A, B, C, D, E, F, G) +- data +- sidcode.txt + +Any further files are optional and not required to just run the game. + +Unpack the package containing iidxhook2 into the revision folder of your choice. +Most likely, you want to target the latest revision you have to run the latest +binary of the game with any bugfixes by developers. + +If you don't run this on old hardware that uses an analog version of a Realtek +integrated sound chip, you have to replace RtEffect.dll with a stubbed/patched +version (RtEffect_patched.dll). Otherwise, the game might crash instantly when +trying to start it. + +Run the gamestart-13.bat file as admin. + +# Configuring iidxhook + +The hook library can be configured via cmd arguments or a configuration file. +The latter is generated (*iidxhook.conf* in the same directory) on the first +start of the game using the gamestart-13.bat file. It contains default values +for all available parameters and comments explaining each parameter. Please +follow the comments when configuring your setup. + +Add the argument *-h* when running gamestart-13.bat +(e.g. *gamestart-13.bat -h*) to print help/usage information with a list of +all available parameters. Every parameter can be either set as command line +argument or using a configuration file. + +To set a parameter from the command line, just add it as an argument after +the bat file like this +``` +gamestart-09.bat -p gfx.windowed=true -p gfx.framed=true +``` + +The syntax for the "key=value" is the same as in the config file. Make sure +to have a pre-ceeding "-p" for every parameter added. + +However, if a parameter is specifed in the configuration file and as a command +line argument, the command line argument overrides the config file's value. + +# Eamuse network setup + +If you want to run the games online, you have to set a valid PCBID in the +configuration file or as a command line argument. You also have to set the +url of the eamuse server you want to connect to. + +Run the game with the gamestart-13.bat file and enable network on the operator +menu. When enabled, the game seems to hang and expects you to power +cycle the machine (i.e. quit the game and restart it). + +# Real hardware support + +## USB IO (ezusb) + +Use the specific iidxio API implementations, e.g. iidxio-ezusb.dll to use +an old C02 EZUSB IO board, to run the game on real hardware. Thanks to a common +abstraction layer, you can also use more modern IO, e.g. IO2 boards with +iidxio-ezusb2.dll, even with old games that do not support them. + +## Slotted/Wave pass card readers + +Replace the default *eamio.dll* with the *eamio-icca.dll* and have either your +slotted (IIDX, DDR Supernova or GF/DM type) or new wave pass card readers +conencted and and assigned to *COM1*. + +### ICCA device settings (device manager) +* Port: COM1 +* BAUD rate: 57600 +* Data bits: 8 +* Parity: None +* Stop bits: 1 +* Flow control: None + +If you encounter issues after the game opened the device, e.g. application +stuck, try a USB <-> COM dongle instead of using one of the COM ports of the +mainboard. + +# Known bugs +## USBIO (FM-DL TIMEOUT) +IIDX occasionally fails to boot with a "USBIO (FM-DL TIMEOUT)" error. If this +happens, run the game again. + +# Troubleshooting and FAQ + +## The game does not run "well" (frame drops, drifting offsync etc) +This can be related to various issues: +* Make sure to run the game as (true) Administrator especially on Windows 7 and +newer. This will also get rid of various other errors (see below) that are +related to permission issues. +* Run the game's process with a higher priority: +``` +start "" /relatime "gamestart.bat" +``` +* Enforce v-sync enabled in your GPU settings. +* Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) +that is not jumping around. Use the timebase feature of one of the newer games to +check that or enable iidxhook's timebase and check the log output for the +determined value. Run this a few times and check if the results differ. +* Use iidxhook's frame rate limiter feature (see further below) to software lock +the refresh rate. This might be necessary on Windows 7 and newer for D3D8 games, +e.g. iidx 9 to 12, which seem to ignore GPU side v-sync. +* Use iidxhook's auto timebase feature (see further below) or set a pre-determined +value to cut down start-up times. + +### The game still stutters (randomly) and drifts off-sync +If this concerns a d3d8 based game, i.e. IIDX 9 to 13, use the d3d8to9 wrapper from +the bemanitools-supplement package (follow the included instructions). + +## "NETWORK WARNING" instead of "NETWORK OK" +This can be caused by: +* Invalid PCBID +* Firewall blocking connections +* Invalid eamuse url or port specified +* Game is not run using the Administrator account +Make sure to check these things first + +## My songs are offsync +* Make sure your machine's refresh rate is stable +* If you don't get a close to 59.94hz refresh rate, use the software monitor +check/auto timebase that's built into iidxhook (refer to help/config file) + +## The game crashes instantly +Replace the original RtEffects.dll with the patched version +RtEffects_patched.dll from utils (for explanation see above). + +## My game runs too fast +iidxhook can limit the frame rate for you (refer to help/config file) + +## My game crashes when I try fullscreen +Use dxwnd and set settings like "Acquire admin caps" and "Fullscreen only" + +## Background videos aren't working. When starting a song, windows is playing the error sound and a message box appears +You are missing a codec to decode and play the videos. There are different +methods available to get background videos working. Probably, the easiest +solution: grab the CLVSD.ax file and go to Start -> Run -> regsvr32 clvsd.ax +Make sure to run cmd.exe as Administrator, otherwise you will get errors caused +by invalid permissions. + +## All background videos are looking streched (starting with HAPPY SKY) +The game requires on a hardware feature that is not present on newer GPUs. +Refer to the help/config file and turn on the UV fix. + +## I installed the CLVSD.ax codec but the game crashes or displays a message box that tells me to disable my debugger +If songs keep crashing upon start and you get an error message that says +``` +DirectShow Texture3D Sample +Could not create source filter to graph! hr=0x80040266 +``` +despite having the codec (CLVSD.ax) installed, remove the debug flag (*-D*) +from gamestart or use a CLVSD.ax codec which has the debugger checks removed. + +## I used the auto timebase option and/or limited my refresh rate but the songs are still going offsync +There aren't many options left. The old games were developed for specific +hardware and are not guaranteed to work well on (especially) newer hardware. +Multiple monitor setups can also have a bad impact on a stable refresh rate. +Try a setup with just a single monitor you want to use for gameplay physically +connected. Furthermore, dedicated and tested/verified hardware by other users +is recommended if you want to save yourself a lot of fiddling. + +## Over-/underscan, bad image quality or latency caused by my monitor's/TV's upscaler +Many modern monitors/TVs cannot upscale 640x480 output properly. This can lead to +over-/underscan, bad image quality or even latency caused by the upscaler of the device +you are using. +If one or multiple of these issues apply, use the built in scaling options by setting +*gfx.scale_back_buffer_width* and *gfx.scale_back_buffer_height* to a target resolution +to scale to. Usually, you want to set this to the monitor's native resolution, e.g. +1920x1080 for full HD. You can play around with a few different filters using +*gfx.scale_back_buffer_filter* which impacts image quality/blurriness on upscaling. +If you want to use this with old d3d8 games (IIDX 9-13), you have to use the d3d8to9 +library from bemanitools-supplement because the d3d8 hook module cannot support this +upscaling feature. Make sure to set *misc.use_d3d9_hooks=true*. diff --git a/doc/iidxhook/iidxhook3.md b/doc/iidxhook/iidxhook3.md new file mode 100644 index 0000000..75936ad --- /dev/null +++ b/doc/iidxhook/iidxhook3.md @@ -0,0 +1,185 @@ +# Game list + +The following games are compatible with this version of iidxhook: +* GOLD +* DJ Troopers +* EMPRESS +* SIRIUS + +# Data setup and running the game + +Ensure your folder with your unpacked data looks like this: +- yyyymmddrr (y = year digit, m = month digit, d = day digit, r = revision digit) +revision folder containing game binary and libraries +- data +- sidcode.txt + +Any further files are optional and not required to just run the game. + +Unpack the package containing iidxhook3 into the revision folder of your choice. +Most likely, you want to target the latest revision you have to run the latest +binary of the game with any bugfixes by developers. + +Run the gamestart-XX.bat file as admin where XX is the version of your choice +that's supported by this hook. + +# Configuring iidxhook + +The hook library can be configured via cmd arguments or a configuration file. +The latter is generated (*iidxhook.conf* in the same directory) on the first +start of the game using the gamestart-XX.bat file. It contains default values +for all available parameters and comments explaining each parameter. Please +follow the comments when configuring your setup. + +Add the argument *-h* when running gamestart-XX.bat +(e.g. *gamestart-XX.bat -h*) to print help/usage information with a list of +all available parameters. Every parameter can be either set as command line +argument or using a configuration file. + +To set a parameter from the command line, just add it as an argument after +the bat file like this +``` +gamestart-09.bat -p gfx.windowed=true -p gfx.framed=true +``` + +The syntax for the "key=value" is the same as in the config file. Make sure +to have a pre-ceeding "-p" for every parameter added. + +However, if a parameter is specifed in the configuration file and as a command +line argument, the command line argument overrides the config file's value. + +# Eamuse network setup + +If you want to run the games online, you have to set a valid PCBID and EAMID +(use the PCBID as the EAMID) in the configuration file or as a command line +argument. You also have to set the url of the eamuse server you want to +connect to. + +Run the game with the gamestart-XX.bat file and enable network on the operator +menu. When enabled, the game seems to hang and expects you to power +cycle the machine (i.e. quit the game and restart it). + +# Real hardware support + +## USB IO (ezusb) + +Use the specific iidxio API implementations, e.g. iidxio-ezusb2.dll to use +the IO2 EZUSB board, to run the game on real hardware. Thanks to a common +abstraction layer, you can also use custom IO boards or whatever Konami hardware +is going to be available in the future. Obviously, someone has to write a +driver, first. + +## Slotted/Wave pass card readers + +Replace the default *eamio.dll* with the *eamio-icca.dll* and have either your +slotted (IIDX, DDR Supernova or GF/DM type) or new wave pass card readers +conencted and and assigned to *COM1*. + +### ICCA device settings (device manager) +* Port: COM1 +* BAUD rate: 57600 +* Data bits: 8 +* Parity: None +* Stop bits: 1 +* Flow control: None + +If you encounter issues after the game opened the device, e.g. application +stuck, try a USB <-> COM dongle instead of using one of the COM ports of the +mainboard. + +# Known bugs +## USBIO (FM-DL TIMEOUT) +IIDX occasionally fails to boot with a "USBIO (FM-DL TIMEOUT)" error. If this +happens, run the game again. + +# Troubleshooting and FAQ + +## The game does not run "well" (frame drops, drifting offsync etc) +This can be related to various issues: +* Make sure to run the game as (true) Administrator especially on Windows 7 and +newer. This will also get rid of various other errors (see below) that are +related to permission issues. +* Run the game's process with a higher priority: +``` +start "" /relatime "gamestart.bat" +``` +* Enforce v-sync enabled in your GPU settings. +* Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) +that is not jumping around. Use the timebase feature of one of the newer games to +check that or enable iidxhook's timebase and check the log output for the +determined value. Run this a few times and check if the results differ. +* Use iidxhook's frame rate limiter feature (see further below) to software lock +the refresh rate. This might be necessary on Windows 7 and newer for D3D8 games, +e.g. iidx 9 to 12, which seem to ignore GPU side v-sync. +* Use iidxhook's auto timebase feature (see further below) or set a pre-determined +value to cut down start-up times. + +## "NETWORK WARNING" instead of "NETWORK OK" +This can be caused by: +* Invalid PCBID +* Firewall blocking connections +* Invalid eamuse url or port specified +* Game is not run using the Administrator account +Make sure to check these things first + +## My songs are offsync +The built-in monitor check just determines if the game should sync to either +59.94 hz (S-Video setting) or 60.04 hz (VGA setting). If you don't have a setup +that runs on (as close as possible) these values: +* Make sure your machine's refresh rate is stable, e.g. 60.00x hz. +* If you don't get a close to 59.94hz (S-Video setting) or 60.04 hz +(VGA setting) refresh rate, go an set the output mode in the operator menu +to "VGA" to enforce the game to run chart syncing on 60.04 hz refresh +rate (even if your setup does not have that value). Next, use the software +monitor check/auto timebase that's built into iidxhook (refer to cmd +help/configfile). + +## My game runs too fast +iidxhook can limit the frame rate for you (refer to help/config file) + +## My game crashes when I try fullscreen +Use dxwnd and set settings like "Acquire admin caps" and "Fullscreen only" + +## Background videos aren't working. When starting a song, windows is playing the error sound and a message box appears +You are missing a codec to decode and play the videos. There are different +methods available to get background videos working. Probably, the easiest +solution: grab the CLVSD.ax file and go to Start -> Run -> regsvr32 clvsd.ax +Make sure to run cmd.exe as Administrator, otherwise you will get errors caused +by invalid permissions. + +## All background videos are looking streched (starting with HAPPY SKY) +The game requires on a hardware feature that is not present on newer GPUs. +Refer to the help/config file and turn on the UV fix. + +## I installed the CLVSD.ax codec but the game crashes or displays a message box that tells me to disable my debugger +If songs keep crashing upon start and you get an error message that says +``` +DirectShow Texture3D Sample +Could not create source filter to graph! hr=0x80040266 +``` +despite having the codec (CLVSD.ax) installed, remove the debug flag (*-D*) +from gamestart or use a CLVSD.ax codec which has the debugger checks removed. + +## I used the auto timebase option and/or limited my refresh rate but the songs are still going offsync +There aren't many options left. The old games were developed for specific +hardware and are not guaranteed to work well on (especially) newer hardware. +Multiple monitor setups can also have a bad impact on a stable refresh rate. +Try a setup with just a single monitor you want to use for gameplay physically +connected. Furthermore, dedicated and tested/verified hardware by other users +is recommended if you want to save yourself a lot of fiddling. + +## I am getting a message box with a japanese error message and a black window immediately after starting the game +The game checks the vendor and product ID of your GPU installed. If it doesn't +match a hardcoded whitelist, the game won't boot. Use the option *gfx.pci_id* +either in the config file or as a cmd argument to spoof these IDs. See the +help message for instructions and possible IDs. + +## Over-/underscan, bad image quality or latency caused by my monitor's/TV's upscaler +Many modern monitors/TVs cannot upscale 640x480 output properly. This can lead to +over-/underscan, bad image quality or even latency caused by the upscaler of the device +you are using. +If one or multiple of these issues apply, use the built in scaling options by setting +*gfx.scale_back_buffer_width* and *gfx.scale_back_buffer_height* to a target resolution +to scale to. Usually, you want to set this to the monitor's native resolution, e.g. +1920x1080 for full HD. You can play around with a few different filters using +*gfx.scale_back_buffer_filter* which impacts image quality/blurriness on upscaling. diff --git a/doc/iidxhook/iidxhook4.md b/doc/iidxhook/iidxhook4.md new file mode 100644 index 0000000..f0a79d9 --- /dev/null +++ b/doc/iidxhook/iidxhook4.md @@ -0,0 +1,201 @@ +# Game list + +The following games are compatible with this version of iidxhook: +* Resort Anthem + +# Data setup and running the game + +We assume that you are using a clean/vanilla data dump. Ensure your ("concents") +folder with your unpacked data looks like this: +- data +- modules +- prop + +* Copy/Move all files from the *modules* directory to the root folder, so they +are located next to the *data* and *prop* folders. +* Copy all files from *prop/defaults* to the *prop* folder. +* Create a new file *app-config.xml* in the *prop* folder with the following +content: +``` + + +``` +* Setup proper paths for *dev/nvram* and *dev/raw* in *prop/avs-config.xml* by +replacing the **-block in that file with the following block: +``` + + + . + + + dev/nvram + fs + + + + dev/raw + + 256 + 256 + +``` +* Unpack the package containing iidxhook4 into the root folder so iidxhook4.dll +and all other files are located in the same folder as *data*, *prop*, +*bm2dx.dll*, etc. +* Run the gamestart-18.bat file as admin. + +# Configuring iidxhook + +The hook library can be configured via cmd arguments or a configuration file. +The latter is generated (*iidxhook-18.conf* in the same directory) on the first +start of the game using the gamestart-18.bat file. It contains default values +for all available parameters and comments explaining each parameter. Please +follow the comments when configuring your setup. + +Add the argument *-h* when running gamestart-18.bat +(e.g. *gamestart-18.bat -h*) to print help/usage information with a list of +all available parameters. Every parameter can be either set as command line +argument or using a configuration file. + +To set a parameter from the command line, just add it as an argument after +the bat file like this +``` +gamestart-09.bat -p gfx.windowed=true -p gfx.framed=true +``` + +The syntax for the "key=value" is the same as in the config file. Make sure +to have a pre-ceeding "-p" for every parameter added. + +However, if a parameter is specifed in the configuration file and as a command +line argument, the command line argument overrides the config file's value. + +# Eamuse network setup + +If you want to run the games online, you need a valid PCBID and the service URL. +Open *prop/ea3-config.xml* and set the values of the *ea3/id/pcbid* and +*ea3/network/services* nodes accordingly. + +Run the game with the gamestart-18.bat file and enable network on the operator +menu. When enabled, the game seems to hang and expects you to power +cycle the machine (i.e. quit the game and restart it). + +# Real hardware support + +## USB IO (ezusb) + +Use the specific iidxio API implementations, e.g. iidxio-ezusb2.dll to use +the IO2 EZUSB board, to run the game on real hardware. Thanks to a common +abstraction layer, you can also use custom IO boards or whatever Konami hardware +is going to be available in the future. Obviously, someone has to write a +driver, first. + +## Slotted/Wave pass card readers + +Replace the default *eamio.dll* with the *eamio-icca.dll* and have either your +slotted (IIDX, DDR Supernova or GF/DM type) or new wave pass card readers +conencted and and assigned to *COM1*. + +### ICCA device settings (device manager) +* Port: COM1 +* BAUD rate: 57600 +* Data bits: 8 +* Parity: None +* Stop bits: 1 +* Flow control: None + +If you encounter issues after the game opened the device, e.g. application +stuck, try a USB <-> COM dongle instead of using one of the COM ports of the +mainboard. + +# Known bugs +## USBIO (FM-DL TIMEOUT) +IIDX occasionally fails to boot with a "USBIO (FM-DL TIMEOUT)" error. If this +happens, run the game again. + +# Troubleshooting and FAQ + +## The game does not run "well" (frame drops, drifting offsync etc) +This can be related to various issues: +* Make sure to run the game as (true) Administrator especially on Windows 7 and +newer. This will also get rid of various other errors (see below) that are +related to permission issues. +* Run the game's process with a higher priority: +``` +start "" /relatime "gamestart.bat" +``` +* Enforce v-sync enabled in your GPU settings. +* Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) +that is not jumping around. Use the timebase feature of one of the newer games to +check that or enable iidxhook's timebase and check the log output for the +determined value. Run this a few times and check if the results differ. +* Use iidxhook's frame rate limiter feature (see further below) to software lock +the refresh rate. This might be necessary on Windows 7 and newer for D3D8 games, +e.g. iidx 9 to 12, which seem to ignore GPU side v-sync. +* Use iidxhook's auto timebase feature (see further below) or set a pre-determined +value to cut down start-up times. + +## "NETWORK WARNING" instead of "NETWORK OK" +This can be caused by: +* Invalid PCBID +* Firewall blocking connections +* Invalid eamuse url or port specified +* Game is not run using the Administrator account +Make sure to check these things first + +## My songs are offsync +The built-in monitor check just determines if the game should sync to either +59.94 hz (S-Video setting) or 60.04 hz (VGA setting). If you don't have a setup +that runs on (as close as possible) these values: +* Make sure your machine's refresh rate is stable, e.g. 60.00x hz. +* If you don't get a close to 59.94hz (S-Video setting) or 60.04 hz +(VGA setting) refresh rate, go an set the output mode in the operator menu +to "VGA" to enforce the game to run chart syncing on 60.04 hz refresh +rate (even if your setup does not have that value). Next, use the software +monitor check/auto timebase that's built into iidxhook (refer to cmd +help/configfile). + +## My game runs too fast +iidxhook can limit the frame rate for you (refer to help/config file) + +## My game crashes when I try fullscreen +Use dxwnd and set settings like "Acquire admin caps" and "Fullscreen only" + +## Background videos aren't working. When starting a song, windows is playing the error sound and a message box appears +You are missing a codec to decode and play the videos. There are different +methods available to get background videos working. Probably, the easiest +solution: grab the CLVSD.ax file and go to Start -> Run -> regsvr32 clvsd.ax +Make sure to run cmd.exe as Administrator, otherwise you will get errors caused +by invalid permissions. + +## I installed the CLVSD.ax codec but the game crashes or displays a message box that tells me to disable my debugger +If songs keep crashing upon start and you get an error message that says +``` +DirectShow Texture3D Sample +Could not create source filter to graph! hr=0x80040266 +``` +despite having the codec (CLVSD.ax) installed, remove the debug flag (*-D*) +from gamestart or use a CLVSD.ax codec which has the debugger checks removed. + +## I used the auto timebase option and/or limited my refresh rate but the songs are still going offsync +There aren't many options left. The old games were developed for specific +hardware and are not guaranteed to work well on (especially) newer hardware. +Multiple monitor setups can also have a bad impact on a stable refresh rate. +Try a setup with just a single monitor you want to use for gameplay physically +connected. Furthermore, dedicated and tested/verified hardware by other users +is recommended if you want to save yourself a lot of fiddling. + +## I am getting a message box with a japanese error message and a black window immediately after starting the game +The game checks the vendor and product ID of your GPU installed. If it doesn't +match a hardcoded whitelist, the game won't boot. Use the option *gfx.pci_id* +either in the config file or as a cmd argument to spoof these IDs. See the +help message for instructions and possible IDs. + +## Over-/underscan, bad image quality or latency caused by my monitor's/TV's upscaler +Many modern monitors/TVs cannot upscale 640x480 output properly. This can lead to +over-/underscan, bad image quality or even latency caused by the upscaler of the device +you are using. +If one or multiple of these issues apply, use the built in scaling options by setting +*gfx.scale_back_buffer_width* and *gfx.scale_back_buffer_height* to a target resolution +to scale to. Usually, you want to set this to the monitor's native resolution, e.g. +1920x1080 for full HD. You can play around with a few different filters using +*gfx.scale_back_buffer_filter* which impacts image quality/blurriness on upscaling. diff --git a/doc/iidxhook/iidxhook5.md b/doc/iidxhook/iidxhook5.md new file mode 100644 index 0000000..30558ae --- /dev/null +++ b/doc/iidxhook/iidxhook5.md @@ -0,0 +1,189 @@ +# Game list + +The following games are compatible with this version of iidxhook: +* Lincle + +# Data setup and running the game + +We assume that you are using a clean/vanilla data dump. Ensure your ("concents") +folder with your unpacked data looks like this: +- data +- modules +- prop + +* Copy/Move all files from the *modules* directory to the root folder, so they +are located next to the *data* and *prop* folders. +* Copy all files from *prop/defaults* to the *prop* folder. +* Create a new file *app-config.xml* in the *prop* folder with the following +content: +``` + + +``` +* Setup proper paths for *dev/nvram* and *dev/raw* in *prop/avs-config.xml* by +replacing the **-block in that file with the following block: +``` + + + . + + + dev/nvram + fs + + + + dev/raw + + 256 + 256 + +``` +* Unpack the package containing iidxhook5 into the root folder so iidxhook5.dll +and all other files are located in the same folder as *data*, *prop*, +*bm2dx.dll*, etc. +* Run the gamestart-19.bat file as admin. + +# Configuring iidxhook + +The hook library can be configured via cmd arguments or a configuration file. +The latter is generated (*iidxhook-19.conf* in the same directory) on the first +start of the game using the gamestart-19.bat file. It contains default values +for all available parameters and comments explaining each parameter. Please +follow the comments when configuring your setup. + +Add the argument *-h* when running gamestart-19.bat +(e.g. *gamestart-19.bat -h*) to print help/usage information with a list of +all available parameters. Every parameter can be either set as command line +argument or using a configuration file. + +To set a parameter from the command line, just add it as an argument after +the bat file like this +``` +gamestart-09.bat -p gfx.windowed=true -p gfx.framed=true +``` + +The syntax for the "key=value" is the same as in the config file. Make sure +to have a pre-ceeding "-p" for every parameter added. + +However, if a parameter is specifed in the configuration file and as a command +line argument, the command line argument overrides the config file's value. + +# Eamuse network setup + +If you want to run the games online, you need a valid PCBID and the service URL. +Open *prop/ea3-config.xml* and set the values of the *ea3/id/pcbid* and +*ea3/network/services* nodes accordingly. + +Run the game with the gamestart-19.bat file and enable network on the operator +menu. When enabled, the game seems to hang and expects you to power +cycle the machine (i.e. quit the game and restart it). + +# Real hardware support + +## USB IO (ezusb) + +Use the specific iidxio API implementations, e.g. iidxio-ezusb2.dll to use +the IO2 EZUSB board, to run the game on real hardware. Thanks to a common +abstraction layer, you can also use custom IO boards or whatever Konami hardware +is going to be available in the future. Obviously, someone has to write a +driver, first. + +## Slotted/Wave pass card readers + +Replace the default *eamio.dll* with the *eamio-icca.dll* and have either your +slotted (IIDX, DDR Supernova or GF/DM type) or new wave pass card readers +conencted and and assigned to *COM1*. + +### ICCA device settings (device manager) +* Port: COM1 +* BAUD rate: 57600 +* Data bits: 8 +* Parity: None +* Stop bits: 1 +* Flow control: None + +If you encounter issues after the game opened the device, e.g. application +stuck, try a USB <-> COM dongle instead of using one of the COM ports of the +mainboard. + +# Known bugs +## USBIO (FM-DL TIMEOUT) +IIDX occasionally fails to boot with a "USBIO (FM-DL TIMEOUT)" error. If this +happens, run the game again. + +# Troubleshooting and FAQ + +## The game does not run "well" (frame drops, drifting offsync etc) +This can be related to various issues: +* Make sure to run the game as (true) Administrator especially on Windows 7 and +newer. This will also get rid of various other errors (see below) that are +related to permission issues. +* Run the game's process with a higher priority: +``` +start "" /relatime "gamestart.bat" +``` +* Enforce v-sync enabled in your GPU settings. +* Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) +that is not jumping around. Use the timebase feature of one of the newer games to +check that or enable iidxhook's timebase and check the log output for the +determined value. Run this a few times and check if the results differ. +* Use iidxhook's frame rate limiter feature (see further below) to software lock +the refresh rate. This might be necessary on Windows 7 and newer for D3D8 games, +e.g. iidx 9 to 12, which seem to ignore GPU side v-sync. +* Use iidxhook's auto timebase feature (see further below) or set a pre-determined +value to cut down start-up times. + +## "NETWORK WARNING" instead of "NETWORK OK" +This can be caused by: +* Invalid PCBID +* Firewall blocking connections +* Invalid eamuse url or port specified +* Game is not run using the Administrator account +Make sure to check these things first + +## My songs are offsync +From this version onwards (if you use the very final data of Lincle), the game +comes with a built-in auto timebase option ("monitor check" on startup) which +dynamically, detects the refresh rate of your current setup. Thus, BT5's +timebase option is not included from this hook version onwards, anymore. +Ensure that refresh rate displayed is very stable, e.g. 60.00x hz, and the +game should be able to provide you with a smooth and sync game experience. + +## My game runs too fast +iidxhook can limit the frame rate for you (refer to help/config file) + +## My game crashes when I try fullscreen +Use dxwnd and set settings like "Acquire admin caps" and "Fullscreen only" + +## Background videos aren't working. When starting a song, windows is playing the error sound and a message box appears +You are missing a codec to decode and play the videos. There are different +methods available to get background videos working. Probably, the easiest +solution: grab the CLVSD.ax file and go to Start -> Run -> regsvr32 clvsd.ax +Make sure to run cmd.exe as Administrator, otherwise you will get errors caused +by invalid permissions. + +## I installed the CLVSD.ax codec but the game crashes or displays a message box that tells me to disable my debugger +If songs keep crashing upon start and you get an error message that says +``` +DirectShow Texture3D Sample +Could not create source filter to graph! hr=0x80040266 +``` +despite having the codec (CLVSD.ax) installed, remove the debug flag (*-D*) +from gamestart or use a CLVSD.ax codec which has the debugger checks removed. + +## I am getting a message box with a japanese error message and a black window immediately after starting the game +The game checks the vendor and product ID of your GPU installed. If it doesn't +match a hardcoded whitelist, the game won't boot. Use the option *gfx.pci_id* +either in the config file or as a cmd argument to spoof these IDs. See the +help message for instructions and possible IDs. + +## Over-/underscan, bad image quality or latency caused by my monitor's/TV's upscaler +Many modern monitors/TVs cannot upscale 640x480 output properly. This can lead to +over-/underscan, bad image quality or even latency caused by the upscaler of the device +you are using. +If one or multiple of these issues apply, use the built in scaling options by setting +*gfx.scale_back_buffer_width* and *gfx.scale_back_buffer_height* to a target resolution +to scale to. Usually, you want to set this to the monitor's native resolution, e.g. +1920x1080 for full HD. You can play around with a few different filters using +*gfx.scale_back_buffer_filter* which impacts image quality/blurriness on upscaling. diff --git a/doc/iidxhook/iidxhook6.md b/doc/iidxhook/iidxhook6.md new file mode 100644 index 0000000..304a5fa --- /dev/null +++ b/doc/iidxhook/iidxhook6.md @@ -0,0 +1,195 @@ +# Game list + +The following games are compatible with this version of iidxhook: +* Tricoro + +# Data setup and running the game + +We assume that you are using a clean/vanilla data dump. Ensure your ("concents") +folder with your unpacked data looks like this: +- data +- modules +- prop + +* Copy/Move all files from the *modules* directory to the root folder, so they +are located next to the *data* and *prop* folders. +* Copy all files from *prop/defaults* to the *prop* folder. +* Create a new file *app-config.xml* in the *prop* folder with the following +content: +``` + + +``` +* Setup proper paths for *dev/nvram* and *dev/raw* in *prop/avs-config.xml* by +replacing the **-block in that file with the following block: +``` + + + . + + + dev/nvram + fs + + + + dev/raw + + 256 + 256 + +``` +* Setup valid logger configuration by replacing the **-block in +*prop/avs-config.xml* with: +``` + + + 0 + + + misc + +``` +* Unpack the package containing iidxhook6 into the root folder so iidxhook6.dll +and all other files are located in the same folder as *data*, *prop*, +*bm2dx.dll*, etc. +* Run the gamestart-20.bat file as admin. + +# Configuring iidxhook + +The hook library can be configured via cmd arguments or a configuration file. +The latter is generated (*iidxhook-20.conf* in the same directory) on the first +start of the game using the gamestart-20.bat file. It contains default values +for all available parameters and comments explaining each parameter. Please +follow the comments when configuring your setup. + +Add the argument *-h* when running gamestart-20.bat +(e.g. *gamestart-20.bat -h*) to print help/usage information with a list of +all available parameters. Every parameter can be either set as command line +argument or using a configuration file. + +To set a parameter from the command line, just add it as an argument after +the bat file like this +``` +gamestart-09.bat -p gfx.windowed=true -p gfx.framed=true +``` + +The syntax for the "key=value" is the same as in the config file. Make sure +to have a pre-ceeding "-p" for every parameter added. + +However, if a parameter is specifed in the configuration file and as a command +line argument, the command line argument overrides the config file's value. + +# Eamuse network setup + +If you want to run the games online, you need a valid PCBID and the service URL. +Open *prop/ea3-config.xml* and set the values of the *ea3/id/pcbid* and +*ea3/network/services* nodes accordingly. + +Run the game with the gamestart-20.bat file and enable network on the operator +menu. When enabled, the game seems to hang and expects you to power +cycle the machine (i.e. quit the game and restart it). + +# Real hardware support + +## USB IO (ezusb) + +Use the specific iidxio API implementations, e.g. iidxio-ezusb2.dll to use +the IO2 EZUSB board, to run the game on real hardware. Thanks to a common +abstraction layer, you can also use custom IO boards or whatever Konami hardware +is going to be available in the future. Obviously, someone has to write a +driver, first. + +## Slotted/Wave pass card readers + +Replace the default *eamio.dll* with the *eamio-icca.dll* and have either your +slotted (IIDX, DDR Supernova or GF/DM type) or new wave pass card readers +conencted and and assigned to *COM1*. + +### ICCA device settings (device manager) +* Port: COM1 +* BAUD rate: 57600 +* Data bits: 8 +* Parity: None +* Stop bits: 1 +* Flow control: None + +If you encounter issues after the game opened the device, e.g. application +stuck, try a USB <-> COM dongle instead of using one of the COM ports of the +mainboard. + +# Known bugs +## USBIO (FM-DL TIMEOUT) +IIDX occasionally fails to boot with a "USBIO (FM-DL TIMEOUT)" error. If this +happens, run the game again. + +# Troubleshooting and FAQ + +## The game does not run "well" (frame drops, drifting offsync etc) +This can be related to various issues: +* Make sure to run the game as (true) Administrator especially on Windows 7 and +newer. This will also get rid of various other errors (see below) that are +related to permission issues. +* Run the game's process with a higher priority: +``` +start "" /relatime "gamestart.bat" +``` +* Enforce v-sync enabled in your GPU settings. +* Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) +that is not jumping around. Use the timebase feature of one of the newer games to +check that or enable iidxhook's timebase and check the log output for the +determined value. Run this a few times and check if the results differ. + +## "NETWORK WARNING" instead of "NETWORK OK" +This can be caused by: +* Invalid PCBID +* Firewall blocking connections +* Invalid eamuse url or port specified +* Game is not run using the Administrator account +Make sure to check these things first + +## My songs are offsync +From this version onwards (or Lincle very final revision), the game comes with +a built-in auto timebase option ("monitor check" on startup) which +dynamically, detects the refresh rate of your current setup. Thus, BT5's +timebase option is not included from this hook version onwards, anymore. +Ensure that refresh rate displayed is very stable, e.g. 60.00x hz, and the +game should be able to provide you with a smooth and sync game experience. + +## My game runs too fast +iidxhook can limit the frame rate for you (refer to help/config file) + +## My game crashes when I try fullscreen +Use dxwnd and set settings like "Acquire admin caps" and "Fullscreen only" + +## Background videos aren't working. When starting a song, windows is playing the error sound and a message box appears +You are missing a codec to decode and play the videos. There are different +methods available to get background videos working. Probably, the easiest +solution: grab the CLVSD.ax file and go to Start -> Run -> regsvr32 clvsd.ax +Make sure to run cmd.exe as Administrator, otherwise you will get errors caused +by invalid permissions. + +## I installed the CLVSD.ax codec but the game crashes or displays a message box that tells me to disable my debugger +If songs keep crashing upon start and you get an error message that says +``` +DirectShow Texture3D Sample +Could not create source filter to graph! hr=0x80040266 +``` +despite having the codec (CLVSD.ax) installed, remove the debug flag (*-D*) +from gamestart or use a CLVSD.ax codec which has the debugger checks removed. + +## I am getting a message box with a japanese error message and a black window immediately after starting the game +The game checks the vendor and product ID of your GPU installed. If it doesn't +match a hardcoded whitelist, the game won't boot. Use the option *gfx.pci_id* +either in the config file or as a cmd argument to spoof these IDs. See the +help message for instructions and possible IDs. + +## Over-/underscan, bad image quality or latency caused by my monitor's/TV's upscaler +Many modern monitors/TVs cannot upscale some lower resolutions, e.g. 640x480, properly. +This can lead to over-/underscan, bad image quality or even latency caused by the upscaler +of the device you are using. +If one or multiple of these issues apply, use the built in scaling options by setting +*gfx.scale_back_buffer_width* and *gfx.scale_back_buffer_height* to a target resolution +to scale to. Usually, you want to set this to the monitor's native resolution, e.g. +1920x1080 for full HD. You can play around with a few different filters using +*gfx.scale_back_buffer_filter* which impacts image quality/blurriness on upscaling. diff --git a/doc/iidxhook/iidxhook7.md b/doc/iidxhook/iidxhook7.md new file mode 100644 index 0000000..97ae161 --- /dev/null +++ b/doc/iidxhook/iidxhook7.md @@ -0,0 +1,201 @@ +# Game list + +The following games are compatible with this version of iidxhook: +* SPADA +* PENDUAL +* copula +* SINOBUZ + +# Data setup and running the game + +We assume that you are using a clean/vanilla data dump. Ensure your ("concents") +folder with your unpacked data looks like this: +- data +- modules +- prop + +* Copy/Move all files from the *modules* directory to the root folder, so they +are located next to the *data* and *prop* folders. +* Copy all files from *prop/defaults* to the *prop* folder. +* Create a new file *app-config.xml* in the *prop* folder with the following +content: +``` + + +``` +* Setup proper paths for *dev/nvram* and *dev/raw* in *prop/avs-config.xml* by +replacing the **-block in that file with the following block: +``` + + + . + + + dev/nvram + fs + + + + dev/raw + + 256 + 256 + +``` +* Setup valid logger configuration by replacing the **-block in +*prop/avs-config.xml* with: +``` + + + 0 + + + misc + +``` +* Unpack the package containing iidxhook7 into the root folder so iidxhook7.dll +and all other files are located in the same folder as *data*, *prop*, +*bm2dx.dll*, etc. +* Run the gamestart-XX.bat file as admin. Where XX matches the version you +want to run. + +# Configuring iidxhook + +The hook library can be configured via cmd arguments or a configuration file. +The latter is generated (*iidxhook-XX.conf* in the same directory) on the first +start of the game using the gamestart-XX.bat file (again, XX matches your target +game version). It contains default values for all available parameters and +comments explaining each parameter. Please follow the comments when configuring +your setup. + +Add the argument *-h* when running gamestart-XX.bat +(e.g. *gamestart-XX.bat -h*) to print help/usage information with a list of +all available parameters. Every parameter can be either set as command line +argument or using a configuration file. + +To set a parameter from the command line, just add it as an argument after +the bat file like this +``` +gamestart-09.bat -p gfx.windowed=true -p gfx.framed=true +``` + +The syntax for the "key=value" is the same as in the config file. Make sure +to have a pre-ceeding "-p" for every parameter added. + +However, if a parameter is specifed in the configuration file and as a command +line argument, the command line argument overrides the config file's value. + +# Eamuse network setup + +If you want to run the games online, you need a valid PCBID and the service URL. +Open *prop/ea3-config.xml* and set the values of the *ea3/id/pcbid* and +*ea3/network/services* nodes accordingly. + +Run the game with the gamestart-XX.bat file and enable network on the operator +menu. When enabled, the game seems to hang and expects you to power +cycle the machine (i.e. quit the game and restart it). + +# Real hardware support + +## USB IO (ezusb) + +Use the specific iidxio API implementations, e.g. iidxio-ezusb2.dll to use +the IO2 EZUSB board, to run the game on real hardware. Thanks to a common +abstraction layer, you can also use custom IO boards or whatever Konami hardware +is going to be available in the future. Obviously, someone has to write a +driver, first. + +## Slotted/Wave pass card readers + +Replace the default *eamio.dll* with the *eamio-icca.dll* and have either your +slotted (IIDX, DDR Supernova or GF/DM type) or new wave pass card readers +conencted and and assigned to *COM1*. + +### ICCA device settings (device manager) +* Port: COM1 +* BAUD rate: 57600 +* Data bits: 8 +* Parity: None +* Stop bits: 1 +* Flow control: None + +If you encounter issues after the game opened the device, e.g. application +stuck, try a USB <-> COM dongle instead of using one of the COM ports of the +mainboard. + +# Known bugs +## USBIO (FM-DL TIMEOUT) +IIDX occasionally fails to boot with a "USBIO (FM-DL TIMEOUT)" error. If this +happens, run the game again. + +# Troubleshooting and FAQ + +## The game does not run "well" (frame drops, drifting offsync etc) +This can be related to various issues: +* Make sure to run the game as (true) Administrator especially on Windows 7 and +newer. This will also get rid of various other errors (see below) that are +related to permission issues. +* Run the game's process with a higher priority: +``` +start "" /relatime "gamestart.bat" +``` +* Enforce v-sync enabled in your GPU settings. +* Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) +that is not jumping around. Use the timebase feature of one of the newer games to +check that or enable iidxhook's timebase and check the log output for the +determined value. Run this a few times and check if the results differ. + +## "NETWORK WARNING" instead of "NETWORK OK" +This can be caused by: +* Invalid PCBID +* Firewall blocking connections +* Invalid eamuse url or port specified +* Game is not run using the Administrator account +Make sure to check these things first + +## My songs are offsync +From IIDX 20 (or Lincle very final revision) onwards, the game comes with +a built-in auto timebase option ("monitor check" on startup) which +dynamically, detects the refresh rate of your current setup. Thus, BT5's +timebase option is not included from this hook version onwards, anymore. +Ensure that refresh rate displayed is very stable, e.g. 60.00x hz, and the +game should be able to provide you with a smooth and sync game experience. + +## My game runs too fast +iidxhook can limit the frame rate for you (refer to help/config file) + +## My game crashes when I try fullscreen +Use dxwnd and set settings like "Acquire admin caps" and "Fullscreen only" + +## Background videos aren't working. When starting a song, windows is playing the error sound and a message box appears +You are missing a codec to decode and play the videos. There are different +methods available to get background videos working. Probably, the easiest +solution: grab the CLVSD.ax file and go to Start -> Run -> regsvr32 clvsd.ax +Make sure to run cmd.exe as Administrator, otherwise you will get errors caused +by invalid permissions. + +## I installed the CLVSD.ax codec but the game crashes or displays a message box that tells me to disable my debugger +If songs keep crashing upon start and you get an error message that says +``` +DirectShow Texture3D Sample +Could not create source filter to graph! hr=0x80040266 +``` +despite having the codec (CLVSD.ax) installed, remove the debug flag (*-D*) +from gamestart or use a CLVSD.ax codec which has the debugger checks removed. + +## I am getting a message box with a japanese error message and a black window immediately after starting the game +The game checks the vendor and product ID of your GPU installed. If it doesn't +match a hardcoded whitelist, the game won't boot. Use the option *gfx.pci_id* +either in the config file or as a cmd argument to spoof these IDs. See the +help message for instructions and possible IDs. + +## Over-/underscan, bad image quality or latency caused by my monitor's/TV's upscaler +Many modern monitors/TVs cannot upscale some lower resolutions, e.g. 640x480, properly. +This can lead to over-/underscan, bad image quality or even latency caused by the upscaler +of the device you are using. +If one or multiple of these issues apply, use the built in scaling options by setting +*gfx.scale_back_buffer_width* and *gfx.scale_back_buffer_height* to a target resolution +to scale to. Usually, you want to set this to the monitor's native resolution, e.g. +1920x1080 for full HD. You can play around with a few different filters using +*gfx.scale_back_buffer_filter* which impacts image quality/blurriness on upscaling. + diff --git a/doc/iidxhook/iidxhook8.md b/doc/iidxhook/iidxhook8.md new file mode 100644 index 0000000..9a6b39d --- /dev/null +++ b/doc/iidxhook/iidxhook8.md @@ -0,0 +1,195 @@ +# Game list + +The following games are compatible with this version of iidxhook: +* CANNON BALLERS + +# Data setup and running the game + +## Supported versions of Windows + +This version requires at least Win 7 x64 and will not run, like the +former versions, on Win XP x86! + +## Dependencies + +Make sure to have the following dependencies installed: +* DirectX 9 +* Visual C++ 2010 Redistributable Package (x64) + +## Data setup + +We assume that you are using a clean/vanilla data dump. Ensure your ("concents") +folder with your unpacked data looks like this: +- data +- modules +- prop + +* Copy/Move all files from the *modules* directory to the root folder, so they +are located next to the *data* and *prop* folders. +* Copy all files from *prop/defaults* to the *prop* folder. +* Create a new file *app-config.xml* in the *prop* folder with the following +content: +``` + + +``` +* Setup proper paths for *dev/nvram* and *dev/raw* in *prop/avs-config.xml* by +replacing the **-block in that file with the following block: +``` + + + . + + + + + + 256 + 256 + +``` +* Unpack the package containing iidxhook8 into the root folder so iidxhook8.dll +and all other files are located in the same folder as *data*, *prop*, +*bm2dx.dll*, etc. +* Run the gamestart-XX.bat file as admin. Where XX matches the version you +want to run. + +# Configuring iidxhook + +The hook library can be configured via cmd arguments or a configuration file. +The latter is generated (*iidxhook-XX.conf* in the same directory) on the first +start of the game using the gamestart-XX.bat file (again, XX matches your target +game version). It contains default values for all available parameters and +comments explaining each parameter. Please follow the comments when configuring +your setup. + +Add the argument *-h* when running gamestart-XX.bat +(e.g. *gamestart-XX.bat -h*) to print help/usage information with a list of +all available parameters. Every parameter can be either set as command line +argument or using a configuration file. + +To set a parameter from the command line, just add it as an argument after +the bat file like this +``` +gamestart-09.bat -p gfx.windowed=true -p gfx.framed=true +``` + +The syntax for the "key=value" is the same as in the config file. Make sure +to have a pre-ceeding "-p" for every parameter added. + +However, if a parameter is specifed in the configuration file and as a command +line argument, the command line argument overrides the config file's value. + +# Eamuse network setup + +If you want to run the games online, you need a valid PCBID and the service URL. +Open *prop/ea3-config.xml* and set the values of the *ea3/id/pcbid* and +*ea3/network/services* nodes accordingly. + +Run the game with the gamestart-XX.bat file and enable network on the operator +menu. When enabled, the game seems to hang and expects you to power +cycle the machine (i.e. quit the game and restart it). + +# Real hardware support + +### BIO2 hardware + +Set the *io.disable_bio2_emu* configuration value to *1* to disable BIO2 +emulation to run the game using real BIO2 hardware. + +### Ezusb and other + +Use the specific iidxio API implementations, e.g. iidxio-ezusb2.dll to use +the IO2 EZUSB board, to run the game on real hardware. Thanks to a common +abstraction layer, you can also use custom IO boards or whatever Konami hardware +is going to be available in the future. Obviously, someone has to write a +driver, first. + +## Slotted/Wave pass card readers + +Replace the default *eamio.dll* with the *eamio-icca.dll* and have either your +slotted (IIDX, DDR Supernova or GF/DM type) or new wave pass card readers +conencted and and assigned to *COM1*. + +### ICCA device settings (device manager) +* Port: COM1 +* BAUD rate: 57600 +* Data bits: 8 +* Parity: None +* Stop bits: 1 +* Flow control: None + +If you encounter issues after the game opened the device, e.g. application +stuck, try a USB <-> COM dongle instead of using one of the COM ports of the +mainboard. + +# Troubleshooting and FAQ + +## The game does not run "well" (frame drops, drifting offsync etc) +This can be related to various issues: +* Make sure to run the game as (true) Administrator especially on Windows 7 and +newer. This will also get rid of various other errors (see below) that are +related to permission issues. +* Run the game's process with a higher priority: +``` +start "" /relatime "gamestart.bat" +``` +* Enforce v-sync enabled in your GPU settings. +* Ensure that you have a constant refresh rate around the 60 hz (59.9xx or 60.0xx) +that is not jumping around. Use the timebase feature of one of the newer games to +check that or enable iidxhook's timebase and check the log output for the +determined value. Run this a few times and check if the results differ. + +## "NETWORK WARNING" instead of "NETWORK OK" +This can be caused by: +* Invalid PCBID +* Firewall blocking connections +* Invalid eamuse url or port specified +* Game is not run using the Administrator account +Make sure to check these things first + +## My songs are offsync +From IIDX 20 (or Lincle very final revision) onwards, the game comes with +a built-in auto timebase option ("monitor check" on startup) which +dynamically, detects the refresh rate of your current setup. Thus, BT5's +timebase option is not included from this hook version onwards, anymore. +Ensure that refresh rate displayed is very stable, e.g. 60.00x hz, and the +game should be able to provide you with a smooth and sync game experience. + +## My game runs too fast +iidxhook can limit the frame rate for you (refer to help/config file) + +## My game crashes when I try fullscreen +Use dxwnd and set settings like "Acquire admin caps" and "Fullscreen only" + +## Background videos aren't working. When starting a song, windows is playing the error sound and a message box appears +You are missing a codec to decode and play the videos. There are different +methods available to get background videos working. Probably, the easiest +solution: grab the CLVSD.ax file and go to Start -> Run -> regsvr32 clvsd.ax +Make sure to run cmd.exe as Administrator, otherwise you will get errors caused +by invalid permissions. + +## I installed the CLVSD.ax codec but the game crashes or displays a message box that tells me to disable my debugger +If songs keep crashing upon start and you get an error message that says +``` +DirectShow Texture3D Sample +Could not create source filter to graph! hr=0x80040266 +``` +despite having the codec (CLVSD.ax) installed, remove the debug flag (*-D*) +from gamestart or use a CLVSD.ax codec which has the debugger checks removed. + +## I am getting a message box with a japanese error message and a black window immediately after starting the game +The game checks the vendor and product ID of your GPU installed. If it doesn't +match a hardcoded whitelist, the game won't boot. Use the option *gfx.pci_id* +either in the config file or as a cmd argument to spoof these IDs. See the +help message for instructions and possible IDs. + +## Over-/underscan, bad image quality or latency caused by my monitor's/TV's upscaler +Many modern monitors/TVs cannot upscale some lower resolutions, e.g. 640x480, properly. +This can lead to over-/underscan, bad image quality or even latency caused by the upscaler +of the device you are using. +If one or multiple of these issues apply, use the built in scaling options by setting +*gfx.scale_back_buffer_width* and *gfx.scale_back_buffer_height* to a target resolution +to scale to. Usually, you want to set this to the monitor's native resolution, e.g. +1920x1080 for full HD. You can play around with a few different filters using +*gfx.scale_back_buffer_filter* which impacts image quality/blurriness on upscaling. diff --git a/doc/iidxhook/iidxio-ezusb.md b/doc/iidxhook/iidxio-ezusb.md new file mode 100644 index 0000000..ca893ce --- /dev/null +++ b/doc/iidxhook/iidxio-ezusb.md @@ -0,0 +1,28 @@ +This library drives a "legacy" ezusb IO board, also known as C02 IO, and +implements the iidxio API of BT5. Thus, it allows you to use this IO board with +*any* version of IIDX that is supported by BT5. + +# Setup +* Rename iidxio-ezusb.dll to iidxio.dll. +* Ensure that your gamestart.bat actually injects the appropriate iidxhook dll, +for example: +``` +*inject iidxhook3.dll bm2dx.exe ...* +``` +or +``` +launcher -K iidxhook4.dll bm2dx.dll ...* +``` +* Before running the game, you have to flash a set of binaries to your IO board +(base firmware and FPGA). The iidxio-ezusb.dll does NOT take care of this and +only drives the hardware during gameplay. The binary images required are not +included with BT5. +* Use the ezusb-tool.exe binary included in the tools sub-package to flash the +appropriate ezusb base firmware. Once the firmware is flashed successfully, +the status LEDs on the side of the board should show a blinking pattern. +* Use the ezusb-iidx-fpga-flash.exe binary to flash the appropriate FPGA binary +dump to the FPGA. +* There is a script called ezusb-boot.bat which combines the two steps above +and can be integrated into the startup process of a dedicated setup. +* If you ignore these steps, you will either run into errors or parts of the +IO board won't work (e.g. lights). \ No newline at end of file diff --git a/doc/iidxhook/iidxio-ezusb2.md b/doc/iidxhook/iidxio-ezusb2.md new file mode 100644 index 0000000..019f05b --- /dev/null +++ b/doc/iidxhook/iidxio-ezusb2.md @@ -0,0 +1,27 @@ +This library drives the ezusb FX2 IO board, also known as IO2, and +implements the iidxio API of BT5. Thus, it allows you to use this IO2 board with +*any* version of IIDX that is supported by BT5. + +# Setup +* Rename iidxio-ezusb2.dll to iidxio.dll. +* Ensure that your gamestart.bat actually injects the appropriate iidxhook dll, +for example: +``` +*inject iidxhook3.dll bm2dx.exe ...* +``` +or +``` +launcher -K iidxhook4.dll bm2dx.dll ...* +``` +* Before running the game, you have to flash the appropriate firmware to your +IO board. The iidxio-ezusb2.dll does NOT take care of this and only drives the +hardware during gameplay. The binary image required is not included with BT5. +* Use the ezusb2-tool.exe binary included in the tools sub-package to first scan +for the device path of your connected hardware. Then, use the device path to +flash the appropriate ezusb base firmware. Once the firmware is flashed +successfully, the status LEDs on the side of the board should show a blinking +pattern. +* There is a script called ezusb2-boot.bat which combines the two steps above +and can be integrated into the startup process of a dedicated setup. +* If you ignore these steps, your IO board won't work with our iidxio +implementation. \ No newline at end of file diff --git a/doc/jbhook/jbhook.md b/doc/jbhook/jbhook.md new file mode 100644 index 0000000..9fe2252 --- /dev/null +++ b/doc/jbhook/jbhook.md @@ -0,0 +1,31 @@ +# Game list + +The following games are compatible with this version of jbhook: +* saucer +* prop +* qubell + +# Data setup and running the game + +Ensure your folder with your unpacked data looks like this: +- data +- prop +- Various dll files including jubeat.dll + +Unpack the package containing jbhook into the folder containing the jubeat.dll +file. + +Run the gamestart.bat file. + +# Eamuse network setup + +* Open the prop/ea3-config.xml +* Replace the *ea3/network/services* URL with network service URL of your +choice (for example http://my.eamuse.com) +* Edit the *ea3/id/pcbid* + +# Real hardware support + +Run the launcher without the hook dll: *launcher jubeat.dll* + +# Troubleshooting and FAQ \ No newline at end of file diff --git a/doc/tools/aciotest.md b/doc/tools/aciotest.md new file mode 100644 index 0000000..f3fcf84 --- /dev/null +++ b/doc/tools/aciotest.md @@ -0,0 +1,2 @@ +Test your real ACIO hardware connected to your machine using this tool. Just +execute it and follow the usage instructions. \ No newline at end of file diff --git a/doc/tools/eamiotest.md b/doc/tools/eamiotest.md new file mode 100644 index 0000000..9f07ba1 --- /dev/null +++ b/doc/tools/eamiotest.md @@ -0,0 +1,3 @@ +Testing tool for development of eamio libraries used for emulating card reader +hardware on bemanitools. Just place eamiotest.exe and the eamio.dll your +custom eamio.dll in the same folder and run eamiotest.exe. \ No newline at end of file diff --git a/doc/tools/ezusb-iidx-fpga-flash.md b/doc/tools/ezusb-iidx-fpga-flash.md new file mode 100644 index 0000000..c7ae048 --- /dev/null +++ b/doc/tools/ezusb-iidx-fpga-flash.md @@ -0,0 +1,6 @@ +Flash a binary FPGA binary (not hex) firmware image to the FPGA of a EZUSB +board. This assumes that your IO board is already flashed using a base +firmware image (either by the game itself or using the *ezusb-tool* +application). Just call the executable and follow the usage instructions. +The type *v1* refers to the first gen protocol used from iidx 9 to 13 and *v2* +to the second gen protocol used from iidx 14 onwards. \ No newline at end of file diff --git a/doc/tools/ezusb-iidx-sram-flash.md b/doc/tools/ezusb-iidx-sram-flash.md new file mode 100644 index 0000000..674b5e7 --- /dev/null +++ b/doc/tools/ezusb-iidx-sram-flash.md @@ -0,0 +1,4 @@ +Tool to write a binary (not hex) image to the SRAM of a ezusb board. +Just run the executable and follow the usage instructions. Ensure that the +correct base firmware supporting SRAM is flashed to the board prior using this +(either by the game or using the *ezusb-tool* application). \ No newline at end of file diff --git a/doc/tools/ezusb-tool.md b/doc/tools/ezusb-tool.md new file mode 100644 index 0000000..f5f5aa4 --- /dev/null +++ b/doc/tools/ezusb-tool.md @@ -0,0 +1,6 @@ +Tool for fundamental legacy EZUSB management tasks, e.g. scanning for connected +devices, querying basic device info (vid, pid, name) and flashing firmware. +This tool requires the "cyusb" driver to be installed and does NOT work with the +"cyusb3" driver and thus not on anything newer than WinXP. + +Just run the tool without any arguments to get usage information. \ No newline at end of file diff --git a/doc/tools/ezusb2-dbg-hook.md b/doc/tools/ezusb2-dbg-hook.md new file mode 100644 index 0000000..81e22cf --- /dev/null +++ b/doc/tools/ezusb2-dbg-hook.md @@ -0,0 +1,22 @@ +A hook library for debugging and dumping usb requests of games that use the +ezusb IO board (IIDX C02, IIDX IO2, Pop'n Music IO2). The library creates +a log file *ezusbdbg.log* in the same directory as your library/executable +which contains data dumps of the usb device's traffic. + +Example usage: +*inject iidxhook1.dll ezusbdbg-hook.dll bm2dx.exe ...* +*launcher -K ezusbdbg-hook.dll bm2dx.dll ...* + +Make sure to provide the following additional arguments: +*--ezusbdbg_path * +The device path points to the path to open the device, e.g. on the old IIDX +games that was *"\\\\.\\Ezusb-0"*. If you don't know the path, you can run +the hook with dummy data, e.g. *--ezusbdbg_path asdfgqwer* and check the log +for any logged open calls with paths to find your ezusb device. + +--ezusbdbg_type <1 or 2> +Specify the type of device to debug. *1* is for the legacy ezusb device, e.g. +IIDX C02, and *2* for the FX2 type device, e.g. IIDX IO2, Pop'n IO2. + +Both parameters must be specified, otherwise the hook will error. Make sure +to check the logfile for any errors or warnings as well. \ No newline at end of file diff --git a/doc/tools/ezusb2-tool.md b/doc/tools/ezusb2-tool.md new file mode 100644 index 0000000..83bd68f --- /dev/null +++ b/doc/tools/ezusb2-tool.md @@ -0,0 +1,6 @@ +Tool for fundamental EZUSB FX2 management tasks, e.g. scanning for connected +devices, querying basic device info (vid, pid, name) and flashing firmware. This +tool requires the newer "cyusb3" driver and does NOT work with the old "ezusb" +driver. + +Just run the tool without any arguments to get usage information. \ No newline at end of file diff --git a/doc/tools/iidx-ezusb-exit-hook.md b/doc/tools/iidx-ezusb-exit-hook.md new file mode 100644 index 0000000..9b0f50b --- /dev/null +++ b/doc/tools/iidx-ezusb-exit-hook.md @@ -0,0 +1,12 @@ +A hook library that can be used with BeatmaniaIIDX games that run the old ezusb +IO board (e.g. 9-13). It allows you to exit the game by pressing Start P1 + +Start P2 + VEFX + Effect simultaneously. This is very useful if you want an +option to exit back to your desktop without having a keyboard attached. + +The exit hook lib must be loaded like any other hook lib you are already +injecting to the game using *inject*. The order for the hook libs is important +as they are loaded in the order specified for the inject call. The entry in +the *gamestart.bat* file should look like this: +*inject iidxhook1.dll iidx-ezusb-exit-hook.dll bm2dx.exe ...* + +Where iidxhook1 is used for version 9-12. Use iidxhook2 for version 13. \ No newline at end of file diff --git a/doc/tools/iidx-ezusb2-exit-hook.md b/doc/tools/iidx-ezusb2-exit-hook.md new file mode 100644 index 0000000..d05fb34 --- /dev/null +++ b/doc/tools/iidx-ezusb2-exit-hook.md @@ -0,0 +1,15 @@ +A hook library that can be used with BeatmaniaIIDX games that run the ezusb +FX2 IO board (e.g. 14-24). It allows you to exit the game by pressing Start P1 + +Start P2 + VEFX + Effect simultaneously. This is very useful if you want an +option to exit back to your desktop without having a keyboard attached. + +The exit hook lib must be loaded like any other hook lib you are already +injecting to the game using either *inject* or *launcher* (depending on the game +version). The order for the hook libs is important as they are loaded in the +order specified for the inject/launcher call. The entry in the *gamestart.bat* +file should look like this for inject: +*inject iidxhook3.dll -K iidx-ezusb2-exit-hook.dll bm2dx.exe ...* +...and for launcher: +*launcher -K iidxhook4.dll -K iidx-ezusb2-exit-hook.dll bm2dx.dll ...* + +Where iidxhook3 is used for 14-15 and iidxhook4 for 20-24. \ No newline at end of file diff --git a/doc/tools/iidxiotest.md b/doc/tools/iidxiotest.md new file mode 100644 index 0000000..0e8d132 --- /dev/null +++ b/doc/tools/iidxiotest.md @@ -0,0 +1,3 @@ +Testing tool for development of iidxio libraries used for emulating the main io +hardware on bemanitools for BeatmaniaIIDX. Just place iidxiotest.exe and your +custom iidxio.dll in the same folder and run iidxiotest.exe. \ No newline at end of file diff --git a/doc/tools/mempatch-hook.md b/doc/tools/mempatch-hook.md new file mode 100644 index 0000000..dfd6890 --- /dev/null +++ b/doc/tools/mempatch-hook.md @@ -0,0 +1,72 @@ +# A simple memory patching hook + +This is a hook which can be passed along with other hooks to be injected into +the target application of your choice (e.g. when using inject or launcher). It +allows you to patch raw memory contents of either the target application or +any libraries loaded with it. No static hex-edits anymore. Instead, create a +simple script file and also document your patches for others which allows them +to easily disable/enable them. + +# Setup + +Copy the *mempatch-hook.dll* to the target application of your choice and add +it to the list of libraries to inject: + +Example when using *inject.exe*: +``` +inject iidxhook3.dll mempatch-hook.dll bm2dx.exe --options iidxhook-16.conf --mempatch myPatch.mph %* +``` + +When using *launcher.exe*: +``` +launcher -K iidxhook4.dll -K mempatch-hook.dll bm2dx.dll --options iidxhook.conf --mempatch myPatch.mph %* +``` + +To load a patch script, add the *--mempatch * argument (as +shown above in the example). You can specify this more than once which allows +you to apply multiple scripts in order, e.g. +*--mempatch myPatch1.mph --mempatch myPatch1.mph*. + +# Patch script format + +A patch script is a simple list of items seperated by a newline character +(i.e. one item = one line). Example script file: + +``` +# This is a comment +# Use comments to document your patches and make the script useful for others + +# Empty lines are allowed as well and skipped by the patcher + +# All numbers specified are hex format only. + +# First entry which gets processed by the patcher. One entry specifies a single +# patch to apply starting a the specified address +# The first parameter (bm2dx.exe) is the base address. Specify the exe name +# of the application for relative addreses to patch inside the exe. You can +# also specify dlls loaded by the target application (e.g. libacio.dll) +# +# The second parameter (137C4C) is the offset. The target address for this patch +# is bm2dx.exe + 137C4C (bm2dx.exe commonly resolves to 400000) -> 5137C4C +# +# The third parameter is the data to patch at the target location. This byte hex +# string can have an arbitrary even length. +# +# The fourth parameter is optional and allows you to specify the expected data +# at the target loation before patching. This gives you the chance to add some +# sort of checksum'ing for the patches if you want. +bm2dx.exe 137C4C 2121212121 4540 + +# The first parameter can also be - which resolves to a base memory address of +# 0 for the loaded application. +# So the target address here is 0 + 537C4C = 537C4C +# +# The fourth parameter isn't used here (optional) +- 537C4C 2222212122 + +# You can also set the third parameter to '-' which means no data and disables +# the patching. This allows you to use the fourth parameter and execute a +# memory check, only. This can be used for signiture checking of the target +# application +bm2dx.exe 137C4C - 4540 +``` \ No newline at end of file diff --git a/doc/tools/pcbidgen.md b/doc/tools/pcbidgen.md new file mode 100644 index 0000000..64670ea --- /dev/null +++ b/doc/tools/pcbidgen.md @@ -0,0 +1,2 @@ +Tool to generate random and (checksum) valid PCBIDs used on various Konami +Arcade games. \ No newline at end of file diff --git a/run-tests-wine.sh b/run-tests-wine.sh new file mode 100755 index 0000000..dd80e87 --- /dev/null +++ b/run-tests-wine.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +cd build +unzip -o tests.zip -d tests +cd tests +chmod +x run-tests.sh +./run-tests.sh diff --git a/src/imports/SMX.h b/src/imports/SMX.h new file mode 100644 index 0000000..3e4459e --- /dev/null +++ b/src/imports/SMX.h @@ -0,0 +1,263 @@ +#ifndef SMX_H +#define SMX_H + +#ifndef __cplusplus +#include +#endif + +#include + +#ifdef SMX_EXPORTS +#define SMX_API __declspec(dllexport) +#else +#define SMX_API __declspec(dllimport) +#endif + +#ifdef __cplusplus +#define SMX_EXTERN_C extern "C" +#else +#define SMX_EXTERN_C +#endif + +struct SMXInfo; +struct SMXConfig; +enum SensorTestMode; +enum SMXUpdateCallbackReason; +struct SMXSensorTestModeData; + +// All functions are nonblocking. Getters will return the most recent state. Setters will +// return immediately and do their work in the background. No functions return errors, and +// setting data on a pad which isn't connected will have no effect. + +// Initialize, and start searching for devices. +// +// UpdateCallback will be called when something happens: connection or disconnection, inputs +// changed, configuration updated, test data updated, etc. It doesn't specify what's changed, +// and the user should check all state that it's interested in. +// +// This is called asynchronously from a helper thread, so the receiver must be thread-safe. +typedef void SMXUpdateCallback(int pad, enum SMXUpdateCallbackReason reason, void *pUser); +SMX_EXTERN_C SMX_API void SMX_Start(SMXUpdateCallback UpdateCallback, void *pUser); + +// Shut down and disconnect from all devices. This will wait for any user callbacks to complete, +// and no user callbacks will be called after this returns. This must not be called from within +// the update callback. +SMX_EXTERN_C SMX_API void SMX_Stop(); + +// Set a function to receive diagnostic logs. By default, logs are written to stdout. +// This can be called before SMX_Start, so it affects any logs sent during initialization. +typedef void SMXLogCallback(const char *log); +SMX_EXTERN_C SMX_API void SMX_SetLogCallback(SMXLogCallback callback); + +// Get info about a pad. Use this to detect which pads are currently connected. +SMX_EXTERN_C SMX_API void SMX_GetInfo(int pad, struct SMXInfo *info); + +// Get a mask of the currently pressed panels. +SMX_EXTERN_C SMX_API uint16_t SMX_GetInputState(int pad); + +// Update the lights. Both pads are always updated together. lightsData is a list of 8-bit RGB +// colors, one for each LED. Each panel has lights in the following order: +// +// 0123 +// 4567 +// 89AB +// CDEF +// +// Panels are in the following order: +// +// 012 9AB +// 345 CDE +// 678 F01 +// +// With 18 panels, 16 LEDs per panel and 3 bytes per LED, each light update has 864 bytes of data. +// +// Lights will update at up to 30 FPS. If lights data is sent more quickly, a best effort will be +// made to send the most recent lights data available, but the panels won't update more quickly. +// +// The panels will return to automatic lighting if no lights are received for a while, so applications +// controlling lights should send light updates continually, even if the lights aren't changing. +SMX_EXTERN_C SMX_API void SMX_SetLights(const char lightsData[864]); + +// By default, the panels light automatically when stepped on. If a lights command is sent by +// the application, this stops happening to allow the application to fully control lighting. +// If no lights update is received for a few seconds, automatic lighting is reenabled by the +// panels. +// +// SMX_ReenableAutoLights can be called to immediately reenable auto-lighting, without waiting +// for the timeout period to elapse. Games don't need to call this, since the panels will return +// to auto-lighting mode automatically after a brief period of no updates. +SMX_EXTERN_C SMX_API void SMX_ReenableAutoLights(); + +// Get the current controller's configuration. +// +// Return true if a configuration is available. If false is returned, no panel is connected +// and no data will be set. +SMX_EXTERN_C SMX_API bool SMX_GetConfig(int pad, struct SMXConfig *config); + +// Update the current controller's configuration. This doesn't block, and the new configuration will +// be sent in the background. SMX_GetConfig will return the new configuration as soon as this call +// returns, without waiting for it to actually be sent to the controller. +SMX_EXTERN_C SMX_API void SMX_SetConfig(int pad, const struct SMXConfig *config); + +// Reset a pad to its original configuration. +SMX_EXTERN_C SMX_API void SMX_FactoryReset(int pad); + +// Request an immediate panel recalibration. This is normally not necessary, but can be helpful +// for diagnostics. +SMX_EXTERN_C SMX_API void SMX_ForceRecalibration(int pad); + +// Set a panel test mode and request test data. This is used by the configuration tool. +SMX_EXTERN_C SMX_API void SMX_SetTestMode(int pad, enum SensorTestMode mode); +SMX_EXTERN_C SMX_API bool SMX_GetTestData(int pad, struct SMXSensorTestModeData *data); + +// Return the build version of the DLL, which is based on the git tag at build time. This +// is only intended for diagnostic logging, and it's also the version we show in SMXConfig. +SMX_EXTERN_C SMX_API const char *SMX_Version(); + +// General info about a connected controller. This can be retrieved with SMX_GetInfo. +struct SMXInfo +{ + // True if we're fully connected to this controller. If this is false, the other + // fields won't be set. + bool m_bConnected; + + // This device's serial number. This can be used to distinguish devices from each + // other if more than one is connected. This is a null-terminated string instead + // of a C++ string for C# marshalling. + char m_Serial[33]; + + // This device's firmware version. + uint16_t m_iFirmwareVersion; +}; + +enum SMXUpdateCallbackReason { + // This is called when a generic state change happens: connection or disconnection, inputs changed, + // test data updated, etc. It doesn't specify what's changed. We simply check the whole state. + SMXUpdateCallback_Updated, + + // This is called when SMX_FactoryReset completes, indicating that SMX_GetConfig will now return + // the reset configuration. + SMXUpdateCallback_FactoryResetCommandComplete +}; + +// The configuration for a connected controller. This can be retrieved with SMX_GetConfig +// and modified with SMX_SetConfig. +// +// The order and packing of this struct corresponds to the configuration packet sent to +// the master controller, so it must not be changed. +struct SMXConfig +{ +#if 0 + // These fields are unused and must be left at their existing values. + uint8_t unused1 = 0xFF, unused2 = 0xFF; + uint8_t unused3 = 0xFF, unused4 = 0xFF; + uint8_t unused5 = 0xFF, unused6 = 0xFF; + + // Panel thresholds are labelled by their numpad position, eg. Panel8 is up. + // If m_iFirmwareVersion is 1, Panel7 corresponds to all of up, down, left and + // right, and Panel2 corresponds to UpLeft, UpRight, DownLeft and DownRight. For + // later firmware versions, each panel is configured independently. + // + // Setting a value to 0xFF disables that threshold. + uint16_t masterDebounceMilliseconds = 0; + uint8_t panelThreshold7Low = 0xFF, panelThreshold7High = 0xFF; // was "cardinal" + uint8_t panelThreshold4Low = 0xFF, panelThreshold4High = 0xFF; // was "center" + uint8_t panelThreshold2Low = 0xFF, panelThreshold2High = 0xFF; // was "corner" + + // These are internal tunables and should be left unchanged. + uint16_t panelDebounceMicroseconds = 4000; + uint16_t autoCalibrationPeriodMilliseconds = 1000; + uint8_t autoCalibrationMaxDeviation = 100; + uint8_t badSensorMinimumDelaySeconds = 15; + uint16_t autoCalibrationAveragesPerUpdate = 60; + + uint8_t unused7 = 0xFF, unused8 = 0xFF; + + uint8_t panelThreshold1Low = 0xFF, panelThreshold1High = 0xFF; // was "up" + + // Which sensors on each panel to enable. This can be used to disable sensors that + // we know aren't populated. This is packed, with four sensors on two pads per byte: + // enabledSensors[0] & 1 is the first sensor on the first pad, and so on. + uint8_t enabledSensors[5]; + + // How long the master controller will wait for a lights command before assuming the + // game has gone away and resume auto-lights. This is in 128ms units. + uint8_t autoLightsTimeout = 1000/128; // 1 second + + // The color to use for each panel when auto-lighting in master mode. This doesn't + // apply when the pads are in autonomous lighting mode (no master), since they don't + // store any configuration by themselves. These colors should be scaled to the 0-170 + // range. + uint8_t stepColor[3*9]; + + // The rotation of the panel, where 0 is the standard rotation, 1 means the panel is + // rotated right 90 degrees, 2 is rotated 180 degrees, and 3 is rotated 270 degrees. + // This value is unused. + uint8_t panelRotation; + + // This is an internal tunable that should be left unchanged. + uint16_t autoCalibrationSamplesPerAverage = 500; + + // The firmware version of the master controller. Where supported (version 2 and up), this + // will always read back the firmware version. This will default to 0xFF on version 1, and + // we'll always write 0xFF here so it doesn't change on that firmware version. + // + // We don't need this since we can read the "I" command which also reports the version, but + // this allows panels to also know the master version. + uint8_t masterVersion = 0xFF; + + // The version of this config packet. This can be used by the firmware to know which values + // have been filled in. Any values not filled in will always be 0xFF, which can be tested + // for, but that doesn't work for values where 0xFF is a valid value. This value is unrelated + // to the firmware version, and just indicates which fields in this packet have been set. + // Note that we don't need to increase this any time we add a field, only when it's important + // that we be able to tell if a field is set or not. + // + // Versions: + // - 0xFF: This is a config packet from before configVersion was added. + // - 0x00: configVersion added + // - 0x02: panelThreshold0Low through panelThreshold8High added + uint8_t configVersion = 0x02; + + // The remaining thresholds (configVersion >= 2). + uint8_t unused9[10]; + uint8_t panelThreshold0Low, panelThreshold0High; + uint8_t panelThreshold3Low, panelThreshold3High; + uint8_t panelThreshold5Low, panelThreshold5High; + uint8_t panelThreshold6Low, panelThreshold6High; + uint8_t panelThreshold8Low, panelThreshold8High; +#endif +}; +//static_assert(sizeof(SMXConfig) == 84, "Expected 84 bytes"); + +// The values (except for Off) correspond with the protocol and must not be changed. +enum SensorTestMode { + SensorTestMode_Off = 0, + // Return the raw, uncalibrated value of each sensor. + SensorTestMode_UncalibratedValues = '0', + + // Return the calibrated value of each sensor. + SensorTestMode_CalibratedValues = '1', + + // Return the sensor noise value. + SensorTestMode_Noise = '2', + + // Return the sensor tare value. + SensorTestMode_Tare = '3', +}; + +// Data for the current SensorTestMode. The interpretation of sensorLevel depends on the mode. +struct SMXSensorTestModeData +{ + // If false, sensorLevel[n][*] is zero because we didn't receive a response from that panel. + bool bHaveDataFromPanel[9]; + + int16_t sensorLevel[9][4]; + bool bBadSensorInput[9][4]; + + // The DIP switch settings on each panel. This is used for diagnostics + // displays. + int iDIPSwitchPerPanel[9]; +}; + +#endif diff --git a/src/imports/avs-ea3.h b/src/imports/avs-ea3.h new file mode 100644 index 0000000..d6969b5 --- /dev/null +++ b/src/imports/avs-ea3.h @@ -0,0 +1,7 @@ +#ifndef IMPORTS_AVS_EA3_H +#define IMPORTS_AVS_EA3_H + +void ea3_boot(struct property_node *conf); +void ea3_shutdown(void); + +#endif diff --git a/src/imports/avs.h b/src/imports/avs.h new file mode 100644 index 0000000..589863a --- /dev/null +++ b/src/imports/avs.h @@ -0,0 +1,202 @@ +#ifndef IMPORTS_AVS_H +#define IMPORTS_AVS_H + +#include +#include +#include + +enum property_create_flag { + PROPERTY_FLAG_READ = 0x1, + PROPERTY_FLAG_WRITE = 0x2, + PROPERTY_FLAG_CREATE = 0x4, + PROPERTY_FLAG_BINARY = 0x8, + PROPERTY_FLAG_APPEND = 0x10, +}; + +enum property_node_traversal { + TRAVERSE_PARENT = 0, + TRAVERSE_FIRST_CHILD = 1, + TRAVERSE_FIRST_ATTR = 2, + TRAVERSE_FIRST_SIBLING = 3, + TRAVERSE_NEXT_SIBLING = 4, + TRAVERSE_PREVIOUS_SIBLING = 5, + TRAVERSE_LAST_SIBLING = 6, + TRAVERSE_NEXT_SEARCH_RESULT = 7, + TRAVERSE_PREV_SEARCH_RESULT = 8, +}; + +enum property_type { + PROPERTY_TYPE_VOID = 1, + PROPERTY_TYPE_S8 = 2, + PROPERTY_TYPE_U8 = 3, + PROPERTY_TYPE_S16 = 4, + PROPERTY_TYPE_U16 = 5, + PROPERTY_TYPE_S32 = 6, + PROPERTY_TYPE_U32 = 7, + PROPERTY_TYPE_S64 = 8, + PROPERTY_TYPE_U64 = 9, + PROPERTY_TYPE_BIN = 10, + PROPERTY_TYPE_STR = 11 +}; + +struct property; +struct property_node; + +struct avs_net_interface { + uint8_t mac_addr[6]; + uint8_t unknown[30]; +}; + +enum psmap_type { + PSMAP_TYPE_S8 = 2, + PSMAP_TYPE_U8 = 3, + PSMAP_TYPE_S16 = 4, + PSMAP_TYPE_U16 = 5, + PSMAP_TYPE_S32 = 6, + PSMAP_TYPE_U32 = 7, + PSMAP_TYPE_S64 = 8, + PSMAP_TYPE_U64 = 9, + PSMAP_TYPE_STR = 10, + /* Used on avs 803 instead of value 10 */ + PSMAP_TYPE_STR_LEGACY = 11, + PSMAP_TYPE_ATTR = 45, + PSMAP_TYPE_BOOL = 50, +}; + +#define PSMAP_FLAG_HAVE_DEFAULT 0x01 + +struct property_psmap { + uint8_t type; + uint8_t flags; /* A guess. Might just be a bool. */ + uint16_t offset; + uint32_t size; + const char *path; + intptr_t xdefault; +}; + +#define PSMAP_BEGIN(name) \ + struct property_psmap name[] = { + +#define PSMAP_REQUIRED(type, xstruct, field, path) \ + { \ + type, \ + 0, \ + offsetof(xstruct, field), \ + sizeof( ((xstruct *) 0)->field ), \ + path, \ + 0, \ + }, \ + +#define PSMAP_OPTIONAL(type, xstruct, field, path, xdefault) \ + { \ + type, \ + PSMAP_FLAG_HAVE_DEFAULT, \ + offsetof(xstruct, field), \ + sizeof( ((xstruct *) 0)->field ), \ + path, \ + (intptr_t) xdefault, \ + }, \ + +#define PSMAP_END \ + { 0xFF, 0, 0, 0, NULL, 0 } \ + }; + +#if AVS_VERSION >= 1500 +# define AVS_LOG_WRITER(name, chars, nchars, ctx) \ + void name(const char * chars , uint32_t nchars , void * ctx ) + + typedef void (*avs_log_writer_t)(const char *chars, uint32_t nchars, + void *ctx); +#else +# define AVS_LOG_WRITER(name, chars, nchars, ctx) \ + void name(void * ctx , const char * chars , uint32_t nchars ) + + typedef void (*avs_log_writer_t)(void *ctx, const char *chars, + uint32_t nchars); +#endif + +typedef int (*avs_reader_t)(uint32_t context, void *bytes, size_t nbytes); + +#if AVS_VERSION >= 1600 +/* "avs" and "std" heaps have been unified */ +typedef void (*avs_boot_t)( + struct property_node *config, void *com_heap, size_t sz_com_heap, + void *reserved, avs_log_writer_t log_writer, void *log_context); + +void avs_boot( + struct property_node *config, void *com_heap, size_t sz_com_heap, + void *reserved, avs_log_writer_t log_writer, void *log_context); +#else +typedef void (*avs_boot_t)( + struct property_node *config, void *std_heap, size_t sz_std_heap, + void *avs_heap, size_t sz_avs_heap, avs_log_writer_t log_writer, + void* log_context); + +void avs_boot( + struct property_node *config, void *std_heap, size_t sz_std_heap, + void *avs_heap, size_t sz_avs_heap, avs_log_writer_t log_writer, + void *log_context); +#endif + +void avs_shutdown(void); + +void log_body_fatal(const char *module, const char *fmt, ...); +void log_body_info(const char *module, const char *fmt, ...); +void log_body_misc(const char *module, const char *fmt, ...); +void log_body_warning(const char *module, const char *fmt, ...); +void log_boot(avs_log_writer_t log_writer, void *log_context); +void log_change_level(int level); + +int avs_net_ctrl(int ioctl, void *bytes, uint32_t nbytes); + +int avs_thread_create(int (*proc)(void *), void *ctx, uint32_t sz_stack, + unsigned int priority); +void avs_thread_destroy(int thread_id); +void avs_thread_exit(int result); +void avs_thread_join(int thread_id, int *result); + +uint32_t property_read_query_memsize( + avs_reader_t reader, uint32_t context, int unk0, int unk1); +struct property *property_create( + int flags, void *buffer, uint32_t buffer_size); +struct property_node *property_search( + struct property *prop, struct property_node *root, const char *path); +int property_insert_read( + struct property *prop, struct property_node *root, avs_reader_t reader, + uint32_t context); +int property_mem_write(struct property *prop, void *bytes, int nbytes); +void *property_desc_to_buffer(struct property *prop); +void property_file_write(struct property *prop, const char *path); +int property_set_flag(struct property *prop, int flags, int mask); +void property_destroy(struct property *prop); + +int property_psmap_import(struct property *prop, struct property_node *root, + void *dest, const struct property_psmap *psmap); +int property_psmap_export(struct property *prop, struct property_node *root, + const void *src, const struct property_psmap *psmap); + +struct property_node *property_node_clone( + struct property *new_parent, int unk0, + struct property_node *src, bool deep); +struct property_node *property_node_create( + struct property *prop, struct property_node *parent, int type, + const char *key, ...); +void property_node_name( + struct property_node *node, char *chars, int nchars); +const char *property_node_refdata(struct property_node *node); +int property_node_refer(struct property *prop, + struct property_node *node, const char *name, + enum property_type type, void *bytes, uint32_t nbytes); +void property_node_remove(struct property_node *node); +enum property_type property_node_type( + struct property_node *node); +struct property_node *property_node_traversal( + struct property_node *node, enum property_node_traversal direction); +void property_node_datasize(struct property_node* node); + +bool std_getenv(const char *key, char *val, uint32_t nbytes); +void std_setenv(const char *key, const char *val); + +void *avs_fs_mount (char* mountpoint, char* fsroot, void* fstype, int flags); + +#endif diff --git a/src/imports/eapki.h b/src/imports/eapki.h new file mode 100644 index 0000000..b000ec6 --- /dev/null +++ b/src/imports/eapki.h @@ -0,0 +1,11 @@ +#ifndef IMPORTS_EAPKI_H +#define IMPORTS_EAPKI_H + +#include + +#include "imports/avs.h" + +typedef bool (*dll_entry_init_t)(char *, struct property_node *); +typedef bool (*dll_entry_main_t)(void); + +#endif diff --git a/src/imports/import_32_0_avs-ea3.def b/src/imports/import_32_0_avs-ea3.def new file mode 100644 index 0000000..577276d --- /dev/null +++ b/src/imports/import_32_0_avs-ea3.def @@ -0,0 +1,5 @@ +LIBRARY libavs-win32-ea3 + +EXPORTS + ea3_boot + ea3_shutdown diff --git a/src/imports/import_32_0_avs.def b/src/imports/import_32_0_avs.def new file mode 100644 index 0000000..cb2ea9d --- /dev/null +++ b/src/imports/import_32_0_avs.def @@ -0,0 +1,36 @@ +LIBRARY libavs-win32 + +EXPORTS + avs_boot + avs_net_ctrl + avs_shutdown + avs_thread_create + avs_thread_destroy + avs_thread_exit + avs_thread_join + log_body_fatal + log_body_info + log_body_misc + log_body_warning + log_boot + log_change_level + property_create + property_desc_to_buffer + property_destroy + property_file_write + property_insert_read + property_mem_write + property_read_query_memsize + property_search + property_set_flag + property_node_clone + property_node_create + property_node_name + property_node_refer + property_node_remove + property_node_type + property_node_traversal + property_node_refdata + std_getenv + std_setenv + diff --git a/src/imports/import_32_1101_avs-ea3.def b/src/imports/import_32_1101_avs-ea3.def new file mode 100644 index 0000000..169618e --- /dev/null +++ b/src/imports/import_32_1101_avs-ea3.def @@ -0,0 +1,5 @@ +LIBRARY libavs-win32-ea3 + +EXPORTS + ea3_boot @47 NONAME + ea3_shutdown @130 NONAME diff --git a/src/imports/import_32_1101_avs.def b/src/imports/import_32_1101_avs.def new file mode 100644 index 0000000..1b63626 --- /dev/null +++ b/src/imports/import_32_1101_avs.def @@ -0,0 +1,28 @@ +LIBRARY libavs-win32 + +EXPORTS + avs_boot @22 NONAME + avs_net_ctrl @107 NONAME + avs_shutdown @140 NONAME + avs_thread_create @156 NONAME + avs_thread_destroy @158 NONAME + avs_thread_exit @159 NONAME + avs_thread_join @161 NONAME + log_assert_body @196 NONAME + log_body_misc @199 NONAME + log_body_info @198 NONAME + log_body_warning @200 NONAME + log_body_fatal @197 NONAME + property_create @245 NONAME + property_desc_to_buffer @246 NONAME + property_destroy @247 NONAME + property_insert_read @255 NONAME + property_node_create @266 NONAME + property_node_refer @278 NONAME + property_node_remove @279 NONAME + property_psmap_import @288 NONAME + property_psmap_export @287 NONAME + property_read_query_memsize @291 NONAME + property_search @294 NONAME + std_getenv @308 NONAME + std_setenv @322 NONAME diff --git a/src/imports/import_32_1304_avs-ea3.def b/src/imports/import_32_1304_avs-ea3.def new file mode 100644 index 0000000..2606209 --- /dev/null +++ b/src/imports/import_32_1304_avs-ea3.def @@ -0,0 +1,5 @@ +LIBRARY libavs-win32-ea3 + +EXPORTS + ea3_boot @94 NONAME + ea3_shutdown @97 NONAME diff --git a/src/imports/import_32_1304_avs.def b/src/imports/import_32_1304_avs.def new file mode 100644 index 0000000..5a98e5a --- /dev/null +++ b/src/imports/import_32_1304_avs.def @@ -0,0 +1,27 @@ +LIBRARY libavs-win32 + +EXPORTS + avs_boot @237 NONAME + avs_net_ctrl @15 NONAME + avs_shutdown @333 NONAME + avs_thread_create @183 NONAME + avs_thread_destroy @76 NONAME + avs_thread_exit @147 NONAME + avs_thread_join @92 NONAME + log_body_misc @44 NONAME + log_body_info @339 NONAME + log_body_warning @219 NONAME + log_body_fatal @128 NONAME + property_create @256 NONAME + property_desc_to_buffer @201 NONAME + property_destroy @264 NONAME + property_insert_read @23 NONAME + property_node_create @316 NONAME + property_node_refer @268 NONAME + property_node_remove @129 NONAME + property_psmap_import @102 NONAME + property_psmap_export @110 NONAME + property_read_query_memsize @100 NONAME + property_search @244 NONAME + std_getenv @226 NONAME + std_setenv @114 NONAME diff --git a/src/imports/import_32_1403_avs-ea3.def b/src/imports/import_32_1403_avs-ea3.def new file mode 100644 index 0000000..d90965c --- /dev/null +++ b/src/imports/import_32_1403_avs-ea3.def @@ -0,0 +1,5 @@ +LIBRARY libavs-win32-ea3 + +EXPORTS + ea3_boot @8 NONAME + ea3_shutdown @9 NONAME diff --git a/src/imports/import_32_1403_avs.def b/src/imports/import_32_1403_avs.def new file mode 100644 index 0000000..32d68b1 --- /dev/null +++ b/src/imports/import_32_1403_avs.def @@ -0,0 +1,24 @@ +LIBRARY libavs-win32 + +EXPORTS + avs_boot @298 NONAME + avs_net_ctrl @100 NONAME + avs_shutdown @299 NONAME + avs_thread_create @6 NONAME + avs_thread_destroy @8 NONAME + avs_thread_join @13 NONAME + log_body_info @363 NONAME + log_body_misc @364 NONAME + log_body_warning @362 NONAME + log_body_fatal @365 NONAME + property_create @129 NONAME + property_desc_to_buffer @131 NONAME + property_destroy @130 NONAME + property_insert_read @133 NONAME + property_node_remove @148 NONAME + property_psmap_import @163 NONAME + property_psmap_export @164 NONAME + property_read_query_memsize @161 NONAME + property_search @146 NONAME + std_getenv @208 NONAME + std_setenv @209 NONAME diff --git a/src/imports/import_32_1508_avs-ea3.def b/src/imports/import_32_1508_avs-ea3.def new file mode 100644 index 0000000..a51556e --- /dev/null +++ b/src/imports/import_32_1508_avs-ea3.def @@ -0,0 +1,7 @@ +LIBRARY libavs-win32-ea3 + +EXPORTS + ea3_boot @8 NONAME + ea3_get_boot_status @11 NONAME + ea3_get_pp_status @10 NONAME + ea3_shutdown @9 NONAME diff --git a/src/imports/import_32_1508_avs.def b/src/imports/import_32_1508_avs.def new file mode 100644 index 0000000..05419ed --- /dev/null +++ b/src/imports/import_32_1508_avs.def @@ -0,0 +1,32 @@ +LIBRARY libavs-win32 + +EXPORTS + avs_boot @285 NONAME + avs_fs_close @65 NONAME + avs_fs_lseek @59 NONAME + avs_fs_lseek64 @60 NONAME + avs_fs_open @58 NONAME + avs_fs_read @61 NONAME + avs_net_ctrl @98 NONAME + avs_shutdown @286 NONAME + avs_thread_create @6 NONAME + avs_thread_destroy @8 NONAME + avs_thread_exit @12 NONAME + avs_thread_join @13 NONAME + log_body_fatal @355 NONAME + log_body_info @357 NONAME + log_body_misc @358 NONAME + log_body_warning @356 NONAME + property_create @127 NONAME + property_desc_to_buffer @129 NONAME + property_destroy @128 NONAME + property_insert_read @131 NONAME + property_node_create @145 NONAME + property_node_refer @158 NONAME + property_node_remove @146 NONAME + property_psmap_export @162 NONAME + property_psmap_import @161 NONAME + property_read_query_memsize @159 NONAME + property_search @144 NONAME + std_getenv @207 NONAME + std_setenv @208 NONAME diff --git a/src/imports/import_32_1601_avs-ea3.def b/src/imports/import_32_1601_avs-ea3.def new file mode 100644 index 0000000..8693db8 --- /dev/null +++ b/src/imports/import_32_1601_avs-ea3.def @@ -0,0 +1,7 @@ +LIBRARY libavs-win32-ea3 + +EXPORTS + ea3_boot @7 NONAME + ea3_shutdown @8 NONAME + ea3_get_pp_status @9 NONAME + ea3_get_boot_status @10 NONAME diff --git a/src/imports/import_32_1601_avs.def b/src/imports/import_32_1601_avs.def new file mode 100644 index 0000000..e260a8c --- /dev/null +++ b/src/imports/import_32_1601_avs.def @@ -0,0 +1,28 @@ +LIBRARY libavs-win32 + +EXPORTS + avs_thread_create @5 NONAME + avs_thread_destroy @7 NONAME + avs_thread_exit @11 NONAME + avs_thread_join @12 NONAME + avs_net_ctrl @98 NONAME + property_create @124 NONAME + property_destroy @125 NONAME + property_desc_to_buffer @126 NONAME + property_insert_read @128 NONAME + property_search @141 NONAME + property_node_create @142 NONAME + property_node_remove @143 NONAME + property_node_refer @155 NONAME + property_read_query_memsize @156 NONAME + property_psmap_export @159 NONAME + property_psmap_import @158 NONAME + std_getenv @204 NONAME + std_setenv @205 NONAME + avs_boot @283 NONAME + avs_shutdown @284 NONAME + log_body_fatal @361 NONAME + log_body_warning @362 NONAME + log_body_info @363 NONAME + log_body_misc @364 NONAME + diff --git a/src/imports/import_32_1603_avs-ea3.def b/src/imports/import_32_1603_avs-ea3.def new file mode 100644 index 0000000..58546a5 --- /dev/null +++ b/src/imports/import_32_1603_avs-ea3.def @@ -0,0 +1,7 @@ +LIBRARY libavs-win32-ea3 + +EXPORTS + ea3_boot @8 NONAME + ea3_shutdown @9 NONAME + ea3_get_pp_status @10 NONAME + ea3_get_boot_status @11 NONAME diff --git a/src/imports/import_32_1603_avs.def b/src/imports/import_32_1603_avs.def new file mode 100644 index 0000000..98f5fe7 --- /dev/null +++ b/src/imports/import_32_1603_avs.def @@ -0,0 +1,27 @@ +LIBRARY libavs-win32 + +EXPORTS + avs_thread_create @5 NONAME + avs_thread_destroy @7 NONAME + avs_thread_exit @11 NONAME + avs_thread_join @12 NONAME + avs_net_ctrl @119 NONAME + property_create @145 NONAME + property_destroy @146 NONAME + property_desc_to_buffer @147 NONAME + property_insert_read @149 NONAME + property_search @162 NONAME + property_node_create @163 NONAME + property_node_remove @164 NONAME + property_node_refer @176 NONAME + property_read_query_memsize @177 NONAME + property_psmap_import @179 NONAME + property_psmap_export @180 NONAME + std_getenv @212 NONAME + std_setenv @213 NONAME + avs_boot @298 NONAME + avs_shutdown @299 NONAME + log_body_fatal @379 NONAME + log_body_warning @380 NONAME + log_body_info @381 NONAME + log_body_misc @382 NONAME diff --git a/src/imports/import_32_1700_avs-ea3.def b/src/imports/import_32_1700_avs-ea3.def new file mode 100644 index 0000000..5d2f430 --- /dev/null +++ b/src/imports/import_32_1700_avs-ea3.def @@ -0,0 +1,5 @@ +LIBRARY avs2-ea3 + +EXPORTS + ea3_boot @37 NONAME + ea3_shutdown @38 NONAME diff --git a/src/imports/import_32_1700_avs.def b/src/imports/import_32_1700_avs.def new file mode 100644 index 0000000..16f5774 --- /dev/null +++ b/src/imports/import_32_1700_avs.def @@ -0,0 +1,28 @@ +LIBRARY avs2-core + +EXPORTS + avs_thread_create @5 NONAME + avs_thread_destroy @7 NONAME + avs_thread_exit @11 NONAME + avs_thread_join @12 NONAME + avs_fs_mount @76 NONAME + avs_net_ctrl @119 NONAME + property_create @145 NONAME + property_destroy @146 NONAME + property_desc_to_buffer @147 NONAME + property_insert_read @149 NONAME + property_search @162 NONAME + property_node_create @163 NONAME + property_node_remove @164 NONAME + property_node_refer @176 NONAME + property_read_query_memsize @177 NONAME + property_psmap_import @179 NONAME + property_psmap_export @180 NONAME + std_getenv @212 NONAME + std_setenv @213 NONAME + avs_boot @298 NONAME + avs_shutdown @299 NONAME + log_body_fatal @379 NONAME + log_body_warning @380 NONAME + log_body_info @381 NONAME + log_body_misc @382 NONAME diff --git a/src/imports/import_32_803_avs-ea3.def b/src/imports/import_32_803_avs-ea3.def new file mode 100644 index 0000000..577276d --- /dev/null +++ b/src/imports/import_32_803_avs-ea3.def @@ -0,0 +1,5 @@ +LIBRARY libavs-win32-ea3 + +EXPORTS + ea3_boot + ea3_shutdown diff --git a/src/imports/import_32_803_avs.def b/src/imports/import_32_803_avs.def new file mode 100644 index 0000000..a7f59b5 --- /dev/null +++ b/src/imports/import_32_803_avs.def @@ -0,0 +1,37 @@ +LIBRARY libavs-win32 + +EXPORTS + avs_boot + avs_net_ctrl + avs_shutdown + avs_thread_create + avs_thread_destroy + avs_thread_exit + avs_thread_join + log_body_fatal + log_body_info + log_body_misc + log_body_warning + log_boot + log_change_level + property_create + property_desc_to_buffer + property_destroy + property_file_write + property_insert_read + property_mem_write + property_read_query_memsize + property_search + property_set_flag + property_node_clone + property_node_create + property_node_datasize + property_node_name + property_node_refer + property_node_remove + property_node_type + property_node_traversal + property_node_refdata + std_getenv + std_setenv + diff --git a/src/imports/import_32_indep_SMX.def b/src/imports/import_32_indep_SMX.def new file mode 100644 index 0000000..1dc81dc --- /dev/null +++ b/src/imports/import_32_indep_SMX.def @@ -0,0 +1,17 @@ +LIBRARY SMX + +EXPORTS + SMX_Start + SMX_Stop + SMX_SetLogCallback + SMX_GetInfo + SMX_GetInputState + SMX_SetLights + SMX_ReenableAutoLights + SMX_GetConfig + SMX_SetConfig + SMX_FactoryReset + SMX_ForceRecalibration + SMX_SetTestMode + SMX_GetTestData + SMX_Version diff --git a/src/imports/import_64_1508_avs-ea3.def b/src/imports/import_64_1508_avs-ea3.def new file mode 100644 index 0000000..9c4a2b3 --- /dev/null +++ b/src/imports/import_64_1508_avs-ea3.def @@ -0,0 +1,7 @@ +LIBRARY libavs-win64-ea3 + +EXPORTS + ea3_boot @8 NONAME + ea3_get_boot_status @11 NONAME + ea3_get_pp_status @10 NONAME + ea3_shutdown @9 NONAME diff --git a/src/imports/import_64_1508_avs.def b/src/imports/import_64_1508_avs.def new file mode 100644 index 0000000..bc9fbf0 --- /dev/null +++ b/src/imports/import_64_1508_avs.def @@ -0,0 +1,32 @@ +LIBRARY libavs-win64 + +EXPORTS + avs_boot @285 NONAME + avs_fs_close @65 NONAME + avs_fs_lseek @59 NONAME + avs_fs_lseek64 @60 NONAME + avs_fs_open @58 NONAME + avs_fs_read @61 NONAME + avs_net_ctrl @98 NONAME + avs_shutdown @286 NONAME + avs_thread_create @6 NONAME + avs_thread_destroy @8 NONAME + avs_thread_exit @12 NONAME + avs_thread_join @13 NONAME + log_body_fatal @355 NONAME + log_body_info @357 NONAME + log_body_misc @358 NONAME + log_body_warning @356 NONAME + property_create @127 NONAME + property_desc_to_buffer @129 NONAME + property_destroy @128 NONAME + property_insert_read @131 NONAME + property_node_create @145 NONAME + property_node_refer @158 NONAME + property_node_remove @146 NONAME + property_psmap_export @162 NONAME + property_psmap_import @161 NONAME + property_read_query_memsize @159 NONAME + property_search @144 NONAME + std_getenv @207 NONAME + std_setenv @208 NONAME diff --git a/src/imports/import_64_1601_avs-ea3.def b/src/imports/import_64_1601_avs-ea3.def new file mode 100644 index 0000000..215d61f --- /dev/null +++ b/src/imports/import_64_1601_avs-ea3.def @@ -0,0 +1,7 @@ +LIBRARY libavs-win64-ea3 + +EXPORTS + ea3_boot @7 NONAME + ea3_shutdown @8 NONAME + ea3_get_pp_status @9 NONAME + ea3_get_boot_status @10 NONAME diff --git a/src/imports/import_64_1601_avs.def b/src/imports/import_64_1601_avs.def new file mode 100644 index 0000000..5bbe452 --- /dev/null +++ b/src/imports/import_64_1601_avs.def @@ -0,0 +1,28 @@ +LIBRARY libavs-win64 + +EXPORTS + avs_thread_create @5 NONAME + avs_thread_destroy @7 NONAME + avs_thread_exit @11 NONAME + avs_thread_join @12 NONAME + avs_net_ctrl @98 NONAME + property_create @124 NONAME + property_destroy @125 NONAME + property_desc_to_buffer @126 NONAME + property_insert_read @128 NONAME + property_search @141 NONAME + property_node_create @142 NONAME + property_node_remove @143 NONAME + property_node_refer @155 NONAME + property_read_query_memsize @156 NONAME + property_psmap_export @159 NONAME + property_psmap_import @158 NONAME + std_getenv @204 NONAME + std_setenv @205 NONAME + avs_boot @283 NONAME + avs_shutdown @284 NONAME + log_body_fatal @361 NONAME + log_body_warning @362 NONAME + log_body_info @363 NONAME + log_body_misc @364 NONAME + diff --git a/src/imports/import_64_1603_avs-ea3.def b/src/imports/import_64_1603_avs-ea3.def new file mode 100644 index 0000000..6f08a5c --- /dev/null +++ b/src/imports/import_64_1603_avs-ea3.def @@ -0,0 +1,7 @@ +LIBRARY libavs-win64-ea3 + +EXPORTS + ea3_boot @8 NONAME + ea3_shutdown @9 NONAME + ea3_get_pp_status @10 NONAME + ea3_get_boot_status @11 NONAME diff --git a/src/imports/import_64_1603_avs.def b/src/imports/import_64_1603_avs.def new file mode 100644 index 0000000..0355922 --- /dev/null +++ b/src/imports/import_64_1603_avs.def @@ -0,0 +1,27 @@ +LIBRARY libavs-win64 + +EXPORTS + avs_thread_create @5 NONAME + avs_thread_destroy @7 NONAME + avs_thread_exit @11 NONAME + avs_thread_join @12 NONAME + avs_net_ctrl @119 NONAME + property_create @145 NONAME + property_destroy @146 NONAME + property_desc_to_buffer @147 NONAME + property_insert_read @149 NONAME + property_search @162 NONAME + property_node_create @163 NONAME + property_node_remove @164 NONAME + property_node_refer @176 NONAME + property_read_query_memsize @177 NONAME + property_psmap_import @179 NONAME + property_psmap_export @180 NONAME + std_getenv @212 NONAME + std_setenv @213 NONAME + avs_boot @298 NONAME + avs_shutdown @299 NONAME + log_body_fatal @379 NONAME + log_body_warning @380 NONAME + log_body_info @381 NONAME + log_body_misc @382 NONAME diff --git a/src/imports/import_64_1700_avs-ea3.def b/src/imports/import_64_1700_avs-ea3.def new file mode 100644 index 0000000..5d2f430 --- /dev/null +++ b/src/imports/import_64_1700_avs-ea3.def @@ -0,0 +1,5 @@ +LIBRARY avs2-ea3 + +EXPORTS + ea3_boot @37 NONAME + ea3_shutdown @38 NONAME diff --git a/src/imports/import_64_1700_avs.def b/src/imports/import_64_1700_avs.def new file mode 100644 index 0000000..16f5774 --- /dev/null +++ b/src/imports/import_64_1700_avs.def @@ -0,0 +1,28 @@ +LIBRARY avs2-core + +EXPORTS + avs_thread_create @5 NONAME + avs_thread_destroy @7 NONAME + avs_thread_exit @11 NONAME + avs_thread_join @12 NONAME + avs_fs_mount @76 NONAME + avs_net_ctrl @119 NONAME + property_create @145 NONAME + property_destroy @146 NONAME + property_desc_to_buffer @147 NONAME + property_insert_read @149 NONAME + property_search @162 NONAME + property_node_create @163 NONAME + property_node_remove @164 NONAME + property_node_refer @176 NONAME + property_read_query_memsize @177 NONAME + property_psmap_import @179 NONAME + property_psmap_export @180 NONAME + std_getenv @212 NONAME + std_setenv @213 NONAME + avs_boot @298 NONAME + avs_shutdown @299 NONAME + log_body_fatal @379 NONAME + log_body_warning @380 NONAME + log_body_info @381 NONAME + log_body_misc @382 NONAME diff --git a/src/main/acio/acio.h b/src/main/acio/acio.h new file mode 100644 index 0000000..dbfe434 --- /dev/null +++ b/src/main/acio/acio.h @@ -0,0 +1,86 @@ +#ifndef AC_IO_AC_IO_H +#define AC_IO_AC_IO_H + +#include +#include +#include + +#include "acio/icca.h" +#include "acio/kfca.h" + +#define AC_IO_SOF 0xAA +#define AC_IO_ESCAPE 0xFF +#define AC_IO_RESPONSE_FLAG 0x80 +#define AC_IO_BROADCAST 0x70 + +#define ac_io_u16(x) _byteswap_ushort(x) +#define ac_io_u32(x) _byteswap_ulong(x) + +enum ac_io_cmd { + AC_IO_CMD_ASSIGN_ADDRS = 0x0001, + AC_IO_CMD_GET_VERSION = 0x0002, + AC_IO_CMD_START_UP = 0x0003, + AC_IO_CMD_KEEPALIVE = 0x0080, + /* Yet unknown command encountered first on jubeat (1) */ + AC_IO_CMD_UNKN_00FF = 0x00FF, + AC_IO_CMD_CLEAR = 0x0100, +}; + +enum ac_io_node_type { + AC_IO_NODE_TYPE_H44B = 0x04010000, + AC_IO_NODE_TYPE_ICCA = 0x03000000, + /* same as ICCA */ + AC_IO_NODE_TYPE_ICCB = 0x03000000, + AC_IO_NODE_TYPE_LED_STRIP = 0x04020000, + AC_IO_NODE_TYPE_LED_SPIKE = 0x05010000, + AC_IO_NODE_TYPE_KFCA = 0x09060000, + AC_IO_NODE_TYPE_BI2A = 0x0d060000, +}; + +#pragma pack(push, 1) + +struct ac_io_version { + /* Names taken from some debug text in libacio.dll */ + uint32_t type; + uint8_t flag; + uint8_t major; + uint8_t minor; + uint8_t revision; + char product_code[4]; + char date[16]; + char time[16]; +}; + +struct ac_io_message { + uint8_t addr; /* High bit: clear = req, set = resp */ + + union { + struct { + uint16_t code; + uint8_t seq_no; + uint8_t nbytes; + + union { + uint8_t raw[0xFF]; + uint8_t count; + uint8_t status; + struct ac_io_version version; + + struct ac_io_icca_misc icca_misc; + struct ac_io_icca_state icca_state; + + struct ac_io_kfca_poll_in kfca_poll_in; + struct ac_io_kfca_poll_out kfca_poll_out; + }; + } cmd; + + struct { + uint8_t nbytes; + uint8_t raw[0xFF]; /* 0xFFucked if I know */ + } bcast; + }; +}; + +#pragma pack(pop) + +#endif diff --git a/src/main/acio/h44b.h b/src/main/acio/h44b.h new file mode 100644 index 0000000..0135944 --- /dev/null +++ b/src/main/acio/h44b.h @@ -0,0 +1,19 @@ +#ifndef ACIO_H44B_H +#define ACIO_H44B_H + +#include + +enum ac_io_h44b_cmd { + AC_IO_H44B_CMD_SET_OUTPUTS = 0x0122, +}; + +struct ac_io_h44b_output { + uint8_t front_rgb[3]; + uint8_t top_rgb[3]; + uint8_t left_rgb[3]; + uint8_t right_rgb[3]; + uint8_t title_rgb[3]; + uint8_t woofer_rgb[3]; +}; + +#endif \ No newline at end of file diff --git a/src/main/acio/icca.h b/src/main/acio/icca.h new file mode 100644 index 0000000..f95a686 --- /dev/null +++ b/src/main/acio/icca.h @@ -0,0 +1,68 @@ +#ifndef AC_IO_ICCA_H +#define AC_IO_ICCA_H + +#include + +enum ac_io_icca_cmd { + /* Yet unknown command encountered first on jubeat (1) */ + AC_IO_ICCA_CMD_UNKN_0120 = 0x0120, + AC_IO_ICCA_CMD_QUEUE_LOOP_START = 0x0130, + AC_IO_ICCA_CMD_ENGAGE = 0x0131, + AC_IO_ICCA_CMD_POLL = 0x0134, + AC_IO_ICCA_CMD_SET_SLOT_STATE = 0x0135, + AC_IO_ICCA_CMD_BEGIN_KEYPAD = 0x013A, + AC_IO_ICCA_CMD_POLL_FELICA = 0x0161, +}; + +enum ac_io_icca_slot_state { + AC_IO_ICCA_SLOT_STATE_OPEN = 0x11, + AC_IO_ICCA_SLOT_STATE_EJECT = 0x12, + AC_IO_ICCA_SLOT_STATE_CLOSE = 0, +}; + +enum ac_io_icca_sensor_state { + /* Card eject event fired once after slot state is set to eject the card */ + AC_IO_ICCA_SENSOR_STATE_CARD_EJECTED = 0x50, + AC_IO_ICCA_SENSOR_MASK_FRONT_ON = (1 << 4), + AC_IO_ICCA_SENSOR_MASK_BACK_ON = (1 << 5) +}; + +enum ac_io_icca_keypad_mask { + AC_IO_ICCA_KEYPAD_MASK_EMPTY = (1 << 0), + AC_IO_ICCA_KEYPAD_MASK_3 = (1 << 1), + AC_IO_ICCA_KEYPAD_MASK_6 = (1 << 2), + AC_IO_ICCA_KEYPAD_MASK_9 = (1 << 3), + + AC_IO_ICCA_KEYPAD_MASK_0 = (1 << 8), + AC_IO_ICCA_KEYPAD_MASK_1 = (1 << 9), + AC_IO_ICCA_KEYPAD_MASK_4 = (1 << 10), + AC_IO_ICCA_KEYPAD_MASK_7 = (1 << 11), + + AC_IO_ICCA_KEYPAD_MASK_00 = (1 << 12), + AC_IO_ICCA_KEYPAD_MASK_2 = (1 << 13), + AC_IO_ICCA_KEYPAD_MASK_5 = (1 << 14), + AC_IO_ICCA_KEYPAD_MASK_8 = (1 << 15), +}; + +#pragma pack(push, 1) + +struct ac_io_icca_misc { + uint8_t unknown; + uint8_t subcmd; +}; + +struct ac_io_icca_state { + /* Similar to the struct returned by libacio, but not quite the same */ + + uint8_t status_code; + uint8_t sensor_state; + uint8_t uid[8]; + uint8_t card_type; + uint8_t keypad_started; + uint8_t key_events[2]; + uint16_t key_state; +}; + +#pragma pack(pop) + +#endif diff --git a/src/main/acio/iccb.h b/src/main/acio/iccb.h new file mode 100644 index 0000000..c35a46a --- /dev/null +++ b/src/main/acio/iccb.h @@ -0,0 +1,48 @@ +#ifndef AC_IO_ICCB_H +#define AC_IO_ICCB_H + +#include + +enum ac_io_iccb_cmd { + /* found on jubeat prop, sent after acio init req, maybe fw update? */ + AC_IO_ICCB_CMD_UNK_0100 = 0x0100, + /* found on jubeat prop, sent right after queue loop start */ + AC_IO_ICCB_CMD_UNK_0116 = 0x0116, + /* found on jubeat prop, sent after 0100 req */ + AC_IO_ICCB_CMD_UNK_0120 = 0x0120, + AC_IO_ICCB_CMD_QUEUE_LOOP_START = 0x0130, + AC_IO_ICCB_CMD_POLL = 0x0134, + AC_IO_ICCB_CMD_UNK_135 = 0x0135, + AC_IO_ICCB_CMD_SLEEP = 0x013A, + AC_IO_ICCB_CMD_READ_CARD = 0x0161 +}; + +enum ac_io_iccb_sensor_state { + AC_IO_ICCB_SENSOR_CARD = 0x02, + AC_IO_ICCB_SENSOR_NO_CARD = 0x04 +}; + +enum ac_io_iccb_card_type { + AC_IO_ICCB_CARD_TYPE_ISO15696 = 0x0, + AC_IO_ICCB_CARD_TYPE_FELICA = 0x1, +}; + +#pragma pack(push, 1) + +struct ac_io_iccb_misc { + uint8_t unknown; + uint8_t subcmd; +}; + +struct ac_io_iccb_state { + uint8_t sensor_state; + uint8_t card_type; + uint8_t uid[8]; + uint8_t unk2; + uint8_t unk3; + uint8_t unk4[4]; +}; + +#pragma pack(pop) + +#endif \ No newline at end of file diff --git a/src/main/acio/kfca.h b/src/main/acio/kfca.h new file mode 100644 index 0000000..7106c4c --- /dev/null +++ b/src/main/acio/kfca.h @@ -0,0 +1,46 @@ +#ifndef AC_IO_KFCA_H +#define AC_IO_KFCA_H + +#define AC_IO_CMD_KFCA_POLL 0x0113 +#define AC_IO_CMD_KFCA_UNK_0120 0x0120 +#define AC_IO_CMD_KFCA_UNK_0128 0x0128 + +#define AC_IO_KFCA_IN_GPIO_SYS_COIN 0x04 +/* ... AC_IO_KFCA_IN_GPIO_SYS_COIN2 0x08 (maybe?) */ +#define AC_IO_KFCA_IN_GPIO_SYS_TEST 0x10 +#define AC_IO_KFCA_IN_GPIO_SYS_SERVICE 0x20 + +#define AC_IO_KFCA_IN_GPIO_0_C 0x0001 +#define AC_IO_KFCA_IN_GPIO_0_B 0x0002 +#define AC_IO_KFCA_IN_GPIO_0_A 0x0004 +#define AC_IO_KFCA_IN_GPIO_0_START 0x0008 +#define AC_IO_KFCA_IN_GPIO_0_HEADPHONE 0x0020 + +#define AC_IO_KFCA_IN_GPIO_1_FX_R 0x0008 +#define AC_IO_KFCA_IN_GPIO_1_FX_L 0x0010 +#define AC_IO_KFCA_IN_GPIO_1_D 0x0020 + +#pragma pack(push, 1) + +struct ac_io_kfca_poll_in { + /* ADC data is 10 bits. Low 6 bits of ADCs 1 through 3 are zero, low 6 bits + of ADC 0 is the system GPIOs (test, service, coin). */ + + union { + uint16_t gpio_sys; + + struct { + uint16_t adc[4]; + uint16_t gpio[4]; + }; + }; +}; + +struct ac_io_kfca_poll_out { + uint32_t gpio; + uint8_t pwm[18]; +}; + +#pragma pack(pop) + +#endif diff --git a/src/main/aciodrv/Module.mk b/src/main/aciodrv/Module.mk new file mode 100644 index 0000000..c7b822a --- /dev/null +++ b/src/main/aciodrv/Module.mk @@ -0,0 +1,9 @@ +libs += aciodrv + +libs_aciodrv := \ + +src_aciodrv := \ + device.c \ + icca.c \ + port.c \ + diff --git a/src/main/aciodrv/device.c b/src/main/aciodrv/device.c new file mode 100644 index 0000000..533735b --- /dev/null +++ b/src/main/aciodrv/device.c @@ -0,0 +1,335 @@ +#define LOG_MODULE "aciodrv-device" + +#include + +#include "aciodrv/device.h" + +#include "aciodrv/port.h" + +#include "util/hex.h" +#include "util/log.h" + +/* Enable to dump all data to the logger */ +//#define AC_IO_MSG_LOG + +static uint8_t aciodrv_device_msg_counter = 1; +static uint8_t aciodrv_device_node_count; +static char aviodrv_device_node_products[16][4]; + +static bool aciodrv_device_init(void) +{ + uint8_t init_seq[1] = {AC_IO_SOF}; + + /* init/reset the device by sending 0xAA until 0xAA is returned */ + int read = 0; + do { + if (aciodrv_port_write(init_seq, sizeof(init_seq)) <= 0) { + return false; + } + + read = aciodrv_port_read(init_seq, sizeof(init_seq)); + } while (read == 0); + + if (read > 0) { + /* empty buffer by reading all data */ + while (read > 0) { + read = aciodrv_port_read(init_seq, sizeof(init_seq)); + } + + return read == 0; + } else { + return false; + } +} + +#ifdef AC_IO_MSG_LOG +static void aciodrv_device_log_buffer(const char* msg, const uint8_t* buffer, + int length) +{ + char str[4096]; + + hex_encode_uc((const void*) buffer, length, str, sizeof(str)); + log_misc("%s, length %d: %s", msg, length, str); +} +#endif + +static bool aciodrv_device_send(const uint8_t* buffer, int length) +{ + uint8_t send_buf[512]; + int send_buf_pos = 0; + uint8_t checksum = 0; + + if (length > sizeof(send_buf)) { + log_warning("Send buffer overflow"); + return false; + } + +#ifdef AC_IO_MSG_LOG + aciodrv_device_log_buffer("Send (1)", buffer, length); +#endif + + send_buf[send_buf_pos++] = AC_IO_SOF; + + /* TODO overrun checks */ + for (int i = 0; i < length; i++) { + if (buffer[i] == AC_IO_SOF || buffer[i] == AC_IO_ESCAPE) { + send_buf[send_buf_pos++] = AC_IO_ESCAPE; + send_buf[send_buf_pos++] = ~buffer[i]; + } else { + send_buf[send_buf_pos++] = buffer[i]; + } + + checksum += buffer[i]; + } + + /* we have to escape the checksum as well! */ + if (checksum == AC_IO_SOF || checksum == AC_IO_ESCAPE) { + send_buf[send_buf_pos++] = AC_IO_ESCAPE; + send_buf[send_buf_pos++] = ~checksum; + } else { + send_buf[send_buf_pos++] = checksum; + } + +#ifdef AC_IO_MSG_LOG + aciodrv_device_log_buffer("Send (2)", send_buf, send_buf_pos); +#endif + + if (aciodrv_port_write(send_buf, send_buf_pos) != send_buf_pos) { + log_warning("Sending data with length %d failed", send_buf_pos); + return false; + } + + return true; +} + +static int aciodrv_device_receive(uint8_t* buffer, int size) +{ + uint8_t recv_buf[512]; + int recv_size = 0; + int read = 0; + uint8_t checksum = 0; + int result_size = 0; + + /* reading a byte stream, we are getting a varying amount + of 0xAAs before we get a valid message. */ + recv_buf[0] = AC_IO_SOF; + do { + read = aciodrv_port_read(recv_buf, 1); + } while (recv_buf[0] == AC_IO_SOF); + + if (read > 0) { + size += 1; + + /* recv_buf[0] is already the first byte of the message. + now read until nothing's left */ + recv_size++; + size--; + + /* important: we have to know how much data we expect + and have to read until we reach the requested amount. + Because this can be interrupted by 0 reads and we + need to handle escaping (which relies on an up to + date recv_buf[recv_size]) we loop until we get a + non-zero read. */ + while (size > 0) { + do { + read = aciodrv_port_read(recv_buf + recv_size, 1); + } while (read == 0); + + if (read < 0) { + break; + } + + /* check for escape byte. these don't count towards the + size we expect! */ + if (recv_buf[recv_size] == AC_IO_ESCAPE) + { + /* next byte is our real data + overwrite escape byte */ + do { + read = aciodrv_port_read(recv_buf + recv_size, 1); + } while (read == 0); + + if (read < 0) { + break; + } + + recv_buf[recv_size] = ~recv_buf[recv_size]; + } + + recv_size += read; + size -= read; + } + +#ifdef AC_IO_MSG_LOG + aciodrv_device_log_buffer("Recv (1)", recv_buf, recv_size); +#endif + + /* recv_size - 1: omit checksum for checksum calc */ + for (int i = 0; i < recv_size - 1; i++) { + checksum += recv_buf[i]; + buffer[i] = recv_buf[i]; + } + + result_size = recv_size - 1; + +#ifdef AC_IO_MSG_LOG + aciodrv_device_log_buffer("Recv (2)", buffer, result_size); +#endif + + if (checksum != recv_buf[recv_size - 1]) { + log_warning("Invalid message checksum: %02X != %02X", + checksum, recv_buf[recv_size - 1]); + return -1; + } + + return result_size; + } + + return -1; +} + +static uint8_t aciodrv_device_enum_nodes(void) +{ + struct ac_io_message msg; + + msg.addr = 0x00; + msg.cmd.code = ac_io_u16(AC_IO_CMD_ASSIGN_ADDRS); + msg.cmd.nbytes = 1; + msg.cmd.count = 0; + + if (!aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + 1)) { + log_warning("Enumerating nodes failed"); + return 0; + } + + log_info("Enumerated %d nodes", msg.cmd.count); + + return msg.cmd.count; +} + +static bool aciodrv_device_get_version(uint8_t node_id, char product[4]) +{ + struct ac_io_message msg; + + msg.addr = node_id; + msg.cmd.code = ac_io_u16(AC_IO_CMD_GET_VERSION); + msg.cmd.nbytes = 0; + + if ( !aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + + sizeof(struct ac_io_version))) { + log_warning("Get version of node %d failed", node_id); + return false; + } + + log_info("Node %d: type %d, flag %d, version %d.%d.%d, product %c%c%c%c, " + "build date: %s %s", + node_id, + msg.cmd.version.type, + msg.cmd.version.flag, + msg.cmd.version.major, + msg.cmd.version.minor, + msg.cmd.version.revision, + msg.cmd.version.product_code[0], + msg.cmd.version.product_code[1], + msg.cmd.version.product_code[2], + msg.cmd.version.product_code[3], + msg.cmd.version.date, + msg.cmd.version.time); + + memcpy(product, msg.cmd.version.product_code, 4); + + return true; +} + +static bool aciodrv_device_start_node(uint8_t node_id) +{ + struct ac_io_message msg; + + msg.addr = node_id; + msg.cmd.code = ac_io_u16(AC_IO_CMD_START_UP); + msg.cmd.nbytes = 0; + + if (!aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + 1)) { + log_warning("Starting node %d failed", node_id); + return false; + } + + log_info("Started node %d, status: %d", node_id, msg.cmd.status); + return true; +} + +bool aciodrv_device_open(const char* port, int baud) +{ + if (!aciodrv_port_open(port, baud)) { + return false; + } + + if (!aciodrv_device_init()) { + return false; + } + + aciodrv_device_node_count = aciodrv_device_enum_nodes(); + if (aciodrv_device_node_count == 0) { + return false; + } + + for (uint8_t i = 0; i < aciodrv_device_node_count; i++) { + if (!aciodrv_device_get_version(i + 1, aviodrv_device_node_products[i])) { + return false; + } + } + + for (uint8_t i = 0; i < aciodrv_device_node_count; i++) { + if (!aciodrv_device_start_node(i + 1)) { + return false; + } + } + + return true; +} + +uint8_t aciodrv_device_get_node_count(void) +{ + return aciodrv_device_node_count; +} + +bool aciodrv_device_get_node_product_ident(uint8_t node_id, char product[4]) +{ + if (aciodrv_device_node_count == 0 || node_id > aciodrv_device_node_count) { + return false; + } + + memcpy(product, aviodrv_device_node_products[node_id], 4); + return true; +} + +bool aciodrv_send_and_recv(struct ac_io_message* msg, int resp_size) +{ + msg->cmd.seq_no = aciodrv_device_msg_counter++; + + if (aciodrv_device_send((uint8_t*) msg, + offsetof(struct ac_io_message, cmd.raw) + msg->cmd.nbytes) <= 0) { + return false; + } + + uint16_t req_code = msg->cmd.code; + + if (aciodrv_device_receive((uint8_t*) msg, + resp_size) <= 0) { + return false; + } + + if (req_code != msg->cmd.code) { + log_warning("Received invalid response %04X for request %04X", + msg->cmd.code, req_code); + return false; + } + + return true; +} + +void aciodrv_device_close(void) +{ + aciodrv_port_close(); +} diff --git a/src/main/aciodrv/device.h b/src/main/aciodrv/device.h new file mode 100644 index 0000000..9b5df18 --- /dev/null +++ b/src/main/aciodrv/device.h @@ -0,0 +1,52 @@ +#ifndef ACIODRV_DEVICE_H +#define ACIODRV_DEVICE_H + +#include + +#include "acio/acio.h" + +/** + * Open an ACIO device connected to a serial port. + * + * @param port Port the device is connected to (e.g. "COM1") + * @param baud Baud rate for communication (e.g. 57600 for ICCA) + * @return True if opening the port and resetting the device was successful, + * false on error. + */ +bool aciodrv_device_open(const char* port, int baud); + +/** + * Get the node count on the opened device. + * + * @return Total num of nodes enumerated on the ACIO device. + */ +uint8_t aciodrv_device_get_node_count(void); + +/** + * Get the product identifier of an enumerated node. + * + * @param node_id Id of the node. Needs to be in range of the total node count. + * @param product Buffer to return the product id to. + * @return True on success, false on error. If True the variable product contains + * the identifier of the queried node. + */ +bool aciodrv_device_get_node_product_ident(uint8_t node_id, char product[4]); + +/** + * Send a message to the ACIO bus and receive an answer. + * Use this to implement the protocol for each type of device that can be + * part of the bus. + * + * @param msg Msg to send to the bus. Make sure that the buffer backing + * this message is big enough to receive the response as well. + * @param resp_size Size of the expecting response. + * @return True on success, false on error. + */ +bool aciodrv_send_and_recv(struct ac_io_message* msg, int resp_size); + +/** + * Close the previously opened ACIO device. + */ +void aciodrv_device_close(void); + +#endif \ No newline at end of file diff --git a/src/main/aciodrv/icca.c b/src/main/aciodrv/icca.c new file mode 100644 index 0000000..6029e0c --- /dev/null +++ b/src/main/aciodrv/icca.c @@ -0,0 +1,107 @@ +#define LOG_MODULE "aciodrv-icca" + +#include + +#include "aciodrv/device.h" + +#include "util/log.h" + +static bool aciodrv_icca_queue_loop_start(uint8_t node_id) +{ + struct ac_io_message msg; + + msg.addr = node_id; + msg.cmd.code = ac_io_u16(AC_IO_ICCA_CMD_QUEUE_LOOP_START); + msg.cmd.nbytes = 1; + msg.cmd.status = 0; + + if (!aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + 1)) { + log_warning("Starting queue loop failed"); + return false; + } + + log_info("Started queue loop of node %d, status: %d", + node_id, msg.cmd.status); + + return true; +} + +bool aciodrv_icca_init(uint8_t node_id) +{ + if (!aciodrv_icca_queue_loop_start(node_id + 1)) { + return false; + } + + return true; +} + +bool aciodrv_icca_set_state(uint8_t node_id, int slot_state, + struct ac_io_icca_state* state) +{ + struct ac_io_message msg; + + msg.addr = node_id + 1; + msg.cmd.code = ac_io_u16(AC_IO_ICCA_CMD_SET_SLOT_STATE); + msg.cmd.nbytes = 2; + /* buffer size of data we expect */ + msg.cmd.raw[0] = sizeof(struct ac_io_icca_state); + msg.cmd.raw[1] = slot_state; + + if ( !aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + + msg.cmd.raw[0])) { + log_warning("Setting state of node %d failed", node_id + 1); + return false; + } + + if (state != NULL) { + memcpy(state, msg.cmd.raw, sizeof(struct ac_io_icca_state)); + } + + return true; +} + +bool aciodrv_icca_get_state(uint8_t node_id, struct ac_io_icca_state* state) +{ + struct ac_io_message msg; + + msg.addr = node_id + 1; + msg.cmd.code = ac_io_u16(AC_IO_ICCA_CMD_POLL); + msg.cmd.nbytes = 1; + /* buffer size of data we expect */ + msg.cmd.count = sizeof(struct ac_io_icca_state); + + if ( !aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + + msg.cmd.count)) { + log_warning("Getting state of node %d failed", node_id + 1); + return false; + } + + if (state != NULL) { + memcpy(state, msg.cmd.raw, sizeof(struct ac_io_icca_state)); + } + + return true; +} + +bool aciodrv_icca_read_card(uint8_t node_id, struct ac_io_icca_state* state) +{ + struct ac_io_message msg; + + msg.addr = node_id + 1; + msg.cmd.code = ac_io_u16(AC_IO_ICCA_CMD_ENGAGE); + msg.cmd.nbytes = 1; + /* buffer size of data we expect */ + msg.cmd.count = sizeof(struct ac_io_icca_state); + + if ( !aciodrv_send_and_recv(&msg, offsetof(struct ac_io_message, cmd.raw) + + msg.cmd.count)) { + log_warning("Reading card of node %d failed", node_id + 1); + return false; + } + + if (state != NULL) { + memcpy(state, msg.cmd.raw, sizeof(struct ac_io_icca_state)); + } + + return true; +} \ No newline at end of file diff --git a/src/main/aciodrv/icca.h b/src/main/aciodrv/icca.h new file mode 100644 index 0000000..b75afc6 --- /dev/null +++ b/src/main/aciodrv/icca.h @@ -0,0 +1,60 @@ +#ifndef ACIODRV_ICCA_H +#define ACIODRV_ICCA_H + +#include "acio/icca.h" + +/** + * Initialize an ICCA node. + * + * @param node_id Id of the node to initialize (0 based). + * @return True if successful, false on error. + * @note This module is supposed to be used in combination with the common + * device driver foundation. + * @see driver.h + */ +bool aciodrv_icca_init(uint8_t node_id); + +/** + * Set the state of on ICCA node. + * + * @param node_id Id of the node to set the state for (0 based). + * @param slot_state State of the slot (refer to corresponding enum). + * @param state Pointer to a state struct to return the current state to + * (optional, NULL for none). + * @return True on success, false on error. + * @note This module is supposed to be used in combination with the common + * device driver foundation. + * @see driver.h + */ +bool aciodrv_icca_set_state(uint8_t node_id, int slot_state, struct ac_io_icca_state* state); + +/** + * Get the current state of an ICCA node. + * + * @param node_id Id of the node to query (0 based). + * @param state Pointer to a state struct to return the current state to + * (optional, NULL for none). + * @return True on success, false on error. + * @note This module is supposed to be used in combination with the common + * device driver foundation. + * @see driver.h + */ +bool aciodrv_icca_get_state(uint8_t node_id, struct ac_io_icca_state* state); + +/** + * Trigger a card read action on the ICCA reader. Make sure to call this + * when you want to read the card. Just polling the state is not sufficient + * to get the most recent card data. Make sure to re-get the state after + * a read call. The state returned here might not be up to date for some reason. + * + * @param node_id Id of the node to query (0 based). + * @param state Pointer to a state struct to return the current state to + * (optional, NULL for none). + * @return True on success, false on error. + * @note This module is supposed to be used in combination with the common + * device driver foundation. + * @see driver.h + */ +bool aciodrv_icca_read_card(uint8_t node_id, struct ac_io_icca_state* state); + +#endif \ No newline at end of file diff --git a/src/main/aciodrv/port.c b/src/main/aciodrv/port.c new file mode 100644 index 0000000..c227a86 --- /dev/null +++ b/src/main/aciodrv/port.c @@ -0,0 +1,157 @@ +#define LOG_MODULE "aciodrv-port" + +#include +#include + +#include + +#include "util/log.h" + +static HANDLE aciodrv_port_fd; + +bool aciodrv_port_open(const char* port, int baud) +{ + COMMTIMEOUTS ct; + DCB dcb; + + log_info("Opening ACIO on %s at %d baud", port, baud); + + aciodrv_port_fd = CreateFile(port, GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_NORMAL, NULL); + + if (aciodrv_port_fd == INVALID_HANDLE_VALUE) { + log_warning("Failed to open %s", port); + + goto early_fail; + } + + if (!SetCommMask(aciodrv_port_fd, EV_RXCHAR)) { + log_warning("SetCommMask failed"); + + goto fail; + } + + if (!SetupComm(aciodrv_port_fd, 0x1000, 0x1000)) { + log_warning("SetupComm failed"); + + goto fail; + } + + if (!PurgeComm(aciodrv_port_fd, + PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR)) { + + log_warning("PurgeComm failed"); + + goto fail; + } + + ct.ReadTotalTimeoutConstant = 0; + ct.WriteTotalTimeoutConstant = 0; + ct.ReadIntervalTimeout = -1; + ct.ReadTotalTimeoutMultiplier = 0; + ct.WriteTotalTimeoutMultiplier = 0; + + if (!SetCommTimeouts(aciodrv_port_fd, &ct)) { + log_warning("SetCommTimeouts failed"); + + goto fail; + } + + dcb.DCBlength = sizeof(dcb); + + if (!GetCommState(aciodrv_port_fd, &dcb)) { + log_warning("GetCommState failed"); + + goto fail; + } + + dcb.BaudRate = baud; + dcb.fBinary = TRUE; + dcb.fParity = FALSE; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fDsrSensitivity = FALSE; + dcb.fOutX = FALSE; + dcb.fInX = FALSE; + dcb.fErrorChar = FALSE; + dcb.fNull = FALSE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + dcb.fAbortOnError = FALSE; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.XonChar = 17; + dcb.XoffChar = 19; + dcb.XonLim = 100; + dcb.XoffLim = 100; + + if (!SetCommState(aciodrv_port_fd, &dcb)) { + log_warning("SetCommState failed"); + + goto fail; + } + + if (!EscapeCommFunction(aciodrv_port_fd, SETDTR)) { + log_warning("SETDTR failed: err = %lu", GetLastError()); + + goto fail; + } + + log_info("Opened ACIO device on %s", port); + + return true; + +fail: + CloseHandle(aciodrv_port_fd); + +early_fail: + aciodrv_port_fd = NULL; + return false; +} + +int aciodrv_port_read(void *bytes, int nbytes) +{ + DWORD nread; + + if (aciodrv_port_fd == NULL) { + return -1; + } + + if (!ClearCommError(aciodrv_port_fd, NULL, NULL)) { + log_warning("ClearCommError failed"); + + return -1; + } + + if (!ReadFile(aciodrv_port_fd, bytes, nbytes, &nread, NULL)) { + log_warning("ReadFile failed: err = %lu", GetLastError()); + + return -1; + } + + return nread; +} + +int aciodrv_port_write(const void *bytes, int nbytes) +{ + DWORD nwrit; + + if (aciodrv_port_fd == NULL) { + return -1; + } + + if (!WriteFile(aciodrv_port_fd, bytes, nbytes, &nwrit, NULL)) { + log_warning("WriteFile failed: err = %lu", GetLastError()); + + return -1; + } + + return nwrit; +} + +void aciodrv_port_close(void) +{ + if (aciodrv_port_fd != NULL) { + CloseHandle(aciodrv_port_fd); + } +} + diff --git a/src/main/aciodrv/port.h b/src/main/aciodrv/port.h new file mode 100644 index 0000000..97b502d --- /dev/null +++ b/src/main/aciodrv/port.h @@ -0,0 +1,42 @@ +#ifndef ACIODRV_PORT_H +#define ACIODRV_PORT_H + +#include +#include + +/** + * Open a serial port for communication with a ACIO device. + * + * @param port Port the device is connected to (e.g. "COM1") + * @param baud Baud rate for communication (e.g. 57600 for ICCA) + * @return True if opening the com port was successful, false on error. + * @note This will open and setup the com port, only. + */ +bool aciodrv_port_open(const char* port, int baud); + +/** + * Read data from the opened com port. + * + * @param bytes Pointer to an allocated buffer to read the data into. + * @param nbytes Number of bytes to read. Has to be less or equal the allocated + * buffer size. + * @return Number of bytes read on success or -1 on error. + */ +int aciodrv_port_read(void *bytes, int nbytes); + +/** + * Write data to the opened com port. + * + * @param bytes Pointer to an allocated buffer with data to write. + * @param nbytes Number of bytes to write. Has to be equal or less the size + * of the allocated buffer. + * @return Number of bytes written on success or -1 on error. + */ +int aciodrv_port_write(const void *bytes, int nbytes); + +/** + * Close the previously opened com port. + */ +void aciodrv_port_close(void); + +#endif \ No newline at end of file diff --git a/src/main/acioemu/Module.mk b/src/main/acioemu/Module.mk new file mode 100644 index 0000000..29c8c81 --- /dev/null +++ b/src/main/acioemu/Module.mk @@ -0,0 +1,11 @@ +libs += acioemu + +src_acioemu := \ + addr.c \ + emu.c \ + h44b.c \ + hdxs.c \ + icca.c \ + iccb.c \ + pipe.c \ + diff --git a/src/main/acioemu/addr.c b/src/main/acioemu/addr.c new file mode 100644 index 0000000..90bae14 --- /dev/null +++ b/src/main/acioemu/addr.c @@ -0,0 +1,39 @@ +#include + +#include "acio/acio.h" + +#include "acioemu/addr.h" +#include "acioemu/emu.h" + +#include "util/log.h" + +void ac_io_emu_cmd_assign_addrs( + struct ac_io_emu *emu, + const struct ac_io_message *req, + uint8_t node_count) +{ + struct ac_io_message resp; + uint16_t cmd; + + log_assert(emu != NULL); + log_assert(req != NULL); + + cmd = ac_io_u16(req->cmd.code); + + if (cmd != AC_IO_CMD_ASSIGN_ADDRS) { + log_warning( + "Address 0 expects address assignment cmd, got %04x", + cmd); + + return; + } + + memset(&resp, 0, sizeof(resp)); + resp.addr = 0; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.count); + resp.cmd.count = node_count; + + ac_io_emu_response_push(emu, &resp, 0); +} diff --git a/src/main/acioemu/addr.h b/src/main/acioemu/addr.h new file mode 100644 index 0000000..108e6ac --- /dev/null +++ b/src/main/acioemu/addr.h @@ -0,0 +1,15 @@ +#ifndef AC_IO_EMU_ADDR_H +#define AC_IO_EMU_ADDR_H + +#include "acio/acio.h" + +#include "acioemu/emu.h" + +#include + +void ac_io_emu_cmd_assign_addrs( + struct ac_io_emu *emu, + const struct ac_io_message *req, + uint8_t node_count); + +#endif diff --git a/src/main/acioemu/emu.c b/src/main/acioemu/emu.c new file mode 100644 index 0000000..dd22471 --- /dev/null +++ b/src/main/acioemu/emu.c @@ -0,0 +1,194 @@ +#include /* Usermode API */ + +#include /* Kernel-mode API for ioctls */ +#include +#include + +#include +#include +#include + +#include "acioemu/emu.h" +#include "acioemu/pipe.h" + +#include "hook/iohook.h" + +#include "util/log.h" +#include "util/str.h" + +static HRESULT ac_io_emu_open(struct ac_io_emu *emu, struct irp *irp); +static HRESULT ac_io_emu_close(struct ac_io_emu *emu, struct irp *irp); +static HRESULT ac_io_emu_read(struct ac_io_emu *emu, struct irp *irp); +static HRESULT ac_io_emu_write(struct ac_io_emu *emu, struct irp *irp); +static HRESULT ac_io_emu_ioctl(struct ac_io_emu *emu, struct irp *irp); + +void ac_io_emu_init(struct ac_io_emu *emu, const wchar_t *filename) +{ + log_assert(emu != NULL); + log_assert(filename != NULL); + + memset(emu, 0, sizeof(*emu)); + emu->fd = iohook_open_dummy_fd(); + emu->wfilename = wstr_dup(filename); + wstr_narrow(filename, &emu->filename); + ac_io_in_init(&emu->in); + ac_io_out_init(&emu->out); +} + +void ac_io_emu_fini(struct ac_io_emu *emu) +{ + log_assert(emu != NULL); + + free(emu->filename); + free(emu->wfilename); + + if (emu->fd != NULL) { + CloseHandle(emu->fd); + } + + memset(emu, 0, sizeof(*emu)); +} + +bool ac_io_emu_match_irp(const struct ac_io_emu *emu, const struct irp *irp) +{ + log_assert(emu != NULL); + log_assert(irp != NULL); + + if (irp->op == IRP_OP_OPEN) { + return wstr_eq(emu->wfilename, irp->open_filename); + } else { + return irp->fd == emu->fd; + } +} + +HRESULT ac_io_emu_dispatch_irp(struct ac_io_emu *emu, struct irp *irp) +{ + log_assert(irp != NULL); + + switch (irp->op) { + case IRP_OP_OPEN: return ac_io_emu_open(emu, irp); + case IRP_OP_CLOSE: return ac_io_emu_close(emu, irp); + case IRP_OP_READ: return ac_io_emu_read(emu, irp); + case IRP_OP_WRITE: return ac_io_emu_write(emu, irp); + case IRP_OP_IOCTL: return ac_io_emu_ioctl(emu, irp); + case IRP_OP_FSYNC: return S_FALSE; + default: return E_NOTIMPL; + } +} + +static HRESULT ac_io_emu_open(struct ac_io_emu *emu, struct irp *irp) +{ + irp->fd = emu->fd; + log_info("%s: ACIO port opened", emu->filename); + + return S_FALSE; +} + +static HRESULT ac_io_emu_close(struct ac_io_emu *emu, struct irp *irp) +{ + log_info("%s: ACIO port closed", emu->filename); + + return S_FALSE; +} + +static HRESULT ac_io_emu_read(struct ac_io_emu *emu, struct irp *irp) +{ + ac_io_in_drain(&emu->in, &irp->read); + + return S_FALSE; +} + +static HRESULT ac_io_emu_write(struct ac_io_emu *emu, struct irp *irp) +{ + const struct ac_io_message *msg; + + for (;;) { + ac_io_out_supply(&emu->out, &irp->write); + + if (!ac_io_out_have_message(&emu->out)) { + break; + } + + msg = ac_io_out_get_message(&emu->out); + + if (msg != NULL) { + break; + } + + ac_io_in_supply(&emu->in, NULL, 0); + ac_io_out_consume_message(&emu->out); + } + + return ac_io_out_have_message(&emu->out) ? S_OK : S_FALSE; +} + +static HRESULT ac_io_emu_ioctl(struct ac_io_emu *emu, struct irp *irp) +{ + SERIAL_STATUS *status; + + log_assert(irp != NULL); + + switch (irp->ioctl) { + case IOCTL_SERIAL_GET_COMMSTATUS: + if (irp->read.bytes == NULL) { + log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Output buffer is NULL"); + + return E_INVALIDARG; + } + + if (irp->read.nbytes < sizeof(*status)) { + log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Buffer is too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + status = (SERIAL_STATUS *) irp->read.bytes; + status->Errors = 0; + status->AmountInInQueue = ac_io_in_is_msg_pending(&emu->in); + + irp->read.pos = sizeof(*status); + + return S_FALSE; + + default: + return S_FALSE; + } +} + +const struct ac_io_message *ac_io_emu_request_peek(const struct ac_io_emu *emu) +{ + log_assert(emu != NULL); + + return ac_io_out_get_message(&emu->out); +} + +void ac_io_emu_request_pop(struct ac_io_emu *emu) +{ + log_assert(emu != NULL); + + ac_io_out_consume_message(&emu->out); +} + +void ac_io_emu_response_push( + struct ac_io_emu *emu, + const struct ac_io_message *resp, + uint64_t delay_us) +{ + log_assert(emu != NULL); + log_assert(resp != NULL); + + ac_io_in_supply(&emu->in, resp, delay_us); +} + +void ac_io_emu_response_push_thunk( + struct ac_io_emu *emu, + ac_io_in_thunk_t thunk, + void *ctx, + uint64_t delay_us) +{ + log_assert(emu != NULL); + log_assert(thunk != NULL); + + ac_io_in_supply_thunk(&emu->in, thunk, ctx, delay_us); +} + diff --git a/src/main/acioemu/emu.h b/src/main/acioemu/emu.h new file mode 100644 index 0000000..2ee538a --- /dev/null +++ b/src/main/acioemu/emu.h @@ -0,0 +1,39 @@ +#ifndef ACIOEMU_EMU_H +#define ACIOEMU_EMU_H + +#include + +#include +#include + +#include "acio/acio.h" + +#include "acioemu/pipe.h" + +#include "hook/iohook.h" + +struct ac_io_emu { + HANDLE fd; + wchar_t *wfilename; + char *filename; + struct ac_io_in in; + struct ac_io_out out; +}; + +void ac_io_emu_init(struct ac_io_emu *emu, const wchar_t *filename); +void ac_io_emu_fini(struct ac_io_emu *emu); +bool ac_io_emu_match_irp(const struct ac_io_emu *emu, const struct irp *irp); +HRESULT ac_io_emu_dispatch_irp(struct ac_io_emu *emu, struct irp *irp); +const struct ac_io_message *ac_io_emu_request_peek(const struct ac_io_emu *emu); +void ac_io_emu_request_pop(struct ac_io_emu *emu); +void ac_io_emu_response_push( + struct ac_io_emu *emu, + const struct ac_io_message *resp, + uint64_t delay_ms); +void ac_io_emu_response_push_thunk( + struct ac_io_emu *emu, + ac_io_in_thunk_t thunk, + void *ctx, + uint64_t delay_ms); + +#endif diff --git a/src/main/acioemu/h44b.c b/src/main/acioemu/h44b.c new file mode 100644 index 0000000..e4f68cd --- /dev/null +++ b/src/main/acioemu/h44b.c @@ -0,0 +1,118 @@ +#define LOG_MODULE "acioemu-h44b" + +#include "acioemu/h44b.h" + +#include /* for _BitScanForward */ + +#include +#include + +#include "acio/h44b.h" + +#include "acioemu/emu.h" + +#include "bemanitools/jbio.h" + +#include "util/hex.h" + +static void ac_io_emu_h44b_cmd_send_version( + struct ac_io_emu_h44b *h44b, + const struct ac_io_message *req); + +static void ac_io_emu_h44b_send_status( + struct ac_io_emu_h44b *h44b, + const struct ac_io_message *req, + uint8_t status); + +void ac_io_emu_h44b_init( + struct ac_io_emu_h44b *h44b, + struct ac_io_emu *emu, + uint8_t unit_no) +{ + memset(h44b, 0, sizeof(*h44b)); + h44b->emu = emu; + h44b->unit_no = unit_no; +} + +void ac_io_emu_h44b_dispatch_request( + struct ac_io_emu_h44b *h44b, + const struct ac_io_message *req) +{ + uint16_t cmd_code; + + cmd_code = ac_io_u16(req->cmd.code); + + switch (cmd_code) { + case AC_IO_CMD_GET_VERSION: + log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr); + ac_io_emu_h44b_cmd_send_version(h44b, req); + + break; + + case AC_IO_CMD_START_UP: + log_misc("AC_IO_CMD_START_UP(%d)", req->addr); + ac_io_emu_h44b_send_status(h44b, req, 0x00); + + break; + + case AC_IO_H44B_CMD_SET_OUTPUTS: + /* Not using the struct ac_io_h44b_output here */ + for (int i = 0; i < 6; i++) { + jb_io_set_rgb_led((enum jb_io_rgb_led) i, + req->cmd.raw[i * 3], + req->cmd.raw[i * 3 + 1], + req->cmd.raw[i * 3 + 2]); + } + + jb_io_write_outputs(); + + ac_io_emu_h44b_send_status(h44b, req, 0x00); + + break; + + default: + log_warning("Unknown ACIO message %04x on h44b node, addr=%d", + cmd_code, req->addr); + + break; + } +} + +static void ac_io_emu_h44b_cmd_send_version( + struct ac_io_emu_h44b *h44b, + const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.version); + resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_H44B); + resp.cmd.version.flag = 0x00; + resp.cmd.version.major = 0x01; + resp.cmd.version.minor = 0x00; + resp.cmd.version.revision = 0x02; + memcpy(resp.cmd.version.product_code, "H44B", + sizeof(resp.cmd.version.product_code)); + strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date)); + strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time)); + + ac_io_emu_response_push(h44b->emu, &resp, 0); +} + +static void ac_io_emu_h44b_send_status( + struct ac_io_emu_h44b *h44b, + const struct ac_io_message *req, + uint8_t status) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.status); + resp.cmd.status = status; + + ac_io_emu_response_push(h44b->emu, &resp, 0); +} diff --git a/src/main/acioemu/h44b.h b/src/main/acioemu/h44b.h new file mode 100644 index 0000000..81c4fad --- /dev/null +++ b/src/main/acioemu/h44b.h @@ -0,0 +1,26 @@ +#ifndef AC_IO_EMU_H44B_H +#define AC_IO_EMU_H44B_H + +#include +#include + +#include "acioemu/emu.h" + +struct ac_io_emu_h44b { + struct ac_io_emu *emu; + uint8_t unit_no; + // TODO +}; + +void acioemu_h44b_init(void); + +void ac_io_emu_h44b_init( + struct ac_io_emu_h44b *h44b, + struct ac_io_emu *emu, + uint8_t unit_no); + +void ac_io_emu_h44b_dispatch_request( + struct ac_io_emu_h44b *h44b, + const struct ac_io_message *req); + +#endif diff --git a/src/main/acioemu/hdxs.c b/src/main/acioemu/hdxs.c new file mode 100644 index 0000000..e3aca70 --- /dev/null +++ b/src/main/acioemu/hdxs.c @@ -0,0 +1,133 @@ +#define LOG_MODULE "acioemu-hdxs" + +#include "acio/acio.h" + +#include "acioemu/emu.h" +#include "acioemu/hdxs.h" + +#include "util/log.h" + +static void ac_io_emu_hdxs_cmd_send_version( + struct ac_io_emu_hdxs *hdxs, + const struct ac_io_message *req); + +static void ac_io_emu_hdxs_send_empty( + struct ac_io_emu_hdxs *hdxs, + const struct ac_io_message *req); + +static void ac_io_emu_hdxs_send_status( + struct ac_io_emu_hdxs *hdxs, + const struct ac_io_message *req, + uint8_t status); + +void ac_io_emu_hdxs_init( + struct ac_io_emu_hdxs *hdxs, + struct ac_io_emu *emu) +{ + log_assert(hdxs != NULL); + log_assert(emu != NULL); + + hdxs->emu = emu; +} + +void ac_io_emu_hdxs_dispatch_request( + struct ac_io_emu_hdxs *hdxs, + const struct ac_io_message *req) +{ + uint16_t cmd_code; + + log_assert(hdxs != NULL); + log_assert(req != NULL); + + cmd_code = ac_io_u16(req->cmd.code); + + switch (cmd_code) { + case AC_IO_CMD_GET_VERSION: + log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr); + ac_io_emu_hdxs_cmd_send_version(hdxs, req); + + break; + + case AC_IO_CMD_START_UP: + log_misc("AC_IO_CMD_START_UP(%d)", req->addr); + ac_io_emu_hdxs_send_status(hdxs, req, 0x00); + + break; + + case AC_IO_CMD_CLEAR: + log_misc("AC_IO_CMD_CLEAR(%d)", req->addr); + + case 0x110: + case 0x112: + case 0x128: + ac_io_emu_hdxs_send_status(hdxs, req, 0x00); + + break; + + case AC_IO_CMD_KEEPALIVE: + ac_io_emu_hdxs_send_empty(hdxs, req); + + break; + + default: + log_warning( + "Unknown ACIO message %04x on HDXS node, addr=%d", + cmd_code, + req->addr); + + break; + } +} + +static void ac_io_emu_hdxs_cmd_send_version( + struct ac_io_emu_hdxs *hdxs, + const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.version); + resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_LED_STRIP); + resp.cmd.version.flag = 0x00; + resp.cmd.version.major = 0x01; + resp.cmd.version.minor = 0x06; + resp.cmd.version.revision = 0x00; + memcpy(resp.cmd.version.product_code, "HDXS", + sizeof(resp.cmd.version.product_code)); + strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date)); + strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time)); + + ac_io_emu_response_push(hdxs->emu, &resp, 0); +} + +static void ac_io_emu_hdxs_send_empty( + struct ac_io_emu_hdxs *hdxs, + const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = 0; + + ac_io_emu_response_push(hdxs->emu, &resp, 0); +} + +static void ac_io_emu_hdxs_send_status( + struct ac_io_emu_hdxs *hdxs, + const struct ac_io_message *req, + uint8_t status) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.status); + resp.cmd.status = status; + + ac_io_emu_response_push(hdxs->emu, &resp, 0); +} diff --git a/src/main/acioemu/hdxs.h b/src/main/acioemu/hdxs.h new file mode 100644 index 0000000..08e2e13 --- /dev/null +++ b/src/main/acioemu/hdxs.h @@ -0,0 +1,21 @@ +#ifndef AC_IO_EMU_HDXS_H +#define AC_IO_EMU_HDXS_H + +#include "acio/acio.h" + +#include "acioemu/emu.h" + +struct ac_io_emu_hdxs { + struct ac_io_emu *emu; + // TODO ops vtbl +}; + +void ac_io_emu_hdxs_init( + struct ac_io_emu_hdxs *hdxs, + struct ac_io_emu *emu); + +void ac_io_emu_hdxs_dispatch_request( + struct ac_io_emu_hdxs *hdxs, + const struct ac_io_message *req); + +#endif diff --git a/src/main/acioemu/icca.c b/src/main/acioemu/icca.c new file mode 100644 index 0000000..1bc6d36 --- /dev/null +++ b/src/main/acioemu/icca.c @@ -0,0 +1,385 @@ +#define LOG_MODULE "acioemu-icca" + +#include "acioemu/icca.h" + +#include /* for _BitScanForward */ + +#include +#include + +#include "acio/icca.h" + +#include "acioemu/emu.h" +#include "acioemu/icca.h" + +#include "bemanitools/eamio.h" + +#include "util/time.h" + +enum ac_io_icca_subcmd { + AC_IO_ICCA_SUBCMD_CARD_SLOT_CLOSE = 0x00, + AC_IO_ICCA_SUBCMD_CARD_SLOT_OPEN = 0x11, + AC_IO_ICCA_SUBCMD_CARD_SLOT_EJECT = 0x12, +}; + +enum ac_io_icca_flag { + AC_IO_ICCA_FLAG_FRONT_SENSOR = 0x10, + AC_IO_ICCA_FLAG_REAR_SENSOR = 0x20, + AC_IO_ICCA_FLAG_SOLENOID = 0x40 +}; + +enum ac_io_icca_status_code { + AC_IO_ICCA_STATUS_FAULT = 0x00, + AC_IO_ICCA_STATUS_IDLE = 0x01, + AC_IO_ICCA_STATUS_GOT_UID = 0x02 +}; + +static void ac_io_emu_icca_cmd_send_version( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req); + +static void ac_io_emu_icca_send_state( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req, + uint64_t delay_us); + +static void ac_io_emu_icca_send_empty( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req); + +static void ac_io_emu_icca_send_status( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req, + uint8_t status); + +void ac_io_emu_icca_init( + struct ac_io_emu_icca *icca, + struct ac_io_emu *emu, + uint8_t unit_no) +{ + memset(icca, 0, sizeof(*icca)); + icca->emu = emu; + icca->unit_no = unit_no; + // queue must be started + icca->fault = true; +} + +void ac_io_emu_icca_dispatch_request( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req) +{ + uint16_t cmd_code; + uint64_t delay_us; + + cmd_code = ac_io_u16(req->cmd.code); + + switch (cmd_code) { + case AC_IO_CMD_GET_VERSION: + log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr); + ac_io_emu_icca_cmd_send_version(icca, req); + + break; + + case AC_IO_CMD_START_UP: + log_misc("AC_IO_CMD_START_UP(%d)", req->addr); + icca->detected_new_reader = false; + ac_io_emu_icca_send_status(icca, req, 0x00); + + break; + + case AC_IO_CMD_CLEAR: + log_misc("AC_IO_ICCA_CMD_CLEAR(%d)", req->addr); + ac_io_emu_icca_send_status(icca, req, 0x00); + + break; + + case AC_IO_CMD_KEEPALIVE: + ac_io_emu_icca_send_empty(icca, req); + + break; + + case AC_IO_ICCA_CMD_QUEUE_LOOP_START: + log_misc("AC_IO_CMD_QUEUE_LOOP_START(%d)", req->addr); + // queue started, reset error state + icca->fault = false; + ac_io_emu_icca_send_status(icca, req, 0x00); + icca->polling_started = true; + + break; + + case AC_IO_CMD_UNKN_00FF: + log_misc("AC_IO_CMD_UNKN_00FF(%d)", req->addr); + ac_io_emu_icca_send_status(icca, req, 0x00); + + break; + + case AC_IO_ICCA_CMD_UNKN_0120: + log_misc("AC_IO_ICCA_CMD_UNKN_0120(%d)", req->addr); + ac_io_emu_icca_send_status(icca, req, 0x00); + + break; + + case AC_IO_ICCA_CMD_BEGIN_KEYPAD: + log_misc("AC_IO_ICCA_CMD_BEGIN_KEYPAD(%d)", req->addr); + ac_io_emu_icca_send_status(icca, req, 0x00); + icca->keypad_started = true; + + break; + + case AC_IO_ICCA_CMD_ENGAGE: + ac_io_emu_icca_send_state(icca, req, 0); + + break; + + case AC_IO_ICCA_CMD_SET_SLOT_STATE: + { + struct ac_io_icca_misc* misc = + (struct ac_io_icca_misc*) &req->cmd.raw; + uint8_t cmd; + + switch (misc->subcmd) { + case AC_IO_ICCA_SUBCMD_CARD_SLOT_CLOSE: + cmd = EAM_IO_CARD_SLOT_CMD_CLOSE; + break; + + case AC_IO_ICCA_SUBCMD_CARD_SLOT_OPEN: + cmd = EAM_IO_CARD_SLOT_CMD_OPEN; + break; + + case AC_IO_ICCA_SUBCMD_CARD_SLOT_EJECT: + cmd = EAM_IO_CARD_SLOT_CMD_EJECT; + icca->engaged = false; + break; + + case 3: + cmd = EAM_IO_CARD_SLOT_CMD_READ; + break; + + default: + cmd = 0xFF; + log_warning("Unhandled slot command %X, node %d", + misc->subcmd, icca->unit_no); + break; + } + + if (cmd != 0xFF) { + if (!eam_io_card_slot_cmd(icca->unit_no, cmd)) { + log_warning("Eamio failed to handle slot cmd %d for node %d", + cmd, icca->unit_no); + } + } + + /* response with current slot state */ + ac_io_emu_icca_send_status(icca, req, misc->subcmd); + + break; + } + + case AC_IO_ICCA_CMD_POLL: + delay_us = time_get_elapsed_us(time_get_counter() - icca->time_counter_last_poll); + + /* emulating delay implemented by hardware. do not delay messages that exceed a certain threshold. */ + if (delay_us > 16000) { + delay_us = 0; + } + + icca->time_counter_last_poll = time_get_counter(); + ac_io_emu_icca_send_state(icca, req, delay_us); + + break; + + case AC_IO_ICCA_CMD_POLL_FELICA: + icca->detected_new_reader = true; + ac_io_emu_icca_send_status(icca, req, 0x01); + + break; + + default: + log_warning("Unknown ACIO message %04x on ICCA node, addr=%d", + cmd_code, req->addr); + + break; + } +} + +static void ac_io_emu_icca_cmd_send_version( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.version); + resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_ICCA); + resp.cmd.version.flag = 0x00; + resp.cmd.version.major = 0x01; + resp.cmd.version.minor = 0x06; + resp.cmd.version.revision = 0x00; + memcpy(resp.cmd.version.product_code, "ICCA", + sizeof(resp.cmd.version.product_code)); + strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date)); + strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time)); + + ac_io_emu_response_push(icca->emu, &resp, 0); +} + +static void ac_io_emu_icca_send_empty( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = 0; + + ac_io_emu_response_push(icca->emu, &resp, 0); +} + +static void ac_io_emu_icca_send_status( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req, + uint8_t status) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.status); + resp.cmd.status = status; + + ac_io_emu_response_push(icca->emu, &resp, 0); +} + +static void ac_io_emu_icca_send_state( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req, + uint64_t delay_us) +{ + struct ac_io_message resp; + struct ac_io_icca_state *body; + unsigned long bit; + uint8_t event; + uint16_t keypad; + uint16_t keypad_rise; + uint8_t sensor_state; + bool card_full_insert; + + if (!eam_io_poll(icca->unit_no)) { + log_warning("Polling eamio failed"); + } + + memset(&resp, 0, sizeof(resp)); + + keypad = eam_io_get_keypad_state(icca->unit_no); + sensor_state = eam_io_get_sensor_state(icca->unit_no); + + keypad_rise = keypad & (icca->last_keypad ^ keypad); + card_full_insert = sensor_state & (1 << EAM_IO_SENSOR_FRONT) && + sensor_state & (1 << EAM_IO_SENSOR_BACK); + + if (sensor_state != icca->last_sensor) { + + if (card_full_insert) { + + if (!eam_io_card_slot_cmd(icca->unit_no, + EAM_IO_CARD_SLOT_CMD_READ)) { + log_warning("EAM_IO_CARD_SLOT_CMD_READ to unit %d failed", + icca->unit_no); + } + + icca->card_result = eam_io_read_card( + icca->unit_no, + icca->uid, + sizeof(icca->uid)); + + // fault if sensor says to read but we got no card + icca->fault = (icca->card_result == EAM_IO_CARD_NONE); + } else { + icca->fault = false; + } + } + + icca->last_sensor = sensor_state; + icca->last_keypad = keypad; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(struct ac_io_icca_state); + + body = (struct ac_io_icca_state *) &resp.cmd.raw; + + if (icca->fault) { + body->status_code = AC_IO_ICCA_STATUS_FAULT; + } else if (card_full_insert) { + body->status_code = AC_IO_ICCA_STATUS_GOT_UID; + } else { + body->status_code = AC_IO_ICCA_STATUS_IDLE; + } + + body->sensor_state = 0; + + if (sensor_state & (1 << EAM_IO_SENSOR_FRONT)) { + body->sensor_state |= AC_IO_ICCA_FLAG_FRONT_SENSOR; + } + + if (sensor_state & (1 << EAM_IO_SENSOR_BACK)) { + body->sensor_state |= AC_IO_ICCA_FLAG_REAR_SENSOR; + } + + if (icca->engaged) { + body->sensor_state |= AC_IO_ICCA_FLAG_SOLENOID; + } + + memcpy(body->uid, icca->uid, sizeof(body->uid)); + body->card_type = 0; + if (body->status_code == AC_IO_ICCA_STATUS_GOT_UID){ + if (icca->detected_new_reader){ + // sensor_state actually refers to cardtype for wavepass readers + // EAM_IO_CARD_ISO15696 = 1 -> 0 + // EAM_IO_CARD_FELICA = 2 -> 1 + body->card_type = icca->card_result - 1; + body->sensor_state = icca->card_result - 1; + } + } + + if (keypad_rise) { + + if (icca->key_events[0]) { + event = (icca->key_events[0] + 0x10) & 0xF0; + } else { + event = 0x00; + } + + _BitScanForward(&bit, keypad_rise); + event |= 0x80 | bit; + + icca->key_events[1] = icca->key_events[0]; + icca->key_events[0] = event; + } + + // this doesn't seem to be an error code. If this is not set to 0x03 + // on slotted readers (only?), the game throws an unknown status error + if (icca->keypad_started) { + body->keypad_started = 0x03; + } else { + body->keypad_started = 0x00; + } + body->key_events[0] = icca->key_events[0]; + body->key_events[1] = icca->key_events[1]; + body->key_state = ac_io_u16(keypad); + + // replace status code if polling hasn't started + // this fixes SDVX IC CARD boot errors + if (!icca->polling_started) { + body->status_code = AC_IO_ICCA_STATUS_FAULT; + } + + ac_io_emu_response_push(icca->emu, &resp, delay_us); +} + diff --git a/src/main/acioemu/icca.h b/src/main/acioemu/icca.h new file mode 100644 index 0000000..4da21d2 --- /dev/null +++ b/src/main/acioemu/icca.h @@ -0,0 +1,34 @@ +#ifndef AC_IO_EMU_ICCA_H +#define AC_IO_EMU_ICCA_H + +#include +#include + +#include "acioemu/emu.h" + +struct ac_io_emu_icca { + struct ac_io_emu *emu; + uint8_t unit_no; + bool fault; + bool engaged; + uint8_t last_sensor; + uint16_t last_keypad; + uint8_t key_events[2]; + uint8_t uid[8]; + uint8_t card_result; + bool detected_new_reader; + bool keypad_started; + bool polling_started; + uint64_t time_counter_last_poll; +}; + +void ac_io_emu_icca_init( + struct ac_io_emu_icca *icca, + struct ac_io_emu *emu, + uint8_t unit_no); + +void ac_io_emu_icca_dispatch_request( + struct ac_io_emu_icca *icca, + const struct ac_io_message *req); + +#endif diff --git a/src/main/acioemu/iccb.c b/src/main/acioemu/iccb.c new file mode 100644 index 0000000..af5f382 --- /dev/null +++ b/src/main/acioemu/iccb.c @@ -0,0 +1,227 @@ +#define LOG_MODULE "acioemu-iccb" + +#include "acioemu/iccb.h" + +#include /* for _BitScanForward */ + +#include +#include + +#include "acio/iccb.h" + +#include "acioemu/emu.h" + +#include "bemanitools/eamio.h" + +static void ac_io_emu_iccb_cmd_send_version( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req); + +static void ac_io_emu_iccb_send_state( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req); + +static void ac_io_emu_iccb_send_empty( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req); + +static void ac_io_emu_iccb_send_status( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req, + uint8_t status); + +void ac_io_emu_iccb_init( + struct ac_io_emu_iccb *iccb, + struct ac_io_emu *emu, + uint8_t unit_no) +{ + memset(iccb, 0, sizeof(*iccb)); + iccb->emu = emu; + iccb->unit_no = unit_no; +} + +void ac_io_emu_iccb_dispatch_request( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req) +{ + uint16_t cmd_code; + + cmd_code = ac_io_u16(req->cmd.code); + + switch (cmd_code) { + case AC_IO_CMD_GET_VERSION: + log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr); + ac_io_emu_iccb_cmd_send_version(iccb, req); + + break; + + case AC_IO_CMD_START_UP: + log_misc("AC_IO_CMD_START_UP(%d)", req->addr); + ac_io_emu_iccb_send_status(iccb, req, 0x00); + + break; + + case AC_IO_CMD_KEEPALIVE: + ac_io_emu_iccb_send_empty(iccb, req); + + break; + + case AC_IO_ICCB_CMD_QUEUE_LOOP_START: + log_misc("AC_IO_CMD_QUEUE_LOOP_START(%d)", req->addr); + ac_io_emu_iccb_send_status(iccb, req, 0x00); + + break; + + case AC_IO_ICCB_CMD_UNK_0100: + case AC_IO_ICCB_CMD_UNK_0116: + case AC_IO_ICCB_CMD_UNK_0120: + log_misc("AC_IO_ICCB_CMD_UNK_%04X(%d)", cmd_code, req->addr); + ac_io_emu_iccb_send_status(iccb, req, 0x00); + + break; + + case AC_IO_ICCB_CMD_SLEEP: + ac_io_emu_iccb_send_status(iccb, req, 0x00); + + break; + + case AC_IO_ICCB_CMD_UNK_135: + /* log_misc("AC_IO_ICCB_CMD_UNK_135"); */ + ac_io_emu_iccb_send_state(iccb, req); + + break; + + case AC_IO_ICCB_CMD_POLL: + /* log_misc("AC_IO_ICCB_CMD_POLL"); */ + ac_io_emu_iccb_send_state(iccb, req); + + break; + + case AC_IO_ICCB_CMD_READ_CARD: + /* log_misc("AC_IO_ICCB_CMD_READ_CARD"); */ + ac_io_emu_iccb_send_state(iccb, req); + + break; + + default: + log_warning("Unknown ACIO message %04x on ICCB node, addr=%d", + cmd_code, req->addr); + + break; + } +} + +static void ac_io_emu_iccb_cmd_send_version( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.version); + resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_ICCB); + resp.cmd.version.flag = 0x00; + resp.cmd.version.major = 0x01; + resp.cmd.version.minor = 0x05; + resp.cmd.version.revision = 0x01; + memcpy(resp.cmd.version.product_code, "ICCB", + sizeof(resp.cmd.version.product_code)); + strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date)); + strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time)); + + ac_io_emu_response_push(iccb->emu, &resp, 0); +} + +static void ac_io_emu_iccb_send_empty( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = 0; + + ac_io_emu_response_push(iccb->emu, &resp, 0); +} + +static void ac_io_emu_iccb_send_status( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req, + uint8_t status) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.status); + resp.cmd.status = status; + + ac_io_emu_response_push(iccb->emu, &resp, 0); +} + +static void ac_io_emu_iccb_send_state( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req) +{ + struct ac_io_message resp; + struct ac_io_iccb_state *body; + bool sensor; + + /* state update */ + + sensor = eam_io_get_sensor_state(iccb->unit_no); + + if (sensor != iccb->last_sensor) { + if (sensor) { + iccb->card_result = eam_io_read_card( + iccb->unit_no, + iccb->uid, + sizeof(iccb->uid)); + + // fault if sensor says to read but we got no card + iccb->fault = (iccb->card_result == EAM_IO_CARD_NONE); + } else { + iccb->fault = false; + } + } + + iccb->last_sensor = sensor; + + if (iccb->fault) { + memset(iccb->uid, 0, sizeof(iccb->uid)); + } + + /* create response */ + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(struct ac_io_iccb_state); + + body = (struct ac_io_iccb_state *) &resp.cmd.raw; + + if (sensor) { + body->sensor_state = AC_IO_ICCB_SENSOR_CARD; + } else { + body->sensor_state = AC_IO_ICCB_SENSOR_NO_CARD; + } + + if (!iccb->fault) { + memcpy(body->uid, iccb->uid, sizeof(body->uid)); + } + + body->card_type = 0x30 | (iccb->card_result - 1); + body->unk2 = 0; + // this doesn't seem to be an error code. If this is not set to 0x03 + // on slotted readers (only?), the game throws an unknown status error + body->unk3 = 0x03; + memset(body->unk4, 0, sizeof(body->unk4)); + + ac_io_emu_response_push(iccb->emu, &resp, 0); +} + diff --git a/src/main/acioemu/iccb.h b/src/main/acioemu/iccb.h new file mode 100644 index 0000000..31b60d8 --- /dev/null +++ b/src/main/acioemu/iccb.h @@ -0,0 +1,27 @@ +#ifndef AC_IO_EMU_ICCB_H +#define AC_IO_EMU_ICCB_H + +#include +#include + +#include "acioemu/emu.h" + +struct ac_io_emu_iccb { + struct ac_io_emu *emu; + uint8_t unit_no; + bool fault; + bool last_sensor; + uint8_t uid[8]; + uint8_t card_result; +}; + +void ac_io_emu_iccb_init( + struct ac_io_emu_iccb *iccb, + struct ac_io_emu *emu, + uint8_t unit_no); + +void ac_io_emu_iccb_dispatch_request( + struct ac_io_emu_iccb *iccb, + const struct ac_io_message *req); + +#endif diff --git a/src/main/acioemu/pipe.c b/src/main/acioemu/pipe.c new file mode 100644 index 0000000..357ad61 --- /dev/null +++ b/src/main/acioemu/pipe.c @@ -0,0 +1,366 @@ +#define LOG_MODULE "acioemu-emu" + +#include +#include +#include +#include +#include + +#include "acioemu/pipe.h" + +#include "util/defs.h" +#include "util/list.h" +#include "util/mem.h" +#include "util/time.h" + +static struct ac_io_in_queued *ac_io_in_queued_alloc(struct ac_io_in *in, + uint64_t delay_us); +static void ac_io_in_queued_populate( + struct ac_io_in_queued *iq, + const struct ac_io_message *msg); +static void ac_io_in_queued_putc(struct ac_io_in_queued *iq, uint8_t b); + +static bool ac_io_out_supply_byte(struct ac_io_out *out, uint8_t b); +static bool ac_io_out_supply_frame_byte(struct ac_io_out *out, uint8_t b); +static bool ac_io_out_detect_broadcast_eof(struct ac_io_out *out); +static bool ac_io_out_detect_command_eof(struct ac_io_out *out); +static bool ac_io_out_check_sum(struct ac_io_out *out); +static bool ac_io_out_accept_message(struct ac_io_out *out); +static bool ac_io_out_reject_message(struct ac_io_out *out); + +static bool ac_io_enable_legacy_mode = false; + +static struct ac_io_in_queued *ac_io_in_queued_alloc(struct ac_io_in *in, + uint64_t delay_us) +{ + struct ac_io_in_queued *iq; + + /* TODO ordered insert by scheduled_time */ + + iq = xmalloc(sizeof(*iq)); + iq->iobuf.bytes = iq->bytes; + iq->iobuf.nbytes = 1; + iq->iobuf.pos = 0; + iq->scheduled_time = time_get_counter(); + iq->delay_us = delay_us; + iq->bytes[0] = AC_IO_SOF; + + list_append(&in->queue, &iq->node); + + return iq; +} + +static void ac_io_in_queued_populate( + struct ac_io_in_queued *iq, + const struct ac_io_message *msg) +{ + uint8_t checksum; + const uint8_t *src; + size_t nbytes; + size_t i; + + if (msg->addr == AC_IO_BROADCAST) { + nbytes = offsetof(struct ac_io_message, bcast.raw) + msg->bcast.nbytes; + } else { + nbytes = offsetof(struct ac_io_message, cmd.raw) + msg->cmd.nbytes; + } + + src = (const uint8_t *) msg; + checksum = 0; + + for (i = 0 ; i < nbytes ; i++) { + ac_io_in_queued_putc(iq, src[i]); + checksum += src[i]; + } + + ac_io_in_queued_putc(iq, checksum); +} + +static void ac_io_in_queued_putc(struct ac_io_in_queued *iq, uint8_t b) +{ + if (b == AC_IO_SOF || b == AC_IO_ESCAPE) { + iq->bytes[iq->iobuf.nbytes++] = AC_IO_ESCAPE; + iq->bytes[iq->iobuf.nbytes++] = ~b; + } else { + iq->bytes[iq->iobuf.nbytes++] = b; + } +} + +void ac_io_legacy_mode(void) +{ + log_info("Running acioemu legacy mode"); + ac_io_enable_legacy_mode = true; +} + +void ac_io_in_init(struct ac_io_in *in) +{ + list_init(&in->queue); +} + +void ac_io_in_supply(struct ac_io_in *in, const struct ac_io_message *msg, + uint64_t delay_us) +{ + struct ac_io_in_queued *dest; + + dest = ac_io_in_queued_alloc(in, delay_us); + dest->thunk = NULL; + + if (msg == NULL) { + return; + } + + ac_io_in_queued_populate(dest, msg); +} + +void ac_io_in_supply_thunk( + struct ac_io_in *in, + ac_io_in_thunk_t thunk, + void *ctx, + uint64_t delay_us) +{ + struct ac_io_in_queued *dest; + + dest = ac_io_in_queued_alloc(in, delay_us); + + dest->thunk = thunk; + dest->thunk_ctx = ctx; +} + +void ac_io_in_drain(struct ac_io_in *in, struct iobuf *dest) +{ + struct ac_io_in_queued *iq; + struct list_node *node; + struct ac_io_message msg; + size_t nmoved; + uint64_t now; + uint64_t elapsed_us; + + now = time_get_counter(); + + do { + node = list_peek_head(&in->queue); + + if (node == NULL) { + break; + } + + iq = containerof(node, struct ac_io_in_queued, node); + elapsed_us = time_get_elapsed_us( + now > iq->scheduled_time ? now - iq->scheduled_time : 0); + + if (elapsed_us < iq->delay_us) { + break; + } + + if (iq->thunk != NULL) { + iq->thunk(iq->thunk_ctx, &msg); + iq->thunk = NULL; + + ac_io_in_queued_populate(iq, &msg); + } + + nmoved = iobuf_move(dest, &iq->iobuf); + + if (iq->iobuf.pos == iq->iobuf.nbytes) { + list_pop_head(&in->queue); + free(iq); + } + + /* Legacy mode for old libacio versions like DistorteD: + spit out single responses instead of combining them into a single + io buffer. Some old libacio versions expect separate messages and + will error (code 0x00000002) on multiple messages in a single buffer + because they just read the first message and drop the remaining ones. + If using legacy mode on newer libacio versions (e.g. Copula), + the game will error with the same error code. + */ + if (ac_io_enable_legacy_mode) { + break; + } + } while (nmoved > 0); +} + +bool ac_io_in_is_msg_pending(const struct ac_io_in *in) +{ + return list_peek_head_const(&in->queue) != NULL; +} + +void ac_io_out_init(struct ac_io_out *out) +{ + out->pos = 0; + out->in_frame = false; + out->escape = false; + out->have_message = false; +} + +void ac_io_out_supply(struct ac_io_out *out, struct const_iobuf *src) +{ + log_assert(!ac_io_out_have_message(out)); + + while (src->pos < src->nbytes) { + if (!ac_io_out_supply_byte(out, src->bytes[src->pos++])) { + break; + } + } +} + +static bool ac_io_out_supply_byte(struct ac_io_out *out, uint8_t b) +{ + if (out->in_frame) { + return ac_io_out_supply_frame_byte(out, b); + } else { + + if (b == AC_IO_SOF) { + out->in_frame = true; + } else if (b == AC_IO_ESCAPE) { + log_warning("Framing error: Escape byte outside frame"); + } + + return true; + } +} + +static bool ac_io_out_supply_frame_byte(struct ac_io_out *out, uint8_t b) +{ + if (out->escape) { + out->escape = false; + + if (b == AC_IO_SOF || b == AC_IO_ESCAPE) { + log_warning("Framing error: Control byte after escape byte"); + } + + b = ~b; + } else if (b == AC_IO_ESCAPE) { + out->escape = true; + + return true; + } else if (b == AC_IO_SOF) { + + if (out->pos == 0) { + /* Got autobaud/empty message */ + out->have_message = true; + + return false; + } else { + log_warning("Truncated message"); + out->pos = 0; + + return true; + } + + } + + /* Payload byte */ + + out->bytes[out->pos++] = b; + + /* Handle contextually-implied end-of-packet events */ + + if (out->pos > offsetof(struct ac_io_message, addr)) { + + if (out->msg.addr == AC_IO_BROADCAST) { + return ac_io_out_detect_broadcast_eof(out); + } else { + return ac_io_out_detect_command_eof(out); + } + + } + + return true; +} + +static bool ac_io_out_detect_broadcast_eof(struct ac_io_out *out) +{ + size_t end; + + if (out->pos > offsetof(struct ac_io_message, bcast.nbytes)) { + end = offsetof(struct ac_io_message,bcast.raw) + + out->msg.bcast.nbytes + + 1; + + if (out->pos == end) { + return ac_io_out_check_sum(out); + } + } + + return true; +} + +static bool ac_io_out_detect_command_eof(struct ac_io_out *out) +{ + size_t end; + + if (out->pos > offsetof(struct ac_io_message, cmd.nbytes)) { + end = offsetof(struct ac_io_message, cmd.raw) + + out->msg.cmd.nbytes + + 1; + + if (out->pos == end) { + return ac_io_out_check_sum(out); + } + } + + return true; +} + +static bool ac_io_out_check_sum(struct ac_io_out *out) +{ + uint8_t checksum; + size_t i; + + checksum = 0; + + for (i = 0 ; i < out->pos - 1 ; i++) { + checksum += out->bytes[i]; + } + + if (checksum == out->bytes[out->pos - 1]) { + return ac_io_out_accept_message(out); + } else { + log_warning("Checksum bad: expected %02x got %02x", + checksum, out->bytes[out->pos - 1]); + + return ac_io_out_reject_message(out); + } +} + +static bool ac_io_out_accept_message(struct ac_io_out *out) +{ + out->have_message = true; + + return false; +} + +static bool ac_io_out_reject_message(struct ac_io_out *out) +{ + ac_io_out_consume_message(out); + + return true; +} + +bool ac_io_out_have_message(const struct ac_io_out *out) +{ + return out->have_message; +} + +const struct ac_io_message *ac_io_out_get_message(const struct ac_io_out *out) +{ + if (out->pos == 0) { + /* Autobaud byte/empty frame */ + return NULL; + } else { + return &out->msg; + } +} + +void ac_io_out_consume_message(struct ac_io_out *out) +{ + if (out->pos != 0) { + out->in_frame = false; + } + + out->pos = 0; + out->have_message = false; + out->escape = false; +} + diff --git a/src/main/acioemu/pipe.h b/src/main/acioemu/pipe.h new file mode 100644 index 0000000..5605460 --- /dev/null +++ b/src/main/acioemu/pipe.h @@ -0,0 +1,66 @@ +#ifndef ACIOEMU_PIPE_H +#define ACIOEMU_PIPE_H + +#include +#include +#include + +#include "acio/acio.h" + +#include "util/iobuf.h" +#include "util/list.h" +#include "util/log.h" + +/* This uses the USB convention where OUT and IN are from the host's (game's) + perspective. So an OUT transaction comes in to us and vice versa. + + (I made those terms up, not Konami). */ + +typedef void (*ac_io_in_thunk_t)(void *ctx, struct ac_io_message *msg); + +struct ac_io_in_queued { + struct list_node node; + struct const_iobuf iobuf; + uint64_t scheduled_time; + uint64_t delay_us; + ac_io_in_thunk_t thunk; + void *thunk_ctx; + uint8_t bytes[sizeof(struct ac_io_message)]; +}; + +struct ac_io_in { + struct list queue; +}; + +struct ac_io_out { + union { + uint8_t bytes[sizeof(struct ac_io_message)]; + struct ac_io_message msg; + }; + + size_t pos; + bool in_frame; + bool escape; + bool have_message; +}; + +void ac_io_legacy_mode(void); + +void ac_io_in_init(struct ac_io_in *in); +void ac_io_in_supply(struct ac_io_in *in, const struct ac_io_message *msg, + uint64_t delay); +void ac_io_in_supply_thunk( + struct ac_io_in *in, + ac_io_in_thunk_t thunk, + void *ctx, + uint64_t delay); +void ac_io_in_drain(struct ac_io_in *in, struct iobuf *dest); +bool ac_io_in_is_msg_pending(const struct ac_io_in *in); + +void ac_io_out_init(struct ac_io_out *out); +void ac_io_out_supply(struct ac_io_out *out, struct const_iobuf *src); +bool ac_io_out_have_message(const struct ac_io_out *out); +const struct ac_io_message *ac_io_out_get_message(const struct ac_io_out *out); +void ac_io_out_consume_message(struct ac_io_out *out); + +#endif diff --git a/src/main/aciotest/Module.mk b/src/main/aciotest/Module.mk new file mode 100644 index 0000000..43bfe5b --- /dev/null +++ b/src/main/aciotest/Module.mk @@ -0,0 +1,9 @@ +exes += aciotest + +libs_aciotest := \ + aciodrv \ + util \ + +src_aciotest := \ + icca.c \ + main.c \ diff --git a/src/main/aciotest/handler.h b/src/main/aciotest/handler.h new file mode 100644 index 0000000..8bf0cb3 --- /dev/null +++ b/src/main/aciotest/handler.h @@ -0,0 +1,16 @@ +#ifndef ACIOTEST_HANDLER_H +#define ACIOTEST_HANDLER_H + +static const uint8_t aciotest_handler_max = 16; + +/** + * Handler interface for an ACIO device. + */ +struct aciotest_handler_node_handler +{ + void* ctx; + bool (*init)(uint8_t node_id, void** ctx); + bool (*update)(uint8_t node_id, void* ctx); +}; + +#endif \ No newline at end of file diff --git a/src/main/aciotest/icca.c b/src/main/aciotest/icca.c new file mode 100644 index 0000000..e465ece --- /dev/null +++ b/src/main/aciotest/icca.c @@ -0,0 +1,83 @@ +#include "aciotest/icca.h" + +#include +#include + +#include "aciodrv/icca.h" + +bool aciotest_icca_handler_init(uint8_t node_id, void** ctx) +{ + *ctx = malloc(sizeof(uint32_t)); + *((uint32_t*) *ctx) = 0; + + return aciodrv_icca_init(node_id); +} + +bool aciotest_icca_handler_update(uint8_t node_id, void* ctx) +{ + if (*((uint32_t*) ctx) == 0) { + *((uint32_t*) ctx) = 1; + + /* eject cards that were left in the reader */ + if (!aciodrv_icca_set_state(node_id, AC_IO_ICCA_SLOT_STATE_EJECT, NULL)) { + return false; + } + + } + + struct ac_io_icca_state state; + + if (!aciodrv_icca_get_state(node_id, &state)) { + return false; + } + + printf(">>> ICCA %d:\n" + "status_code: %d\n" + "sensor_state: %d\n" + "keypad_started: %d\n" + "card_type: %d\n" + "UUID: %02X%02X%02X%02X%02X%02X%02X%02X\n" + "key_state: %04X\n" + "key_events[0]: %02X\n" + "key_events[1]: %02X\n", + node_id, state.status_code, state.sensor_state, state.keypad_started, + state.card_type, + state.uid[0], state.uid[1], state.uid[2], state.uid[3], + state.uid[4], state.uid[5], state.uid[6], state.uid[7], + state.key_state, state.key_events[0], state.key_events[1]); + + /* eject card with "empty" key */ + if (state.key_state & AC_IO_ICCA_KEYPAD_MASK_EMPTY) { + + if (!aciodrv_icca_set_state(node_id, AC_IO_ICCA_SLOT_STATE_EJECT, NULL)) { + return false; + } + + } + + /* allow new card to be inserted when slot is clear */ + if ( !(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_BACK_ON) && + !(state.sensor_state & AC_IO_ICCA_SENSOR_MASK_FRONT_ON)) { + + if (!aciodrv_icca_set_state(node_id, AC_IO_ICCA_SLOT_STATE_OPEN, NULL)) { + return false; + } + + } + + /* lock the card when fully inserted */ + if ( (state.sensor_state & AC_IO_ICCA_SENSOR_MASK_BACK_ON) && + (state.sensor_state & AC_IO_ICCA_SENSOR_MASK_FRONT_ON)) { + + if (!aciodrv_icca_set_state(node_id, AC_IO_ICCA_SLOT_STATE_CLOSE, NULL)) { + return false; + } + + if (!aciodrv_icca_read_card(node_id, NULL)) { + return false; + } + + } + + return true; +} \ No newline at end of file diff --git a/src/main/aciotest/icca.h b/src/main/aciotest/icca.h new file mode 100644 index 0000000..0b14d20 --- /dev/null +++ b/src/main/aciotest/icca.h @@ -0,0 +1,10 @@ +#ifndef ACIOTEST_ICCA_H +#define ACIOTEST_ICCA_H + +#include +#include + +bool aciotest_icca_handler_init(uint8_t node_id, void** ctx); +bool aciotest_icca_handler_update(uint8_t node_id, void* ctx); + +#endif \ No newline at end of file diff --git a/src/main/aciotest/main.c b/src/main/aciotest/main.c new file mode 100644 index 0000000..21045f7 --- /dev/null +++ b/src/main/aciotest/main.c @@ -0,0 +1,120 @@ +#include +#include +#include + +#include + +#include "aciodrv/device.h" + +#include "aciotest/handler.h" +#include "aciotest/icca.h" + +#include "util/log.h" + +static uint8_t aciotest_cnt = 0; + +/** + * Enumerate supported ACIO nodes based on their product id. + */ +static bool aciotest_assign_handler(char product[4], + struct aciotest_handler_node_handler* handler) +{ + if (!memcmp(product, "ICCA", 4) + || !memcmp(product, "ICCB", 4) + || !memcmp(product, "ICCC", 4) + ){ + handler->init = aciotest_icca_handler_init; + handler->update = aciotest_icca_handler_update; + + return true; + } + + return false; +} + +/** + * Tool to test real ACIO hardware. + */ +int main(int argc, char** argv) +{ + if (argc < 3) { + printf( + "aciotest, build "__DATE__ " " __TIME__ "\n" + "Usage: %s \n" + "Example for two slotted readers: %s COM1 57600\n", + argv[0], argv[0]); + return -1; + } + + log_to_writer(log_writer_stdout, NULL); + + if (!aciodrv_device_open(argv[1], atoi(argv[2]))) { + printf("Opening acio device failed\n"); + return -1; + } + + printf("Opening acio device successful\n"); + + uint8_t node_count = aciodrv_device_get_node_count(); + printf("Enumerated %d nodes\n", node_count); + + struct aciotest_handler_node_handler handler[aciotest_handler_max]; + memset(&handler, 0, + sizeof(struct aciotest_handler_node_handler) * aciotest_handler_max); + + for (uint8_t i = 0; i < node_count; i++) { + char product[4]; + aciodrv_device_get_node_product_ident(i, product); + printf("> %d: %c%c%c%c\n", i + 1, + product[0], product[1], product[2], product[3]); + + if (!aciotest_assign_handler(product, &handler[i])) { + printf("ERROR: Unsupported acio node product %c%c%c%c on node %d\n", + product[0], product[1], product[2], product[3], i); + } + + } + + for (uint8_t i = 0; i < aciotest_handler_max; i++) { + + if (handler[i].init != NULL) { + + if (!handler[i].init(i, &handler[i].ctx)) { + printf("ERROR: Initializing node %d failed\n", i); + handler[i].update = NULL; + } + + } + + } + + printf(">>> Initializing done, press enter to start update loop <<<\n"); + + if (getchar() != '\n') { + return 0; + } + + while (true) { + system("cls"); + printf("%d\n", aciotest_cnt++); + + for (uint8_t i = 0; i < aciotest_handler_max; i++) { + + if (handler[i].update != NULL) { + + if (!handler[i].update(i, handler[i].ctx)) { + printf("ERROR: Updating node %d, removed from loop\n", i); + handler[i].update = NULL; + Sleep(5000); + } + + } + + } + + /* avoid cpu banging */ + Sleep(20); + } + + return 0; +} \ No newline at end of file diff --git a/src/main/bemanitools/bstio.h b/src/main/bemanitools/bstio.h new file mode 100644 index 0000000..65c967e --- /dev/null +++ b/src/main/bemanitools/bstio.h @@ -0,0 +1,49 @@ +#ifndef BEMANITOOLS_SDVXIO_H +#define BEMANITOOLS_SDVXIO_H + +/* IO emulation provider for BeatStream */ + +#include +#include + +#include "bemanitools/glue.h" + +enum bst_io_in_gpio_sys_bit { + SDVX_IO_IN_GPIO_SYS_COIN = 2, + SDVX_IO_IN_GPIO_SYS_TEST = 4, + SDVX_IO_IN_GPIO_SYS_SERVICE = 5, +}; + +/* The first function that will be called on your DLL. You will be supplied + with four function pointers that may be used to log messages to the game's + log file. See comments in glue.h for further information. */ + +void bst_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal); + +/* Initialize your BST IO emulation DLL. Thread management functions are + provided to you; you must use these functions to create your own threads if + you want to make use of the logging functions that are provided to + eam_io_set_loggers(). You will also need to pass these thread management + functions on to geninput if you intend to make use of that library. + + See glue.h and geninput.h for further details. */ + +bool bst_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy); + +/* Shut down your SDVX IO emulation DLL */ + +void bst_io_fini(void); + +/* Read input state. Returns true if successful. */ + +bool bst_io_read_input(void); + +/* Get state of coin, test, service inputs */ + +uint8_t bst_io_get_input(void); + +// TODO: Lighting + +#endif diff --git a/src/main/bemanitools/ddrio.h b/src/main/bemanitools/ddrio.h new file mode 100644 index 0000000..1fb72b5 --- /dev/null +++ b/src/main/bemanitools/ddrio.h @@ -0,0 +1,84 @@ +#ifndef BEMANITOOLS_DDRIO_H +#define BEMANITOOLS_DDRIO_H + +#include +#include + +#include "bemanitools/glue.h" + +enum ddr_pad_bit { + DDR_TEST = 0x04, + DDR_COIN = 0x05, + DDR_SERVICE = 0x06, + + DDR_P2_START = 0x08, + DDR_P2_UP = 0x09, + DDR_P2_DOWN = 0x0A, + DDR_P2_LEFT = 0x0B, + DDR_P2_RIGHT = 0x0C, + DDR_P2_MENU_LEFT = 0x0E, + DDR_P2_MENU_RIGHT = 0x0F, + DDR_P2_MENU_UP = 0x02, + DDR_P2_MENU_DOWN = 0x03, + + DDR_P1_START = 0x10, + DDR_P1_UP = 0x11, + DDR_P1_DOWN = 0x12, + DDR_P1_LEFT = 0x13, + DDR_P1_RIGHT = 0x14, + DDR_P1_MENU_LEFT = 0x16, + DDR_P1_MENU_RIGHT = 0x17, + DDR_P1_MENU_UP = 0x00, + DDR_P1_MENU_DOWN = 0x01, +}; + +/* p3io controls menu btn and marquee lights + extio controls neons and stage lights. */ + +enum p3io_light_bit { + LIGHT_P1_MENU = 0x00, + LIGHT_P2_MENU = 0x01, + LIGHT_P2_LOWER_LAMP = 0x04, + LIGHT_P2_UPPER_LAMP = 0x05, + LIGHT_P1_LOWER_LAMP = 0x06, + LIGHT_P1_UPPER_LAMP = 0x07 +}; + +enum extio_light_bit { + LIGHT_NEONS = 0x0E, + + LIGHT_P2_RIGHT = 0x13, + LIGHT_P2_LEFT = 0x14, + LIGHT_P2_DOWN = 0x15, + LIGHT_P2_UP = 0x16, + + LIGHT_P1_RIGHT = 0x1B, + LIGHT_P1_LEFT = 0x1C, + LIGHT_P1_DOWN = 0x1D, + LIGHT_P1_UP = 0x1E +}; + +/* The first function that will be called on your DLL. You will be supplied + with four function pointers that may be used to log messages to the game's + log file. See comments in glue.h for further information. */ + +void ddr_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal); + +/* Initialize your DDR IO emulation DLL. Thread management functions are + provided to you; you must use these functions to create your own threads if + you want to make use of the logging functions that are provided to + eam_io_set_loggers(). You will also need to pass these thread management + functions on to geninput if you intend to make use of that library. + + See glue.h and geninput.h for further details. */ + +bool ddr_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy); + +uint32_t ddr_io_read_pad(void); +void ddr_io_set_lights_extio(uint32_t extio_lights); +void ddr_io_set_lights_p3io(uint32_t p3io_lights); +void ddr_io_fini(void); + +#endif diff --git a/src/main/bemanitools/eamio.h b/src/main/bemanitools/eamio.h new file mode 100644 index 0000000..520b1fd --- /dev/null +++ b/src/main/bemanitools/eamio.h @@ -0,0 +1,132 @@ +#ifndef BEMANITOOLS_EAM_H +#define BEMANITOOLS_EAM_H + +/* Card reader emulator API. You may replace the stock EAMIO.DLL supplied by + Bemanitools with your own custom implementation, which should implement the + interface contract defined in this header file. */ + +#include +#include + +#include "bemanitools/glue.h" + +/* Scan codes for the so-called "10 key" button panel on each card reader. Each + scan code corresponds to a bit position within the 16-bit bitfield that you + return from eam_io_get_keypad_state(). */ + +enum eam_io_keypad_scan_code { + EAM_IO_KEYPAD_0 = 0, + EAM_IO_KEYPAD_1 = 1, + EAM_IO_KEYPAD_4 = 2, + EAM_IO_KEYPAD_7 = 3, + EAM_IO_KEYPAD_00 = 4, + EAM_IO_KEYPAD_2 = 5, + EAM_IO_KEYPAD_5 = 6, + EAM_IO_KEYPAD_8 = 7, + EAM_IO_KEYPAD_DECIMAL = 8, + EAM_IO_KEYPAD_3 = 9, + EAM_IO_KEYPAD_6 = 10, + EAM_IO_KEYPAD_9 = 11, + + EAM_IO_KEYPAD_COUNT = 12, /* Not an actual scan code */ +}; + +/* Emulating the sensors of a slotted card reader. The reader has one + sensor at the front that detects if a card is getting inserted or + if the card is not fully removed. When the back sensor is triggered + the card is locked in the slot and its data is read. */ + +enum eam_io_sensor_state { + EAM_IO_SENSOR_FRONT = 0, + EAM_IO_SENSOR_BACK = 1, +}; + +/* Different commands for the (slotted) reader. The game triggers one + of these actions and the card slot as to execute it. When non-slotted + readers are emulated, these states are not used/set. */ + +enum eam_io_card_slot_cmd { + EAM_IO_CARD_SLOT_CMD_CLOSE = 0, + EAM_IO_CARD_SLOT_CMD_OPEN = 1, + EAM_IO_CARD_SLOT_CMD_EJECT = 2, + EAM_IO_CARD_SLOT_CMD_READ = 3, +}; + +/* Emulating of the card type for new readers. */ + +enum eam_io_read_card_result { + EAM_IO_CARD_NONE = 0, + EAM_IO_CARD_ISO15696 = 1, + EAM_IO_CARD_FELICA = 2, +}; + +/* A private function pointer table returned by the stock EAMIO.DLL + implementation and consumed by config.exe. The contents of this table are + undocumented and subject to change without notice. */ + +struct eam_io_config_api; + +/* The first function that will be called on your DLL. You will be supplied + with four function pointers that may be used to log messages to the game's + log file. See comments in glue.h for further information. */ + +void eam_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal); + +/* Initialize your card reader emulation DLL. Thread management functions are + provided to you; you must use these functions to create your own threads if + you want to make use of the logging functions that are provided to + eam_io_set_loggers(). You will also need to pass these thread management + functions on to geninput if you intend to make use of that library. + + See glue.h and geninput.h for further details. */ + +bool eam_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy); + +/* Shut down your card reader emulation DLL. */ + +void eam_io_fini(void); + +/* Return the state of the number pad on your reader. This function will be + called frequently. See enum eam_io_keypad_scan_code above for the meaning of + each bit within the return value. + + This function will be called even if the running game does not actually have + a number pad on the real cabinet (e.g. Jubeat). + + unit_no is either 0 or 1. Games with only a single reader (jubeat, popn, + drummania) will only use unit_no 0. */ + +uint16_t eam_io_get_keypad_state(uint8_t unit_no); + +/* Indicate which sensors (front and back) are triggered for a slotted reader + (refer to enum). To emulate non-slotted readers, just set both sensors + to on to indicate the card is in range of the reader. This function + will be called frequently. */ + +uint8_t eam_io_get_sensor_state(uint8_t unit_no); + +/* Read a card ID. This function is only called when the return value of + eam_io_get_sensor_state() changes from false to true, so you may take your + time and perform file I/O etc, within reason. You must return exactly eight + bytes into the buffer pointed to by card_id. */ + +uint8_t eam_io_read_card(uint8_t unit_no, uint8_t *card_id, uint8_t nbytes); + +/* Send a command to the card slot. This is called by the game to execute + certain actions on a slotted reader (refer to enum). When emulating + wave pass readers, this is function is never called. */ + +bool eam_io_card_slot_cmd(uint8_t unit_no, uint8_t cmd); + +/* This function is called frequently. Update your device and states in here */ + +bool eam_io_poll(uint8_t unit_no); + +/* Return a pointer to an internal configuration API for use by config.exe. + Custom implementations should return NULL. */ + +const struct eam_io_config_api *eam_io_get_config_api(void); + +#endif diff --git a/src/main/bemanitools/glue.h b/src/main/bemanitools/glue.h new file mode 100644 index 0000000..50d7d8f --- /dev/null +++ b/src/main/bemanitools/glue.h @@ -0,0 +1,47 @@ +#ifndef BEMANITOOLS_GLUE_H +#define BEMANITOOLS_GLUE_H + +/* Common definitions for integration bindings */ + +#include + +#ifdef __GNUC__ +/* Bemanitools is compiled with GCC (MinGW, specifically) as of version 5 */ +#define LOG_CHECK_FMT __attribute__(( format(printf, 2, 3) )) +#else +/* Compile it out for MSVC plebs */ +#define LOG_CHECK_FMT +#endif + +/* An AVS-style logger function. Comes in four flavors: misc, info, warning, + and fatal, with increasing severity. Fatal loggers do not return, they + abort the running process after writing their message to the log. + + "module" is an arbitrary short string identifying the source of the log + message. The name of the calling DLL is a good default choice for this + string, although you might want to identify a module within your DLL here + instead. + + "fmt" is a printf-style format string. Depending on the context in which + your DLL is running you might end up calling a logger function exported + from libavs, which has its own printf implementation (including a number of + proprietary extensions), so don't use any overly exotic formats. */ + +typedef void (*log_formatter_t)(const char *module, const char *fmt, ...) + LOG_CHECK_FMT; + +/* An API for spawning threads. This API is defined by libavs, although + Bemanitools itself may supply compatible implementations of these functions + to your DLL, depending on the context in which it runs. + + NOTE: You may only use the logging functions from a thread where Bemanitools + calls you, or a thread that you create using this API. Failure to observe + this restriction will cause the process to crash. This is a limitation of + libavs itself, not Bemanitools. */ + +typedef int (*thread_create_t)(int (*proc)(void *), void *ctx, + uint32_t stack_sz, unsigned int priority); +typedef void (*thread_join_t)(int thread_id, int *result); +typedef void (*thread_destroy_t)(int thread_id); + +#endif diff --git a/src/main/bemanitools/iidxio.h b/src/main/bemanitools/iidxio.h new file mode 100644 index 0000000..f85ba50 --- /dev/null +++ b/src/main/bemanitools/iidxio.h @@ -0,0 +1,155 @@ +#ifndef BEMANITOOLS_IIDXIO_H +#define BEMANITOOLS_IIDXIO_H + +/* IO emulation provider for beatmania IIDX. */ + +#include +#include + +#include "bemanitools/glue.h" + +/* Bit mapping for the "pad" word */ + +enum iidx_io_sys_bit { + IIDX_IO_SYS_TEST = 0x00, + IIDX_IO_SYS_SERVICE = 0x01, + IIDX_IO_SYS_COIN = 0x02 +}; + +enum iidx_io_panel_bit { + IIDX_IO_PANEL_P1_START = 0x00, + IIDX_IO_PANEL_P2_START = 0x01, + IIDX_IO_PANEL_VEFX = 0x02, + IIDX_IO_PANEL_EFFECT = 0x03 +}; + +enum iidx_io_key_bit { + IIDX_IO_KEY_P1_1 = 0x00, + IIDX_IO_KEY_P1_2 = 0x01, + IIDX_IO_KEY_P1_3 = 0x02, + IIDX_IO_KEY_P1_4 = 0x03, + IIDX_IO_KEY_P1_5 = 0x04, + IIDX_IO_KEY_P1_6 = 0x05, + IIDX_IO_KEY_P1_7 = 0x06, + + IIDX_IO_KEY_P2_1 = 0x07, + IIDX_IO_KEY_P2_2 = 0x08, + IIDX_IO_KEY_P2_3 = 0x09, + IIDX_IO_KEY_P2_4 = 0x0A, + IIDX_IO_KEY_P2_5 = 0x0B, + IIDX_IO_KEY_P2_6 = 0x0C, + IIDX_IO_KEY_P2_7 = 0x0D +}; + +/* Bit mapping for the P1 and P2 deck lights */ + +enum iidx_io_deck_light { + IIDX_IO_DECK_LIGHT_P1_1 = 0, + IIDX_IO_DECK_LIGHT_P1_2 = 1, + IIDX_IO_DECK_LIGHT_P1_3 = 2, + IIDX_IO_DECK_LIGHT_P1_4 = 3, + IIDX_IO_DECK_LIGHT_P1_5 = 4, + IIDX_IO_DECK_LIGHT_P1_6 = 5, + IIDX_IO_DECK_LIGHT_P1_7 = 6, + + IIDX_IO_DECK_LIGHT_P2_1 = 8, + IIDX_IO_DECK_LIGHT_P2_2 = 9, + IIDX_IO_DECK_LIGHT_P2_3 = 10, + IIDX_IO_DECK_LIGHT_P2_4 = 11, + IIDX_IO_DECK_LIGHT_P2_5 = 12, + IIDX_IO_DECK_LIGHT_P2_6 = 13, + IIDX_IO_DECK_LIGHT_P2_7 = 14, +}; + +/* Bit mapping for the front panel lights */ + +enum iidx_io_panel_light { + IIDX_IO_PANEL_LIGHT_P1_START = 0, + IIDX_IO_PANEL_LIGHT_P2_START = 1, + IIDX_IO_PANEL_LIGHT_VEFX = 2, + IIDX_IO_PANEL_LIGHT_EFFECT = 3, +}; + +/* The first function that will be called on your DLL. You will be supplied + with four function pointers that may be used to log messages to the game's + log file. See comments in glue.h for further information. */ + +void iidx_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal); + +/* Initialize your IIDX IO emulation DLL. Thread management functions are + provided to you; you must use these functions to create your own threads if + you want to make use of the logging functions that are provided to + eam_io_set_loggers(). You will also need to pass these thread management + functions on to geninput if you intend to make use of that library. + + See glue.h and geninput.h for further details. */ + +bool iidx_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy); + +/* Shut down your IIDX IO emulation DLL */ + +void iidx_io_fini(void); + +/* Set the deck lighting state. See enum iidx_io_deck_light above. */ + +void iidx_io_ep1_set_deck_lights(uint16_t deck_lights); + +/* Set front panel lighting state. See enum iidx_io_panel_light above. */ + +void iidx_io_ep1_set_panel_lights(uint8_t panel_lights); + +/* Set state of the eight halogens above the marquee. */ + +void iidx_io_ep1_set_top_lamps(uint8_t top_lamps); + +/* Switch the top neons on or off. */ + +void iidx_io_ep1_set_top_neons(bool top_neons); + +/* Transmit the lighting state to the lighting controller. This function is + called immediately after all of the other iidx_io_ep1_set_*() functions. + + Return false in the event of an IO error. This will lock the game into an + IO error screen. */ + +bool iidx_io_ep1_send(void); + +/* Read input state from the input controller. This function is called + immediately before all of the iidx_io_ep2_get_*() functions. + + Return false in the event of an IO error. This will lock the game into an + IO error screen. */ + +bool iidx_io_ep2_recv(void); + +/* Get absolute turntable position, expressed in 1/256ths of a rotation. + player_no is either 0 or 1. */ + +uint8_t iidx_io_ep2_get_turntable(uint8_t player_no); + +/* Get slider position, where 0 is the bottom position and 15 is the topmost + position. slider_no is a number between 0 (leftmost) and 4 (rightmost). */ + +uint8_t iidx_io_ep2_get_slider(uint8_t slider_no); + +/* Get the state of the system buttons. See enums above. */ + +uint8_t iidx_io_ep2_get_sys(void); + +/* Get the state of the panel buttons. See enums above. */ + +uint8_t iidx_io_ep2_get_panel(void); + +/* Get the state of the 14 key buttons. See enums above. */ + +uint16_t iidx_io_ep2_get_keys(void); + +/* Write a nine-character string to the 16-segment display. This happens on a + different schedule to all of the other IO operations, so you should initiate + the communication as soon as this function is called */ + +bool iidx_io_ep3_write_16seg(const char *text); + +#endif diff --git a/src/main/bemanitools/input.h b/src/main/bemanitools/input.h new file mode 100644 index 0000000..9c31d92 --- /dev/null +++ b/src/main/bemanitools/input.h @@ -0,0 +1,87 @@ +#ifndef BEMANITOOLS_INPUT_H +#define BEMANITOOLS_INPUT_H + +/* Generic input API. This header file defines the public API for geninput.dll. + + You may use geninput to supply generic input mapping services for controls + that your custom IO DLLs do not natively provide. For instance, you might + want to make a custom IIDXIO.DLL that interfaces with your own 16-segment + LCD marquee device while still using the stock IIDXIO.DLL input and lighting + code, which uses the generic services provided by geninput.dll. + + All other exports from geninput.dll are undocumented and subject to change + without notice. */ + +#include +#include + +#include "bemanitools/glue.h" + +/* Supply logging functions to geninput. You should pass on the logging + functions that are supplied to your own custom DLLs. + + This is the only function that can safely be called before input_init(). */ + +void input_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal); + +/* Initialize the generic input subsystem. You must pass on the thread + management functions that have been supplied to your DLL. + + Calling any geninput functions other than input_set_loggers() before calling + input_init() will probably crash the running process. + + You will also need to call mapper_config_load() with the appropriate + game_type parameter, otherwise you will not receive any input, and any + attempts to set a light output level will have no effect. */ + +void input_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy); + +/* Shut down the generic input subsystem. After calling this function, no + geninput functions other than input_set_loggers() or input_init() may be + called. */ + +void input_fini(void); + +/* Load input mappings for a particular game, as configured from config.exe. + + Currently recognized game types are: + + ddr: Dance Dance Revolution + dm: Drum Mania + gf: Guitar Freaks + iidx: beatmania IIDX + pnm: pop'n music + sdvx: Sound Voltex + ju: jubeat + + Returns true if a suitable config file was found and successfully loaded. */ + +bool mapper_config_load(const char *game_type); + +/* Return the absolute position of an analog spinner, expressed in 1/256ths of + a complete rotation. */ + +uint8_t mapper_read_analog(uint8_t analog); + +/* Map the current state of all attached input devices to a 64-bit bit field. + The exact layout of this bit field varies between game types, although we + try to approximate the contents of each emulated IO PCB's own state packet + as closely as is reasonably practical. */ + +uint64_t mapper_update(void); + +/* Set the intensity of any light on a controller corresponding to a particular + software-controlled light on an arcade cabinet, where 0 is off and 255 is + full intensity. Consult the header files for the light identifiers used for + each game type. The mappings between these light identifiers and the actual + lights on the user's controller (if any) are configured by the user by means + of the config.exe program. + + Note that any calls to this function do not take effect until the next call + to mapper_update(). */ + +void mapper_write_light(uint8_t light, uint8_t intensity); + +#endif diff --git a/src/main/bemanitools/jbio.h b/src/main/bemanitools/jbio.h new file mode 100644 index 0000000..c717cc4 --- /dev/null +++ b/src/main/bemanitools/jbio.h @@ -0,0 +1,83 @@ +#ifndef BEMANITOOLS_JBIO_H +#define BEMANITOOLS_JBIO_H + +/* IO emulation provider for jubeat. */ + +#include +#include + +#include "bemanitools/glue.h" + +/* input bit mappings. Panels on the controller are + panel 1 top left corner down to panel 16 bottom right corner */ +enum jb_io_panel_bit { + JB_IO_PANEL_01 = 0x00, + JB_IO_PANEL_02 = 0x01, + JB_IO_PANEL_03 = 0x02, + JB_IO_PANEL_04 = 0x03, + JB_IO_PANEL_05 = 0x04, + JB_IO_PANEL_06 = 0x05, + JB_IO_PANEL_07 = 0x06, + JB_IO_PANEL_08 = 0x07, + JB_IO_PANEL_09 = 0x08, + JB_IO_PANEL_10 = 0x09, + JB_IO_PANEL_11 = 0x0A, + JB_IO_PANEL_12 = 0x0B, + JB_IO_PANEL_13 = 0x0C, + JB_IO_PANEL_14 = 0x0D, + JB_IO_PANEL_15 = 0x0E, + JB_IO_PANEL_16 = 0x0F, +}; + +/* Bit mappings for "system" inputs */ +enum jb_io_sys_bit { + JB_IO_SYS_TEST = 0x00, + JB_IO_SYS_SERVICE = 0x01, + JB_IO_SYS_COIN = 0x02, +}; + +/* RGB led units to address */ +enum jb_io_rgb_led { + JB_IO_RGB_LED_FRONT = 0, + JB_IO_RGB_LED_TOP = 1, + JB_IO_RGB_LED_LEFT = 2, + JB_IO_RGB_LED_RIGHT = 3, + JB_IO_RGB_LED_TITLE = 4, + JB_IO_RGB_LED_WOOFER = 5 +}; + +/* The first function that will be called on your DLL. You will be supplied + with four function pointers that may be used to log messages to the game's + log file. See comments in glue.h for further information. */ + +void jb_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal); + +/* Initialize your JB IO emulation DLL. Thread management functions are + provided to you; you must use these functions to create your own threads if + you want to make use of the logging functions that are provided to + jb_io_set_loggers(). You will also need to pass these thread management + functions on to geninput if you intend to make use of that library. + + See glue.h and geninput.h for further details. */ + +bool jb_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy); + +/* Shut down your JB IO emulation DLL */ + +void jb_io_fini(void); + +/* TODO doc */ + +bool jb_io_read_inputs(void); + +bool jb_io_write_outputs(void); + +uint8_t jb_io_get_sys_inputs(void); + +uint16_t jb_io_get_panel_inputs(void); + +void jb_io_set_rgb_led(enum jb_io_rgb_led unit, uint8_t r, uint8_t g, uint8_t b); + +#endif diff --git a/src/main/bemanitools/sdvxio.h b/src/main/bemanitools/sdvxio.h new file mode 100644 index 0000000..3e85f3b --- /dev/null +++ b/src/main/bemanitools/sdvxio.h @@ -0,0 +1,95 @@ +#ifndef BEMANITOOLS_SDVXIO_H +#define BEMANITOOLS_SDVXIO_H + +/* IO emulation provider for SOUND VOLTEX */ + +#include +#include + +#include "bemanitools/glue.h" + +enum sdvx_io_in_gpio_sys_bit { + SDVX_IO_IN_GPIO_SYS_COIN = 2, + SDVX_IO_IN_GPIO_SYS_TEST = 4, + SDVX_IO_IN_GPIO_SYS_SERVICE = 5, +}; + +enum sdvx_io_in_gpio_0_bit { + SDVX_IO_IN_GPIO_0_C = 0, + SDVX_IO_IN_GPIO_0_B = 1, + SDVX_IO_IN_GPIO_0_A = 2, + SDVX_IO_IN_GPIO_0_START = 3, + SDVX_IO_IN_GPIO_0_HEADPHONE = 4, +}; + +enum sdvx_io_in_gpio_1_bit { + SDVX_IO_IN_GPIO_1_FX_R = 3, + SDVX_IO_IN_GPIO_1_FX_L = 4, + SDVX_IO_IN_GPIO_1_D = 5, +}; + +enum sdvx_io_out_gpio_bit { + SDVX_IO_OUT_GPIO_D = 0, + SDVX_IO_OUT_GPIO_FX_L = 1, + SDVX_IO_OUT_GPIO_FX_R = 2, + SDVX_IO_OUT_GPIO_START = 12, + SDVX_IO_OUT_GPIO_A = 13, + SDVX_IO_OUT_GPIO_B = 14, + SDVX_IO_OUT_GPIO_C = 15, +}; + +/* The first function that will be called on your DLL. You will be supplied + with four function pointers that may be used to log messages to the game's + log file. See comments in glue.h for further information. */ + +void sdvx_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal); + +/* Initialize your SDVX IO emulation DLL. Thread management functions are + provided to you; you must use these functions to create your own threads if + you want to make use of the logging functions that are provided to + eam_io_set_loggers(). You will also need to pass these thread management + functions on to geninput if you intend to make use of that library. + + See glue.h and geninput.h for further details. */ + +bool sdvx_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy); + +/* Shut down your SDVX IO emulation DLL */ + +void sdvx_io_fini(void); + +/* Set state of the GPIO (on/off) lights (see bit definitions above) */ + +void sdvx_io_set_gpio_lights(uint32_t gpio_lights); + +/* Set state of a PWM (dimmable) light channel. These come in groups of three + (red, green, blue). There are a six group of three PWM channels, for a + total of 18 channels (0 through 17). */ + +void sdvx_io_set_pwm_light(uint8_t light_no, uint8_t intensity); + +/* Transmit the light state to the IOPCB */ + +bool sdvx_io_write_output(void); + +/* Read input state */ + +bool sdvx_io_read_input(void); + +/* Get state of coin, test, service inputs */ + +uint8_t sdvx_io_get_input_gpio_sys(void); + +/* Get gameplay button state. Parameter selects GPIO bank 0 or 1. See bit + definitions above for details. */ + +uint16_t sdvx_io_get_input_gpio(uint8_t gpio_bank); + +/* Get a 10-bit (!) spinner position, where spinner_no is 0 or 1. + High six bits are ignored. */ + +uint16_t sdvx_io_get_spinner_pos(uint8_t spinner_no); + +#endif diff --git a/src/main/bemanitools/vefxio.h b/src/main/bemanitools/vefxio.h new file mode 100644 index 0000000..246d942 --- /dev/null +++ b/src/main/bemanitools/vefxio.h @@ -0,0 +1,56 @@ +#ifndef BEMANITOOLS_VEFXIO_H +#define BEMANITOOLS_VEFXIO_H + +/* IO emulation provider for beatmania IIDX Effector Panel. */ + +#include +#include + +#include "bemanitools/glue.h" + +/* The first function that will be called on your DLL. You will be supplied + with four function pointers that may be used to log messages to the game's + log file. See comments in glue.h for further information. */ + +void vefx_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal); + +/* Initialize your IIDX IO emulation DLL. Thread management functions are + provided to you; you must use these functions to create your own threads if + you want to make use of the logging functions that are provided to + eam_io_set_loggers(). You will also need to pass these thread management + functions on to geninput if you intend to make use of that library. + + See glue.h and geninput.h for further details. */ + +bool vefx_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy); + +/* Shut down your IIDX IO emulation DLL */ + +void vefx_io_fini(void); + +/* Read input state from the input controller. This function is called + immediately before the vefx_io_get_slider() function. + + Return false in the event of an IO error. This will lock the game into an + IO error screen. + + If making a custom driver, ppad can be used to update regular IO if needed + + See iidxio.c for mappings. */ + +bool vefx_io_recv(uint64_t* ppad); + +/* Get slider position, where 0 is the bottom position and 15 is the topmost + position. slider_no is a number between 0 (leftmost) and 4 (rightmost). */ + +uint8_t vefx_io_get_slider(uint8_t slider_no); + +/* Write a nine-character string to the 16-segment display. This happens on a + different schedule to all of the other IO operations, so you should initiate + the communication as soon as this function is called */ + +bool vefx_io_write_16seg(const char *text); + +#endif diff --git a/src/main/bsthook/Module.mk b/src/main/bsthook/Module.mk new file mode 100644 index 0000000..ef61d28 --- /dev/null +++ b/src/main/bsthook/Module.mk @@ -0,0 +1,20 @@ +avsdlls += bsthook + +deplibs_bsthook := \ + avs \ + +libs_bsthook := \ + acioemu \ + bstio \ + hook \ + hooklib \ + util \ + eamio \ + +src_bsthook := \ + acio.c \ + dllmain.c \ + gfx.c \ + kfca.c \ + settings.c \ + diff --git a/src/main/bsthook/acio.c b/src/main/bsthook/acio.c new file mode 100644 index 0000000..7041456 --- /dev/null +++ b/src/main/bsthook/acio.c @@ -0,0 +1,93 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "acioemu/addr.h" +#include "acioemu/emu.h" +#include "acioemu/icca.h" + +#include "bsthook/acio.h" +#include "bsthook/kfca.h" + +#include "hook/iohook.h" + +#include "imports/avs.h" + +#include "util/defs.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +static struct ac_io_emu ac_io_emu; +static struct ac_io_emu_icca ac_io_emu_icca; + +void ac_io_bus_init(void) +{ + ac_io_emu_init(&ac_io_emu, L"COM2"); + ac_io_emu_icca_init(&ac_io_emu_icca, &ac_io_emu, 0); + kfca_init(&ac_io_emu); +} + +void ac_io_bus_fini(void) +{ + ac_io_emu_fini(&ac_io_emu); +} + +HRESULT ac_io_bus_dispatch_irp(struct irp *irp) +{ + const struct ac_io_message *msg; + HRESULT hr; + + log_assert(irp != NULL); + + if (!ac_io_emu_match_irp(&ac_io_emu, irp)) { + return irp_invoke_next(irp); + } + + for (;;) { + hr = ac_io_emu_dispatch_irp(&ac_io_emu, irp); + + if (hr != S_OK) { + return hr; + } + + msg = ac_io_emu_request_peek(&ac_io_emu); + + switch (msg->addr) { + case 0: + ac_io_emu_cmd_assign_addrs(&ac_io_emu, msg, 2); + + break; + + case 1: + ac_io_emu_icca_dispatch_request(&ac_io_emu_icca, msg); + + break; + + case 2: + kfca_dispatch_request(msg); + + break; + + case AC_IO_BROADCAST: + log_warning("Broadcast(?) message on BST ACIO bus?"); + + break; + + default: + log_warning("ACIO message on unhandled bus address: %d", + msg->addr); + + break; + } + + ac_io_emu_request_pop(&ac_io_emu); + } +} diff --git a/src/main/bsthook/acio.h b/src/main/bsthook/acio.h new file mode 100644 index 0000000..d6d6ee2 --- /dev/null +++ b/src/main/bsthook/acio.h @@ -0,0 +1,12 @@ +#ifndef IIDXHOOK_AC_IO_H +#define IIDXHOOK_AC_IO_H + +#include + +#include "hook/iohook.h" + +void ac_io_bus_init(void); +void ac_io_bus_fini(void); +HRESULT ac_io_bus_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/bsthook/bsthook.def b/src/main/bsthook/bsthook.def new file mode 100644 index 0000000..7071574 --- /dev/null +++ b/src/main/bsthook/bsthook.def @@ -0,0 +1,4 @@ +LIBRARY bsthook + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/bsthook/dllmain.c b/src/main/bsthook/dllmain.c new file mode 100644 index 0000000..71bc87e --- /dev/null +++ b/src/main/bsthook/dllmain.c @@ -0,0 +1,136 @@ +#include + +#include + +#include "bemanitools/bstio.h" +#include "bemanitools/eamio.h" + +#include "hook/iohook.h" + +#include "hooklib/app.h" +#include "hooklib/rs232.h" + +#include "imports/avs.h" + +#include "bsthook/acio.h" +#include "bsthook/gfx.h" +#include "bsthook/settings.h" + +#include "util/cmdline.h" +#include "util/defs.h" +#include "util/log.h" + +static const irp_handler_t bsthook_handlers[] = { + ac_io_bus_dispatch_irp, +}; + +static bool my_dll_entry_init(char *sidcode, struct property_node *config); +static bool my_dll_entry_main(void); + +static bool my_dll_entry_init(char *sidcode, struct property_node *config) +{ + bool ok; + + log_info("--- Begin bsthook dll_entry_init ---"); + + ac_io_bus_init(); + + log_info("Starting up BeatStream IO backend"); + + bst_io_set_loggers( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + ok = bst_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + + if (!ok) { + goto bst_io_fail; + } + + eam_io_set_loggers( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + ok = eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + + if (!ok) { + goto eam_io_fail; + } + + log_info("--- End bsthook dll_entry_init ---"); + + return app_hook_invoke_init(sidcode, config); + +eam_io_fail: + bst_io_fini(); + +bst_io_fail: + ac_io_bus_fini(); + + return false; +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + log_info("Shutting down card reader backend"); + eam_io_fini(); + + log_info("Shutting down SDVX IO backend"); + bst_io_fini(); + + ac_io_bus_fini(); + + return result; +} + +BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) +{ + int i; + int argc; + char **argv; + + if (reason != DLL_PROCESS_ATTACH) { + return TRUE; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + args_recover(&argc, &argv); + + for (i = 1 ; i < argc ; i++) { + if (argv[i][0] != '-') { + continue; + } + + switch (argv[i][1]) { + case 'w': + gfx_set_windowed(); + + break; + } + } + + args_free(argc, argv); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + iohook_init(bsthook_handlers, lengthof(bsthook_handlers)); + rs232_hook_init(); + + gfx_init(); + settings_hook_init(); + + return TRUE; +} + diff --git a/src/main/bsthook/gfx.c b/src/main/bsthook/gfx.c new file mode 100644 index 0000000..1da96a1 --- /dev/null +++ b/src/main/bsthook/gfx.c @@ -0,0 +1,83 @@ +#include +#include + +#include + +#include "hook/com-proxy.h" +#include "hook/pe.h" +#include "hook/table.h" + +#include "sdvxhook/gfx.h" + +#include "util/defs.h" +#include "util/log.h" + +static HRESULT STDCALL my_CreateDevice( + IDirect3D9 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, DWORD flags, + D3DPRESENT_PARAMETERS *pp, IDirect3DDevice9 **pdev); +static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver); + +static IDirect3D9 * (STDCALL *real_Direct3DCreate9)(UINT sdk_ver); + +static const struct hook_symbol gfx_hook_syms[] = { + { + .name = "Direct3DCreate9", + .patch = my_Direct3DCreate9, + .link = (void **) &real_Direct3DCreate9 + }, +}; + +static bool gfx_windowed; + +static HRESULT STDCALL my_CreateDevice( + IDirect3D9 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, DWORD flags, + D3DPRESENT_PARAMETERS *pp, IDirect3DDevice9 **pdev) +{ + IDirect3D9 *real = COM_PROXY_UNWRAP(self); + HRESULT hr; + + log_misc("IDirect3D9::CreateDevice hook hit"); + + if (gfx_windowed) { + pp->Windowed = TRUE; + pp->FullScreen_RefreshRateInHz = 0; + } + + hr = IDirect3D9_CreateDevice(real, adapter, type, hwnd, flags, pp, pdev); + + return hr; +} + +static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver) +{ + IDirect3D9 *api; + IDirect3D9Vtbl *api_vtbl; + struct com_proxy *api_proxy; + + log_info("Direct3DCreate9 hook hit"); + + api = real_Direct3DCreate9(sdk_ver); + api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl)); + api_vtbl = api_proxy->vptr; + + api_vtbl->CreateDevice = my_CreateDevice; + + return (IDirect3D9 *) api_proxy; +} + +void gfx_init(void) +{ + hook_table_apply( + NULL, + "d3d9.dll", + gfx_hook_syms, + lengthof(gfx_hook_syms)); + + log_info("Inserted graphics hooks"); +} + +void gfx_set_windowed(void) +{ + gfx_windowed = true; +} + diff --git a/src/main/bsthook/gfx.h b/src/main/bsthook/gfx.h new file mode 100644 index 0000000..2064049 --- /dev/null +++ b/src/main/bsthook/gfx.h @@ -0,0 +1,7 @@ +#ifndef BSTHOOK_GFX_H +#define BSTHOOK_GFX_H + +void gfx_init(void); +void gfx_set_windowed(void); + +#endif diff --git a/src/main/bsthook/kfca.c b/src/main/bsthook/kfca.c new file mode 100644 index 0000000..0b37b50 --- /dev/null +++ b/src/main/bsthook/kfca.c @@ -0,0 +1,136 @@ +#include + +#include +#include + +#include "acio/acio.h" + +#include "acioemu/emu.h" + +#include "bemanitools/bstio.h" + +#include "util/defs.h" + +static void kfca_send_version(const struct ac_io_message *req); +static void kfca_report_status(const struct ac_io_message *req, uint8_t status); +static void kfca_report_nil(const struct ac_io_message *req); +static void kfca_poll(const struct ac_io_message *req); + +static struct ac_io_emu *kfca_ac_io_emu; + +void kfca_init(struct ac_io_emu *emu) +{ + kfca_ac_io_emu = emu; +} + +void kfca_dispatch_request(const struct ac_io_message *req) +{ + uint16_t cmd_code; + + cmd_code = ac_io_u16(req->cmd.code); + + switch (cmd_code) { + case AC_IO_CMD_GET_VERSION: + log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr); + kfca_send_version(req); + + break; + + case AC_IO_CMD_START_UP: + log_misc("AC_IO_CMD_START_UP(%d)", req->addr); + kfca_report_status(req, 0x00); + + break; + + case AC_IO_CMD_KFCA_POLL: + kfca_poll(req); + + break; + + case AC_IO_CMD_KFCA_UNK_0120: + log_misc("AC_IO_CMD_KFCA_UNK_%04X(%d)", cmd_code, req->addr); + kfca_report_status(req, 0x00); + + break; + + case AC_IO_CMD_KFCA_UNK_0128: + log_misc("AC_IO_CMD_KFCA_UNK_%04X(%d)", cmd_code, req->addr); + kfca_report_nil(req); + + break; + + default: + log_warning("Unknown ACIO message %04x on KFCA mode, addr=%d", + cmd_code, req->addr); + + break; + } +} + +static void kfca_send_version(const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.version); + resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_KFCA); + resp.cmd.version.flag = 0x00; + resp.cmd.version.major = 0x01; + resp.cmd.version.minor = 0x01; + resp.cmd.version.revision = 0x00; + memcpy(resp.cmd.version.product_code, "KFCA", + sizeof(resp.cmd.version.product_code)); + strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date)); + strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time)); + + ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0); +} + +static void kfca_report_status(const struct ac_io_message *req, uint8_t status) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.status); + resp.cmd.status = status; + + ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0); +} + +static void kfca_report_nil(const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = 0; + + ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0); +} + +static void kfca_poll(const struct ac_io_message *req) +{ + struct ac_io_message resp; + struct ac_io_kfca_poll_in *pin; + + bst_io_read_input(); + + pin = &resp.cmd.kfca_poll_in; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(*pin); + + memset(pin, 0, sizeof(*pin)); + + pin->gpio_sys = ac_io_u16(bst_io_get_input()); + + ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0); +} + diff --git a/src/main/bsthook/kfca.h b/src/main/bsthook/kfca.h new file mode 100644 index 0000000..4419b72 --- /dev/null +++ b/src/main/bsthook/kfca.h @@ -0,0 +1,11 @@ +#ifndef BSTHOOK_KFCA_H +#define BSTHOOK_KFCA_H + +#include "acio/acio.h" + +#include "acioemu/emu.h" + +void kfca_init(struct ac_io_emu *in); +void kfca_dispatch_request(const struct ac_io_message *req); + +#endif diff --git a/src/main/bsthook/settings.c b/src/main/bsthook/settings.c new file mode 100644 index 0000000..d4282fa --- /dev/null +++ b/src/main/bsthook/settings.c @@ -0,0 +1,91 @@ +#define LOG_MODULE "settings-hook" + +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" + +/* ------------------------------------------------------------------------- */ + +static HANDLE STDCALL my_CreateFileA( + LPCSTR lpFileName, + DWORD dwDesiredAccess, + DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE (STDCALL *real_CreateFileA)( + LPCSTR lpFileName, + DWORD dwDesiredAccess, + DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, + HANDLE hTemplateFile); + +/* ------------------------------------------------------------------------- */ + +static const struct hook_symbol settings_hook_syms[] = { + { + .name = "CreateFileA", + .patch = my_CreateFileA, + .link = (void **) &real_CreateFileA + }, +}; + +/* ------------------------------------------------------------------------- */ + +static HANDLE STDCALL my_CreateFileA( + LPCSTR lpFileName, + DWORD dwDesiredAccess, + DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + if ( lpFileName != NULL && + lpFileName[0] == 'e' && + lpFileName[1] == ':') { + HANDLE handle; + char new_path[MAX_PATH]; + + strcpy(new_path, lpFileName); + new_path[1] = '\\'; + log_misc("Remapped settings path %s", new_path); + + handle = real_CreateFileA(new_path, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile); + + return handle; + } + + return real_CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile); +} + + +/* ------------------------------------------------------------------------- */ + +void settings_hook_init(void) +{ + hook_table_apply( + NULL, + "kernel32.dll", + settings_hook_syms, + lengthof(settings_hook_syms)); + + log_info("Inserted settings hooks"); +} diff --git a/src/main/bsthook/settings.h b/src/main/bsthook/settings.h new file mode 100644 index 0000000..68831a9 --- /dev/null +++ b/src/main/bsthook/settings.h @@ -0,0 +1,10 @@ +#ifndef BSTHOOK_SETTINGS_H +#define BSTHOOK_SETTINGS_H + +/** + * Remaps the paths for the settings drive e:\ + * to the local folder e\. + */ +void settings_hook_init(void); + +#endif diff --git a/src/main/bstio/Module.mk b/src/main/bstio/Module.mk new file mode 100644 index 0000000..1e37a61 --- /dev/null +++ b/src/main/bstio/Module.mk @@ -0,0 +1,8 @@ +dlls += bstio + +libs_bstio := \ + geninput \ + +src_bstio := \ + bstio.c \ + diff --git a/src/main/bstio/bstio.c b/src/main/bstio/bstio.c new file mode 100644 index 0000000..63b9feb --- /dev/null +++ b/src/main/bstio/bstio.c @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "bemanitools/bstio.h" +#include "bemanitools/glue.h" +#include "bemanitools/input.h" + +static uint8_t bst_io_gpio_sys; + +void bst_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + input_set_loggers(misc, info, warning, fatal); +} + +bool bst_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + input_init(thread_create, thread_join, thread_destroy); + mapper_config_load("bst"); + + return true; +} + +void bst_io_fini(void) +{ + input_fini(); +} + +bool bst_io_read_input(void) +{ + bst_io_gpio_sys = mapper_update(); + + return true; +} + +uint8_t bst_io_get_input(void) +{ + return bst_io_gpio_sys; +} + diff --git a/src/main/bstio/bstio.def b/src/main/bstio/bstio.def new file mode 100644 index 0000000..7c85a5f --- /dev/null +++ b/src/main/bstio/bstio.def @@ -0,0 +1,8 @@ +LIBRARY bstio + +EXPORTS + bst_io_fini + bst_io_get_input + bst_io_init + bst_io_read_input + bst_io_set_loggers diff --git a/src/main/cconfig/Module.mk b/src/main/cconfig/Module.mk new file mode 100644 index 0000000..b48aea7 --- /dev/null +++ b/src/main/cconfig/Module.mk @@ -0,0 +1,11 @@ +libs += cconfig + +libs_cconfig := \ + util \ + +src_cconfig := \ + cconfig-hook.c \ + cconfig-util.c \ + cconfig.c \ + cmd.c \ + conf.c \ diff --git a/src/main/cconfig/cconfig-hook.c b/src/main/cconfig/cconfig-hook.c new file mode 100644 index 0000000..f8634cc --- /dev/null +++ b/src/main/cconfig/cconfig-hook.c @@ -0,0 +1,96 @@ +#include + +#include "cconfig/cconfig-util.h" +#include "cconfig/cmd.h" +#include "cconfig/conf.h" + +#include "cconfig/cconfig-hook.h" + +#include "util/cmdline.h" +#include "util/log.h" + +bool cconfig_hook_config_init(struct cconfig* config, const char* usage_header, + enum cconfig_cmd_usage_out cmd_usage_out) +{ + bool success; + int argc; + char **argv; + enum cconfig_conf_error conf_error; + char* config_path; + + success = true; + + args_recover(&argc, &argv); + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + goto failure_usage; + } + } + + config_path = NULL; + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--config")) { + if (i + 1 >= argc) { + log_fatal("--config parameter not followed by a config file " + "path param"); + goto failure; + } + + config_path = argv[i + 1]; + + break; + } + } + + if (config_path) { + log_misc("Loading config file: %s", config_path); + conf_error = cconfig_conf_load_from_file(config, config_path, false); + + if (conf_error == CCONFIG_CONF_ERROR_NO_SUCH_FILE) { + /* Create default config */ + if (cconfig_conf_save_to_file(config, config_path) != + CCONFIG_CONF_SUCCESS) { + log_fatal("Creating default config file '%s' failed", + config_path); + goto failure; + } else { + log_info("Default configuration '%s' created. Restart " + "application", config_path); + goto failure; + } + } else if (conf_error != CCONFIG_CONF_SUCCESS) { + log_fatal("Error loading config file '%s': %d", config_path, + conf_error); + goto failure; + } + + log_misc("Config state after file loading:"); + cconfig_util_log(config, log_impl_misc); + } + + log_misc("Parsing override config parameters from cmd"); + + /* Override defaults or values loaded from file with values from cmd */ + if (!cconfig_cmd_parse(config, "-p", argc, argv, false)) { + log_fatal("Error parsing cmd args for config values"); + goto failure_usage; + } + + log_misc("Config state after cmd parameter overrides:"); + cconfig_util_log(config, log_impl_misc); + + goto success; + +failure_usage: + cconfig_cmd_print_usage(config, usage_header, cmd_usage_out); + +failure: + success = false; + +success: + args_free(argc, argv); + + return success; +} \ No newline at end of file diff --git a/src/main/cconfig/cconfig-hook.h b/src/main/cconfig/cconfig-hook.h new file mode 100644 index 0000000..aa9fa41 --- /dev/null +++ b/src/main/cconfig/cconfig-hook.h @@ -0,0 +1,10 @@ +#ifndef CCONFIG_HOOK_H +#define CCONFIG_HOOK_H + +#include "cconfig/cconfig.h" +#include "cconfig/cmd.h" + +bool cconfig_hook_config_init(struct cconfig* config, const char* usage_header, + enum cconfig_cmd_usage_out cmd_usage_out); + +#endif \ No newline at end of file diff --git a/src/main/cconfig/cconfig-util.c b/src/main/cconfig/cconfig-util.c new file mode 100644 index 0000000..2c30f65 --- /dev/null +++ b/src/main/cconfig/cconfig-util.c @@ -0,0 +1,211 @@ +#define LOG_MODULE "cconfig-util" + +#include +#include + +#include "cconfig/cconfig-util.h" + +#include "util/hex.h" +#include "util/log.h" +#include "util/mem.h" + +bool cconfig_util_get_int(struct cconfig* config, const char* key, int32_t* ret, + int32_t default_value) +{ + struct cconfig_entry* entry; + + log_assert(config); + log_assert(key); + + entry = cconfig_get(config, key); + + if (entry) { + if (sscanf(entry->value, "%d", ret) == 1) { + return true; + } + } + + *ret = default_value; + return false; +} + +bool cconfig_util_get_float(struct cconfig* config, const char* key, float* ret, + float default_value) +{ + struct cconfig_entry* entry; + + log_assert(config); + log_assert(key); + + entry = cconfig_get(config, key); + + if (entry) { + if (sscanf(entry->value, "%f", ret) == 1) { + return true; + } + } + + *ret = default_value; + return false; +} + +bool cconfig_util_get_bool(struct cconfig* config, const char* key, bool* ret, + bool default_value) +{ + struct cconfig_entry* entry; + + log_assert(config); + log_assert(key); + + entry = cconfig_get(config, key); + + if (entry) { + if (!strcmp(entry->value, "true")) { + *ret = true; + return true; + } else if (!strcmp(entry->value, "false")) { + *ret = false; + return true; + } + } + + *ret = default_value; + return false; +} + +bool cconfig_util_get_str(struct cconfig* config, const char* key, + char* buffer, size_t len, const char* default_value) +{ + struct cconfig_entry* entry; + size_t str_len; + + log_assert(config); + log_assert(key); + + entry = cconfig_get(config, key); + + if (entry) { + str_len = strlen(entry->value); + + if (str_len <= len) { + strcpy(buffer, entry->value); + return true; + } + } + + strcpy(buffer, default_value); + return false; +} + +bool cconfig_util_get_data(struct cconfig* config, const char* key, + uint8_t* buffer, size_t len, const uint8_t* default_value) +{ + size_t res_len; + struct cconfig_entry* entry; + + log_assert(config); + log_assert(key); + log_assert(len); + + entry = cconfig_get(config, key); + + if (entry) { + res_len = strlen(entry->value); + res_len = res_len / 2 + res_len % 2; + + if (len <= res_len) { + if (hex_decode(buffer, len, entry->value, strlen(entry->value))) { + return true; + } + } + } + + memcpy(buffer, default_value, len); + return false; +} + +void cconfig_util_set_int(struct cconfig* config, const char* key, + int32_t value, const char* desc) +{ + char* str; + size_t str_len; + + log_assert(config); + log_assert(key); + log_assert(desc); + + str_len = snprintf(NULL, 0, "%d", value) + 1; + str = xmalloc(str_len); + snprintf(str, str_len, "%d", value); + + cconfig_set(config, key, str, desc); + + free(str); +} + +void cconfig_util_set_float(struct cconfig* config, const char* key, + float value, const char* desc) +{ + char* str; + size_t str_len; + + log_assert(config); + log_assert(key); + log_assert(desc); + + str_len = snprintf(NULL, 0, "%f", value) + 1; + str = xmalloc(str_len); + snprintf(str, str_len, "%f", value); + + cconfig_set(config, key, str, desc); + + free(str); +} + +void cconfig_util_set_bool(struct cconfig* config, const char* key, bool value, + const char* desc) +{ + log_assert(config); + log_assert(key); + log_assert(desc); + + cconfig_set(config, key, value ? "true" : "false", desc); +} + +void cconfig_util_set_str(struct cconfig* config, const char* key, + const char* value, const char* desc) +{ + log_assert(config); + log_assert(key); + log_assert(desc); + log_assert(value); + + cconfig_set(config, key, value, desc); +} + +void cconfig_util_set_data(struct cconfig* config, const char* key, + const uint8_t* value, size_t len, const char* desc) +{ + char* str; + size_t str_len; + + log_assert(config); + log_assert(key); + log_assert(desc); + + str_len = len * 2 + 1; + str = xmalloc(str_len); + hex_encode_uc(value, len, str, str_len); + + cconfig_set(config, key, str, desc); + + free(str); +} + +void cconfig_util_log(struct cconfig* config, log_formatter_t log_formatter) +{ + for (uint32_t i = 0; i < config->nentries; i++) { + log_formatter(LOG_MODULE, "%s=%s", config->entries[i].key, + config->entries[i].value); + } +} \ No newline at end of file diff --git a/src/main/cconfig/cconfig-util.h b/src/main/cconfig/cconfig-util.h new file mode 100644 index 0000000..a8404b5 --- /dev/null +++ b/src/main/cconfig/cconfig-util.h @@ -0,0 +1,44 @@ +#ifndef CCONFIG_UTIL_H +#define CCONFIG_UTIL_H + +#include +#include +#include + +#include "cconfig/cconfig.h" + +#include "util/log.h" + +bool cconfig_util_get_int(struct cconfig* config, const char* key, int32_t* ret, + int32_t default_value); + +bool cconfig_util_get_float(struct cconfig* config, const char* key, float* ret, + float default_value); + +bool cconfig_util_get_bool(struct cconfig* config, const char* key, bool* ret, + bool default_value); + +bool cconfig_util_get_str(struct cconfig* config, const char* key, + char* buffer, size_t len, const char* default_value); + +bool cconfig_util_get_data(struct cconfig* config, const char* key, + uint8_t* buffer, size_t len, const uint8_t* default_value); + +void cconfig_util_set_int(struct cconfig* config, const char* key, + int32_t value, const char* desc); + +void cconfig_util_set_float(struct cconfig* config, const char* key, + float value, const char* desc); + +void cconfig_util_set_bool(struct cconfig* config, const char* key, bool value, + const char* desc); + +void cconfig_util_set_str(struct cconfig* config, const char* key, + const char* value, const char* desc); + +void cconfig_util_set_data(struct cconfig* config, const char* key, + const uint8_t* value, size_t len, const char* desc); + +void cconfig_util_log(struct cconfig* config, log_formatter_t log_formatter); + +#endif \ No newline at end of file diff --git a/src/main/cconfig/cconfig.c b/src/main/cconfig/cconfig.c new file mode 100644 index 0000000..a29f182 --- /dev/null +++ b/src/main/cconfig/cconfig.c @@ -0,0 +1,109 @@ +#include + +#include "cconfig/cconfig.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +static struct cconfig_entry* cconfig_extend_config(struct cconfig* config) +{ + config->nentries++; + + config->entries = xrealloc(config->entries, + config->nentries * sizeof(struct cconfig_entry)); + memset(&config->entries[config->nentries - 1], 0, + sizeof(struct cconfig_entry)); + + return &config->entries[config->nentries - 1]; +} + +struct cconfig* cconfig_init() +{ + struct cconfig* config; + + config = xmalloc(sizeof(struct cconfig)); + memset(config, 0, sizeof(struct cconfig)); + + return config; +} + +struct cconfig_entry* cconfig_get(struct cconfig* config, const char* key) +{ + log_assert(config); + log_assert(key); + + for (uint32_t i = 0; i < config->nentries; i++) { + if (!strcmp(config->entries[i].key, key)) { + return &config->entries[i]; + } + } + + return NULL; +} + +void cconfig_set(struct cconfig* config, const char* key, const char* value, + const char* desc) +{ + struct cconfig_entry* entry; + + log_assert(config); + log_assert(key); + log_assert(value); + log_assert(desc); + + entry = cconfig_get(config, key); + + if (!entry) { + entry = cconfig_extend_config(config); + } else { + free(entry->key); + free(entry->value); + free(entry->desc); + + memset(entry, 0, sizeof(struct cconfig_entry)); + } + + entry->key = str_dup(key); + entry->desc = str_dup(desc); + entry->value = str_dup(value); +} + +void cconfig_set2(struct cconfig* config, const char* key, const char* value) +{ + struct cconfig_entry* entry; + + log_assert(config); + log_assert(key); + log_assert(value); + + entry = cconfig_get(config, key); + + if (!entry) { + entry = cconfig_extend_config(config); + } else { + free(entry->key); + free(entry->value); + } + + entry->key = str_dup(key); + entry->value = str_dup(value); + + /* Description optional, but do not wipe previous description if + available */ + if (!entry->desc) { + entry->desc = ""; + } +} + +void cconfig_finit(struct cconfig* config) +{ + for (uint32_t i = 0; i < config->nentries; i++) { + free(config->entries[i].key); + free(config->entries[i].value); + free(config->entries[i].desc); + } + + free(config->entries); + free(config); +} \ No newline at end of file diff --git a/src/main/cconfig/cconfig.h b/src/main/cconfig/cconfig.h new file mode 100644 index 0000000..0dfa108 --- /dev/null +++ b/src/main/cconfig/cconfig.h @@ -0,0 +1,31 @@ +#ifndef CCONFIG_H +#define CCONFIG_H + +#include +#include +#include + +struct cconfig_entry { + char* key; + char* value; + char* desc; +}; + +struct cconfig { + uint32_t nentries; + struct cconfig_entry* entries; +}; + +struct cconfig* cconfig_init(); + +struct cconfig_entry* cconfig_get(struct cconfig* config, + const char* key); + +void cconfig_set(struct cconfig* config, const char* key, const char* value, + const char* desc); + +void cconfig_set2(struct cconfig* config, const char* key, const char* value); + +void cconfig_finit(struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/cconfig/cmd.c b/src/main/cconfig/cmd.c new file mode 100644 index 0000000..62fbd73 --- /dev/null +++ b/src/main/cconfig/cmd.c @@ -0,0 +1,132 @@ +#define LOG_MODULE "cconfig-cmd" + +#include +#include +#include +#include + +#include "cconfig/cmd.h" + +#include "util/hex.h" +#include "util/log.h" +#include "util/str.h" + +static void cconfig_cmd_usage_print(enum cconfig_cmd_usage_out output, + const char* fmt, ...) +{ + char buffer[32768]; + va_list ap; + + va_start(ap, fmt); + + switch (output) { + case CCONFIG_CMD_USAGE_OUT_STDOUT: + vfprintf(stdout, fmt, ap); + break; + + case CCONFIG_CMD_USAGE_OUT_STDERR: + vfprintf(stderr, fmt, ap); + break; + + case CCONFIG_CMD_USAGE_OUT_DBG: + _vsnprintf(buffer, sizeof(buffer), fmt, ap); + OutputDebugString(buffer); + break; + + case CCONFIG_CMD_USAGE_OUT_LOG: + _vsnprintf(buffer, sizeof(buffer), fmt, ap); + log_info("%s", buffer); + break; + + default: + log_assert(false); + break; + } + + va_end(ap); +} + +bool cconfig_cmd_parse(struct cconfig* config, const char* key_ident, int argc, + char** argv, bool add_params_if_absent) +{ + bool no_error; + struct cconfig_entry* entry; + char* tmp; + char* cur_tok; + int ntok; + char* toks[2]; + + no_error = true; + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], key_ident)) { + if (i + 1 >= argc) { + no_error = false; + break; + } + + /* Not another key ident is following */ + if (!strcmp(argv[i + 1], key_ident)) { + no_error = false; + break; + } + + ++i; + + tmp = str_dup(argv[i]); + ntok = 0; + + cur_tok = strtok(tmp, "="); + + while (cur_tok != NULL) { + toks[ntok] = cur_tok; + ntok++; + cur_tok = strtok(NULL, "="); + + if (ntok == 2) { + break; + } + } + + if (ntok != 2) { + /* Tokenizing key=value parameter error */ + log_warning("Parsing parameter '%s' failed, ignore", argv[i]); + + free(tmp); + no_error = false; + continue; + } + + log_misc("Key: %s, Value: %s", toks[0], toks[1]); + + entry = cconfig_get(config, toks[0]); + + if (entry || add_params_if_absent) { + cconfig_set2(config, toks[0], toks[1]); + } else { + /* Ignore cmd params that are not found in config */ + log_warning("Could not find cmd parameter with key '%s' in " + "config, ignored", toks[0]); + } + + free(tmp); + } + } + + return no_error; +} + +void cconfig_cmd_print_usage(struct cconfig* config, const char* usage_header, + enum cconfig_cmd_usage_out output) +{ + cconfig_cmd_usage_print(output, "%s\n", usage_header); + + for (uint32_t i = 0; i < config->nentries; i++) { + cconfig_cmd_usage_print(output, + " %s: %s\n" + " default: %s\n", + config->entries[i].key, + config->entries[i].desc, + config->entries[i].value); + } +} \ No newline at end of file diff --git a/src/main/cconfig/cmd.h b/src/main/cconfig/cmd.h new file mode 100644 index 0000000..299f398 --- /dev/null +++ b/src/main/cconfig/cmd.h @@ -0,0 +1,21 @@ +#ifndef CCONFIG_CMD_H +#define CCONFIG_CMD_H + +#include + +#include "cconfig/cconfig.h" + +enum cconfig_cmd_usage_out { + CCONFIG_CMD_USAGE_OUT_STDOUT, + CCONFIG_CMD_USAGE_OUT_STDERR, + CCONFIG_CMD_USAGE_OUT_DBG, + CCONFIG_CMD_USAGE_OUT_LOG, +}; + +bool cconfig_cmd_parse(struct cconfig* config, const char* key_ident, int argc, + char** argv, bool add_params_if_absent); + +void cconfig_cmd_print_usage(struct cconfig* config, const char* usage_header, + enum cconfig_cmd_usage_out output); + +#endif \ No newline at end of file diff --git a/src/main/cconfig/conf.c b/src/main/cconfig/conf.c new file mode 100644 index 0000000..47937c2 --- /dev/null +++ b/src/main/cconfig/conf.c @@ -0,0 +1,117 @@ +#define LOG_MODULE "cconfig-conf" + +#include +#include +#include + +#include "cconfig/conf.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/str.h" + +enum cconfig_conf_error cconfig_conf_load_from_file(struct cconfig* config, + const char* path, bool add_params_if_absent) +{ + char* pos_lines; + char* pos_key_val; + char* ctx_lines; + char* ctx_key_val; + char* data; + size_t len; + + if (!file_load(path, (void**) &data, &len, true)) { + /* If file does not exist, create one with default configuration + values */ + if (path_exists(path)) { + return CCONFIG_CONF_ERROR_FILE_CORRUPTED; + } else { + return CCONFIG_CONF_ERROR_NO_SUCH_FILE; + } + } + + pos_lines = strtok_r(data, "\n", &ctx_lines); + + while (pos_lines != NULL) { + char* pos_line_dup; + char* key = NULL; + char* val = NULL; + int cnt = 0; + struct cconfig_entry* entry; + + /* ignore comments and empty lines */ + if (strlen(pos_lines) > 0 && pos_lines[0] != '#') { + pos_line_dup = str_dup(pos_lines); + pos_key_val = strtok_r(pos_line_dup, "=", &ctx_key_val); + + log_misc("Line: %s", pos_lines); + + while (pos_key_val != NULL) { + + if (cnt == 0) { + key = pos_key_val; + } else if (cnt == 1) { + val = pos_key_val; + } + + pos_key_val = strtok_r(NULL, "=", &ctx_key_val); + cnt++; + } + + /* Key requiured, value can be NULL */ + if (cnt != 1 && cnt != 2) { + log_warning("Invalid options line %s in options file %s", + pos_lines, path); + free(pos_line_dup); + free(data); + return CCONFIG_CONF_ERROR_PARSING; + } + + /* NULL not allowed but empty string */ + if (!val) { + val = ""; + } + + log_misc("Key: %s, Value: %s", key, val); + + entry = cconfig_get(config, key); + + if (entry || add_params_if_absent) { + cconfig_set2(config, key, val); + } else { + /* Ignore cmd params that are not found in config */ + log_warning("Could not find parameter with key '%s' in " + "config, ignored", key); + } + + free(pos_line_dup); + } + + pos_lines = strtok_r(NULL, "\n", &ctx_lines); + } + + free(data); + + return CCONFIG_CONF_SUCCESS; +} + +enum cconfig_conf_error cconfig_conf_save_to_file(struct cconfig* config, + const char* path) +{ + FILE* file; + + file = fopen(path, "wb+"); + + if (file == NULL) { + return CCONFIG_CONF_ERROR_NO_SUCH_FILE; + } + + for (uint32_t i = 0; i < config->nentries; i++) { + fprintf(file, "# %s\n", config->entries[i].desc); + fprintf(file, "%s=%s\n\n", config->entries[i].key, + config->entries[i].value); + } + + fclose(file); + return CCONFIG_CONF_SUCCESS; +} \ No newline at end of file diff --git a/src/main/cconfig/conf.h b/src/main/cconfig/conf.h new file mode 100644 index 0000000..be71365 --- /dev/null +++ b/src/main/cconfig/conf.h @@ -0,0 +1,16 @@ +#include + +#include "cconfig/cconfig.h" + +enum cconfig_conf_error { + CCONFIG_CONF_SUCCESS = 0, + CCONFIG_CONF_ERROR_NO_SUCH_FILE = 1, + CCONFIG_CONF_ERROR_FILE_CORRUPTED = 2, + CCONFIG_CONF_ERROR_PARSING = 3, +}; + +enum cconfig_conf_error cconfig_conf_load_from_file(struct cconfig* config, + const char* path, bool add_params_if_absent); + +enum cconfig_conf_error cconfig_conf_save_to_file(struct cconfig* config, + const char* path); \ No newline at end of file diff --git a/src/main/config/Module.mk b/src/main/config/Module.mk new file mode 100644 index 0000000..2f7bac8 --- /dev/null +++ b/src/main/config/Module.mk @@ -0,0 +1,30 @@ +exes += config +rc_config := config.rc +cppflags_config := -DUNICODE + +libs_config := \ + eamio \ + geninput \ + util \ + +ldflags_config := \ + -lcomctl32 \ + -lcomdlg32 \ + -lgdi32 \ + -mwindows \ + +src_config := \ + analogs.c \ + bind-adv.c \ + bind.c \ + bind-light.c \ + buttons.c \ + eam.c \ + gametype.c \ + lights.c \ + main.c \ + schema.c \ + snap.c \ + spinner.c \ + usages.c \ + diff --git a/src/main/config/analogs.c b/src/main/config/analogs.c new file mode 100644 index 0000000..b639094 --- /dev/null +++ b/src/main/config/analogs.c @@ -0,0 +1,540 @@ +#include +#include + +#include +#include + +#include "config/resource.h" +#include "config/schema.h" +#include "config/usages.h" + +#include "geninput/hid-mgr.h" +#include "geninput/input-config.h" +#include "geninput/mapper.h" + +#include "util/array.h" +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +#define SENSITIVITY_SCALE 4 + +struct analogs_ui { + struct array children; +}; + +struct analog_ui { + const struct analog_def *def; + struct array hids; + struct array control_nos; + struct hid_stub *selected_hid; + uint8_t pos; +}; + +static INT_PTR CALLBACK analogs_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static INT_PTR analogs_ui_handle_init(HWND hwnd, const PROPSHEETPAGE *psp); +static INT_PTR analogs_ui_handle_activate(HWND hwnd); +static INT_PTR analogs_ui_handle_passivate(HWND hwnd); +static INT_PTR analogs_ui_handle_tick(HWND hwnd); +static INT_PTR analogs_ui_handle_fini(HWND hwnd); + +static INT_PTR CALLBACK analog_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static INT_PTR analog_ui_handle_init(HWND hwnd, struct analog_def *def); +static void analog_ui_handle_init_label(HWND hwnd); +static void analog_ui_handle_init_dev(HWND hwnd); +static void analog_ui_handle_init_sensitivity(HWND hwnd); +static bool analog_ui_match_device(struct hid_stub *hid); +static void analog_ui_populate_controls(HWND hwnd); +static INT_PTR analog_ui_handle_device_change(HWND hwnd); +static INT_PTR analog_ui_handle_control_change(HWND hwnd); +static INT_PTR analog_ui_handle_sensitivity_change(HWND hwnd); +static INT_PTR analog_ui_handle_tick(HWND hwnd); +static INT_PTR analog_ui_handle_fini(HWND hwnd); + +HPROPSHEETPAGE analogs_ui_tab_create(HINSTANCE inst, + const struct schema *schema) +{ + PROPSHEETPAGE psp; + + memset(&psp, 0, sizeof(psp)); + psp.dwSize = sizeof(psp); + psp.dwFlags = PSP_DEFAULT; + psp.hInstance = inst; + psp.pszTemplate = MAKEINTRESOURCE(IDD_TAB_ANALOGS); + psp.pfnDlgProc = analogs_ui_dlg_proc; + psp.lParam = (LPARAM) schema; + + return CreatePropertySheetPage(&psp); +} + +static INT_PTR CALLBACK analogs_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + const NMHDR *n; + + switch (msg) { + case WM_INITDIALOG: + return analogs_ui_handle_init(hwnd, (PROPSHEETPAGE *) lparam); + + case WM_NOTIFY: + n = (NMHDR *) lparam; + + switch (n->code) { + case PSN_SETACTIVE: + return analogs_ui_handle_activate(hwnd); + + case PSN_KILLACTIVE: + return analogs_ui_handle_passivate(hwnd); + } + + return FALSE; + + case WM_TIMER: + return analogs_ui_handle_tick(hwnd); + + case WM_DESTROY: + return analogs_ui_handle_fini(hwnd); + } + + return FALSE; +} + +static INT_PTR analogs_ui_handle_init(HWND hwnd, const PROPSHEETPAGE *psp) +{ + struct analogs_ui *ui; + const struct schema *schema; + long ypos; + size_t i; + HINSTANCE inst; + HWND child; + RECT r; + + ui = xmalloc(sizeof(*ui)); + array_init(&ui->children); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM) ui); + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + schema = (struct schema *) psp->lParam; + + ypos = 0; + + for (i = 0 ; i < schema->nanalogs ; i++) { + child = CreateDialogParam(inst, MAKEINTRESOURCE(IDD_ANALOG), hwnd, + analog_ui_dlg_proc, (LPARAM) &schema->analogs[i]); + + GetWindowRect(child, &r); + SetWindowPos(child, HWND_BOTTOM, 0, ypos, 0, 0, + SWP_NOSIZE | SWP_SHOWWINDOW); + + ypos += r.bottom - r.top; + + *array_append(HWND, &ui->children) = child; + } + + return TRUE; +} + +static INT_PTR analogs_ui_handle_activate(HWND hwnd) +{ + SetTimer(hwnd, 1, 17, NULL); + + return TRUE; +} + +static INT_PTR analogs_ui_handle_passivate(HWND hwnd) +{ + KillTimer(hwnd, 1); + + return TRUE; +} + +static INT_PTR analogs_ui_handle_tick(HWND hwnd) +{ + HWND child; + struct analogs_ui *ui; + size_t i; + + mapper_update(); + + ui = (struct analogs_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + for (i = 0 ; i < ui->children.nitems ; i++) { + child = *array_item(HWND, &ui->children, i); + SendMessage(child, WM_USER, 0, 0); + } + + return TRUE; +} + +static INT_PTR analogs_ui_handle_fini(HWND hwnd) +{ + struct analogs_ui *ui; + + ui = (struct analogs_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + array_fini(&ui->children); + free(ui); + + return TRUE; +} + +static INT_PTR CALLBACK analog_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_INITDIALOG: + return analog_ui_handle_init(hwnd, (struct analog_def *) lparam); + + case WM_USER: + return analog_ui_handle_tick(hwnd); + + case WM_COMMAND: + switch (HIWORD(wparam)) { + case CBN_SELCHANGE: + switch (LOWORD(wparam)) { + case IDC_DEVICE: + return analog_ui_handle_device_change(hwnd); + + case IDC_CONTROL: + return analog_ui_handle_control_change(hwnd); + + default: + return FALSE; + } + + default: + return FALSE; + } + + case WM_HSCROLL: + if (GetDlgItem(hwnd, IDC_SENSITIVITY) == (HWND) lparam) { + return analog_ui_handle_sensitivity_change(hwnd); + } else { + return FALSE; + } + + case WM_DESTROY: + return analog_ui_handle_fini(hwnd); + + default: + return FALSE; + } +} + +static INT_PTR analog_ui_handle_init(HWND hwnd, struct analog_def *def) +{ + struct analog_ui *ui; + + ui = xmalloc(sizeof(*ui)); + ui->def = def; + array_init(&ui->hids); + array_init(&ui->control_nos); + ui->selected_hid = NULL; + ui->pos = 0; + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM) ui); + + analog_ui_handle_init_label(hwnd); + analog_ui_handle_init_dev(hwnd); + analog_ui_handle_init_sensitivity(hwnd); + + return TRUE; +} + +static void analog_ui_handle_init_label(HWND hwnd) +{ + struct analog_ui *ui; + wchar_t label[128]; + HINSTANCE inst; + HWND box; + + ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + box = GetDlgItem(hwnd, IDC_GROUP); + + LoadString(inst, ui->def->label_rsrc, label, lengthof(label)); + SendMessage(box, WM_SETTEXT, 0, (LPARAM) label); +} + +static void analog_ui_handle_init_dev(HWND hwnd) +{ + struct analog_ui *ui; + struct mapped_analog ma; + wchar_t *dev_name; + struct hid_stub *hid; + size_t nchars; + LRESULT index; + HWND dev_list; + + ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + dev_list = GetDlgItem(hwnd, IDC_DEVICE); + + SendMessage(dev_list, CB_ADDSTRING, 0, (LPARAM) L""); + + hid_mgr_lock(); + + ma.hid = NULL; + mapper_get_analog_map(ui->def->tag, &ma); + + for (hid = hid_mgr_get_first_stub() + ; hid != NULL + ; hid = hid_mgr_get_next_stub(hid)) { + if (!analog_ui_match_device(hid)) { + continue; + } + + if (!hid_stub_get_name(hid, NULL, &nchars)) { + continue; + } + + dev_name = xmalloc(nchars * sizeof(*dev_name)); + + if (!hid_stub_get_name(hid, dev_name, &nchars)) { + free(dev_name); + + continue; + } + + index = SendMessage(dev_list, CB_ADDSTRING, 0, (LPARAM) dev_name); + + free(dev_name); + + *array_append(struct hid_stub *, &ui->hids) = hid; + + if (ma.hid == hid) { + SendMessage(dev_list, CB_SETCURSEL, index, 0); + + ui->selected_hid = hid; + analog_ui_populate_controls(hwnd); + } + } + + hid_mgr_unlock(); +} + +static void analog_ui_handle_init_sensitivity(HWND hwnd) +{ + struct analog_ui *ui; + int pos; + HWND slider; + + ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + slider = GetDlgItem(hwnd, IDC_SENSITIVITY); + pos = 256 * SENSITIVITY_SCALE + mapper_get_analog_sensitivity(ui->def->tag); + + SendMessage(slider, TBM_SETTICFREQ, 256, 0); + SendMessage(slider, TBM_SETRANGE, FALSE, + MAKELPARAM(0, SENSITIVITY_SCALE * 256 * 2)); + SendMessage(slider, TBM_SETPOS, TRUE, (LPARAM) pos); + + EnableWindow(slider, !mapper_is_analog_absolute(ui->def->tag)); +} + +static bool analog_ui_match_device(struct hid_stub *hid) +{ + struct hid_control *controls; + size_t ncontrols; + size_t i; + + if (!hid_stub_get_controls(hid, NULL, &ncontrols)) { + goto size_fail; + } + + controls = xmalloc(ncontrols * sizeof(*controls)); + + if (!hid_stub_get_controls(hid, controls, &ncontrols)) { + goto content_fail; + } + + for (i = 0 ; i < ncontrols ; i++) { + if (controls[i].value_max - controls[i].value_min > 1) { + break; + } + } + + free(controls); + + return i < ncontrols; + +content_fail: + free(controls); + +size_fail: + return false; +} + +static void analog_ui_populate_controls(HWND hwnd) +{ + char usage_desc[512]; + wchar_t *tmp; + struct analog_ui *ui; + struct mapped_analog ma; + struct hid_control *controls; + size_t ncontrols; + size_t i; + long nitems; + LRESULT index; + HWND controls_ctl; + + ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + controls_ctl = GetDlgItem(hwnd, IDC_CONTROL); + nitems = (long) SendMessage(controls_ctl, CB_GETCOUNT, 0, 0); + + InvalidateRect(controls_ctl, NULL, TRUE); + + for (index = nitems ; index >= 0 ; index--) { + SendMessage(controls_ctl, CB_DELETESTRING, index, 0); + } + + array_fini(&ui->control_nos); + array_init(&ui->control_nos); + + if (ui->selected_hid == NULL) { + return; + } + + if (!hid_stub_get_controls(ui->selected_hid, NULL, &ncontrols)) { + goto size_fail; + } + + controls = xmalloc(sizeof(*controls) * ncontrols); + + if (!hid_stub_get_controls(ui->selected_hid, controls, &ncontrols)) { + goto content_fail; + } + + SendMessage(controls_ctl, CB_ADDSTRING, 0, (LPARAM) L""); + + mapper_get_analog_map(ui->def->tag, &ma); + + for (i = 0 ; i < ncontrols ; i++) { + if (controls[i].value_max - controls[i].value_min <= 1) { + continue; + } + + usages_get(usage_desc, lengthof(usage_desc), controls[i].usage); + tmp = str_widen(usage_desc); + + index = SendMessage(controls_ctl, CB_ADDSTRING, 0, (LPARAM) tmp); + free(tmp); + + if (i == ma.control_no) { + SendMessage(controls_ctl, CB_SETCURSEL, index, 0); + } + + *array_append(size_t, &ui->control_nos) = i; + } + + free(controls); + + return; + +content_fail: + free(controls); + +size_fail: + return; +} + +static INT_PTR analog_ui_handle_device_change(HWND hwnd) +{ + struct mapped_analog ma; + struct analog_ui *ui; + LRESULT index; + + ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + index = SendMessage(GetDlgItem(hwnd, IDC_DEVICE), CB_GETCURSEL, 0, 0); + + log_assert((size_t) index <= ui->hids.nitems); + + if (index == 0) { + ui->selected_hid = NULL; + } else { + ui->selected_hid = *array_item(struct hid_stub *, &ui->hids, index - 1); + } + + ma.hid = NULL; + mapper_set_analog_map(ui->def->tag, &ma); + + hid_mgr_lock(); + analog_ui_populate_controls(hwnd); + hid_mgr_unlock(); + + return TRUE; +} + +static INT_PTR analog_ui_handle_control_change(HWND hwnd) +{ + struct mapped_analog ma; + struct analog_ui *ui; + LRESULT index; + HWND slider; + + ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + index = SendMessage(GetDlgItem(hwnd, IDC_CONTROL), CB_GETCURSEL, 0, 0); + + log_assert((size_t) index <= ui->control_nos.nitems); + + if (index == 0) { + ma.hid = NULL; + } else { + ma.hid = ui->selected_hid; + ma.control_no = *array_item(size_t, &ui->control_nos, index - 1); + } + + mapper_set_analog_map(ui->def->tag, &ma); + + slider = GetDlgItem(hwnd, IDC_SENSITIVITY); + + EnableWindow(slider, !mapper_is_analog_absolute(ui->def->tag)); + + return TRUE; +} + +static INT_PTR analog_ui_handle_sensitivity_change(HWND hwnd) +{ + struct analog_ui *ui; + int pos; + HWND slider; + + ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + slider = GetDlgItem(hwnd, IDC_SENSITIVITY); + pos = (int) SendMessage(slider, TBM_GETPOS, 0, 0); + + mapper_set_analog_sensitivity(ui->def->tag, pos - 256 * SENSITIVITY_SCALE); + + return TRUE; +} + +static INT_PTR analog_ui_handle_tick(HWND hwnd) +{ + struct analog_ui *ui; + HWND pos_ctl; + + ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + pos_ctl = GetDlgItem(hwnd, IDC_POSITION); + + ui->pos = mapper_read_analog(ui->def->tag); + + SendMessage(pos_ctl, WM_USER, 0, (LPARAM) ui->pos); + + return TRUE; +} + +static INT_PTR analog_ui_handle_fini(HWND hwnd) +{ + struct analog_ui *ui; + + ui = (struct analog_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + array_fini(&ui->hids); + array_fini(&ui->control_nos); + free(ui); + + return TRUE; +} + diff --git a/src/main/config/bind-adv.c b/src/main/config/bind-adv.c new file mode 100644 index 0000000..539bf77 --- /dev/null +++ b/src/main/config/bind-adv.c @@ -0,0 +1,530 @@ +#include + +#include +#include +#include +#include + +#include "config/bind-adv.h" +#include "config/resource.h" +#include "config/usages.h" + +#include "geninput/hid-mgr.h" + +#include "util/array.h" +#include "util/defs.h" +#include "util/mem.h" +#include "util/str.h" + +struct bind_adv_state { + uintptr_t timer_id; + struct mapped_action ma; + struct array devs; + struct hid_stub *cur_hid; + struct hid_control *ctls; + size_t nctls; + bool was_valid; +}; + +static INT_PTR CALLBACK bind_adv_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static INT_PTR bind_adv_handle_init(HWND hwnd, struct bind_adv_state *state); +static void bind_adv_handle_init_devs(HWND hwnd); +static void bind_adv_handle_init_insert_dev(HWND hwnd, struct hid_stub *hid); +static INT_PTR bind_adv_handle_change_ctl(HWND hwnd); +static INT_PTR bind_adv_handle_change_dev(HWND hwnd); +static INT_PTR bind_adv_handle_change_range(HWND hwnd); +static INT_PTR bind_adv_handle_tick(HWND hwnd); +static INT_PTR bind_adv_handle_ok(HWND hwnd); +static INT_PTR bind_adv_handle_fini(HWND hwnd); +static bool bind_adv_get_dev_no(HWND hwnd, size_t *dev_no_out); +static bool bind_adv_get_ctl_no(HWND hwnd, size_t *ctl_no_out); +static bool bind_adv_get_range(HWND hwnd, int32_t *out_min, int32_t *out_max); +static bool bind_adv_get_int(HWND control, int32_t *out); +static bool bind_adv_is_valid(HWND hwnd); +static void bind_adv_validate(HWND hwnd); +static void bind_adv_select_dev(HWND hwnd, size_t dev_no); +static void bind_adv_select_ctl(HWND hwnd, size_t ctl_no); +static void bind_adv_set_range(HWND hwnd, int32_t range_min, int32_t range_max); + +bool bind_adv(HINSTANCE inst, HWND hwnd, struct mapped_action *ma, + bool was_valid) +{ + struct bind_adv_state state; + INT_PTR ok; + + memset(&state, 0, sizeof(state)); + + state.ma = *ma; + state.was_valid = was_valid; + + ok = DialogBoxParam(inst, MAKEINTRESOURCE(IDD_BIND_ADV), hwnd, + bind_adv_dlg_proc, (LPARAM) &state) != 0; + + if (ok) { + *ma = state.ma; + } + + return ok != 0; +} + +static INT_PTR CALLBACK bind_adv_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_INITDIALOG: + return bind_adv_handle_init(hwnd, (struct bind_adv_state *) lparam); + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case IDC_CONTROL: + switch (HIWORD(wparam)) { + case CBN_SELCHANGE: + return bind_adv_handle_change_ctl(hwnd); + } + + return FALSE; + + case IDC_DEVICE: + switch (HIWORD(wparam)) { + case CBN_SELCHANGE: + return bind_adv_handle_change_dev(hwnd); + } + + return FALSE; + + case IDC_BINDING_MIN: + case IDC_BINDING_MAX: + switch (HIWORD(wparam)) { + case EN_CHANGE: + return bind_adv_handle_change_range(hwnd); + } + + return FALSE; + + case IDOK: + switch (HIWORD(wparam)) { + case BN_CLICKED: + return bind_adv_handle_ok(hwnd); + } + + return FALSE; + + case IDCANCEL: + switch (HIWORD(wparam)) { + case BN_CLICKED: + EndDialog(hwnd, FALSE); + + return TRUE; + } + + return FALSE; + } + + return FALSE; + + case WM_TIMER: + return bind_adv_handle_tick(hwnd); + + case WM_DESTROY: + return bind_adv_handle_fini(hwnd); + } + + return FALSE; +} + +static INT_PTR bind_adv_handle_init(HWND hwnd, struct bind_adv_state *state) +{ + struct hid_stub *hid; + size_t i; + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM) state); + + bind_adv_handle_init_devs(hwnd); + + state->timer_id = SetTimer(hwnd, 1, 17, NULL); + + if (!state->was_valid) { + return TRUE; + } + + for (i = 0 ; i < state->devs.nitems ; i++) { + hid = *array_item(struct hid_stub *, &state->devs, i); + + if (hid == state->ma.hid) { + bind_adv_select_dev(hwnd, (int) i); + bind_adv_select_ctl(hwnd, state->ma.control_no); + bind_adv_set_range(hwnd, state->ma.value_min, + state->ma.value_max); + } + } + + return TRUE; +} + +static void bind_adv_handle_init_devs(HWND hwnd) +{ + struct hid_stub *hid; + + hid_mgr_lock(); + + for (hid = hid_mgr_get_first_stub() + ; hid != NULL + ; hid = hid_mgr_get_next_stub(hid)) { + bind_adv_handle_init_insert_dev(hwnd, hid); + } + + hid_mgr_unlock(); +} + +static void bind_adv_handle_init_insert_dev(HWND hwnd, struct hid_stub *hid) +{ + struct bind_adv_state *state; + wchar_t *name; + size_t nchars; + HWND devs; + + state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + devs = GetDlgItem(hwnd, IDC_DEVICE); + + if (!hid_stub_get_name(hid, NULL, &nchars)) { + goto size_fail; + } + + name = xmalloc(sizeof(*name) * nchars); + + if (!hid_stub_get_name(hid, name, &nchars)) { + goto name_fail; + } + + *array_append(struct hid_stub *, &state->devs) = hid; + + SendMessage(devs, CB_ADDSTRING, 0, (LPARAM) name); + +name_fail: + free(name); + +size_fail: + ; +} + +static INT_PTR bind_adv_handle_change_ctl(HWND hwnd) +{ + size_t ctl_no; + + if (bind_adv_get_ctl_no(hwnd, &ctl_no)) { + bind_adv_select_ctl(hwnd, ctl_no); + } + + return TRUE; +} + +static INT_PTR bind_adv_handle_change_dev(HWND hwnd) +{ + size_t dev_no; + + if (bind_adv_get_dev_no(hwnd, &dev_no)) { + bind_adv_select_dev(hwnd, dev_no); + } + + return TRUE; +} + +static INT_PTR bind_adv_handle_change_range(HWND hwnd) +{ + bind_adv_validate(hwnd); + + return TRUE; +} + +static INT_PTR bind_adv_handle_tick(HWND hwnd) +{ + struct bind_adv_state *state; + wchar_t wchars[16]; + size_t ctl_no; + int32_t value; + HWND label; + + state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + if (!bind_adv_get_ctl_no(hwnd, &ctl_no)) { + return TRUE; + } + + hid_mgr_lock(); + + if (hid_stub_get_value(state->cur_hid, ctl_no, &value)) { + wstr_format(wchars, lengthof(wchars), L"%d", value); + + label = GetDlgItem(hwnd, IDC_CURRENT); + SetWindowText(label, wchars); + } + + hid_mgr_unlock(); + + return TRUE; +} + +static INT_PTR bind_adv_handle_ok(HWND hwnd) +{ + struct bind_adv_state *state; + size_t dev_no; + size_t ctl_no; + + state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + /* bind_adv_validate() ought to ensure that OK is disabled whenever the + user's input is somehow invalid. Still, it never hurts to double check. + + Ignore any OK clicks that we somehow receive with invalid input. */ + + if (!bind_adv_get_dev_no(hwnd, &dev_no)) { + return TRUE; + } + + state->ma.hid = *array_item(struct hid_stub *, &state->devs, dev_no); + + if (!bind_adv_get_ctl_no(hwnd, &ctl_no)) { + return TRUE; + } + + state->ma.control_no = ctl_no; + + if (!bind_adv_get_range(hwnd, &state->ma.value_min, &state->ma.value_max)) { + return TRUE; + } + + /* Input OK, shut down this dialog. */ + + EndDialog(hwnd, TRUE); + + return TRUE; +} + +static INT_PTR bind_adv_handle_fini(HWND hwnd) +{ + struct bind_adv_state *state; + + state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + array_fini(&state->devs); + + KillTimer(hwnd, state->timer_id); + + return TRUE; +} + +static bool bind_adv_get_dev_no(HWND hwnd, size_t *dev_no_out) +{ + const struct bind_adv_state *state; + size_t dev_no; + HWND devs; + + state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + devs = GetDlgItem(hwnd, IDC_DEVICE); + + dev_no = SendMessage(devs, CB_GETCURSEL, 0, 0); + + if (dev_no >= state->devs.nitems) { + return false; + } + + if (dev_no_out != NULL) { + *dev_no_out = dev_no; + } + + return true; +} + +static bool bind_adv_get_ctl_no(HWND hwnd, size_t *ctl_no_out) +{ + const struct bind_adv_state *state; + size_t ctl_no; + HWND ctls; + + state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + ctls = GetDlgItem(hwnd, IDC_CONTROL); + + ctl_no = SendMessage(ctls, CB_GETCURSEL, 0, 0); + + if (ctl_no >= state->nctls) { + return false; + } + + if (ctl_no_out != NULL) { + *ctl_no_out = ctl_no; + } + + return true; +} + +static bool bind_adv_get_range(HWND hwnd, int32_t *out_min, int32_t *out_max) +{ + HWND wnd_min; + HWND wnd_max; + + wnd_min = GetDlgItem(hwnd, IDC_BINDING_MIN); + wnd_max = GetDlgItem(hwnd, IDC_BINDING_MAX); + + return bind_adv_get_int(wnd_min, out_min) + && bind_adv_get_int(wnd_max, out_max); +} + +static bool bind_adv_get_int(HWND control, int32_t *out) +{ + wchar_t text[16]; + int32_t tmp; + + text[0] = L'\0'; + GetWindowText(control, text, lengthof(text)); + + if (swscanf(text, L"%d", &tmp) != 1) { + return false; + } + + if (out != NULL) { + *out = tmp; + } + + return true; +} + +static bool bind_adv_is_valid(HWND hwnd) +{ + if (!bind_adv_get_dev_no(hwnd, NULL)) { + return false; + } + + if (!bind_adv_get_ctl_no(hwnd, NULL)) { + return false; + } + + if (!bind_adv_get_range(hwnd, NULL, NULL)) { + return false; + } + + return true; +} + +static void bind_adv_validate(HWND hwnd) +{ + HWND ok; + + ok = GetDlgItem(hwnd, IDOK); + EnableWindow(ok, bind_adv_is_valid(hwnd) ? TRUE : FALSE); +} + +static void bind_adv_select_dev(HWND hwnd, size_t dev_no) +{ + struct bind_adv_state *state; + struct hid_stub *hid; + char chars[256]; + wchar_t wchars[256]; + size_t i; + HWND ctls; + HWND devs; + + state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + ctls = GetDlgItem(hwnd, IDC_CONTROL); + devs = GetDlgItem(hwnd, IDC_DEVICE); + + if (dev_no >= state->devs.nitems) { + return; + } + + SendMessage(devs, CB_SETCURSEL, (WPARAM) dev_no, 0); + SendMessage(ctls, CB_RESETCONTENT, 0, 0); + + hid = *array_item(struct hid_stub *, &state->devs, dev_no); + + state->cur_hid = hid; + + hid_mgr_lock(); + + if (!hid_stub_get_controls(state->cur_hid, NULL, &state->nctls)) { + goto size_fail; + } + + free(state->ctls); + state->ctls = xmalloc(sizeof(*state->ctls) * state->nctls); + + if (!hid_stub_get_controls(state->cur_hid, state->ctls, &state->nctls)) { + goto data_fail; + } + + for (i = 0 ; i < state->nctls ; i++) { + wchars[0] = L'\0'; + + usages_get(chars, lengthof(chars), state->ctls[i].usage); + MultiByteToWideChar(CP_UTF8, 0, chars, -1, wchars, lengthof(wchars)); + + SendMessage(ctls, CB_ADDSTRING, 0, (LPARAM) wchars); + } + + hid_mgr_unlock(); + + bind_adv_validate(hwnd); + + return; + +data_fail: + free(state->ctls); + + state->ctls = NULL; + state->nctls = 0; + +size_fail: + hid_mgr_unlock(); + + bind_adv_validate(hwnd); +} + +static void bind_adv_select_ctl(HWND hwnd, size_t ctl_no) +{ + wchar_t text[128]; + struct bind_adv_state *state; + const struct hid_control *ctl; + HWND ctls; + HWND limit_min; + HWND limit_max; + + state = (struct bind_adv_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + ctls = GetDlgItem(hwnd, IDC_CONTROL); + limit_min = GetDlgItem(hwnd, IDC_LIMIT_MIN); + limit_max = GetDlgItem(hwnd, IDC_LIMIT_MAX); + + if (ctl_no >= state->nctls) { + return; + } + + SendMessage(ctls, CB_SETCURSEL, (WPARAM) ctl_no, 0); + + ctl = &state->ctls[ctl_no]; + + wstr_format(text, lengthof(text), L"%d", ctl->value_min); + SetWindowText(limit_min, text); + + wstr_format(text, lengthof(text), L"%d", ctl->value_max); + SetWindowText(limit_max, text); + + bind_adv_validate(hwnd); +} + +static void bind_adv_set_range(HWND hwnd, int32_t range_min, int32_t range_max) +{ + wchar_t text[16]; + HWND wnd_min; + HWND wnd_max; + + wnd_min = GetDlgItem(hwnd, IDC_BINDING_MIN); + wnd_max = GetDlgItem(hwnd, IDC_BINDING_MAX); + + wstr_format(text, lengthof(text), L"%d", range_min); + SetWindowText(wnd_min, text); + + wstr_format(text, lengthof(text), L"%d", range_max); + SetWindowText(wnd_max, text); + + bind_adv_validate(hwnd); +} + diff --git a/src/main/config/bind-adv.h b/src/main/config/bind-adv.h new file mode 100644 index 0000000..5d00723 --- /dev/null +++ b/src/main/config/bind-adv.h @@ -0,0 +1,15 @@ +#ifndef CONFIG_BIND_ADV_H +#define CONFIG_BIND_ADV_H + +#include + +#include + +#include "config/schema.h" + +#include "geninput/input-config.h" + +bool bind_adv(HINSTANCE inst, HWND hwnd, struct mapped_action *ma, + bool was_valid); + +#endif diff --git a/src/main/config/bind-light.c b/src/main/config/bind-light.c new file mode 100644 index 0000000..aab9999 --- /dev/null +++ b/src/main/config/bind-light.c @@ -0,0 +1,162 @@ +#include +#include + +#include +#include +#include + +#include "config/resource.h" +#include "config/schema.h" +#include "config/usages.h" + +#include "geninput/hid-mgr.h" +#include "geninput/mapper.h" + +#include "util/defs.h" +#include "util/mem.h" + +struct bind_light_state { + const struct schema *schema; + const struct mapped_light *ml; + uint8_t *game_light; + bool *bound; +}; + +static INT_PTR CALLBACK bind_light_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static INT_PTR bind_light_handle_init(HWND hwnd, + struct bind_light_state *state); +static INT_PTR bind_light_handle_ok(HWND hwnd); + +bool bind_light(HINSTANCE inst, HWND hwnd, const struct schema *schema, + const struct mapped_light *ml, uint8_t *game_light, bool *bound) +{ + struct bind_light_state state; + + state.schema = schema; + state.ml = ml; + state.game_light = game_light; + state.bound = bound; + + return DialogBoxParam(inst, MAKEINTRESOURCE(IDD_BIND_LIGHT), hwnd, + bind_light_dlg_proc, (LPARAM) &state) != 0; +} + +static INT_PTR CALLBACK bind_light_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_INITDIALOG: + return bind_light_handle_init(hwnd, + (struct bind_light_state *) lparam); + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case IDOK: + switch (HIWORD(wparam)) { + case BN_CLICKED: + return bind_light_handle_ok(hwnd); + } + + return FALSE; + + case IDCANCEL: + switch (HIWORD(wparam)) { + case BN_CLICKED: + EndDialog(hwnd, FALSE); + + return TRUE; + } + + return FALSE; + } + } + + return FALSE; +} + +static INT_PTR bind_light_handle_init(HWND hwnd, + struct bind_light_state *state) +{ + char chars[256]; + wchar_t wchars[256]; + struct hid_light *lights; + size_t nlights; + size_t i; + HINSTANCE inst; + HWND hid_light_ctl; + HWND game_light_ctl; + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR) state); + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + game_light_ctl = GetDlgItem(hwnd, IDC_GAME_LIGHT); + + ComboBox_AddString(game_light_ctl, L""); + + for (i = 0 ; i < state->schema->nlights ; i++) { + LoadString(inst, state->schema->lights[i].name_rsrc, + wchars, lengthof(wchars)); + ComboBox_AddString(game_light_ctl, wchars); + + if (state->bound + && state->schema->lights[i].bit == *state->game_light) { + ComboBox_SetCurSel(game_light_ctl, i + 1); + } + } + + hid_light_ctl = GetDlgItem(hwnd, IDC_LIGHT); + + hid_mgr_lock(); + + if (!hid_stub_get_lights(state->ml->hid, NULL, &nlights)) { + goto size_fail; + } + + if (state->ml->light_no >= nlights) { + goto bounds_fail; + } + + lights = xmalloc(sizeof(*lights) * nlights); + + if (!hid_stub_get_lights(state->ml->hid, lights, &nlights)) { + goto data_fail; + } + + usages_get(chars, lengthof(chars), lights[state->ml->light_no].usage); + MultiByteToWideChar(CP_UTF8, 0, chars, -1, wchars, lengthof(wchars)); + + SetWindowText(hid_light_ctl, wchars); + +data_fail: + free(lights); + +bounds_fail: +size_fail: + hid_mgr_unlock(); + + return TRUE; +} + +static INT_PTR bind_light_handle_ok(HWND hwnd) +{ + struct bind_light_state *state; + int pos; + HWND game_light_ctl; + + state = (struct bind_light_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + game_light_ctl = GetDlgItem(hwnd, IDC_GAME_LIGHT); + pos = ComboBox_GetCurSel(game_light_ctl); + + if (pos <= 0) { + *state->bound = false; + } else { + *state->bound = true; + *state->game_light = state->schema->lights[pos - 1].bit; + } + + EndDialog(hwnd, TRUE); + + return TRUE; +} + diff --git a/src/main/config/bind-light.h b/src/main/config/bind-light.h new file mode 100644 index 0000000..f96fd01 --- /dev/null +++ b/src/main/config/bind-light.h @@ -0,0 +1,16 @@ +#ifndef CONFIG_BIND_LIGHT_H +#define CONFIG_BIND_LIGHT_H + +#include + +#include +#include + +#include "config/schema.h" + +#include "geninput/mapper.h" + +bool bind_light(HINSTANCE inst, HWND hwnd, const struct schema *schema, + const struct mapped_light *ml, uint8_t *game_light, bool *bound); + +#endif diff --git a/src/main/config/bind.c b/src/main/config/bind.c new file mode 100644 index 0000000..255f14d --- /dev/null +++ b/src/main/config/bind.c @@ -0,0 +1,127 @@ +#include +#include + +#include "config/bind.h" +#include "config/resource.h" +#include "config/schema.h" +#include "config/snap.h" + +#include "util/defs.h" + +struct bind_state { + const struct action_def *action; + struct mapped_action *ma; + struct snap snaps[2]; + size_t cur_snap; + uintptr_t timer_id; +}; + +static INT_PTR CALLBACK bind_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static INT_PTR bind_handle_init(HWND hwnd, struct bind_state *self); +static INT_PTR bind_handle_tick(HWND hwnd); +static INT_PTR bind_handle_cancel(HWND hwnd); +static INT_PTR bind_handle_destroy(HWND hwnd); + +bool bind_control(HINSTANCE inst, HWND hwnd, const struct action_def *action, + struct mapped_action *ma) +{ + struct bind_state self; + bool result; + + self.action = action; + self.ma = ma; + self.cur_snap = 0; + + snap_init(&self.snaps[1]); + snap_init(&self.snaps[0]); + + result = DialogBoxParam(inst, MAKEINTRESOURCE(IDD_BIND), hwnd, + bind_dlg_proc, (LPARAM) &self) != 0; + + snap_fini(&self.snaps[0]); + snap_fini(&self.snaps[1]); + + return result; +} + +static INT_PTR CALLBACK bind_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_INITDIALOG: + return bind_handle_init(hwnd, (struct bind_state *) lparam); + + case WM_TIMER: + return bind_handle_tick(hwnd); + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case IDCANCEL: + return bind_handle_cancel(hwnd); + + default: + return FALSE; + } + + case WM_DESTROY: + return bind_handle_destroy(hwnd); + + default: + return FALSE; + } +} + +static INT_PTR bind_handle_init(HWND hwnd, struct bind_state *self) +{ + HINSTANCE inst; + wchar_t str[128]; + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + LoadString(inst, self->action->name_rsrc, str, lengthof(str)); + SetWindowText(GetDlgItem(hwnd, IDC_ACTION_NAME), str); + + self->timer_id = SetTimer(hwnd, 0, 17, NULL); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) self); + + return TRUE; +} + +static INT_PTR bind_handle_tick(HWND hwnd) +{ + struct bind_state *self; + + self = (struct bind_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + self->cur_snap = !self->cur_snap; + + snap_fini(&self->snaps[self->cur_snap]); + snap_init(&self->snaps[self->cur_snap]); + + if (snap_find_edge(&self->snaps[self->cur_snap], + &self->snaps[!self->cur_snap], self->ma)) { + EndDialog(hwnd, 1); + } + + return TRUE; +} + +static INT_PTR bind_handle_cancel(HWND hwnd) +{ + EndDialog(hwnd, 0); + + return TRUE; +} + +static INT_PTR bind_handle_destroy(HWND hwnd) +{ + struct bind_state *self; + + self = (struct bind_state *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + KillTimer(hwnd, self->timer_id); + + return TRUE; +} + diff --git a/src/main/config/bind.h b/src/main/config/bind.h new file mode 100644 index 0000000..579adf9 --- /dev/null +++ b/src/main/config/bind.h @@ -0,0 +1,15 @@ +#ifndef CONFIG_BIND_H +#define CONFIG_BIND_H + +#include + +#include + +#include "config/schema.h" + +#include "geninput/mapper.h" + +bool bind_control(HINSTANCE inst, HWND hwnd, const struct action_def *action, + struct mapped_action *ma); + +#endif diff --git a/src/main/config/buttons.c b/src/main/config/buttons.c new file mode 100644 index 0000000..9ce0b0e --- /dev/null +++ b/src/main/config/buttons.c @@ -0,0 +1,426 @@ +#include +#include +#include + +#include +#include +#include + +#include "config/bind.h" +#include "config/bind-adv.h" +#include "config/resource.h" +#include "config/schema.h" +#include "config/usages.h" + +#include "geninput/input-config.h" +#include "geninput/mapper.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/winres.h" + +static INT_PTR CALLBACK buttons_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static bool buttons_get_control_name(const struct mapped_action *ma, + wchar_t *str, size_t nchars); +static INT_PTR buttons_handle_init(HWND hwnd, const PROPSHEETPAGE *psp); +static INT_PTR buttons_handle_binding_adv(HWND hwnd); +static INT_PTR buttons_handle_binding_clear(HWND hwnd); +static INT_PTR buttons_handle_binding_edit(HWND hwnd, BOOL bind_many); +static INT_PTR buttons_handle_switch_page(HWND hwnd, NMUPDOWN *n); +static INT_PTR buttons_handle_fini(HWND hwnd); +static void buttons_update(HWND hwnd); +static void buttons_update_bindings(HWND hwnd); +static void buttons_update_pager(HWND hwnd); + +struct buttons_tab { + const struct schema *schema; + uint8_t page_no; + uint8_t npages; +}; + +HPROPSHEETPAGE buttons_tab_create(HINSTANCE inst, const struct schema *schema) +{ + PROPSHEETPAGE psp; + + memset(&psp, 0, sizeof(psp)); + psp.dwSize = sizeof(psp); + psp.dwFlags = PSP_DEFAULT; + psp.hInstance = inst; + psp.pszTemplate = MAKEINTRESOURCE(IDD_TAB_BUTTONS); + psp.pfnDlgProc = buttons_dlg_proc; + psp.lParam = (LPARAM) schema; + + return CreatePropertySheetPage(&psp); +} + +static INT_PTR CALLBACK buttons_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + NMHDR *n; + + switch (msg) { + case WM_INITDIALOG: + return buttons_handle_init(hwnd, (const PROPSHEETPAGE *) lparam); + + case WM_NOTIFY: + n = (NMHDR *) lparam; + + switch (n->idFrom) { + case IDC_PAGE: + switch (n->code) { + case UDN_DELTAPOS: + return buttons_handle_switch_page(hwnd, + (NMUPDOWN *) n); + + default: + return FALSE; + } + + case IDC_BINDINGS: + switch (n->code) { + case NM_DBLCLK: + return buttons_handle_binding_edit(hwnd, FALSE); + + default: + return FALSE; + } + + default: + return FALSE; + } + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case IDC_BINDING_ADV: + return buttons_handle_binding_adv(hwnd); + + case IDC_BINDING_CLEAR: + return buttons_handle_binding_clear(hwnd); + + case IDC_BINDING_EDIT: + return buttons_handle_binding_edit(hwnd, FALSE); + + case IDC_BINDING_EDIT_MANY: + return buttons_handle_binding_edit(hwnd, TRUE); + + default: + return FALSE; + } + + case WM_DESTROY: + return buttons_handle_fini(hwnd); + + default: + return FALSE; + } +} + +static INT_PTR buttons_handle_init(HWND hwnd, const PROPSHEETPAGE *psp) +{ + struct buttons_tab *self; + HWND bindings_ctl; + HINSTANCE inst; + LVCOLUMN col; + LVITEM item; + wchar_t str[128]; + size_t i; + + self = xmalloc(sizeof(*self)); + self->schema = (const struct schema *) psp->lParam; + self->page_no = 0; + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) self); + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + bindings_ctl = GetDlgItem(hwnd, IDC_BINDINGS); + + ListView_SetExtendedListViewStyle(bindings_ctl, + LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); + + LoadString(inst, IDS_COL_ACTION, str, lengthof(str)); + + col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; + col.fmt = LVCFMT_LEFT; + col.cx = 120; + col.pszText = str; + + ListView_InsertColumn(bindings_ctl, 0, &col); + + LoadString(inst, IDS_COL_BUTTON, str, lengthof(str)); + + col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; + col.fmt = LVCFMT_LEFT; + col.cx = 120; + col.pszText = str; + + ListView_InsertColumn(bindings_ctl, 1, &col); + + for (i = 0 ; i < self->schema->nactions ; i++) { + memset(&item, 0, sizeof(item)); + + LoadString(inst, self->schema->actions[i].name_rsrc, str, + lengthof(str)); + + item.mask = LVIF_TEXT; + item.iItem = (int) i; + item.iSubItem = 0; + item.pszText = str; + + ListView_InsertItem(bindings_ctl, &item); + } + + buttons_update(hwnd); + + return TRUE; +} + +static void buttons_update(HWND hwnd) +{ + buttons_update_pager(hwnd); + buttons_update_bindings(hwnd); + + /* Flickery, but seems to be the only way to avoid label garbling when + the page label changes. Need to do better. */ + InvalidateRect(hwnd, NULL, TRUE); +} + +static void buttons_update_pager(HWND hwnd) +{ + struct buttons_tab *self; + HINSTANCE inst; + wchar_t str[128]; + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + self->npages = mapper_get_npages() + 1; + + rswprintf(str, lengthof(str), inst, IDS_PAGE, self->page_no + 1, + self->npages); + SetWindowText(GetDlgItem(hwnd, IDC_PAGE_TEXT), str); +} + +static void buttons_update_bindings(HWND hwnd) +{ + action_iter_t pos; + struct mapped_action ma; + struct buttons_tab *self; + HINSTANCE inst; + HWND bindings_ctl; + LVITEM item; + wchar_t str[128]; + size_t i; + + self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + bindings_ctl = GetDlgItem(hwnd, IDC_BINDINGS); + + /* Clear binding cell text */ + + for (i = 0 ; i < self->schema->nactions ; i++) { + memset(&item, 0, sizeof(item)); + + item.mask = LVIF_TEXT; + item.iItem = (int) i; + item.iSubItem = 1; + item.pszText = L""; + + ListView_SetItem(bindings_ctl, &item); + } + + /* Repopulate bindings */ + + hid_mgr_lock(); + + for (pos = mapper_iterate_actions() + ; action_iter_is_valid(pos) + ; action_iter_next(pos)) { + if (action_iter_get_page(pos) != self->page_no) { + continue; + } + + action_iter_get_mapping(pos, &ma); + + if (!buttons_get_control_name(&ma, str, lengthof(str))) { + /* Can't read device metadata, device is probably unplugged */ + LoadString(inst, IDS_NOT_PRESENT, str, lengthof(str)); + } + + memset(&item, 0, sizeof(item)); + + item.mask = LVIF_TEXT; + item.iItem = (int) action_iter_get_action(pos); + item.iSubItem = 1; + item.pszText = str; + + ListView_SetItem(bindings_ctl, &item); + } + + action_iter_free(pos); + hid_mgr_unlock(); +} + +static bool buttons_get_control_name(const struct mapped_action *ma, + wchar_t *str, size_t nchars) +{ + char chars[128]; + struct hid_control *controls; + size_t ncontrols; + + if (!hid_stub_get_controls(ma->hid, NULL, &ncontrols)) { + goto count_fail; + } + + controls = xmalloc(ncontrols * sizeof(*controls)); + + if (!hid_stub_get_controls(ma->hid, controls, &ncontrols)) { + goto data_fail; + } + + if (ma->control_no >= ncontrols) { + goto bounds_fail; + } + + usages_get(chars, lengthof(chars), controls[ma->control_no].usage); + MultiByteToWideChar(CP_UTF8, 0, chars, lengthof(chars), str, nchars); + + free(controls); + + return true; + +bounds_fail: +data_fail: + free(controls); + +count_fail: + return false; +} + +static INT_PTR buttons_handle_binding_adv(HWND hwnd) +{ + HINSTANCE inst; + struct mapped_action ma; + struct buttons_tab *self; + int action_no; + uint8_t bit; + bool is_valid; + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + action_no = ListView_GetSelectionMark(GetDlgItem(hwnd, IDC_BINDINGS)); + + if (action_no < 0 || action_no >= (int) self->schema->nactions) { + return TRUE; + } + + bit = self->schema->actions[action_no].bit; + + is_valid = mapper_get_action_map((uint8_t) action_no, self->page_no, &ma); + + if (bind_adv(inst, hwnd, &ma, is_valid)) { + mapper_set_action_map((uint8_t) action_no, self->page_no, bit, &ma); + buttons_update(hwnd); + } + + return TRUE; +} + +static INT_PTR buttons_handle_binding_clear(HWND hwnd) +{ + struct buttons_tab *self; + int action_no; + + self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + action_no = ListView_GetSelectionMark(GetDlgItem(hwnd, IDC_BINDINGS)); + + if (action_no < 0 || action_no >= (int) self->schema->nactions) { + return TRUE; + } + + mapper_clear_action_map((uint8_t) action_no, self->page_no); + buttons_update(hwnd); + + return TRUE; +} + +static INT_PTR buttons_handle_binding_edit(HWND hwnd, BOOL bind_many) +{ + HINSTANCE inst; + HWND listview; + struct buttons_tab *self; + struct mapped_action ma; + int action_start; + int action_no; + uint8_t bit; + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + listview = GetDlgItem(hwnd, IDC_BINDINGS); + + action_start = ListView_GetSelectionMark(listview); + + if (action_start < 0 || action_start >= (int) self->schema->nactions) { + /* wtf... */ + return TRUE; + } + + for(action_no = action_start; + action_no < self->schema->nactions; + action_no++) { + // Make sure the next listview item is visible + ListView_SetItemState (listview, action_no, LVIS_FOCUSED | LVIS_SELECTED, 0x000F); + ListView_EnsureVisible(listview, action_no, FALSE); + + bit = self->schema->actions[action_no].bit; + + if (bind_control(inst, hwnd, &self->schema->actions[action_no], &ma)) { + mapper_set_action_map((uint8_t) action_no, self->page_no, bit, &ma); + buttons_update(hwnd); + + log_misc("Bind act %d -> dev %p ctl %u range [%d, %d]", + self->schema->actions[action_no].bit, ma.hid, + (unsigned int) ma.control_no, ma.value_min, ma.value_max); + } else { + break; + } + + if(!bind_many) { + break; + } + } + + return TRUE; +} + +static INT_PTR buttons_handle_switch_page(HWND hwnd, NMUPDOWN *n) +{ + struct buttons_tab *self; + + self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + /* The buttons on a horizontal spinner are backwards -.- */ + + if (n->iDelta > 0 && self->page_no > 0) { + self->page_no--; + buttons_update(hwnd); + } else if (n->iDelta < 0 && self->page_no < self->npages - 1) { + self->page_no++; + buttons_update(hwnd); + } + + return TRUE; +} + +static INT_PTR buttons_handle_fini(HWND hwnd) +{ + struct buttons_tab *self; + + self = (struct buttons_tab *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + free(self); + + return TRUE; +} + diff --git a/src/main/config/config.exe.manifest b/src/main/config/config.exe.manifest new file mode 100644 index 0000000..fa80169 --- /dev/null +++ b/src/main/config/config.exe.manifest @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/main/config/config.rc b/src/main/config/config.rc new file mode 100644 index 0000000..1e40ba3 --- /dev/null +++ b/src/main/config/config.rc @@ -0,0 +1,392 @@ +// Generated by ResEdit 1.5.11 +// Copyright (C) 2006-2012 +// http://www.resedit.net + +#include +#include +#include +#include "resource.h" + +1 RT_MANIFEST config.exe.manifest + + + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDR_USAGES USAGES "config/usages.txt" + + + +// +// Dialog resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_ANALOG DIALOG 0, 0, 220, 100 +STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_VISIBLE | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "Ms Shell Dlg" +{ + GROUPBOX "", IDC_GROUP, 5, 5, 210, 90 + CONTROL "", IDC_SENSITIVITY, TRACKBAR_CLASS, WS_TABSTOP | TBS_AUTOTICKS, 10, 75, 200, 15 + LTEXT "Device", IDC_STATIC, 15, 15, 24, 8, SS_LEFT + COMBOBOX IDC_DEVICE, 15, 25, 135, 30, WS_GROUP | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS + COMBOBOX IDC_CONTROL, 15, 50, 135, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS + LTEXT "Sensitivity", IDC_STATIC, 15, 65, 32, 8, SS_LEFT + LTEXT "Control", IDC_STATIC, 15, 40, 23, 8, SS_LEFT + CONTROL "", IDC_POSITION, "spinner", 0x50020000, 160, 15, 50, 50, 0x02002000 +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_BIND DIALOG 0, 0, 200, 64 +STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP +CAPTION "Bind action (ESC to cancel)" +FONT 8, "Ms Shell Dlg" +{ + CTEXT "Static", IDC_ACTION_NAME, 5, 25, 190, 8, SS_CENTER +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_BIND_ADV DIALOG 0, 0, 242, 175 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "Binding Editor" +FONT 8, "Ms Shell Dlg" +{ + DEFPUSHBUTTON "OK", IDOK, 130, 155, 50, 14 + PUSHBUTTON "Cancel", IDCANCEL, 185, 155, 50, 14 + LTEXT "Device", IDC_STATIC, 14, 14, 24, 8, SS_LEFT + LTEXT "Control", IDC_STATIC, 14, 34, 23, 8, SS_LEFT + GROUPBOX "Raw Control Value", IDC_STATIC, 5, 55, 230, 95 + LTEXT "Limits", IDC_STATIC, 14, 88, 18, 8, SS_LEFT + LTEXT "Binding", IDC_STATIC, 14, 108, 24, 8, SS_LEFT + LTEXT "Current Value", IDC_STATIC, 14, 128, 44, 8, SS_LEFT + LTEXT "Minimum", IDC_STATIC, 80, 70, 28, 8, SS_LEFT + LTEXT "Maximum", IDC_STATIC, 160, 70, 30, 8, SS_LEFT + COMBOBOX IDC_DEVICE, 75, 10, 150, 30, WS_GROUP | WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_HASSTRINGS + COMBOBOX IDC_CONTROL, 75, 30, 150, 20, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_HASSTRINGS + EDITTEXT IDC_LIMIT_MIN, 75, 85, 70, 14, NOT WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_LIMIT_MAX, 155, 85, 70, 14, NOT WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_BINDING_MIN, 75, 105, 70, 14, ES_AUTOHSCROLL | ES_NUMBER + EDITTEXT IDC_BINDING_MAX, 155, 105, 70, 14, ES_AUTOHSCROLL | ES_NUMBER + EDITTEXT IDC_CURRENT, 75, 125, 150, 14, NOT WS_TABSTOP | ES_CENTER | ES_AUTOHSCROLL | ES_READONLY +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_BIND_LIGHT DIALOG 0, 0, 242, 76 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "Light Binding" +FONT 8, "Ms Shell Dlg" +{ + DEFPUSHBUTTON "OK", IDOK, 130, 55, 50, 14 + PUSHBUTTON "Cancel", IDCANCEL, 185, 55, 50, 14 + LTEXT "Game Light", IDC_STATIC, 14, 34, 37, 8, SS_LEFT + COMBOBOX IDC_GAME_LIGHT, 75, 30, 160, 30, WS_GROUP | WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_HASSTRINGS + LTEXT "HID Light", IDC_STATIC, 14, 14, 31, 8, SS_LEFT + EDITTEXT IDC_LIGHT, 75, 10, 160, 14, ES_AUTOHSCROLL | ES_READONLY +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_GAME_TYPE DIALOG 0, 0, 205, 70 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "Game series" +FONT 8, "Ms Shell Dlg" +{ + DEFPUSHBUTTON "OK", IDOK, 95, 50, 50, 14 + PUSHBUTTON "Cancel", IDCANCEL, 150, 50, 50, 14 + COMBOBOX IDC_GAME_TYPE, 5, 20, 195, 30, WS_GROUP | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS + LTEXT "Select game series:", IDC_STATIC, 5, 10, 62, 8, SS_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_READER DIALOG 0, 0, 220, 90 +STYLE DS_3DLOOK | DS_CENTER | DS_CONTROL | DS_SHELLFONT | WS_VISIBLE | WS_CHILDWINDOW | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "Ms Shell Dlg" +{ + LTEXT "Keyboard device", IDC_STATIC, 15, 20, 54, 8, SS_LEFT + COMBOBOX IDC_KBD_DEVICE, 15, 30, 135, 12, WS_GROUP | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS + LTEXT "Card ID file", IDC_STATIC, 15, 50, 36, 8, SS_LEFT + EDITTEXT IDC_CARD_PATH, 15, 60, 135, 14, ES_AUTOHSCROLL + PUSHBUTTON "&Browse...", IDC_BROWSE, 155, 60, 50, 14 + GROUPBOX "", IDC_GROUP, 5, 5, 210, 80 + LTEXT "Keypad status", IDC_STATIC, 155, 20, 46, 8, SS_LEFT + LTEXT "", IDC_KEYPAD_STATE, 160, 30, 45, 12, SS_LEFT | SS_CENTERIMAGE +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_TAB_ANALOGS DIALOG 0, 0, 220, 215 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "Analogs" +FONT 8, "Ms Shell Dlg" +{ + LTEXT "", IDC_STATIC, 0, 0, 8, 8, SS_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_TAB_BUTTONS DIALOG 0, 0, 220, 215 +STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_CHILDWINDOW | WS_SYSMENU +CAPTION "Buttons" +FONT 8, "Ms Shell Dlg" +{ + CONTROL "Page XX / XX", IDC_PAGE_TEXT, WC_STATIC, NOT WS_GROUP | SS_SIMPLE, 35, 7, 45, 8, WS_EX_LEFT + CONTROL "", IDC_BINDINGS, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_SINGLESEL | LVS_REPORT, 5, 20, 210, 170, WS_EX_LEFT + PUSHBUTTON "&Bind...", IDC_BINDING_EDIT, 5, 195, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Bind &many...", IDC_BINDING_EDIT_MANY, 58, 195, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "&Edit...", IDC_BINDING_ADV, 111, 195, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "&Clear", IDC_BINDING_CLEAR, 165, 195, 50, 14, 0, WS_EX_LEFT + CONTROL "", IDC_PAGE, UPDOWN_CLASS, WS_TABSTOP | UDS_ARROWKEYS | UDS_HORZ, 5, 5, 25, 11, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_TAB_LIGHTS DIALOG 0, 0, 220, 215 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "Lights" +FONT 8, "Ms Shell Dlg" +{ + LTEXT "Device", IDC_STATIC, 5, 5, 24, 8, SS_LEFT + COMBOBOX IDC_DEVICE, 5, 15, 210, 30, CBS_DROPDOWNLIST | CBS_HASSTRINGS + LTEXT "Lights", IDC_STATIC, 5, 33, 20, 8, SS_LEFT + CONTROL "", IDC_LIGHT, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_SINGLESEL | LVS_REPORT, 5, 45, 210, 145 + CTEXT "WARNING: Clicking on a HID light will cause it to illuminate.", IDC_STATIC, 5, 200, 210, 8, SS_CENTER +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_TAB_NETWORK DIALOG 0, 0, 220, 215 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "Network" +FONT 8, "Ms Shell Dlg" +{ + LTEXT "", IDC_STATIC, 0, 0, 8, 8, SS_LEFT + AUTOCHECKBOX "Generate card ID files on insertion if they do not already exist", IDC_AUTOGEN, 5, 200, 206, 8 + AUTOCHECKBOX "Use top keyboard row for PIN pad input", IDC_ALT_10K, 5, 185, 141, 8 +} + + + +// +// String Table resources +// +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +STRINGTABLE +{ + IDS_APPNAME "BemaniPC Input Configuration" + IDS_BAD_GAME_TYPE "Unknown game type: %S" + IDS_PAGE "Page %02d / %02d" + IDS_COL_BUTTON "Button" + IDS_COL_ACTION "Action" + IDS_NOT_PRESENT "" + IDS_READER_P1 "P1 Card Reader" + IDS_READER_P2 "P2 Card Reader" + IDS_MUXED_TITLE "Notice" + IDS_MUXED_MSG "You have bound both card readers to the same keypad device. As a result, the Num Lock key will be used to select the active card reader: when Num Lock is off, P1 will receive input, and when Num Lock is on, P2 will receive input.\n\nAre you sure you want to use these settings?" + IDS_COL_LIGHT_HID "HID Light" + IDS_COL_LIGHT_GAME "Game Light" + IDS_GENERIC_TEST "Test" + IDS_GENERIC_SERVICE "Service" + IDS_IIDX_SCHEMA "beatmania IIDX" + IDS_IIDX_P1_1 "P1 1" + IDS_IIDX_P1_2 "P1 2" + IDS_IIDX_P1_3 "P1 3" + IDS_IIDX_P1_4 "P1 4" + IDS_IIDX_P1_5 "P1 5" + IDS_IIDX_P1_6 "P1 6" + IDS_IIDX_P1_7 "P1 7" + IDS_IIDX_P2_1 "P2 1" + IDS_IIDX_P2_2 "P2 2" + IDS_IIDX_P2_3 "P2 3" + IDS_IIDX_P2_4 "P2 4" + IDS_IIDX_P2_5 "P2 5" + IDS_IIDX_P2_6 "P2 6" + IDS_IIDX_P2_7 "P2 7" + IDS_IIDX_P1_START "P1 Start" + IDS_IIDX_P2_START "P2 Start" + IDS_IIDX_VEFX "VEFX" + IDS_IIDX_EFFECT "Effect" + IDS_IIDX_P1_TT_UP "P1 TT +" + IDS_IIDX_P1_TT_DOWN "P1 TT -" + IDS_IIDX_P1_TT_STAB "P1 TT +/-" + IDS_IIDX_P2_TT_UP "P2 TT +" + IDS_IIDX_P2_TT_DOWN "P2 TT -" + IDS_IIDX_P2_TT_STAB "P2 TT +/-" + IDS_IIDX_P1_TT "P1 Turntable" + IDS_IIDX_P2_TT "P2 Turntable" + IDS_IIDX_SPOT_1_LIGHT "Spot Light #1" + IDS_IIDX_SPOT_2_LIGHT "Spot Light #2" + IDS_IIDX_SPOT_3_LIGHT "Spot Light #3" + IDS_IIDX_SPOT_4_LIGHT "Spot Light #4" + IDS_IIDX_SPOT_5_LIGHT "Spot Light #5" + IDS_IIDX_SPOT_6_LIGHT "Spot Light #6" + IDS_IIDX_SPOT_7_LIGHT "Spot Light #7" + IDS_IIDX_SPOT_8_LIGHT "Spot Light #8" + IDS_IIDX_NEON_LIGHT "Top Neons" + IDS_DDR_SCHEMA "Dance Dance Revolution" + IDS_DDR_P1_START "P1 Start" + IDS_DDR_P1_MENU_UP "P1 Menu Up" + IDS_DDR_P1_MENU_DOWN "P1 Menu Down" + IDS_DDR_P1_MENU_LEFT "P1 Menu Left" + IDS_DDR_P1_MENU_RIGHT "P1 Menu Right" + IDS_DDR_P1_UP "P1 Up" + IDS_DDR_P1_DOWN "P1 Down" + IDS_DDR_P1_LEFT "P1 Left" + IDS_DDR_P1_RIGHT "P1 Right" + IDS_DDR_P2_START "P2 Start" + IDS_DDR_P2_MENU_UP "P2 Menu Up" + IDS_DDR_P2_MENU_DOWN "P2 Menu Down" + IDS_DDR_P2_MENU_LEFT "P2 Menu Left" + IDS_DDR_P2_MENU_RIGHT "P2 Menu Right" + IDS_DDR_P2_UP "P2 Up" + IDS_DDR_P2_DOWN "P2 Down" + IDS_DDR_P2_LEFT "P2 Left" + IDS_DDR_P2_RIGHT "P2 Right" + IDS_DDR_P1_MENU_LIGHT "P1 Menu Lights" + IDS_DDR_P1_TOP_LIGHT "P1 Marquee Top Light" + IDS_DDR_P1_BOTTOM_LIGHT "P1 Marquee Bottom Light" + IDS_DDR_P2_MENU_LIGHT "P2 Menu Lights" + IDS_DDR_P2_TOP_LIGHT "P2 Marquee Top Light" + IDS_DDR_P2_BOTTOM_LIGHT "P2 Marquee Bottom Light" + IDS_DDR_BASS_LIGHT "Bass Neons" + IDS_PNM_SCHEMA "pop'n music" + IDS_PNM_BTN1 "Button 1" + IDS_PNM_BTN2 "Button 2" + IDS_PNM_BTN3 "Button 3" + IDS_PNM_BTN4 "Button 4" + IDS_PNM_BTN5 "Button 5" + IDS_PNM_BTN6 "Button 6" + IDS_PNM_BTN7 "Button 7" + IDS_PNM_BTN8 "Button 8" + IDS_PNM_BTN9 "Button 9" + IDS_JB_SCHEMA "Jubeat" + IDS_JB_PANEL1 "Panel 1" + IDS_JB_PANEL2 "Panel 2" + IDS_JB_PANEL3 "Panel 3" + IDS_JB_PANEL4 "Panel 4" + IDS_JB_PANEL5 "Panel 5" + IDS_JB_PANEL6 "Panel 6" + IDS_JB_PANEL7 "Panel 7" + IDS_JB_PANEL8 "Panel 8" + IDS_JB_PANEL9 "Panel 9" + IDS_JB_PANEL10 "Panel 10" + IDS_JB_PANEL11 "Panel 11" + IDS_JB_PANEL12 "Panel 12" + IDS_JB_PANEL13 "Panel 13" + IDS_JB_PANEL14 "Panel 14" + IDS_JB_PANEL15 "Panel 15" + IDS_JB_PANEL16 "Panel 16" + IDS_JB_RGB_FRONT_R "RGB Front R" + IDS_JB_RGB_FRONT_G "RGB Front G" + IDS_JB_RGB_FRONT_B "RGB Front B" + IDS_JB_RGB_TOP_R "RGB Top R" + IDS_JB_RGB_TOP_G "RGB Top G" + IDS_JB_RGB_TOP_B "RGB Top B" + IDS_JB_RGB_LEFT_R "RGB Left R" + IDS_JB_RGB_LEFT_G "RGB Left G" + IDS_JB_RGB_LEFT_B "RGB Left B" + IDS_JB_RGB_RIGHT_R "RGB Right R" + IDS_JB_RGB_RIGHT_G "RGB Right G" + IDS_JB_RGB_RIGHT_B "RGB Right B" + IDS_JB_RGB_TITLE_R "RGB Title R" + IDS_JB_RGB_TITLE_G "RGB Title G" + IDS_JB_RGB_TITLE_B "RGB Title B" + IDS_JB_RGB_WOOFER_R "RGB Woofer R" + IDS_JB_RGB_WOOFER_G "RGB Woofer G" + IDS_JB_RGB_WOOFER_B "RGB Woofer B" + IDS_SDVX_SCHEMA "Sound Voltex" + IDS_SDVX_START "Start" + IDS_SDVX_BTN_A "Button A" + IDS_SDVX_BTN_B "Button B" + IDS_SDVX_BTN_C "Button C" + IDS_SDVX_BTN_D "Button D" + IDS_SDVX_FX_L "FX-L" + IDS_SDVX_FX_R "FX-R" + IDS_SDVX_VOL_L "VOL-L" + IDS_SDVX_VOL_R "VOL-R" + IDS_SDVX_RGB1_R "Wing Left Up R" + IDS_SDVX_RGB1_G "Wing Left Up G" + IDS_SDVX_RGB1_B "Wing Left Up B" + IDS_SDVX_RGB2_R "Wing Right Up R" + IDS_SDVX_RGB2_G "Wing Right Up G" + IDS_SDVX_RGB2_B "Wing Right Up B" + IDS_SDVX_RGB3_R "Wing Left Low R" + IDS_SDVX_RGB3_G "Wing Left Low G" + IDS_SDVX_RGB3_B "Wing Left Low B" + IDS_SDVX_RGB4_R "Wing Right Low R" + IDS_SDVX_RGB4_G "Wing Right Low G" + IDS_SDVX_RGB4_B "Wing Right Low B" + IDS_SDVX_RGB5_R "Woofer R" + IDS_SDVX_RGB5_G "Woofer G" + IDS_SDVX_RGB5_B "Woofer B" + IDS_SDVX_RGB6_R "Controller R" + IDS_SDVX_RGB6_G "Controller G" + IDS_SDVX_RGB6_B "Controller B" + IDS_DM_SCHEMA "Drummania" + IDS_DM_START "Start" + IDS_DM_MENU_LEFT "Menu Left" + IDS_DM_MENU_RIGHT "Menu Right" + IDS_DM_HI_HAT "Hi-Hat" + IDS_DM_SNARE "Snare" + IDS_DM_HIGH_TOM "High Tom" + IDS_DM_LOW_TOM "Low Tom" + IDS_DM_CYMBAL "Cymbal" + IDS_DM_BASS "Bass Drum" + IDS_DM_SPEAKER_LIGHT "Speakers" + IDS_DM_SPOT_LIGHT "Spot Light" + IDS_DM_START_LIGHT "Start" + IDS_DM_MENU_LIGHT "Menu Buttons" + IDS_GF_SCHEMA "Guitar Freaks" + IDS_GF_P1_START "P1 Start" + IDS_GF_P1_PICK "P1 Pick" + IDS_GF_P1_PICK_A "P1 Pick A (Guitar Hero compatible)" + IDS_GF_P1_PICK_B "P1 Pick B (Guitar Hero compatible)" + IDS_GF_P1_WAIL "P1 Wail" + IDS_GF_P1_EFFECT "P1 Effect" + IDS_GF_P1_RED "P1 Red" + IDS_GF_P1_GREEN "P1 Green" + IDS_GF_P1_BLUE "P1 Blue" + IDS_GF_P2_START "P2 Start" + IDS_GF_P2_PICK "P2 Pick" + IDS_GF_P2_PICK_A "P2 Pick A (Guitar Hero compatible)" + IDS_GF_P2_PICK_B "P2 Pick B (Guitar Hero compatible)" + IDS_GF_P2_WAIL "P2 Wail" + IDS_GF_P2_EFFECT "P2 Effect" + IDS_GF_P2_RED "P2 Red" + IDS_GF_P2_GREEN "P2 Green" + IDS_GF_P2_BLUE "P2 Blue" + IDS_GF_P1_SPOT_LIGHT "P1 Spotlight" + IDS_GF_P2_SPOT_LIGHT "P2 Spotlight" + IDS_RB_SCHEMA "Reflec Beat" + IDS_BST_SCHEMA "BeatStream" + IDS_IIDX_PANEL_SCHEMA "beatmania IIDX Effector Panel" + IDS_IIDX_PANEL_S1_UP "Effector Slider 1 Up" + IDS_IIDX_PANEL_S1_DOWN "Effector Slider 1 Down" + IDS_IIDX_PANEL_S2_UP "Effector Slider 2 Up" + IDS_IIDX_PANEL_S2_DOWN "Effector Slider 2 Down" + IDS_IIDX_PANEL_S3_UP "Effector Slider 3 Up" + IDS_IIDX_PANEL_S3_DOWN "Effector Slider 3 Down" + IDS_IIDX_PANEL_S4_UP "Effector Slider 4 Up" + IDS_IIDX_PANEL_S4_DOWN "Effector Slider 4 Down" + IDS_IIDX_PANEL_S5_UP "Effector Slider 5 Up" + IDS_IIDX_PANEL_S5_DOWN "Effector Slider 5 Down" + IDS_IIDX_PANEL_S1 "Effector Slider 1" + IDS_IIDX_PANEL_S2 "Effector Slider 2" + IDS_IIDX_PANEL_S3 "Effector Slider 3" + IDS_IIDX_PANEL_S4 "Effector Slider 4" + IDS_IIDX_PANEL_S5 "Effector Slider 5" +} diff --git a/src/main/config/eam.c b/src/main/config/eam.c new file mode 100644 index 0000000..f134b4c --- /dev/null +++ b/src/main/config/eam.c @@ -0,0 +1,489 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "config/resource.h" +#include "config/schema.h" + +#include "eamio/eam-config.h" + +#include "geninput/hid-mgr.h" +#include "geninput/input-config.h" +#include "geninput/kbd.h" + +#include "util/array.h" +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +/* You may or may not have noticed that there is a blank label in the Network + tab's dialog resource definition. This is a consequence of the way that + Windows UI styles work. WIN32K's (i.e. raw USER32's) GUI routines draw + widgets using the Windows 95 chunky bezels and flat surfaces style, but if + your app pulls in COMMCTRL v6 or later using an SxS manifest, then it + overrides the built-in Win32 widgets with its own glossy versions, in + addition to the extended control library that its API provides. + + I'm guessing that top-level windows and dialogs (the tab is a dialog) get + subclassed as soon as one of these glossified built-ins gets WM_CREATEd + within a window. Hence the presence of the Static control to make sure this + takes place. + + Otherwise, you get a flat grey shaded tab body, which looks wrong on Vista + and above. */ + +struct eam_ui { + struct array children; +}; + +struct eam_unit_ui { + const struct eam_unit_def *def; + struct array devs; +}; + +static INT_PTR CALLBACK eam_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static INT_PTR eam_ui_handle_init(HWND hwnd, const PROPSHEETPAGE *psp); +static INT_PTR eam_ui_handle_activate(HWND hwnd); +static INT_PTR eam_ui_handle_passivate(HWND hwnd); +static INT_PTR eam_ui_handle_tick(HWND hwnd); +static INT_PTR eam_ui_handle_change_alt_10k(HWND hwnd); +static INT_PTR eam_ui_handle_change_autogen(HWND hwnd); +static INT_PTR eam_ui_handle_fini(HWND hwnd); + +static INT_PTR CALLBACK eam_unit_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static INT_PTR eam_unit_ui_handle_init(HWND hwnd, + const struct eam_unit_def *def); +static void eam_unit_ui_handle_init_devs(HWND hwnd); +static void eam_unit_ui_handle_init_path(HWND hwnd); +static INT_PTR eam_unit_ui_handle_browse(HWND hwnd); +static INT_PTR eam_unit_ui_handle_change_device(HWND hwnd); +static INT_PTR eam_unit_ui_handle_tick(HWND hwnd); +static INT_PTR eam_unit_ui_handle_fini(HWND hwnd); + +static const struct eam_io_config_api *eam_io_config_api; + +HPROPSHEETPAGE eam_ui_tab_create(HINSTANCE inst, const struct schema *schema, + const struct eam_io_config_api *api) +{ + PROPSHEETPAGE psp; + + eam_io_config_api = api; + + memset(&psp, 0, sizeof(psp)); + psp.dwSize = sizeof(psp); + psp.dwFlags = PSP_DEFAULT; + psp.hInstance = inst; + psp.pszTemplate = MAKEINTRESOURCE(IDD_TAB_NETWORK); + psp.pfnDlgProc = eam_ui_dlg_proc; + psp.lParam = (LPARAM) schema; + + return CreatePropertySheetPage(&psp); +} + +static INT_PTR CALLBACK eam_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + const NMHDR *n; + + switch (msg) { + case WM_INITDIALOG: + return eam_ui_handle_init(hwnd, (PROPSHEETPAGE *) lparam); + + case WM_TIMER: + return eam_ui_handle_tick(hwnd); + + case WM_COMMAND: + switch (HIWORD(wparam)) { + case BN_CLICKED: + switch (LOWORD(wparam)) { + case IDC_AUTOGEN: + return eam_ui_handle_change_autogen(hwnd); + + case IDC_ALT_10K: + return eam_ui_handle_change_alt_10k(hwnd); + + default: + return FALSE; + } + + default: + return FALSE; + } + + case WM_NOTIFY: + n = (NMHDR *) lparam; + + switch (n->code) { + case PSN_SETACTIVE: + return eam_ui_handle_activate(hwnd); + + case PSN_KILLACTIVE: + return eam_ui_handle_passivate(hwnd); + } + + return FALSE; + + case WM_DESTROY: + return eam_ui_handle_fini(hwnd); + + default: + return FALSE; + } +} + +static INT_PTR eam_ui_handle_init(HWND hwnd, const PROPSHEETPAGE *psp) +{ + struct eam_ui *ui; + const struct schema *schema; + long ypos; + size_t i; + HINSTANCE inst; + HWND child; + RECT r; + + ui = xmalloc(sizeof(*ui)); + array_init(&ui->children); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM) ui); + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + schema = (struct schema *) psp->lParam; + + ypos = 0; + + for (i = 0 ; i < schema->nunits ; i++) { + child = CreateDialogParam(inst, MAKEINTRESOURCE(IDD_READER), hwnd, + eam_unit_ui_dlg_proc, (LPARAM) &schema->units[i]); + + GetWindowRect(child, &r); + SetWindowPos(child, HWND_BOTTOM, 0, ypos, 0, 0, + SWP_NOSIZE | SWP_SHOWWINDOW); + + ypos += r.bottom - r.top; + + *array_append(HWND, &ui->children) = child; + } + + if (eam_io_config_api->get_autogen()) { + SendMessage(GetDlgItem(hwnd, IDC_AUTOGEN), BM_SETCHECK, BST_CHECKED, 0); + } + + if (eam_io_config_api->get_alt_10k()) { + SendMessage(GetDlgItem(hwnd, IDC_ALT_10K), BM_SETCHECK, BST_CHECKED, 0); + } + + return TRUE; +} + +static INT_PTR eam_ui_handle_activate(HWND hwnd) +{ + SetTimer(hwnd, 17, 1, NULL); + + return TRUE; +} + +static INT_PTR eam_ui_handle_passivate(HWND hwnd) +{ + KillTimer(hwnd, 1); + + return TRUE; +} + +static INT_PTR eam_ui_handle_tick(HWND hwnd) +{ + HWND child; + struct eam_ui *ui; + size_t i; + + ui = (struct eam_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + mapper_update(); + + for (i = 0 ; i < ui->children.nitems ; i++) { + child = *array_item(HWND, &ui->children, i); + SendMessage(child, WM_USER, 0, 0); + } + + return TRUE; +} + +static INT_PTR eam_ui_handle_change_alt_10k(HWND hwnd) +{ + HWND btn; + bool alt_10k; + + btn = GetDlgItem(hwnd, IDC_ALT_10K); + alt_10k = SendMessage(btn, BM_GETCHECK, 0, 0) == BST_CHECKED; + + eam_io_config_api->set_alt_10k(alt_10k); + + return TRUE; +} + +static INT_PTR eam_ui_handle_change_autogen(HWND hwnd) +{ + HWND btn; + bool autogen; + + btn = GetDlgItem(hwnd, IDC_AUTOGEN); + autogen = SendMessage(btn, BM_GETCHECK, 0, 0) == BST_CHECKED; + + eam_io_config_api->set_autogen(autogen); + + return TRUE; +} + +static INT_PTR eam_ui_handle_fini(HWND hwnd) +{ + struct eam_ui *ui; + + ui = (struct eam_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + array_fini(&ui->children); + free(ui); + + return TRUE; +} + +static INT_PTR CALLBACK eam_unit_ui_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_INITDIALOG: + return eam_unit_ui_handle_init(hwnd, + (struct eam_unit_def *) lparam); + + case WM_USER: + return eam_unit_ui_handle_tick(hwnd); + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case IDC_KBD_DEVICE: + switch (HIWORD(wparam)) { + case CBN_SELCHANGE: + return eam_unit_ui_handle_change_device(hwnd); + + default: + return FALSE; + } + + case IDC_BROWSE: + switch (HIWORD(wparam)) { + case BN_CLICKED: + return eam_unit_ui_handle_browse(hwnd); + + default: + return FALSE; + } + + default: + return FALSE; + } + + case WM_DESTROY: + return eam_unit_ui_handle_fini(hwnd); + + default: + return FALSE; + } +} + +static INT_PTR eam_unit_ui_handle_init(HWND hwnd, + const struct eam_unit_def *def) +{ + struct eam_unit_ui *ui; + wchar_t str[128]; + HINSTANCE inst; + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + + LoadString(inst, def->label_rsrc, str, lengthof(str)); + SetWindowText(GetDlgItem(hwnd, IDC_GROUP), str); + + ui = xmalloc(sizeof(*ui)); + ui->def = def; + array_init(&ui->devs); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM) ui); + + eam_unit_ui_handle_init_devs(hwnd); + eam_unit_ui_handle_init_path(hwnd); + + return TRUE; +} + +static void eam_unit_ui_handle_init_devs(HWND hwnd) +{ + struct eam_unit_ui *ui; + uint32_t dev_usage; + wchar_t *dev_name; + struct hid_stub *hid; + size_t nchars; + LRESULT index; + HWND dev_list; + + ui = (struct eam_unit_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + dev_list = GetDlgItem(hwnd, IDC_KBD_DEVICE); + + *array_append(struct hid_stub *, &ui->devs) = NULL; + SendMessage(dev_list, CB_ADDSTRING, 0, (LPARAM) TEXT("")); + + hid_mgr_lock(); + + for (hid = hid_mgr_get_first_stub() + ; hid != NULL + ; hid = hid_mgr_get_next_stub(hid)) { + if (!hid_stub_get_device_usage(hid, &dev_usage)) { + continue; + } + + if (dev_usage != KBD_DEVICE_USAGE_KEYBOARD + && dev_usage != KBD_DEVICE_USAGE_KEYPAD) { + continue; + } + + if (!hid_stub_get_name(hid, NULL, &nchars)) { + continue; + } + + dev_name = xmalloc(nchars * sizeof(*dev_name)); + + if (!hid_stub_get_name(hid, dev_name, &nchars)) { + free(dev_name); + + continue; + } + + index = SendMessage(dev_list, CB_ADDSTRING, 0, (LPARAM) dev_name); + + free(dev_name); + + *array_append(struct hid_stub *, &ui->devs) = hid; + + if (hid == eam_io_config_api->get_keypad_device(ui->def->unit_no)) { + SendMessage(dev_list, CB_SETCURSEL, index, 0); + } + } + + hid_mgr_unlock(); +} + +static void eam_unit_ui_handle_init_path(HWND hwnd) +{ + struct eam_unit_ui *ui; + const char *path; + wchar_t *wpath; + + ui = (struct eam_unit_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + path = eam_io_config_api->get_card_path(ui->def->unit_no); + + if (path != NULL) { + wpath = str_widen(path); + SetWindowText(GetDlgItem(hwnd, IDC_CARD_PATH), wpath); + free(wpath); + } +} + +static INT_PTR eam_unit_ui_handle_browse(HWND hwnd) +{ + struct eam_unit_ui *ui; + char *path_tmp; + OPENFILENAME ofn; + TCHAR path[MAX_PATH]; + HWND path_ctl; + + ui = (struct eam_unit_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + path_ctl = GetDlgItem(hwnd, IDC_CARD_PATH); + + path[0] = 0; + GetWindowText(path_ctl, path, lengthof(path)); + + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = hwnd; + ofn.lpstrFile = path; + ofn.nMaxFile = lengthof(path); + ofn.Flags = OFN_DONTADDTORECENT | OFN_ENABLESIZING | OFN_EXPLORER; + + if (GetOpenFileName(&ofn) && wstr_narrow(path, &path_tmp)) { + eam_io_config_api->set_card_path(ui->def->unit_no, path_tmp); + free(path_tmp); + + SendMessage(path_ctl, WM_SETTEXT, 0, (LPARAM) path); + } + + return TRUE; +} + +static INT_PTR eam_unit_ui_handle_change_device(HWND hwnd) +{ + struct eam_unit_ui *ui; + struct hid_stub *hid; + HWND dev_list; + LRESULT index; + + ui = (struct eam_unit_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + dev_list = GetDlgItem(hwnd, IDC_KBD_DEVICE); + + index = SendMessage(dev_list, CB_GETCURSEL, 0, 0); + + if (index != CB_ERR) { + log_assert((size_t) index < ui->devs.nitems); + + hid = *array_item(struct hid_stub *, &ui->devs, index); + eam_io_config_api->set_keypad_device(ui->def->unit_no, hid); + } + + return TRUE; +} + +static INT_PTR eam_unit_ui_handle_tick(HWND hwnd) +{ + struct eam_unit_ui *ui; + uint16_t state; + wchar_t state_str[9]; + + ui = (struct eam_unit_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + state = eam_io_get_keypad_state(ui->def->unit_no); + + wstr_format(state_str, lengthof(state_str), L"%04x", state); + + SetWindowText(GetDlgItem(hwnd, IDC_KEYPAD_STATE), state_str); + + return TRUE; +} + +static INT_PTR eam_unit_ui_handle_fini(HWND hwnd) +{ + struct eam_unit_ui *ui; + wchar_t path[MAX_PATH]; + char *tmp; + + ui = (struct eam_unit_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + path[0] = 0; + GetWindowText(GetDlgItem(hwnd, IDC_CARD_PATH), path, lengthof(path)); + + if (path[0]) { + if (wstr_narrow(path, &tmp)) { + eam_io_config_api->set_card_path(ui->def->unit_no, tmp); + free(tmp); + } + } else { + eam_io_config_api->set_card_path(ui->def->unit_no, NULL); + } + + array_fini(&ui->devs); + free(ui); + + return TRUE; +} + diff --git a/src/main/config/gametype.c b/src/main/config/gametype.c new file mode 100644 index 0000000..212fdc6 --- /dev/null +++ b/src/main/config/gametype.c @@ -0,0 +1,102 @@ +#include + +#include + +#include "config/gametype.h" +#include "config/resource.h" +#include "config/schema.h" + +#include "util/defs.h" +#include "util/log.h" + +static INT_PTR CALLBACK game_type_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static INT_PTR game_type_handle_init(HWND hwnd); +static INT_PTR game_type_handle_ok(HWND hwnd); +static INT_PTR game_type_handle_cancel(HWND hwnd); + +const struct schema *game_type_from_str(const char *name) +{ + size_t i; + + for (i = 0 ; i < nschemas ; i++) { + if (_stricmp(schemas[i].name, name) == 0) { + return &schemas[i]; + } + } + + return NULL; +} + +const struct schema *game_type_from_dialog(HINSTANCE inst) +{ + return (struct schema *) DialogBoxParam(inst, + MAKEINTRESOURCE(IDD_GAME_TYPE), NULL, game_type_dlg_proc, 0); +} + +static INT_PTR CALLBACK game_type_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_INITDIALOG: + return game_type_handle_init(hwnd); + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case IDOK: + return game_type_handle_ok(hwnd); + + case IDCANCEL: + return game_type_handle_cancel(hwnd); + + default: + return FALSE; + } + + default: + return FALSE; + } +} + +static INT_PTR game_type_handle_init(HWND hwnd) +{ + HINSTANCE inst; + HWND ctl; + size_t i; + wchar_t text[1024]; + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + ctl = GetDlgItem(hwnd, IDC_GAME_TYPE); + + for (i = 0 ; i < nschemas ; i++) { + LoadString(inst, schemas[i].label, text, lengthof(text)); + SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM) text); + } + + SendMessage(ctl, CB_SETCURSEL, 0, 0); + + return TRUE; +} + +static INT_PTR game_type_handle_ok(HWND hwnd) +{ + HWND ctl; + size_t i; + + ctl = GetDlgItem(hwnd, IDC_GAME_TYPE); + i = (size_t) SendMessage(ctl, CB_GETCURSEL, 0, 0); + + log_assert(i < nschemas); + + EndDialog(hwnd, (INT_PTR) &schemas[i]); + + return TRUE; +} + +static INT_PTR game_type_handle_cancel(HWND hwnd) +{ + EndDialog(hwnd, (INT_PTR) NULL); + + return TRUE; +} + diff --git a/src/main/config/gametype.h b/src/main/config/gametype.h new file mode 100644 index 0000000..dff2ad9 --- /dev/null +++ b/src/main/config/gametype.h @@ -0,0 +1,11 @@ +#ifndef CONFIG_GAMETYPE_H +#define CONFIG_GAMETYPE_H + +#include + +#include "config/schema.h" + +const struct schema *game_type_from_str(const char *name); +const struct schema *game_type_from_dialog(HINSTANCE inst); + +#endif diff --git a/src/main/config/lights.c b/src/main/config/lights.c new file mode 100644 index 0000000..c7cb1e9 --- /dev/null +++ b/src/main/config/lights.c @@ -0,0 +1,513 @@ +#include +#include +#include +#include + +#include +#include + +#include "config/bind-light.h" +#include "config/resource.h" +#include "config/schema.h" +#include "config/usages.h" + +#include "geninput/input-config.h" +#include "geninput/mapper.h" + +#include "util/array.h" +#include "util/defs.h" +#include "util/mem.h" + +#define PULSE_DELTA 0.03f + +struct lights_ui { + const struct schema *schema; + struct array devices; + struct hid_stub *hid; + struct hid_light *lights; + size_t nlights; + bool pulse_active; + bool pulse_up; + size_t pulse_light_no; + float pulse_coeff; +}; + +static INT_PTR CALLBACK lights_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static INT_PTR lights_handle_init(HWND hwnd, const PROPSHEETPAGE *psp); +static void lights_handle_init_devices(HWND hwnd); +static void lights_handle_init_device(HWND hwnd, struct hid_stub *hid); +static void lights_handle_init_lights(HWND hwnd); +static INT_PTR lights_handle_change_dev(HWND hwnd); +static INT_PTR lights_handle_highlight_light(HWND hwnd, const NMLISTVIEW *n); +static INT_PTR lights_handle_bind_light(HWND hwnd); +static INT_PTR lights_handle_pulse_tick(HWND hwnd); +static void lights_update_bindings(HWND hwnd); +static void lights_pulse_start(HWND hwnd, size_t light_no); +static void lights_pulse_stop(HWND hwnd); +static INT_PTR lights_handle_fini(HWND hwnd); + +HPROPSHEETPAGE lights_tab_create(HINSTANCE inst, const struct schema *schema) +{ + PROPSHEETPAGE psp; + + memset(&psp, 0, sizeof(psp)); + psp.dwSize = sizeof(psp); + psp.dwFlags = PSP_DEFAULT; + psp.hInstance = inst; + psp.pszTemplate = MAKEINTRESOURCE(IDD_TAB_LIGHTS); + psp.pfnDlgProc = lights_dlg_proc; + psp.lParam = (LPARAM) schema; + + return CreatePropertySheetPage(&psp); +} + +static INT_PTR CALLBACK lights_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + NMHDR *n; + + switch (msg) { + case WM_INITDIALOG: + return lights_handle_init(hwnd, (const PROPSHEETPAGE *) lparam); + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case IDC_DEVICE: + switch (HIWORD(wparam)) { + case CBN_SELCHANGE: + return lights_handle_change_dev(hwnd); + } + + return FALSE; + } + + return FALSE; + + case WM_NOTIFY: + n = (NMHDR *) lparam; + + switch (n->idFrom) { + case IDC_LIGHT: + switch (n->code) { + case LVN_ITEMCHANGED: + return lights_handle_highlight_light(hwnd, + (NMLISTVIEW *) n); + + case NM_DBLCLK: + return lights_handle_bind_light(hwnd); + } + + return FALSE; + } + + return FALSE; + + case WM_TIMER: + return lights_handle_pulse_tick(hwnd); + + case WM_DESTROY: + return lights_handle_fini(hwnd); + } + + return FALSE; +} + +static INT_PTR lights_handle_init(HWND hwnd, const PROPSHEETPAGE *psp) +{ + struct lights_ui *ui; + + ui = xmalloc(sizeof(*ui)); + ui->schema = (const struct schema *) psp->lParam; + ui->hid = NULL; + ui->lights = NULL; + array_init(&ui->devices); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR) ui); + + lights_handle_init_devices(hwnd); + lights_handle_init_lights(hwnd); + + return TRUE; +} + +static void lights_handle_init_devices(HWND hwnd) +{ + struct hid_stub *hid; + + hid_mgr_lock(); + + for (hid = hid_mgr_get_first_stub() + ; hid != NULL + ; hid = hid_mgr_get_next_stub(hid)) { + lights_handle_init_device(hwnd, hid); + } + + hid_mgr_unlock(); +} + +static void lights_handle_init_device(HWND hwnd, struct hid_stub *hid) +{ + wchar_t *chars; + size_t nchars; + size_t nlights; + struct lights_ui *ui; + HWND devs; + + if (!hid_stub_get_lights(hid, NULL, &nlights) || nlights == 0) { + goto lights_fail; + } + + if (!hid_stub_get_name(hid, NULL, &nchars)) { + goto name_sz_fail; + } + + chars = xmalloc(sizeof(*chars) * nchars); + + if (!hid_stub_get_name(hid, chars, &nchars)) { + goto name_fail; + } + + ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + devs = GetDlgItem(hwnd, IDC_DEVICE); + + *array_append(struct hid_stub *, &ui->devices) = hid; + ComboBox_AddString(devs, chars); + + return; + +name_fail: + free(chars); + +name_sz_fail: +lights_fail: + return; +} + +static void lights_handle_init_lights(HWND hwnd) +{ + wchar_t str[128]; + HINSTANCE inst; + LVCOLUMN col; + HWND lights; + + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + lights = GetDlgItem(hwnd, IDC_LIGHT); + + ListView_SetExtendedListViewStyle(lights, + LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); + + LoadString(inst, IDS_COL_LIGHT_HID, str, lengthof(str)); + + col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; + col.fmt = LVCFMT_LEFT; + col.cx = 120; + col.pszText = str; + + ListView_InsertColumn(lights, 0, &col); + + LoadString(inst, IDS_COL_LIGHT_GAME, str, lengthof(str)); + + col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; + col.fmt = LVCFMT_LEFT; + col.cx = 120; + col.pszText = str; + + ListView_InsertColumn(lights, 1, &col); +} + +static INT_PTR lights_handle_change_dev(HWND hwnd) +{ + char chars[256]; + wchar_t wchars[256]; + struct lights_ui *ui; + struct hid_stub *hid; + size_t i; + int dev_idx; + LVITEM item; + HWND devs_ctl; + HWND lights_ctl; + + lights_pulse_stop(hwnd); + + ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + devs_ctl = GetDlgItem(hwnd, IDC_DEVICE); + lights_ctl = GetDlgItem(hwnd, IDC_LIGHT); + dev_idx = ComboBox_GetCurSel(devs_ctl); + + if (dev_idx >= (int) ui->devices.nitems) { + goto unlocked_fail; + } + + ListView_DeleteAllItems(lights_ctl); + + hid = *array_item(struct hid_stub *, &ui->devices, dev_idx); + + hid_mgr_lock(); + + if (!hid_stub_get_lights(hid, NULL, &ui->nlights)) { + goto locked_fail; + } + + ui->lights = xrealloc(ui->lights, sizeof(*ui->lights) * ui->nlights); + + if (!hid_stub_get_lights(hid, ui->lights, &ui->nlights)) { + free(ui->lights); + ui->lights = NULL; + + goto locked_fail; + } + + for (i = 0 ; i < ui->nlights ; i++) { + if(ui->lights[i].name[0] != L'\0') { + item.pszText = ui->lights[i].name; + } else { + wchars[0] = L'\0'; + + usages_get(chars, lengthof(chars), ui->lights[i].usage); + MultiByteToWideChar(CP_UTF8, 0, chars, lengthof(chars), wchars, + lengthof(wchars)); + + item.pszText = wchars; + } + + item.mask = LVIF_TEXT; + item.iItem = (int) i; + item.iSubItem = 0; + + ListView_InsertItem(lights_ctl, &item); + } + + ui->hid = hid; + lights_update_bindings(hwnd); + +locked_fail: + hid_mgr_unlock(); + +unlocked_fail: + return TRUE; +} + +static INT_PTR lights_handle_highlight_light(HWND hwnd, const NMLISTVIEW *n) +{ + size_t light_no; + + light_no = n->iItem; + + if (n->uNewState & LVIS_SELECTED) { + lights_pulse_start(hwnd, light_no); + } + + return TRUE; +} + +static INT_PTR lights_handle_bind_light(HWND hwnd) +{ + struct lights_ui *ui; + struct mapped_light ml; + uint8_t game_light; + bool bound; + HINSTANCE inst; + HWND lights_ctl; + int pos; + + ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + lights_ctl = GetDlgItem(hwnd, IDC_LIGHT); + + pos = ListView_GetSelectionMark(lights_ctl); + + if (pos < 0) { + return TRUE; + } + + ml.hid = ui->hid; + ml.light_no = pos; + + if (bind_light(inst, hwnd, ui->schema, &ml, &game_light, &bound)) { + if (bound) { + mapper_set_light_map(&ml, game_light); + } else { + mapper_clear_light_map(&ml); + } + + lights_update_bindings(hwnd); + } + + return TRUE; +} + +static INT_PTR lights_handle_pulse_tick(HWND hwnd) +{ + struct lights_ui *ui; + size_t light_no; + float intensity; + float bias; + float scale; + float tmp; + + ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + if (!ui->pulse_active) { + return TRUE; + } + + light_no = ui->pulse_light_no; + + if (ui->hid == NULL || light_no >= ui->nlights) { + return TRUE; + } + + if (ui->pulse_up) { + tmp = ui->pulse_coeff + PULSE_DELTA; + + if (tmp > 1.0f) { + ui->pulse_coeff = 1.0f; + ui->pulse_up = false; + } else { + ui->pulse_coeff = tmp; + } + } else { + tmp = ui->pulse_coeff - PULSE_DELTA; + + if (tmp < 0.0f) { + ui->pulse_coeff = 0.0f; + ui->pulse_up = true; + } else { + ui->pulse_coeff = tmp; + } + } + + bias = (float) (ui->lights[light_no].value_min); + scale = (float) (ui->lights[light_no].value_max + - ui->lights[light_no].value_min); + + /* Intensity perception is non-linear. Pulse quadratically. */ + + intensity = bias + scale * ui->pulse_coeff * ui->pulse_coeff + 0.5f; + + hid_mgr_lock(); + hid_stub_set_light(ui->hid, light_no, (int32_t) intensity); + hid_mgr_unlock(); + + return TRUE; +} + +static void lights_update_bindings(HWND hwnd) +{ + light_iter_t pos; + struct mapped_light ml; + uint8_t game_light; + size_t i; + wchar_t wchars[256]; + struct lights_ui *ui; + HINSTANCE inst; + HWND lights_ctl; + + ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + inst = (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + lights_ctl = GetDlgItem(hwnd, IDC_LIGHT); + + for (i = 0 ; i < ui->nlights ; i++) { + ListView_SetItemText(lights_ctl, i, 1, NULL); + } + + for (pos = mapper_iterate_lights() + ; light_iter_is_valid(pos) + ; light_iter_next(pos)) { + light_iter_get_mapping(pos, &ml); + + if (ml.hid != ui->hid) { + continue; + } + + if (ml.light_no >= ui->nlights) { + /* Shouldn't happen unless the HID report descriptor has changed + since we last configured this device */ + + continue; + } + + game_light = light_iter_get_game_light(pos); + + for (i = 0 ; i < ui->schema->nlights ; i++) { + if (ui->schema->lights[i].bit == game_light) { + LoadString(inst, ui->schema->lights[i].name_rsrc, wchars, + lengthof(wchars)); + ListView_SetItemText(lights_ctl, ml.light_no, 1, wchars); + } + } + } + + light_iter_free(pos); +} + +static void lights_pulse_start(HWND hwnd, size_t light_no) +{ + struct lights_ui *ui; + int32_t intensity; + + ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + if (ui->pulse_active) { + lights_pulse_stop(hwnd); + } + + /* Start light at max intensity */ + + intensity = ui->lights[light_no].value_max; + + hid_mgr_lock(); + hid_stub_set_light(ui->hid, light_no, intensity); + hid_mgr_unlock(); + + ui->pulse_active = true; + ui->pulse_up = false; + ui->pulse_light_no = light_no; + ui->pulse_coeff = 1.0f; + + SetTimer(hwnd, 1, 17, NULL); +} + +static void lights_pulse_stop(HWND hwnd) +{ + struct lights_ui *ui; + size_t light_no; + int32_t intensity; + + ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + if (!ui->pulse_active) { + return; + } + + light_no = ui->pulse_light_no; + + if (ui->hid != NULL && light_no < ui->nlights) { + intensity = ui->lights[light_no].value_min; + + hid_mgr_lock(); + hid_stub_set_light(ui->hid, light_no, intensity); + hid_mgr_unlock(); + } + + ui->pulse_active = false; + + KillTimer(hwnd, 1); +} + +static INT_PTR lights_handle_fini(HWND hwnd) +{ + struct lights_ui *ui; + + lights_pulse_stop(hwnd); + + ui = (struct lights_ui *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + + array_fini(&ui->devices); + free(ui->lights); + free(ui); + + return TRUE; +} + diff --git a/src/main/config/main.c b/src/main/config/main.c new file mode 100644 index 0000000..eb91bde --- /dev/null +++ b/src/main/config/main.c @@ -0,0 +1,158 @@ +#include +#include + +#include +#include +#include + +#include "config/gametype.h" +#include "config/resource.h" +#include "config/schema.h" +#include "config/spinner.h" +#include "config/usages.h" + +#include "eamio/eam-config.h" + +#include "geninput/input-config.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" +#include "util/thread.h" +#include "util/winres.h" + +HPROPSHEETPAGE analogs_ui_tab_create(HINSTANCE inst, + const struct schema *schema); +HPROPSHEETPAGE buttons_tab_create(HINSTANCE inst, const struct schema *schema); +HPROPSHEETPAGE lights_tab_create(HINSTANCE inst, const struct schema *schema); +HPROPSHEETPAGE eam_ui_tab_create(HINSTANCE inst, const struct schema *schema, + const struct eam_io_config_api *eam_io_config_api); + +static void my_fatal(const char *module, const char *fmt, ...) +{ + va_list ap; + char buf[1024]; + + va_start(ap, fmt); + str_vformat(buf, sizeof(buf), fmt, ap); + va_end(ap); + + MessageBoxA(NULL, buf, NULL, MB_ICONERROR | MB_OK); + DebugBreak(); + + ExitProcess(-1); +} + +int main(int argc, char **argv) +{ + INITCOMMONCONTROLSEX iccx; + HINSTANCE inst; + HPROPSHEETPAGE psp[4]; + PROPSHEETHEADER psh; + intptr_t result; + const struct eam_io_config_api *eam_io_config_api; + const struct schema *schema; + wchar_t text[1024]; + int max_light; + size_t i; + + inst = GetModuleHandle(NULL); + + log_to_writer(log_writer_debug, NULL); + log_to_external(log_impl_misc, log_impl_info, log_impl_warning, my_fatal); + + usages_init(inst); + + memset(&iccx, 0, sizeof(iccx)); + iccx.dwSize = sizeof(iccx); + iccx.dwICC = ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_WIN95_CLASSES; + + if (!InitCommonControlsEx(&iccx)) { + log_fatal("InitCommonControlsEx failed"); + } + + spinner_init(inst); + + if (argc == 2) { + schema = game_type_from_str(argv[1]); + + if (schema == NULL) { + rswprintf(text, lengthof(text), inst, IDS_BAD_GAME_TYPE, argv[1]); + MessageBox(NULL, text, NULL, MB_ICONERROR | MB_OK); + + return EXIT_FAILURE; + } + } else { + schema = game_type_from_dialog(inst); + + if (schema == NULL) { + return EXIT_FAILURE; + } + } + + input_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + input_init(crt_thread_create, crt_thread_join, crt_thread_destroy); + + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + eam_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy); + eam_io_config_api = eam_io_get_config_api(); + + if (!mapper_config_load(schema->name)) { + log_info("Initializing empty config for %s", schema->name); + + max_light = -1; + + for (i = 0 ; i < schema->nlights ; i++) { + if (max_light < schema->lights[i].bit) { + max_light = schema->lights[i].bit; + } + } + + mapper_set_nlights((uint8_t) (max_light + 1)); + mapper_set_nanalogs((uint8_t) schema->nanalogs); + } + + memset(&psh, 0, sizeof(psh)); + psh.dwSize = sizeof(psh); + psh.dwFlags = PSH_NOAPPLYNOW | PSH_NOCONTEXTHELP | PSH_USEHICON; + psh.hInstance = inst; + psh.hIcon = LoadIcon(NULL, IDI_QUESTION); + psh.pszCaption = MAKEINTRESOURCE(IDS_APPNAME); + psh.phpage = psp; + psh.nPages = 0; + + psp[psh.nPages++] = buttons_tab_create(inst, schema); + psp[psh.nPages++] = lights_tab_create(inst, schema); + + if (schema->nanalogs > 0) { + psp[psh.nPages++] = analogs_ui_tab_create(inst, schema); + } + + if (eam_io_config_api != NULL) { + psp[psh.nPages++] = eam_ui_tab_create(inst, schema, eam_io_config_api); + } + + /* Run GUI */ + + result = PropertySheet(&psh); + + /* Save settings and clean up */ + + if (result > 0) { + if (eam_io_config_api != NULL) { + eam_io_config_api->config_save(); + } + + mapper_config_save(schema->name); + } + + eam_io_fini(); + input_fini(); + spinner_fini(inst); + usages_fini(); + + return EXIT_SUCCESS; +} + diff --git a/src/main/config/resource.h b/src/main/config/resource.h new file mode 100644 index 0000000..de69f6d --- /dev/null +++ b/src/main/config/resource.h @@ -0,0 +1,245 @@ +#ifndef IDC_STATIC +#define IDC_STATIC (-1) +#endif + +#define IDR_USAGES 101 +#define IDD_TAB_BUTTONS 102 +#define IDD_TAB_NETWORK 103 +#define IDD_BIND 104 +#define IDD_READER 105 +#define IDD_GAME_TYPE 107 +#define IDD_ANALOG 109 +#define IDD_TAB_ANALOGS 111 +#define IDD_BIND_ADV 113 +#define IDD_TAB_LIGHTS 115 +#define IDD_BIND_LIGHT 117 +#define IDC_GAME_LIGHT 1000 +#define IDC_KBD_DEVICE 1000 +#define IDC_AUTOGEN 1001 +#define IDC_CARD_PATH 1001 +#define IDC_LIGHT 1001 +#define IDC_SENSITIVITY 1001 +#define IDC_ACTION_NAME 1002 +#define IDC_ALT_10K 1002 +#define IDC_KEYPAD_STATE 1002 +#define IDC_GAME_TYPE 1003 +#define IDC_GROUP 1003 +#define IDC_LIMIT_MIN 1003 +#define IDC_BROWSE 1004 +#define IDC_BINDINGS 1005 +#define IDC_DEVICE 1005 +#define IDC_BINDING_EDIT 1006 +#define IDC_CONTROL 1006 +#define IDC_BINDING_CLEAR 1007 +#define IDC_BINDING_MIN 1008 +#define IDC_PAGE_TEXT 1008 +#define IDC_POSITION 1008 +#define IDC_PAGE 1009 +#define IDC_BINDING_MAX 1010 +#define IDC_LIMIT_MAX 1011 +#define IDC_CURRENT 1013 +#define IDC_BINDING_EDIT_MANY 40000 +#define IDS_APPNAME 40000 +#define IDS_BAD_GAME_TYPE 40001 +#define IDS_PAGE 40002 +#define IDS_COL_BUTTON 40003 +#define IDS_COL_ACTION 40004 +#define IDS_NOT_PRESENT 40005 +#define IDS_READER_P1 40006 +#define IDS_READER_P2 40007 +#define IDS_MUXED_TITLE 40008 +#define IDS_MUXED_MSG 40009 +#define IDS_COL_LIGHT_HID 40010 +#define IDS_COL_LIGHT_GAME 40011 +#define IDS_GENERIC_TEST 40012 +#define IDS_GENERIC_SERVICE 40013 +#define IDS_IIDX_SCHEMA 40014 +#define IDS_IIDX_P1_1 40015 +#define IDS_IIDX_P1_2 40016 +#define IDS_IIDX_P1_3 40017 +#define IDS_IIDX_P1_4 40018 +#define IDS_IIDX_P1_5 40019 +#define IDS_IIDX_P1_6 40020 +#define IDS_IIDX_P1_7 40021 +#define IDS_IIDX_P2_1 40022 +#define IDS_IIDX_P2_2 40023 +#define IDS_IIDX_P2_3 40024 +#define IDS_IIDX_P2_4 40025 +#define IDS_IIDX_P2_5 40026 +#define IDS_IIDX_P2_6 40027 +#define IDS_IIDX_P2_7 40028 +#define IDS_IIDX_P1_START 40029 +#define IDS_IIDX_P2_START 40030 +#define IDS_IIDX_VEFX 40031 +#define IDS_IIDX_EFFECT 40032 +#define IDS_IIDX_P1_TT_UP 40033 +#define IDS_IIDX_P1_TT_DOWN 40034 +#define IDS_IIDX_P1_TT_STAB 40035 +#define IDS_IIDX_P2_TT_UP 40036 +#define IDS_IIDX_P2_TT_DOWN 40037 +#define IDS_IIDX_P2_TT_STAB 40038 +#define IDS_IIDX_P1_TT 40039 +#define IDS_IIDX_P2_TT 40040 +#define IDS_IIDX_SPOT_1_LIGHT 40041 +#define IDS_IIDX_SPOT_2_LIGHT 40042 +#define IDS_IIDX_SPOT_3_LIGHT 40043 +#define IDS_IIDX_SPOT_4_LIGHT 40044 +#define IDS_IIDX_SPOT_5_LIGHT 40045 +#define IDS_IIDX_SPOT_6_LIGHT 40046 +#define IDS_IIDX_SPOT_7_LIGHT 40047 +#define IDS_IIDX_SPOT_8_LIGHT 40048 +#define IDS_IIDX_NEON_LIGHT 40049 +#define IDS_DDR_SCHEMA 40050 +#define IDS_DDR_P1_START 40051 +#define IDS_DDR_P1_MENU_UP 40052 +#define IDS_DDR_P1_MENU_DOWN 40053 +#define IDS_DDR_P1_MENU_LEFT 40054 +#define IDS_DDR_P1_MENU_RIGHT 40055 +#define IDS_DDR_P1_UP 40056 +#define IDS_DDR_P1_DOWN 40057 +#define IDS_DDR_P1_LEFT 40058 +#define IDS_DDR_P1_RIGHT 40059 +#define IDS_DDR_P2_START 40060 +#define IDS_DDR_P2_MENU_UP 40061 +#define IDS_DDR_P2_MENU_DOWN 40062 +#define IDS_DDR_P2_MENU_LEFT 40063 +#define IDS_DDR_P2_MENU_RIGHT 40064 +#define IDS_DDR_P2_UP 40065 +#define IDS_DDR_P2_DOWN 40066 +#define IDS_DDR_P2_LEFT 40067 +#define IDS_DDR_P2_RIGHT 40068 +#define IDS_DDR_P1_MENU_LIGHT 40069 +#define IDS_DDR_P1_TOP_LIGHT 40070 +#define IDS_DDR_P1_BOTTOM_LIGHT 40071 +#define IDS_DDR_P2_MENU_LIGHT 40072 +#define IDS_DDR_P2_TOP_LIGHT 40073 +#define IDS_DDR_P2_BOTTOM_LIGHT 40074 +#define IDS_DDR_BASS_LIGHT 40075 +#define IDS_PNM_SCHEMA 40076 +#define IDS_PNM_BTN1 40077 +#define IDS_PNM_BTN2 40078 +#define IDS_PNM_BTN3 40079 +#define IDS_PNM_BTN4 40080 +#define IDS_PNM_BTN5 40081 +#define IDS_PNM_BTN6 40082 +#define IDS_PNM_BTN7 40083 +#define IDS_PNM_BTN8 40084 +#define IDS_PNM_BTN9 40085 +#define IDS_JB_SCHEMA 40086 +#define IDS_JB_PANEL1 40087 +#define IDS_JB_PANEL2 40088 +#define IDS_JB_PANEL3 40089 +#define IDS_JB_PANEL4 40090 +#define IDS_JB_PANEL5 40091 +#define IDS_JB_PANEL6 40092 +#define IDS_JB_PANEL7 40093 +#define IDS_JB_PANEL8 40094 +#define IDC_BINDING_ADV 40095 +#define IDS_JB_PANEL9 40095 +#define IDS_JB_PANEL10 40096 +#define IDS_JB_PANEL11 40097 +#define IDS_JB_PANEL12 40098 +#define IDS_JB_PANEL13 40099 +#define IDS_JB_PANEL14 40100 +#define IDS_JB_PANEL15 40101 +#define IDS_JB_PANEL16 40102 +#define IDS_SDVX_SCHEMA 40103 +#define IDS_SDVX_START 40104 +#define IDS_SDVX_BTN_A 40105 +#define IDS_SDVX_BTN_B 40106 +#define IDS_SDVX_BTN_C 40107 +#define IDS_SDVX_BTN_D 40108 +#define IDS_SDVX_FX_L 40109 +#define IDS_SDVX_FX_R 40110 +#define IDS_SDVX_VOL_L 40111 +#define IDS_SDVX_VOL_R 40112 +#define IDS_DM_SCHEMA 40113 +#define IDS_DM_START 40114 +#define IDS_DM_MENU_LEFT 40115 +#define IDS_DM_MENU_RIGHT 40116 +#define IDS_DM_HI_HAT 40117 +#define IDS_DM_SNARE 40118 +#define IDS_DM_HIGH_TOM 40119 +#define IDS_DM_LOW_TOM 40120 +#define IDS_DM_CYMBAL 40121 +#define IDS_DM_BASS 40122 +#define IDS_DM_SPEAKER_LIGHT 40123 +#define IDS_DM_SPOT_LIGHT 40124 +#define IDS_DM_START_LIGHT 40125 +#define IDS_DM_MENU_LIGHT 40126 +#define IDS_GF_SCHEMA 40127 +#define IDS_GF_P1_START 40128 +#define IDS_GF_P1_PICK 40129 +#define IDS_GF_P1_PICK_A 40130 +#define IDS_GF_P1_PICK_B 40131 +#define IDS_GF_P1_WAIL 40132 +#define IDS_GF_P1_EFFECT 40133 +#define IDS_GF_P1_RED 40134 +#define IDS_GF_P1_GREEN 40135 +#define IDS_GF_P1_BLUE 40136 +#define IDS_GF_P2_START 40137 +#define IDS_GF_P2_PICK 40138 +#define IDS_GF_P2_PICK_A 40139 +#define IDS_GF_P2_PICK_B 40140 +#define IDS_GF_P2_WAIL 40141 +#define IDS_GF_P2_EFFECT 40142 +#define IDS_GF_P2_RED 40143 +#define IDS_GF_P2_GREEN 40144 +#define IDS_GF_P2_BLUE 40145 +#define IDS_GF_P1_SPOT_LIGHT 40146 +#define IDS_GF_P2_SPOT_LIGHT 40147 +#define IDS_RB_SCHEMA 40148 +#define IDS_SDVX_RGB1_R 40149 +#define IDS_SDVX_RGB1_G 40150 +#define IDS_SDVX_RGB1_B 40151 +#define IDS_SDVX_RGB2_R 40152 +#define IDS_SDVX_RGB2_G 40153 +#define IDS_SDVX_RGB2_B 40154 +#define IDS_SDVX_RGB3_R 40155 +#define IDS_SDVX_RGB3_G 40156 +#define IDS_SDVX_RGB3_B 40157 +#define IDS_SDVX_RGB4_R 40158 +#define IDS_SDVX_RGB4_G 40159 +#define IDS_SDVX_RGB4_B 40160 +#define IDS_SDVX_RGB5_R 40161 +#define IDS_SDVX_RGB5_G 40162 +#define IDS_SDVX_RGB5_B 40163 +#define IDS_SDVX_RGB6_R 40164 +#define IDS_SDVX_RGB6_G 40165 +#define IDS_SDVX_RGB6_B 40166 +#define IDS_JU5_SCHEMA 40167 +#define IDS_JB_RGB_FRONT_R 40168 +#define IDS_JB_RGB_FRONT_G 40169 +#define IDS_JB_RGB_FRONT_B 40170 +#define IDS_JB_RGB_TOP_R 40171 +#define IDS_JB_RGB_TOP_G 40172 +#define IDS_JB_RGB_TOP_B 40173 +#define IDS_JB_RGB_LEFT_R 40174 +#define IDS_JB_RGB_LEFT_G 40175 +#define IDS_JB_RGB_LEFT_B 40176 +#define IDS_JB_RGB_RIGHT_R 40177 +#define IDS_JB_RGB_RIGHT_G 40178 +#define IDS_JB_RGB_RIGHT_B 40179 +#define IDS_JB_RGB_TITLE_R 40180 +#define IDS_JB_RGB_TITLE_G 40181 +#define IDS_JB_RGB_TITLE_B 40182 +#define IDS_JB_RGB_WOOFER_R 40183 +#define IDS_JB_RGB_WOOFER_G 40184 +#define IDS_JB_RGB_WOOFER_B 40185 +#define IDS_BST_SCHEMA 40186 +#define IDS_IIDX_PANEL_SCHEMA 40187 +#define IDS_IIDX_PANEL_S1_UP 40188 +#define IDS_IIDX_PANEL_S1_DOWN 40189 +#define IDS_IIDX_PANEL_S2_UP 40190 +#define IDS_IIDX_PANEL_S2_DOWN 40191 +#define IDS_IIDX_PANEL_S3_UP 40192 +#define IDS_IIDX_PANEL_S3_DOWN 40193 +#define IDS_IIDX_PANEL_S4_UP 40194 +#define IDS_IIDX_PANEL_S4_DOWN 40195 +#define IDS_IIDX_PANEL_S5_UP 40196 +#define IDS_IIDX_PANEL_S5_DOWN 40197 +#define IDS_IIDX_PANEL_S1 40198 +#define IDS_IIDX_PANEL_S2 40199 +#define IDS_IIDX_PANEL_S3 40200 +#define IDS_IIDX_PANEL_S4 40201 +#define IDS_IIDX_PANEL_S5 40202 diff --git a/src/main/config/schema.c b/src/main/config/schema.c new file mode 100644 index 0000000..2c621a5 --- /dev/null +++ b/src/main/config/schema.c @@ -0,0 +1,391 @@ +#include "config/resource.h" +#include "config/schema.h" + +#include "util/defs.h" + +static const struct action_def dm_actions[] = { + { 0x01, IDS_GENERIC_TEST }, + { 0x00, IDS_GENERIC_SERVICE }, + + { 0x08, IDS_DM_START }, + { 0x0F, IDS_DM_MENU_LEFT }, + { 0x11, IDS_DM_MENU_RIGHT }, + + { 0x0A, IDS_DM_HI_HAT }, + { 0x0C, IDS_DM_SNARE }, + { 0x0E, IDS_DM_HIGH_TOM }, + { 0x10, IDS_DM_LOW_TOM }, + { 0x12, IDS_DM_CYMBAL }, + { 0x16, IDS_DM_BASS } +}; + +static const struct light_def dm_lights[] = { + { 0x00, IDS_DM_HI_HAT }, + { 0x01, IDS_DM_SNARE }, + { 0x02, IDS_DM_HIGH_TOM }, + { 0x03, IDS_DM_LOW_TOM }, + { 0x04, IDS_DM_CYMBAL }, + { 0x09, IDS_DM_BASS }, + { 0x0A, IDS_DM_SPEAKER_LIGHT }, + { 0x08, IDS_DM_SPOT_LIGHT }, + { 0x06, IDS_DM_START_LIGHT }, + { 0x07, IDS_DM_MENU_LIGHT } +}; + +static const struct action_def gf_actions[] = { + { 0x01, IDS_GENERIC_TEST }, + { 0x00, IDS_GENERIC_SERVICE }, + + { 0x08, IDS_GF_P1_START }, + { 0x0A, IDS_GF_P1_PICK }, + { 0x18, IDS_GF_P1_PICK_A }, + { 0x19, IDS_GF_P1_PICK_B }, + { 0x1C, IDS_GF_P1_EFFECT }, + { 0x0C, IDS_GF_P1_WAIL }, + { 0x12, IDS_GF_P1_RED }, + { 0x14, IDS_GF_P1_GREEN }, + { 0x16, IDS_GF_P1_BLUE }, + + { 0x09, IDS_GF_P2_START }, + { 0x0B, IDS_GF_P2_PICK }, + { 0x1A, IDS_GF_P2_PICK_A }, + { 0x1B, IDS_GF_P2_PICK_B }, + { 0x1D, IDS_GF_P2_EFFECT }, + { 0x0D, IDS_GF_P2_WAIL }, + { 0x13, IDS_GF_P2_RED }, + { 0x15, IDS_GF_P2_GREEN }, + { 0x17, IDS_GF_P2_BLUE } +}; + +static const struct light_def gf_lights[] = { + { 0x08, IDS_GF_P1_SPOT_LIGHT }, + { 0x09, IDS_GF_P2_SPOT_LIGHT }, + { 0x0A, IDS_GF_P1_START }, + { 0x0B, IDS_GF_P2_START } +}; + +static const struct action_def iidx_actions[] = { + { 0x1C, IDS_GENERIC_TEST }, + { 0x1D, IDS_GENERIC_SERVICE }, + + { 0x08, IDS_IIDX_P1_1 }, + { 0x09, IDS_IIDX_P1_2 }, + { 0x0A, IDS_IIDX_P1_3 }, + { 0x0B, IDS_IIDX_P1_4 }, + { 0x0C, IDS_IIDX_P1_5 }, + { 0x0D, IDS_IIDX_P1_6 }, + { 0x0E, IDS_IIDX_P1_7 }, + + { 0x0F, IDS_IIDX_P2_1 }, + { 0x10, IDS_IIDX_P2_2 }, + { 0x11, IDS_IIDX_P2_3 }, + { 0x12, IDS_IIDX_P2_4 }, + { 0x13, IDS_IIDX_P2_5 }, + { 0x14, IDS_IIDX_P2_6 }, + { 0x15, IDS_IIDX_P2_7 }, + + { 0x18, IDS_IIDX_P1_START }, + { 0x19, IDS_IIDX_P2_START }, + { 0x1A, IDS_IIDX_VEFX }, + { 0x1B, IDS_IIDX_EFFECT }, + + { 0x00, IDS_IIDX_P1_TT_UP }, + { 0x01, IDS_IIDX_P1_TT_DOWN }, + { 0x02, IDS_IIDX_P1_TT_STAB }, + + { 0x03, IDS_IIDX_P2_TT_UP }, + { 0x04, IDS_IIDX_P2_TT_DOWN }, + { 0x05, IDS_IIDX_P2_TT_STAB }, + + { 0x20, IDS_IIDX_PANEL_S1_UP }, + { 0x21, IDS_IIDX_PANEL_S1_DOWN }, + + { 0x22, IDS_IIDX_PANEL_S2_UP }, + { 0x23, IDS_IIDX_PANEL_S2_DOWN }, + + { 0x24, IDS_IIDX_PANEL_S3_UP }, + { 0x25, IDS_IIDX_PANEL_S3_DOWN }, + + { 0x26, IDS_IIDX_PANEL_S4_UP }, + { 0x27, IDS_IIDX_PANEL_S4_DOWN }, + + { 0x28, IDS_IIDX_PANEL_S5_UP }, + { 0x29, IDS_IIDX_PANEL_S5_DOWN } +}; + +static const struct light_def iidx_lights[] = { + { 0x00, IDS_IIDX_P1_1 }, + { 0x01, IDS_IIDX_P1_2 }, + { 0x02, IDS_IIDX_P1_3 }, + { 0x03, IDS_IIDX_P1_4 }, + { 0x04, IDS_IIDX_P1_5 }, + { 0x05, IDS_IIDX_P1_6 }, + { 0x06, IDS_IIDX_P1_7 }, + + { 0x07, IDS_IIDX_P2_1 }, + { 0x08, IDS_IIDX_P2_2 }, + { 0x09, IDS_IIDX_P2_3 }, + { 0x0A, IDS_IIDX_P2_4 }, + { 0x0B, IDS_IIDX_P2_5 }, + { 0x0C, IDS_IIDX_P2_6 }, + { 0x0D, IDS_IIDX_P2_7 }, + + { 0x18, IDS_IIDX_P1_START }, + { 0x19, IDS_IIDX_P2_START }, + { 0x1A, IDS_IIDX_VEFX }, + { 0x1B, IDS_IIDX_EFFECT }, + + { 0x10, IDS_IIDX_SPOT_1_LIGHT }, + { 0x11, IDS_IIDX_SPOT_2_LIGHT }, + { 0x12, IDS_IIDX_SPOT_3_LIGHT }, + { 0x13, IDS_IIDX_SPOT_4_LIGHT }, + { 0x14, IDS_IIDX_SPOT_5_LIGHT }, + { 0x15, IDS_IIDX_SPOT_6_LIGHT }, + { 0x16, IDS_IIDX_SPOT_7_LIGHT }, + { 0x17, IDS_IIDX_SPOT_8_LIGHT }, + + { 0x1F, IDS_IIDX_NEON_LIGHT } +}; + +static const struct analog_def iidx_analogs[] = { + { 0, IDS_IIDX_P1_TT }, + { 1, IDS_IIDX_P2_TT } +}; + +static const struct action_def ddr_actions[] = { + { 0x04, IDS_GENERIC_TEST }, + { 0x06, IDS_GENERIC_SERVICE }, + + { 0x10, IDS_DDR_P1_START }, + { 0x00, IDS_DDR_P1_MENU_UP }, + { 0x01, IDS_DDR_P1_MENU_DOWN }, + { 0x16, IDS_DDR_P1_MENU_LEFT }, + { 0x17, IDS_DDR_P1_MENU_RIGHT }, + { 0x11, IDS_DDR_P1_UP }, + { 0x12, IDS_DDR_P1_DOWN }, + { 0x13, IDS_DDR_P1_LEFT }, + { 0x14, IDS_DDR_P1_RIGHT }, + + { 0x08, IDS_DDR_P2_START }, + { 0x02, IDS_DDR_P2_MENU_UP }, + { 0x03, IDS_DDR_P2_MENU_DOWN }, + { 0x0E, IDS_DDR_P2_MENU_LEFT }, + { 0x0F, IDS_DDR_P2_MENU_RIGHT }, + { 0x09, IDS_DDR_P2_UP }, + { 0x0A, IDS_DDR_P2_DOWN }, + { 0x0B, IDS_DDR_P2_LEFT }, + { 0x0C, IDS_DDR_P2_RIGHT } +}; + +static const struct light_def ddr_lights[] = { + /* These are split between non-overlapping P3IO and EXTIO state words */ + + { 0x00, IDS_DDR_P1_MENU_LIGHT }, + { 0x07, IDS_DDR_P1_TOP_LIGHT }, + { 0x07, IDS_DDR_P1_BOTTOM_LIGHT }, + + { 0x1E, IDS_DDR_P1_UP }, + { 0x1D, IDS_DDR_P1_DOWN }, + { 0x1C, IDS_DDR_P1_LEFT }, + { 0x1B, IDS_DDR_P1_RIGHT }, + + { 0x01, IDS_DDR_P2_MENU_LIGHT }, + { 0x05, IDS_DDR_P2_TOP_LIGHT }, + { 0x04, IDS_DDR_P2_BOTTOM_LIGHT }, + + { 0x16, IDS_DDR_P2_UP }, + { 0x15, IDS_DDR_P2_DOWN }, + { 0x14, IDS_DDR_P2_LEFT }, + { 0x13, IDS_DDR_P2_RIGHT }, + + { 0x0E, IDS_DDR_BASS_LIGHT } +}; + +static const struct action_def pnm_actions[] = { + { 0x07, IDS_GENERIC_TEST }, + { 0x06, IDS_GENERIC_SERVICE }, + + { 0x08, IDS_PNM_BTN1 }, + { 0x09, IDS_PNM_BTN2 }, + { 0x0A, IDS_PNM_BTN3 }, + { 0x0B, IDS_PNM_BTN4 }, + { 0x0C, IDS_PNM_BTN5 }, + { 0x0D, IDS_PNM_BTN6 }, + { 0x0E, IDS_PNM_BTN7 }, + { 0x0F, IDS_PNM_BTN8 }, + { 0x10, IDS_PNM_BTN9 } +}; + +static const struct light_def pnm_lights[] = { + { 0x17, IDS_PNM_BTN1 }, + { 0x18, IDS_PNM_BTN2 }, + { 0x19, IDS_PNM_BTN3 }, + { 0x1A, IDS_PNM_BTN4 }, + { 0x1B, IDS_PNM_BTN5 }, + { 0x1C, IDS_PNM_BTN6 }, + { 0x1D, IDS_PNM_BTN7 }, + { 0x1E, IDS_PNM_BTN8 }, + { 0x1F, IDS_PNM_BTN9 }, +}; + +static const struct action_def jb_actions[] = { + { 0x10, IDS_GENERIC_TEST }, + { 0x11, IDS_GENERIC_SERVICE }, + + { 0x00, IDS_JB_PANEL1 }, + { 0x01, IDS_JB_PANEL2 }, + { 0x02, IDS_JB_PANEL3 }, + { 0x03, IDS_JB_PANEL4 }, + { 0x04, IDS_JB_PANEL5 }, + { 0x05, IDS_JB_PANEL6 }, + { 0x06, IDS_JB_PANEL7 }, + { 0x07, IDS_JB_PANEL8 }, + { 0x08, IDS_JB_PANEL9 }, + { 0x09, IDS_JB_PANEL10 }, + { 0x0A, IDS_JB_PANEL11 }, + { 0x0B, IDS_JB_PANEL12 }, + { 0x0C, IDS_JB_PANEL13 }, + { 0x0D, IDS_JB_PANEL14 }, + { 0x0E, IDS_JB_PANEL15 }, + { 0x0F, IDS_JB_PANEL16 } +}; + +static const struct light_def jb_lights[] = { + { 0x00, IDS_JB_RGB_FRONT_R }, + { 0x01, IDS_JB_RGB_FRONT_G }, + { 0x02, IDS_JB_RGB_FRONT_B }, + { 0x03, IDS_JB_RGB_TOP_R }, + { 0x04, IDS_JB_RGB_TOP_G }, + { 0x05, IDS_JB_RGB_TOP_B }, + { 0x06, IDS_JB_RGB_LEFT_R }, + { 0x07, IDS_JB_RGB_LEFT_G }, + { 0x08, IDS_JB_RGB_LEFT_B }, + { 0x09, IDS_JB_RGB_RIGHT_R }, + { 0x0A, IDS_JB_RGB_RIGHT_G }, + { 0x0B, IDS_JB_RGB_RIGHT_B }, + { 0x0C, IDS_JB_RGB_TITLE_R }, + { 0x0D, IDS_JB_RGB_TITLE_G }, + { 0x0E, IDS_JB_RGB_TITLE_B }, + { 0x0F, IDS_JB_RGB_WOOFER_R }, + { 0x10, IDS_JB_RGB_WOOFER_G }, + { 0x11, IDS_JB_RGB_WOOFER_B }, +}; + +static const struct action_def sdvx_actions[] = { + { 0x05, IDS_GENERIC_TEST }, + { 0x04, IDS_GENERIC_SERVICE }, + { 0x0B, IDS_SDVX_START }, + { 0x0A, IDS_SDVX_BTN_A }, + { 0x09, IDS_SDVX_BTN_B }, + { 0x08, IDS_SDVX_BTN_C }, + { 0x15, IDS_SDVX_BTN_D }, + { 0x14, IDS_SDVX_FX_L }, + { 0x13, IDS_SDVX_FX_R } +}; + +static const struct light_def sdvx_lights[] = { + { 0x0D, IDS_SDVX_BTN_A }, + { 0x0E, IDS_SDVX_BTN_B }, + { 0x0F, IDS_SDVX_BTN_C }, + { 0x00, IDS_SDVX_BTN_D }, + { 0x01, IDS_SDVX_FX_L }, + { 0x02, IDS_SDVX_FX_R }, + { 0x0C, IDS_SDVX_START }, + { 0x10, IDS_SDVX_RGB1_R }, + { 0x11, IDS_SDVX_RGB1_G }, + { 0x12, IDS_SDVX_RGB1_B }, + { 0x13, IDS_SDVX_RGB2_R }, + { 0x14, IDS_SDVX_RGB2_G }, + { 0x15, IDS_SDVX_RGB2_B }, + { 0x16, IDS_SDVX_RGB3_R }, + { 0x17, IDS_SDVX_RGB3_G }, + { 0x18, IDS_SDVX_RGB3_B }, + { 0x19, IDS_SDVX_RGB4_R }, + { 0x1A, IDS_SDVX_RGB4_G }, + { 0x1B, IDS_SDVX_RGB4_B }, + { 0x1C, IDS_SDVX_RGB5_R }, + { 0x1D, IDS_SDVX_RGB5_G }, + { 0x1E, IDS_SDVX_RGB5_B }, + { 0x1F, IDS_SDVX_RGB6_R }, + { 0x20, IDS_SDVX_RGB6_G }, + { 0x21, IDS_SDVX_RGB6_B } +}; + +static const struct analog_def sdvx_analogs[] = { + { 0, IDS_SDVX_VOL_L }, + { 1, IDS_SDVX_VOL_R } +}; + +static const struct action_def rb_actions[] = { + { 0x00, IDS_GENERIC_TEST }, + { 0x01, IDS_GENERIC_SERVICE } +}; + +static const struct action_def bst_actions[] = { + { 0x05, IDS_GENERIC_TEST }, + { 0x04, IDS_GENERIC_SERVICE }, +}; + +static const struct eam_unit_def schema_eam_unit_defs[] = { + { IDS_READER_P1, 0 }, + { IDS_READER_P2, 1 } +}; + +const struct schema schemas[] = { + { "iidx", IDS_IIDX_SCHEMA, + iidx_actions, lengthof(iidx_actions), + iidx_lights, lengthof(iidx_lights), + iidx_analogs, lengthof(iidx_analogs), + schema_eam_unit_defs, 2 }, + + { "pnm", IDS_PNM_SCHEMA, + pnm_actions, lengthof(pnm_actions), + pnm_lights, lengthof(pnm_lights), + NULL, 0, + schema_eam_unit_defs, 1 }, + + { "gf", IDS_GF_SCHEMA, + gf_actions, lengthof(gf_actions), + gf_lights, lengthof(gf_lights), + NULL, 0, + schema_eam_unit_defs, 2 }, + + { "dm", IDS_DM_SCHEMA, + dm_actions, lengthof(dm_actions), + dm_lights, lengthof(dm_lights), + NULL, 0, + schema_eam_unit_defs, 1 }, + + { "ddr", IDS_DDR_SCHEMA, + ddr_actions, lengthof(ddr_actions), + ddr_lights, lengthof(ddr_lights), + NULL, 0, + schema_eam_unit_defs, 2 }, + + { "jb", IDS_JB_SCHEMA, + jb_actions, lengthof(jb_actions), + jb_lights, lengthof(jb_lights), + NULL, 0, + schema_eam_unit_defs, 1 }, + + { "sdvx", IDS_SDVX_SCHEMA, + sdvx_actions, lengthof(sdvx_actions), + sdvx_lights, lengthof(sdvx_lights), + sdvx_analogs, lengthof(sdvx_analogs), + schema_eam_unit_defs, 1 }, + + { "rb", IDS_RB_SCHEMA, + rb_actions, lengthof(rb_actions), + NULL, 0, + NULL, 0, + schema_eam_unit_defs, 1 }, + + { "bst", IDS_BST_SCHEMA, + bst_actions, lengthof(bst_actions), + NULL, 0, + NULL, 0, + schema_eam_unit_defs, 1 }, +}; + +const size_t nschemas = lengthof(schemas); + diff --git a/src/main/config/schema.h b/src/main/config/schema.h new file mode 100644 index 0000000..78d0bbb --- /dev/null +++ b/src/main/config/schema.h @@ -0,0 +1,43 @@ +#ifndef CONFIG_SCHEMA_H +#define CONFIG_SCHEMA_H + +#include +#include + +struct action_def { + uint8_t bit; + unsigned int name_rsrc; +}; + +struct light_def { + uint8_t bit; + unsigned int name_rsrc; +}; + +struct analog_def { + uint8_t tag; + unsigned int label_rsrc; +}; + +struct eam_unit_def { + unsigned int label_rsrc; + uint8_t unit_no; +}; + +struct schema { + const char *name; + unsigned int label; + const struct action_def *actions; + size_t nactions; + const struct light_def *lights; + size_t nlights; + const struct analog_def *analogs; + size_t nanalogs; + const struct eam_unit_def *units; + size_t nunits; +}; + +extern const struct schema schemas[]; +extern const size_t nschemas; + +#endif diff --git a/src/main/config/snap.c b/src/main/config/snap.c new file mode 100644 index 0000000..b86bf6f --- /dev/null +++ b/src/main/config/snap.c @@ -0,0 +1,247 @@ +#include +#include +#include +#include +#include + +#include "config/snap.h" + +#include "geninput/hid-mgr.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" + +enum snap_control_heuristic { + CONTROL_CENTERING_AXIS, + CONTROL_MULTISWITCH +}; + +struct snap_known_control { + uint32_t usage; + enum snap_control_heuristic heuristic; +}; + +static struct snap_known_control snap_known_controls[] = { + /* X axis */ + { 0x00010030, CONTROL_CENTERING_AXIS }, + + /* Y axis */ + { 0x00010031, CONTROL_CENTERING_AXIS }, + + /* Z axis */ + { 0x00010032, CONTROL_CENTERING_AXIS }, + + /* X rotation */ + { 0x00010033, CONTROL_CENTERING_AXIS }, + + /* Y rotation */ + { 0x00010034, CONTROL_CENTERING_AXIS }, + + /* Z rotation */ + { 0x00010035, CONTROL_CENTERING_AXIS }, + + /* I don't have an adapter that presents a slider/dial/wheel so I don't + know how to deal with those things right now */ + + /* Hat switch */ + { 0x00010039, CONTROL_MULTISWITCH } +}; + +static bool snap_check_for_edge(const struct hid_control *ctl, int32_t val, + int32_t other_val, struct mapped_action *ma); + +void snap_init(struct snap *snap) +{ + size_t i; + size_t j; + size_t ncontrols; + size_t ndevs; + struct hid_stub *pos; + + hid_mgr_lock(); + + ndevs = 0; + + for (pos = hid_mgr_get_first_stub() + ; pos != NULL + ; pos = hid_mgr_get_next_stub(pos)) { + + ndevs++; + } + + snap->ndevs = ndevs; + snap->devs = xcalloc(ndevs * sizeof(*snap->devs)); + + i = 0; + + for (pos = hid_mgr_get_first_stub(), i = 0 + ; pos != NULL && i < ndevs + ; pos = hid_mgr_get_next_stub(pos), i++) { + + if (!hid_stub_is_attached(pos)) { + continue; + } + + if (!hid_stub_get_controls(pos, NULL, &ncontrols)) { + continue; + } + + snap->devs[i].hid = pos; + snap->devs[i].ncontrols = ncontrols; + snap->devs[i].controls = xcalloc(ncontrols + * sizeof(struct hid_control)); + snap->devs[i].states = xcalloc(ncontrols * sizeof(int32_t)); + + hid_stub_get_controls(pos, snap->devs[i].controls, &ncontrols); + + for (j = 0 ; j < ncontrols ; j++) { + hid_stub_get_value(pos, j, &snap->devs[i].states[j]); + } + } + + hid_mgr_unlock(); +} + +bool snap_find_edge(const struct snap *snap, const struct snap *other_snap, + struct mapped_action *ma) +{ + const struct hid_control *ctl; + int32_t val; + int32_t other_val; + size_t i; + size_t j; + + /* Most of these checks are to ensure some other device didn't sneak in + between these two snapshots. If it has, then detecting a state + transition becomes a bit awkward so we just say that nothing happened + and wait for the next poll period. + + Yes, creating a total input snapshot 60ish times per second while + attempting to bind a key isn't exactly the most efficient thing in the + world. We don't care, it's just a config program. */ + + if (snap->ndevs != other_snap->ndevs) { + return false; + } + + for (i = 0 ; i < snap->ndevs ; i++) { + if (snap->devs[i].hid != other_snap->devs[i].hid) { + return false; + } + + if (snap->devs[i].ncontrols != other_snap->devs[i].ncontrols) { + return false; + } + + if (memcmp(snap->devs[i].controls, other_snap->devs[i].controls, + snap->devs[i].ncontrols * sizeof(struct hid_control)) != 0) { + return false; + } + + for (j = 0 ; j < snap->devs[i].ncontrols ; j++) { + ctl = &snap->devs[i].controls[j]; + val = snap->devs[i].states[j]; + other_val = other_snap->devs[i].states[j]; + + if (snap_check_for_edge(ctl, val, other_val, ma)) { + ma->hid = snap->devs[i].hid; + ma->control_no = j; + + return true; + } + } + } + + return false; +} + +static bool snap_check_for_edge(const struct hid_control *ctl, int32_t val, + int32_t other_val, struct mapped_action *ma) +{ + size_t i; + int32_t range; + int32_t dz_min; + int32_t dz_max; + + if (val != other_val) { + log_misc("Value change on %08x: %d -> %d", ctl->usage, other_val, val); + } + + if (ctl->value_max - ctl->value_min == 1) { + /* Button-like thing, deal with it the obvious way */ + if (val == ctl->value_max && val != other_val) { + ma->value_min = ctl->value_max; + ma->value_max = ctl->value_max; + + return true; + } + } else { + /* Here we kinda have to take things on a case by case basis */ + + for (i = 0 ; i < lengthof(snap_known_controls) ; i++) { + if (snap_known_controls[i].usage != ctl->usage) { + continue; + } + + switch (snap_known_controls[i].heuristic) { + case CONTROL_CENTERING_AXIS: + /* Dead zones keep giving me grief, so I'm going to be + super-aggressive and assume a 50%-ish dead zone + + (yes I know the rounding here is a little iffy) */ + + range = ctl->value_max - ctl->value_min; + dz_min = ctl->value_min + ((range * 1) / 4); + dz_max = ctl->value_min + ((range * 3) / 4); + + if (val <= dz_min && other_val > dz_min) { + ma->value_min = ctl->value_min; + ma->value_max = dz_min; + + return true; + } else if (val >= dz_max && other_val < dz_max) { + ma->value_min = dz_max; + ma->value_max = ctl->value_max; + + return true; + } + + return false; + + case CONTROL_MULTISWITCH: + /* Assume positions are discrete. Precisely match any + transitioned-to value that isn't a null state. */ + + if (val >= ctl->value_min && val <= ctl->value_max + && val != other_val) { + ma->value_min = val; + ma->value_max = val; + + return true; + } + + return false; + } + } + + /* Still haven't found it? Well, we don't know how to determine if this + control is being actively pressed or not, so the user will just have + to manually key in an advanced binding. */ + } + + return false; +} + +void snap_fini(struct snap *snap) +{ + size_t i; + + for (i = 0 ; i < snap->ndevs ; i++) { + free(snap->devs[i].states); + free(snap->devs[i].controls); + } + + free(snap->devs); +} + diff --git a/src/main/config/snap.h b/src/main/config/snap.h new file mode 100644 index 0000000..cb95f12 --- /dev/null +++ b/src/main/config/snap.h @@ -0,0 +1,27 @@ +#ifndef CONFIG_SNAP_H +#define CONFIG_SNAP_H + +#include +#include +#include + +#include "geninput/mapper.h" + +struct snap_dev { + struct hid_stub *hid; + int32_t *states; + struct hid_control *controls; + uint32_t ncontrols; +}; + +struct snap { + struct snap_dev *devs; + uint32_t ndevs; +}; + +void snap_init(struct snap *snap); +bool snap_find_edge(const struct snap *snap, const struct snap *other_snap, + struct mapped_action *ma); +void snap_fini(struct snap *snap); + +#endif diff --git a/src/main/config/spinner.c b/src/main/config/spinner.c new file mode 100644 index 0000000..64f1747 --- /dev/null +++ b/src/main/config/spinner.c @@ -0,0 +1,118 @@ +#include + +#include +#include +#include + +#define METRIC_COLOR RGB(0, 0, 0) +#define METRIC_RADIUS_CIRCLE 0.8f +#define METRIC_RADIUS_NOTCH 0.2f +#define METRIC_THICKNESS 3 + +static const wchar_t spinner_cls[] = L"spinner"; + +static LRESULT CALLBACK spinner_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static LRESULT spinner_handle_paint(HWND hwnd); +static LRESULT spinner_handle_update(HWND hwnd, uint8_t pos); + +void spinner_init(HINSTANCE inst) +{ + WNDCLASSEX wcx; + + memset(&wcx, 0, sizeof(wcx)); + wcx.cbSize = sizeof(wcx); + wcx.lpfnWndProc = spinner_wnd_proc; + wcx.hInstance = inst; + wcx.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); + wcx.lpszClassName = spinner_cls; + + RegisterClassEx(&wcx); +} + +static LRESULT CALLBACK spinner_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_CREATE: + SetWindowLong(hwnd, GWLP_USERDATA, 0); + + return TRUE; + + case WM_PAINT: + return spinner_handle_paint(hwnd); + + case WM_USER: + return spinner_handle_update(hwnd, (uint8_t) lparam); + + default: + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} + +static LRESULT spinner_handle_paint(HWND hwnd) +{ + HDC dc; + HPEN pen; + PAINTSTRUCT ps; + RECT r; + float cx; + float cy; + float dx; + float dy; + float theta; + uint8_t itheta; + + /* GWLP_USERDATA angle has 256 steps in a complete revolution. + To convert to radians, multiply by (2 * PI / 256). */ + + itheta = (uint8_t) GetWindowLongPtr(hwnd, GWLP_USERDATA); + theta = itheta * 0.0245437f; + + GetWindowRect(hwnd, &r); + + cx = (r.right - r.left) / 2.0f; + cy = (r.bottom - r.top) / 2.0f; + + dc = BeginPaint(hwnd, &ps); + + pen = CreatePen(PS_SOLID, METRIC_THICKNESS, METRIC_COLOR); + SelectObject(dc, pen); + SelectObject(dc, GetStockObject(WHITE_BRUSH)); + + Ellipse(dc, + (int) (cx * (1.0f - METRIC_RADIUS_CIRCLE)), + (int) (cy * (1.0f - METRIC_RADIUS_CIRCLE)), + (int) (cx * (1.0f + METRIC_RADIUS_CIRCLE)), + (int) (cy * (1.0f + METRIC_RADIUS_CIRCLE))); + + dx = sinf(theta); + dy = -cosf(theta); + + MoveToEx(dc, + (int) (cx * (1.0f + dx * METRIC_RADIUS_NOTCH)), + (int) (cy * (1.0f + dy * METRIC_RADIUS_NOTCH)), NULL); + + LineTo(dc, + (int) (cx * (1.0f + dx * METRIC_RADIUS_CIRCLE)), + (int) (cy * (1.0f + dy * METRIC_RADIUS_CIRCLE))); + + DeleteObject(pen); + EndPaint(hwnd, &ps); + + return 0; +} + +static LRESULT spinner_handle_update(HWND hwnd, uint8_t pos) +{ + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM) pos); + InvalidateRect(hwnd, NULL, TRUE); + + return 0; +} + +void spinner_fini(HINSTANCE inst) +{ + UnregisterClass(spinner_cls, inst); +} + diff --git a/src/main/config/spinner.h b/src/main/config/spinner.h new file mode 100644 index 0000000..8e36d20 --- /dev/null +++ b/src/main/config/spinner.h @@ -0,0 +1,9 @@ +#ifndef CONFIG_SPINNER_H +#define CONFIG_SPINNER_H + +#include + +void spinner_init(HINSTANCE inst); +void spinner_fini(HINSTANCE inst); + +#endif diff --git a/src/main/config/usages.c b/src/main/config/usages.c new file mode 100644 index 0000000..1d84057 --- /dev/null +++ b/src/main/config/usages.c @@ -0,0 +1,177 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "config/resource.h" + +#include "util/array.h" +#include "util/log.h" +#include "util/str.h" +#include "util/winres.h" + +struct usage { + uint16_t id; + char name[128]; +}; + +struct usage_page { + uint16_t id; + struct array usages; + char wildcard[128]; +}; + +static struct array usages; + +static bool usages_begin_page(const char *line, struct usage_page *page); +static bool usages_read_usage(const char *line, struct usage_page *page); + +void usages_init(HINSTANCE inst) +{ + struct resource res; + struct usage_page *cur_page; + struct usage_page tmp_page; + char line[512]; + size_t line_no; + size_t nchars; + + resource_open(&res, inst, "USAGES", IDR_USAGES); + array_init(&usages); + + cur_page = NULL; + line_no = 0; + + while (resource_fgets(&res, line, sizeof(line))) { + line_no++; + + nchars = strlen(line); + + if (nchars == 0 || line[0] == '#' + || strspn(line, " \t\r\n") == nchars) { + continue; + } + + str_trim(line); + + if (!isspace(line[0])) { + if (!usages_begin_page(line, &tmp_page)) { + log_fatal("IDR_USAGES:%u: Invalid usage page", + (unsigned int) line_no); + + break; + } + + cur_page = array_append(struct usage_page, &usages); + memcpy(cur_page, &tmp_page, sizeof(tmp_page)); + } else { + if (cur_page == NULL) { + log_fatal("IDR_USAGES:%u: Usage before first page", + (unsigned int) line_no); + + break; + } + + if (!usages_read_usage(line, cur_page)) { + log_fatal("IDR_USAGES:%u: Invalid usage", + (unsigned int) line_no); + + break; + } + } + } +} + +static bool usages_begin_page(const char *line, struct usage_page *page) +{ + int id; + + if (sscanf(line, "%i", &id) != 1) { + return false; + } + + page->id = (uint16_t) id; + array_init(&page->usages); + + return true; +} + +static bool usages_read_usage(const char *line, struct usage_page *page) +{ + struct usage *usage; + int id; + int offset; + char c; + + if (sscanf(line, " %c %n", &c, &offset) == 1 && c == '*') { + str_cpy(page->wildcard, sizeof(page->wildcard), &line[offset]); + page->wildcard[sizeof(page->wildcard) - 1] = '\0'; + + return true; + } else if (sscanf(line, " %i %n", &id, &offset) == 1) { + usage = array_append(struct usage, &page->usages); + + usage->id = (uint16_t) id; + str_cpy(usage->name, sizeof(usage->name), &line[offset]); + usage->name[sizeof(usage->name) - 1] = '\0'; + + return true; + } else { + return false; + } +} + +void usages_get(char *chars, size_t nchars, uint32_t usage_id) +{ + const struct usage_page *page; + const struct usage *usage; + uint16_t hi; + uint16_t lo; + size_t i; + size_t j; + + hi = ((usage_id >> 16) & 0xFFFF); + lo = ((usage_id >> 0) & 0xFFFF); + + for (i = 0 ; i < usages.nitems ; i++) { + page = array_item(struct usage_page, &usages, i); + + if (page->id != hi) { + continue; + } + + for (j = 0 ; j < page->usages.nitems ; j++) { + usage = array_item(struct usage, &page->usages, j); + + if (usage->id == lo) { + str_cpy(chars, nchars, usage->name); + chars[nchars - 1] = '\0'; + + return; + } + } + + if (page->wildcard[0]) { + str_format(chars, nchars, page->wildcard, lo); + + return; + } + } + + str_format(chars, nchars, "%#08x", usage_id); +} + +void usages_fini(void) +{ + size_t i; + + for (i = 0 ; i < usages.nitems ; i++) { + array_fini(&array_item(struct usage_page, &usages, i)->usages); + } + + array_fini(&usages); +} + diff --git a/src/main/config/usages.h b/src/main/config/usages.h new file mode 100644 index 0000000..310a057 --- /dev/null +++ b/src/main/config/usages.h @@ -0,0 +1,12 @@ +#ifndef CONFIG_USAGES_H +#define CONFIG_USAGES_H + +#include + +#include + +void usages_init(HINSTANCE inst); +void usages_get(char *chars, uint32_t nchars, uint32_t usage); +void usages_fini(void); + +#endif diff --git a/src/main/config/usages.txt b/src/main/config/usages.txt new file mode 100755 index 0000000..8d99a80 --- /dev/null +++ b/src/main/config/usages.txt @@ -0,0 +1,1244 @@ +# $NetBSD: usb_hid_usages,v 1.3 1999/07/02 15:46:53 simonb Exp $ +# $FreeBSD: src/share/misc/usb_hid_usages,v 1.1.2.1 2000/07/02 13:13:55 n_hibma Exp $ +# +# USB HID usage table +# Syntax: +# - lines that do not start with a white space give the number and name of +# a usage page. +# - lines that start with a white space give the number and name of +# a usage with the last given page. +# If the number is * then the line matches all usages and the name +# is a printf formatting string that will be given the usage number. +# +1 Generic Desktop + 0x00 Undefined + 0x01 Pointer + 0x02 Mouse + 0x03 Reserved + 0x04 Joystick + 0x05 Game Pad + 0x06 Keyboard + 0x07 Keypad + 0x08 Multi-axis Controller + 0x30 X + 0x31 Y + 0x32 Z + 0x33 Rx + 0x34 Ry + 0x35 Rz + 0x36 Slider + 0x37 Dial + 0x38 Wheel + 0x39 Hat Switch + 0x3A Counted Buffer + 0x3B Byte Count + 0x3C Motion Wakeup + 0x40 Vx + 0x41 Vy + 0x42 Vz + 0x43 Vbrx + 0x44 Vbry + 0x45 Vbrx + 0x46 Vno + 0x80 System Control + 0x81 System Power Down + 0x82 System Sleep + 0x83 System Wake Up + 0x84 System Context Menu + 0x85 System Main Menu + 0x86 System App Menu + 0x87 System Menu Help + 0x88 System Menu Exit + 0x89 System Menu Select + 0x8A System Menu Right + 0x8B System Menu Left + 0x8C System Menu Up + 0x8D System Menu Down + 0x90 D-pad Up + 0x91 D-pad Down + 0x92 D-pad Right + 0x93 D-pad Left + +2 Simulation Controls + 0x00 Undefined + 0x01 Flight Simulation Device + 0x02 Automobile Simulation Device + 0x03 Tank Simulation Device + 0x04 Spaceship Simulation Device + 0x05 Submarine Simulation Device + 0x06 Sailing Simulation Device + 0x07 Motorcycle Simulation Device + 0x08 Sports Simulation Device + 0x09 Airplane Simulation Device + 0x0A Helicopter Simulation Device + 0x0B Magic Carpet Simulation Device + 0x0C Bicycle + 0x20 Flight Control Stick + 0x21 Flight Stick + 0x22 Cyclic Control + 0x23 Cyclic Trim + 0x24 Flight Yoke + 0x25 Track Control + 0x26 Driving Control + 0xB0 Aileron + 0xB1 Aileron Trim + 0xB2 Anti-Torque Control + 0xB3 Auto-pilot Enable + 0xB4 Chaff Release + 0xB5 Collective Control + 0xB6 Dive Brake + 0xB7 Electronic Counter Measures + 0xB8 Elevator + 0xB9 Elevator Trim + 0xBA Rudder + 0xBB Throttle + 0xBC Flight Communication + 0xBD Flare Release + 0xBE Landing Gear + 0xBF Toe Brake + 0xC0 Trigger + 0xC1 Weapons Arm + 0xC2 Weapons Select + 0xC3 Wing Flaps + 0xC4 Accelerator + 0xC5 Brake + 0xC6 Clutch + 0xC7 Shifter + 0xC8 Steering + 0xC9 Turret Direction + 0xCA Barrel Elevation + 0xCB Dive Plane + 0xCC Ballast + 0xCD Bicycle Crank + 0xCE Handle Bars + 0xCF Front Brake + 0xD0 Rear Brake + +3 VR Controls + 0x00 Unidentified + 0x01 Belt + 0x02 Body Suit + 0x03 Flexor + 0x04 Glove + 0x05 Head Tracker + 0x06 Head Mounted Display + 0x07 Hand Tracker + 0x08 Oculometer + 0x09 Vest + 0x0A Animatronic Device + 0x20 Stereo Enable + 0x21 Display Enable + +4 Sports Controls + 0x00 Unidentified + 0x01 Baseball Bat + 0x02 Golf Club + 0x03 Rowing Machine + 0x04 Treadmill + 0x30 Oar + 0x31 Slope + 0x32 Rate + 0x33 Stick Speed + 0x34 Stick Face Angle + 0x35 Stick Heel/Toe + 0x36 Stick Follow Through + 0x37 Stick Tempo + 0x38 Stick Type + 0x39 Stick Height + 0x50 Putter + 0x51 1 Iron + 0x52 2 Iron + 0x53 3 Iron + 0x54 4 Iron + 0x55 5 Iron + 0x56 6 Iron + 0x57 7 Iron + 0x58 8 Iron + 0x59 9 Iron + 0x5A 10 Iron + 0x5B 11 Iron + 0x5C Sand Wedge + 0x5D Loft Wedge + 0x5E Power Wedge + 0x5F 1 Wood + 0x60 3 Wood + 0x61 5 Wood + 0x62 7 Wood + 0x63 9 Wood + +5 Game Controls + 0x00 Undefined + 0x01 3D Game Controller + 0x02 Pinball Device + 0x03 Gun Device + 0x20 Point of View + 0x21 Turn Right/Left + 0x22 Pitch Right/Left + 0x23 Roll Forward/Backward + 0x24 Move Right/Left + 0x25 Move Forward/Backward + 0x26 Move Up/Down + 0x27 Lean Right/Left + 0x28 Lean Forward/Backward + 0x29 Height of POV + 0x2A Flipper + 0x2B Secondary Flipper + 0x2C Bump + 0x2D New Game + 0x2E Shoot Ball + 0x2F Player + 0x30 Gun Bolt + 0x31 Gun Clip + 0x32 Gun Selector + 0x33 Gun Single Shot + 0x34 Gun Burst + 0x35 Gun Automatic + 0x36 Gun Safety + 0x37 Gamepad Fire/Jump + 0x39 Gamepad Trigger + +7 Keyboard + 0x00 Reserved (no event indicated) + 0x01 Keyboard ErrorRollOver + 0x02 Keyboard POSTFail + 0x03 Keyboard ErrorUndefined + 0x04 Keyboard a and A + 0x05 Keyboard b and B + 0x06 Keyboard c and C + 0x07 Keyboard d and D + 0x08 Keyboard e and E + 0x09 Keyboard f and F + 0x0A Keyboard g and G + 0x0B Keyboard h and H + 0x0C Keyboard i and I + 0x0D Keyboard j and J + 0x0E Keyboard k and K + 0x0F Keyboard l and L + 0x10 Keyboard m and M + 0x11 Keyboard n and N + 0x12 Keyboard o and O + 0x13 Keyboard p and P + 0x14 Keyboard q and Q + 0x15 Keyboard r and R + 0x16 Keyboard s and S + 0x17 Keyboard t and T + 0x18 Keyboard u and U + 0x19 Keyboard v and V + 0x1A Keyboard w and W + 0x1B Keyboard x and X + 0x1C Keyboard y and Y + 0x1D Keyboard z and Z + 0x1E Keyboard 1 and ! + 0x1F Keyboard 2 and @ + 0x20 Keyboard 3 and # + 0x21 Keyboard 4 and $ + 0x22 Keyboard 5 and % + 0x23 Keyboard 6 and ^ + 0x24 Keyboard 7 and & + 0x25 Keyboard 8 and * + 0x26 Keyboard 9 and ( + 0x27 Keyboard 0 and ) + 0x28 Keyboard Return (ENTER) + 0x29 Keyboard ESCAPE + 0x2A Keyboard DELETE (Backspace) + 0x2B Keyboard Tab + 0x2C Keyboard Spacebar + 0x2D Keyboard - and (underscore) + 0x2E Keyboard = and + + 0x2F Keyboard [ and { + 0x30 Keyboard ] and } + 0x31 Keyboard \ and | + 0x32 Keyboard Non-US # and ~ + 0x33 Keyboard ; and : + 0x34 Keyboard ' and " + 0x35 Keyboard Grave Accent and Tilde + 0x36 Keyboard, and < + 0x37 Keyboard . and > + 0x38 Keyboard / and ? + 0x39 Keyboard Caps Lock + 0x3A Keyboard F1 + 0x3B Keyboard F2 + 0x3C Keyboard F3 + 0x3D Keyboard F4 + 0x3E Keyboard F5 + 0x3F Keyboard F6 + 0x40 Keyboard F7 + 0x41 Keyboard F8 + 0x42 Keyboard F9 + 0x43 Keyboard F10 + 0x44 Keyboard F11 + 0x45 Keyboard F12 + 0x46 Keyboard PrintScreen + 0x47 Keyboard Scroll Lock + 0x48 Keyboard Pause + 0x49 Keyboard Insert + 0x4A Keyboard Home + 0x4B Keyboard PageUp + 0x4C Keyboard Delete Forward + 0x4D Keyboard End + 0x4E Keyboard PageDown + 0x4F Keyboard RightArrow + 0x50 Keyboard LeftArrow + 0x51 Keyboard DownArrow + 0x52 Keyboard UpArrow + 0x53 Keypad Num Lock and Clear + 0x54 Keypad / + 0x55 Keypad * + 0x56 Keypad - + 0x57 Keypad + + 0x58 Keypad ENTER + 0x59 Keypad 1 and End + 0x5A Keypad 2 and Down Arrow + 0x5B Keypad 3 and PageDn + 0x5C Keypad 4 and Left Arrow + 0x5D Keypad 5 + 0x5E Keypad 6 and Right Arrow + 0x5F Keypad 7 and Home + 0x60 Keypad 8 and Up Arrow + 0x61 Keypad 9 and PageUp + 0x62 Keypad 0 and Insert + 0x63 Keypad . and Delete + 0x64 Keyboard Non-US \ and | + 0x65 Keyboard Application + 0x66 Keyboard Power + 0x67 Keypad = + 0x68 Keyboard F13 + 0x69 Keyboard F14 + 0x6A Keyboard F15 + 0x6B Keyboard F16 + 0x6C Keyboard F17 + 0x6D Keyboard F18 + 0x6E Keyboard F19 + 0x6F Keyboard F20 + 0x70 Keyboard F21 + 0x71 Keyboard F22 + 0x72 Keyboard F23 + 0x73 Keyboard F24 + 0x74 Keyboard Execute + 0x75 Keyboard Help + 0x76 Keyboard Menu + 0x77 Keyboard Select + 0x78 Keyboard Stop + 0x79 Keyboard Again + 0x7A Keyboard Undo + 0x7B Keyboard Cut + 0x7C Keyboard Copy + 0x7D Keyboard Paste + 0x7E Keyboard Find + 0x7F Keyboard Mute + 0x80 Keyboard Volume Up + 0x81 Keyboard Volume Down + 0x82 Keyboard Locking Caps Lock + 0x83 Keyboard Locking Num Lock + 0x84 Keyboard Locking Scroll Lock + 0x85 Keypad Comma + 0x86 Keypad Equal Sign + 0x87 Keyboard International1 + 0x88 Keyboard International2 + 0x89 Keyboard International3 + 0x8A Keyboard International4 + 0x8B Keyboard International5 + 0x8C Keyboard International6 + 0x8D Keyboard International7 + 0x8E Keyboard International8 + 0x8F Keyboard International9 + 0x90 Keyboard LANG1 + 0x91 Keyboard LANG2 + 0x92 Keyboard LANG3 + 0x93 Keyboard LANG4 + 0x94 Keyboard LANG5 + 0x95 Keyboard LANG6 + 0x96 Keyboard LANG7 + 0x97 Keyboard LANG8 + 0x98 Keyboard LANG9 + 0x99 Keyboard Alternate Erase + 0x9A Keyboard SysReq/Attention + 0x9B Keyboard Cancel + 0x9C Keyboard Clear + 0x9D Keyboard Prior + 0x9E Keyboard Return + 0x9F Keyboard Separator + 0xA0 Keyboard Out + 0xA1 Keyboard Oper + 0xA2 Keyboard Clear/Again + 0xA3 Keyboard CrSel/Props + 0xA4 Keyboard ExSel + 0xE0 Keyboard LeftControl + 0xE1 Keyboard LeftShift + 0xE2 Keyboard LeftAlt + 0xE3 Keyboard Left GUI + 0xE4 Keyboard RightControl + 0xE5 Keyboard RightShift + 0xE6 Keyboard RightAlt + 0xE7 Keyboard Right GUI + +8 LEDs + 0x00 Undefined + 0x01 Num Lock + 0x02 Caps Lock + 0x03 Scroll Lock + 0x04 Compose + 0x05 Kana + 0x06 Power + 0x07 Shift + 0x08 Do Not Disturb + 0x09 Mute + 0x0A Tone Enable + 0x0B High Cut Filter + 0x0C Low Cut Filter + 0x0D Equalizer Enable + 0x0E Sound Field On + 0x0F Surround Field On + 0x10 Repeat + 0x11 Stereo + 0x12 Sampling Rate Detect + 0x13 Spinning + 0x14 CAV + 0x15 CLV + 0x16 Recording Format Detect + 0x17 Off-Hook + 0x18 Ring + 0x19 Message Waiting + 0x1A Data Mode + 0x1B Battery Operation + 0x1C Battery OK + 0x1D Battery Low + 0x1E Speaker + 0x1F Head Set + 0x20 Hold + 0x21 Microphone + 0x22 Coverage + 0x23 Night Mode + 0x24 Send Calls + 0x25 Call Pickup + 0x26 Conference + 0x27 Stand-by + 0x28 Camera On + 0x29 Camera Off + 0x2A On-Line + 0x2B Off-Line + 0x2C Busy + 0x2D Ready + 0x2E Paper-Out + 0x2F Paper-Jam + 0x30 Remote + 0x31 Forward + 0x32 Reverse + 0x33 Stop + 0x34 Rewind + 0x35 Fast Forward + 0x36 Play + 0x37 Pause + 0x38 Record + 0x39 Error + 0x3A Usage Selected Indicator + 0x3B Usage In Use Indicator + 0x3C Usage Multi Mode Indicator + 0x3D Indicator On + 0x3E Indicator Flash + 0x3F Indicator Slow Blink + 0x40 Indicator Fast Blink + 0x41 Indicator Off + 0x42 Flash On Time + 0x43 Slow Blink On Time + 0x44 Slow Blink Off Time + 0x45 Fast Blink On Time + 0x46 Fast Blink Off Time + 0x47 Usage Indicator Color + 0x48 Red + 0x49 Green + 0x4A Amber + 0x4B Generic Indicator + 0x4C System Suspend + 0x4D External Power Connected + * Reserved + +9 Button + 0x00 No Button Pressed + * Button %d + +10 Ordinal + 0x00 Unused + * Instance %d + +11 Telephony + 0x00 Unassigned + 0x01 Phone + 0x02 Answering Machine + 0x03 Message Controls + 0x04 Handset + 0x05 Headset + 0x06 Telephony Key Pad + 0x07 Programmable Button + 0x20 Hook Switch + 0x21 Flash + 0x22 Feature + 0x23 Hold + 0x24 Redial + 0x25 Transfer + 0x26 Drop + 0x27 Park + 0x28 Forward Calls + 0x29 Alternate Function + 0x2A Line + 0x2B Speaker Phone + 0x2C Conference + 0x2D Ring Enable + 0x2E Ring Select + 0x2F Phone Mute + 0x30 Caller ID + 0x50 Speed Dial + 0x51 Store Number + 0x52 Recall Number + 0x53 Phone Directory + 0x70 Voice Mail + 0x71 Screen Calls + 0x72 Do Not Disturb + 0x73 Message + 0x74 Answer On/Off + 0x90 Inside Dial Tone + 0x91 Outside Dial Tone + 0x92 Inside Ring Tone + 0x93 Outside Ring Tone + 0x94 Priority Ring Tone + 0x95 Inside Ringback + 0x96 Priority Ringback + 0x97 Line Busy Tone + 0x98 Reorder Tone + 0x99 Call Waiting Tone + 0x9A Confirmation Tone 1 + 0x9B Confirmation Tone 2 + 0x9C Tones Off + 0xB0 Phone Key 0 + 0xB1 Phone Key 1 + 0xB2 Phone Key 2 + 0xB3 Phone Key 3 + 0xB4 Phone Key 4 + 0xB5 Phone Key 5 + 0xB6 Phone Key 6 + 0xB7 Phone Key 7 + 0xB8 Phone Key 8 + 0xB9 Phone Key 9 + 0xBA Phone Key Star + 0xBB Phone Key Pound + 0xBC Phone Key A + 0xBD Phone Key B + 0xBE Phone Key C + 0xBF Phone Key D + +12 Consumer + 0x00 Unassigned + 0x01 Consumer Control + 0x02 Numeric Key Pad + 0x03 Programmable Buttons + 0x20 +10 + 0x21 +100 + 0x22 AM/PM + 0x30 Power + 0x31 Reset + 0x32 Sleep + 0x33 Sleep After + 0x34 Sleep Mode + 0x35 Illumination + 0x36 Function Buttons + 0x40 Menu + 0x41 Menu Pick + 0x42 Menu Up + 0x43 Menu Down + 0x44 Menu Left + 0x45 Menu Right + 0x46 Menu Escape + 0x47 Menu Value Increase + 0x48 Menu Value Decrease + 0x60 Data On Screen + 0x61 Closed Caption + 0x62 Closed Caption Select + 0x63 VCR/TV + 0x64 Broadcast Mode + 0x65 Snapshot + 0x66 Still + 0x80 Selection + 0x81 Assign Selection + 0x82 Mode Step + 0x83 Recall Last + 0x84 Enter Channel + 0x85 Order Movie + 0x86 Channel + 0x87 Media Selection + 0x88 Media Select Computer + 0x89 Media Select TV + 0x8A Media Select WWW + 0x8B Media Select DVD + 0x8C Media Select Telephone + 0x8D Media Select Program Guide + 0x8E Media Select Video Phone + 0x8F Media Select Games + 0x90 Media Select Messages + 0x91 Media Select CD + 0x92 Media Select VCR + 0x93 Media Select Tuner + 0x94 Quit + 0x95 Help + 0x96 Media Select Tape + 0x97 Media Select Cable + 0x98 Media Select Satellite + 0x99 Media Select Security + 0x9A Media Select Home + 0x9B Media Select Call + 0x9C Channel Increment + 0x9D Channel Decrement + 0x9E Media Select SAP + 0xA0 VCR Plus + 0xA1 Once + 0xA2 Daily + 0xA3 Weekly + 0xA4 Monthly + 0xB0 Play + 0xB1 Pause + 0xB2 Record + 0xB3 Fast Forward + 0xB4 Rewind + 0xB5 Scan Next Track + 0xB6 Scan Previous Track + 0xB7 Stop + 0xB8 Eject + 0xB9 Random Play + 0xBA Select DisC + 0xBB Enter Disc + 0xBC Repeat + 0xBD Tracking + 0xBE Track Normal + 0xBF Slow Tracking + 0xC0 Frame Forward + 0xC1 Frame Back + 0xC2 Mark + 0xC3 Clear Mark + 0xC4 Repeat From Mark + 0xC5 Return To Mark + 0xC6 Search Mark Forward + 0xC7 Search Mark Backwards + 0xC8 Counter Reset + 0xC9 Show Counter + 0xCA Tracking Increment + 0xCB Tracking Decrement + 0xE0 Volume + 0xE1 Balance + 0xE2 Mute + 0xE3 Bass + 0xE4 Treble + 0xE5 Bass Boost + 0xE6 Surround Mode + 0xE7 Loudness + 0xE8 MPX + 0xE9 Volume Up + 0xEA Volume Down + 0xF0 Speed Select + 0xF1 Playback Speed + 0xF2 Standard Play + 0xF3 Long Play + 0xF4 Extended Play + 0xF5 Slow + 0x100 Fan Enable + 0x101 Fan Speed + 0x102 Light + 0x103 Light Illumination Level + 0x104 Climate Control Enable + 0x105 Room Temperature + 0x106 Security Enable + 0x107 Fire Alarm + 0x108 Police Alarm + 0x150 Balance Right + 0x151 Balance Left + 0x152 Bass Increment + 0x153 Bass Decrement + 0x154 Treble Increment + 0x155 Treble Decrement + 0x160 Speaker System + 0x161 Channel Left + 0x162 Channel Right + 0x163 Channel Center + 0x164 Channel Front + 0x165 Channel Center Front + 0x166 Channel Side + 0x167 Channel Surround + 0x168 Channel Low Frequency Enhancement + 0x169 Channel Top + 0x16A Channel Unknown + 0x170 Sub-channel + 0x171 Sub-channel Increment + 0x172 Sub-channel Decrement + 0x173 Alternate Audio Increment + 0x174 Alternate Audio Decrement + 0x180 Application Launch Buttons + 0x181 AL Launch Button Configuration Tool + 0x182 AL Programmable Button Configuration + 0x183 AL Consumer Control Configuration + 0x184 AL Word Processor + 0x185 AL Text Editor + 0x186 AL Spreadsheet + 0x187 AL Graphics Editor + 0x188 AL Presentation App + 0x189 AL Database App + 0x18A AL Email Reader + 0x18B AL Newsreader + 0x18C AL Voicemail + 0x18D AL Contacts/Address Book + 0x18E AL Calendar/Schedule + 0x18F AL Task/Project Manager + 0x190 AL Log/Journal/Timecard + 0x191 AL Checkbook/Finance + 0x192 AL Calculator + 0x193 AL A/V Capture/Playback + 0x194 AL Local Machine Browser + 0x195 AL LAN/WAN Browser + 0x196 AL Internet Browser + 0x197 AL Remote Networking/ISP Connect + 0x198 AL Network Conference + 0x199 AL Network Chat + 0x19A AL Telephony/Dialer + 0x19B AL Logon + 0x19C AL Logoff + 0x19D AL Logon/Logoff + 0x19E AL Terminal Lock/Screensaver + 0x19F AL Control Panel + 0x1A0 AL Command Line Processor/Run + 0x1A1 AL Process/Task Manager + 0x1A2 AL Select Tast/Application + 0x1A3 AL Next Task/Application + 0x1A4 AL Previous Task/Application + 0x1A5 AL Preemptive Halt Task/Application + 0x200 Generic GUI Application Controls + 0x201 AC New + 0x202 AC Open + 0x203 AC Close + 0x204 AC Exit + 0x205 AC Maximize + 0x206 AC Minimize + 0x207 AC Save + 0x208 AC Print + 0x209 AC Properties + 0x21A AC Undo + 0x21B AC Copy + 0x21C AC Cut + 0x21D AC Paste + 0x21E AC Select All + 0x21F AC Find + 0x220 AC Find and Replace + 0x221 AC Search + 0x222 AC Go To + 0x223 AC Home + 0x224 AC Back + 0x225 AC Forward + 0x226 AC Stop + 0x227 AC Refresh + 0x228 AC Previous Link + 0x229 AC Next Link + 0x22A AC Bookmarks + 0x22B AC History + 0x22C AC Subscriptions + 0x22D AC Zoom In + 0x22E AC Zoom Out + 0x22F AC Zoom + 0x230 AC Full Screen View + 0x231 AC Normal View + 0x232 AC View Toggle + 0x233 AC Scroll Up + 0x234 AC Scroll Down + 0x235 AC Scroll + 0x236 AC Pan Left + 0x237 AC Pan Right + 0x238 AC Pan + 0x239 AC New Window + 0x23A AC Tile Horizontally + 0x23B AC Tile Vertically + 0x23C AC Format + +13 Digitizer + 0x00 Undefined + 0x01 Digitizer + 0x02 Pen + 0x03 Light Pen + 0x04 Touch Screen + 0x05 Touch Pad + 0x06 White Board + 0x07 Coordinate Measuring Machine + 0x08 3-D Digitizer + 0x09 Stereo Plotter + 0x0A Articulated Arm + 0x0B Armature + 0x0C Multiple Point Digitizer + 0x0D Free Space Wand + 0x20 Stylus + 0x21 Puck + 0x22 Finger + 0x30 Tip Pressure + 0x31 Barrel Pressure + 0x32 In Range + 0x33 Touch + 0x34 Untouch + 0x35 Tap + 0x36 Quality + 0x37 Data Valid + 0x38 Transducer Index + 0x39 Tablet Function Keys + 0x3A Program Change Keys + 0x3B Battery Strength + 0x3C Invert + 0x3D X Tilt + 0x3E Y Tilt + 0x3F Azimuth + 0x40 Altitude + 0x41 Twist + 0x42 Tip Switch + 0x43 Secondary Tip Switch + 0x44 Barrel Switch + 0x45 Eraser + 0x46 Tablet Pick + +15 Physical Interface Device + +16 Unicode + * Unicode Char u%04x + +20 Alphnumeric Display + 0x00 Undefined + 0x01 Alphanumeric Display + 0x20 Display Attributes Report + 0x21 ASCII Character Set + 0x22 Data Read Back + 0x23 Font Read Back + 0x24 Display Control Report + 0x25 Clear Display + 0x26 Display Enable + 0x27 Screen Saver Delay + 0x28 Screen Saver Enable + 0x29 Vertical Scroll + 0x2A Horizontal Scroll + 0x2B Character Report + 0x2C Display Data + 0x2D Display Status + 0x2E Stat Not Ready + 0x2F Stat Ready + 0x30 Err Not a loadable character + 0x31 Err Font data cannot be read + 0x32 Cursor Position Report + 0x33 Row + 0x34 Column + 0x35 Rows + 0x36 Columns + 0x37 Cursor Pixel Positioning + 0x38 Cursor Mode + 0x39 Cursor Enable + 0x3A Cursor Blink + 0x3B Font Report + 0x3C Font Data + 0x3D Character Width + 0x3E Character Height + 0x3F Character Spacing Horizontal + 0x40 Character Spacing Vertical + 0x41 Unicode Character Set + +128 Monitor + 0x00 Undefined + 0x01 Monitor Control + 0x02 EDID Information + 0x03 VDIF Information + 0x04 VESA Version + 0x05 On Screen Display + 0x06 Auto Size Center + 0x07 Polarity Horz Synch + 0x08 Polarity Vert Synch + 0x09 Sync Type + 0x0A Screen Position + 0x0B Horizontal Frequency + 0x0C Vertical Frequency + +129 Monitor Enumerated Values + 0x00 unassigned + * ENUM %d + +130 VESA Virtual Controls + 0x10 Brightness + 0x12 Contrast + 0x16 Video Gain Red + 0x18 Video Gain Green + 0x1A Video Gain Blue + 0x1C Focus + 0x20 Horizontal Position + 0x22 Horizontal Size + 0x24 Horizontal Pincushion + 0x26 Horizontal Pincushion Balance + 0x28 Horizontal Misconvergence + 0x2A Horizontal Linearity + 0x2C Horizontal Linearity Balance + 0x30 Vertical Position + 0x32 Vertical Size + 0x34 Vertical Pincushion + 0x36 Vertical Pincushion Balance + 0x38 Vertical Misconvergence + 0x3A Vertical Linearity + 0x3C Vertical Linearity Balance + 0x40 Parallelogram Distortion + 0x42 Trapezoidal Distortion + 0x44 Tilt + 0x46 Top Corner Distortion Control + 0x48 Top Corner Distortion Balance + 0x4A Bottom Corner Distortion Control + 0x4C Bottom Corner Distortion Balance + 0x56 Moiré Horizontal + 0x58 Moiré Vertical + 0x5E Input Level Select + 0x60 Input Source Select + 0x62 Stereo Mode + 0x6C Video Black Level Red + 0x6E Video Black Level Green + 0x70 Video Black Level Blue + +131 VESA Command + 0x00 Undefined + 0x01 Settings + 0x02 Degauss + +132 Power Device + 0x00 Undefined + 0x01 iName + 0x02 PresentStatus + 0x03 ChangedStatus + 0x04 UPS + 0x05 PowerSupply + 0x10 BatterySystem + 0x11 BatterySystemID + 0x12 Battery + 0x13 BatteryID + 0x14 Charger + 0x15 ChargerID + 0x16 PowerConverter + 0x17 PowerConverterID + 0x18 OutletSystem + 0x19 OutletSystemID + 0x1A Input + 0x1B InputID + 0x1C Output + 0x1D OutputID + 0x1E Flow + 0x1F FlowID + 0x20 Outlet + 0x21 OutletID + 0x22 Gang + 0x23 GangID + 0x24 Sink + 0x25 SinkID + 0x30 Voltage + 0x31 Current + 0x32 Frequency + 0x33 ApparentPower + 0x34 ActivePower + 0x35 PercentLoad + 0x36 Temperature + 0x37 Humidity + 0x40 ConfigVoltage + 0x41 ConfigCurrent + 0x42 ConfigFrequency + 0x43 ConfigApparentPower + 0x44 ConfigActivePower + 0x45 ConfigPercentLoad + 0x46 ConfigTemperature + 0x47 ConfigHumidity + 0x50 SwitchOnControl + 0x51 SwitchOffControl + 0x52 ToggleControl + 0x53 LowVoltageTransfer + 0x54 HighVoltageTransfer + 0x55 DelayBeforeReboot + 0x56 DelayBeforeStartup + 0x57 DelayBeforeShutdown + 0x58 Test + 0x59 Vendorspecificcommand + 0x60 Present + 0x61 Good + 0x62 InternalFailure + 0x63 VoltageOutOfRange + 0x64 FrequencyOutOfRange + 0x65 Overload + 0x66 OverCharged + 0x67 OverTemperature + 0x68 ShutdownRequested + 0x69 ShutdownImminent + 0x6A VendorSpecificAnswerValid + 0x6B SwitchOn/Off + 0x6C Switcheble + 0x6D Used + 0x6E Boost + 0x6F Buck + 0x70 Initialized + 0x71 Tested + +133 Battery System + 0x00 Undefined + 0x01 SMBBatteryMode + 0x02 SMBBatteryStatus + 0x03 SMBAlarmWarning + 0x04 SMBChargerMode + 0x05 SMBChargerStatus + 0x06 SMBChargerSpecInfo + 0x07 SMBSelectorState + 0x08 SMBSelectorPreset + 0x09 SMBSelectorInfo + 0x10 OptionalMfgFunction1 + 0x11 OptionalMfgFunction2 + 0x12 OptionalMfgFunction3 + 0x13 OptionalMfgFunction4 + 0x14 OptionalMfgFunction5 + 0x15 ConnectionToSMBus + 0x16 OutputConnection + 0x17 ChargerConnection + 0x18 BatteryInsertion + 0x19 Usenext + 0x1A OKToUse + 0x28 ManufacturerAccess + 0x29 RemainingCapacityLimit + 0x2A RemainingTimeLimit + 0x2B AtRate + 0x2C CapacityMode + 0x2D BroadcastToCharger + 0x2E PrimaryBattery + 0x2F ChargeController + 0x40 TerminateCharge + 0x41 TermminateDischarge + 0x42 BelowRemainingCapacityLimit + 0x43 RemainingTimeLimitExpired + 0x44 Charging + 0x45 Discharging + 0x46 FullyCharged + 0x47 FullyDischarged + 0x48 ConditionningFlag + 0x49 AtRateOK + 0x4A SMBErrorCode + 0x4B NeedReplacement + 0x60 AtRateTimeToFull + 0x61 AtRateTimeToEmpty + 0x62 AverageCurrent + 0x63 Maxerror + 0x64 RelativeStateOfCharge + 0x65 AbsoluteStateOfCharge + 0x66 RemainingCapacity + 0x67 FullChargeCapacity + 0x68 RunTimeToEmpty + 0x69 AverageTimeToEmpty + 0x6A AverageTimeToFull + 0x6B CycleCount + 0x80 BattPackModelLevel + 0x81 InternalChargeController + 0x82 PrimaryBatterySupport + 0x83 DesignCapacity + 0x84 SpecificationInfo + 0x85 ManufacturerDate + 0x86 SerialNumber + 0x87 iManufacturerName + 0x88 iDevicename + 0x89 iDeviceChemistery + 0x8A iManufacturerData + 0x8B Rechargeable + 0x8C WarningCapacityLimit + 0x8D CapacityGranularity1 + 0x8E CapacityGranularity2 + 0xC0 InhibitCharge + 0xC1 EnablePolling + 0xC2 ResetToZero + 0xD0 ACPresent + 0xD1 BatteryPresent + 0xD2 PowerFail + 0xD3 AlarmInhibited + 0xD4 ThermistorUnderRange + 0xD5 ThermistorHot + 0xD6 ThermistorCold + 0xD7 ThermistorOverRange + 0xD8 VoltageOutOfRange + 0xD9 CurrentOutOfRange + 0xDA CurrentNotRegulated + 0xDB VoltageNotRegulated + 0xDC MasterMode + 0xDD ChargerBattery/HostControlled + 0xF0 ChargerSpecInfo + 0xF1 ChargerSpecRef + 0xF2 Level2 + 0xF3 Level3 + +140 Bar Code Scanner + +141 Scale Device + +144 Camera Control + +145 Arcade Device + +# Some Micro$oft non-standard extensions +0xff00 Microsoft + 0xe9 Base Up + 0xea Base Down + +# APC non-standard page? (1.6.2003) Riccardo "VIC" Torrini +0xff84 __APC_Power Device + 0x00 Undefined + 0x01 iName + 0x02 PresentStatus + 0x03 ChangedStatus + 0x04 UPS + 0x05 PowerSupply + 0x10 BatterySystem + 0x11 BatterySystemID + 0x12 Battery + 0x13 BatteryID + 0x14 Charger + 0x15 ChargerID + 0x16 PowerConverter + 0x17 PowerConverterID + 0x18 OutletSystem + 0x19 OutletSystemID + 0x1A Input + 0x1B InputID + 0x1C Output + 0x1D OutputID + 0x1E Flow + 0x1F FlowID + 0x20 Outlet + 0x21 OutletID + 0x22 Gang + 0x23 GangID + 0x24 Sink + 0x25 SinkID + 0x30 Voltage + 0x31 Current + 0x32 Frequency + 0x33 ApparentPower + 0x34 ActivePower + 0x35 PercentLoad + 0x36 Temperature + 0x37 Humidity + 0x40 ConfigVoltage + 0x41 ConfigCurrent + 0x42 ConfigFrequency + 0x43 ConfigApparentPower + 0x44 ConfigActivePower + 0x45 ConfigPercentLoad + 0x46 ConfigTemperature + 0x47 ConfigHumidity + 0x50 SwitchOnControl + 0x51 SwitchOffControl + 0x52 ToggleControl + 0x53 LowVoltageTransfer + 0x54 HighVoltageTransfer + 0x55 DelayBeforeReboot + 0x56 DelayBeforeStartup + 0x57 DelayBeforeShutdown + 0x58 Test + 0x59 Vendorspecificcommand + 0x60 Present + 0x61 Good + 0x62 InternalFailure + 0x63 VoltageOutOfRange + 0x64 FrequencyOutOfRange + 0x65 Overload + 0x66 OverCharged + 0x67 OverTemperature + 0x68 ShutdownRequested + 0x69 ShutdownImminent + 0x6A VendorSpecificAnswerValid + 0x6B SwitchOn/Off + 0x6C Switcheble + 0x6D Used + 0x6E Boost + 0x6F Buck + 0x70 Initialized + 0x71 Tested + +0xff85 __APC_Battery System + 0x00 Undefined + 0x01 SMBBatteryMode + 0x02 SMBBatteryStatus + 0x03 SMBAlarmWarning + 0x04 SMBChargerMode + 0x05 SMBChargerStatus + 0x06 SMBChargerSpecInfo + 0x07 SMBSelectorState + 0x08 SMBSelectorPreset + 0x09 SMBSelectorInfo + 0x10 OptionalMfgFunction1 + 0x11 OptionalMfgFunction2 + 0x12 OptionalMfgFunction3 + 0x13 OptionalMfgFunction4 + 0x14 OptionalMfgFunction5 + 0x15 ConnectionToSMBus + 0x16 OutputConnection + 0x17 ChargerConnection + 0x18 BatteryInsertion + 0x19 Usenext + 0x1A OKToUse + 0x28 ManufacturerAccess + 0x29 RemainingCapacityLimit + 0x2A RemainingTimeLimit + 0x2B AtRate + 0x2C CapacityMode + 0x2D BroadcastToCharger + 0x2E PrimaryBattery + 0x2F ChargeController + 0x40 TerminateCharge + 0x41 TermminateDischarge + 0x42 BelowRemainingCapacityLimit + 0x43 RemainingTimeLimitExpired + 0x44 Charging + 0x45 Discharging + 0x46 FullyCharged + 0x47 FullyDischarged + 0x48 ConditionningFlag + 0x49 AtRateOK + 0x4A SMBErrorCode + 0x4B NeedReplacement + 0x60 AtRateTimeToFull + 0x61 AtRateTimeToEmpty + 0x62 AverageCurrent + 0x63 Maxerror + 0x64 RelativeStateOfCharge + 0x65 AbsoluteStateOfCharge + 0x66 RemainingCapacity + 0x67 FullChargeCapacity + 0x68 RunTimeToEmpty + 0x69 AverageTimeToEmpty + 0x6A AverageTimeToFull + 0x6B CycleCount + 0x80 BattPackModelLevel + 0x81 InternalChargeController + 0x82 PrimaryBatterySupport + 0x83 DesignCapacity + 0x84 SpecificationInfo + 0x85 ManufacturerDate + 0x86 SerialNumber + 0x87 iManufacturerName + 0x88 iDevicename + 0x89 iDeviceChemistery + 0x8A iManufacturerData + 0x8B Rechargeable + 0x8C WarningCapacityLimit + 0x8D CapacityGranularity1 + 0x8E CapacityGranularity2 + 0xC0 InhibitCharge + 0xC1 EnablePolling + 0xC2 ResetToZero + 0xD0 ACPresent + 0xD1 BatteryPresent + 0xD2 PowerFail + 0xD3 AlarmInhibited + 0xD4 ThermistorUnderRange + 0xD5 ThermistorHot + 0xD6 ThermistorCold + 0xD7 ThermistorOverRange + 0xD8 VoltageOutOfRange + 0xD9 CurrentOutOfRange + 0xDA CurrentNotRegulated + 0xDB VoltageNotRegulated + 0xDC MasterMode + 0xDD ChargerBattery/HostControlled + 0xF0 ChargerSpecInfo + 0xF1 ChargerSpecRef + 0xF2 Level2 + 0xF3 Level3 diff --git a/src/main/ddrhook/Module.mk b/src/main/ddrhook/Module.mk new file mode 100644 index 0000000..1af5180 --- /dev/null +++ b/src/main/ddrhook/Module.mk @@ -0,0 +1,30 @@ +avsdlls += ddrhook + +deplibs_ddrhook := \ + avs \ + +libs_ddrhook := \ + acioemu \ + p3ioemu \ + p3io \ + hook \ + hooklib \ + security \ + util \ + eamio \ + ddrio \ + +src_ddrhook := \ + extio.c \ + dllmain.c \ + _com4.c \ + dinput.c \ + gfx.c \ + guid.c \ + master.c \ + misc.c \ + monitor.c \ + p3io.c \ + usbmem.c \ + spike.c + diff --git a/src/main/ddrhook/_com4.c b/src/main/ddrhook/_com4.c new file mode 100644 index 0000000..fc1b23c --- /dev/null +++ b/src/main/ddrhook/_com4.c @@ -0,0 +1,106 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "acioemu/addr.h" +#include "acioemu/hdxs.h" +#include "acioemu/icca.h" + +#include "ddrhook/_com4.h" + +#include "hook/iohook.h" + +#include "p3ioemu/uart.h" + +#include "util/defs.h" +#include "util/iobuf.h" +#include "util/log.h" + +static struct ac_io_emu com4_ac_io_emu; +static struct ac_io_emu_hdxs com4_hdxs; +static struct ac_io_emu_icca com4_icca[2]; + +void com4_init(void) +{ + uint8_t i; + + /* This isn't a real COM port, we configure the core P3IO emulator code to + generate IRPs addressed to COM4 and then we intercept them. */ + + p3io_uart_set_path(0, L"COM4"); + ac_io_emu_init(&com4_ac_io_emu, L"COM4"); + ac_io_emu_hdxs_init(&com4_hdxs, &com4_ac_io_emu); + + for (i = 0 ; i < lengthof(com4_icca) ; i++) { + ac_io_emu_icca_init(&com4_icca[i], &com4_ac_io_emu, i); + } +} + +void com4_fini(void) +{ + ac_io_emu_fini(&com4_ac_io_emu); +} + +HRESULT com4_dispatch_irp(struct irp *irp) +{ + const struct ac_io_message *msg; + HRESULT hr; + + log_assert(irp != NULL); + + if (!ac_io_emu_match_irp(&com4_ac_io_emu, irp)) { + return irp_invoke_next(irp); + } + + for (;;) { + hr = ac_io_emu_dispatch_irp(&com4_ac_io_emu, irp); + + if (hr != S_OK) { + return hr; + } + + msg = ac_io_emu_request_peek(&com4_ac_io_emu); + + switch (msg->addr) { + case 0: + ac_io_emu_cmd_assign_addrs(&com4_ac_io_emu, msg, 3); + + break; + + case 1: + ac_io_emu_icca_dispatch_request(&com4_icca[0], msg); + + break; + + case 2: + ac_io_emu_icca_dispatch_request(&com4_icca[1], msg); + + break; + + case 3: + ac_io_emu_hdxs_dispatch_request(&com4_hdxs, msg); + + break; + + case AC_IO_BROADCAST: + log_warning("Broadcast(?) message on p3io ACIO bus?"); + + break; + + default: + log_warning("p3io ACIO message on unhandled bus address: %d", + msg->addr); + + break; + } + + ac_io_emu_request_pop(&com4_ac_io_emu); + } +} diff --git a/src/main/ddrhook/_com4.h b/src/main/ddrhook/_com4.h new file mode 100644 index 0000000..e206ff3 --- /dev/null +++ b/src/main/ddrhook/_com4.h @@ -0,0 +1,10 @@ +#ifndef IIDXHOOK_COM4_H +#define IIDXHOOK_COM4_H + +#include "hook/iohook.h" + +void com4_init(void); +void com4_fini(void); +HRESULT com4_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/ddrhook/ddrhook.def b/src/main/ddrhook/ddrhook.def new file mode 100644 index 0000000..85d3a1e --- /dev/null +++ b/src/main/ddrhook/ddrhook.def @@ -0,0 +1,4 @@ +LIBRARY ddrhook + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/ddrhook/dinput.c b/src/main/ddrhook/dinput.c new file mode 100644 index 0000000..14a5543 --- /dev/null +++ b/src/main/ddrhook/dinput.c @@ -0,0 +1,85 @@ +#include + +#define DIRECTINPUT_VERSION 0x0800 +#include + +#include + +#include "hook/com-proxy.h" +#include "hook/pe.h" +#include "hook/table.h" + +#include "util/defs.h" +#include "util/log.h" + +static HRESULT STDCALL my_DirectInput8Create( HINSTANCE hinst, DWORD dwVersion, + REFIID riidltf, LPVOID * ppvOut, LPVOID punkOuter); + +static HRESULT (STDCALL *real_DirectInput8Create)( HINSTANCE hinst, DWORD dwVersion, + REFIID riidltf, LPVOID * ppvOut, LPVOID punkOuter); + +static const struct hook_symbol dinput_syms[] = { + { + .name = "DirectInput8Create", + .patch = my_DirectInput8Create, + .link = (void **) &real_DirectInput8Create, + }, +}; + +static HRESULT STDCALL my_CreateDevice( + IDirectInput8W *self, + REFGUID rguid, + LPDIRECTINPUTDEVICE8W * lplpDirectInputDevice, + LPUNKNOWN pUnkOuter +) { + log_misc("IDirectInput8::CreateDevice hook hit"); + return DIERR_NOINTERFACE; +} + +static HRESULT STDCALL my_EnumDevices( + IDirectInput8W *self, + DWORD dwDevType, + LPDIENUMDEVICESCALLBACKW lpCallback, + LPVOID pvRef, + DWORD dwFlags +) { + log_misc("IDirectInput8::EnumDevices hook hit"); + + return DI_OK; +} + +static HRESULT STDCALL my_DirectInput8Create( HINSTANCE hinst, DWORD dwVersion, + REFIID riidltf, LPVOID * ppvOut, LPVOID punkOuter) +{ + IDirectInput8W *api; + IDirectInput8WVtbl *api_vtbl; + struct com_proxy *api_proxy; + HRESULT res; + + log_info("DirectInput8Create hook hit"); + + res = real_DirectInput8Create(hinst, dwVersion, riidltf, (LPVOID *)&api, punkOuter); + if(res != DI_OK) { + return res; + } + api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl)); + api_vtbl = api_proxy->vptr; + + api_vtbl->EnumDevices = my_EnumDevices; + api_vtbl->CreateDevice = my_CreateDevice; + + *(IDirectInput8W**)ppvOut = (IDirectInput8W *)api_proxy; + + return res; +} + +void dinput_init(HMODULE target) +{ + hook_table_apply( + target, + "dinput8.dll", + dinput_syms, + lengthof(dinput_syms)); + + log_info("Inserted dinput hooks into %p", target); +} diff --git a/src/main/ddrhook/dinput.h b/src/main/ddrhook/dinput.h new file mode 100644 index 0000000..4748d3d --- /dev/null +++ b/src/main/ddrhook/dinput.h @@ -0,0 +1,8 @@ +#ifndef HOOK_DINPUT_H +#define HOOK_DINPUT_H + +#include + +void dinput_init(HMODULE target); + +#endif diff --git a/src/main/ddrhook/dllmain.c b/src/main/ddrhook/dllmain.c new file mode 100644 index 0000000..f9ce666 --- /dev/null +++ b/src/main/ddrhook/dllmain.c @@ -0,0 +1,154 @@ +#include + +#include + +#include "bemanitools/ddrio.h" +#include "bemanitools/eamio.h" + +#include "ddrhook/_com4.h" +#include "ddrhook/extio.h" +#include "ddrhook/gfx.h" +#include "ddrhook/master.h" +#include "ddrhook/p3io.h" +#include "ddrhook/spike.h" +#include "ddrhook/usbmem.h" + +#include "hook/iohook.h" + +#include "hooklib/app.h" +#include "hooklib/rs232.h" + +#include "imports/avs.h" + +#include "p3ioemu/emu.h" + +#include "util/cmdline.h" +#include "util/defs.h" +#include "util/log.h" + +static bool my_dll_entry_init(char *sidcode, struct property_node *param); +static bool my_dll_entry_main(void); + +bool standard_def; +bool _15khz; + +static const irp_handler_t ddrhook_handlers[] = { + p3io_emu_dispatch_irp, + extio_dispatch_irp, + spike_dispatch_irp, + usbmem_dispatch_irp, + com4_dispatch_irp, +}; + +static bool my_dll_entry_init(char *sidcode, struct property_node *param) +{ + int argc; + char **argv; + bool ok; + int i; + + log_info("--- Begin ddrhook dll_entry_init ---"); + + args_recover(&argc, &argv); + + for (i = 1 ; i < argc ; i++) { + if (argv[i][0] != '-') { + continue; + } + + switch (argv[i][1]) { + case 'o': + standard_def = true; + + break; + + case 'w': + gfx_set_windowed(); + + break; + } + } + + args_free(argc, argv); + + iohook_init(ddrhook_handlers, lengthof(ddrhook_handlers)); + rs232_hook_init(); + + master_insert_hooks(NULL); + p3io_ddr_init(); + extio_init(); + usbmem_init(); + spike_init(); + com4_init(); + + log_info("Initializing DDR IO backend"); + + ddr_io_set_loggers( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + ok = ddr_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + + if (!ok) { + return false; + } + + log_info("Initializing card reader backend"); + + eam_io_set_loggers( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + ok = eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + + if (!ok) { + return false; + } + + log_info("--- End ddrhook dll_entry_init ---"); + + return app_hook_invoke_init(sidcode, param); +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + log_misc("Shutting down card reader backend"); + eam_io_fini(); + + log_misc("Shutting down DDR IO backend"); + ddr_io_fini(); + + com4_fini(); + spike_fini(); + usbmem_fini(); + extio_fini(); + p3io_emu_fini(); + + return result; +} + +BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) +{ + if (reason != DLL_PROCESS_ATTACH) { + goto end; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + +end: + return TRUE; +} diff --git a/src/main/ddrhook/extio.c b/src/main/ddrhook/extio.c new file mode 100644 index 0000000..eed44af --- /dev/null +++ b/src/main/ddrhook/extio.c @@ -0,0 +1,156 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "bemanitools/ddrio.h" + +#include "hook/iohook.h" + +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +static HRESULT extio_open(struct irp *irp); +static HRESULT extio_close(struct irp *irp); +static HRESULT extio_write(struct irp *irp); +static HRESULT extio_read(struct irp *irp); +static HRESULT extio_ioctl(struct irp *irp); + +static HANDLE extio_fd; +static bool extio_pending; + +void extio_init(void) +{ + log_assert(extio_fd == NULL); + + extio_fd = iohook_open_dummy_fd(); +} + +void extio_fini(void) +{ + if (extio_fd != NULL) { + CloseHandle(extio_fd); + } + + extio_fd = NULL; +} + +HRESULT extio_dispatch_irp(struct irp *irp) +{ + log_assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != extio_fd) { + return irp_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return extio_open(irp); + case IRP_OP_CLOSE: return extio_close(irp); + case IRP_OP_READ: return extio_read(irp); + case IRP_OP_WRITE: return extio_write(irp); + case IRP_OP_IOCTL: return extio_ioctl(irp); + default: return E_NOTIMPL; + } +} + +static HRESULT extio_open(struct irp *irp) +{ + log_assert(irp != NULL); + + if (!wstr_eq(irp->open_filename, L"COM1")) { + return irp_invoke_next(irp); + } + + log_info("EXTIO RS232 port opened"); + irp->fd = extio_fd; + + return S_OK; +} + +static HRESULT extio_close(struct irp *irp) +{ + log_info("EXTIO RS232 port closed"); + + return S_OK; +} + +static HRESULT extio_write(struct irp *irp) +{ + const uint32_t *lights_be; + uint32_t lights; + + log_assert(irp != NULL); + log_assert(irp->write.bytes != NULL); + + if (irp->write.nbytes >= sizeof(lights)) { + lights_be = (const uint32_t *) irp->write.bytes; + lights = _byteswap_ulong(*lights_be); + ddr_io_set_lights_extio(lights); + + extio_pending = true; + } else { + log_warning("Short EXTIO write"); + } + + irp->write.pos = irp->write.nbytes; + + return S_OK; +} + +static HRESULT extio_read(struct irp *irp) +{ + log_assert(irp != NULL); + log_assert(irp->read.bytes != NULL); + + if (extio_pending && irp->read.nbytes > 0) { + irp->read.bytes[0] = 0x11; + irp->read.pos = 1; + extio_pending = false; + } + + return S_OK; +} + +static HRESULT extio_ioctl(struct irp *irp) +{ + SERIAL_STATUS *status; + + log_assert(irp != NULL); + + switch (irp->ioctl) { + case IOCTL_SERIAL_GET_COMMSTATUS: + if (irp->read.bytes == NULL) { + log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Output buffer is NULL"); + + return E_INVALIDARG; + } + + if (irp->read.nbytes < sizeof(*status)) { + log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Buffer is too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + status = (SERIAL_STATUS *) irp->read.bytes; + status->Errors = 0; + status->AmountInInQueue = extio_pending ? 1 : 0; + + irp->read.pos = sizeof(*status); + + break; + + default: + break; + } + + return S_OK; +} + diff --git a/src/main/ddrhook/extio.h b/src/main/ddrhook/extio.h new file mode 100644 index 0000000..4253c07 --- /dev/null +++ b/src/main/ddrhook/extio.h @@ -0,0 +1,12 @@ +#ifndef DDRHOOK_EXTIO_H +#define DDRHOOK_EXTIO_H + +#include + +#include "hook/iohook.h" + +void extio_init(void); +void extio_fini(void); +HRESULT extio_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/ddrhook/gfx.c b/src/main/ddrhook/gfx.c new file mode 100644 index 0000000..d442f54 --- /dev/null +++ b/src/main/ddrhook/gfx.c @@ -0,0 +1,89 @@ +#include +#include + +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "util/defs.h" +#include "util/log.h" + +static HRESULT STDCALL my_CreateDevice( + IDirect3D9 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, + DWORD flags, D3DPRESENT_PARAMETERS *pp, IDirect3DDevice9 **pdev); + +static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver); + +static IDirect3D9 * (STDCALL *real_Direct3DCreate9)( + UINT sdk_ver); + +static bool gfx_windowed; + +static const struct hook_symbol gfx_d3d9_hook_syms[] = { + { + .name = "Direct3DCreate9", + .patch = my_Direct3DCreate9, + .link = (void **) &real_Direct3DCreate9, + }, +}; + +static HRESULT STDCALL my_CreateDevice( + IDirect3D9 *self, + UINT adapter, + D3DDEVTYPE type, + HWND hwnd, + DWORD flags, + D3DPRESENT_PARAMETERS *pp, + IDirect3DDevice9 **pdev) +{ + IDirect3D9 *real; + + real = COM_PROXY_UNWRAP(self); + + if (gfx_windowed) { + pp->Windowed = TRUE; + pp->FullScreen_RefreshRateInHz = 0; + } + + return IDirect3D9_CreateDevice(real, adapter, type, hwnd, flags, pp, pdev); +} + +static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver) +{ + IDirect3D9 *api; + IDirect3D9Vtbl *api_vtbl; + struct com_proxy *api_proxy; + + log_info("Direct3DCreate9 hook hit"); + + api = real_Direct3DCreate9(sdk_ver); + api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl)); + api_vtbl = api_proxy->vptr; + + api_vtbl->CreateDevice = my_CreateDevice; + + return (IDirect3D9 *) api_proxy; +} + +void gfx_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "d3d9.dll", + gfx_d3d9_hook_syms, + lengthof(gfx_d3d9_hook_syms)); + + log_info("Inserted graphics hooks"); +} + +bool gfx_get_windowed(void) +{ + return gfx_windowed; +} + +void gfx_set_windowed(void) +{ + gfx_windowed = true; +} + diff --git a/src/main/ddrhook/gfx.h b/src/main/ddrhook/gfx.h new file mode 100644 index 0000000..da7e6d5 --- /dev/null +++ b/src/main/ddrhook/gfx.h @@ -0,0 +1,12 @@ +#ifndef DDRHOOK_GFX_H +#define DDRHOOK_GFX_H + +#include + +#include + +void gfx_insert_hooks(HMODULE target); +bool gfx_get_windowed(void); +void gfx_set_windowed(void); + +#endif diff --git a/src/main/ddrhook/guid.c b/src/main/ddrhook/guid.c new file mode 100644 index 0000000..c0096d2 --- /dev/null +++ b/src/main/ddrhook/guid.c @@ -0,0 +1,4 @@ +#include +#include + +#include "ddrhook/monitor.h" diff --git a/src/main/ddrhook/master.c b/src/main/ddrhook/master.c new file mode 100644 index 0000000..b817265 --- /dev/null +++ b/src/main/ddrhook/master.c @@ -0,0 +1,69 @@ +#include "ddrhook/dinput.h" +#include "ddrhook/gfx.h" +#include "ddrhook/master.h" +#include "ddrhook/misc.h" +#include "ddrhook/monitor.h" + +#include "hook/table.h" + +#include "p3ioemu/devmgr.h" + +#include "util/defs.h" +#include "util/log.h" + +static HMODULE (STDCALL *real_LoadLibraryA)(const char *name); + +static HMODULE STDCALL my_LoadLibraryA(const char *name); + +static const struct hook_symbol master_kernel32_syms[] = { + { + .name = "LoadLibraryA", + .patch = my_LoadLibraryA, + .link = (void **) &real_LoadLibraryA, + }, +}; + +static HMODULE STDCALL my_LoadLibraryA(const char *name) +{ + HMODULE result; + + result = GetModuleHandleA(name); + + if (result != NULL) { + log_misc("LoadLibraryA(%s) -> %p [already loaded]", name, result); + + return result; + } + + result = real_LoadLibraryA(name); + log_misc("LoadLibraryA(%s) -> %p [newly loaded]", name, result); + + if (result == NULL) { + return result; + } + + master_insert_hooks(result); + + return result; +} + +void master_insert_hooks(HMODULE target) +{ + /* Insert all other hooks here */ + + p3io_setupapi_insert_hooks(target); + monitor_setupapi_insert_hooks(target); + misc_insert_hooks(target); + dinput_init(target); + gfx_insert_hooks(target); + + /* Insert dynamic loader hooks so that we can hook late-loaded modules */ + + hook_table_apply( + target, + "kernel32.dll", + master_kernel32_syms, + lengthof(master_kernel32_syms)); + + log_info("Inserted dynamic loader hooks into %p", target); +} diff --git a/src/main/ddrhook/master.h b/src/main/ddrhook/master.h new file mode 100644 index 0000000..52f0194 --- /dev/null +++ b/src/main/ddrhook/master.h @@ -0,0 +1,8 @@ +#ifndef DDRHOOK_MASTER_H +#define DDRHOOK_MASTER_H + +#include + +void master_insert_hooks(HMODULE target); + +#endif diff --git a/src/main/ddrhook/misc.c b/src/main/ddrhook/misc.c new file mode 100644 index 0000000..2b3facf --- /dev/null +++ b/src/main/ddrhook/misc.c @@ -0,0 +1,232 @@ +#include + +#include + +#include "ddrhook/gfx.h" + +#include "hook/pe.h" +#include "hook/table.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" + +static LONG (STDCALL *real_ChangeDisplaySettingsExA)( + char *dev_name, DEVMODE *dev_mode, HWND hwnd, DWORD flags, void *param); +static LRESULT (STDCALL *real_SendMessageW)( + HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); +static HWND (STDCALL *real_CreateWindowExW)(DWORD dwExStyle, LPCWSTR lpClassName, + LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, + int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam); +static LONG (STDCALL *real_SetWindowLongW)(HWND hWnd, int nIndex, LONG dwNewLong); +static BOOL (STDCALL *real_SetWindowPos)(HWND hWnd, HWND hWndInsertAfter, + int X, int Y, int cx, int cy, UINT uFlags); + +static LONG STDCALL my_ChangeDisplaySettingsExA( + char *dev_name, DEVMODE *dev_mode, HWND hwnd, DWORD flags, void *param); +static SHORT STDCALL my_GetKeyState(int vk); +static LRESULT STDCALL my_SendMessageW( + HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); +static HWND STDCALL my_CreateWindowExA( + DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam); +static HWND STDCALL my_CreateWindowExW(DWORD dwExStyle, LPCWSTR lpClassName, + LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, + int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam); +static LONG STDCALL my_SetWindowLongW(HWND hWnd, int nIndex, LONG dwNewLong); +static BOOL STDCALL my_SetWindowPos(HWND hWnd, HWND hWndInsertAfter, + int X, int Y, int cx, int cy, UINT uFlags); + +static const struct hook_symbol misc_user32_syms[] = { + { + .name = "ChangeDisplaySettingsExA", + .patch = my_ChangeDisplaySettingsExA, + .link = (void **) &real_ChangeDisplaySettingsExA, + }, + { + .name = "SendMessageW", + .patch = my_SendMessageW, + .link = (void **) &real_SendMessageW, + }, + { + .name = "GetKeyState", + .patch = my_GetKeyState, + }, + { + .name = "CreateWindowExA", + .patch = my_CreateWindowExA, + }, + { + .name = "CreateWindowExW", + .patch = my_CreateWindowExW, + .link = (void **) &real_CreateWindowExW, + }, + { + .name = "SetWindowLongW", + .patch = my_SetWindowLongW, + .link = (void **) &real_SetWindowLongW, + }, + { + .name = "SetWindowPos", + .patch = my_SetWindowPos, + .link = (void **) &real_SetWindowPos, + }, +}; + +static LONG STDCALL my_SetWindowLongW(HWND hWnd, int nIndex, LONG dwNewLong) { + if(nIndex == GWL_STYLE) + dwNewLong |= WS_OVERLAPPEDWINDOW; + return real_SetWindowLongW(hWnd, nIndex, dwNewLong); +} + +static BOOL STDCALL my_SetWindowPos(HWND hWnd, HWND hWndInsertAfter, + int X, int Y, int cx, int cy, UINT uFlags) { + return true; +} + +static LONG STDCALL my_ChangeDisplaySettingsExA( + char *dev_name, DEVMODE *dev_mode, HWND hwnd, DWORD flags, void *param) +{ + if (gfx_get_windowed()) { + return DISP_CHANGE_SUCCESSFUL; + } else { + return real_ChangeDisplaySettingsExA(dev_name, dev_mode, hwnd, flags, + param); + } +} + +static void calc_win_size_with_framed(HWND hwnd, DWORD x, DWORD y, DWORD width, + DWORD height, LPWINDOWPOS wp) +{ + /* taken from dxwnd */ + RECT rect; + DWORD style; + int max_x, max_y; + HMENU menu; + + rect.left = x; + rect.top = y; + max_x = width; + max_y = height; + rect.right = x + max_x; + rect.bottom = y + max_y; + + style = GetWindowLong(hwnd, GWL_STYLE); + menu = GetMenu(hwnd); + AdjustWindowRect(&rect, style, (menu != NULL)); + + /* shift down-right so that the border is visible + and also update the iPosX,iPosY upper-left coordinates + of the client area */ + + if (rect.left < 0) { + rect.right -= rect.left; + rect.left = 0; + } + + if (rect.top < 0) { + rect.bottom -= rect.top; + rect.top = 0; + } + + wp->x = rect.left; + wp->y = rect.top; + wp->cx = rect.right - rect.left; + wp->cy = rect.bottom - rect.top; +} + +static HWND STDCALL my_CreateWindowExW(DWORD dwExStyle, LPCWSTR lpClassName, + LPCWSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, + int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam) +{ + if (gfx_get_windowed()) { + /* use a different style */ + dwStyle |= WS_OVERLAPPEDWINDOW; + /* also show mouse cursor */ + ShowCursor(TRUE); + } + + if(!lpWindowName) + lpWindowName = L"Dance Dance Revolution"; + + HWND hwnd = real_CreateWindowExW(dwExStyle, lpClassName, lpWindowName, + dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, + lpParam); + + if (hwnd == INVALID_HANDLE_VALUE) { + return hwnd; + } + + if (gfx_get_windowed()) { + /* we have to adjust the window size, because the window needs to be a + slightly bigger than the rendering resolution (window caption and + stuff is included in the window size) */ + WINDOWPOS wp; + calc_win_size_with_framed(hwnd, X, Y, nWidth, nHeight, &wp); + SetWindowPos(hwnd, 0, wp.x, wp.y, wp.cx, wp.cy, 0); + X = wp.x; + Y = wp.y; + nWidth = wp.cx; + nHeight = wp.cy; + } + + return hwnd; +} + +static HWND STDCALL my_CreateWindowExA( + DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam) +{ + LPWSTR longWindowName = NULL; + LPWSTR longClassName = NULL; + if(lpWindowName) + longWindowName = str_widen(lpWindowName); + if(lpClassName) + longClassName = str_widen(lpClassName); + + HWND ret = my_CreateWindowExW(dwExStyle, longClassName, longWindowName, + dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + + if(longWindowName) + free(longWindowName); + if(longClassName) + free(longClassName); + + return ret; +} + +static SHORT STDCALL my_GetKeyState(int vk) +{ + /* yeah we kinda have our own keyboard input thing */ + return 0; +} + +static LRESULT STDCALL my_SendMessageW( + HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (hwnd == HWND_BROADCAST) { + /* OK COOL STORY BRO */ + log_misc("Dropping message sent to HWND_BROADCAST"); + + return TRUE; + } else { + return real_SendMessageW(hwnd, msg, wparam, lparam); + } +} + +void misc_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "user32.dll", + misc_user32_syms, + lengthof(misc_user32_syms)); + + log_info("Inserted miscellaneous hooks into %p", target); +} + diff --git a/src/main/ddrhook/misc.h b/src/main/ddrhook/misc.h new file mode 100644 index 0000000..15aeb5e --- /dev/null +++ b/src/main/ddrhook/misc.h @@ -0,0 +1,8 @@ +#ifndef HOOK_MISC_H +#define HOOK_MISC_H + +#include + +void misc_insert_hooks(HMODULE target); + +#endif diff --git a/src/main/ddrhook/monitor.c b/src/main/ddrhook/monitor.c new file mode 100644 index 0000000..9e664b9 --- /dev/null +++ b/src/main/ddrhook/monitor.c @@ -0,0 +1,208 @@ +#include +#include + +#include +#include + +#include "ddrhook/monitor.h" + +#include "hook/table.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" + +/* Link pointers */ + +static HDEVINFO (WINAPI *next_SetupDiGetClassDevsW)( + const GUID *class_guid, + const wchar_t *enumerator, + HWND hwnd, + DWORD flags); + +static BOOL (WINAPI *next_SetupDiEnumDeviceInfo)( + HDEVINFO dev_info, + DWORD index, + SP_DEVINFO_DATA *info_data); + +static BOOL (WINAPI *next_SetupDiGetDeviceRegistryPropertyA)( + HDEVINFO dev_info, + SP_DEVINFO_DATA *info_data, + DWORD prop, + DWORD *reg_type, + BYTE *bytes, + DWORD nbytes, + DWORD *need_nbytes); + +static BOOL (WINAPI *next_SetupDiDestroyDeviceInfoList)(HDEVINFO dev_info); + +/* API hooks */ + +static HDEVINFO WINAPI my_SetupDiGetClassDevsW( + const GUID *class_guid, + const wchar_t *enumerator, + HWND hwnd, + DWORD flags); + +static BOOL WINAPI my_SetupDiEnumDeviceInfo( + HDEVINFO dev_info, + DWORD index, + SP_DEVINFO_DATA *info_data); + +static BOOL WINAPI my_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO dev_info, + SP_DEVINFO_DATA *info_data, + DWORD prop, + DWORD *reg_type, + BYTE *bytes, + DWORD nbytes, + DWORD *need_nbytes); + +static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO dev_info); + +static const struct hook_symbol monitor_setupapi_syms[] = { + { + .name = "SetupDiGetClassDevsW", + .patch = my_SetupDiGetClassDevsW, + .link = (void **) &next_SetupDiGetClassDevsW, + }, { + .name = "SetupDiEnumDeviceInfo", + .patch = my_SetupDiEnumDeviceInfo, + .link = (void **) &next_SetupDiEnumDeviceInfo, + }, { + .name = "SetupDiGetDeviceRegistryPropertyA", + .patch = my_SetupDiGetDeviceRegistryPropertyA, + .link = (void **) &next_SetupDiGetDeviceRegistryPropertyA, + }, { + .name = "SetupDiDestroyDeviceInfoList", + .patch = my_SetupDiDestroyDeviceInfoList, + .link = (void **) &next_SetupDiDestroyDeviceInfoList, + }, +}; + +extern bool standard_def; +static HDEVINFO monitor_hdevinfo; + +static HDEVINFO WINAPI my_SetupDiGetClassDevsW( + const GUID *class_guid, + const wchar_t *enumerator, + HWND hwnd, + DWORD flags) +{ + HDEVINFO result; + + result = next_SetupDiGetClassDevsW(class_guid, enumerator, hwnd, flags); + + if ( result != INVALID_HANDLE_VALUE && + IsEqualGUID(class_guid, &monitor_guid)) { + monitor_hdevinfo = result; + } + + return result; +} + +static BOOL WINAPI my_SetupDiEnumDeviceInfo( + HDEVINFO dev_info, + DWORD index, + SP_DEVINFO_DATA *info_data) +{ + if (dev_info != monitor_hdevinfo) { + return next_SetupDiEnumDeviceInfo(dev_info, index, info_data); + } + + if (info_data == NULL || info_data->cbSize != sizeof(*info_data)) { + SetLastError(ERROR_INVALID_USER_BUFFER); + + return FALSE; + } + + if (index > 0) { + SetLastError(ERROR_NO_MORE_ITEMS); + + return FALSE; + } + + SetLastError(ERROR_SUCCESS); + + return TRUE; +} + +static BOOL WINAPI my_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO dev_info, + SP_DEVINFO_DATA *info_data, + DWORD prop, + DWORD *reg_type, + BYTE *bytes, + DWORD nbytes, + DWORD *nbytes_out) +{ + const char *txt; + size_t txt_nbytes; + + if (dev_info != monitor_hdevinfo) { + return next_SetupDiGetDeviceRegistryPropertyA( + dev_info, + info_data, + prop, + reg_type, + bytes, + nbytes, + nbytes_out); + } + + /* The only implemented property */ + log_assert(prop == SPDRP_DEVICEDESC); + + if (standard_def) { + txt = "Generic Television"; + } else { + txt = "Generic Monitor"; + } + + txt_nbytes = strlen(txt) + 1; + + if (reg_type != NULL) { + *reg_type = REG_SZ; + } + + if (nbytes_out != NULL) { + *nbytes_out = txt_nbytes; + } + + if (nbytes < txt_nbytes) { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + + return FALSE; + } + + if (bytes == NULL) { + SetLastError(ERROR_INVALID_USER_BUFFER); + + return FALSE; + } + + str_cpy((char *) bytes, nbytes, txt); + SetLastError(ERROR_SUCCESS); + + return TRUE; +} + +static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO dev_info) +{ + if (dev_info == monitor_hdevinfo) { + monitor_hdevinfo = NULL; + } + + return next_SetupDiDestroyDeviceInfoList(dev_info); +} + +void monitor_setupapi_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "setupapi.dll", + monitor_setupapi_syms, + lengthof(monitor_setupapi_syms)); + + log_info("Inserted monitor setupapi hooks into %p", target); +} diff --git a/src/main/ddrhook/monitor.h b/src/main/ddrhook/monitor.h new file mode 100644 index 0000000..0a597a5 --- /dev/null +++ b/src/main/ddrhook/monitor.h @@ -0,0 +1,15 @@ +#ifndef DDRHOOK_MONITOR_H +#define DDRHOOK_MONITOR_H + +#include + +DEFINE_GUID( + monitor_guid, + 0x4D36E96E, + 0xE325, + 0x11CE, + 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18); + +void monitor_setupapi_insert_hooks(HMODULE target); + +#endif diff --git a/src/main/ddrhook/p3io.c b/src/main/ddrhook/p3io.c new file mode 100644 index 0000000..a87f5bd --- /dev/null +++ b/src/main/ddrhook/p3io.c @@ -0,0 +1,79 @@ +#include + +#include +#include + +#include "bemanitools/ddrio.h" + +#include "ddrhook/p3io.h" + +#include "p3ioemu/emu.h" +#include "p3ioemu/uart.h" + +#include "util/log.h" + +extern bool _15khz; +extern bool standard_def; + +static HRESULT p3io_ddr_read_jamma(void *ctx, uint32_t *state); +static HRESULT p3io_ddr_set_outputs(void *ctx, uint32_t outputs); +static HRESULT p3io_ddr_get_cab_type(void *ctx, enum p3io_cab_type *type); +static HRESULT p3io_ddr_get_video_freq(void *ctx, enum p3io_video_freq *freq); + +static const struct p3io_ops p3io_ddr_ops = { + .read_jamma = p3io_ddr_read_jamma, + .set_outputs = p3io_ddr_set_outputs, + .get_cab_type = p3io_ddr_get_cab_type, + .get_video_freq = p3io_ddr_get_video_freq, +}; + +void p3io_ddr_init(void) +{ + p3io_emu_init(&p3io_ddr_ops, NULL); +} + +void p3io_ddr_fini(void) +{ + p3io_emu_fini(); +} + +static HRESULT p3io_ddr_read_jamma(void *ctx, uint32_t *state) +{ + log_assert(state != NULL); + + *state = ddr_io_read_pad(); + + return S_OK; +} + +static HRESULT p3io_ddr_set_outputs(void *ctx, uint32_t outputs) +{ + ddr_io_set_lights_p3io(outputs); + + return S_OK; +} + +static HRESULT p3io_ddr_get_cab_type(void *ctx, enum p3io_cab_type *type) +{ + if (standard_def) { + *type = P3IO_CAB_TYPE_SD; + } else { + *type = P3IO_CAB_TYPE_HD; + } + + return S_OK; +} + +static HRESULT p3io_ddr_get_video_freq(void *ctx, enum p3io_video_freq *freq) +{ + if (_15khz) { + *freq = P3IO_VIDEO_FREQ_15KHZ; + } else { + *freq = P3IO_VIDEO_FREQ_31KHZ; + } + + return S_OK; +} + +// TODO coinstock +// TODO round plug diff --git a/src/main/ddrhook/p3io.h b/src/main/ddrhook/p3io.h new file mode 100644 index 0000000..ae1570d --- /dev/null +++ b/src/main/ddrhook/p3io.h @@ -0,0 +1,14 @@ +#ifndef DDRHOOK_P3IO_H +#define DDRHOOK_P3IO_H + +#include + +#include "hook/iohook.h" + +extern const wchar_t p3io_dev_node_prefix[]; +extern const wchar_t p3io_dev_node[]; + +void p3io_ddr_init(void); +void p3io_ddr_fini(void); + +#endif diff --git a/src/main/ddrhook/spike.c b/src/main/ddrhook/spike.c new file mode 100644 index 0000000..d1c1191 --- /dev/null +++ b/src/main/ddrhook/spike.c @@ -0,0 +1,172 @@ +#define LOG_MODULE "spike" + +#include + +#include +#include +#include + +#include +#include +#include + +#include "acioemu/addr.h" +#include "acioemu/emu.h" + +#include "hook/iohook.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" + +static struct ac_io_emu spike_ac_io_emu; + +static void spike_handle_broadcast(const struct ac_io_message *bcast); +static void spike_handle_get_version(const struct ac_io_message *req); +static void spike_handle_status(const struct ac_io_message *req); +static void spike_send_empty(const struct ac_io_message *req); + +void spike_init(void) +{ + ac_io_emu_init(&spike_ac_io_emu, L"COM2"); +} + +void spike_fini(void) +{ + ac_io_emu_fini(&spike_ac_io_emu); +} + +HRESULT spike_dispatch_irp(struct irp *irp) +{ + const struct ac_io_message *msg; + HRESULT hr; + + log_assert(irp != NULL); + + if (!ac_io_emu_match_irp(&spike_ac_io_emu, irp)) { + return irp_invoke_next(irp); + } + + for (;;) { + hr = ac_io_emu_dispatch_irp(&spike_ac_io_emu, irp); + + if (hr != S_OK) { + return hr; + } + + msg = ac_io_emu_request_peek(&spike_ac_io_emu); + + switch (msg->addr) { + case 0: + ac_io_emu_cmd_assign_addrs(&spike_ac_io_emu, msg, 7); + + break; + + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + switch (ac_io_u16(msg->cmd.code)) { + case AC_IO_CMD_GET_VERSION: + spike_handle_get_version(msg); + + break; + + case AC_IO_CMD_START_UP: + spike_handle_status(msg); + + break; + + case AC_IO_CMD_KEEPALIVE: + spike_send_empty(msg); + + break; + + case 0x100: + case 0x110: + case 0x112: + case 0x128: + spike_handle_status(msg); + + break; + + default: + log_warning("Spike ACIO unhandled cmd: %04X", + ac_io_u16(msg->cmd.code)); + } + + break; + + case AC_IO_BROADCAST: + spike_handle_broadcast(msg); + + break; + + default: + log_warning( + "Spike ACIO message on unhandled bus address: %d", + msg->addr); + + break; + } + + ac_io_emu_request_pop(&spike_ac_io_emu); + } +} + +static void spike_handle_broadcast(const struct ac_io_message *bcast) +{ + /* The payload consists of 7x10 byte chunks. Chunk 0 seems to correspond + to the front LED strip, and chunks 1-6 are the 2x3 spike bundles on the + side of a dedicab. No idea about the format of this data other than + that, try figuring it out if you're bored. */ +} + +static void spike_handle_get_version(const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.version); + resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_LED_SPIKE); + resp.cmd.version.flag = 0x00; + resp.cmd.version.major = 0x01; + resp.cmd.version.minor = 0x01; + resp.cmd.version.revision = 0x00; + memcpy(resp.cmd.version.product_code, "DDRS", + sizeof(resp.cmd.version.product_code)); + strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date)); + strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time)); + + ac_io_emu_response_push(&spike_ac_io_emu, &resp, 0); +} + +static void spike_handle_status(const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.status); + resp.cmd.status = 0x00; + + ac_io_emu_response_push(&spike_ac_io_emu, &resp, 0); +} + +static void spike_send_empty(const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = 0; + + ac_io_emu_response_push(&spike_ac_io_emu, &resp, 0); +} diff --git a/src/main/ddrhook/spike.h b/src/main/ddrhook/spike.h new file mode 100644 index 0000000..29ebf31 --- /dev/null +++ b/src/main/ddrhook/spike.h @@ -0,0 +1,12 @@ +#ifndef DDRHOOK_SPIKE_H +#define DDRHOOK_SPIKE_H + +#include + +#include "hook/iohook.h" + +void spike_init(void); +void spike_fini(void); +HRESULT spike_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/ddrhook/usbmem.c b/src/main/ddrhook/usbmem.c new file mode 100644 index 0000000..1e5ac5c --- /dev/null +++ b/src/main/ddrhook/usbmem.c @@ -0,0 +1,190 @@ +#define LOG_MODULE "usbmem" + +#include + +#include +#include +#include + +#include +#include +#include + +#include "hook/iohook.h" + +#include "util/log.h" +#include "util/iobuf.h" +#include "util/str.h" + +#define USBMEM_BUF_SIZE 128 + +static HANDLE usbmem_fd; +static char usbmem_response[USBMEM_BUF_SIZE]; +static bool usbmem_pending; + +static HRESULT usbmem_open(struct irp *irp); +static HRESULT usbmem_close(struct irp *irp); +static HRESULT usbmem_write(struct irp *irp); +static HRESULT usbmem_read(struct irp *irp); +static HRESULT usbmem_ioctl(struct irp *irp); + +void usbmem_init(void) +{ + log_assert(usbmem_fd == NULL); + + usbmem_fd = iohook_open_dummy_fd(); +} + +void usbmem_fini(void) +{ + if (usbmem_fd != NULL) { + CloseHandle(usbmem_fd); + } + + usbmem_fd = NULL; +} + +HRESULT usbmem_dispatch_irp(struct irp *irp) +{ + log_assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != usbmem_fd) { + return irp_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return usbmem_open(irp); + case IRP_OP_CLOSE: return usbmem_close(irp); + case IRP_OP_READ: return usbmem_read(irp); + case IRP_OP_WRITE: return usbmem_write(irp); + case IRP_OP_IOCTL: return usbmem_ioctl(irp); + default: return E_NOTIMPL; + } +} + +static HRESULT usbmem_open(struct irp *irp) +{ + log_assert(irp != NULL); + + if (!wstr_eq(irp->open_filename, L"COM3")) { + return irp_invoke_next(irp); + } + + irp->fd = usbmem_fd; + log_info("USB edit data PCB opened"); + + return S_OK; +} + +static HRESULT usbmem_close(struct irp *irp) +{ + log_info("USB edit data PCB closed"); + + return S_OK; +} + +static HRESULT usbmem_write(struct irp *irp) +{ + struct const_iobuf *src; + char request[USBMEM_BUF_SIZE]; + uint32_t nbytes; + + log_assert(irp != NULL); + log_assert(irp->write.bytes != NULL); + + src = &irp->write; + nbytes = src->nbytes > USBMEM_BUF_SIZE ? USBMEM_BUF_SIZE : src->nbytes; + memcpy(request, src->bytes, nbytes); + request[nbytes - 1] = '\0'; /* This is always a CR. */ + + log_misc(">%s", request); + + if (strlen(request) > 0) { + if (str_eq(request, "sver")) { + str_cpy(usbmem_response, + sizeof(usbmem_response), + "done GQHDXJAA DJHACKRS"); + } else if ( + str_eq(request, "on_a") || + str_eq(request, "on_b") || + str_eq(request, "offa") || + str_eq(request, "offb") ) { + str_cpy(usbmem_response, sizeof(usbmem_response), "done"); + } else if ( + strncmp(request, "lma ", 4) == 0 || + strncmp(request, "lmb ", 4) == 0) { + str_cpy(usbmem_response, sizeof(usbmem_response), "done"); + } else { + str_cpy(usbmem_response, sizeof(usbmem_response), "not connected"); + } + } + + usbmem_pending = true; + src->pos = nbytes; + + return S_OK; +} + +static HRESULT usbmem_read(struct irp *irp) +{ + struct iobuf *dest; + uint32_t rlength; + + log_assert(irp != NULL); + log_assert(irp->read.bytes != NULL); + + dest = &irp->read; + + if (usbmem_pending) { + if (strlen(usbmem_response) > 0) { + log_misc("%s", usbmem_response); + } + + str_cat(usbmem_response, sizeof(usbmem_response), "\r>"); + usbmem_pending = false; + } + + rlength = strlen(usbmem_response); + memcpy(dest->bytes, usbmem_response, rlength); + memset(usbmem_response, 0, USBMEM_BUF_SIZE); + + dest->pos = rlength; + + return S_OK; +} + +static HRESULT usbmem_ioctl(struct irp *irp) +{ + SERIAL_STATUS *status; + + log_assert(irp != NULL); + + switch (irp->ioctl) { + case IOCTL_SERIAL_GET_COMMSTATUS: + if (irp->read.bytes == NULL) { + log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Output buffer is NULL"); + + return E_INVALIDARG; + } + + if (irp->read.nbytes < sizeof(*status)) { + log_warning("IOCTL_SERIAL_GET_COMMSTATUS: Buffer is too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + status = (SERIAL_STATUS *) irp->read.bytes; + status->Errors = 0; + status->AmountInInQueue = usbmem_pending ? 1 : 0; + + irp->read.pos = sizeof(*status); + + break; + + default: + break; + } + + return S_OK; +} + diff --git a/src/main/ddrhook/usbmem.h b/src/main/ddrhook/usbmem.h new file mode 100644 index 0000000..378fab4 --- /dev/null +++ b/src/main/ddrhook/usbmem.h @@ -0,0 +1,8 @@ +#ifndef HOOK_USBMEM_H +#define HOOK_USBMEM_H + +void usbmem_init(void); +void usbmem_fini(void); +HRESULT usbmem_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/ddrio-mm/Module.mk b/src/main/ddrio-mm/Module.mk new file mode 100644 index 0000000..9368a62 --- /dev/null +++ b/src/main/ddrio-mm/Module.mk @@ -0,0 +1,13 @@ +dlls += ddrio-mm + +ldflags_ddrio-mm:= \ + -lhid \ + -lsetupapi \ + +libs_ddrio-mm := \ + mm \ + util \ + +src_ddrio-mm := \ + ddrio.c \ + diff --git a/src/main/ddrio-mm/ddrio-mm.def b/src/main/ddrio-mm/ddrio-mm.def new file mode 100644 index 0000000..626d450 --- /dev/null +++ b/src/main/ddrio-mm/ddrio-mm.def @@ -0,0 +1,9 @@ +LIBRARY ddrio-mm + +EXPORTS + ddr_io_set_loggers + ddr_io_fini + ddr_io_init + ddr_io_read_pad + ddr_io_set_lights_extio + ddr_io_set_lights_p3io diff --git a/src/main/ddrio-mm/ddrio.c b/src/main/ddrio-mm/ddrio.c new file mode 100644 index 0000000..4be7402 --- /dev/null +++ b/src/main/ddrio-mm/ddrio.c @@ -0,0 +1,217 @@ +#include + +#include +#include +#include +#include + +#include "bemanitools/ddrio.h" + +#include "mm/mm.h" + +#include "util/cmdline.h" +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" + +struct ddr_bittrans { + uint32_t mm; + uint32_t p3io; +}; + +static const struct ddr_bittrans input_map[] = { + { 0x00000001, 1 << DDR_SERVICE }, + { 0x00000002, 1 << DDR_TEST }, + { 0x00100000, 1 << DDR_P1_MENU_LEFT }, + { 0x00400000, 1 << DDR_P1_MENU_RIGHT }, + { 0x00000100, 1 << DDR_P1_START }, + { 0x00200000, 1 << DDR_P2_MENU_LEFT }, + { 0x00800000, 1 << DDR_P2_MENU_RIGHT }, + { 0x00000200, 1 << DDR_P2_START }, + { 0x00004000, 1 << DDR_P1_LEFT }, + { 0x00010000, 1 << DDR_P1_RIGHT }, + { 0x00000400, 1 << DDR_P1_UP }, + { 0x00001000, 1 << DDR_P1_DOWN }, + { 0x00008000, 1 << DDR_P2_LEFT }, + { 0x00020000, 1 << DDR_P2_RIGHT }, + { 0x00000800, 1 << DDR_P2_UP }, + { 0x00002000, 1 << DDR_P2_DOWN }, + + /* Nonstandard */ + { 0x01000000, 1 << DDR_P1_MENU_UP }, + { 0x04000000, 1 << DDR_P1_MENU_DOWN }, + { 0x02000000, 1 << DDR_P2_MENU_UP }, + { 0x08000000, 1 << DDR_P2_MENU_DOWN }, +}; + +static const struct ddr_bittrans extio_light_map[] = { + { 0x00000100, 1 << LIGHT_P1_UP }, + { 0x00000200, 1 << LIGHT_P1_DOWN }, + { 0x00000400, 1 << LIGHT_P1_LEFT }, + { 0x00000800, 1 << LIGHT_P1_RIGHT }, + { 0x00010000, 1 << LIGHT_P2_UP }, + { 0x00020000, 1 << LIGHT_P2_DOWN }, + { 0x00040000, 1 << LIGHT_P2_LEFT }, + { 0x00080000, 1 << LIGHT_P2_RIGHT }, + { 0x01000000, 1 << LIGHT_NEONS }, +}; + +static const struct ddr_bittrans p3io_light_map[] = { + { 0x00000004, 1 << LIGHT_P1_MENU }, + { 0x00000008, 1 << LIGHT_P2_MENU }, + { 0x00000010, 1 << LIGHT_P2_LOWER_LAMP }, + { 0x00000020, 1 << LIGHT_P2_UPPER_LAMP }, + { 0x00000040, 1 << LIGHT_P1_LOWER_LAMP }, + { 0x00000080, 1 << LIGHT_P1_UPPER_LAMP }, +}; + +static bool initted; +static CRITICAL_SECTION cs; +static struct mm_output out; + +static int ddr_io_get_lag_param(void) +{ + int argc; + char **argv; + int result; + int i; + + result = 0; + + args_recover(&argc, &argv); + + for (i = 1 ; i < argc ; i++) { + if (argv[i][0] != '-') { + continue; + } + + switch (argv[i][1]) { + case 'a': + if (i + 1 == argc) { + continue; + } + + result = atoi(argv[i + 1]); + + break; + } + } + + args_free(argc, argv); + + if (result < 0) { + /* snark snark */ + log_warning("This PCB is incapable of seeing into the future. " + "Defaulting to 0 injected lag samples"); + + result = 0; + } + + return result; +} + +void ddr_io_set_loggers( + log_formatter_t misc, + log_formatter_t info, + log_formatter_t warning, + log_formatter_t fatal) +{ + log_to_external(misc, info, warning, fatal); +} + +bool ddr_io_init( + thread_create_t thread_create, + thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + bool ok; + + log_assert(!initted); + + ok = mm_init(2 + ddr_io_get_lag_param()); + + if (!ok) { + return false; + } + + InitializeCriticalSection(&cs); + out.lights = 0x00101000; /* Hold pad IO board FSMs in reset */ + + initted = true; + + return true; +} + +uint32_t ddr_io_read_pad(void) +{ + struct mm_input in; + uint32_t i; + uint32_t pad; + + EnterCriticalSection(&cs); + + mm_update(&out, &in); + pad = 0; + + for (i = 0 ; i < lengthof(input_map) ; i++) { + if (in.jamma & input_map[i].mm) { + pad |= input_map[i].p3io; + } + } + + LeaveCriticalSection(&cs); + + return pad; +} + +void ddr_io_set_lights_extio(uint32_t extio_lights) +{ + uint32_t clr; + uint32_t set; + int i; + + clr = 0; + set = 0; + + for (i = 0 ; i < lengthof(extio_light_map) ; i++) { + if (extio_lights & extio_light_map[i].p3io /* misnomer but w/e */) { + set |= extio_light_map[i].mm; + } else { + clr |= extio_light_map[i].mm; + } + } + + atomic_fetch_or(&out.lights, set); + atomic_fetch_and(&out.lights, ~clr); +} + +void ddr_io_set_lights_p3io(uint32_t p3io_lights) +{ + uint32_t clr; + uint32_t set; + int i; + + clr = 0; + set = 0; + + for (i = 0 ; i < lengthof(p3io_light_map) ; i++) { + if (p3io_lights & p3io_light_map[i].p3io) { + set |= p3io_light_map[i].mm; + } else { + clr |= p3io_light_map[i].mm; + } + } + + atomic_fetch_or(&out.lights, set); + atomic_fetch_and(&out.lights, ~clr); +} + +void ddr_io_fini(void) +{ + if (initted) { + mm_fini(); + DeleteCriticalSection(&cs); + initted = false; + } +} + diff --git a/src/main/ddrio-smx/Module.mk b/src/main/ddrio-smx/Module.mk new file mode 100644 index 0000000..cc0563b --- /dev/null +++ b/src/main/ddrio-smx/Module.mk @@ -0,0 +1,13 @@ +dlls += ddrio-smx +imps += SMX + +deplibs_ddrio-smx := \ + SMX \ + +libs_ddrio-smx := \ + geninput \ + util \ + +src_ddrio-smx := \ + ddrio.c \ + diff --git a/src/main/ddrio-smx/ddrio-smx.def b/src/main/ddrio-smx/ddrio-smx.def new file mode 100644 index 0000000..41a26fa --- /dev/null +++ b/src/main/ddrio-smx/ddrio-smx.def @@ -0,0 +1,9 @@ +LIBRARY ddrio-smx + +EXPORTS + ddr_io_set_loggers + ddr_io_fini + ddr_io_init + ddr_io_read_pad + ddr_io_set_lights_extio + ddr_io_set_lights_p3io diff --git a/src/main/ddrio-smx/ddrio.c b/src/main/ddrio-smx/ddrio.c new file mode 100644 index 0000000..cd7d426 --- /dev/null +++ b/src/main/ddrio-smx/ddrio.c @@ -0,0 +1,212 @@ +#include + +#include +#include + +#include "bemanitools/ddrio.h" +#include "bemanitools/input.h" + +#include "imports/avs.h" +#include "imports/SMX.h" + +#include "util/defs.h" +#include "util/log.h" + +struct ddr_io_smx_pad_map { + int pad_no; + uint16_t smx_bit; + uint32_t ddr_bit; +}; + +struct ddr_io_smx_light_map { + uint32_t extio_bit; + int smx_light_offset; + unsigned char r; + unsigned char g; + unsigned char b; +}; + +static void ddr_io_smx_callback( + int pad_no, + enum SMXUpdateCallbackReason reason, + void *ctx); + +static const struct ddr_io_smx_pad_map ddr_io_smx_pad_map[] = { + { 0, 1 << 1, 1 << DDR_P1_UP }, + { 0, 1 << 3, 1 << DDR_P1_LEFT }, + { 0, 1 << 5, 1 << DDR_P1_RIGHT }, + { 0, 1 << 7, 1 << DDR_P1_DOWN }, + + { 1, 1 << 1, 1 << DDR_P2_UP }, + { 1, 1 << 3, 1 << DDR_P2_LEFT }, + { 1, 1 << 5, 1 << DDR_P2_RIGHT }, + { 1, 1 << 7, 1 << DDR_P2_DOWN }, +}; + +static const struct ddr_io_smx_light_map ddr_io_smx_light_map[] = { + /* Light L/R blue and U/D red to match DDR pad color scheme */ + + { 1 << LIGHT_P1_UP, 48 * 1, 0xFF, 0x00, 0x00 }, + { 1 << LIGHT_P1_LEFT, 48 * 3, 0x00, 0x00, 0xFF }, + { 1 << LIGHT_P1_RIGHT, 48 * 5, 0x00, 0x00, 0xFF }, + { 1 << LIGHT_P1_DOWN, 48 * 7, 0xFF, 0x00, 0x00 }, + + { 1 << LIGHT_P2_UP, 48 * 10, 0xFF, 0x00, 0x00 }, + { 1 << LIGHT_P2_LEFT, 48 * 12, 0x00, 0x00, 0xFF }, + { 1 << LIGHT_P2_RIGHT, 48 * 14, 0x00, 0x00, 0xFF }, + { 1 << LIGHT_P2_DOWN, 48 * 16, 0xFF, 0x00, 0x00 }, +}; + +static _Atomic uint32_t ddr_io_smx_pad_state[2]; +static CRITICAL_SECTION ddr_io_smx_lights_lock; +static uint8_t ddr_io_smx_lights_counter; +static char ddr_io_smx_lights[864]; + +void ddr_io_set_loggers( + log_formatter_t misc, + log_formatter_t info, + log_formatter_t warning, + log_formatter_t fatal) +{ + log_to_external(misc, info, warning, fatal); + input_set_loggers(misc, info, warning, fatal); + + /* We would need a log server thread to accept log messages from SMX, since + it uses raw Win32 threads and not AVS threads (only AVS threads are + permitted to use the AVS logging API). So it's not really worth it. */ +} + +bool ddr_io_init( + thread_create_t thread_create, + thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + /* Use geninput for menu/operator btns */ + + input_init(thread_create, thread_join, thread_destroy); + mapper_config_load("ddr"); + + /* Start up SMX API */ + + log_info("Starting SMX.DLL"); + log_info("SMX.DLL version: %s", "[predates SMX_Version()]"); + SMX_Start(ddr_io_smx_callback, NULL); + log_info("Started SMX.DLL"); + + InitializeCriticalSection(&ddr_io_smx_lights_lock); + + return true; +} + +uint32_t ddr_io_read_pad(void) +{ + /* SMX pads require a constant stream of lighting updates or they will + quickly revert to autonomous lighting control. Here is a hacky way of + feeding that light data to the pad. Hopefully it won't starve anything + important. + + Changing the light state causes the counter to reset, and the counter + itself ought to roll over at a rate of about 4 Hz. */ + + EnterCriticalSection(&ddr_io_smx_lights_lock); + + if (ddr_io_smx_lights_counter == 0) { + SMX_SetLights(ddr_io_smx_lights); + } + + ddr_io_smx_lights_counter++; + + LeaveCriticalSection(&ddr_io_smx_lights_lock); + + /* Sleep first: input is timestamped immediately AFTER the ioctl returns. + + Which is the right thing to do, for once. We sleep here because + the game polls input in a tight loop. Can't complain, at there isn't + an artificial limit on the poll frequency. */ + + Sleep(1); + + /* We don't atomically read both pads, but they are separate USB devices + so they don't update in lockstep anyway. */ + + return mapper_update() | + atomic_load(&ddr_io_smx_pad_state[0]) | + atomic_load(&ddr_io_smx_pad_state[1]) ; +} + +void ddr_io_set_lights_extio(uint32_t lights) +{ + const struct ddr_io_smx_light_map *map; + size_t offset; + size_t i; + size_t j; + + EnterCriticalSection(&ddr_io_smx_lights_lock); + + ddr_io_smx_lights_counter = 0; + memset(ddr_io_smx_lights, 0, sizeof(ddr_io_smx_lights)); + + for (i = 0 ; i < lengthof(ddr_io_smx_light_map) ; i++) { + map = &ddr_io_smx_light_map[i]; + + if (lights & map->extio_bit) { + offset = map->smx_light_offset; + + for (j = 0 ; j < 48 ; j += 3) { + ddr_io_smx_lights[offset + j ] = map->r; + ddr_io_smx_lights[offset + j + 1] = map->g; + ddr_io_smx_lights[offset + j + 2] = map->b; + } + } + } + + LeaveCriticalSection(&ddr_io_smx_lights_lock); +} + +void ddr_io_set_lights_p3io(uint32_t lights) +{ + uint8_t i; + + for (i = 0x0E; i <= 0x1E; i++) { + mapper_write_light(i, lights & (1 << i) ? 255 : 0); + } +} + +void ddr_io_fini(void) +{ + log_info("Stopping SMX.DLL"); + SMX_Stop(); + log_info("Stopped SMX.DLL"); + + DeleteCriticalSection(&ddr_io_smx_lights_lock); + input_fini(); +} + +static void ddr_io_smx_callback( + int pad_no, + enum SMXUpdateCallbackReason reason, + void *ctx) +{ + const struct ddr_io_smx_pad_map *map; + uint16_t smx_state; + uint32_t ddr_state; + size_t i; + + if (pad_no < 0 || pad_no > 1) { + return; + } + + smx_state = SMX_GetInputState(pad_no); + ddr_state = 0; + + for (i = 0 ; i < lengthof(ddr_io_smx_pad_map) ; i++) { + map = &ddr_io_smx_pad_map[i]; + + if (pad_no == map->pad_no && (smx_state & map->smx_bit) != 0) { + ddr_state |= map->ddr_bit; + } + } + + atomic_store(&ddr_io_smx_pad_state[pad_no], ddr_state); +} + diff --git a/src/main/ddrio/Module.mk b/src/main/ddrio/Module.mk new file mode 100644 index 0000000..6a25b77 --- /dev/null +++ b/src/main/ddrio/Module.mk @@ -0,0 +1,8 @@ +dlls += ddrio + +libs_ddrio := \ + geninput \ + +src_ddrio := \ + ddrio.c \ + diff --git a/src/main/ddrio/ddrio.c b/src/main/ddrio/ddrio.c new file mode 100644 index 0000000..a0c6721 --- /dev/null +++ b/src/main/ddrio/ddrio.c @@ -0,0 +1,59 @@ +#include + +#include "imports/avs.h" + +#include "bemanitools/input.h" + +#include "util/log.h" + +void ddr_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + input_set_loggers(misc, info, warning, fatal); +} + +bool ddr_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + input_init(thread_create, thread_join, thread_destroy); + mapper_config_load("ddr"); + + return true; +} + +uint32_t ddr_io_read_pad(void) +{ + /* Sleep first: input is timestamped immediately AFTER the ioctl returns. + + Which is the right thing to do, for once. We sleep here because + the game polls input in a tight loop. Can't complain, at there isn't + an artificial limit on the poll frequency. */ + + Sleep(1); + + return (uint32_t) mapper_update(); +} + +void ddr_io_set_lights_extio(uint32_t lights) +{ + uint8_t i; + + for (i = 0x00; i <= 0x07; i++) { + mapper_write_light(i, lights & (1 << i) ? 255 : 0); + } +} + +void ddr_io_set_lights_p3io(uint32_t lights) +{ + uint8_t i; + + for (i = 0x0E; i <= 0x1E; i++) { + mapper_write_light(i, lights & (1 << i) ? 255 : 0); + } +} + +void ddr_io_fini(void) +{ + input_fini(); +} + diff --git a/src/main/ddrio/ddrio.def b/src/main/ddrio/ddrio.def new file mode 100644 index 0000000..86547a1 --- /dev/null +++ b/src/main/ddrio/ddrio.def @@ -0,0 +1,9 @@ +LIBRARY ddrio + +EXPORTS + ddr_io_set_loggers + ddr_io_fini + ddr_io_init + ddr_io_read_pad + ddr_io_set_lights_extio + ddr_io_set_lights_p3io diff --git a/src/main/eamio-icca/Module.mk b/src/main/eamio-icca/Module.mk new file mode 100644 index 0000000..1199b2a --- /dev/null +++ b/src/main/eamio-icca/Module.mk @@ -0,0 +1,9 @@ +dlls += \ + eamio-icca \ + +libs_eamio-icca := \ + aciodrv \ + util \ + +src_eamio-icca := \ + eamio-icca.c \ diff --git a/src/main/eamio-icca/eamio-icca.c b/src/main/eamio-icca/eamio-icca.c new file mode 100644 index 0000000..82a03ed --- /dev/null +++ b/src/main/eamio-icca/eamio-icca.c @@ -0,0 +1,147 @@ +#include +#include + +#include +#include +#include + +#include "aciodrv/device.h" +#include "aciodrv/icca.h" + +#include "bemanitools/eamio.h" + +#include "util/log.h" + +static const uint8_t eam_io_keypad_mappings[16] = { + EAM_IO_KEYPAD_DECIMAL, + EAM_IO_KEYPAD_3, + EAM_IO_KEYPAD_6, + EAM_IO_KEYPAD_9, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + EAM_IO_KEYPAD_0, + EAM_IO_KEYPAD_1, + EAM_IO_KEYPAD_4, + EAM_IO_KEYPAD_7, + EAM_IO_KEYPAD_00, + EAM_IO_KEYPAD_2, + EAM_IO_KEYPAD_5, + EAM_IO_KEYPAD_8 +}; + +static struct ac_io_icca_state eam_io_icca_state[2]; + +void eam_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + log_to_external(misc, info, warning, fatal); +} + +bool eam_io_init(thread_create_t create, thread_join_t join, + thread_destroy_t destroy) +{ + if (!aciodrv_device_open("COM1", 57600)) { + log_warning("Opening acio device on COM1 failed"); + return false; + } + + for (uint8_t i = 0; i < 2; i++) { + + if (!aciodrv_icca_init(i)) { + log_warning("Initializing icca %d failed", i); + return false; + } + } + + return true; +} + +void eam_io_fini(void) +{ + aciodrv_device_close(); +} + +uint16_t eam_io_get_keypad_state(uint8_t unit_no) +{ + uint16_t keypad_result = 0; + + uint16_t keypad = eam_io_icca_state[unit_no].key_state; + + for (uint8_t i = 0; i < sizeof(eam_io_keypad_mappings); ++i) { + if ((keypad & (1 << i)) && eam_io_keypad_mappings[i] != 0xFF) { + keypad_result |= (1 << eam_io_keypad_mappings[i]); + } + } + + return keypad_result; +} + +uint8_t eam_io_get_sensor_state(uint8_t unit_no) +{ + uint8_t sensors = 0; + + if ((eam_io_icca_state[unit_no].sensor_state & + AC_IO_ICCA_SENSOR_MASK_BACK_ON) > 0) { + sensors |= (1 << EAM_IO_SENSOR_BACK); + } + if ((eam_io_icca_state[unit_no].sensor_state & + AC_IO_ICCA_SENSOR_MASK_FRONT_ON) > 0) { + sensors |= (1 << EAM_IO_SENSOR_FRONT); + } + + return sensors; +} + +uint8_t eam_io_read_card(uint8_t unit_no, uint8_t *card_id, uint8_t nbytes) +{ + memcpy(card_id, eam_io_icca_state[unit_no].uid, nbytes); + if (card_id[0] == 0xe0 && card_id[1] == 0x04) { + return EAM_IO_CARD_ISO15696; + } else { + return EAM_IO_CARD_FELICA; + } +} + +bool eam_io_card_slot_cmd(uint8_t unit_no, uint8_t cmd) +{ + switch (cmd) { + case EAM_IO_CARD_SLOT_CMD_CLOSE: + return aciodrv_icca_set_state(unit_no, + AC_IO_ICCA_SLOT_STATE_CLOSE, NULL); + + case EAM_IO_CARD_SLOT_CMD_OPEN: + return aciodrv_icca_set_state(unit_no, + AC_IO_ICCA_SLOT_STATE_OPEN, NULL); + + case EAM_IO_CARD_SLOT_CMD_EJECT: + return aciodrv_icca_set_state(unit_no, + AC_IO_ICCA_SLOT_STATE_EJECT, NULL); + + case EAM_IO_CARD_SLOT_CMD_READ: + return aciodrv_icca_read_card(unit_no, NULL) && + aciodrv_icca_get_state(unit_no, &eam_io_icca_state[unit_no]); + + default: + break; + } + + return false; +} + +bool eam_io_poll(uint8_t unit_no) +{ + return aciodrv_icca_get_state(unit_no, &eam_io_icca_state[unit_no]); +} + +const struct eam_io_config_api *eam_io_get_config_api(void) +{ + return NULL; +} + +BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *ctx) +{ + return TRUE; +} + diff --git a/src/main/eamio-icca/eamio-icca.def b/src/main/eamio-icca/eamio-icca.def new file mode 100644 index 0000000..38a7030 --- /dev/null +++ b/src/main/eamio-icca/eamio-icca.def @@ -0,0 +1,12 @@ +LIBRARY eamio-icca + +EXPORTS + eam_io_fini + eam_io_init + eam_io_get_config_api + eam_io_get_keypad_state + eam_io_get_sensor_state + eam_io_read_card + eam_io_card_slot_cmd + eam_io_set_loggers + eam_io_poll diff --git a/src/main/eamio/Module.mk b/src/main/eamio/Module.mk new file mode 100644 index 0000000..f7a7409 --- /dev/null +++ b/src/main/eamio/Module.mk @@ -0,0 +1,6 @@ +dlls += eamio +libs_eamio := geninput util +src_eamio := \ + eam-api.c \ + eam-impl.c \ + eam-s11n.c \ diff --git a/src/main/eamio/eam-api.c b/src/main/eamio/eam-api.c new file mode 100644 index 0000000..648af44 --- /dev/null +++ b/src/main/eamio/eam-api.c @@ -0,0 +1,258 @@ +#include +#include + +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/input.h" + +#include "eamio/eam-config.h" +#include "eamio/eam-impl.h" +#include "eamio/eam-s11n.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/msg-thread.h" +#include "util/thread.h" + +static void eam_handle_hotplug_msg(WPARAM wparam, + const DEV_BROADCAST_HDR *hdr); +static FILE *eam_io_config_open(const char *mode); +static void eam_io_config_load(void); +static void eam_io_config_save(void); +static bool eam_io_get_autogen(void); +static void eam_io_set_autogen(bool autogen); +static bool eam_io_get_alt_10k(void); +static void eam_io_set_alt_10k(bool alt_10k); +static struct hid_stub *eam_io_get_keypad_device(uint8_t unit_no); +static void eam_io_set_keypad_device(uint8_t unit_no, struct hid_stub *hid); +static const char *eam_io_get_card_path(uint8_t unit_no); +static void eam_io_set_card_path(uint8_t unit_no, const char *path); + +static HANDLE eam_hinst; +static struct eam *eam_inst; + +static const struct eam_io_config_api eam_io_config_api = { + .config_save = eam_io_config_save, + .get_autogen = eam_io_get_autogen, + .set_autogen = eam_io_set_autogen, + .get_alt_10k = eam_io_get_alt_10k, + .set_alt_10k = eam_io_set_alt_10k, + .get_keypad_device = eam_io_get_keypad_device, + .set_keypad_device = eam_io_set_keypad_device, + .get_card_path = eam_io_get_card_path, + .set_card_path = eam_io_set_card_path, +}; + +void msg_window_setup(HWND hwnd) +{ + log_info("Drive insertion listener ready, thread id = %d", + (int) GetCurrentThreadId()); +} + +LRESULT WINAPI msg_window_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_DEVICECHANGE: + eam_handle_hotplug_msg(wparam, (DEV_BROADCAST_HDR *) lparam); + + return TRUE; + + default: + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} + +static void eam_handle_hotplug_msg(WPARAM wparam, const DEV_BROADCAST_HDR *hdr) +{ + uint32_t drives; + unsigned long bit; + const DEV_BROADCAST_VOLUME *vol; + + if (wparam != DBT_DEVICEARRIVAL) { + return; + } + + if (hdr->dbch_devicetype != DBT_DEVTYP_VOLUME) { + return; + } + + vol = (const DEV_BROADCAST_VOLUME *) hdr; + drives = vol->dbcv_unitmask; + + while (_BitScanForward(&bit, drives)) { + drives &= ~(1 << bit); + eam_impl_notify_hotplug(eam_inst, (uint8_t) bit); + } +} + +void msg_window_teardown(HWND hwnd) +{ + log_info("Volume insertion listener shutting down"); +} + +static FILE *eam_io_config_open(const char *mode) +{ + return fopen_appdata("DJHACKERS", "eam_v4_22.bin", mode); +} + +void eam_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + log_to_external(misc, info, warning, fatal); +} + +bool eam_io_init(thread_create_t create, thread_join_t join, + thread_destroy_t destroy) +{ + input_init(create, join, destroy); + thread_api_init(create, join, destroy); + eam_io_config_load(); + msg_thread_init(eam_hinst); + + return true; +} + +static void eam_io_config_load(void) +{ + struct eam *eam; + FILE *f; + + f = eam_io_config_open("rb"); + + if (f == NULL) { + log_warning("Failed to open eam config, using defaults"); + + goto open_fail; + } + + eam = eam_impl_config_load(f); + + if (eam == NULL) { + log_warning("eam config is corrupted, using defaults"); + + goto read_fail; + } + + eam_inst = eam; + + fclose(f); + + return; + +read_fail: + fclose(f); + +open_fail: + eam_inst = eam_impl_create(); +} + +void eam_io_fini(void) +{ + msg_thread_fini(); + eam_impl_destroy(eam_inst); + input_fini(); +} + +const struct eam_io_config_api *eam_io_get_config_api(void) +{ + return &eam_io_config_api; +} + +static void eam_io_config_save(void) +{ + FILE *f; + + f = eam_io_config_open("wb"); + + if (f == NULL) { + return; + } + + eam_impl_config_save(eam_inst, f); + fclose(f); +} + +static bool eam_io_get_autogen(void) +{ + return eam_impl_get_autogen(eam_inst); +} + +static void eam_io_set_autogen(bool autogen) +{ + eam_impl_set_autogen(eam_inst, autogen); +} + +static bool eam_io_get_alt_10k(void) +{ + return eam_impl_get_alt_10k(eam_inst); +} + +static void eam_io_set_alt_10k(bool alt_10k) +{ + eam_impl_set_alt_10k(eam_inst, alt_10k); +} + +static struct hid_stub *eam_io_get_keypad_device(uint8_t unit_no) +{ + return eam_impl_get_keypad_device(eam_inst, unit_no); +} + +static void eam_io_set_keypad_device(uint8_t unit_no, struct hid_stub *hid) +{ + eam_impl_set_keypad_device(eam_inst, unit_no, hid); +} + +static const char *eam_io_get_card_path(uint8_t unit_no) +{ + return eam_impl_get_card_path(eam_inst, unit_no); +} + +static void eam_io_set_card_path(uint8_t unit_no, const char *path) +{ + eam_impl_set_card_path(eam_inst, unit_no, path); +} + +uint16_t eam_io_get_keypad_state(uint8_t unit_no) +{ + return eam_impl_get_keypad_state(eam_inst, unit_no); +} + +uint8_t eam_io_get_sensor_state(uint8_t unit_no) +{ + if (eam_impl_get_sensor_state(eam_inst, unit_no)) { + return (1 << EAM_IO_SENSOR_FRONT) | (1 << EAM_IO_SENSOR_BACK); + } else { + return 0x00; + } +} + +uint8_t eam_io_read_card(uint8_t unit_no, uint8_t *card_id, uint8_t nbytes) +{ + return eam_impl_read_card(eam_inst, unit_no, card_id, nbytes); +} + +bool eam_io_card_slot_cmd(uint8_t unit_no, uint8_t cmd) +{ + // ignored + return true; +} + +bool eam_io_poll(uint8_t unit_no) +{ + // ignored + return true; +} + +BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { + eam_hinst = hinst; + } + + return TRUE; +} + diff --git a/src/main/eamio/eam-config.h b/src/main/eamio/eam-config.h new file mode 100644 index 0000000..ebf4812 --- /dev/null +++ b/src/main/eamio/eam-config.h @@ -0,0 +1,18 @@ +#ifndef EAMIO_CONFIG_H +#define EAMIO_CONFIG_H + +#include "bemanitools/eamio.h" + +struct eam_io_config_api { + void (*config_save)(void); + bool (*get_autogen)(void); + void (*set_autogen)(bool autogen); + bool (*get_alt_10k)(void); + void (*set_alt_10k)(bool alt_10k); + struct hid_stub *(*get_keypad_device)(uint8_t unit_no); + void (*set_keypad_device)(uint8_t unit_no, struct hid_stub *hid); + const char *(*get_card_path)(uint8_t unit_no); + void (*set_card_path)(uint8_t unit_no, const char *path); +}; + +#endif diff --git a/src/main/eamio/eam-impl.c b/src/main/eamio/eam-impl.c new file mode 100644 index 0000000..b4d8097 --- /dev/null +++ b/src/main/eamio/eam-impl.c @@ -0,0 +1,504 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "bemanitools/eamio.h" + +#include "eamio/eam-impl.h" + +#include "geninput/hid-mgr.h" + +#include "util/defs.h" +#include "util/fs.h" +#include "util/hex.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +#define EAM_SENSOR_COOLDOWN 1000 + +struct eam_unit { + char *card_path; + struct hid_stub *hid; + size_t keypad_ctls[EAM_IO_KEYPAD_COUNT]; + size_t sensor_ctl; + bool bound_ctls; + uint8_t drive_no; + uint32_t sensor_time; + bool sensor_hot; +}; + +struct eam { + /* This lock protects drive_no and sensor_time, which get read/written + respectively by eam_impl_notify_hotplug, which gets called by the Win32 + message pump thread. */ + + CRITICAL_SECTION lock; + struct eam_unit units[EAM_UNIT_COUNT]; + bool autogen; + bool alt_10k; + bool mux; +}; + +static const uint32_t eam_keypad_usages[EAM_IO_KEYPAD_COUNT + 1] = { + /* [EAM_KEYPAD_0] = */ 0x00070062, + /* [EAM_KEYPAD_1] = */ 0x00070059, + /* [EAM_KEYPAD_4] = */ 0x0007005C, + /* [EAM_KEYPAD_7] = */ 0x0007005F, + /* [EAM_KEYPAD_00] = */ 0x00070058, /* Keypad ENTER */ + /* [EAM_KEYPAD_2] = */ 0x0007005A, + /* [EAM_KEYPAD_5] = */ 0x0007005D, + /* [EAM_KEYPAD_8] = */ 0x00070060, + /* [EAM_KEYPAD_DECIMAL] = */ 0x00070063, + /* [EAM_KEYPAD_3] = */ 0x0007005B, + /* [EAM_KEYPAD_6] = */ 0x0007005E, + /* [EAM_KEYPAD_9] = */ 0x00070061, + /* Sensor = */ 0x00070057 +}; + +static const uint32_t eam_keypad_usages_alt[EAM_IO_KEYPAD_COUNT + 1] = { + /* [EAM_KEYPAD_0] = */ 0x00070027, + /* [EAM_KEYPAD_1] = */ 0x0007001E, + /* [EAM_KEYPAD_4] = */ 0x00070021, + /* [EAM_KEYPAD_7] = */ 0x00070024, + /* [EAM_KEYPAD_00] = */ 0x0007002D, /* - and _ */ + /* [EAM_KEYPAD_2] = */ 0x0007001F, + /* [EAM_KEYPAD_5] = */ 0x00070022, + /* [EAM_KEYPAD_8] = */ 0x00070025, + /* [EAM_KEYPAD_DECIMAL] = */ 0x0007002E, /* + and = */ + /* [EAM_KEYPAD_3] = */ 0x00070020, + /* [EAM_KEYPAD_6] = */ 0x00070023, + /* [EAM_KEYPAD_9] = */ 0x00070026, + /* Sensor = */ 0x0007002A /* Backspace */ +}; + +static uint8_t eam_impl_get_active_unit(void); +static void eam_impl_bind_keypad(struct eam *eam, uint8_t unit_no); +static bool eam_impl_autogen(struct eam_unit *unit, uint8_t *card_id); + +struct eam *eam_impl_create(void) +{ + struct eam *eam; + struct eam_unit *unit; + size_t btn_no; + uint8_t unit_no; + + eam = xmalloc(sizeof(*eam)); + + InitializeCriticalSection(&eam->lock); + + for (unit_no = 0 ; unit_no < lengthof(eam->units) ; unit_no++) { + unit = &eam->units[unit_no]; + + unit->card_path = NULL; + unit->hid = NULL; + + for (btn_no = 0 ; btn_no < lengthof(unit->keypad_ctls) ; btn_no++) { + unit->keypad_ctls[btn_no] = (size_t) -1; + } + + unit->sensor_ctl = (size_t) -1; + unit->bound_ctls = false; + unit->drive_no = (uint8_t) -1; + unit->sensor_time = 0; + } + + eam->autogen = false; + eam->alt_10k = false; + eam->mux = false; + + return eam; +} + +static uint8_t eam_impl_get_active_unit(void) +{ + return GetKeyState(VK_NUMLOCK) & 0x0001; +} + +bool eam_impl_get_autogen(struct eam *eam) +{ + return eam->autogen; +} + +void eam_impl_set_autogen(struct eam *eam, bool autogen) +{ + eam->autogen = autogen != false; +} + +bool eam_impl_get_alt_10k(struct eam *eam) +{ + return eam->alt_10k; +} + +void eam_impl_set_alt_10k(struct eam *eam, bool alt_10k) +{ + int i; + + for (i = 0 ; i < lengthof(eam->units) ; i++) { + eam->units[i].bound_ctls = false; + } + + eam->alt_10k = alt_10k != false; +} + +struct hid_stub *eam_impl_get_keypad_device(struct eam *eam, uint8_t unit_no) +{ + log_assert(unit_no < lengthof(eam->units)); + + return eam->units[unit_no].hid; +} + +void eam_impl_set_keypad_device(struct eam *eam, uint8_t unit_no, + struct hid_stub *hid) +{ + log_assert(unit_no < lengthof(eam->units)); + + eam->units[unit_no].hid = hid; + eam->units[unit_no].bound_ctls = false; + + eam->mux = hid != NULL && eam->units[0].hid == eam->units[1].hid; +} + +const char *eam_impl_get_card_path(struct eam *eam, uint8_t unit_no) +{ + log_assert(unit_no < lengthof(eam->units)); + + return eam->units[unit_no].card_path; +} + +void eam_impl_set_card_path(struct eam *eam, uint8_t unit_no, + const char *path) +{ + log_assert(unit_no < lengthof(eam->units)); + + EnterCriticalSection(&eam->lock); + + free(eam->units[unit_no].card_path); + + if (path != NULL) { + eam->units[unit_no].card_path = str_dup(path); + + if (isalpha(path[0]) && path[1] == ':') { + eam->units[unit_no].drive_no = toupper(path[0]) - 'A'; + } else { + eam->units[unit_no].drive_no = (uint8_t) -1; + } + } else { + eam->units[unit_no].card_path = NULL; + eam->units[unit_no].drive_no = (uint8_t) -1; + } + + LeaveCriticalSection(&eam->lock); +} + +uint16_t eam_impl_get_keypad_state(struct eam *eam, uint8_t unit_no) +{ + struct eam_unit *unit; + uint16_t result; + int32_t value; + size_t i; + + log_assert(unit_no < lengthof(eam->units)); + + if (eam->mux && eam_impl_get_active_unit() != unit_no) { + return 0; + } + + eam_impl_bind_keypad(eam, unit_no); + + unit = &eam->units[unit_no]; + result = 0; + + if (unit->bound_ctls) { + for (i = 0 ; i < lengthof(unit->keypad_ctls) ; i++) { + if (unit->keypad_ctls[i] == (size_t) -1) { + continue; + } + + if (!hid_stub_get_value(unit->hid, unit->keypad_ctls[i], &value)) { + continue; + } + + if (value) { + result |= 1 << i; + } + } + } + + return result; +} + +static void eam_impl_bind_keypad(struct eam *eam, uint8_t unit_no) +{ + const uint32_t *usages; + struct hid_control *controls; + struct eam_unit *unit; + size_t ncontrols; + size_t control_no; + size_t btn_no; + + unit = &eam->units[unit_no]; + + if (unit->bound_ctls + || unit->hid == NULL + || !hid_stub_is_attached(unit->hid)) { + return; + } + + if (eam->alt_10k) { + usages = eam_keypad_usages_alt; + } else { + usages = eam_keypad_usages; + } + + /* Set flag first: don't try again if this fails for whatever reason. */ + + unit->bound_ctls = true; + + hid_mgr_lock(); + + if (!hid_stub_get_controls(unit->hid, NULL, &ncontrols)) { + goto size_fail; + } + + controls = xmalloc(ncontrols * sizeof(*controls)); + + if (!hid_stub_get_controls(unit->hid, controls, &ncontrols)) { + goto content_fail; + } + + for (control_no = 0 ; control_no < ncontrols ; control_no++) { + for (btn_no = 0 ; btn_no < EAM_IO_KEYPAD_COUNT ; btn_no++) { + if (controls[control_no].usage == usages[btn_no]) { + unit->keypad_ctls[btn_no] = control_no; + } + } + + if (controls[control_no].usage == usages[EAM_IO_KEYPAD_COUNT]) { + unit->sensor_ctl = control_no; + } + } + + free(controls); + + hid_mgr_unlock(); + + return; + +content_fail: + free(controls); + +size_fail: + hid_mgr_unlock(); +} + +bool eam_impl_get_sensor_state(struct eam *eam, uint8_t unit_no) +{ + struct eam_unit *unit; + uint32_t now; + int32_t value; + bool result; + + log_assert(unit_no < lengthof(eam->units)); + + unit = &eam->units[unit_no]; + result = false; + + eam_impl_bind_keypad(eam, unit_no); + + if (!unit->bound_ctls) { + return false; + } + + if (unit->sensor_ctl == (size_t) -1) { + return false; + } + + if (!hid_stub_get_value(unit->hid, unit->sensor_ctl, &value)) { + return false; + } + + now = GetTickCount(); + + EnterCriticalSection(&eam->lock); + + /* Bump cooldown as long as sensor button is held + (cooldown timer might also be set by a USB hotplug event) */ + + if (value != 0 && (!eam->mux || eam_impl_get_active_unit() == unit_no)) { + unit->sensor_time = now + EAM_SENSOR_COOLDOWN; + unit->sensor_hot = true; + } + + if (unit->sensor_hot) { + if ((int32_t) (unit->sensor_time - now) > 0) { + result = true; + } else { + unit->sensor_time = 0; + unit->sensor_hot = false; + + result = false; + } + } + + LeaveCriticalSection(&eam->lock); + + return result; +} + +uint8_t eam_impl_read_card(struct eam *eam, uint8_t unit_no, uint8_t *card_id, + uint8_t nbytes) +{ + char line[128]; + struct eam_unit *unit; + size_t len; + FILE *f; + + log_assert(unit_no < lengthof(eam->units)); + log_assert(card_id != NULL); + log_assert(nbytes == EAM_CARD_NBYTES); + + unit = &eam->units[unit_no]; + + if (unit->card_path == NULL) { + goto path_fail; + } + + f = fopen(unit->card_path, "r"); + + if (f == NULL) { + if (eam->autogen) { + if (!eam_impl_autogen(unit, card_id)) { + log_warning("Unit %d: Failed to generate card ID into %s", + unit_no, unit->card_path); + + goto fopen_fail; + } + + return true; + } else { + log_warning("Unit %d: Card file at %s not present", + unit_no, unit->card_path); + + goto fopen_fail; + } + } + + if (fgets(line, sizeof(line), f) == NULL) { + log_warning("%s: fgets() failed", unit->card_path); + + goto fgets_fail; + } + + str_trim(line); + len = strlen(line); + + if (len != 2 * EAM_CARD_NBYTES) { + log_warning("%s: Expected %u chars (got %u)", + unit->card_path, 2 * EAM_CARD_NBYTES, (unsigned int) len); + + goto len_fail; + } + + if (!hex_decode(card_id, nbytes, line, len)) { + log_warning("%s: Invalid hex [%s]", unit->card_path, card_id); + + goto decode_fail; + } + + log_misc("Unit %d: Loaded card ID [%s] from file %s", + unit_no, line, unit->card_path); + + fclose(f); + + if (card_id[0] == 0xe0 && card_id[1] == 0x04) { + return EAM_IO_CARD_ISO15696; + } else { + return EAM_IO_CARD_FELICA; + } + +decode_fail: +len_fail: +fgets_fail: + fclose(f); + +fopen_fail: +path_fail: + return EAM_IO_CARD_NONE; +} + +static bool eam_impl_autogen(struct eam_unit *unit, uint8_t *card_id) +{ + char hex[2 * EAM_CARD_NBYTES + 1]; + FILE *f; + size_t i; + + f = fopen(unit->card_path, "w"); + + if (f == NULL) { + return false; + } + + srand(GetTickCount()); + + card_id[0] = 0xE0; + card_id[1] = 0x04; + card_id[2] = 0x01; + card_id[3] = 0x00; + + for (i = 4 ; i < 8 ; i++) { + /* LSBit entropy of typical LFSR RNGs is usually poor */ + card_id[i] = rand() >> 7; + } + + hex_encode_uc(card_id, EAM_CARD_NBYTES, hex, sizeof(hex)); + fwrite(hex, sizeof(hex) - 1, 1, f); + + fclose(f); + + log_info("Generated random card ID [%s] into file %s", + hex, unit->card_path); + + return true; +} + +void eam_impl_notify_hotplug(struct eam *eam, uint8_t drive_no) +{ + struct eam_unit *unit; + uint8_t unit_no; + + EnterCriticalSection(&eam->lock); + + for (unit_no = 0 ; unit_no < lengthof(eam->units) ; unit_no++) { + if (eam->units[unit_no].drive_no == drive_no) { + /* MMSYSTEM timeGetTime() is overkill, we don't exactly need super + accurate timestamps here. */ + + unit = &eam->units[unit_no]; + + unit->sensor_time = GetTickCount() + EAM_SENSOR_COOLDOWN; + unit->sensor_hot = true; + } + } + + LeaveCriticalSection(&eam->lock); +} + +void eam_impl_destroy(struct eam *eam) +{ + int8_t unit_no; + + for (unit_no = lengthof(eam->units) - 1 ; unit_no >= 0 ; unit_no--) { + free(eam->units[unit_no].card_path); + } + + DeleteCriticalSection(&eam->lock); + + free(eam); +} + diff --git a/src/main/eamio/eam-impl.h b/src/main/eamio/eam-impl.h new file mode 100644 index 0000000..075aade --- /dev/null +++ b/src/main/eamio/eam-impl.h @@ -0,0 +1,35 @@ +#ifndef EAMIO_EAM_IMPL_H +#define EAMIO_EAM_IMPL_H + +#include +#include +#include + +#include "bemanitools/eamio.h" + +#include "geninput/hid-mgr.h" + +#define EAM_CARD_NBYTES 8 +#define EAM_UNIT_COUNT 2 + +struct eam; + +struct eam *eam_impl_create(void); +bool eam_impl_get_autogen(struct eam *eam); +void eam_impl_set_autogen(struct eam *eam, bool autogen); +bool eam_impl_get_alt_10k(struct eam *eam); +void eam_impl_set_alt_10k(struct eam *eam, bool alt_10k); +struct hid_stub *eam_impl_get_keypad_device(struct eam *eam, uint8_t unit_no); +void eam_impl_set_keypad_device(struct eam *eam, uint8_t unit_no, + struct hid_stub *hid); +const char *eam_impl_get_card_path(struct eam *eam, uint8_t unit_no); +void eam_impl_set_card_path(struct eam *eam, uint8_t unit_no, + const char *path); +uint16_t eam_impl_get_keypad_state(struct eam *eam, uint8_t unit_no); +bool eam_impl_get_sensor_state(struct eam *eam, uint8_t unit_no); +uint8_t eam_impl_read_card(struct eam *eam, uint8_t unit_no, uint8_t *card_id, + uint8_t nbytes); +void eam_impl_notify_hotplug(struct eam *eam, uint8_t drive_no); +void eam_impl_destroy(struct eam *eam); + +#endif diff --git a/src/main/eamio/eam-s11n.c b/src/main/eamio/eam-s11n.c new file mode 100644 index 0000000..a611db9 --- /dev/null +++ b/src/main/eamio/eam-s11n.c @@ -0,0 +1,122 @@ +#include +#include + +#include "eamio/eam-impl.h" +#include "eamio/eam-s11n.h" + +#include "util/fs.h" + +struct eam *eam_impl_config_load(FILE *f) +{ + struct eam *eam; + struct hid_stub *hid; + int8_t nunits; + int8_t unit_no; + char *card_path; + char *dev_node; + bool autogen; + bool alt_10k; + bool has_card_path; + bool has_hid; + + eam = eam_impl_create(); + + if (!read_u8(f, &autogen)) { + goto early_fail; + } + + if (!read_u8(f, &alt_10k)) { + goto early_fail; + } + + eam_impl_set_autogen(eam, autogen); + eam_impl_set_alt_10k(eam, alt_10k); + + if (!read_u8(f, &nunits) || nunits != EAM_UNIT_COUNT) { + goto early_fail; + } + + hid_mgr_lock(); + + for (unit_no = 0 ; unit_no < EAM_UNIT_COUNT ; unit_no++) { + if (!read_u8(f, &has_card_path)) { + goto late_fail; + } + + if (!read_u8(f, &has_hid)) { + goto late_fail; + } + + if (has_card_path) { + if (!read_str(f, &card_path)) { + goto late_fail; + } + + eam_impl_set_card_path(eam, unit_no, card_path); + free(card_path); + } + + if (has_hid) { + if (!read_str(f, &dev_node)) { + goto late_fail; + } + + hid = hid_mgr_get_named_stub(dev_node); + eam_impl_set_keypad_device(eam, unit_no, hid); + + free(dev_node); + } + } + + hid_mgr_unlock(); + + return eam; + +late_fail: + hid_mgr_unlock(); + +early_fail: + eam_impl_destroy(eam); + + return NULL; +} + +void eam_impl_config_save(struct eam *eam, FILE *f) +{ + const char *card_path; + struct hid_stub *hid; + uint8_t nunits; + uint8_t unit_no; + bool autogen; + bool alt_10k; + bool has_card_path; + bool has_hid; + + autogen = eam_impl_get_autogen(eam); + alt_10k = eam_impl_get_alt_10k(eam); + nunits = EAM_UNIT_COUNT; + + write_u8(f, &autogen); + write_u8(f, &alt_10k); + write_u8(f, &nunits); + + for (unit_no = 0 ; unit_no < EAM_UNIT_COUNT ; unit_no++) { + card_path = eam_impl_get_card_path(eam, unit_no); + hid = eam_impl_get_keypad_device(eam, unit_no); + + has_card_path = card_path != NULL; + has_hid = hid != NULL; + + write_u8(f, &has_card_path); + write_u8(f, &has_hid); + + if (has_card_path) { + write_str(f, card_path); + } + + if (has_hid) { + write_str(f, hid_stub_get_dev_node(hid)); + } + } +} + diff --git a/src/main/eamio/eam-s11n.h b/src/main/eamio/eam-s11n.h new file mode 100644 index 0000000..1c741bc --- /dev/null +++ b/src/main/eamio/eam-s11n.h @@ -0,0 +1,11 @@ +#ifndef GENINPUT_EAM_S11N_H +#define GENINPUT_EAM_S11N_H + +#include + +#include "eamio/eam-impl.h" + +struct eam *eam_impl_config_load(FILE *f); +void eam_impl_config_save(struct eam *eam, FILE *f); + +#endif diff --git a/src/main/eamio/eamio.def b/src/main/eamio/eamio.def new file mode 100644 index 0000000..7bfca97 --- /dev/null +++ b/src/main/eamio/eamio.def @@ -0,0 +1,12 @@ +LIBRARY eamio + +EXPORTS + eam_io_fini + eam_io_init + eam_io_get_config_api + eam_io_get_keypad_state + eam_io_get_sensor_state + eam_io_read_card + eam_io_card_slot_cmd + eam_io_set_loggers + eam_io_poll diff --git a/src/main/eamiotest/Module.mk b/src/main/eamiotest/Module.mk new file mode 100644 index 0000000..a54384b --- /dev/null +++ b/src/main/eamiotest/Module.mk @@ -0,0 +1,8 @@ +exes += eamiotest + +libs_eamiotest := \ + eamio \ + util \ + +src_eamiotest := \ + main.c \ diff --git a/src/main/eamiotest/main.c b/src/main/eamiotest/main.c new file mode 100644 index 0000000..559e6bf --- /dev/null +++ b/src/main/eamiotest/main.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include + +#include + +#include "bemanitools/eamio.h" + +#include "util/log.h" +#include "util/thread.h" + +/** + * Tool to test your implementations of eamio. + */ +int main(int argc, char** argv) +{ + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + printf("Initializing eamio failed\n"); + return -1; + } + + printf(">>> Initializing eamio successful, press enter to continue <<<\n"); + + if (getchar() != '\n') { + return 0; + } + + uint16_t keypad_prev[2]; + uint8_t card[2][8]; + + memset(keypad_prev, 0, sizeof(keypad_prev)); + memset(card, 0, sizeof(card)); + + while (true) { + system("cls"); + + if ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) { + break; + } + + for (uint8_t node = 0; node < 2; ++node) { + + if (!eam_io_poll(node)) { + printf("ERROR: Polling node %d failed", node); + return -2; + } + + uint16_t keypad = eam_io_get_keypad_state(node); + uint16_t keypad_rise = ~keypad_prev[node] & keypad; + + uint8_t sensors = eam_io_get_sensor_state(node); + + printf( + "Press escape to quit\n" + "Reader %d\n" + "------------------\n" + " |7: %d|8: %d|9: %d|\n" + " |4: %d|5: %d|6: %d|\n" + " |1: %d|2: %d|3: %d|\n" + " |0: %d|O: %d|_: %d|\n" + "------------------\n" + "|front: %d back: %d|\n" + "------------------\n" + "|%02X%02X%02X%02X%02X%02X%02X%02X|\n" + "------------------\n", + node, + (keypad & (1 << EAM_IO_KEYPAD_7)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_8)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_9)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_4)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_5)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_6)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_1)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_2)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_3)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_0)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_00)) > 0, + (keypad & (1 << EAM_IO_KEYPAD_DECIMAL)) > 0, + (sensors & (1 << EAM_IO_SENSOR_FRONT)) > 0, + (sensors & (1 << EAM_IO_SENSOR_BACK)) > 0, + card[node][0], card[node][1], card[node][2], card[node][3], + card[node][4], card[node][5], card[node][6], card[node][7]); + + if (sensors & (1 << EAM_IO_SENSOR_BACK)) { + eam_io_card_slot_cmd(node, EAM_IO_CARD_SLOT_CMD_CLOSE); + eam_io_poll(node); + eam_io_card_slot_cmd(node, EAM_IO_CARD_SLOT_CMD_READ); + eam_io_poll(node); + eam_io_read_card(node, card[node], 8); + } + + if (sensors == 0) { + memset(card[node], 0, 8); + + eam_io_card_slot_cmd(node, EAM_IO_CARD_SLOT_CMD_CLOSE); + eam_io_poll(node); + eam_io_card_slot_cmd(node, EAM_IO_CARD_SLOT_CMD_OPEN); + } + + if (keypad_rise & (1 << EAM_IO_KEYPAD_DECIMAL)) { + eam_io_card_slot_cmd(node, EAM_IO_CARD_SLOT_CMD_EJECT); + } + + keypad_prev[node] = keypad; + } + + /* avoid CPU banging */ + Sleep(5); + } + + eam_io_fini(); + + return 0; +} \ No newline at end of file diff --git a/src/main/ezusb-emu/Module.mk b/src/main/ezusb-emu/Module.mk new file mode 100644 index 0000000..8fc6a0d --- /dev/null +++ b/src/main/ezusb-emu/Module.mk @@ -0,0 +1,9 @@ +libs += ezusb-emu + +libs_ezusb-emu := \ + ezusb \ + +src_ezusb-emu := \ + desc.c \ + device.c \ + util.c diff --git a/src/main/ezusb-emu/conf.h b/src/main/ezusb-emu/conf.h new file mode 100644 index 0000000..11f8e9f --- /dev/null +++ b/src/main/ezusb-emu/conf.h @@ -0,0 +1,7 @@ +#ifndef EZUSB_EMU_CONF_H +#define EZUSB_EMU_CONF_H + +//#define EZUSB_EMU_DEBUG_DUMP +//#define EZUSB_EMU_FW_DUMP + +#endif \ No newline at end of file diff --git a/src/main/ezusb-emu/desc.c b/src/main/ezusb-emu/desc.c new file mode 100644 index 0000000..61ba229 --- /dev/null +++ b/src/main/ezusb-emu/desc.c @@ -0,0 +1,16 @@ +#include "ezusb-emu/desc.h" + +const struct ezusb_emu_desc_device ezusb_emu_desc_device = { + .setupapi = { + .device_guid = { + 0xAE18AA60, + 0x7F6A, + 0x11D4, + { 0x97, 0xDD, 0x00, 0x01, 0x02, 0x29, 0xB9, 0x59 } + }, + .device_desc = "Cypress EZ-USB (2235) - EEPROM missing", + .device_path = "\\\\.\\Ezusb-0" + }, + .vid = 0x0547, + .pid = 0x2235, +}; \ No newline at end of file diff --git a/src/main/ezusb-emu/desc.h b/src/main/ezusb-emu/desc.h new file mode 100644 index 0000000..f72eced --- /dev/null +++ b/src/main/ezusb-emu/desc.h @@ -0,0 +1,17 @@ +#ifndef EZUSB_EMU_DESC_H +#define EZUSB_EMU_DESC_H + +#include + +#include "hooklib/setupapi.h" + +struct ezusb_emu_desc_device { + struct hook_setupapi_data setupapi; + uint16_t vid; + uint16_t pid; +}; + +/* C02 IO board */ +extern const struct ezusb_emu_desc_device ezusb_emu_desc_device; + +#endif diff --git a/src/main/ezusb-emu/device.c b/src/main/ezusb-emu/device.c new file mode 100644 index 0000000..fb90308 --- /dev/null +++ b/src/main/ezusb-emu/device.c @@ -0,0 +1,308 @@ +#define LOG_MODULE "ezusb-emu-device" + +#include +#include +#include + +#include + +#include "ezusb/ezusbsys2.h" +#include "ezusb/util.h" + +#include "ezusb-emu/conf.h" +#include "ezusb-emu/desc.h" +#include "ezusb-emu/device.h" +#include "ezusb-emu/msg.h" +#include "ezusb-emu/util.h" + +#include "hook/iohook.h" + +#include "imports/avs.h" + +#include "util/fs.h" +#include "util/hex.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +static HRESULT ezusb_get_device_descriptor(struct irp *irp); +static HRESULT ezusb_vendor_req(struct irp *irp); +static HRESULT ezusb_upload_fw(struct irp *irp); +static HRESULT ezusb_pipe_read(struct irp *irp); +static HRESULT ezusb_pipe_write(struct irp *irp); + +static HRESULT ezusb_open(struct irp *irp); +static HRESULT ezusb_ioctl(struct irp *irp); + +enum ezusb_pipe { + /* This is just the NT driver API. Add 1 to get the actual EP number. */ + EZUSB_PIPE_INTERRUPT_OUT = 0, + EZUSB_PIPE_INTERRUPT_IN = 1, + EZUSB_PIPE_BULK_OUT = 2, + EZUSB_PIPE_BULK_IN = 3 +}; + +static HANDLE ezusb_emu_fd; +static struct ezusb_firmware* ezusb_emu_firmware; +static struct ezusb_emu_msg_hook* ezusb_emu_dev_fx_msg_hook; + +void ezusb_emu_device_hook_init(struct ezusb_emu_msg_hook* msg_hook) +{ + log_assert(ezusb_emu_fd == NULL); + + ezusb_emu_fd = iohook_open_dummy_fd(); + ezusb_emu_dev_fx_msg_hook = msg_hook; +} + +void ezusb_emu_device_hook_fini(void) +{ + if (ezusb_emu_fd != NULL) { + CloseHandle(ezusb_emu_fd); + } + + ezusb_emu_fd = NULL; +} + +HRESULT ezusb_emu_device_dispatch_irp(struct irp *irp) +{ + if (irp->op != IRP_OP_OPEN && irp->fd != ezusb_emu_fd) { + return irp_invoke_next(irp); + } + + /* read/write are not supported, and the game-side EZUSB code constantly + churns through FDs opening and closing them (so we silently acknowledge + CloseHandle calls and don't even log them). */ + + switch (irp->op) { + case IRP_OP_OPEN: return ezusb_open(irp); + case IRP_OP_CLOSE: return S_OK; + case IRP_OP_IOCTL: return ezusb_ioctl(irp); + default: return E_NOTIMPL; + } +} + +/* + * WIN32 I/O AND IOHOOK LAYER + */ + +static HRESULT ezusb_open(struct irp *irp) +{ + log_assert(irp != NULL); + + if (!wstr_eq(irp->open_filename, L"\\\\.\\Ezusb-0")) { + return irp_invoke_next(irp); + } + + irp->fd = ezusb_emu_fd; + + return S_OK; +} + +static HRESULT ezusb_ioctl(struct irp *irp) +{ + /* For debugging */ +#ifdef EZUSB_EMU_DEBUG_DUMP + /* For debugging */ + ezusb_emu_util_log_usb_msg("BEFORE", irp->ioctl, irp->read.bytes, + irp->read.nbytes, irp->read.bytes, irp->read.nbytes, irp->write.bytes, + irp->write.nbytes); +#endif + + /* Cases are listed in order of first receipt */ + switch (irp->ioctl) { + case IOCTL_Ezusb_GET_DEVICE_DESCRIPTOR: + return ezusb_get_device_descriptor(irp); + + case IOCTL_Ezusb_VENDOR_REQUEST: + return ezusb_vendor_req(irp); + + case IOCTL_EZUSB_ANCHOR_DOWNLOAD: + return ezusb_upload_fw(irp); + + case IOCTL_EZUSB_BULK_READ: + /* Misnomer: can be bulk or interrupt. */ + return ezusb_pipe_read(irp); + + case IOCTL_EZUSB_BULK_WRITE: + /* Ditto. */ + return ezusb_pipe_write(irp); + + default: + log_warning("Unknown ioctl %08x", irp->ioctl); + + return E_INVALIDARG; + } + +#ifdef EZUSB_EMU_DEBUG_DUMP + /* For debugging */ + ezusb_emu_util_log_usb_msg("AFTER", irp->ioctl, irp->read.bytes, + irp->read.nbytes, irp->read.bytes, irp->read.nbytes, irp->write.bytes, + irp->write.nbytes); +#endif +} +/* + * USB TRANSFER LAYER + */ + +static HRESULT ezusb_get_device_descriptor(struct irp *irp) +{ + USB_DEVICE_DESCRIPTOR *desc; + + log_assert(irp != NULL); + + if (irp->read.nbytes < sizeof(*desc)) { + log_warning("USB_DEVICE_DESCRIPTOR buffer too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + desc = (USB_DEVICE_DESCRIPTOR *) irp->read.bytes; + + memset(desc, 0, sizeof(*desc)); + desc->idVendor = ezusb_emu_desc_device.vid; + desc->idProduct = ezusb_emu_desc_device.pid; + irp->read.pos = sizeof(*desc); + + log_misc( + "get_device_descriptor: vid %02x, pid %02x", + desc->idVendor, + desc->idProduct); + + return S_OK; +} + +static HRESULT ezusb_vendor_req(struct irp *irp) +{ + VENDOR_OR_CLASS_REQUEST_CONTROL *vc; + + log_assert(irp != NULL); + + if (irp->write.nbytes < sizeof(*vc)) { + log_warning("VENDOR_OR_CLASS_REQUEST_CONTROL buffer too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + vc = (VENDOR_OR_CLASS_REQUEST_CONTROL *) irp->write.bytes; + + log_misc( + "vendor req %02x, value %04x, index %04x", + vc->request, + vc->value, + vc->index); + + if (vc->request == 0x00 && vc->value == 0x0001 && vc->index == 0x0100) { + log_misc("vendor req: reset hold, starting fw download..."); + ezusb_emu_firmware = ezusb_firmware_alloc(); + } else if (vc->request == 0x00 && vc->value == 0x0001 && vc->index == 0x0000) { + log_misc("vendor req: reset release, finished fw download"); + /* FW download finished, reset 8051 and start the downloaded FW */ + + ezusb_emu_firmware->crc = ezusb_firmware_crc(ezusb_emu_firmware); + +#ifdef EZUSB_EMU_FW_DUMP + if (!ezusb_firmware_save("ezusb_fx.bin", ezusb_emu_firmware)) { + log_fatal("Saving dumped firmware failed"); + } else { + log_misc("firmware dumped do ezusb_fx.bin file"); + } +#endif + + free(ezusb_emu_firmware); + ezusb_emu_firmware = NULL; + } else { + log_warning("VENDOR_OR_CLASS_REQUEST_CONTROL unknown request"); + } + + return S_OK; +} + +static HRESULT ezusb_upload_fw(struct irp *irp) +{ + ANCHOR_DOWNLOAD_CONTROL *hdr; + + log_assert(irp != NULL); + + if (irp->write.nbytes < sizeof(*hdr)) { + log_warning("ANCHOR_DOWNLOAD_CONTROL buffer too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + hdr = (ANCHOR_DOWNLOAD_CONTROL *) irp->write.bytes; + + /* The semantics for this ioctl are FUCKED! The second buffer is + supposed to receive data, not send it! */ + + log_misc( + "upload_fw: offset: %04x, nbytes: %04x", + hdr->Offset, + irp->read.nbytes); + ezusb_firmware_add_segment(ezusb_emu_firmware, + ezusb_firmware_segment_alloc(hdr->Offset, irp->read.nbytes, + (void*) irp->read.bytes)); + + return S_OK; +} + +static HRESULT ezusb_pipe_read(struct irp *irp) +{ + BULK_TRANSFER_CONTROL *ctl; + + log_assert(irp != NULL); + + if (irp->write.nbytes < sizeof(*ctl)) { + log_warning("BULK_TRANSFER_CONTROL buffer too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + ctl = (BULK_TRANSFER_CONTROL *) irp->write.bytes; + + switch (ctl->pipeNum) { + case EZUSB_PIPE_INTERRUPT_IN: + return ezusb_emu_dev_fx_msg_hook->interrupt_read(&irp->read); + + case EZUSB_PIPE_BULK_IN: + return ezusb_emu_dev_fx_msg_hook->bulk_read(&irp->read); + + default: + log_warning("No such read pipe: %u", (unsigned int) ctl->pipeNum); + + return E_INVALIDARG; + } +} + +static HRESULT ezusb_pipe_write(struct irp *irp) +{ + BULK_TRANSFER_CONTROL *ctl; + struct const_iobuf write; + + if (irp->write.nbytes < sizeof(*ctl)) { + log_warning("BULK_TRANSFER_CONTROL buffer too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + ctl = (BULK_TRANSFER_CONTROL *) irp->write.bytes; + + /* ugh */ + write.bytes = irp->read.bytes; + write.nbytes = irp->read.nbytes; + write.pos = 0; + + switch (ctl->pipeNum) { + case EZUSB_PIPE_INTERRUPT_OUT: + return ezusb_emu_dev_fx_msg_hook->interrupt_write(&write); + + case EZUSB_PIPE_BULK_OUT: + return ezusb_emu_dev_fx_msg_hook->bulk_write(&write); + + default: + log_warning("No such write pipe: %u", (unsigned int) ctl->pipeNum); + + return E_INVALIDARG; + } +} + + diff --git a/src/main/ezusb-emu/device.h b/src/main/ezusb-emu/device.h new file mode 100644 index 0000000..224a6de --- /dev/null +++ b/src/main/ezusb-emu/device.h @@ -0,0 +1,32 @@ +#ifndef EZUSB_EMU_DEVICE_H +#define EZUSB_EMU_DEVICE_H + +#include + +#include +#include + +#include "ezusb-emu/msg.h" + +#include "hook/iohook.h" + +/** + * Hook IO functions to intercept with ezusb (IIDX C02) communication and + * detour to our emulation code. + * + * @param msg_hook Hook functions to dispatch ezusb interrupt and bulk device + * messages to + */ +void ezusb_emu_device_hook_init(struct ezusb_emu_msg_hook* msg_hook); + +/** + * Cleanup the hooked IO functions. + */ +void ezusb_emu_device_hook_fini(void); + +/** + * Iohook interface. + */ +HRESULT ezusb_emu_device_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/ezusb-emu/msg.h b/src/main/ezusb-emu/msg.h new file mode 100644 index 0000000..6689824 --- /dev/null +++ b/src/main/ezusb-emu/msg.h @@ -0,0 +1,18 @@ +#ifndef EZUSB_EMU_MSG_H +#define EZUSB_EMU_MSG_H + +#include + +#include +#include + +#include "util/iobuf.h" + +struct ezusb_emu_msg_hook { + HRESULT (*interrupt_read)(struct iobuf *read); + HRESULT (*interrupt_write)(struct const_iobuf *write); + HRESULT (*bulk_read)(struct iobuf *read); + HRESULT (*bulk_write)(struct const_iobuf *write); +}; + +#endif diff --git a/src/main/ezusb-emu/util.c b/src/main/ezusb-emu/util.c new file mode 100644 index 0000000..dea0b72 --- /dev/null +++ b/src/main/ezusb-emu/util.c @@ -0,0 +1,75 @@ +#define LOG_MODULE "ezusb-emu-util" + +#include + +#include +#include +#include + +#include "ezusb-emu/util.h" + +#include "util/hex.h" +#include "util/log.h" + +enum ezusb_pipe { + /* This is just the NT driver API. Add 1 to get the actual EP number. */ + EZUSB_PIPE_INTERRUPT_OUT = 0, + EZUSB_PIPE_INTERRUPT_IN = 1, + EZUSB_PIPE_BULK_OUT = 2, + EZUSB_PIPE_BULK_IN = 3 +}; + +void ezusb_emu_util_log_usb_msg(const char* prefix, uint32_t ctl_code, + const BULK_TRANSFER_CONTROL *ctl, uint32_t ctl_size, void* header, + uint32_t header_bytes, void* data, uint32_t data_bytes) +{ + char header_str[4096]; + char data_str[4096]; + const char* ctl_code_str; + + switch (ctl_code) { + case IOCTL_Ezusb_GET_DEVICE_DESCRIPTOR: + ctl_code_str = "GET_DEVICE_DESCRIPTOR"; + break; + + case IOCTL_Ezusb_VENDOR_REQUEST: + ctl_code_str = "VENDOR_REQUEST"; + break; + + case IOCTL_EZUSB_ANCHOR_DOWNLOAD: + ctl_code_str = "ANCHOR_DOWNLOAD"; + break; + + case IOCTL_EZUSB_BULK_READ: + if (ctl->pipeNum == EZUSB_PIPE_INTERRUPT_IN) { + ctl_code_str = "INT_READ"; + } else if (ctl->pipeNum == EZUSB_PIPE_BULK_IN) { + ctl_code_str = "BULK_READ"; + } else { + ctl_code_str = "INVALID_READ"; + } + + break; + + case IOCTL_EZUSB_BULK_WRITE: + if (ctl->pipeNum == EZUSB_PIPE_INTERRUPT_OUT) { + ctl_code_str = "INT_WRITE"; + } else if (ctl->pipeNum == EZUSB_PIPE_BULK_OUT) { + ctl_code_str = "BULK_WRITE"; + } else { + ctl_code_str = "INVALID_WRITE"; + } + + break; + + default: + ctl_code_str = "UNKNOWN"; + break; + } + + hex_encode_uc(header, header_bytes, header_str, sizeof(header_str)); + hex_encode_uc(data, data_bytes, data_str, sizeof(data_str)); + + log_warning("[EZUSB DUMP %s][%s] header(%d) %s |||| data(%d) %s", + prefix, ctl_code_str, header_bytes, header_str, data_bytes, data_str); +} \ No newline at end of file diff --git a/src/main/ezusb-emu/util.h b/src/main/ezusb-emu/util.h new file mode 100644 index 0000000..e6d5a93 --- /dev/null +++ b/src/main/ezusb-emu/util.h @@ -0,0 +1,18 @@ +#ifndef EZUSB_EMU_UTIL_H +#define EZUSB_EMU_UTIL_H + +#include + +#include +#include +#include + +#include "ezusb/ezusbsys2.h" + +#include "hook/iohook.h" + +void ezusb_emu_util_log_usb_msg(const char* prefix, uint32_t ctl_code, + const BULK_TRANSFER_CONTROL *ctl, uint32_t ctl_size, void* header, + uint32_t header_bytes, void* data, uint32_t data_bytes); + +#endif diff --git a/src/main/ezusb-iidx-emu/Module.mk b/src/main/ezusb-iidx-emu/Module.mk new file mode 100644 index 0000000..a8bbef5 --- /dev/null +++ b/src/main/ezusb-iidx-emu/Module.mk @@ -0,0 +1,19 @@ +libs += ezusb-iidx-emu + +libs_ezusb-iidx-emu := \ + ezusbemu \ + +src_ezusb-iidx-emu := \ + card-mag.c \ + msg.c \ + node-16seg.c \ + node-coin.c \ + node-eeprom.c \ + node-fpga.c \ + node-none.c \ + node-security-mem.c \ + node-security-plug.c \ + node-serial.c \ + node-sram.c \ + node-wdt.c \ + nodes.c \ diff --git a/src/main/ezusb-iidx-emu/card-mag.c b/src/main/ezusb-iidx-emu/card-mag.c new file mode 100644 index 0000000..cef7fb6 --- /dev/null +++ b/src/main/ezusb-iidx-emu/card-mag.c @@ -0,0 +1,175 @@ +#include "ezusb-iidx-emu/card-mag.h" + +#include + +#include "security/mcode.h" + +#include "util/crc.h" +#include "util/log.h" + +static const uint16_t ezusb_iidx_emu_card_mag_checksum_table_payload[256] = { + 0x0, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, 0x8C48, 0x9DC1, 0x0AF5A, + 0x0BED3, 0x0CA6C, 0x0DBE5, 0x0E97E, 0x0F8F7, 0x1081, 0x108, 0x3393, 0x221A, 0x56A5, + 0x472C, 0x75B7, 0x643E, 0x9CC9, 0x8D40, 0x0BFDB, 0x0AE52, 0x0DAED, 0x0CB64, 0x0F9FF, + 0x0E876, 0x2102, 0x308B, 0x210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD, 0x0AD4A, + 0x0BCC3, 0x8E58, 0x9FD1, 0x0EB6E, 0x0FAE7, 0x0C87C, 0x0D9F5, 0x3183, 0x200A, + 0x1291, 0x318, 0x77A7, 0x662E, 0x54B5, 0x453C, 0x0BDCB, 0x0AC42, 0x9ED9, 0x8F50, + 0x0FBEF, 0x0EA66, 0x0D8FD, 0x0C974, 0x4204, 0x538D, 0x6116, 0x709F, 0x420, 0x15A9, + 0x2732, 0x36BB, 0x0CE4C, 0x0DFC5, 0x0ED5E, 0x0FCD7, 0x8868, 0x99E1, 0x0AB7A, + 0x0BAF3, 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x528, 0x37B3, 0x263A, 0x0DECD, + 0x0CF44, 0x0FDDF, 0x0EC56, 0x98E9, 0x8960, 0x0BBFB, 0x0AA72, 0x6306, 0x728F, + 0x4014, 0x519D, 0x2522, 0x34AB, 0x630, 0x17B9, 0x0EF4E, 0x0FEC7, 0x0CC5C, 0x0DDD5, + 0x0A96A, 0x0B8E3, 0x8A78, 0x9BF1, 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, + 0x16B1, 0x738, 0x0FFCF, 0x0EE46, 0x0DCDD, 0x0CD54, 0x0B9EB, 0x0A862, 0x9AF9, + 0x8B70, 0x8408, 0x9581, 0x0A71A, 0x0B693, 0x0C22C, 0x0D3A5, 0x0E13E, 0x0F0B7, + 0x840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF, 0x9489, 0x8500, + 0x0B79B, 0x0A612, 0x0D2AD, 0x0C324, 0x0F1BF, 0x0E036, 0x18C1, 0x948, 0x3BD3, + 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, 0x0A50A, 0x0B483, 0x8618, 0x9791, 0x0E32E, + 0x0F2A7, 0x0C03C, 0x0D1B5, 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, + 0x5DFD, 0x0B58B, 0x0A402, 0x9699, 0x8710, 0x0F3AF, 0x0E226, 0x0D0BD, 0x0C134, + 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C, 0x0C60C, 0x0D785, + 0x0E51E, 0x0F497, 0x8028, 0x91A1, 0x0A33A, 0x0B2B3, 0x4A44, 0x5BCD, 0x6956, 0x78DF, + 0x0C60, 0x1DE9, 0x2F72, 0x3EFB, 0x0D68D, 0x0C704, 0x0F59F, 0x0E416, 0x90A9, 0x8120, + 0x0B3BB, 0x0A232, 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, + 0x0E70E, 0x0F687, 0x0C41C, 0x0D595, 0x0A12A, 0x0B0A3, 0x8238, 0x93B1, 0x6B46, + 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, 0x0F78F, 0x0E606, 0x0D49D, + 0x0C514, 0x0B1AB, 0x0A022, 0x92B9, 0x8330, 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, + 0x2C6A, 0x1EF1, 0x0F78 +}; + +/* ------------------------------------------------------------------ */ + +// static void target_form_8_digit_to_long(const uint8_t* in_buf, uint8_t* out_buf) +// { +// out_buf[0] = (in_buf[0] & 0x3F) + 0x20; +// out_buf[1] = ((in_buf[1] >> 2) & 0x3F) + 0x20; +// out_buf[2] = (((in_buf[1] & 0x03) << 4) | ((in_buf[2] >> 4) & 0x0F)) + 0x20; +// } + +static void ezusb_iidx_emu_card_mag_target_form_8_digit_to_short( + const uint8_t* in_buf, uint8_t* out_buf) +{ + out_buf[0] = (out_buf[0] & 0xC0) | ((in_buf[0] - 0x20) & 0x3F); + out_buf[1] = (((in_buf[1] - 0x20) & 0x3F) << 2) | + (((in_buf[2] - 0x20) >> 4) & 0x03); + out_buf[2] = (((in_buf[2] - 0x20) & 0x0F) << 4) | (out_buf[2] & 0x0F); +} + +static uint16_t ezusb_iidx_emu_card_mag_calc_checksum_payload( + const uint8_t* buffer, size_t length) +{ + unsigned int result; // eax@1 + int v3; // esi@2 + const uint8_t* v4; // ecx@2 + + result = 0xFFFF; + + v3 = length; + v4 = buffer; + + do { + result = ezusb_iidx_emu_card_mag_checksum_table_payload[*v4++ ^ + (unsigned __int8) result] ^ (result >> 8); + --v3; + } while (v3); + + return result; +} + +static void ezusb_iidx_emu_card_mag_update_checksums( + struct ezusb_iidx_emu_card_mag_data* card, const char* card_version) +{ + /* at least the header checksum and the checksums for the + data sectors are the same for all games...*/ + + /* checksum the header only */ + card->header.checksum = crc8(&card->header.flags, 4, 0); + + /* checksum the actual payload of the payload */ + for (uint8_t i = 0; i < MAG_CARD_NUM_DATA_SECTORS; i++) { + /* seed is hardcoded to 0 */ + card->data_sector[i].checksum = crc8( + (uint8_t*) &card->data_sector[i], 9, 0); + } + + /* but the checksum for all data sectors differs... */ + if ( !memcmp(card_version, SECURITY_MCODE_GAME_IIDX_9, + SECURITY_MCODE_GAME_LEN) || + !memcmp(card_version, SECURITY_MCODE_GAME_IIDX_11, + SECURITY_MCODE_GAME_LEN) || + !memcmp(card_version, SECURITY_MCODE_GAME_IIDX_12, + SECURITY_MCODE_GAME_LEN) ) { + /* checksum the whole payload with the unused part (padding) + big endian or mistake by konami dev... */ + uint16_t crc = crc16((const uint8_t*) &card->data_sector[0], + MAG_CARD_NUM_DATA_SECTORS * 10 + sizeof(card->padding), 0); + uint16_t* ptr = (uint16_t*) card->checksum; + *ptr = crc; + } else { + /* 10th style */ + uint16_t* ptr = (uint16_t*) card->checksum; + *ptr = ezusb_iidx_emu_card_mag_calc_checksum_payload( + (const uint8_t*) &card->data_sector[0], + MAG_CARD_NUM_DATA_SECTORS * 10 + sizeof(card->padding)); + } +} + +void ezusb_iidx_emu_card_mag_generate_data( + struct ezusb_iidx_emu_card_mag_data* card, uint8_t* card_id, + uint8_t card_type, bool card_used, const char* card_version) +{ + memset(card, 0, sizeof(struct ezusb_iidx_emu_card_mag_data)); + + /* these flags have to be set like this, otherwise + the card is detected unknown */ + /* 0 on bit 0 */ + card->header.flags &= ~(1 << 0); + /* 0 on bit 2 */ + card->header.flags &= ~(1 << 2); + /* 1 on bit 3 */ + card->header.flags |= (1 << 3); + /* 0 on bit 4 */ + card->header.flags &= ~(1 << 4); + /* furthermore, the last two bits of the version field are also flags */ + /* 0 on bit 6 */ + card->header.card_version[0] &= ~(1 << 6); + /* 0 on bit 7 */ + card->header.card_version[0] &= ~(1 << 7); + /* and the last 4 bits not used by the version in version field[2] + have to be set to 0001, otherwise the card is of unknown type */ + card->header.card_version[2] = 1; + + ezusb_iidx_emu_card_mag_target_form_8_digit_to_short( + (const uint8_t*) card_version, card->header.card_version); + + if (card_used) { + /* the first one is checked if the card is already formated + for a specific game version */ + card->header.flags |= (1 << 6); + /* the second flag is used if the card is not formated for + a specific game version, but a generic eamuse card + don't use this, because it will interfere on 9th style on the card + check */ + //card->header.flags |= (1 << 7); + } else { + card->header.flags &= ~(1 << 6); + card->header.flags &= ~(1 << 7); + } + + /* the game flips this back to little endian + so we have to give it big endian ordering here */ + for (uint8_t i = 0; i < MAG_CARD_NUM_DATA_SECTORS; i++) { + + if (!memcmp(card_version, SECURITY_MCODE_GAME_IIDX_9, + SECURITY_MCODE_GAME_LEN)) { + memcpy(card->data_sector_9th[i].card_id, card_id, 8); + card->data_sector_9th[i].card_type = card_type; + } else { + memcpy(card->data_sector[i].card_id, card_id, 8); + card->data_sector[i].card_type = card_type; + } + + } + + ezusb_iidx_emu_card_mag_update_checksums(card, card_version); +} diff --git a/src/main/ezusb-iidx-emu/card-mag.h b/src/main/ezusb-iidx-emu/card-mag.h new file mode 100644 index 0000000..ad9b98c --- /dev/null +++ b/src/main/ezusb-iidx-emu/card-mag.h @@ -0,0 +1,71 @@ +#ifndef EZUSB_IIDX_EMU_CARD_MAG_H +#define EZUSB_IIDX_EMU_CARD_MAG_H + +#include +#include + +#define MAG_CARD_NUM_DATA_SECTORS 5 + +/* Structure for the data format of a magnetic card */ +struct ezusb_iidx_emu_card_mag_data { + struct { + /* several flags (used, fixed flags) */ + uint8_t flags; + /* card version: + * C02 + * D01 + * E11 + * ECO + * @@@ -> eamuse common + * ??? -> konami common + */ + uint8_t card_version[3]; + /* crc8 header only */ + uint8_t checksum; + } header; + + union { + /* special treatment for 9th, only */ + struct { + /* valid card types 0 to 4 */ + uint8_t card_type; + /* 64-bit card id */ + uint8_t card_id[8]; + /* crc8 for id and type*/ + uint8_t checksum; + } data_sector_9th[MAG_CARD_NUM_DATA_SECTORS]; + + /* 10th, Red, HappySky */ + struct { + /* 64-bit card id */ + uint8_t card_id[8]; + /* valid card types 0 to 4 */ + uint8_t card_type; + /* crc8 for id and type*/ + uint8_t checksum; + } data_sector[MAG_CARD_NUM_DATA_SECTORS]; + }; + + /* part of the data, but not used */ + uint8_t padding[8]; + /* crc16 for whole payload (i.e. everything minus header) */ + /* uint8_t[2] array to avoid alignment issues */ + uint8_t checksum[2]; +}; + +/** + * Generate a data blob resembling the structure of the data stored on real + * magnetic cards + * + * @param card Pointer to reserved memory to write the resulting card data to + * @param card_id 64-bit card id to use for the magnetic card + * @param card_type Type of the magnetic card (valid: 0 - 4) + * @param card_used True to flag the card used (recommended), false unused + * @param card_version Version branding for the card (mcode of the game). + * @see struct magnetic_card + */ +void ezusb_iidx_emu_card_mag_generate_data( + struct ezusb_iidx_emu_card_mag_data* card, uint8_t* card_id, + uint8_t card_type, bool card_used, const char* card_version); + +#endif diff --git a/src/main/ezusb-iidx-emu/conf.h b/src/main/ezusb-iidx-emu/conf.h new file mode 100644 index 0000000..c74e4ed --- /dev/null +++ b/src/main/ezusb-iidx-emu/conf.h @@ -0,0 +1,7 @@ +#ifndef EZUSB_IIDX_EMU_CONF_H +#define EZUSB_IIDX_EMU_CONF_H + +// #define EZUSB_IIDX_EMU_NODE_FPGA_DUMP +// #define EZUSB_IIDX_EMU_NODE_SRAM_DUMP + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/msg.c b/src/main/ezusb-iidx-emu/msg.c new file mode 100644 index 0000000..442b8b2 --- /dev/null +++ b/src/main/ezusb-iidx-emu/msg.c @@ -0,0 +1,261 @@ +#define LOG_MODULE "ezusb-iidx-emu-msg" + +#include +#include +#include + +#include "bemanitools/iidxio.h" + +#include "hook/iohook.h" + +#include "ezusb-emu/msg.h" + +#include "ezusb-iidx/msg.h" + +#include "ezusb-iidx-emu/msg.h" +#include "ezusb-iidx-emu/nodes.h" +#include "ezusb-iidx-emu/node-coin.h" +#include "ezusb-iidx-emu/node-serial.h" + +#include "util/hex.h" +#include "util/log.h" + +/* ------------------------------------------------------------------------ */ + +static HRESULT ezusb_iidx_emu_msg_interrupt_read(struct iobuf *read); +static HRESULT ezusb_iidx_emu_msg_interrupt_write(struct const_iobuf *write); +static HRESULT ezusb_iidx_emu_msg_bulk_read(struct iobuf *read); +static HRESULT ezusb_iidx_emu_msg_bulk_write(struct const_iobuf *write); + +/* ------------------------------------------------------------------------ */ + +static struct ezusb_emu_msg_hook ezusb_iidx_emu_msg_hook = { + .interrupt_read = ezusb_iidx_emu_msg_interrupt_read, + .interrupt_write = ezusb_iidx_emu_msg_interrupt_write, + .bulk_read = ezusb_iidx_emu_msg_bulk_read, + .bulk_write = ezusb_iidx_emu_msg_bulk_write +}; + +/* ------------------------------------------------------------------------ */ + +static const struct ezusb_iidx_emu_node* ezusb_iidx_emu_msg_nodes[256] = +{ + [EZUSB_IIDX_MSG_NODE_16SEG] = &ezusb_iidx_emu_node_16seg, + [EZUSB_IIDX_MSG_NODE_COIN] = &ezusb_iidx_emu_node_coin, + [EZUSB_IIDX_MSG_NODE_EEPROM] = &ezusb_iidx_emu_node_eeprom_v1, + [EZUSB_IIDX_MSG_NODE_FPGA_V1] = &ezusb_iidx_emu_node_fpga_v1, + [EZUSB_IIDX_MSG_NODE_NONE] = &ezusb_iidx_emu_node_none, + [EZUSB_IIDX_MSG_NODE_SECURITY_MEM] = &ezusb_iidx_emu_node_security_mem_v1, + [EZUSB_IIDX_MSG_NODE_SECURITY_PLUG] = &ezusb_iidx_emu_node_security_plug_v1, + [EZUSB_IIDX_MSG_NODE_SERIAL] = &ezusb_iidx_emu_node_serial, + [EZUSB_IIDX_MSG_NODE_SRAM] = &ezusb_iidx_emu_node_sram, + [EZUSB_IIDX_MSG_NODE_WDT] = &ezusb_iidx_emu_node_wdt, +}; + +static const struct ezusb_iidx_emu_node* ezusb_iidx_emu_msg_v2_nodes[256] = +{ + [EZUSB_IIDX_MSG_NODE_16SEG] = &ezusb_iidx_emu_node_16seg, + [EZUSB_IIDX_MSG_NODE_COIN] = &ezusb_iidx_emu_node_coin, + [EZUSB_IIDX_MSG_NODE_EEPROM] = &ezusb_iidx_emu_node_eeprom_v2, + [EZUSB_IIDX_MSG_NODE_FPGA_V2] = &ezusb_iidx_emu_node_fpga_v2, + [EZUSB_IIDX_MSG_NODE_NONE] = &ezusb_iidx_emu_node_none, + [EZUSB_IIDX_MSG_NODE_SECURITY_MEM] = &ezusb_iidx_emu_node_security_mem_v2, + [EZUSB_IIDX_MSG_NODE_SECURITY_PLUG] = &ezusb_iidx_emu_node_security_plug_v2, + [EZUSB_IIDX_MSG_NODE_SRAM] = &ezusb_iidx_emu_node_sram, + [EZUSB_IIDX_MSG_NODE_WDT] = &ezusb_iidx_emu_node_wdt, +}; + +static const struct ezusb_iidx_emu_node** ezusb_iidx_emu_node_handler; +static uint8_t ezusb_iidx_emu_msg_status = 0; +static uint8_t ezusb_iidx_emu_msg_seq_no = 0; +static uint8_t ezusb_iidx_emu_msg_read_cur_node = 0; + +/* ------------------------------------------------------------------------ */ + +struct ezusb_emu_msg_hook* ezusb_iidx_emu_msg_init(void) +{ + /* Init all nodes */ + for (uint32_t i = 0; i < 256; i++) { + + /* "Constructor" optional */ + if (ezusb_iidx_emu_msg_nodes[i] && + ezusb_iidx_emu_msg_nodes[i]->init_node) { + ezusb_iidx_emu_msg_nodes[i]->init_node(); + } + } + + ezusb_iidx_emu_node_handler = ezusb_iidx_emu_msg_nodes; + + return &ezusb_iidx_emu_msg_hook; +} + +struct ezusb_emu_msg_hook* ezusb_iidx_emu_msg_v2_init(void) +{ + /* Init all nodes */ + for (uint32_t i = 0; i < 256; i++) { + + /* "Constructor" optional */ + if (ezusb_iidx_emu_msg_v2_nodes[i] && + ezusb_iidx_emu_msg_v2_nodes[i]->init_node) { + ezusb_iidx_emu_msg_v2_nodes[i]->init_node(); + } + } + + ezusb_iidx_emu_node_handler = ezusb_iidx_emu_msg_v2_nodes; + + return &ezusb_iidx_emu_msg_hook; +} + +static HRESULT ezusb_iidx_emu_msg_interrupt_read(struct iobuf *read) +{ + struct ezusb_iidx_msg_interrupt_read_packet* msg_resp = + (struct ezusb_iidx_msg_interrupt_read_packet*) read->bytes; + + if (!iidx_io_ep2_recv()) { + return E_FAIL; + } + + msg_resp->p1_turntable = iidx_io_ep2_get_turntable(0); + msg_resp->p2_turntable = iidx_io_ep2_get_turntable(1); + + msg_resp->sliders[0] = iidx_io_ep2_get_slider(0) | + (iidx_io_ep2_get_slider(1) << 4); + + msg_resp->sliders[1] = iidx_io_ep2_get_slider(2) | + (iidx_io_ep2_get_slider(3) << 4); + + msg_resp->sliders[2] = iidx_io_ep2_get_slider(4); + + msg_resp->inverted_pad = ((iidx_io_ep2_get_keys() & 0x3FFF) << 8) | + ((iidx_io_ep2_get_panel() & 0x0F) << 24) | + ((iidx_io_ep2_get_sys() & 0x03) << 28) | + (((iidx_io_ep2_get_sys() >> 2) & 0x01) << 22); + + /* make sure to update the current coin mode state, otherwise + the game will bang the IO and try to enforce the coin state it wants to + set, which results in crashing the whole code here due to flooding it */ + msg_resp->inverted_pad &= ~(1 << 31); + + if (ezusb_iidx_emu_node_coin_get_mode() == 1) { + msg_resp->inverted_pad |= (1 << 31); + } + + msg_resp->inverted_pad = ~msg_resp->inverted_pad; + + msg_resp->status = ezusb_iidx_emu_msg_status; + /* Reset status after delivered (important for eeprom reading) */ + ezusb_iidx_emu_msg_status = 0; + + msg_resp->seq_no = ezusb_iidx_emu_msg_seq_no++; + msg_resp->fpga_write_ready = 1; + + /* serial io flags + make sure flag is always cleared to pass on boot */ + msg_resp->serial_io_busy_flag = 0; + + if (ezusb_iidx_emu_node_serial_read_buffer_busy()) { + msg_resp->serial_io_busy_flag |= (1 << 0); + } + + if (ezusb_iidx_emu_node_serial_write_buffer_busy()) { + msg_resp->serial_io_busy_flag |= (1 << 1); + } + + /* this needs to be 2 with FPGA2_CMD_CHECK2, + otherwise the game's fpga check will fail */ + msg_resp->fpga2_check_flag_unkn = 2; + + read->pos = sizeof(*msg_resp); + + return S_OK; +} + +static HRESULT ezusb_iidx_emu_msg_interrupt_write(struct const_iobuf *write) +{ + const struct ezusb_iidx_msg_interrupt_write_packet* msg_req = + (const struct ezusb_iidx_msg_interrupt_write_packet*) write->bytes; + + if (write->nbytes < sizeof(*msg_req)) { + log_warning("Interrupt write message too small"); + + return E_INVALIDARG; + } + + iidx_io_ep1_set_deck_lights(msg_req->deck_lights); + iidx_io_ep1_set_panel_lights(msg_req->panel_lights); + iidx_io_ep1_set_top_lamps(msg_req->top_lamps); + iidx_io_ep1_set_top_neons(msg_req->top_neons); + + if (!iidx_io_ep1_send()) { + return E_FAIL; + } + + if (!ezusb_iidx_emu_node_handler[msg_req->node]) { + ezusb_iidx_emu_msg_read_cur_node = 0; + log_warning("Unrecognised node in interrupt message: %02x", + msg_req->node); + + return E_INVALIDARG; + } + + /* Remember node for next bulk read */ + ezusb_iidx_emu_msg_read_cur_node = msg_req->node; + ezusb_iidx_emu_msg_status = ezusb_iidx_emu_node_handler[msg_req->node]-> + process_cmd(msg_req->cmd, msg_req->cmd_detail[0], + msg_req->cmd_detail[1]); + + return S_OK; +} + +static HRESULT ezusb_iidx_emu_msg_bulk_read(struct iobuf *read) +{ + struct ezusb_iidx_msg_bulk_packet* pkt = + (struct ezusb_iidx_msg_bulk_packet*) read->bytes; + + if (read->nbytes < sizeof(*pkt)) { + log_warning("Bulk read buffer too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + if (!ezusb_iidx_emu_node_handler[ezusb_iidx_emu_msg_read_cur_node]) { + log_warning( + "Bulk read unsupported on cur_node = %d", + ezusb_iidx_emu_msg_read_cur_node); + + return E_FAIL; + } + + if (!ezusb_iidx_emu_node_handler[ezusb_iidx_emu_msg_read_cur_node]-> + read_packet(pkt)) { + return E_FAIL; + } + + read->pos = sizeof(*pkt); + + return S_OK; +} + +static HRESULT ezusb_iidx_emu_msg_bulk_write(struct const_iobuf *write) +{ + const struct ezusb_iidx_msg_bulk_packet* pkt = + (const struct ezusb_iidx_msg_bulk_packet*) write->bytes; + + if (write->nbytes < sizeof(*pkt)) { + log_warning("Bulk write packet too small"); + + return E_INVALIDARG; + } + + if (!ezusb_iidx_emu_node_handler[pkt->node]) { + log_warning("Bulk write not supported on pkt->node = %02x", pkt->node); + + return E_FAIL; + } + + if (!ezusb_iidx_emu_node_handler[pkt->node]->write_packet(pkt)) { + return E_FAIL; + } + + return S_OK; +} diff --git a/src/main/ezusb-iidx-emu/msg.h b/src/main/ezusb-iidx-emu/msg.h new file mode 100644 index 0000000..deec155 --- /dev/null +++ b/src/main/ezusb-iidx-emu/msg.h @@ -0,0 +1,33 @@ +#ifndef EZUSB_IIDX_EMU_MSG_H +#define EZUSB_IIDX_EMU_MSG_H + +#include +#include + +#include "hook/iohook.h" + +#include "ezusb-emu/msg.h" + +/** + * Init the fully emulated IIDX msg backend for a EZUSB (C02) board. This + * activates the "old" V1 node handling backend which was used on iidx 09 to 13. + * On iidx14, the firmware was updated and removed nodes that were not used + * anymore, e.g. serial card reader handling. + * + * @return ezusb_emu_msg_hook structure with hook calls for ezusb msg + * dispatching + */ +struct ezusb_emu_msg_hook* ezusb_iidx_emu_msg_init(void); + +/** + * Init the fully emulated IIDX msg backend for a EZUSB (C02) board. This + * activates the "newer" V2 node handling backend which was used from iidx 14 + * onwards. The firmware was updated and removed nodes that were not used + * anymore, e.g. serial card reader handling. + * + * @return ezusb_emu_msg_hook structure with hook calls for ezusb msg + * dispatching + */ +struct ezusb_emu_msg_hook* ezusb_iidx_emu_msg_v2_init(void); + +#endif diff --git a/src/main/ezusb-iidx-emu/node-16seg.c b/src/main/ezusb-iidx-emu/node-16seg.c new file mode 100644 index 0000000..bc6b51a --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-16seg.c @@ -0,0 +1,41 @@ +#define LOG_MODULE "ezusb-iidx-emu-node-16seg" + +#include "ezusb-iidx-emu/node-16seg.h" + +#include + +#include "bemanitools/iidxio.h" + +#include "ezusb-iidx/seg16-cmd.h" + +#include "util/log.h" + +uint8_t ezusb_iidx_emu_node_16seg_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + switch (cmd_id) + { + case EZUSB_IIDX_16SEG_CMD_WRITE: + return EZUSB_IIDX_16SEG_CMD_STATUS_OK; + + default: + log_warning("Unknown 16seg node command: %02x", cmd_id); + return EZUSB_IIDX_16SEG_CMD_STATUS_FAULT; + } +} + +bool ezusb_iidx_emu_node_16seg_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_fatal("Read packet not supported on 16seg node"); + return false; +} + +bool ezusb_iidx_emu_node_16seg_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + char _16seg[10]; + + memcpy(_16seg, pkg->payload, 9); + _16seg[9] = '\0'; + + return iidx_io_ep3_write_16seg(_16seg); +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-16seg.h b/src/main/ezusb-iidx-emu/node-16seg.h new file mode 100644 index 0000000..2586c25 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-16seg.h @@ -0,0 +1,13 @@ +#ifndef EZUSB_IIDX_EMU_NODE_16SEG_H +#define EZUSB_IIDX_EMU_NODE_16SEG_H + +#include "ezusb-iidx-emu/node.h" + +uint8_t ezusb_iidx_emu_node_16seg_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2); + +bool ezusb_iidx_emu_node_16seg_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg); + +bool ezusb_iidx_emu_node_16seg_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-coin.c b/src/main/ezusb-iidx-emu/node-coin.c new file mode 100644 index 0000000..c42d3fb --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-coin.c @@ -0,0 +1,44 @@ +#define LOG_MODULE "ezusb-iidx-emu-node-coin" + +#include "ezusb-iidx-emu/node-coin.h" + +#include "ezusb-iidx/coin-cmd.h" + +#include "util/log.h" + +static uint8_t ezusb_iidx_emu_node_coin_mode = 0; + +uint8_t ezusb_iidx_emu_node_coin_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_COIN_CMD_SET_COIN_MODE_1: + ezusb_iidx_emu_node_coin_mode = 0; + return EZUSB_IIDX_COIN_CMD_STATUS_OK; + + case EZUSB_IIDX_COIN_CMD_SET_COIN_MODE_2: + ezusb_iidx_emu_node_coin_mode = 1; + return EZUSB_IIDX_COIN_CMD_STATUS_OK; + + default: + log_warning("Unknown coin node command: %02x", cmd_id); + return EZUSB_IIDX_COIN_CMD_STATUS_FAULT; + } +} + +bool ezusb_iidx_emu_node_coin_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_fatal("Read packet not supported on coin node"); + return false; +} + +bool ezusb_iidx_emu_node_coin_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_fatal("Write packet not supported on coin node"); + return false; +} + +uint8_t ezusb_iidx_emu_node_coin_get_mode(void) +{ + return ezusb_iidx_emu_node_coin_mode; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-coin.h b/src/main/ezusb-iidx-emu/node-coin.h new file mode 100644 index 0000000..97cce87 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-coin.h @@ -0,0 +1,15 @@ +#ifndef EZUSB_IIDX_EMU_NODE_COIN_H +#define EZUSB_IIDX_EMU_NODE_COIN_H + +#include "ezusb-iidx-emu/node.h" + +uint8_t ezusb_iidx_emu_node_coin_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2); + +bool ezusb_iidx_emu_node_coin_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg); + +bool ezusb_iidx_emu_node_coin_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg); + +uint8_t ezusb_iidx_emu_node_coin_get_mode(void); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-eeprom.c b/src/main/ezusb-iidx-emu/node-eeprom.c new file mode 100644 index 0000000..551c6d0 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-eeprom.c @@ -0,0 +1,107 @@ +#define LOG_MODULE "ezusb-iidx-emu-node-eeprom" + +#include + +#include "ezusb-iidx-emu/node-eeprom.h" + +#include "ezusb-iidx/eeprom-cmd.h" + +#include "util/log.h" + +/* not verified, but we got calls with 3 pages only so far */ +#define EEPROM_NPAGES 3 + +static uint8_t ezusb_iidx_emu_node_eeprom_read_page_pos; +static uint8_t ezusb_iidx_emu_node_eeprom_mem[EZUSB_PAGESIZE * EEPROM_NPAGES]; + +void ezusb_iidx_emu_node_eeprom_init(void) +{ + ezusb_iidx_emu_node_eeprom_read_page_pos = 0; + memset(ezusb_iidx_emu_node_eeprom_mem, 0xFF, sizeof(ezusb_iidx_emu_node_eeprom_mem)); +} + +uint8_t ezusb_iidx_emu_node_eeprom_process_cmd_v1(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_EEPROM_CMD_READ: + log_misc("EZUSB_EEPROM_V1_CMD_READ"); + ezusb_iidx_emu_node_eeprom_read_page_pos = 0; + return EZUSB_IIDX_EEPROM_CMD_STATUS_V1_READ_OK; + + case EZUSB_IIDX_EEPROM_CMD_WRITE: + log_misc("EZUSB_EEPROM_V1_CMD_WRITE"); + return EZUSB_IIDX_EEPROM_CMD_STATUS_V2_WRITE_OK; + + default: + log_warning("Unrecognised eeprom v1 command: %02x", cmd_id); + return EZUSB_IIDX_EEPROM_CMD_STATUS_V1_FAULT; + } +} + +uint8_t ezusb_iidx_emu_node_eeprom_process_cmd_v2(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_EEPROM_CMD_READ: + log_misc("EZUSB_EEPROM_V2_CMD_READ"); + ezusb_iidx_emu_node_eeprom_read_page_pos = 0; + return EZUSB_IIDX_EEPROM_CMD_STATUS_V2_READ_OK; + + case EZUSB_IIDX_EEPROM_CMD_WRITE: + log_misc("EZUSB_EEPROM_V2_CMD_WRITE"); + return EZUSB_IIDX_EEPROM_CMD_STATUS_V2_WRITE_OK; + + default: + log_warning("Unrecognised eeprom v2 command: %02x", cmd_id); + return EZUSB_IIDX_EEPROM_CMD_STATUS_V2_FAULT; + } +} + +bool ezusb_iidx_emu_node_eeprom_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_misc("Reading EEPROM page 0x%02X", ezusb_iidx_emu_node_eeprom_read_page_pos); + + if (ezusb_iidx_emu_node_eeprom_read_page_pos >= EEPROM_NPAGES) { + log_warning("Reading EEPROM ezusb_iidx_emu_node_eeprom_mem overrun"); + return false; + } + + /* has to be 0x22 to get accepted */ + pkg->node = 0x22; + pkg->page = ezusb_iidx_emu_node_eeprom_read_page_pos; + memset(pkg->payload, 0, EZUSB_PAGESIZE); + memcpy(pkg->payload, ezusb_iidx_emu_node_eeprom_mem + + ezusb_iidx_emu_node_eeprom_read_page_pos * EZUSB_PAGESIZE, EZUSB_PAGESIZE); + + ezusb_iidx_emu_node_eeprom_read_page_pos++; + return true; +} + +bool ezusb_iidx_emu_node_eeprom_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_misc("Writing EEPROM page 0x%02X", pkg->page); + + if (pkg->page >= EEPROM_NPAGES) { + log_warning("Writing EEPROM ezusb_iidx_emu_node_eeprom_mem overrun"); + return false; + } + + memcpy(ezusb_iidx_emu_node_eeprom_mem + pkg->page * + EZUSB_PAGESIZE, pkg->payload, EZUSB_PAGESIZE); + return true; +} + +/* ------------------------------------------------------------------------- */ + +size_t ezusb_iidx_emu_node_eeprom_write_memory(const uint8_t* buffer, size_t offset, + size_t length) +{ + if (offset + length > sizeof(ezusb_iidx_emu_node_eeprom_mem)) { + log_warning("Writing eeprom ezusb_iidx_emu_node_eeprom_mem overrun, truncated"); + length = sizeof(ezusb_iidx_emu_node_eeprom_mem) - offset; + } + + memcpy(ezusb_iidx_emu_node_eeprom_mem + offset, buffer, length); + return length; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-eeprom.h b/src/main/ezusb-iidx-emu/node-eeprom.h new file mode 100644 index 0000000..cd16fd8 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-eeprom.h @@ -0,0 +1,23 @@ +#ifndef EZUSB_IIDX_EMU_NODE_EEPROM_H +#define EZUSB_IIDX_EMU_NODE_EEPROM_H + +#include "ezusb-iidx-emu/node.h" + +void ezusb_iidx_emu_node_eeprom_init(void); +/* Used on 9th to DistorteD */ +uint8_t ezusb_iidx_emu_node_eeprom_process_cmd_v1(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2); + +/* Used on Gold to Sirius */ +uint8_t ezusb_iidx_emu_node_eeprom_process_cmd_v2(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2); + +bool ezusb_iidx_emu_node_eeprom_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg); + +bool ezusb_iidx_emu_node_eeprom_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg); + +/* Used by the security plug to write security related data */ +size_t ezusb_iidx_emu_node_eeprom_write_memory(const uint8_t* buffer, size_t offset, + size_t length); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-fpga.c b/src/main/ezusb-iidx-emu/node-fpga.c new file mode 100644 index 0000000..13f00f6 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-fpga.c @@ -0,0 +1,117 @@ +#define LOG_MODULE "ezusb-iidx-emu-node-fpga" + +#include + +#include "ezusb-iidx/fpga-cmd.h" +#include "ezusb-iidx-emu/conf.h" +#include "ezusb-iidx-emu/node-fpga.h" + +#include "util/fs.h" +#include "util/log.h" + + +static uint16_t ezusb_iidx_emu_node_fpga_write_ptr; +static uint16_t ezusb_iidx_emu_node_fpga_prog_size; +static uint8_t ezusb_iidx_emu_node_fpga_mem[0xFFFF]; + + +uint8_t ezusb_iidx_emu_node_fpga_v1_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_FPGA_CMD_V1_INIT: + log_misc("EZUSB_IIDX_FPGA_CMD_V1_INIT"); + return EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK_2; + + case EZUSB_IIDX_FPGA_CMD_V1_CHECK: + log_misc("EZUSB_IIDX_FPGA_CMD_V1_CHECK"); + return EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK; + + case EZUSB_IIDX_FPGA_CMD_V1_CHECK_2: + log_misc("EZUSB_IIDX_FPGA_CMD_V1_CHECK_2"); + return EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK_2; + + case EZUSB_IIDX_FPGA_CMD_V1_WRITE: + ezusb_iidx_emu_node_fpga_prog_size = (cmd_data << 8) | cmd_data2; + log_misc("EZUSB_IIDX_FPGA_CMD_V1_WRITE (prog size %04X bytes)", + ezusb_iidx_emu_node_fpga_prog_size); + ezusb_iidx_emu_node_fpga_write_ptr = 0; + memset(ezusb_iidx_emu_node_fpga_mem, 0xFF, + sizeof(ezusb_iidx_emu_node_fpga_mem)); + return EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK; + + case EZUSB_IIDX_FPGA_CMD_V1_WRITE_DONE: + log_misc("EZUSB_IIDX_FPGA_CMD_V1_WRITE_DONE"); +#ifdef EZUSB_IIDX_EMU_NODE_FPGA_DUMP + file_save("fpga.bin", ezusb_iidx_emu_node_fpga_mem, + ezusb_iidx_emu_node_fpga_prog_size); + + log_info("Dumped fpga firmware to fpga.bin"); +#endif + return EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK_2; + + default: + log_warning("Unrecognised fpga v1 command: %02x", cmd_id); + return EZUSB_IIDX_FPGA_CMD_STATUS_V1_FAULT; + } +} + +uint8_t ezusb_iidx_emu_node_fpga_v2_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_FPGA_CMD_V2_INIT: + log_misc("EZUSB_IIDX_FPGA_CMD_V2_INIT"); + return EZUSB_IIDX_FPGA_CMD_STATUS_V2_INIT_OK; + + case EZUSB_IIDX_FPGA_CMD_V2_CHECK: + log_misc("EZUSB_IIDX_FPGA_CMD_V2_CHECK"); + return EZUSB_IIDX_FPGA_CMD_STATUS_V2_CHECK_OK; + + case EZUSB_IIDX_FPGA_CMD_V2_WRITE: + ezusb_iidx_emu_node_fpga_prog_size = (cmd_data << 8) | cmd_data2; + log_misc("EZUSB_IIDX_FPGA_CMD_V2_WRITE (%04X bytes)", + ezusb_iidx_emu_node_fpga_prog_size); + ezusb_iidx_emu_node_fpga_write_ptr = 0; + memset(ezusb_iidx_emu_node_fpga_mem, 0xFF, + sizeof(ezusb_iidx_emu_node_fpga_mem)); + return EZUSB_IIDX_FPGA_CMD_STATUS_V2_WRITE_OK; + + case EZUSB_IIDX_FPGA_CMD_V2_WRITE_DONE: + log_misc("EZUSB_IIDX_FPGA_CMD_V2_WRITE_DONE"); +#ifdef EZUSB_IIDX_EMU_NODE_FPGA_DUMP + file_save("fpga.bin", ezusb_iidx_emu_node_fpga_mem, + ezusb_iidx_emu_node_fpga_prog_size); + + log_info("Dumped fpga firmware to fpga.bin"); +#endif + return EZUSB_IIDX_FPGA_CMD_STATUS_V2_WRITE_OK; + + default: + log_warning("Unrecognised fpga v2 command: %02x", cmd_id); + return EZUSB_IIDX_FPGA_CMD_STATUS_V2_FAULT; + } +} + +bool ezusb_iidx_emu_node_fpga_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_misc("FPGA read packet"); + + /* stub */ + pkg->node = 0x00; + pkg->page = 0x00; + memset(pkg->payload, 0x00, sizeof(pkg->payload)); + + return true; +} + +bool ezusb_iidx_emu_node_fpga_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_misc("FPGA write packet: %02X %02X", pkg->node, pkg->page); + + memcpy(ezusb_iidx_emu_node_fpga_mem + ezusb_iidx_emu_node_fpga_write_ptr, pkg->payload, + EZUSB_PAGESIZE); + ezusb_iidx_emu_node_fpga_write_ptr += EZUSB_PAGESIZE; + + return true; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-fpga.h b/src/main/ezusb-iidx-emu/node-fpga.h new file mode 100644 index 0000000..fe56805 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-fpga.h @@ -0,0 +1,18 @@ +#ifndef EZUSB_IIDX_EMU_NODE_FPGA_H +#define EZUSB_IIDX_EMU_NODE_FPGA_H + +#include "ezusb-iidx-emu/node.h" + +/* Used on 9th to DistorteD */ +uint8_t ezusb_iidx_emu_node_fpga_v1_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2); + +/* Used on Gold onwards */ +uint8_t ezusb_iidx_emu_node_fpga_v2_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2); + +bool ezusb_iidx_emu_node_fpga_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg); + +bool ezusb_iidx_emu_node_fpga_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-none.c b/src/main/ezusb-iidx-emu/node-none.c new file mode 100644 index 0000000..7f0bfcc --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-none.c @@ -0,0 +1,17 @@ +#include "ezusb-iidx-emu/node-none.h" + +uint8_t ezusb_iidx_emu_node_none_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + return 0; +} + +bool ezusb_iidx_emu_node_none_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg) +{ + return true; +} + +bool ezusb_iidx_emu_node_none_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + return true; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-none.h b/src/main/ezusb-iidx-emu/node-none.h new file mode 100644 index 0000000..f20ec97 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-none.h @@ -0,0 +1,13 @@ +#ifndef EZUSB_IIDX_EMU_NODE_NONE_H +#define EZUSB_IIDX_EMU_NODE_NONE_H + +#include "ezusb-iidx-emu/node.h" + +uint8_t ezusb_iidx_emu_node_none_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2); + +bool ezusb_iidx_emu_node_none_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg); + +bool ezusb_iidx_emu_node_none_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-security-mem.c b/src/main/ezusb-iidx-emu/node-security-mem.c new file mode 100644 index 0000000..61cf87e --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-security-mem.c @@ -0,0 +1,120 @@ +#define LOG_MODULE "ezusb-iidx-emu-node-sec-mem" + +#include + +#include "ezusb-iidx/secmem-cmd.h" +#include "ezusb-iidx-emu/node-security-mem.h" + +#include "util/log.h" + +#define SECURITY2_NPAGES 5 + +/* Starting GOLD with the new IO2 (C02 is not affected), the game does not + transfer 5 pages of security data to the IO2 board. Instead, the board seems + to be preloaded with security data by the firmware (?) and just a single + page of data is sent to the board to store. The following dump was taken + from the GOLD exec 2007022101 +*/ +static uint8_t ezusb_iidx_emu_node_security_mem_mem + [EZUSB_PAGESIZE * SECURITY2_NPAGES] = { + 0x00, 0x90, 0x31, 0xCF, 0x95, 0x7A, 0x59, 0xE5, + 0xD2, 0xBF, 0x2C, 0xDB, 0xB5, 0x83, 0x4D, 0x03, + 0x17, 0x5D, 0x25, 0x2A, 0xFD, 0x72, 0x1E, 0x01, + 0x02, 0x60, 0x88, 0x92, 0x9A, 0x9B, 0x2A, 0xA9, + 0x73, 0x5A, 0x0E, 0x9B, 0xC8, 0xCD, 0x85, 0x4D, + 0xE0, 0xBA, 0xF4, 0xEC, 0x8A, 0x24, 0x76, 0x3C, + 0xDC, 0x35, 0xC7, 0xD7, 0xFF, 0xFF, 0x9C, 0x64, + 0x44, 0x4C, 0xD7, 0x06, 0x60, 0x17, 0xAD, 0x0E, + 0x02, 0xEB, 0x46, 0x45, 0x96, 0xB0, 0xD6, 0xB9, + 0x7C, 0x34, 0xBE, 0x77, 0x75, 0xF2, 0xBE, 0x1B, + 0x99, 0x62, 0xBC, 0x9B, 0x92, 0x5C, 0x26, 0x39, + 0x6C, 0xCD, 0x84, 0xFD, 0xC0, 0x58, 0x2B, 0xA8, + 0x7D, 0x10, 0xB3, 0x81, 0x25, 0xF3, 0x24, 0xE7, + 0xB1, 0x4D, 0x6D, 0x12, 0xF7, 0xAE, 0x27, 0xE0, + 0xD2, 0x95, 0x30, 0x2D, 0xD1, 0x79, 0x27, 0x81, + 0xBB, 0x67, 0x47, 0x91, 0xAE, 0xC1, 0xB8, 0x79, + 0x1F, 0x5E, 0xD5, 0x08, 0x84, 0xA9, 0x6D, 0x1A, + 0xF3, 0xEB, 0x8C, 0x58, 0x78, 0x5F, 0xD8, 0x51, + 0x74, 0x45, 0xFB, 0x4C, 0xBD, 0x91, 0x32, 0xC2, + 0xD6, 0x65, 0x80, 0xE3, 0x07, 0xFE, 0x92, 0x0C, + 0x88, 0x31, 0xD7, 0xA0, 0xA8, 0x32, 0xD7, 0x1F, + 0x1C, 0xBE, 0x50, 0xF0, 0x49, 0x56, 0x23, 0xBB, + 0xD5, 0xB5, 0x99, 0xBF, 0x40, 0x24, 0x00, 0x0F, + 0xCE, 0xDA, 0x35, 0x1D, 0x8D, 0x03, 0x1D, 0x74, + 0xC0, 0xAF, 0x8B, 0x12, 0x6F, 0x33, 0xB2, 0x4A, + 0x6F, 0x3B, 0x93, 0x88, 0xA0, 0x29, 0x81, 0xF6, + 0xB2, 0xEC, 0x30, 0x56, 0x2D, 0xFE, 0x75, 0xFF, + 0x18, 0xA0, 0x18, 0x70, 0xEE, 0x0C, 0xE5, 0x4A, + 0x3A, 0xC4, 0x69, 0x33, 0xA0, 0x9A, 0x73, 0x77, + 0x99, 0xA2, 0xDA, 0xD4, 0x9F, 0xB8, 0x90, 0x60, + 0x2F, 0xBC, 0x8E, 0xE7, 0x3E, 0x30, 0x9A, 0xB2, + 0x95, 0x59, 0x7E, 0x14, 0xBD, 0x9C, 0x9E, 0xB0}; + +void ezusb_iidx_emu_node_security_mem_init(void) +{ + /* Starting GOLD with the IO2 board, we have to preload the memory. + Doesn't matter for the C02 board overwriting the memory anyway */ + /* + memset(ezusb_iidx_emu_node_security_mem_mem, 0xFF, + sizeof(ezusb_iidx_emu_node_security_mem_mem)); + */ +} + +uint8_t ezusb_iidx_emu_node_security_mem_v1_process_cmd(uint8_t cmd_id, + uint8_t cmd_data, uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_SECMEM_CMD_WRITE: + log_misc("EZUSB_SECURITY_MEM_V1_CMD_WRITE"); + return EZUSB_IIDX_SECMEM_CMD_STATUS_V1_WRITE_OK; + + default: + log_warning("Unrecognised security memory v1 command: %02x", cmd_id); + return EZUSB_IIDX_SECMEM_CMD_STATUS_V1_FAULT; + } +} + +uint8_t ezusb_iidx_emu_node_security_mem_v2_process_cmd(uint8_t cmd_id, + uint8_t cmd_data, uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_SECMEM_CMD_WRITE: + log_misc("EZUSB_SECURITY_MEM_V2_CMD_WRITE"); + return EZUSB_IIDX_SECMEM_CMD_STATUS_V2_WRITE_OK; + + default: + log_warning("Unrecognised security memory v2 command: %02x", cmd_id); + return EZUSB_IIDX_SECMEM_CMD_STATUS_V2_FAULT; + } +} + +bool ezusb_iidx_emu_node_security_mem_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_misc("Reading security memory"); + return true; +} + +bool ezusb_iidx_emu_node_security_mem_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_misc("Writing security memory 0x%02X", pkg->page); + + if (pkg->page >= SECURITY2_NPAGES) { + log_warning("Writing security memory overrun"); + return false; + } + + memcpy(ezusb_iidx_emu_node_security_mem_mem + pkg->page * EZUSB_PAGESIZE, pkg->payload, EZUSB_PAGESIZE); + return true; +} + +/* ------------------------------------------------------------------------- */ + +uint8_t ezusb_iidx_emu_node_security_mem_read_memory(uint32_t pos) +{ + if (pos < sizeof(ezusb_iidx_emu_node_security_mem_mem)) { + return ezusb_iidx_emu_node_security_mem_mem[pos]; + } + + log_warning("Reading security memory overrun"); + return 0; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-security-mem.h b/src/main/ezusb-iidx-emu/node-security-mem.h new file mode 100644 index 0000000..226424f --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-security-mem.h @@ -0,0 +1,24 @@ +#ifndef EZUSB_IIDX_EMU_NODE_SECURITY_MEM_H +#define EZUSB_IIDX_EMU_NODE_SECURITY_MEM_H + +#include "ezusb-iidx-emu/node.h" + +void ezusb_iidx_emu_node_security_mem_init(void); + +/* Used on 9th to DistorteD */ +uint8_t ezusb_iidx_emu_node_security_mem_v1_process_cmd(uint8_t cmd_id, + uint8_t cmd_data, uint8_t cmd_data2); + +/* Used on Gold to Sirius */ +uint8_t ezusb_iidx_emu_node_security_mem_v2_process_cmd(uint8_t cmd_id, + uint8_t cmd_data, uint8_t cmd_data2); + +bool ezusb_iidx_emu_node_security_mem_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg); + +bool ezusb_iidx_emu_node_security_mem_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg); + +/* Used by the security plug to read from security memory to encrypt data + sent to the host */ +uint8_t ezusb_iidx_emu_node_security_mem_read_memory(uint32_t pos); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-security-plug.c b/src/main/ezusb-iidx-emu/node-security-plug.c new file mode 100644 index 0000000..56faf0a --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-security-plug.c @@ -0,0 +1,439 @@ +#define LOG_MODULE "ezusb-iidx-emu-node-sec-plug" + +#include + +#include "ezusb-iidx/secplug-cmd.h" +#include "ezusb-iidx-emu/node-eeprom.h" +#include "ezusb-iidx-emu/node-security-mem.h" +#include "ezusb-iidx-emu/node-security-plug.h" + +#include "security/rp.h" +#include "security/rp2.h" +#include "security/util.h" + +#include "util/log.h" + +static struct security_mcode ezusb_iidx_emu_node_security_plug_boot_version; +static uint32_t ezusb_iidx_emu_node_security_plug_boot_seeds[3]; + +static struct security_mcode ezusb_iidx_emu_node_security_plug_black_mcode; +static struct security_mcode ezusb_iidx_emu_node_security_plug_white_mcode; + +static struct security_id ezusb_iidx_emu_node_security_plug_pcbid; +static struct security_id ezusb_iidx_emu_node_security_plug_eamid; + +static enum ezusb_iidx_secplug_dongle_slot + ezusb_iidx_emu_node_security_plug_active_dongle_slot; +static enum ezusb_iidx_secplug_dongle_memory + ezusb_iidx_emu_node_security_plug_active_dongle_mem; +static uint8_t ezusb_iidx_emu_node_security_plug_enc_rom_data_seed; + +static void ezusb_iidx_emu_node_security_plug_encrypt_rom_data(uint8_t* buffer, + uint8_t length); + +/* ------------------------------------------------------------------------- */ +void ezusb_iidx_emu_node_security_plug_set_boot_version( + const struct security_mcode* boot_version) +{ + char* tmp; + + log_assert(boot_version); + + tmp = security_mcode_to_str(boot_version); + + log_misc("boot version: %s", tmp); + free(tmp); + + memcpy(&ezusb_iidx_emu_node_security_plug_boot_version, boot_version, + sizeof(struct security_mcode)); +} + +void ezusb_iidx_emu_node_security_plug_set_boot_seeds(const uint32_t* seeds) +{ + log_assert(seeds); + + log_misc("boot seeds: %d %d %d", seeds[0], seeds[1], seeds[2]); + + memcpy(&ezusb_iidx_emu_node_security_plug_boot_seeds, seeds, + sizeof(ezusb_iidx_emu_node_security_plug_boot_seeds)); +} + +void ezusb_iidx_emu_node_security_plug_set_plug_black_mcode( + const struct security_mcode* mcode) +{ + char* tmp; + + log_assert(mcode); + + tmp = security_mcode_to_str(mcode); + + log_misc("black mcode: %s", tmp); + free(tmp); + + memcpy(&ezusb_iidx_emu_node_security_plug_black_mcode, mcode, + sizeof(struct security_mcode)); +} + +void ezusb_iidx_emu_node_security_plug_set_plug_white_mcode( + const struct security_mcode* mcode) +{ + char* tmp; + + log_assert(mcode); + + tmp = security_mcode_to_str(mcode); + + log_misc("white mcode: %s", tmp); + free(tmp); + + memcpy(&ezusb_iidx_emu_node_security_plug_white_mcode, mcode, + sizeof(struct security_mcode)); +} + +void ezusb_iidx_emu_node_security_plug_set_pcbid( + const struct security_id* pcbid) +{ + char* tmp; + + tmp = security_id_to_str(pcbid, false); + + log_misc("PCBID: %s", tmp); + free(tmp); + + memcpy(&ezusb_iidx_emu_node_security_plug_pcbid, pcbid, + sizeof(struct security_id)); +} + +void ezusb_iidx_emu_node_security_plug_set_eamid( + const struct security_id* eamid) +{ + char* tmp; + + tmp = security_id_to_str(eamid, false); + + log_misc("EAMID: %s", tmp); + free(tmp); + + memcpy(&ezusb_iidx_emu_node_security_plug_eamid, eamid, + sizeof(struct security_id)); +} + +/* ------------------------------------------------------------- */ + +uint8_t ezusb_iidx_emu_node_security_plug_process_cmd_v1(uint8_t cmd_id, + uint8_t cmd_data, uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_SECPLUG_CMD_V1_READ_ROM: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V1_READ_ROM"); + ezusb_iidx_emu_node_security_plug_active_dongle_mem = + EZUSB_IIDX_SECPLUG_DONGLE_MEM_ROM; + ezusb_iidx_emu_node_security_plug_enc_rom_data_seed = cmd_data; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V1_OK; + + case EZUSB_IIDX_SECPLUG_CMD_V1_READ_DATA: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V1_READ_DATA"); + ezusb_iidx_emu_node_security_plug_active_dongle_mem = + EZUSB_IIDX_SECPLUG_DONGLE_MEM_DATA; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V1_OK; + + case EZUSB_IIDX_SECPLUG_CMD_V1_SECURITY_CONVERSION: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V1_SECURITY_CONVERSION"); + /* TODO ? */ + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V1_OK; + + case EZUSB_IIDX_SECPLUG_CMD_V1_SELECT_BLACK_DONGLE: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V1_SELECT_BLACK_DONGLE"); + ezusb_iidx_emu_node_security_plug_active_dongle_slot = + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_BLACK; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V1_OK; + + case EZUSB_IIDX_SECPLUG_CMD_V1_SELECT_WHITE_DONGLE: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V1_SELECT_WHITE_DONGLE"); + ezusb_iidx_emu_node_security_plug_active_dongle_slot = + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_WHITE; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V1_OK; + + default: + log_warning("Unrecognised security plug v1 command: %02x", cmd_id); + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V1_FAULT; + } +} + +uint8_t ezusb_iidx_emu_node_security_plug_process_cmd_v2(uint8_t cmd_id, + uint8_t cmd_data, uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_SECPLUG_CMD_V2_INIT: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V2_INIT"); + ezusb_iidx_emu_node_security_plug_active_dongle_mem = + EZUSB_IIDX_SECPLUG_DONGLE_MEM_ROM; + ezusb_iidx_emu_node_security_plug_enc_rom_data_seed = cmd_data; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_OK; + + case EZUSB_IIDX_SECPLUG_CMD_V2_READ_DATA: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V2_READ_DATA"); + ezusb_iidx_emu_node_security_plug_active_dongle_mem = + EZUSB_IIDX_SECPLUG_DONGLE_MEM_DATA; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_DATA_OK; + + case EZUSB_IIDX_SECPLUG_CMD_V2_READ_ROM: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V2_READ_ROM"); + ezusb_iidx_emu_node_security_plug_active_dongle_mem = + EZUSB_IIDX_SECPLUG_DONGLE_MEM_ROM; + ezusb_iidx_emu_node_security_plug_enc_rom_data_seed = cmd_data; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_ROM_OK; + + case EZUSB_IIDX_SECPLUG_CMD_V2_SELECT_WHITE_DONGLE_2: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V2_SELECT_WHITE_DONGLE_2"); + ezusb_iidx_emu_node_security_plug_active_dongle_slot = + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_WHITE; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_SECURITY_SEL_OK; + + case EZUSB_IIDX_SECPLUG_CMD_V2_SELECT_BLACK_DONGLE: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V2_SELECT_BLACK_DONGLE"); + ezusb_iidx_emu_node_security_plug_active_dongle_slot = + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_BLACK; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_SECURITY_SEL_OK; + + case EZUSB_IIDX_SECPLUG_CMD_V2_SELECT_WHITE_DONGLE: + log_misc("EZUSB_IIDX_SECPLUG_CMD_V2_SELECT_WHITE_DONGLE"); + ezusb_iidx_emu_node_security_plug_active_dongle_slot = + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_WHITE; + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_SECURITY_SEL_OK; + + default: + log_warning("Unrecognised security plug v2 command: %02x", cmd_id); + return EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_FAIL; + } +} + +bool ezusb_iidx_emu_node_security_plug_read_packet_v1( + struct ezusb_iidx_msg_bulk_packet* pkg) +{ + char* tmp; + + memset(pkg, 0x00, sizeof(struct ezusb_iidx_msg_bulk_packet)); + + if ( ezusb_iidx_emu_node_security_plug_active_dongle_slot == + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_BLACK && + ezusb_iidx_emu_node_security_plug_active_dongle_mem == + EZUSB_IIDX_SECPLUG_DONGLE_MEM_ROM) + { + tmp = security_id_to_str(&ezusb_iidx_emu_node_security_plug_pcbid, + false); + + log_misc("Reading security plug v1 black rom, PCBID %s", tmp); + + free(tmp); + + if (!security_id_verify(&ezusb_iidx_emu_node_security_plug_pcbid)) { + log_fatal("PCBID verification failed"); + return false; + } + + pkg->node = 0x11; + pkg->page = 0x00; + + memcpy(pkg->payload, &ezusb_iidx_emu_node_security_plug_pcbid, + sizeof(struct security_id)); + + ezusb_iidx_emu_node_security_plug_encrypt_rom_data(pkg->payload, 10); + } + else if ( ezusb_iidx_emu_node_security_plug_active_dongle_slot == + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_BLACK && + ezusb_iidx_emu_node_security_plug_active_dongle_mem == + EZUSB_IIDX_SECPLUG_DONGLE_MEM_DATA) + { + log_misc("Reading security plug v1 black data"); + + pkg->node = 0x12; + pkg->page = 0x00; + + security_rp_generate_signed_eeprom_data( + &ezusb_iidx_emu_node_security_plug_boot_version, + ezusb_iidx_emu_node_security_plug_boot_seeds, + &ezusb_iidx_emu_node_security_plug_black_mcode, + &ezusb_iidx_emu_node_security_plug_pcbid, + (struct security_rp_eeprom*) pkg->payload); + + /* the signed test vector will be compared to the eeprom contents + write test vector to eeprom to pass checks */ + ezusb_iidx_emu_node_eeprom_write_memory(pkg->payload, 6, 6); + } + else if ( ezusb_iidx_emu_node_security_plug_active_dongle_slot == + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_WHITE && + ezusb_iidx_emu_node_security_plug_active_dongle_mem == + EZUSB_IIDX_SECPLUG_DONGLE_MEM_ROM) + { + tmp = security_id_to_str(&ezusb_iidx_emu_node_security_plug_eamid, + false); + + log_misc("Reading security plug v1 white rom, EAMID %s", tmp); + + free(tmp); + + if (!security_id_verify(&ezusb_iidx_emu_node_security_plug_eamid)) { + log_fatal("EAMID verification failed"); + return false; + } + + pkg->node = 0x11; + pkg->page = 0x00; + + memcpy(pkg->payload, &ezusb_iidx_emu_node_security_plug_eamid, + sizeof(struct security_id)); + + ezusb_iidx_emu_node_security_plug_encrypt_rom_data(pkg->payload, 10); + } + else if ( ezusb_iidx_emu_node_security_plug_active_dongle_slot == + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_WHITE && + ezusb_iidx_emu_node_security_plug_active_dongle_mem == + EZUSB_IIDX_SECPLUG_DONGLE_MEM_DATA) + { + log_misc("Reading security plug v1 white data"); + + pkg->node = 0x12; + pkg->page = 0x00; + + security_rp_generate_signed_eeprom_data( + &ezusb_iidx_emu_node_security_plug_boot_version, + ezusb_iidx_emu_node_security_plug_boot_seeds, + &ezusb_iidx_emu_node_security_plug_white_mcode, + &ezusb_iidx_emu_node_security_plug_eamid, + (struct security_rp_eeprom*) pkg->payload); + + /* XXX position 0 not verified, guess + the test vector will be compared against eeprom contents + write test vector to eeprom to pass checks */ + ezusb_iidx_emu_node_eeprom_write_memory(pkg->payload, 0, 6); + } + else + { + log_warning("Invalid security plug v1 read"); + return false; + } + + return true; +} + +bool ezusb_iidx_emu_node_security_plug_read_packet_v2( + struct ezusb_iidx_msg_bulk_packet* pkg) +{ + char* tmp; + + log_misc("Reading security plug v2 packet"); + + memset(pkg, 0x00, sizeof(struct ezusb_iidx_msg_bulk_packet)); + + if ( ezusb_iidx_emu_node_security_plug_active_dongle_slot == + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_BLACK && + ezusb_iidx_emu_node_security_plug_active_dongle_mem == + EZUSB_IIDX_SECPLUG_DONGLE_MEM_ROM) + { + tmp = security_id_to_str(&ezusb_iidx_emu_node_security_plug_pcbid, + false); + + log_misc("Reading security plug v2 black rom, PCBID %s", tmp); + + free(tmp); + + if (!security_id_verify(&ezusb_iidx_emu_node_security_plug_pcbid)) { + log_fatal("PCBID verification failed"); + return false; + } + + pkg->node = 0x11; + pkg->page = 0x00; + + memcpy(pkg->payload, &ezusb_iidx_emu_node_security_plug_pcbid, + sizeof(struct security_id)); + + ezusb_iidx_emu_node_security_plug_encrypt_rom_data(pkg->payload, 10); + } + else if ( ezusb_iidx_emu_node_security_plug_active_dongle_slot == + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_BLACK && + ezusb_iidx_emu_node_security_plug_active_dongle_mem == + EZUSB_IIDX_SECPLUG_DONGLE_MEM_DATA) + { + log_misc("Reading security plug v2 black data"); + + pkg->node = 0x12; + pkg->page = 0x00; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_BLACK, + &ezusb_iidx_emu_node_security_plug_boot_version, + &ezusb_iidx_emu_node_security_plug_black_mcode, + &ezusb_iidx_emu_node_security_plug_pcbid, + (struct security_rp2_eeprom*) pkg->payload); + } + else if ( ezusb_iidx_emu_node_security_plug_active_dongle_slot == + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_WHITE && + ezusb_iidx_emu_node_security_plug_active_dongle_mem == + EZUSB_IIDX_SECPLUG_DONGLE_MEM_ROM) + { + tmp = security_id_to_str(&ezusb_iidx_emu_node_security_plug_eamid, + false); + + log_misc("Reading security plug v2 white rom, EAMID %s", tmp); + + free(tmp); + + if (!security_id_verify(&ezusb_iidx_emu_node_security_plug_eamid)) { + log_fatal("EAMID verification failed"); + return false; + } + + pkg->node = 0x11; + pkg->page = 0x00; + + memcpy(pkg->payload, &ezusb_iidx_emu_node_security_plug_eamid, + sizeof(struct security_id)); + + ezusb_iidx_emu_node_security_plug_encrypt_rom_data(pkg->payload, 10); + } + else if ( ezusb_iidx_emu_node_security_plug_active_dongle_slot == + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_WHITE && + ezusb_iidx_emu_node_security_plug_active_dongle_mem == + EZUSB_IIDX_SECPLUG_DONGLE_MEM_DATA) + { + log_misc("Reading security plug v2 white data"); + + pkg->node = 0x12; + pkg->page = 0x00; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_WHITE, + &ezusb_iidx_emu_node_security_plug_boot_version, + &ezusb_iidx_emu_node_security_plug_white_mcode, + &ezusb_iidx_emu_node_security_plug_eamid, + (struct security_rp2_eeprom*) pkg->payload); + } + + return true; +} + +bool ezusb_iidx_emu_node_security_plug_write_packet( + const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + log_misc("Writing security plug packet"); + return true; +} + +/* ------------------------------------------------------------- */ + +static void ezusb_iidx_emu_node_security_plug_encrypt_rom_data(uint8_t* buffer, + uint8_t length) +{ + uint8_t data; + + for (int i = 0; i < length; i++) { + data = ezusb_iidx_emu_node_security_mem_read_memory( + (ezusb_iidx_emu_node_security_plug_enc_rom_data_seed + i) & 0xFF); + /* + log_misc("Encrypting %02X, seed %02X with sec mem lookup %02X", + buffer[i], ezusb_iidx_emu_node_security_plug_enc_rom_data_seed, + data); + */ + buffer[i] ^= data; + } +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-security-plug.h b/src/main/ezusb-iidx-emu/node-security-plug.h new file mode 100644 index 0000000..65ea8fc --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-security-plug.h @@ -0,0 +1,124 @@ +#ifndef EZUSB_IIDX_EMU_NODE_SECURITY_PLUG_H +#define EZUSB_IIDX_EMU_NODE_SECURITY_PLUG_H + +#include "ezusb-iidx-emu/node.h" + +#include "security/id.h" +#include "security/mcode.h" + +/** + * Set the boot version of the game. The boot version mcode is used for + * bootstrapping the security backend (of the ezusb.dll). + * + * @param boot_version Pointer to the boot version to set. + */ +void ezusb_iidx_emu_node_security_plug_set_boot_version( + const struct security_mcode* boot_version); + +/** + * Set the boot seeds. The boot seeds (three numbers >= 0) are used for + * bootstrapping the security backend (of the ezusb.dll). + * + * @param seeds Pointer to an array of three boot seeds to set. + */ +void ezusb_iidx_emu_node_security_plug_set_boot_seeds(const uint32_t* seeds); + +/** + * The mcode of the target game to boot. This mcode is typically printed onto + * the black plug corresponding to the game to boot. + * + * @param mcode Pointer to the mcode to set. + */ +void ezusb_iidx_emu_node_security_plug_set_plug_black_mcode( + const struct security_mcode* mcode); + +/** + * The mcode of the target game to boot. This is not an actual mcode but to our + * knowledge is always the string "@@@@@@@@" which is used to identify the + * white plug. + * + * @param mcode Pointer to the mcode to set. + */ +void ezusb_iidx_emu_node_security_plug_set_plug_white_mcode( + const struct security_mcode* mcode); + +/** + * Set the PCBID. This ID is stored in the ROM of the black plug and is + * basically the serial number for the copy of the game. + * + * @param pcbid Pointer to the PCBID to set. + */ +void ezusb_iidx_emu_node_security_plug_set_pcbid( + const struct security_id* pcbid); + +/** + * Set the EAMID. This ID is stored in the ROM of the white plug and is + * basically the identifier for eamuse participation. This ID (and the white + * dongle) were not available on IIDX 09 to 13 and got introduced with IIDX 14 + * (and a bunch of other games). I assume the idea was to have the same EAMID + * accross multiple games (e.g. same arcade) but different PCBIDs still + * identifying the copies of the games running. + * + * @param eamid Pointer to the EAMID to set. + */ +void ezusb_iidx_emu_node_security_plug_set_eamid( + const struct security_id* eamid); + +/** + * Trigger a command on the security node. This call is used on IIDX 9 to 13. + * + * @param cmd_id Id of the command (see enums). + * @param cmd_data Additional data for command (used/unused depending on cmd). + * @param cmd_data2 Additional data for command (used/unused depending on cmd). + * @return Status value of the executed operation. + */ +uint8_t ezusb_iidx_emu_node_security_plug_process_cmd_v1(uint8_t cmd_id, + uint8_t cmd_data, uint8_t cmd_data2); + +/** + * Trigger a command on the security node. This call is used on IIDX 14 to 17. + * + * @param cmd_id Id of the command (see enums). + * @param cmd_data Additional data for command (used/unused depending on cmd). + * @param cmd_data2 Additional data for command (used/unused depending on cmd). + * @return Status value of the executed operation. + */ +uint8_t ezusb_iidx_emu_node_security_plug_process_cmd_v2(uint8_t cmd_id, + uint8_t cmd_data, uint8_t cmd_data2); + +/** + * Bulk endpoint to read data from the security node. This is preceeded by + * calling a sepcific command (see process cmd functions and enums of cmds). + * + * This version is used on IIDX 9 to 13. + * + * @param pkg Pointer to a bulk package to read the data into. + * @return True on success, false on error. + */ +bool ezusb_iidx_emu_node_security_plug_read_packet_v1( + struct ezusb_iidx_msg_bulk_packet* pkg); + +/** + * Bulk endpoint to read data from the security node. This is preceeded by + * calling a sepcific command (see process cmd functions and enums of cmds). + * + * This version is used on IIDX 14 to 17. + * + * @param pkg Pointer to a bulk package to read the data into. + * @return True on success, false on error. + */ +bool ezusb_iidx_emu_node_security_plug_read_packet_v2( + struct ezusb_iidx_msg_bulk_packet* pkg); + +/** + * Bulk endpoint to provide data to write to the security node. This is + * preceeded by calling a sepcific command (see process cmd functions and enums + * of cmds). + * + * @param pkg Pointer to a bulk package with data to write to the node. + * @result True on success, false on error. + */ +bool ezusb_iidx_emu_node_security_plug_write_packet( + const struct ezusb_iidx_msg_bulk_packet* pkg); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-serial.c b/src/main/ezusb-iidx-emu/node-serial.c new file mode 100644 index 0000000..767a0fa --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-serial.c @@ -0,0 +1,1182 @@ +#define LOG_MODULE "ezusb-iidx-emu-node-serial" + +#include + +#include "bemanitools/eamio.h" + +#include "ezusb-iidx/serial-cmd.h" +#include "ezusb-iidx-emu/card-mag.c" +#include "ezusb-iidx-emu/node-serial.h" + +#include "security/mcode.h" + +#include "util/log.h" +#include "util/hex.h" +#include "util/thread.h" + +#define CARD_ID_LEN 8 + +enum ezusb_iidx_emu_node_serial_serial_msg_cmd { + CMD_H8_REQ = 0xAA, + CMD_H8_RESP = 0xA5, + CMD_NODE_REQ = 0x00, + CMD_NODE_RESP = 0x01 +}; + +enum ezusb_iidx_emu_node_serial_h8_cmd { + H8_CMD_NODE_ENUM = 0x01, + H8_CMD_GET_VERSION = 0x02, + H8_CMD_PROG_EXEC = 0x03, +}; + +enum ezusb_iidx_emu_node_serial_node_cmd { + NODE_CMD_CARD_INIT = 0x00, + NODE_CMD_KEYBOARD_INIT = 0x10, + NODE_CMD_CARD_RW_UNIT_GET_STATUS = 0x12, + NODE_CMD_CARD_SLOT_SET_STATE = 0x14, + NODE_CMD_CARD_WRITE = 0x16, + NODE_CMD_CARD_READ = 0x18, + /* Not sure what this does. Only used in combination + with writing the card on the format call */ + NODE_CMD_CARD_FORMAT_COMPLETE = 0x1E, + NODE_CMD_CARD_GET_STATUS = 0x20, + NODE_CMD_KEYBOARD_GET_STATUS = 0x24, + NODE_CMD_KEYBOARD_READ_DATA = 0x26, + NODE_CMD_KEYBOARD_GET_BUFFER_SIZE = 0x27 +}; + +enum ezusb_iidx_emu_node_serial_card_slot_state { + /* Closes/locks the card slot if a card is inserted to avoid pulling out + If no card is inserted, close means that no cards are accepted + you have to call open first to accept a card */ + CARD_SLOT_STATE_CLOSE = 0, + /* Open the card slot and allow a new card to be inserted and accepted */ + CARD_SLOT_STATE_OPEN = 1, + /* Eject an inserted card */ + CARD_SLOT_STATE_EJECT = 2, + /* Trigger card formating and write some data to it */ + CARD_SLOT_STATE_FORMAT = 3, + /* Request reading data from the card. */ + CARD_SLOT_STATE_READ = 4, + /* Request writing data to the card */ + CARD_SLOT_STATE_WRITE = 5, +}; + +#define MAG_CARD_DATA_SIZE 128 + +struct ezusb_iidx_emu_node_serial_msg +{ + uint8_t msg_cmd; + uint8_t node_id; + uint8_t node_cmd; + uint8_t payload_len; + + union payload { + struct { + uint8_t node_id_to_assign; + } node_enum_req; + + struct { + uint8_t total_nodes; + } node_enum_resp; + + struct { + uint32_t type; + uint8_t dup; + uint8_t version_maj; + uint8_t version_min; + uint8_t version_rev; + char comment[5]; + } get_version_resp; + + struct { + uint16_t buffer_size_type; + } keyboard_get_buffer_size_resp; + + struct { + uint8_t buffer_size_type; + } keyboard_read_data_req; + + struct { + /* dynamic size */ + uint8_t data[0xFF]; + } keyboard_read_data_resp; + + struct { + uint8_t ezusb_iidx_emu_node_serial_card_slot_state; + } card_slot_state_req; + + struct { + /* 1 byte header + 128 byte card data */ + uint8_t header; + uint8_t data[MAG_CARD_DATA_SIZE]; + } card_read_resp; + + struct { + uint8_t data[MAG_CARD_DATA_SIZE]; + } card_write_req; + + /* + * get_version_req + * proc_exec_req + * card_init_req + * keyboard_init_req + * card_get_status_req + * keyboard_get_status_req + * card_rw_unit_get_status_req + * keyboard_get_buffer_size_req + * card_read_req + * card_format_complete_req + */ + struct { + /* no payload */ + } common_req; + + /* + * proc_exec_resp + * card_init_resp + * keyboard_init_resp + * card_get_status_resp + * keyboard_get_status_resp + * card_rw_unit_get_status_resp + * card_slot_state_resp + * card_write_resp + * card_format_complete_resp + */ + struct { + uint8_t status; + } common_status_resp; + }; +}; + +/* ------------------------------------------------------------------------- */ + +enum ezusb_iidx_emu_node_serial_emu_card_slot_state { + EMU_CARD_SLOT_STATE_CLOSE = 0, + EMU_CARD_SLOT_STATE_OPEN = 1, + EMU_CARD_SLOT_STATE_EJECT = 2, + EMU_CARD_SLOT_STATE_READ = 3, +}; + +enum ezusb_iidx_emu_node_serial_emu_state { + EMU_STATE_UNINIT = 0, + EMU_STATE_INIT = 1, + EMU_STATE_LOOP = 2, + EMU_STATE_REQ_CARD = 3, + EMU_STATE_CARD_READ = 4, + EMU_STATE_ERROR = 5, +}; + +struct ezusb_iidx_emu_node_serial_emulation_state { + volatile long state_current; + volatile LONG keypad_buf; + + bool card_slot_sensor_front; + bool card_slot_sensor_back; + + uint8_t ezusb_iidx_emu_node_serial_card_slot_state; + + CRITICAL_SECTION card_cs; + uint8_t card_id[CARD_ID_LEN]; +}; + +/* ------------------------------------------------------------------------- */ + +static const char ezusb_iidx_emu_node_serial_eamio_mapping[EAM_IO_KEYPAD_COUNT] = { + '0', + '1', + '4', + '7', + 'O', + '2', + '5', + '8', + 'E', + '3', + '6', + '9' +}; + +static const uint8_t HEADER_BYTE = 0xAA; + +static bool ezusb_iidx_emu_node_serial_read_buf_busy; +static bool ezusb_iidx_emu_node_serial_write_buf_busy; +static uint8_t ezusb_iidx_emu_node_serial_read_buf[512]; +static uint8_t ezusb_iidx_emu_node_serial_read_buf_page; +static uint16_t ezusb_iidx_emu_node_serial_read_buf_data_len; +static uint8_t ezusb_iidx_emu_node_serial_write_buf[512]; +static uint8_t ezusb_iidx_emu_node_serial_wrote_buf_page; +static uint16_t ezusb_iidx_emu_node_serial_write_buf_data_len; + +static uint8_t ezusb_iidx_emu_node_serial_card_attr_type; +static bool ezusb_iidx_emu_node_serial_card_attr_used = true; +static char ezusb_iidx_emu_node_serial_card_attr_version[3] = + SECURITY_MCODE_GAME_IIDX_9; + +static uint8_t* ezusb_iidx_emu_node_serial_write_loopback_card_buf[2]; + +static int ezusb_iidx_emu_node_serial_emu_thread = -1; +static struct ezusb_iidx_emu_node_serial_emulation_state ezusb_iidx_emu_node_serial_emulation_state[2]; + +static void ezusb_iidx_emu_node_serial_dump_buf_log(const char* header_msg, + const uint8_t* buffer, uint16_t length); + +static void ezusb_iidx_emu_node_serial_exec_req_resp(const uint8_t* buffer_in, + uint16_t buffer_in_length, uint8_t* buffer_out, uint16_t* buffer_out_length); + +static int ezusb_iidx_emu_node_serial_emu_thread_proc(void* ctx); + +/* ------------------------------------------------------------------------- */ + +void ezusb_iidx_emu_node_serial_init(void) +{ + ezusb_iidx_emu_node_serial_read_buf_busy = false; + ezusb_iidx_emu_node_serial_write_buf_busy = false; + memset(ezusb_iidx_emu_node_serial_read_buf, 0xFF, sizeof(ezusb_iidx_emu_node_serial_read_buf)); + ezusb_iidx_emu_node_serial_read_buf_page = 0; + ezusb_iidx_emu_node_serial_read_buf_data_len = 0; + memset(ezusb_iidx_emu_node_serial_write_buf, 0xFF, sizeof(ezusb_iidx_emu_node_serial_write_buf)); + ezusb_iidx_emu_node_serial_wrote_buf_page = 0; + ezusb_iidx_emu_node_serial_write_buf_data_len = 0; + + memset(ezusb_iidx_emu_node_serial_emulation_state, 0, + sizeof(struct ezusb_iidx_emu_node_serial_emulation_state) * 2); + + for (uint8_t i = 0; i < 2; i++) { + InitializeCriticalSection(&ezusb_iidx_emu_node_serial_emulation_state[i].card_cs); + } + + ezusb_iidx_emu_node_serial_emu_thread = + thread_create(ezusb_iidx_emu_node_serial_emu_thread_proc, NULL, 0x4000, 0); +} + +uint8_t ezusb_iidx_emu_node_serial_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_SERIAL_CMD_READ_BUFFER: + //log_misc("EZUSB_SERIAL_CMD_READ_BUFFER"); + ezusb_iidx_emu_node_serial_read_buf_busy = false; + /* always reset page count on req */ + ezusb_iidx_emu_node_serial_read_buf_page = 0; + return EZUSB_IIDX_SERIAL_CMD_STATUS_OK; + + case EZUSB_IIDX_SERIAL_CMD_WRITE_BUFFER: + //log_misc("EZUSB_SERIAL_CMD_WRITE_BUFFER"); + ezusb_iidx_emu_node_serial_write_buf_busy = false; + /* always reset page count on req */ + ezusb_iidx_emu_node_serial_wrote_buf_page = 0; + return EZUSB_IIDX_SERIAL_CMD_STATUS_OK; + + case EZUSB_IIDX_SERIAL_CMD_CLEAR_READ_BUFFER: + //log_misc("EZUSB_SERIAL_CMD_CLEAR_READ_BUFFER"); + ezusb_iidx_emu_node_serial_read_buf_busy = false; + /* always reset page count on req */ + ezusb_iidx_emu_node_serial_read_buf_page = 0; + return EZUSB_IIDX_SERIAL_CMD_STATUS_OK; + + case EZUSB_IIDX_SERIAL_CMD_CLEAR_WRITE_BUFFER: + //log_misc("EZUSB_SERIAL_CMD_CLEAR_WRITE_BUFFER"); + ezusb_iidx_emu_node_serial_write_buf_busy = false; + /* always reset page count on req */ + ezusb_iidx_emu_node_serial_wrote_buf_page = 0; + return EZUSB_IIDX_SERIAL_CMD_STATUS_OK; + + default: + log_warning("Unrecognised serial command: %02x", cmd_id); + return EZUSB_IIDX_SERIAL_CMD_STATUS_FAULT; + } +} + +bool ezusb_iidx_emu_node_serial_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg) +{ + uint16_t buffer_offset = 0; + uint16_t data_length = 0; + + /* id to get accepted */ + pkg->node = 0x42; + pkg->page = ezusb_iidx_emu_node_serial_read_buf_page; + + memset(pkg->payload, 0xFF, sizeof(pkg->payload)); + + /* calc data size */ + buffer_offset = ezusb_iidx_emu_node_serial_read_buf_page * sizeof(pkg->payload); + + if (ezusb_iidx_emu_node_serial_read_buf_data_len >= sizeof(pkg->payload)) { + data_length = sizeof(pkg->payload); + ezusb_iidx_emu_node_serial_read_buf_data_len -= sizeof(pkg->payload); + ezusb_iidx_emu_node_serial_read_buf_page++; + } else { + data_length = ezusb_iidx_emu_node_serial_read_buf_data_len; + pkg->page = 0x40 + data_length; + ezusb_iidx_emu_node_serial_read_buf_data_len = 0; + } + + memcpy(pkg->payload, ezusb_iidx_emu_node_serial_read_buf + buffer_offset, data_length); + + return true; +} + +bool ezusb_iidx_emu_node_serial_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + bool execute = false; + uint16_t buffer_offset = 0; + uint16_t data_length = 0; + + buffer_offset = ezusb_iidx_emu_node_serial_wrote_buf_page * sizeof(pkg->payload); + + /* the stuff getting received here is a serial stream + which is terminated by a size >= 0x42 + if the page var is < 0x42, it indicates the page */ + + if (pkg->page >= 0x42) { + data_length = pkg->page - 0x42; + execute = true; + } else { + /* full page */ + data_length = sizeof(pkg->payload); + ezusb_iidx_emu_node_serial_wrote_buf_page++; + } + + ezusb_iidx_emu_node_serial_write_buf_data_len += data_length; + memcpy(ezusb_iidx_emu_node_serial_write_buf + buffer_offset, pkg->payload, data_length); + + /* serial buffer is filled, execute request/response chain + dunno if this is a good spot to do this... */ + + if (execute) { + ezusb_iidx_emu_node_serial_exec_req_resp(ezusb_iidx_emu_node_serial_write_buf, + ezusb_iidx_emu_node_serial_write_buf_data_len, ezusb_iidx_emu_node_serial_read_buf, + &ezusb_iidx_emu_node_serial_read_buf_data_len); + + /* not sure, but there seems to be a bug with the game + not resetting the buffer in the init phase + let us make sure we got this when receiving the serial stream + terminator and having executed something */ + ezusb_iidx_emu_node_serial_write_buf_data_len = 0; + } + + return true; +} + +/* ------------------------------------------------------------------------- */ + +bool ezusb_iidx_emu_node_serial_read_buffer_busy(void) +{ + return ezusb_iidx_emu_node_serial_read_buf_busy; +} + +bool ezusb_iidx_emu_node_serial_write_buffer_busy(void) +{ + return ezusb_iidx_emu_node_serial_write_buf_busy; +} + +void ezusb_iidx_emu_node_serial_set_card_attributes(uint8_t card_type, bool used_card, + const char* card_version) +{ + ezusb_iidx_emu_node_serial_card_attr_type = card_type > 4 ? 4 : card_type; + ezusb_iidx_emu_node_serial_card_attr_used = used_card; + memcpy(ezusb_iidx_emu_node_serial_card_attr_version, card_version, + SECURITY_MCODE_GAME_LEN); +} + +/* ------------------------------------------------------------------------- */ + +static void ezusb_iidx_emu_node_serial_dump_buf_log(const char* header_msg, + const uint8_t* buffer, uint16_t length) +{ + char tmp[4096]; + memset(tmp, 0, sizeof(tmp)); + hex_encode_uc(buffer, length, tmp, sizeof(tmp)); + log_misc(">>> Serial data dump, %s (%d): %s", header_msg, length, tmp); +} + +static uint8_t calc_serial_buffer_checksum(const uint8_t* buffer, uint16_t length) +{ + uint8_t checksum = 0; + for (size_t i = 0; i < length; i++) + checksum += buffer[i]; + + return checksum; +} + +static struct ezusb_iidx_emu_node_serial_msg* create_generic_node_response_ok( + const struct ezusb_iidx_emu_node_serial_msg* msg_in, uint16_t* msg_out_len) +{ + *msg_out_len = 4 + 1; + struct ezusb_iidx_emu_node_serial_msg* resp = malloc(*msg_out_len); + resp->msg_cmd = CMD_NODE_RESP; + resp->node_id = msg_in->node_id; + resp->node_cmd = msg_in->node_cmd; + resp->payload_len = 1; + resp->common_status_resp.status = 0; + return resp; +} + +static struct ezusb_iidx_emu_node_serial_msg* process_serial_msg( + const struct ezusb_iidx_emu_node_serial_msg* msg_in, + uint16_t msg_in_len, uint16_t* msg_out_len) +{ + //log_misc("!!! Serial processing msg_cmd %02X, node_cmd %02X, node_id %02X", + // msg_in->msg_cmd, msg_in->node_cmd, msg_in->node_id); + + switch (msg_in->msg_cmd) { + case CMD_H8_REQ: + { + + switch (msg_in->node_cmd) { + case H8_CMD_NODE_ENUM: + { + log_misc("H8_CMD_NODE_ENUM"); + *msg_out_len = 4 + 1; + struct ezusb_iidx_emu_node_serial_msg* resp = malloc(*msg_out_len); + resp->msg_cmd = CMD_H8_RESP; + /* This response is not from a specific node, id needs to be 0 */ + resp->node_id = 0; + resp->node_cmd = msg_in->node_cmd; + resp->payload_len = 1; + resp->node_enum_resp.total_nodes = 2; + return resp; + } + + case H8_CMD_GET_VERSION: + { + log_misc("H8_CMD_GET_VERSION: %d", msg_in->node_id); + *msg_out_len = 4 + 13; + struct ezusb_iidx_emu_node_serial_msg* resp = malloc(*msg_out_len); + resp->msg_cmd = CMD_H8_RESP; + resp->node_id = msg_in->node_id; + resp->node_cmd = msg_in->node_cmd; + /* Payload length is not 13 here, needs to 5 (comment length?) + Might be a bug in the ezusb code */ + resp->payload_len = 5; + resp->get_version_resp.type = 0x03; + resp->get_version_resp.dup = 0x00; + resp->get_version_resp.version_maj = 1; + resp->get_version_resp.version_min = 6; + resp->get_version_resp.version_rev = 0; + resp->get_version_resp.comment[0] = 'I'; + resp->get_version_resp.comment[1] = 'C'; + resp->get_version_resp.comment[2] = 'C'; + resp->get_version_resp.comment[3] = 'A'; + resp->get_version_resp.comment[4] = '\0'; + return resp; + } + + case H8_CMD_PROG_EXEC: + { + log_misc("H8_CMD_PROG_EXEC: %d", msg_in->node_id); + + *msg_out_len = 4 + 1; + struct ezusb_iidx_emu_node_serial_msg* resp = malloc(*msg_out_len); + resp->msg_cmd = CMD_H8_RESP; + resp->node_id = msg_in->node_id; + resp->node_cmd = msg_in->node_cmd; + resp->payload_len = 1; + + /* init reader emulation here if not initialized, yet */ + uint8_t state = + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .state_current; + + if (state == EMU_STATE_UNINIT) { + InterlockedExchange( + &ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .state_current, EMU_STATE_INIT); + + /* this should not take too long, wait */ + while (true) { + state = + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .state_current; + + if (state == EMU_STATE_LOOP) { + resp->common_status_resp.status = 0; + break; + } else if (state == EMU_STATE_ERROR) { + resp->common_status_resp.status = 0xFF; + break; + } + + Sleep(10); + } + } else { + + if (state == EMU_STATE_ERROR) { + resp->common_status_resp.status = 0xFF; + } else { + resp->common_status_resp.status = 0; + } + + } + + return resp; + } + + default: + log_warning("Serial invalid node cmd on h8 req: %02X", + msg_in->node_cmd); + ezusb_iidx_emu_node_serial_dump_buf_log("", (const uint8_t*) msg_in, + msg_in_len); + return NULL; + } + } + + case CMD_NODE_REQ: + { + + switch (msg_in->node_cmd) { + case NODE_CMD_CARD_INIT: + log_misc("NODE_CMD_CARD_INIT: %d", msg_in->node_id); + return create_generic_node_response_ok(msg_in, msg_out_len); + + case NODE_CMD_KEYBOARD_INIT: + log_misc("NODE_CMD_KEYBOARD_INIT: %d", msg_in->node_id); + return create_generic_node_response_ok(msg_in, msg_out_len); + + case NODE_CMD_KEYBOARD_GET_STATUS: + return create_generic_node_response_ok(msg_in, msg_out_len); + + case NODE_CMD_CARD_GET_STATUS: + return create_generic_node_response_ok(msg_in, msg_out_len); + + case NODE_CMD_CARD_RW_UNIT_GET_STATUS: + { + /* report front and back sensor states */ + + *msg_out_len = 4 + 1; + struct ezusb_iidx_emu_node_serial_msg* resp = malloc(*msg_out_len); + resp->msg_cmd = CMD_NODE_RESP; + resp->node_id = msg_in->node_id; + resp->node_cmd = msg_in->node_cmd; + resp->payload_len = 1; + resp->common_status_resp.status = 0; + + if ( ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .state_current == EMU_STATE_ERROR) { + resp->common_status_resp.status = 0xFF; + } else { + + if ( ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .card_slot_sensor_back) { + /* back, triggers card inserted */ + resp->common_status_resp.status |= 128; + /* needs to be set if either front or back is triggered */ + resp->common_status_resp.status |= 2; + } + + if ( ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .card_slot_sensor_front) { + /* front */ + resp->common_status_resp.status |= 64; + /* needs to be set if either front or back is triggered */ + resp->common_status_resp.status |= 2; + } + + } + + return resp; + } + + case NODE_CMD_CARD_SLOT_SET_STATE: + { + switch (msg_in->card_slot_state_req.ezusb_iidx_emu_node_serial_card_slot_state) { + case CARD_SLOT_STATE_CLOSE: + log_misc("CARD_SLOT_STATE_CLOSE, node %d", + msg_in->node_id); + + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .ezusb_iidx_emu_node_serial_card_slot_state = + EMU_CARD_SLOT_STATE_CLOSE; + + break; + + case CARD_SLOT_STATE_OPEN: + log_misc("CARD_SLOT_STATE_OPEN, node %d", + msg_in->node_id); + + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .ezusb_iidx_emu_node_serial_card_slot_state = + EMU_CARD_SLOT_STATE_OPEN; + + break; + + case CARD_SLOT_STATE_EJECT: + log_misc("CARD_SLOT_STATE_EJECT, node %d", + msg_in->node_id); + + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .ezusb_iidx_emu_node_serial_card_slot_state = + EMU_CARD_SLOT_STATE_EJECT; + + break; + + case CARD_SLOT_STATE_FORMAT: + log_misc("CARD_SLOT_STATE_FORMAT, node %d", + msg_in->node_id); + break; + + case CARD_SLOT_STATE_READ: + log_misc("CARD_SLOT_STATE_READ, node %d", + msg_in->node_id); + + InterlockedExchange( + &ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .state_current, EMU_STATE_REQ_CARD); + + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .ezusb_iidx_emu_node_serial_card_slot_state = + EMU_CARD_SLOT_STATE_READ; + + break; + + case CARD_SLOT_STATE_WRITE: + log_misc("CARD_SLOT_STATE_WRITE, node %d", + msg_in->node_id); + break; + + default: + log_warning("Invalid card slot state %d for node %d", + msg_in->card_slot_state_req + .ezusb_iidx_emu_node_serial_card_slot_state, msg_in->node_id); + } + + return create_generic_node_response_ok(msg_in, msg_out_len); + } + + case NODE_CMD_CARD_READ: + { + *msg_out_len = 4 + 129; + struct ezusb_iidx_emu_node_serial_msg* resp = malloc(*msg_out_len); + resp->msg_cmd = CMD_NODE_RESP; + resp->node_id = msg_in->node_id; + resp->node_cmd = msg_in->node_cmd; + /* game expects 24 here, even more data is sent */ + resp->payload_len = 24; + /* fixed header byte */ + resp->card_read_resp.header = 0x48; + memset(resp->card_read_resp.data, 0x00, MAG_CARD_DATA_SIZE); + + /* loop back read for write (see write) */ + + if (ezusb_iidx_emu_node_serial_write_loopback_card_buf[msg_in->node_id - 1] == NULL) { + + struct ezusb_iidx_emu_card_mag_data* card = + (struct ezusb_iidx_emu_card_mag_data*) + resp->card_read_resp.data; + + uint8_t state = + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .state_current; + + /* wait for card to get read */ + while (state == EMU_STATE_REQ_CARD) { + state = ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .state_current; + Sleep(5); + } + + if (state == EMU_STATE_CARD_READ) { + uint8_t card_invert[CARD_ID_LEN]; + + EnterCriticalSection( + &ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .card_cs); + + log_info("Read card %02X%02X%02X%02X%02X%02X%02X%02X", + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1].card_id[0], + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1].card_id[1], + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1].card_id[2], + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1].card_id[3], + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1].card_id[4], + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1].card_id[5], + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1].card_id[6], + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1].card_id[7]); + + /* game flips endianess */ + for (uint8_t i = 0; i < CARD_ID_LEN; i++) { + card_invert[7 - i] = + ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .card_id[i]; + } + + ezusb_iidx_emu_card_mag_generate_data(card, + card_invert, + ezusb_iidx_emu_node_serial_card_attr_type, + ezusb_iidx_emu_node_serial_card_attr_used, + ezusb_iidx_emu_node_serial_card_attr_version); + + /* ezusb_iidx_emu_node_serial_dump_buf_log("card data", + resp->card_read_resp.data, + sizeof(struct ezusb_iidx_emu_card_mag_data)); */ + + InterlockedExchange( + &ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .state_current, EMU_STATE_LOOP); + + LeaveCriticalSection( + &ezusb_iidx_emu_node_serial_emulation_state[msg_in->node_id - 1] + .card_cs); + + } else if (state == EMU_STATE_ERROR) { + /* this indicates an error */ + resp->payload_len = 1; + resp->card_read_resp.data[0] = 0xFF; + } + } else { + log_misc("Reading loopback card buffer"); + memcpy(resp->card_read_resp.data, + ezusb_iidx_emu_node_serial_write_loopback_card_buf[msg_in->node_id - 1], + MAG_CARD_DATA_SIZE); + + free(ezusb_iidx_emu_node_serial_write_loopback_card_buf[msg_in->node_id - 1]); + ezusb_iidx_emu_node_serial_write_loopback_card_buf[msg_in->node_id - 1] = NULL; + } + + return resp; + } + + case NODE_CMD_CARD_WRITE: + { + /* i hate this solution, but it gets rid of the problem that + we don't know the whole magnetic card spec and the game + is ok with just a bunch of them when read only + If a write occurs (for example if the card is registered + for a new player) we will cashe the data which arrives on + the write and loop it back on the next read (next read only) */ + + if (ezusb_iidx_emu_node_serial_write_loopback_card_buf[msg_in->node_id - 1] != NULL) { + free(ezusb_iidx_emu_node_serial_write_loopback_card_buf[msg_in->node_id - 1]); + } + + ezusb_iidx_emu_node_serial_write_loopback_card_buf[msg_in->node_id - 1] = + malloc(MAG_CARD_DATA_SIZE); + memcpy(ezusb_iidx_emu_node_serial_write_loopback_card_buf[msg_in->node_id - 1], + msg_in->card_write_req.data, MAG_CARD_DATA_SIZE); + + return create_generic_node_response_ok(msg_in, msg_out_len); + } + + case NODE_CMD_CARD_FORMAT_COMPLETE: + { + /* just reply, no handling of data necessary */ + return create_generic_node_response_ok(msg_in, msg_out_len); + } + + case NODE_CMD_KEYBOARD_GET_BUFFER_SIZE: + { + *msg_out_len = 4 + 2; + struct ezusb_iidx_emu_node_serial_msg* resp = malloc(*msg_out_len); + resp->msg_cmd = CMD_NODE_RESP; + resp->node_id = msg_in->node_id; + resp->node_cmd = msg_in->node_cmd; + resp->payload_len = 2; + resp->common_status_resp.status = 0; + + /* read buffer sizes. the size is getting converted + and the converted value sent to us again on the read call + 0 -> 0 no data to send + 1 -> 1 + 2 -> 2 + 3 -> 3-4 i.e. max buffer size 4 + 4 -> 5-8 i.e. max buffer size 8 + 5 -> 9-16 i.e. max buffer size 16 + 6 -> 17-32 i.e. max buffer size 32 + 7 -> 33-64 i.e. max buffer size 64 */ + + /* The card reader driver is really bad performance wise + so it's even slower if we use a ring buffer like + data structure. It's easier to use a single byte + buffer and even faster to handle for the code here */ + + uint16_t bufferSize = 0; + uint8_t node = msg_in->node_id - 1; + if (ezusb_iidx_emu_node_serial_emulation_state[node].keypad_buf != 0) { + bufferSize = 1; + } + + bufferSize = bufferSize >= 64 ? 64 : bufferSize; + + /* convert */ + if (bufferSize < 3) { + resp->keyboard_get_buffer_size_resp.buffer_size_type = + bufferSize; + } else if (bufferSize <= 4) { + resp->keyboard_get_buffer_size_resp.buffer_size_type = 3; + } else if (bufferSize <= 8) { + resp->keyboard_get_buffer_size_resp.buffer_size_type = 4; + } else if (bufferSize <= 16) { + resp->keyboard_get_buffer_size_resp.buffer_size_type = 5; + } else if (bufferSize <= 32) { + resp->keyboard_get_buffer_size_resp.buffer_size_type = 6; + } else { + /* avoid overflow, clip everything else */ + resp->keyboard_get_buffer_size_resp.buffer_size_type = 7; + } + + return resp; + } + + case NODE_CMD_KEYBOARD_READ_DATA: + { + *msg_out_len = 4 + 2; + struct ezusb_iidx_emu_node_serial_msg* resp = malloc(*msg_out_len); + resp->msg_cmd = CMD_NODE_RESP; + resp->node_id = msg_in->node_id; + resp->node_cmd = msg_in->node_cmd; + /* has to be the same as the size returned */ + resp->payload_len = msg_in->keyboard_read_data_req.buffer_size_type; + + uint8_t maxBufferSize = 0; + + /* convert back */ + switch (msg_in->keyboard_read_data_req.buffer_size_type) + { + case 0: + maxBufferSize = 0; break; + case 1: + maxBufferSize = 1; break; + case 2: + maxBufferSize = 2; break; + case 3: + maxBufferSize = 4; break; + case 4: + maxBufferSize = 8; break; + case 5: + maxBufferSize = 16; break; + case 6: + maxBufferSize = 32; break; + case 7: + maxBufferSize = 64; break; + default: + log_fatal("Invalid buffer size %d for reading keyboard data.", + msg_in->keyboard_read_data_req.buffer_size_type); + break; + } + + memset(resp->keyboard_read_data_resp.data, 0, maxBufferSize); + + if (maxBufferSize > 0) { + uint8_t node = msg_in->node_id - 1; + resp->keyboard_read_data_resp.data[0] = + InterlockedExchange( + &ezusb_iidx_emu_node_serial_emulation_state[node].keypad_buf, + 0); + } + + return resp; + } + + default: + log_warning("Serial invalid node cmd on node req: %02X", + msg_in->node_cmd); + ezusb_iidx_emu_node_serial_dump_buf_log("", (const uint8_t*) msg_in, + msg_in_len); + return NULL; + } + } + + default: + log_warning("Serial invalid msg cmd id %02X", msg_in->msg_cmd); + ezusb_iidx_emu_node_serial_dump_buf_log("", (const uint8_t*) msg_in, msg_in_len); + return NULL; + } +} + +static void ezusb_iidx_emu_node_serial_exec_req_resp(const uint8_t* buffer_in, + uint16_t buffer_in_length, uint8_t* buffer_out, uint16_t* buffer_out_length) +{ + /* data layout + index 0: 0xAA header byte always + index 1 to (length-1) - 1 excluding: payload + index (length-1) - 1: checksum byte -> add up all data bytes between + first and last byte + + no escape byte like on newer readers here */ + + /* Before we do any processing: + Filter garbage/broken messages. Maybe caused by buggy code (?) some messages + during the uart init phase start with 0x00 and don't have a valid checksum. + However, the game wants a response for that but doesn't care about the contents + so we just throw the trash back where it came from */ + + /* + ezusb_iidx_emu_node_serial_dump_buf_log("request packed", + (const uint8_t*) buffer_in, buffer_in_length); + */ + + if ((buffer_in_length > 0 && buffer_in[0] == 0x00) || + /* That's some uart init/reset message. Response expected, same message */ + (buffer_in_length == 4 && buffer_in[0] == 0xAA)) { + *buffer_out_length = buffer_in_length; + memcpy(buffer_out, buffer_in, buffer_in_length); + return; + } + + /* check header */ + + if (buffer_in_length > 0 && buffer_in[0] != HEADER_BYTE) { + log_warning("Invalid serial message, not starting with header byte"); + ezusb_iidx_emu_node_serial_dump_buf_log("", (const uint8_t*) buffer_in, buffer_in_length); + *buffer_out_length = 0; + return; + } + + /* Checksum of each message is all data between header and checksum + byte (last byte of msg) */ + + if (buffer_in[buffer_in_length - 1] != calc_serial_buffer_checksum( + buffer_in + 1, buffer_in_length - 2)) { + log_warning("Serial message invalid checksum"); + ezusb_iidx_emu_node_serial_dump_buf_log("", buffer_in, buffer_in_length); + *buffer_out_length = 0; + return; + } + + + const uint8_t* msg_buf_in = &buffer_in[1]; + uint16_t msg_buf_in_len = buffer_in_length - 2; + + /* Exception: catch uart init/reset message, game expects to respond with + the same message this message is sent/written multiple times and read + back once only by the game */ + + if (msg_buf_in_len == 2 && msg_buf_in[0] == HEADER_BYTE && + msg_buf_in[1] == HEADER_BYTE) { + uint8_t* msg_buf_out = &buffer_out[1]; + uint16_t msg_buf_out_len = 0; + + msg_buf_out[0] = HEADER_BYTE; + msg_buf_out[1] = HEADER_BYTE; + msg_buf_out_len = 2; + + buffer_out[0] = HEADER_BYTE; + buffer_out[msg_buf_out_len + 1] = calc_serial_buffer_checksum(msg_buf_out, + msg_buf_out_len); + *buffer_out_length = msg_buf_out_len + 2; + } else { + /* Wtf, really? */ + /* All messages except the uart init/reset and node enum message + must send the request they respond to along... */ + const struct ezusb_iidx_emu_node_serial_msg* msg_in = + (const struct ezusb_iidx_emu_node_serial_msg*) msg_buf_in; + + if (msg_in->msg_cmd == CMD_H8_REQ && msg_in->node_cmd == H8_CMD_NODE_ENUM) { + uint8_t* msg_buf_out = &buffer_out[1]; + uint16_t msg_buf_out_len = 0; + struct ezusb_iidx_emu_node_serial_msg* msg_out = + process_serial_msg((const struct ezusb_iidx_emu_node_serial_msg*) msg_buf_in, + msg_buf_in_len, &msg_buf_out_len); + + if (msg_out == NULL) { + *buffer_out_length = 0; + return; + } + + memcpy(msg_buf_out, msg_out, msg_buf_out_len); + free(msg_out); + + /* pack serial out data, add header and checksum */ + buffer_out[0] = HEADER_BYTE; + buffer_out[msg_buf_out_len + 1] = calc_serial_buffer_checksum( + msg_buf_out, msg_buf_out_len); + *buffer_out_length = msg_buf_out_len + 2; + } else { + /* everything else that's not fucked up */ + uint8_t* msg_buf_out = &buffer_out[buffer_in_length + 1]; + uint16_t msg_buf_out_len = 0; + struct ezusb_iidx_emu_node_serial_msg* msg_out = + process_serial_msg((const struct ezusb_iidx_emu_node_serial_msg*) msg_buf_in, + msg_buf_in_len, &msg_buf_out_len); + + if (msg_out == NULL) { + *buffer_out_length = 0; + return; + } + + /* Copy request followed by packed response */ + memcpy(buffer_out, buffer_in, buffer_in_length); + + memcpy(msg_buf_out, msg_out, msg_buf_out_len); + free(msg_out); + + /* pack serial out data (after the request which needs to be part + of the full response buffer), add header and checksum */ + buffer_out[buffer_in_length] = HEADER_BYTE; + buffer_out[buffer_in_length + msg_buf_out_len + 1] = + calc_serial_buffer_checksum(msg_buf_out, msg_buf_out_len); + + *buffer_out_length = buffer_in_length + msg_buf_out_len + 2; + + } + } + + /* + ezusb_iidx_emu_node_serial_dump_buf_log("response packed", + (const uint8_t*) buffer_out, *buffer_out_length); + */ +} + +/* ------------------------------------------------------------------------- */ + +static uint8_t convert_keyboard_char(char c) +{ + switch (c) { + case '0': return 0x70; + case '1': return 0x69; + case '2': return 0x72; + case '3': return 0x7A; + case '4': return 0x6B; + case '5': return 0x73; + case '6': return 0x74; + case '7': return 0x6C; + case '8': return 0x75; + case '9': return 0x7D; + /* seems like 00 is not supported, use 0 instead */ + case 'O': return 0x70; + /* that's deleting/backspace */ + case 'E': return 0x66; + default: return 0; + } + /* more codes, don't seem to be used + resp->m_data[10] = 0x77; + resp->m_data[11] = 0x4A; + resp->m_data[12] = 0x7C; + resp->m_data[13] = 0x66; + resp->m_data[14] = 0x7B; + resp->m_data[15] = 0x76; + resp->m_data[16] = 0x5A; */ +} + +static void keyboard_add_key_press_to_buffer(uint8_t node, char key) +{ + InterlockedCompareExchange(&ezusb_iidx_emu_node_serial_emulation_state[node].keypad_buf, + convert_keyboard_char(key), 0); +} + +static int ezusb_iidx_emu_node_serial_emu_thread_proc(void* ctx) +{ + log_info("Started magnetic card reader emulation thread"); + + /* wait until we are allowed to execute */ + uint8_t init = 0; + while (init < 2) { + for (uint8_t i = 0; i < 2; i++) { + if (ezusb_iidx_emu_node_serial_emulation_state[i].state_current == EMU_STATE_INIT) { + InterlockedExchange( + &ezusb_iidx_emu_node_serial_emulation_state[i].state_current, + EMU_STATE_LOOP); + init++; + } + } + + Sleep(10); + } + + uint16_t keyboard_state_prev[2] = {0, 0}; + while (true) { + for (uint8_t node = 0; node < 2; node++) { + + if (!eam_io_poll(node)) { + log_warning("Polling eamio, node %d failed", node); + continue; + } + + /* read card slot sensors */ + + uint8_t sensors = eam_io_get_sensor_state(node); + if (sensors & (1 << EAM_IO_SENSOR_FRONT)) { + ezusb_iidx_emu_node_serial_emulation_state[node] + .card_slot_sensor_front = true; + } else { + ezusb_iidx_emu_node_serial_emulation_state[node] + .card_slot_sensor_front = false; + } + + if (sensors & (1 << EAM_IO_SENSOR_BACK)) { + ezusb_iidx_emu_node_serial_emulation_state[node] + .card_slot_sensor_back = true; + } else { + ezusb_iidx_emu_node_serial_emulation_state[node] + .card_slot_sensor_back = false; + } + + /* handle keypad */ + + uint16_t keypad = eam_io_get_keypad_state(node); + uint16_t keypad_rise = ~keyboard_state_prev[node] & keypad; + + for (uint8_t i = 0; i < sizeof(ezusb_iidx_emu_node_serial_eamio_mapping); ++i) { + if (keypad_rise & (1 << i)) { + keyboard_add_key_press_to_buffer(node, + ezusb_iidx_emu_node_serial_eamio_mapping[i]); + } + } + + keyboard_state_prev[node] = keypad; + + /* set card slot state */ + switch (ezusb_iidx_emu_node_serial_emulation_state[node] + .ezusb_iidx_emu_node_serial_card_slot_state) { + case EMU_CARD_SLOT_STATE_CLOSE: + eam_io_card_slot_cmd(node, EAM_IO_CARD_SLOT_CMD_CLOSE); + break; + + case EMU_CARD_SLOT_STATE_EJECT: + eam_io_card_slot_cmd(node, EAM_IO_CARD_SLOT_CMD_EJECT); + + EnterCriticalSection( + &ezusb_iidx_emu_node_serial_emulation_state[node].card_cs); + + /* invalidate card id */ + memset(ezusb_iidx_emu_node_serial_emulation_state[node].card_id, 0, + CARD_ID_LEN); + + LeaveCriticalSection( + &ezusb_iidx_emu_node_serial_emulation_state[node].card_cs); + break; + + case EMU_CARD_SLOT_STATE_OPEN: + eam_io_card_slot_cmd(node, EAM_IO_CARD_SLOT_CMD_OPEN); + break; + + case EMU_CARD_SLOT_STATE_READ: + eam_io_card_slot_cmd(node, EAM_IO_CARD_SLOT_CMD_READ); + + /* read once */ + if ( ezusb_iidx_emu_node_serial_emulation_state[node] + .state_current == EMU_STATE_REQ_CARD) { + EnterCriticalSection( + &ezusb_iidx_emu_node_serial_emulation_state[node].card_cs); + + if ( !eam_io_read_card(node, + ezusb_iidx_emu_node_serial_emulation_state[node].card_id, + CARD_ID_LEN)) { + memset(ezusb_iidx_emu_node_serial_emulation_state[node].card_id, + 0, CARD_ID_LEN); + InterlockedExchange( + &ezusb_iidx_emu_node_serial_emulation_state[node].state_current, + EMU_STATE_ERROR); + } else { + InterlockedExchange( + &ezusb_iidx_emu_node_serial_emulation_state[node].state_current, + EMU_STATE_CARD_READ); + } + + LeaveCriticalSection( + &ezusb_iidx_emu_node_serial_emulation_state[node].card_cs); + } + + break; + + default: + break; + } + } + + /* avoid cpu banging */ + Sleep(5); + } + + return 0; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-serial.h b/src/main/ezusb-iidx-emu/node-serial.h new file mode 100644 index 0000000..cccf180 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-serial.h @@ -0,0 +1,36 @@ +#ifndef EZUSB_IIDX_EMU_NODE_SERIAL_H +#define EZUSB_IIDX_EMU_NODE_SERIAL_H + +#include "ezusb-iidx-emu/node.h" + +void ezusb_iidx_emu_node_serial_init(void); + +uint8_t ezusb_iidx_emu_node_serial_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2); + +bool ezusb_iidx_emu_node_serial_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg); + +bool ezusb_iidx_emu_node_serial_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg); + +/** + * Check if the uart read buffer is busy (refer to ezusb interrupt read) + */ +bool ezusb_iidx_emu_node_serial_read_buffer_busy(void); + +/** + * Check if the uart write buffer is busy (refer to ezusb interrupt read) + */ +bool ezusb_iidx_emu_node_serial_write_buffer_busy(void); + +/** + * Set a few attributes to emulate magnetic cards for different game versions + * @param card_type Type of the card (0 - 4) + * @param used_Card Used card with data on it or empty card. If you don't + * know what you are doing set this to true. + * @param card_version Version of the card to emulate (mcode of the game + * that supports mag cards) + */ +void ezusb_iidx_emu_node_serial_set_card_attributes(uint8_t card_type, + bool used_card, const char* card_version); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-sram.c b/src/main/ezusb-iidx-emu/node-sram.c new file mode 100644 index 0000000..8a5af87 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-sram.c @@ -0,0 +1,97 @@ +#define LOG_MODULE "ezusb-iidx-emu-node-sram" + +#include + +#include "ezusb-iidx/sram-cmd.h" +#include "ezusb-iidx-emu/conf.h" +#include "ezusb-iidx-emu/node-sram.h" + +#include "util/fs.h" +#include "util/log.h" + +#define SRAM_NPAGES 12 + +static uint8_t ezusb_iidx_emu_node_sram_buf[EZUSB_PAGESIZE * SRAM_NPAGES]; +static enum ezusb_iidx_sram_command ezusb_iidx_emu_node_sram_last_cmd; +static int ezusb_iidx_emu_node_sram_read_page; + +uint8_t ezusb_iidx_emu_node_sram_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + ezusb_iidx_emu_node_sram_last_cmd = cmd_id; + + switch (cmd_id) { + case EZUSB_IIDX_SRAM_CMD_READ: + log_misc("EZUSB_IIDX_SRAM_CMD_READ"); + ezusb_iidx_emu_node_sram_read_page = 0; + break; + + case EZUSB_IIDX_SRAM_CMD_WRITE: + log_misc("EZUSB_IIDX_SRAM_CMD_WRITE"); + ezusb_iidx_emu_node_sram_read_page = 0; + break; + + case EZUSB_IIDX_SRAM_CMD_DONE: + log_misc("EZUSB_IIDX_SRAM_CMD_DONE"); + +#ifdef EZUSB_IIDX_EMU_NODE_SRAM_DUMP + file_save("sram.bin", ezusb_iidx_emu_node_sram_buf, + EZUSB_PAGESIZE * SRAM_NPAGES); + + log_info("Dumped sram data to sram.bin"); +#endif + + break; + + default: + log_warning("Unrecognised sram command: %02x", cmd_id); + break; + } + + return 0; +} + +bool ezusb_iidx_emu_node_sram_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg) +{ + if (ezusb_iidx_emu_node_sram_last_cmd == EZUSB_IIDX_SRAM_CMD_READ) { + + if (ezusb_iidx_emu_node_sram_read_page >= SRAM_NPAGES) { + log_warning("SRAM read overrun"); + + return false; + } + + log_misc("Reading SRAM page %02x", ezusb_iidx_emu_node_sram_read_page); + + /* Gold to Sirius must have this set to get accepted */ + pkg->node = 0x40; + pkg->page = (uint8_t) ezusb_iidx_emu_node_sram_read_page; + memcpy( pkg->payload, + ezusb_iidx_emu_node_sram_buf + ezusb_iidx_emu_node_sram_read_page * + EZUSB_PAGESIZE, EZUSB_PAGESIZE); + ezusb_iidx_emu_node_sram_read_page++; + + return true; + + } else { + log_warning("Unexpected SRAM read: ezusb_iidx_emu_node_sram_last_cmd = %02x", + ezusb_iidx_emu_node_sram_last_cmd); + + return false; + } +} + +bool ezusb_iidx_emu_node_sram_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + if (pkg->page >= SRAM_NPAGES) { + log_warning("SRAM write overrun"); + + return false; + } + + log_misc("Writing SRAM page %02x", pkg->page); + memcpy(ezusb_iidx_emu_node_sram_buf + pkg->page * EZUSB_PAGESIZE, pkg->payload, + EZUSB_PAGESIZE); + + return true; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-sram.h b/src/main/ezusb-iidx-emu/node-sram.h new file mode 100644 index 0000000..9e451e6 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-sram.h @@ -0,0 +1,13 @@ +#ifndef EZUSB_IIDX_EMU_NODE_SRAM_H +#define EZUSB_IIDX_EMU_NODE_SRAM_H + +#include "ezusb-iidx-emu/node.h" + +uint8_t ezusb_iidx_emu_node_sram_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2); + +bool ezusb_iidx_emu_node_sram_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg); + +bool ezusb_iidx_emu_node_sram_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-wdt.c b/src/main/ezusb-iidx-emu/node-wdt.c new file mode 100644 index 0000000..436a0bc --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-wdt.c @@ -0,0 +1,30 @@ +#define LOG_MODULE "ezusb-iidx-emu-node-wdt" + +#include "ezusb-iidx/wdt-cmd.h" +#include "ezusb-iidx-emu/node-wdt.h" + +#include "util/log.h" + +uint8_t ezusb_iidx_emu_node_wdt_process_cmd(uint8_t cmd_id, uint8_t cmd_data, + uint8_t cmd_data2) +{ + switch (cmd_id) { + case EZUSB_IIDX_WDT_CMD_INIT: + log_misc("EZUSB_IIDX_WDT_CMD_INIT"); + return EZUSB_IIDX_WDT_CMD_STATUS_OK; + + default: + log_warning("Unrecognised wdt command: %02x", cmd_id); + return EZUSB_IIDX_WDT_CMD_STATUS_FAULT; + } +} + +bool ezusb_iidx_emu_node_wdt_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg) +{ + return true; +} + +bool ezusb_iidx_emu_node_wdt_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg) +{ + return true; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node-wdt.h b/src/main/ezusb-iidx-emu/node-wdt.h new file mode 100644 index 0000000..f793cf3 --- /dev/null +++ b/src/main/ezusb-iidx-emu/node-wdt.h @@ -0,0 +1,12 @@ +#ifndef EZUSB_IIDX_EMU_NODE_WDT_H +#define EZUSB_IIDX_EMU_NODE_WDT_H + +#include "ezusb-iidx-emu/node.h" + +uint8_t ezusb_iidx_emu_node_wdt_process_cmd(uint8_t cmd_id, uint8_t cmd_data, uint8_t cmd_data2); + +bool ezusb_iidx_emu_node_wdt_read_packet(struct ezusb_iidx_msg_bulk_packet* pkg); + +bool ezusb_iidx_emu_node_wdt_write_packet(const struct ezusb_iidx_msg_bulk_packet* pkg); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/node.h b/src/main/ezusb-iidx-emu/node.h new file mode 100644 index 0000000..9a91d3e --- /dev/null +++ b/src/main/ezusb-iidx-emu/node.h @@ -0,0 +1,29 @@ +#ifndef EZUSB_IIDX_EMU_NODE_H +#define EZUSB_IIDX_EMU_NODE_H + +#include +#include + +#include "ezusb-iidx/msg.h" + +/** + * Interface for an ezusb node + */ +struct ezusb_iidx_emu_node { + /* Addressable node id by the ezusb device */ + const uint8_t node_id; + + /* "Constructor" like init call. Called when node is added */ + void (*init_node)(void); + + /* Process an incoming command detrmined for this node */ + uint8_t (*process_cmd)(uint8_t cmd_id, uint8_t cmd_data, uint8_t cmd_data2); + + /* Bulk read data from the node */ + bool (*read_packet)(struct ezusb_iidx_msg_bulk_packet* pkg); + + /* Bulk write data to the node */ + bool (*write_packet)(const struct ezusb_iidx_msg_bulk_packet* pkg); +}; + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx-emu/nodes.c b/src/main/ezusb-iidx-emu/nodes.c new file mode 100644 index 0000000..cd57ced --- /dev/null +++ b/src/main/ezusb-iidx-emu/nodes.c @@ -0,0 +1,159 @@ +/* + * This file contains the ezusb nodes that appeared on + * all IIDX games so far. + */ + +#include "ezusb-iidx/msg.h" + +#include "ezusb-iidx-emu/node.h" +#include "ezusb-iidx-emu/node-16seg.h" +#include "ezusb-iidx-emu/node-coin.h" +#include "ezusb-iidx-emu/node-eeprom.h" +#include "ezusb-iidx-emu/node-fpga.h" +#include "ezusb-iidx-emu/node-none.h" +#include "ezusb-iidx-emu/node-security-mem.h" +#include "ezusb-iidx-emu/node-security-plug.h" +#include "ezusb-iidx-emu/node-serial.h" +#include "ezusb-iidx-emu/node-sram.h" +#include "ezusb-iidx-emu/node-wdt.h" +#include "ezusb-iidx-emu/nodes.h" + +/* All IIDX games */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_16seg = +{ + .node_id = EZUSB_IIDX_MSG_NODE_16SEG, + .init_node = NULL, + .process_cmd = ezusb_iidx_emu_node_16seg_process_cmd, + .read_packet = ezusb_iidx_emu_node_16seg_read_packet, + .write_packet = ezusb_iidx_emu_node_16seg_write_packet +}; + +/* All IIDX games */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_coin = +{ + .node_id = EZUSB_IIDX_MSG_NODE_COIN, + .init_node = NULL, + .process_cmd = ezusb_iidx_emu_node_coin_process_cmd, + .read_packet = ezusb_iidx_emu_node_coin_read_packet, + .write_packet = ezusb_iidx_emu_node_coin_write_packet +}; + +/* Used on 9th to DistorteD */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_eeprom_v1 = +{ + .node_id = EZUSB_IIDX_MSG_NODE_EEPROM, + .init_node = ezusb_iidx_emu_node_eeprom_init, + .process_cmd = ezusb_iidx_emu_node_eeprom_process_cmd_v1, + .read_packet = ezusb_iidx_emu_node_eeprom_read_packet, + .write_packet = ezusb_iidx_emu_node_eeprom_write_packet +}; + +/* Used on Gold to Sirius */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_eeprom_v2 = +{ + .node_id = EZUSB_IIDX_MSG_NODE_EEPROM, + .init_node = ezusb_iidx_emu_node_eeprom_init, + .process_cmd = ezusb_iidx_emu_node_eeprom_process_cmd_v2, + .read_packet = ezusb_iidx_emu_node_eeprom_read_packet, + .write_packet = ezusb_iidx_emu_node_eeprom_write_packet +}; + +/* Used on 9th to DistorteD */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_fpga_v1 = +{ + .node_id = EZUSB_IIDX_MSG_NODE_FPGA_V1, + .init_node = NULL, + .process_cmd = ezusb_iidx_emu_node_fpga_v1_process_cmd, + .read_packet = ezusb_iidx_emu_node_fpga_read_packet, + .write_packet = ezusb_iidx_emu_node_fpga_write_packet +}; + +/* Used on Gold onwards */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_fpga_v2 = +{ + .node_id = EZUSB_IIDX_MSG_NODE_FPGA_V2, + .init_node = NULL, + .process_cmd = ezusb_iidx_emu_node_fpga_v2_process_cmd, + .read_packet = ezusb_iidx_emu_node_fpga_read_packet, + .write_packet = ezusb_iidx_emu_node_fpga_write_packet +}; + +/* All IIDX games */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_none = +{ + .node_id = EZUSB_IIDX_MSG_NODE_NONE, + .init_node = NULL, + .process_cmd = ezusb_iidx_emu_node_none_process_cmd, + .read_packet = ezusb_iidx_emu_node_none_read_packet, + .write_packet = ezusb_iidx_emu_node_none_write_packet +}; + +/* Used on 9th to DistorteD */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_security_mem_v1 = +{ + .node_id = EZUSB_IIDX_MSG_NODE_SECURITY_MEM, + .init_node = ezusb_iidx_emu_node_security_mem_init, + .process_cmd = ezusb_iidx_emu_node_security_mem_v1_process_cmd, + .read_packet = ezusb_iidx_emu_node_security_mem_read_packet, + .write_packet = ezusb_iidx_emu_node_security_mem_write_packet +}; + +/* Used on Gold to Sirius */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_security_mem_v2 = +{ + .node_id = EZUSB_IIDX_MSG_NODE_SECURITY_MEM, + .init_node = ezusb_iidx_emu_node_security_mem_init, + .process_cmd = ezusb_iidx_emu_node_security_mem_v2_process_cmd, + .read_packet = ezusb_iidx_emu_node_security_mem_read_packet, + .write_packet = ezusb_iidx_emu_node_security_mem_write_packet +}; + +/* Used on 9th to DistorteD */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_security_plug_v1 = +{ + .node_id = EZUSB_IIDX_MSG_NODE_SECURITY_PLUG, + .init_node = NULL, + .process_cmd = ezusb_iidx_emu_node_security_plug_process_cmd_v1, + .read_packet = ezusb_iidx_emu_node_security_plug_read_packet_v1, + .write_packet = ezusb_iidx_emu_node_security_plug_write_packet +}; + +/* Used on Gold to Sirius */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_security_plug_v2 = +{ + .node_id = EZUSB_IIDX_MSG_NODE_SECURITY_PLUG, + .init_node = NULL, + .process_cmd = ezusb_iidx_emu_node_security_plug_process_cmd_v2, + .read_packet = ezusb_iidx_emu_node_security_plug_read_packet_v2, + .write_packet = ezusb_iidx_emu_node_security_plug_write_packet +}; + +/* Used on 9th to HappySky for magnetic readers */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_serial = +{ + .node_id = EZUSB_IIDX_MSG_NODE_SERIAL, + .init_node = ezusb_iidx_emu_node_serial_init, + .process_cmd = ezusb_iidx_emu_node_serial_process_cmd, + .read_packet = ezusb_iidx_emu_node_serial_read_packet, + .write_packet = ezusb_iidx_emu_node_serial_write_packet +}; + +/* All IIDX games */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_sram = +{ + .node_id = EZUSB_IIDX_MSG_NODE_SRAM, + .init_node = NULL, + .process_cmd = ezusb_iidx_emu_node_sram_process_cmd, + .read_packet = ezusb_iidx_emu_node_sram_read_packet, + .write_packet = ezusb_iidx_emu_node_sram_write_packet +}; + +/* Used on 9th to Sirius */ +const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_wdt = +{ + .node_id = EZUSB_IIDX_MSG_NODE_WDT, + .init_node = NULL, + .process_cmd = ezusb_iidx_emu_node_wdt_process_cmd, + .read_packet = ezusb_iidx_emu_node_wdt_read_packet, + .write_packet = ezusb_iidx_emu_node_wdt_write_packet +}; diff --git a/src/main/ezusb-iidx-emu/nodes.h b/src/main/ezusb-iidx-emu/nodes.h new file mode 100644 index 0000000..633824b --- /dev/null +++ b/src/main/ezusb-iidx-emu/nodes.h @@ -0,0 +1,21 @@ +#ifndef EZUSBEMU_NODES_IIDX_H +#define EZUSBEMU_NODES_IIDX_H + +#include "ezusb-iidx-emu/node.h" + +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_16seg; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_coin; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_eeprom_v1; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_eeprom_v2; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_fpga_v1; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_fpga_v2; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_none; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_security_mem_v1; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_security_mem_v2; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_security_plug_v1; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_security_plug_v2; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_serial; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_sram; +extern const struct ezusb_iidx_emu_node ezusb_iidx_emu_node_wdt; + +#endif diff --git a/src/main/ezusb-iidx-fpga-flash/Module.mk b/src/main/ezusb-iidx-fpga-flash/Module.mk new file mode 100644 index 0000000..c250b35 --- /dev/null +++ b/src/main/ezusb-iidx-fpga-flash/Module.mk @@ -0,0 +1,12 @@ +exes += ezusb-iidx-fpga-flash + +ldflags_ezusb-iidx-fpga-flash := \ + -lsetupapi \ + +libs_ezusb-iidx-fpga-flash := \ + ezusb \ + ezusb-iidx \ + util \ + +src_ezusb-iidx-fpga-flash := \ + main.c \ diff --git a/src/main/ezusb-iidx-fpga-flash/main.c b/src/main/ezusb-iidx-fpga-flash/main.c new file mode 100644 index 0000000..c51c510 --- /dev/null +++ b/src/main/ezusb-iidx-fpga-flash/main.c @@ -0,0 +1,61 @@ +#include +#include + +#include + +#include "ezusb/ezusb.h" +#include "ezusb-iidx/fpga.h" + +#include "util/fs.h" +#include "util/log.h" + +int main(int argc, char** argv) +{ + HANDLE handle; + void* buffer; + size_t size; + bool res; + + if (argc < 3) { + printf("ezusb-iidx-fpga-flash for EZUSB hardware, e.g. IIDX C02 IO, " + "build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: %s [v1/v2] [fpga fw bin]\n", argv[0]); + return -1; + } + + log_to_writer(log_writer_stderr, NULL); + + log_info("Opening ezusb '%s'...", EZUSB_DEVICE_PATH); + + handle = ezusb_open(EZUSB_DEVICE_PATH); + + if (handle == INVALID_HANDLE_VALUE) { + log_fatal("Cannot open ezusb device: %s", EZUSB_DEVICE_PATH); + return -2; + } + + if (!file_load(argv[2], &buffer, &size, false)) { + log_fatal("Loading file %s failed", argv[2]); + ezusb_close(handle); + return -3; + } + + log_info("Downloading FPGA firmware..."); + + if (!strcmp(argv[1], "v1")) { + res = ezusb_iidx_fpga_v1_init(handle, buffer, size); + } else { + res = ezusb_iidx_fpga_v2_init(handle, buffer, size); + } + + if (!res) { + log_fatal("Downloading FPGA firmware failed"); + ezusb_close(handle); + return -4; + } + + log_misc("Firmware download successful"); + ezusb_close(handle); + + return 0; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx-sram-flash/Module.mk b/src/main/ezusb-iidx-sram-flash/Module.mk new file mode 100644 index 0000000..b6e6a73 --- /dev/null +++ b/src/main/ezusb-iidx-sram-flash/Module.mk @@ -0,0 +1,12 @@ +exes += ezusb-iidx-sram-flash + +ldflags_ezusb-iidx-sram-flash := \ + -lsetupapi \ + +libs_ezusb-iidx-sram-flash := \ + ezusb \ + ezusb-iidx \ + util \ + +src_ezusb-iidx-sram-flash := \ + main.c \ diff --git a/src/main/ezusb-iidx-sram-flash/main.c b/src/main/ezusb-iidx-sram-flash/main.c new file mode 100644 index 0000000..43dbc16 --- /dev/null +++ b/src/main/ezusb-iidx-sram-flash/main.c @@ -0,0 +1,57 @@ +#include +#include + +#include + +#include "ezusb/ezusb.h" +#include "ezusb-iidx/sram.h" + +#include "util/fs.h" +#include "util/log.h" + +int main(int argc, char** argv) +{ + HANDLE handle; + void* buffer; + size_t size; + bool res; + + if (argc < 3) { + printf("ezusb-iidx-sram-flash for EZUSB hardware, e.g. IIDX C02 IO, " + "build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: %s [ezusb dev path] [sram bin]\n", argv[0]); + return -1; + } + + log_to_writer(log_writer_stdout, NULL); + + log_info("Opening ezusb '%s'...", argv[1]); + + handle = ezusb_open(argv[1]); + + if (handle == INVALID_HANDLE_VALUE) { + log_fatal("Cannot open ezusb device: %s", argv[1]); + return -2; + } + + if (!file_load(argv[2], &buffer, &size, false)) { + log_fatal("Loading file %s failed", argv[2]); + ezusb_close(handle); + return -3; + } + + log_info("Initializing SRAM..."); + + res = ezusb_iidx_sram_init(handle, buffer, size); + + if (!res) { + log_fatal("Downloading SRAM data failed"); + ezusb_close(handle); + return -4; + } + + log_misc("Data download successful\n"); + ezusb_close(handle); + + return 0; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx/Module.mk b/src/main/ezusb-iidx/Module.mk new file mode 100644 index 0000000..78adde2 --- /dev/null +++ b/src/main/ezusb-iidx/Module.mk @@ -0,0 +1,9 @@ +libs += ezusb-iidx + +libs_ezusb-iidx := \ + util \ + +src_ezusb-iidx := \ + ezusb-iidx.c \ + fpga.c \ + sram.c \ diff --git a/src/main/ezusb-iidx/coin-cmd.h b/src/main/ezusb-iidx/coin-cmd.h new file mode 100644 index 0000000..aa8a12f --- /dev/null +++ b/src/main/ezusb-iidx/coin-cmd.h @@ -0,0 +1,16 @@ +#ifndef EZUSB_IIDX_COIN_CMD_H +#define EZUSB_IIDX_COIN_CMD_H + +enum ezusb_iidx_coin_command { + /* set when "playing" the game */ + EZUSB_IIDX_COIN_CMD_SET_COIN_MODE_1 = 0x01, + /* set when in service menu */ + EZUSB_IIDX_COIN_CMD_SET_COIN_MODE_2 = 0x02 +}; + +enum ezusb_iidx_coin_command_status { + EZUSB_IIDX_COIN_CMD_STATUS_OK = 0x00, + EZUSB_IIDX_COIN_CMD_STATUS_FAULT = 0xFE +}; + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/eeprom-cmd.h b/src/main/ezusb-iidx/eeprom-cmd.h new file mode 100644 index 0000000..218c279 --- /dev/null +++ b/src/main/ezusb-iidx/eeprom-cmd.h @@ -0,0 +1,22 @@ +#ifndef EZUSB_IIDX_EEPROM_CMD_H +#define EZUSB_IIDX_EEPROM_CMD_H + +/* commands are the same for v1 and v2 */ +enum ezusb_iidx_eeprom_command { + EZUSB_IIDX_EEPROM_CMD_READ = 0x02, + EZUSB_IIDX_EEPROM_CMD_WRITE = 0x03 +}; + +enum ezusb_iidx_eeprom_command_status_v1 { + EZUSB_IIDX_EEPROM_CMD_STATUS_V1_READ_OK = 0x01, + EZUSB_IIDX_EEPROM_CMD_STATUS_V1_WRITE_OK = 0x02, + EZUSB_IIDX_EEPROM_CMD_STATUS_V1_FAULT = 0xFE +}; + +enum ezusb_iidx_eeprom_command_status_v2 { + EZUSB_IIDX_EEPROM_CMD_STATUS_V2_READ_OK = 0x21, + EZUSB_IIDX_EEPROM_CMD_STATUS_V2_WRITE_OK = 0x22, + EZUSB_IIDX_EEPROM_CMD_STATUS_V2_FAULT = 0xFE +}; + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/ezusb-iidx.c b/src/main/ezusb-iidx/ezusb-iidx.c new file mode 100644 index 0000000..91311e2 --- /dev/null +++ b/src/main/ezusb-iidx/ezusb-iidx.c @@ -0,0 +1,271 @@ +#define LOG_MODULE "ezusb-iidx" + +#include "ezusb-iidx/ezusb-iidx.h" + +#include "ezusb/ezusbsys2.h" + +#include "util/hex.h" +#include "util/log.h" +#include "util/time.h" + +#include "msg.h" + +//#define DEBUG_DUMP + +/* For debugging */ +#ifdef DEBUG_DUMP +static void ezusb_iidx_log_usb(const char* prefix, uint32_t ctl_code, + const BULK_TRANSFER_CONTROL *ctl, uint32_t ctl_size, void* header, + uint32_t header_bytes, void* data, uint32_t data_bytes) +{ + char header_str[4096]; + char data_str[4096]; + const char* ctl_code_str; + + switch (ctl_code) { + case IOCTL_Ezusb_GET_DEVICE_DESCRIPTOR: + ctl_code_str = "GET_DEVICE_DESCRIPTOR"; + break; + + case IOCTL_Ezusb_VENDOR_REQUEST: + ctl_code_str = "VENDOR_REQUEST"; + break; + + case IOCTL_EZUSB_ANCHOR_DOWNLOAD: + ctl_code_str = "ANCHOR_DOWNLOAD"; + break; + + case IOCTL_EZUSB_BULK_READ: + if (ctl->pipeNum == EZUSB_IIDX_MSG_PIPE_INTERRUPT_IN) { + ctl_code_str = "INT_READ"; + } else if (ctl->pipeNum == EZUSB_IIDX_MSG_PIPE_BULK_IN) { + ctl_code_str = "BULK_READ"; + } else { + ctl_code_str = "INVALID_READ"; + } + + break; + + case IOCTL_EZUSB_BULK_WRITE: + if (ctl->pipeNum == EZUSB_IIDX_MSG_PIPE_INTERRUPT_OUT) { + ctl_code_str = "INT_WRITE"; + } else if (ctl->pipeNum == EZUSB_IIDX_MSG_PIPE_BULK_OUT) { + ctl_code_str = "BULK_WRITE"; + } else { + ctl_code_str = "INVALID_WRITE"; + } + + break; + + default: + ctl_code_str = "UNKNOWN"; + break; + } + + hex_encode_uc(header, header_bytes, header_str, sizeof(header_str)); + hex_encode_uc(data, data_bytes, data_str, sizeof(data_str)); + + log_misc("[EZUSB DUMP %s][%s] header(%d) %s |||| data(%d) %s\n", + prefix, ctl_code_str, header_bytes, header_str, data_bytes, data_str); +} +#endif + +static BOOL ezusb_iidx_device_io_control_wrapper( + HANDLE fd, uint32_t code, void *in_bytes, uint32_t in_nbytes, + void *out_bytes, uint32_t out_nbytes, DWORD *out_returned, + OVERLAPPED *ovl) +{ + BOOL ret; + +#ifdef DEBUG_DUMP + ezusb_iidx_log_usb("BEFORE", code, in_bytes, in_nbytes, in_bytes, in_nbytes, + out_bytes, out_nbytes); +#endif + + ret = DeviceIoControl(fd, code, in_bytes, in_nbytes, out_bytes, + out_nbytes, out_returned, ovl); + +#ifdef DEBUG_DUMP + ezusb_iidx_log_usb("AFTER", code, in_bytes, in_nbytes, in_bytes, in_nbytes, + out_bytes, out_nbytes); +#endif + + return ret; +} + +bool ezusb_iidx_ioctl(HANDLE handle, uint32_t code, void* in_bytes, + uint32_t in_nbytes, void* out_bytes, uint32_t out_nbytes, + uint32_t* out_returned) +{ + return DeviceIoControl(handle, code, in_bytes, in_nbytes, out_bytes, + out_nbytes, (LPDWORD) out_returned, NULL); +} + +bool ezusb_iidx_interrupt_read(HANDLE handle, + struct ezusb_iidx_msg_interrupt_read_packet* packet) +{ + BULK_TRANSFER_CONTROL transfer; + DWORD outpkt; + + transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_INTERRUPT_IN; + + return ezusb_iidx_device_io_control_wrapper( + handle, IOCTL_EZUSB_BULK_READ, &transfer, + sizeof(transfer), packet, + sizeof(struct ezusb_iidx_msg_interrupt_read_packet), &outpkt, NULL); +} + +bool ezusb_iidx_interrupt_write(HANDLE handle, + const struct ezusb_iidx_msg_interrupt_write_packet* packet) +{ + BULK_TRANSFER_CONTROL transfer; + DWORD outpkt; + + transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_INTERRUPT_OUT; + + return ezusb_iidx_device_io_control_wrapper( + handle, IOCTL_EZUSB_BULK_WRITE, &transfer, + sizeof(transfer), (void*) packet, + sizeof(struct ezusb_iidx_msg_interrupt_write_packet), &outpkt, NULL); +} + +bool ezusb_iidx_bulk_read(HANDLE handle, + struct ezusb_iidx_msg_bulk_packet* packet) +{ + BULK_TRANSFER_CONTROL transfer; + DWORD outpkt; + + transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_BULK_IN; + + return ezusb_iidx_device_io_control_wrapper( + handle, IOCTL_EZUSB_BULK_READ, &transfer, + sizeof(transfer), packet, + sizeof(struct ezusb_iidx_msg_bulk_packet), &outpkt, NULL); +} + +bool ezusb_iidx_bulk_write(HANDLE handle, + const struct ezusb_iidx_msg_bulk_packet* packet) +{ + BULK_TRANSFER_CONTROL transfer; + DWORD outpkt; + + transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_BULK_OUT; + + return ezusb_iidx_device_io_control_wrapper( + handle, IOCTL_EZUSB_BULK_WRITE, &transfer, + sizeof(transfer), (void*) packet, + sizeof(struct ezusb_iidx_msg_bulk_packet), &outpkt, NULL); +} + +bool ezusb_iidx_execute_cmd(HANDLE handle, uint8_t node, + uint8_t cmd, uint8_t cmd_detail, uint8_t cmd_detail2, + struct ezusb_iidx_msg_interrupt_read_packet* int_read_data) +{ + struct ezusb_iidx_msg_interrupt_write_packet int_write; + + memset(&int_write, 0, sizeof(struct ezusb_iidx_msg_interrupt_write_packet)); + + int_write.node = node; + int_write.cmd = cmd; + int_write.cmd_detail[0] = cmd_detail; + int_write.cmd_detail[1] = cmd_detail2; + + log_misc("Execute cmd: %02X %02X %02X %02X", + node, cmd, cmd_detail, cmd_detail2); + + if (!ezusb_iidx_interrupt_write(handle, &int_write)) { + log_warning("Interrupt write failed"); + return false; + } + + Sleep(10); + + if (!ezusb_iidx_interrupt_read(handle, int_read_data)) { + log_warning("Interrupt read failed"); + return false; + } + + log_misc("Cmd result: %02X", int_read_data->status); + + return true; +} + +uint8_t ezusb_iidx_execute_cmd_retry(HANDLE handle, uint8_t node, + uint8_t cmd, uint8_t cmd_detail, uint8_t cmd_detail2, uint8_t int_reads) +{ + struct ezusb_iidx_msg_interrupt_read_packet int_read; + struct ezusb_iidx_msg_interrupt_write_packet int_write; + + memset(&int_write, 0, sizeof(struct ezusb_iidx_msg_interrupt_write_packet)); + + int_write.node = node; + int_write.cmd = cmd; + int_write.cmd_detail[0] = cmd_detail; + int_write.cmd_detail[1] = cmd_detail2; + + log_misc("Execute cmd: %02X %02X %02X %02X", + node, cmd, cmd_detail, cmd_detail2); + + if (!ezusb_iidx_interrupt_write(handle, &int_write)) { + log_warning("Interrupt write failed"); + return 0xFF; + } + + Sleep(10); + + for (uint8_t i = 0; i < int_reads; i++) { + if (!ezusb_iidx_interrupt_read(handle, &int_read)) { + log_warning("Interrupt read failed"); + return 0xFF; + } + } + + log_misc("Cmd result: %02X", int_read.status); + + return int_read.status; +} + +bool ezusb_iidx_execute_cmd_timeout(HANDLE handle, uint8_t node, + uint8_t cmd, uint8_t cmd_detail, uint8_t cmd_detail2, + uint8_t expected_ret_code, uint32_t timeout_ms) +{ + struct ezusb_iidx_msg_interrupt_read_packet int_read; + struct ezusb_iidx_msg_interrupt_write_packet int_write; + + memset(&int_write, 0, sizeof(struct ezusb_iidx_msg_interrupt_write_packet)); + + int_write.node = node; + int_write.cmd = cmd; + int_write.cmd_detail[0] = cmd_detail; + int_write.cmd_detail[1] = cmd_detail2; + + log_misc("Execute cmd: %02X %02X %02X %02X", + node, cmd, cmd_detail, cmd_detail2); + + if (!ezusb_iidx_interrupt_write(handle, &int_write)) { + log_warning("Interrupt write failed"); + return false; + } + + Sleep(10); + + uint64_t start = time_get_counter(); + + while (time_get_elapsed_ms(time_get_counter() - start) < timeout_ms) { + if (!ezusb_iidx_interrupt_read(handle, &int_read)) { + log_warning("Interrupt read failed"); + return false; + } + + if (int_read.status == expected_ret_code) { + log_misc("Cmd result: %02X", expected_ret_code); + return true; + } + + Sleep(10); + } + + log_warning("Cmd failed, result: %02X", int_read.status); + + return false; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx/ezusb-iidx.h b/src/main/ezusb-iidx/ezusb-iidx.h new file mode 100644 index 0000000..5c35c89 --- /dev/null +++ b/src/main/ezusb-iidx/ezusb-iidx.h @@ -0,0 +1,37 @@ +#ifndef EZUSB_IIDX_H +#define EZUSB_IIDX_H + +#include + +#include + +#include "msg.h" + +bool ezusb_iidx_ioctl(HANDLE handle, uint32_t code, void* in_bytes, + uint32_t in_nbytes, void* out_bytes, uint32_t out_nbytes, + uint32_t* out_returned); + +bool ezusb_iidx_interrupt_read(HANDLE handle, + struct ezusb_iidx_msg_interrupt_read_packet* packet); + +bool ezusb_iidx_interrupt_write(HANDLE handle, + const struct ezusb_iidx_msg_interrupt_write_packet* packet); + +bool ezusb_iidx_bulk_read(HANDLE handle, + struct ezusb_iidx_msg_bulk_packet* packet); + +bool ezusb_iidx_bulk_write(HANDLE handle, + const struct ezusb_iidx_msg_bulk_packet* packet); + +bool ezusb_iidx_execute_cmd(HANDLE handle, uint8_t node, + uint8_t cmd, uint8_t cmd_detail, uint8_t cmd_detail2, + struct ezusb_iidx_msg_interrupt_read_packet* int_read_data); + +uint8_t ezusb_iidx_execute_cmd_retry(HANDLE handle, uint8_t node, + uint8_t cmd, uint8_t cmd_detail, uint8_t cmd_detail2, uint8_t int_reads); + +bool ezusb_iidx_execute_cmd_timeout(HANDLE handle, uint8_t node, + uint8_t cmd, uint8_t cmd_detail, uint8_t cmd_detail2, + uint8_t expected_ret_code, uint32_t timeout_ms); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/fpga-cmd.h b/src/main/ezusb-iidx/fpga-cmd.h new file mode 100644 index 0000000..d34b77f --- /dev/null +++ b/src/main/ezusb-iidx/fpga-cmd.h @@ -0,0 +1,37 @@ +#ifndef EZUSB_IIDX_FPGA_CMD_H +#define EZUSB_IIDX_FPGA_CMD_H + +enum ezusb_iidx_fpga_cmd_v1 { + EZUSB_IIDX_FPGA_CMD_V1_INIT = 0x01, + // TODO rename to reset + EZUSB_IIDX_FPGA_CMD_V1_CHECK = 0xFF, + // TODO rename to check? -> see firmware + EZUSB_IIDX_FPGA_CMD_V1_CHECK_2 = 0x02, + EZUSB_IIDX_FPGA_CMD_V1_WRITE = 0x03, + EZUSB_IIDX_FPGA_CMD_V1_WRITE_DONE = 0x04 +}; + +enum ezusb_iidx_fpga_cmd_v2 { + EZUSB_IIDX_FPGA_CMD_V2_INIT = 0x01, + EZUSB_IIDX_FPGA_CMD_V2_CHECK = 0x02, + EZUSB_IIDX_FPGA_CMD_V2_WRITE = 0x03, + EZUSB_IIDX_FPGA_CMD_V2_WRITE_DONE = 0x04 +}; + +enum ezusb_iidx_fpga_cmd_status_v1 { + EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK = 0x00, + /* I even checked the firmware and they used the same + return code for both, error and ok on some calls...*/ + EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK_2 = 0xFE, + EZUSB_IIDX_FPGA_CMD_STATUS_V1_FAULT = 0xFE, +}; + +enum ezusb_iidx_fpga_cmd_status_v2 { + EZUSB_IIDX_FPGA_CMD_STATUS_V2_NULL = 0x00, + EZUSB_IIDX_FPGA_CMD_STATUS_V2_INIT_OK = 0x41, + EZUSB_IIDX_FPGA_CMD_STATUS_V2_CHECK_OK = 0x42, + EZUSB_IIDX_FPGA_CMD_STATUS_V2_WRITE_OK = 0x43, + EZUSB_IIDX_FPGA_CMD_STATUS_V2_FAULT = 0xFE +}; + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/fpga.c b/src/main/ezusb-iidx/fpga.c new file mode 100644 index 0000000..e455726 --- /dev/null +++ b/src/main/ezusb-iidx/fpga.c @@ -0,0 +1,258 @@ +#define LOG_MODULE "ezusb-iidx-fpga" + +#include "ezusb-iidx/fpga.h" + +#include + +#include "util/log.h" + +#include "ezusb-iidx.h" +#include "fpga-cmd.h" + +static bool ezusb_iidx_fpga_write_fw_page(HANDLE handle, uint8_t node, + const void* buffer, uint16_t size) +{ + struct ezusb_iidx_msg_bulk_packet packet; + + memset(&packet.payload, 0, 62); + + packet.node = node; + packet.page = 0; + + if (size > 62) { + size = 62; + } + + //log_misc("Writing fpga page, size %d", size); + + memcpy(packet.payload, buffer, size); + + return ezusb_iidx_bulk_write(handle, &packet); +} + +static bool ezusb_iidx_fpga_write_fw(HANDLE handle, uint8_t node, + const void* buffer, uint16_t size) +{ + uint16_t offset; + + offset = 0; + + log_misc("Writing fw, size %d...", size); + + /* Write in ezusb page sizes */ + while (offset < size) { + if ( !ezusb_iidx_fpga_write_fw_page(handle, node, buffer + offset, + size - offset)) { + return false; + } + + offset += 62; + } + + log_misc("Writing fw finished"); + + return true; +} + +static bool ezusb_iidx_fpga_v1_reset(HANDLE handle) +{ + log_info("[v1] Reset"); + + if ( !ezusb_iidx_execute_cmd_timeout(handle, EZUSB_IIDX_MSG_NODE_FPGA_V1, + EZUSB_IIDX_FPGA_CMD_V1_INIT, 0x41, 0, + EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK_2, + 10000)) { + log_warning("[v1] Reset failed"); + return false; + } + + return true; +} + +static bool ezusb_iidx_fpga_v1_write(HANDLE handle, const void* buffer, uint16_t size) +{ + log_info("[v1] Write firmware, size %d", size); + + /* Have a bunch of sleep between each command. If we try to send commands as fast as possible, the + hardware fails executing commands and might also crash. */ + + if ( !ezusb_iidx_execute_cmd_timeout(handle, EZUSB_IIDX_MSG_NODE_FPGA_V1, + EZUSB_IIDX_FPGA_CMD_V1_CHECK, 0x41, 0, + EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK, + 10000)) { + log_warning("[v1] Check failed"); + return false; + } + + Sleep(10); + + if ( !ezusb_iidx_execute_cmd_timeout(handle, EZUSB_IIDX_MSG_NODE_FPGA_V1, + EZUSB_IIDX_FPGA_CMD_V1_WRITE, size >> 8, size, + EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK, + 10000)) { + log_warning("[v1] Write failed"); + return false; + } + + Sleep(10); + + if ( !ezusb_iidx_fpga_write_fw(handle, EZUSB_IIDX_MSG_NODE_FPGA_V1, + buffer, size)) { + log_warning("[v1] Writing fw failed"); + return false; + } + + Sleep(10); + + if ( !ezusb_iidx_execute_cmd_timeout(handle, EZUSB_IIDX_MSG_NODE_FPGA_V1, + EZUSB_IIDX_FPGA_CMD_V1_WRITE_DONE, size >> 8, size, + EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK_2, + 10000)) { + log_warning("[v1] Write done failed"); + return false; + } + + Sleep(10); + + if ( !ezusb_iidx_execute_cmd_timeout(handle, EZUSB_IIDX_MSG_NODE_FPGA_V1, + EZUSB_IIDX_FPGA_CMD_V1_CHECK, size >> 8, size, + EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK, + 10000)) { + log_warning("[v1] Check failed"); + return false; + } + + Sleep(10); + + if ( !ezusb_iidx_execute_cmd_timeout(handle, EZUSB_IIDX_MSG_NODE_FPGA_V1, + EZUSB_IIDX_FPGA_CMD_V1_CHECK_2, size >> 8, size, + EZUSB_IIDX_FPGA_CMD_STATUS_V1_OK_2, + 10000)) { + log_warning("[v1] Start failed"); + return false; + } + + return true; +} + +static bool ezusb_iidx_fpga_v2_reset(HANDLE handle) +{ + log_info("[v2] Reset"); + + if ( !ezusb_iidx_execute_cmd_timeout(handle, EZUSB_IIDX_MSG_NODE_FPGA_V2, + EZUSB_IIDX_FPGA_CMD_V2_INIT, 1, 0, + EZUSB_IIDX_FPGA_CMD_STATUS_V2_INIT_OK, + 10000)) { + log_warning("[v2] Reset failed"); + return false; + } + + return true; +} + +static bool ezusb_iidx_fpga_v2_write(HANDLE handle, const void* buffer, uint16_t size) +{ + log_info("[v2] Waiting for fpga ready to write..."); + + /* poll until the device reports fpga write ready */ + while (true) { + struct ezusb_iidx_msg_interrupt_read_packet int_read_data; + + if ( !ezusb_iidx_execute_cmd(handle, EZUSB_IIDX_MSG_NODE_FPGA_V2, + EZUSB_IIDX_FPGA_CMD_V2_WRITE, size >> 8, size, + &int_read_data)) { + log_warning("[v2] Write failed"); + return false; + } + + if (int_read_data.fpga_write_ready == 1) { + break; + } + + Sleep(10); + } + + log_info("[v2] Write firmware, size %d", size); + + if ( !ezusb_iidx_fpga_write_fw(handle, EZUSB_IIDX_MSG_NODE_FPGA_V2, + buffer, size)) { + log_warning("[v2] Writing fw failed"); + return false; + } + + if ( !ezusb_iidx_execute_cmd_timeout(handle, EZUSB_IIDX_MSG_NODE_FPGA_V2, + EZUSB_IIDX_FPGA_CMD_V2_WRITE_DONE, size >> 8, size, + EZUSB_IIDX_FPGA_CMD_STATUS_V2_WRITE_OK, + 10000)) { + log_warning("[v2] Write done failed"); + return false; + } + + if ( !ezusb_iidx_execute_cmd_timeout(handle, EZUSB_IIDX_MSG_NODE_FPGA_V2, + EZUSB_IIDX_FPGA_CMD_V2_CHECK, size >> 8, size, + EZUSB_IIDX_FPGA_CMD_STATUS_V2_CHECK_OK, + 10000)) { + log_warning("[v2] Start failed"); + return false; + } + + return true; +} + +static bool ezusb_iidx_fpga_run_prog(HANDLE handle) +{ + /* Run the fpga prog */ + struct ezusb_iidx_msg_interrupt_write_packet int_write; + memset(&int_write, 0, sizeof(struct ezusb_iidx_msg_interrupt_write_packet)); + + int_write.fpga_run = 1; + + if (!ezusb_iidx_interrupt_write(handle, &int_write)) { + log_warning("Run fpga prog failed"); + return false; + } + + return true; +} + +bool ezusb_iidx_fpga_v1_init(HANDLE handle, const void* buffer, uint16_t size) +{ + if (!ezusb_iidx_fpga_v1_reset(handle)) { + return false; + } + + Sleep(10); + + if (!ezusb_iidx_fpga_v1_write(handle, buffer, size)) { + return false; + } + + Sleep(10); + + if (!ezusb_iidx_fpga_run_prog(handle)) { + return false; + } + + return true; +} + +bool ezusb_iidx_fpga_v2_init(HANDLE handle, const void* buffer, uint16_t size) +{ + if (!ezusb_iidx_fpga_v2_reset(handle)) { + return false; + } + + Sleep(10); + + if (!ezusb_iidx_fpga_v2_write(handle, buffer, size)) { + return false; + } + + Sleep(10); + + if (!ezusb_iidx_fpga_run_prog(handle)) { + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx/fpga.h b/src/main/ezusb-iidx/fpga.h new file mode 100644 index 0000000..2fc86cc --- /dev/null +++ b/src/main/ezusb-iidx/fpga.h @@ -0,0 +1,12 @@ +#ifndef EZUSB_IIDX_FPGA_H +#define EZUSB_IIDX_FPGA_H + +#include +#include +#include + +bool ezusb_iidx_fpga_v1_init(HANDLE handle, const void* buffer, uint16_t size); + +bool ezusb_iidx_fpga_v2_init(HANDLE handle, const void* buffer, uint16_t size); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/msg.h b/src/main/ezusb-iidx/msg.h new file mode 100644 index 0000000..fa50828 --- /dev/null +++ b/src/main/ezusb-iidx/msg.h @@ -0,0 +1,108 @@ +#ifndef EZUSB_IIDX_MSG_H +#define EZUSB_IIDX_MSG_H + +#include +#include + +#define EZUSB_PAGESIZE 62 + +enum ezusb_iidx_msg_pipe { + EZUSB_IIDX_MSG_PIPE_INTERRUPT_OUT = 0, + EZUSB_IIDX_MSG_PIPE_INTERRUPT_IN = 1, + EZUSB_IIDX_MSG_PIPE_BULK_OUT = 2, + EZUSB_IIDX_MSG_PIPE_BULK_IN = 3 +}; + +enum ezusb_iidx_msg_node { + EZUSB_IIDX_MSG_NODE_NONE = 0x00, + EZUSB_IIDX_MSG_NODE_SECURITY_PLUG = 0x01, + EZUSB_IIDX_MSG_NODE_EEPROM = 0x02, + EZUSB_IIDX_MSG_NODE_SERIAL = 0x04, + EZUSB_IIDX_MSG_NODE_FPGA_V2 = 0x04, + EZUSB_IIDX_MSG_NODE_16SEG = 0x05, + EZUSB_IIDX_MSG_NODE_COIN = 0x09, + EZUSB_IIDX_MSG_NODE_FPGA_V1 = 0x10, + EZUSB_IIDX_MSG_NODE_WDT = 0x0C, + EZUSB_IIDX_MSG_NODE_SRAM = 0x40, + EZUSB_IIDX_MSG_NODE_SECURITY_MEM = 0xFE, +}; + +struct ezusb_iidx_msg_bulk_packet { + uint8_t node; + uint8_t page; + uint8_t payload[EZUSB_PAGESIZE]; +}; + +struct ezusb_iidx_msg_interrupt_write_packet { + uint16_t deck_lights; + uint8_t node; + uint8_t cmd; + uint8_t cmd_detail[2]; + uint8_t panel_lights; + uint8_t unk0; + uint8_t top_lamps; + uint8_t top_neons; + /* Ensure this is always enabled (1) after flashing the fpga prog. + Otherwise, all data coming from the fpga is never updated */ + uint8_t fpga_run; + uint8_t unk2; + uint8_t unk3; + uint8_t unk4; + uint8_t unk5; + uint8_t unk6; +}; + +struct ezusb_iidx_msg_interrupt_read_packet { + /* + Dip switches somewhere here? + 0: Not used + 1: Not used + 2: Not used + 3: Not used + 4: Not used + 5: Not used + 6: usb mute? + 7: Not used + + 8: P1_1 + 9: P1_2 + 10: P1_3 + 11: P1_4 + 12: P1_5 + 13: P1_6 + 14: P1_7 + 15: P2_1 + + 16: P2_2 + 17: P2_3 + 18: P2_4 + 19: P2_5 + 20: P2_6 + 21: P2_7 + 22: Coin mech + 23: Not used + 24: P1_Start + + 25: P2_Start + 26: VEFX + 27: Effector + 28: Test + 29: Service + 30: unknown/not used + 31: coin mode state: this needs to be set according to the currently + active coin node (coin mode1 -> 0, coin mode 2 -> 1) + */ + uint32_t inverted_pad; + uint8_t status; + uint8_t unk0; + uint8_t unk1; + uint8_t p2_turntable; + uint8_t p1_turntable; + uint8_t seq_no; + uint8_t fpga2_check_flag_unkn; + uint8_t fpga_write_ready; + uint8_t serial_io_busy_flag; + uint8_t sliders[3]; +}; + +#endif diff --git a/src/main/ezusb-iidx/secmem-cmd.h b/src/main/ezusb-iidx/secmem-cmd.h new file mode 100644 index 0000000..7535033 --- /dev/null +++ b/src/main/ezusb-iidx/secmem-cmd.h @@ -0,0 +1,18 @@ +#ifndef EZUSB_IIDX_SECMEM_CMD_H +#define EZUSB_IIDX_SECMEM_CMD_H + +enum ezusb_iidx_secmem_command { + EZUSB_IIDX_SECMEM_CMD_WRITE = 0x00, +}; + +enum ezusb_iidx_secmem_command_status_v1 { + EZUSB_IIDX_SECMEM_CMD_STATUS_V1_WRITE_OK = 0x60, + EZUSB_IIDX_SECMEM_CMD_STATUS_V1_FAULT = 0xFE, +}; + +enum ezusb_iidx_secmem_command_status_v2 { + EZUSB_IIDX_SECMEM_CMD_STATUS_V2_WRITE_OK = 0x11, + EZUSB_IIDX_SECMEM_CMD_STATUS_V2_FAULT = 0xFE, +}; + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/secplug-cmd.h b/src/main/ezusb-iidx/secplug-cmd.h new file mode 100644 index 0000000..02f1470 --- /dev/null +++ b/src/main/ezusb-iidx/secplug-cmd.h @@ -0,0 +1,44 @@ +#ifndef EZUSB_IIDX_SECPLUG_CMD_H +#define EZUSB_IIDX_SECPLUG_CMD_H + +enum ezusb_iidx_secplug_dongle_slot { + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_BLACK = 0x00, + EZUSB_IIDX_SECPLUG_DONGLE_SLOT_WHITE = 0x01, +}; + +enum ezusb_iidx_secplug_dongle_memory { + EZUSB_IIDX_SECPLUG_DONGLE_MEM_ROM = 0x00, + EZUSB_IIDX_SECPLUG_DONGLE_MEM_DATA = 0x01 +}; + +enum ezusb_iidx_secplug_command_v1 { + EZUSB_IIDX_SECPLUG_CMD_V1_READ_ROM = 0x01, + EZUSB_IIDX_SECPLUG_CMD_V1_READ_DATA = 0x02, + EZUSB_IIDX_SECPLUG_CMD_V1_SECURITY_CONVERSION = 0x03, + EZUSB_IIDX_SECPLUG_CMD_V1_SELECT_BLACK_DONGLE = 0x04, + EZUSB_IIDX_SECPLUG_CMD_V1_SELECT_WHITE_DONGLE = 0x05 +}; + +enum ezusb_iidx_secplug_command_status_v1 { + EZUSB_IIDX_SECPLUG_CMD_STATUS_V1_OK = 0x00, + EZUSB_IIDX_SECPLUG_CMD_STATUS_V1_FAULT = 0xFE +}; + +enum ezusb_iidx_secplug_command_v2 { + EZUSB_IIDX_SECPLUG_CMD_V2_INIT = 0x01, + EZUSB_IIDX_SECPLUG_CMD_V2_READ_DATA = 0x02, + EZUSB_IIDX_SECPLUG_CMD_V2_READ_ROM = 0x06, + EZUSB_IIDX_SECPLUG_CMD_V2_SELECT_WHITE_DONGLE_2 = 0x07, + EZUSB_IIDX_SECPLUG_CMD_V2_SELECT_BLACK_DONGLE = 0x08, + EZUSB_IIDX_SECPLUG_CMD_V2_SELECT_WHITE_DONGLE = 0x09 +}; + +enum ezusb_iidx_secplug_command_status_v2 { + EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_OK = 0x12, + EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_DATA_OK = 0x13, + EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_ROM_OK = 0x15, + EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_SECURITY_SEL_OK = 0x16, + EZUSB_IIDX_SECPLUG_CMD_STATUS_V2_FAIL = 0xFE +}; + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/seg16-cmd.h b/src/main/ezusb-iidx/seg16-cmd.h new file mode 100644 index 0000000..fe93c3a --- /dev/null +++ b/src/main/ezusb-iidx/seg16-cmd.h @@ -0,0 +1,14 @@ +#ifndef EZUSB_IIDX_SEG16_CMD_H +#define EZUSB_IIDX_SEG16_CMD_H + +enum ezusb_iidx_16seg_command { + EZUSB_IIDX_16SEG_CMD_WRITE = 0x03, +}; + +enum ezusb_iidx_16seg_command_status { + EZUSB_IIDX_16SEG_CMD_STATUS_OK = 0x00, + EZUSB_IIDX_16SEG_CMD_STATUS_FAULT = 0xFE +}; + + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/serial-cmd.h b/src/main/ezusb-iidx/serial-cmd.h new file mode 100644 index 0000000..cc484fb --- /dev/null +++ b/src/main/ezusb-iidx/serial-cmd.h @@ -0,0 +1,16 @@ +#ifndef EZUSB_IIDX_SERIAL_CMD_H +#define EZUSB_IIDX_SERIAL_CMD_H + +enum ezusb_iidx_serial_command { + EZUSB_IIDX_SERIAL_CMD_READ_BUFFER = 0x02, + EZUSB_IIDX_SERIAL_CMD_WRITE_BUFFER = 0x03, + EZUSB_IIDX_SERIAL_CMD_CLEAR_READ_BUFFER = 0x04, + EZUSB_IIDX_SERIAL_CMD_CLEAR_WRITE_BUFFER = 0x05 +}; + +enum ezusb_iidx_serial_command_status { + EZUSB_IIDX_SERIAL_CMD_STATUS_OK = 0x00, + EZUSB_IIDX_SERIAL_CMD_STATUS_FAULT = 0xFE +}; + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/sram-cmd.h b/src/main/ezusb-iidx/sram-cmd.h new file mode 100644 index 0000000..fdbd48c --- /dev/null +++ b/src/main/ezusb-iidx/sram-cmd.h @@ -0,0 +1,10 @@ +#ifndef EZUSB_IIDX_SRAM_CMD_H +#define EZUSB_IIDX_SRAM_CMD_H + +enum ezusb_iidx_sram_command { + EZUSB_IIDX_SRAM_CMD_WRITE = 0x03, + EZUSB_IIDX_SRAM_CMD_READ = 0x02, + EZUSB_IIDX_SRAM_CMD_DONE = 0x04 +}; + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/sram.c b/src/main/ezusb-iidx/sram.c new file mode 100644 index 0000000..df0a44a --- /dev/null +++ b/src/main/ezusb-iidx/sram.c @@ -0,0 +1,116 @@ +#define LOG_MODULE "ezusb-iidx-sram" + +#include "ezusb-iidx/sram.h" + +#include "util/log.h" + +#include "ezusb-iidx.h" +#include "sram-cmd.h" + +static bool ezusb_iidx_sram_write_page(HANDLE handle, uint8_t node, + uint8_t page, const void* buffer, uint16_t size) +{ + struct ezusb_iidx_msg_bulk_packet packet; + + memset(&packet.payload, 0, 62); + + packet.node = node; + packet.page = page; + + if (size > 62) { + size = 62; + } + + //log_misc("Writing sram page %d, size %d", page, size); + + memcpy(packet.payload, buffer, size); + + return ezusb_iidx_bulk_write(handle, &packet); +} + +static bool ezusb_iidx_sram_read_page(HANDLE handle, uint8_t node) +{ + struct ezusb_iidx_msg_bulk_packet packet; + + memset(&packet, 0, sizeof(struct ezusb_iidx_msg_bulk_packet)); + + return ezusb_iidx_bulk_read(handle, &packet); +} + +static bool ezusb_iidx_sram_write(HANDLE handle, uint8_t node, + const void* buffer, uint16_t size, uint8_t* pages_written) +{ + uint8_t page; + uint16_t offset; + + page = 0; + offset = 0; + *pages_written = 0; + + /* Write in ezusb page sizes */ + while (offset < size) { + if ( !ezusb_iidx_sram_write_page(handle, node, page++, + buffer + offset, size - offset)) { + return false; + } + + offset += 62; + + /* if we are too fast, the downloads might fail */ + Sleep(10); + (*pages_written)++; + } + + return true; +} + +static bool ezusb_iidx_sram_read(HANDLE handle, uint8_t node, uint8_t pages) +{ + for (uint8_t i = 0; i < pages; i++) { + if (!ezusb_iidx_sram_read_page(handle, node)) { + return false; + } + + /* if we are too fast, this might fail */ + Sleep(10); + } + + return true; +} + +bool ezusb_iidx_sram_init(HANDLE handle, const void* buffer, uint16_t size) +{ + uint8_t pages_written; + struct ezusb_iidx_msg_interrupt_read_packet int_read_data; + + log_info("Init, size %d", size); + + ezusb_iidx_execute_cmd(handle, EZUSB_IIDX_MSG_NODE_SRAM, + EZUSB_IIDX_SRAM_CMD_WRITE, 1, 0, &int_read_data); + + log_info("Writing..."); + + if ( !ezusb_iidx_sram_write(handle, EZUSB_IIDX_MSG_NODE_SRAM, + buffer, size, &pages_written)) { + log_warning("Writing sram failed, pages written so far: %d", + pages_written); + return false; + } + + log_info("Data written, pages: %d", pages_written); + log_info("Reading back..."); + + ezusb_iidx_execute_cmd(handle, EZUSB_IIDX_MSG_NODE_SRAM, + EZUSB_IIDX_SRAM_CMD_READ, 1, 0, &int_read_data); + + if (!ezusb_iidx_sram_read(handle, EZUSB_IIDX_MSG_NODE_SRAM, + pages_written)) { + log_warning("Reading back failed"); + return false; + } + + ezusb_iidx_execute_cmd(handle, EZUSB_IIDX_MSG_NODE_SRAM, + EZUSB_IIDX_SRAM_CMD_DONE, 1, 0, &int_read_data); + + return true; +} \ No newline at end of file diff --git a/src/main/ezusb-iidx/sram.h b/src/main/ezusb-iidx/sram.h new file mode 100644 index 0000000..626ee47 --- /dev/null +++ b/src/main/ezusb-iidx/sram.h @@ -0,0 +1,10 @@ +#ifndef EZUSB_IIDX_SRAM_H +#define EZUSB_IIDX_SRAM_H + +#include +#include +#include + +bool ezusb_iidx_sram_init(HANDLE handle, const void* buffer, uint16_t size); + +#endif \ No newline at end of file diff --git a/src/main/ezusb-iidx/wdt-cmd.h b/src/main/ezusb-iidx/wdt-cmd.h new file mode 100644 index 0000000..4de3d51 --- /dev/null +++ b/src/main/ezusb-iidx/wdt-cmd.h @@ -0,0 +1,13 @@ +#ifndef EZUSB_IIDX_WDT_CMD_H +#define EZUSB_IIDX_WDT_CMD_H + +enum ezusb_iidx_wdt_command { + EZUSB_IIDX_WDT_CMD_INIT = 0x3C +}; + +enum ezusb_iidx_wdt_command_status { + EZUSB_IIDX_WDT_CMD_STATUS_OK = 0x00, + EZUSB_IIDX_WDT_CMD_STATUS_FAULT = 0xFE +}; + +#endif \ No newline at end of file diff --git a/src/main/ezusb-tool/Module.mk b/src/main/ezusb-tool/Module.mk new file mode 100644 index 0000000..349bc08 --- /dev/null +++ b/src/main/ezusb-tool/Module.mk @@ -0,0 +1,11 @@ +exes += ezusb-tool + +ldflags_ezusb-tool := \ + -lsetupapi \ + +libs_ezusb-tool := \ + ezusb \ + util \ + +src_ezusb-tool := \ + main.c \ diff --git a/src/main/ezusb-tool/main.c b/src/main/ezusb-tool/main.c new file mode 100644 index 0000000..31401ba --- /dev/null +++ b/src/main/ezusb-tool/main.c @@ -0,0 +1,123 @@ +#include + +#include +#include + +#include "ezusb/util.h" +#include "ezusb/ezusb.h" + +#include "util/log.h" + +static int info() +{ + HANDLE handle; + struct ezusb_ident ident; + + log_misc("Opening device %s", EZUSB_DEVICE_PATH); + + handle = ezusb_open(EZUSB_DEVICE_PATH); + + if (handle == INVALID_HANDLE_VALUE) { + log_fatal("Could not open device %s", EZUSB_DEVICE_PATH); + return -3; + } + + if (!ezusb_get_ident(handle, &ident)) { + log_fatal("Getting ident information failed"); + return -4; + } + + ezusb_close(handle); + + log_info("Ident information format: ,,"); + printf("%04X,%04X,%s\n", ident.vid, ident.pid, ident.name); + + return 0; +} + +static int flash(const char* fw_path) +{ + HANDLE handle; + struct ezusb_firmware* fw; + + fw = ezusb_firmware_load(fw_path); + + if (!fw) { + log_fatal("Loading firmware from file '%s' failed", fw_path); + return -5; + } + + log_misc("Loaded firmware, crc 0x%X, segments %d", fw->crc, + fw->segment_count); + + if (ezusb_firmware_crc(fw) != fw->crc) { + log_fatal("Firmware CRC check failed"); + return -6; + } + + log_misc("Opening device %s", EZUSB_DEVICE_PATH); + + handle = ezusb_open(EZUSB_DEVICE_PATH); + + if (handle == INVALID_HANDLE_VALUE) { + ezusb_firmware_free(fw); + log_fatal("Could not open device %s", EZUSB_DEVICE_PATH); + return -7; + } + + log_misc("Flashing firmware..."); + + if (!ezusb_download_firmware(handle, fw)) { + log_fatal("Flashing firmware failed"); + return -8; + } else { + log_info("Flashing firmware successful"); + } + + ezusb_close(handle); + ezusb_firmware_free(fw); + + return 0; +} + +static void usage(const char* argv0) +{ + printf("ezusb-tool for EZUSB hardware, e.g. IIDX C02 IO, build " + __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: %s [cmd] ...\n" + "Available commands:\n" + " info: Get basic information (vid, pid, name) of a connected " + "device\n" + " flash: Flash a firmware binary\n", argv0); +} + +int main(int argc, char** argv) +{ + int arg_pos; + + if (argc < 2) { + usage(argv[0]); + return -1; + } + + arg_pos = 1; + + log_to_writer(log_writer_stderr, NULL); + + if (!strcmp(argv[arg_pos], "info")) { + return info(); + } else if (!strcmp(argv[arg_pos], "flash")) { + arg_pos++; + + if (arg_pos >= argc) { + printf("Usage: flash [fw_path]\n" + " fw_path: Path to firmware binary file to flash\n"); + return -1; + } + + return flash(argv[arg_pos]); + } else { + usage(argv[0]); + return -1; + } +} \ No newline at end of file diff --git a/src/main/ezusb/Module.mk b/src/main/ezusb/Module.mk new file mode 100644 index 0000000..661d84b --- /dev/null +++ b/src/main/ezusb/Module.mk @@ -0,0 +1,7 @@ +libs += ezusb + +libs_ezusb := \ + +src_ezusb := \ + ezusb.c \ + util.c \ diff --git a/src/main/ezusb/ezusb.c b/src/main/ezusb/ezusb.c new file mode 100644 index 0000000..64190ea --- /dev/null +++ b/src/main/ezusb/ezusb.c @@ -0,0 +1,122 @@ +#include +#include + +#include "ezusb/ezusb.h" +#include "ezusb/ezusbsys2.h" + +#include "util/log.h" +#include "util/str.h" + +static bool ezusb_reset(HANDLE handle, bool hold) +{ + VENDOR_OR_CLASS_REQUEST_CONTROL req; + DWORD outpkt; + + /* fml...I can't believe these values are correct. according to + the documentation they are wrong D: */ + + req.direction = 0xA0; + + if (hold) { + req.requestType = 0x00; + } else { + req.requestType = 0x84; + } + + req.recepient = 0x92; + + req.requestTypeReservedBits = 0x7F; + req.request = 0x00; + req.value = 0x0001; + + if (hold) { + req.index = 0x0100; + } else { + /* release */ + req.index = 0x0000; + } + + if (!DeviceIoControl(handle, IOCTL_Ezusb_VENDOR_REQUEST, &req, + sizeof(req), &req, sizeof(req), &outpkt, NULL)) { + return false; + } + + return true; +} + +static bool ezusb_write_firmware(HANDLE handle, const void* buffer, + uint16_t ram_offset, uint32_t size) +{ + ANCHOR_DOWNLOAD_CONTROL req; + DWORD outpkt; + + req.Offset = ram_offset; + + if (!DeviceIoControl(handle, IOCTL_EZUSB_ANCHOR_DOWNLOAD, &req, + sizeof(req), (void*) buffer, size, &outpkt, NULL)) { + return false; + } + + return true; +} + +HANDLE ezusb_open(const char* path) +{ + log_assert(path); + + return CreateFileA(path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, 0, NULL); +} + +bool ezusb_get_ident(HANDLE handle, struct ezusb_ident* ident) +{ + USB_DEVICE_DESCRIPTOR desc; + DWORD outpkt; + + log_assert(handle); + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(ident); + + if (!DeviceIoControl(handle, IOCTL_Ezusb_GET_DEVICE_DESCRIPTOR, &desc, + sizeof(desc), &desc, sizeof(desc), &outpkt, NULL)) { + return false; + } + + memset(ident->name, 0, sizeof(ident->name)); + ident->vid = desc.idVendor; + ident->pid = desc.idProduct; + + return true; +} + +bool ezusb_download_firmware(HANDLE handle, struct ezusb_firmware* fw) +{ + log_assert(handle); + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(fw); + + if (!ezusb_reset(handle, true)) { + return false; + } + + for (uint16_t i = 0; i < fw->segment_count; i++) { + if (!ezusb_write_firmware(handle, fw->segments[i]->data, + fw->segments[i]->offset, fw->segments[i]->size)) { + return false; + } + } + + if (!ezusb_reset(handle, false)) { + return false; + } + + return true; +} + +void ezusb_close(HANDLE handle) +{ + log_assert(handle); + log_assert(handle != INVALID_HANDLE_VALUE); + + CloseHandle(handle); +} \ No newline at end of file diff --git a/src/main/ezusb/ezusb.h b/src/main/ezusb/ezusb.h new file mode 100644 index 0000000..b1a9231 --- /dev/null +++ b/src/main/ezusb/ezusb.h @@ -0,0 +1,86 @@ +/** + * Module for fundamental communication with a legacy Ezusb board, e.g. + * IIDX C02 IO. + * + * This module does not implement any sort of higher level protocol which is + * required by the games to exchange data with the device, e.g. getting input + * states of keys and setting light outputs. Further modules are utilizing this + * base layer to implement higher level communication. + * + * Note: This module requires the old "cyusb" driver which is NOT compatible + * with anything newer than WinXP and does not support 64-bit systems. The newer + * "cyusb3" driver does NOT work with this. + * + * @author icex2 + */ +#ifndef EZUSB_H +#define EZUSB_H + +#include +#include + +#include + +#include "ezusb/util.h" + +static const GUID EZUSB_GUID = {0xAE18AA60, 0x7F6A, 0x11D4, + {0x97, 0xDD, 0x00, 0x01, 0x02, 0x29, 0xB9, 0x59}}; + +/* Used by IIDX instead of scanning for the device */ +/* static const char* EZUSB_DEFAULT_PATHS[6] = { + "\\\\.\\Ezusb-0", + "\\\\.\\Ezusb-1", + "\\\\.\\Ezusb-2", + "\\\\.\\Ezusb-3", + "\\\\.\\Ezusb-4", + "\\\\.\\Ezusb-5" +}; */ + +#define EZUSB_DEVICE_PATH "\\\\.\\Ezusb-0" + +/** + * Open a connected Ezusb device. Using the old "ezusb" driver on WinXP, + * the driver exposes the device to one of the following paths: + * \\\\.\\Ezusb-0 + * \\\\.\\Ezusb-1 + * \\\\.\\Ezusb-2 + * \\\\.\\Ezusb-3 + * \\\\.\\Ezusb-4 + * \\\\.\\Ezusb-5 + * + * Where \\\\.\\Ezusb-X is used if slot X-1 is still occupied, e.g. when + * flashing the device and after a usb re-enumerate. + * + * @param device_path Path to the Ezusb device. + * @return A valid handle to the device on success, INVALID_HANDLE_VALUE on + * error. + */ +HANDLE ezusb_open(const char* path); + +/** + * Get identifier information of the device. + * + * @param handle Valid device handle. + * @param ident Pointer to memory to write the identifier information to. + * @return True if successful and identifier information was written to the + * the struct, false on error. + */ +bool ezusb_get_ident(HANDLE handle, struct ezusb_ident* ident); + +/** + * Download a firmware to the ezusb device. + * + * @param handle Valid device handle. + * @param fw Firmware to write to the target ezusb device. + * @return True on success, false on failure. + */ +bool ezusb_download_firmware(HANDLE handle, struct ezusb_firmware* fw); + +/** + * Close an opened ezusb device handle. + * + * @param handle Valid handle to close. + */ +void ezusb_close(HANDLE handle); + +#endif \ No newline at end of file diff --git a/src/main/ezusb/ezusbsys.h b/src/main/ezusb/ezusbsys.h new file mode 100644 index 0000000..f2cebdf --- /dev/null +++ b/src/main/ezusb/ezusbsys.h @@ -0,0 +1,1148 @@ +////////////////////////////////////////////////////////////////////// +// +// File: ezusbsys.h +// $Archive: /USB/Drivers/ezusbdrv/ezusbsys.h $ +// +// Purpose: +// Header file for the Ezusb USB Device Driver +// +// Environment: +// kernel mode +// +// $Author: Mdn $ +// +// $History: ezusbsys.h $ +// +// ***************** Version 5 ***************** +// User: Mdn Date: 10/06/00 Time: 10:08a +// Updated in $/USB/Drivers/ezusbdrv +// Added IOCTL_EZUSB_SET_FEATURE +// +// ***************** Version 4 ***************** +// User: Mdn Date: 8/17/00 Time: 9:46a +// Updated in $/USB/Drivers/ezusbdrv +// added a generic get descriptor function. +// +// ***************** Version 3 ***************** +// User: Mdn Date: 8/04/00 Time: 11:40a +// Updated in $/USB/Drivers/ezmon +// added support for monitor download to FX2 +// +// ***************** Version 2 ***************** +// User: Mdn Date: 7/21/00 Time: 4:27p +// Updated in $/USB/Drivers/ezusbdrv +// Added a Started flag to the device extension. This flag gets set after +// the device has successfully Started. It is used to prevent a potential +// race condition where an application could get a handle (with +// CreateFile()) before the device is completely enumerated. +// +// ***************** Version 1 ***************** +// User: Tpm Date: 6/09/00 Time: 6:32p +// Created in $/USB/Drivers/ezusbdrv +// +// ***************** Version 30 ***************** +// User: Tpm Date: 3/14/00 Time: 4:14p +// Updated in $/EzUsb/Examples/Unsupported/EzMr +// Added commets: +// DEVIOCTL.H is a Microsoft header file. +// DEVIOCTL.H is normally obtained by installing the Windows DDK. +// +// ***************** Version 29 ***************** +// User: Markm Date: 5/18/99 Time: 3:37p +// Updated in $/EzUsb/Drivers/ezusbdrv +// +// ***************** Version 28 ***************** +// User: Markm Date: 2/23/99 Time: 9:50a +// Updated in $/EzUsb/Drivers/ezusbdrv +// Driver now supports ISO IN streaming with a path to user mode. +// +// ***************** Version 27 ***************** +// User: Markm Date: 2/10/99 Time: 3:32p +// Updated in $/EzUsb/Drivers/ezusbdrv +// removed lots of unused protoypes and structures. Added ring buffer +// support functions. +// +// ***************** Version 26 ***************** +// User: Markm Date: 2/01/99 Time: 11:57a +// Updated in $/EzUsb/Drivers/ezusbdrv +// Added preliminary support for ISO streaming. +// +// ***************** Version 25 ***************** +// User: Tpm Date: 10/19/98 Time: 3:45p +// Updated in $/EzUsb/Examples/CTst +// Fix RMA#1 bug: side B fails renum. +// +// ***************** Version 24 ***************** +// User: Tpm Date: 10/01/98 Time: 11:51p +// Updated in $/EzUsb/Examples/Unsupported/EzMr +// Apply Tim's Comments. +// +// ***************** Version 23 ***************** +// User: Tpm Date: 6/26/98 Time: 10:27a +// Updated in $/EzUsb/Examples/EzMr +// Set Version. +// +// ***************** Version 22 ***************** +// User: Tpm Date: 6/26/98 Time: 5:41a +// Updated in $/EzUsb/Examples/EzMr +// Pre-Build. +// +// ***************** Version 21 ***************** +// User: Tpm Date: 6/26/98 Time: 4:01a +// Updated in $/EzUsb/Examples/CTst +// Clean Build. +// +// ***************** Version 20 ***************** +// User: Tpm Date: 6/26/98 Time: 2:41a +// Updated in $/EzUsb/Examples/EzMr +// Clean Build. +// +// ***************** Version 19 ***************** +// User: Tpm Date: 6/25/98 Time: 1:09p +// Updated in $/EzUsb/EzMr +// Add Headers. +// +// ***************** Version 18 ***************** +// User: Tpm Date: 6/25/98 Time: 10:50a +// Updated in $/EzUsb/EzMr +// REL1.1. +// +// ***************** Version 17 ***************** +// User: Markm Date: 4/09/98 Time: 3:01p +// Updated in $/EZUSB/ezusb driver +// Added stuff to support download of Intel Hex records. +// +// ***************** Version 16 ***************** +// User: Markm Date: 4/07/98 Time: 1:53p +// Updated in $/EZUSB/ezusb driver +// Added IOCTL_EZUSB_GET_DRIVER_VERSION +// +// ***************** Version 15 ***************** +// User: Markm Date: 4/06/98 Time: 4:27p +// Updated in $/EZUSB/ezusb driver +// Modified ISO transfer code. +// * Transfer descriptors for the ISO transfer are now sent up to the +// caller along with the actual data, so the caller can get the status of +// the transfer on a packet-by-packet basis. +// * Disabled default values. Caller must specify all fields in the ISO +// control structure. +// * Corrected bug where the Stream and Transfer objects weren't being +// freed. +// +// Added some code to measure the latency of a bulk transfer. +// +// ***************** Version 14 ***************** +// User: Markm Date: 3/19/98 Time: 10:13a +// Updated in $/EZUSB/ezusb driver +// Added IOCTL_EZUSB_ANCHOR_DOWNLOAD to support A0 loads to a specific +// memory offset. +// +// ***************** Version 13 ***************** +// User: Markm Date: 2/26/98 Time: 4:05p +// Updated in $/EZUSB/ezusb driver +// Added protoypes for anchor download and 8051 reset functions. +// Added firmware structure definition. +// Added some EZ-USB register defines. +// +// ***************** Version 11 ***************** +// User: Markm Date: 2/11/98 Time: 9:51a +// Updated in $/EZUSB/ezusb driver +// Added an open file handle count to the device extension. +// +// ***************** Version 10 ***************** +// User: Markm Date: 2/02/98 Time: 3:36p +// Updated in $/EZUSB/ezusb driver +// Added protypes for new functions +// +// ***************** Version 9 ***************** +// User: Markm Date: 1/27/98 Time: 11:37a +// Updated in $/EZUSB/ezusb driver +// Added members to the ISO stream object to allow for user specified +// transfer parameters. +// +// ***************** Version 8 ***************** +// User: Markm Date: 1/22/98 Time: 11:52a +// Updated in $/EZUSB/ezusb driver +// removed unused code. +// added IOCTL's for ISO loopback/read/write +// +// ***************** Version 7 ***************** +// User: Markm Date: 1/18/98 Time: 3:18p +// Updated in $/EZUSB/ezusb driver +// Added new IOCTL's. Added members to the device extension to support +// robust device removal. +// +// ***************** Version 6 ***************** +// User: Markm Date: 1/14/98 Time: 10:30a +// Updated in $/EZUSB/ezusb driver +// Added IOCTL's for handling bulk transfers. +// +// ***************** Version 5 ***************** +// User: Markm Date: 1/02/98 Time: 1:41p +// Updated in $/EZUSB/ezusb driver +// Added support for setting the interface, preliminary code for naming +// pipes, get string descriptor +// +// ***************** Version 4 ***************** +// User: Markm Date: 11/18/97 Time: 3:21p +// Updated in $/EZUSB/ezusb driver +// added abort pipe IOCTL +// +// ***************** Version 3 ***************** +// User: Markm Date: 11/14/97 Time: 4:31p +// Updated in $/EZUSB/ezusb driver +// added code to experiment wth different methods of switiching +// interfaces. +// +// ***************** Version 2 ***************** +// User: Markm Date: 11/07/97 Time: 1:21p +// Updated in $/EZUSB/ezusb driver +// Added Reset Pipe IOCTL +// +// Copyright (c) 1997 Anchor Chips, Inc. May not be reproduced without +// permission. See the license agreement for more details. +// +////////////////////////////////////////////////////////////////////// + +// +// Vendor specific request code for Anchor Upload/Download +// +// This one is implemented in the core +// +#define ANCHOR_LOAD_INTERNAL 0xA0 + +// +// These commands are not implemented in the core. Requires firmware +// +#define ANCHOR_LOAD_EXTERNAL 0xA3 +#define ANCHOR_ISFX2 0xAC + +// +// This is the highest internal RAM address for the AN2131Q +// +#define MAX_INTERNAL_ADDRESS 0x1B3F + +#define INTERNAL_RAM(address) ((address <= MAX_INTERNAL_ADDRESS) ? 1 : 0) +// +// EZ-USB Control and Status Register. Bit 0 controls 8051 reset +// +#define CPUCS_REG_EZUSB 0x7F92 +#define CPUCS_REG_FX2 0xE600 + + +#ifndef _BYTE_DEFINED +#define _BYTE_DEFINED +typedef unsigned char BYTE; +#endif // !_BYTE_DEFINED + +#ifndef _WORD_DEFINED +#define _WORD_DEFINED +typedef unsigned short WORD; +#endif // !_WORD_DEFINED + +typedef struct _VENDOR_REQUEST_IN +{ + BYTE bRequest; + WORD wValue; + WORD wIndex; + WORD wLength; + BYTE direction; + BYTE bData; +} VENDOR_REQUEST_IN, *PVENDOR_REQUEST_IN; + +/////////////////////////////////////////////////////////// +// +// control structure for bulk and interrupt data transfers +// +/////////////////////////////////////////////////////////// +typedef struct _BULK_TRANSFER_CONTROL +{ + ULONG pipeNum; +} BULK_TRANSFER_CONTROL, *PBULK_TRANSFER_CONTROL; + +typedef struct _BULK_LATENCY_CONTROL +{ + ULONG bulkPipeNum; + ULONG intPipeNum; + ULONG loops; +} BULK_LATENCY_CONTROL, *PBULK_LATENCY_CONTROL; + + +/////////////////////////////////////////////////////////// +// +// control structure isochronous loopback test +// +/////////////////////////////////////////////////////////// +typedef struct _ISO_LOOPBACK_CONTROL +{ + // iso pipe to write to + ULONG outPipeNum; + + // iso pipe to read from + ULONG inPipeNum; + + // amount of data to read/write from/to the pipe each frame. If not + // specified, the MaxPacketSize of the out pipe is used. + ULONG packetSize; + +} ISO_LOOPBACK_CONTROL, *PISO_LOOPBACK_CONTROL; + +/////////////////////////////////////////////////////////// +// +// control structure for sending vendor or class specific requests +// to the control endpoint. +// +/////////////////////////////////////////////////////////// +typedef struct _VENDOR_OR_CLASS_REQUEST_CONTROL +{ + // transfer direction (0=host to device, 1=device to host) + UCHAR direction; + + // request type (1=class, 2=vendor) + UCHAR requestType; + + // recipient (0=device,1=interface,2=endpoint,3=other) + UCHAR recepient; + // + // see the USB Specification for an explanation of the + // following paramaters. + // + UCHAR requestTypeReservedBits; + UCHAR request; + USHORT value; + USHORT index; +} VENDOR_OR_CLASS_REQUEST_CONTROL, *PVENDOR_OR_CLASS_REQUEST_CONTROL; + +typedef struct _SET_FEATURE_CONTROL +{ + USHORT FeatureSelector; + USHORT Index; +} SET_FEATURE_CONTROL, *PSET_FEATURE_CONTROL; + +/////////////////////////////////////////////////////////// +// +// control structure for isochronous data transfers +// +/////////////////////////////////////////////////////////// +typedef struct _ISO_TRANSFER_CONTROL +{ + // + // pipe number to perform the ISO transfer to/from. Direction is + // implied by the pipe number. + // + ULONG PipeNum; + // + // ISO packet size. Determines how much data is transferred each + // frame. Should be less than or equal to the maxpacketsize for + // the endpoint. + // + ULONG PacketSize; + // + // Total number of ISO packets to transfer. + // + ULONG PacketCount; + // + // The following two parameters detmine how buffers are managed for + // an ISO transfer. In order to maintain an ISO stream, the driver + // must create at least 2 transfer buffers and ping pong between them. + // BufferCount determines how many buffers the driver creates to ping + // pong between. FramesPerBuffer specifies how many USB frames of data + // are transferred by each buffer. + // + ULONG FramesPerBuffer; // 10 is a good value + ULONG BufferCount; // 2 is a good value +} ISO_TRANSFER_CONTROL, *PISO_TRANSFER_CONTROL; + + +/////////////////////////////////////////////////////////// +// +// control structure for Anchor Downloads +// +/////////////////////////////////////////////////////////// +typedef struct _ANCHOR_DOWNLOAD_CONTROL +{ + WORD Offset; +} ANCHOR_DOWNLOAD_CONTROL, *PANCHOR_DOWNLOAD_CONTROL; + +#define MAX_INTEL_HEX_RECORD_LENGTH 16 + +typedef struct _INTEL_HEX_RECORD +{ + BYTE Length; + WORD Address; + BYTE Type; + BYTE Data[MAX_INTEL_HEX_RECORD_LENGTH]; +} INTEL_HEX_RECORD, *PINTEL_HEX_RECORD; + +typedef struct _SET_INTERFACE_IN +{ + UCHAR interfaceNum; + UCHAR alternateSetting; +} SET_INTERFACE_IN, *PSET_INTERFACE_IN; + +typedef struct _GET_STRING_DESCRIPTOR_IN +{ + UCHAR Index; + USHORT LanguageId; +} GET_STRING_DESCRIPTOR_IN, *PGET_STRING_DESCRIPTOR_IN; + +typedef struct _EZUSB_DRIVER_VERSION +{ + WORD MajorVersion; + WORD MinorVersion; + WORD BuildVersion; +} EZUSB_DRIVER_VERSION, *PEZUSB_DRIVER_VERSION; + +#ifdef DRIVER + +typedef struct _RING_BUFFER +{ + PUCHAR inPtr; + PUCHAR outPtr; + ULONG totalSize; + ULONG currentSize; + KSPIN_LOCK spinLock; + PUCHAR buffer; +} RING_BUFFER, *PRING_BUFFER; + +PRING_BUFFER +AllocRingBuffer( + ULONG Size + ); + +VOID +FreeRingBuffer( + PRING_BUFFER ringBuffer + ); + +ULONG +ReadRingBuffer( + PRING_BUFFER ringBuffer, + PUCHAR readBuffer, + ULONG numberOfBytesToRead + ); + +ULONG +WriteRingBuffer( + PRING_BUFFER ringBuffer, + PUCHAR writeBuffer, + ULONG numberOfBytesToWrite + ); + +typedef struct _EZUSB_FIRMWARE +{ + // tag contains a string to identify the start of the firmware + // image in the driver binary. Another utilty can then be used + // to replace the firmware image inthe driver without requiring + // a recompile + UCHAR tag[10]; + ULONG size; + UCHAR firmware[]; +} EZUSB_FIRMWARE, *PEZUSB_FIRMWARE; + +// +// this is the default number of IRP's to queue for streaming ISO +// data. +// +#define DEFAULT_ISO_BUFFER_COUNT 2 + +// +// Default number of frames of ISO data transferred by a single ISO +// URB/IRP +// +#define DEFAULT_ISO_FRAMES_PER_BUFFER 10 + +typedef struct _ISO_STREAM_OBJECT ISO_STREAM_OBJECT, *PISO_STREAM_OBJECT; + +typedef struct _ISO_TRANSFER_OBJECT +{ + ULONG Frame; + PISO_STREAM_OBJECT StreamObject; + PURB Urb; + PIRP Irp; + KEVENT Done; +} ISO_TRANSFER_OBJECT, *PISO_TRANSFER_OBJECT; + +typedef struct _ISO_STREAM_OBJECT +{ + PDEVICE_OBJECT DeviceObject; + ULONG PacketSize; + ULONG NumPackets; + PUSBD_PIPE_INFORMATION PipeInfo; + PVOID TransferBuffer; + ULONG TransferBufferLength; + PVOID IsoDescriptorBuffer; + ULONG FramesPerBuffer; + ULONG BufferCount; + ULONG PendingTransfers; + PRING_BUFFER DataRingBuffer; + PRING_BUFFER DescriptorRingBuffer; + PISO_TRANSFER_OBJECT TransferObject; +} ISO_STREAM_OBJECT, *PISO_STREAM_OBJECT; + + +#define Ezusb_NAME_MAX 64 + + + + +// +// This is an unused structure in this driver, but is provided here +// so when you extend the driver to deal with USB pipes, you may wish +// to use this structure as an example or model. +// +typedef struct _EZUSB_PIPE { + ULONG Mode; + ULONG Option; + ULONG Param1; + ULONG Param2; + WCHAR Name[Ezusb_NAME_MAX]; + PUSBD_PIPE_INFORMATION PipeInfo; +} EZUSB_PIPE, *PEZUSB_PIPE; + + +/* +// The interface number on this device that this driver expects to use +// This would be in the bInterfaceNumber field of the Interface Descriptor, hence +// this device driver would need to know this value. +*/ +#define SAMPLE_INTERFACE_NBR 0x00 + + +// +// A structure representing the instance information associated with +// this particular device. +// +typedef struct _DEVICE_EXTENSION +{ + + // physical device object + PDEVICE_OBJECT PhysicalDeviceObject; + + // Device object we call when submitting Urbs/Irps to the USB stack + PDEVICE_OBJECT StackDeviceObject; + + // Indicates that we have recieved a STOP message + BOOLEAN Stopped; + + // Indicates that we are enumerated and configured. Used to hold + // of requests until we are ready for them + BOOLEAN Started; + + // Indicates the device needs to be cleaned up (ie., some configuration + // has occurred and needs to be torn down). + BOOLEAN NeedCleanup; + + // configuration handle for the configuration the + // device is currently in + USBD_CONFIGURATION_HANDLE ConfigurationHandle; + + // ptr to the USB device descriptor + // for this device + PUSB_DEVICE_DESCRIPTOR DeviceDescriptor; + + // we support up to one interface + PUSBD_INTERFACE_INFORMATION Interface; + + // the number of device handles currently open to the device object. + // Gets incremented by Create and decremented by Close + ULONG OpenHandles; + + // Name buffer for our named Functional device object link + WCHAR DeviceLinkNameBuffer[Ezusb_NAME_MAX]; + + // This member is used to store the URB status of the + // most recently failed URB. If a USB transfer fails, a caller + // can use IOCTL_EZUSB_GET_LAST_ERROR to retrieve this value. + // There's only room for one, so you better get it quick (or at + // least before the next URB failure occurs). + USBD_STATUS LastFailedUrbStatus; + + // use counter for the device. Gets incremented when the driver receives + // a request and gets decremented when a request s completed. + LONG usage; + + // this ev gets set when it is ok to remove the device + KEVENT evRemove; + + // TRUE if we're trying to remove this device + BOOLEAN removing; + + BOOLEAN StopIsoStream; + + PRING_BUFFER DataRingBuffer; + PRING_BUFFER DescriptorRingBuffer; + +} DEVICE_EXTENSION, *PDEVICE_EXTENSION; + + +#if DBG + +#define Ezusb_KdPrint(_x_) DbgPrint("Ezusb.SYS: "); \ + DbgPrint _x_ ; +#define TRAP() DbgBreakPoint() +#else +#define Ezusb_KdPrint(_x_) +#define TRAP() +#endif + + +NTSTATUS +Ezusb_Dispatch( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + +VOID +Ezusb_Unload( + IN PDRIVER_OBJECT DriverObject + ); + +NTSTATUS +Ezusb_StartDevice( + IN PDEVICE_OBJECT DeviceObject + ); + +NTSTATUS +Ezusb_StopDevice( + IN PDEVICE_OBJECT DeviceObject + ); + +NTSTATUS +Ezusb_RemoveDevice( + IN PDEVICE_OBJECT DeviceObject + ); + +NTSTATUS +Ezusb_CallUSBD( + IN PDEVICE_OBJECT DeviceObject, + IN PURB Urb + ); + +NTSTATUS +Ezusb_PnPAddDevice( + IN PDRIVER_OBJECT DriverObject, + IN PDEVICE_OBJECT PhysicalDeviceObject + ); + +NTSTATUS +Ezusb_CreateDeviceObject( + IN PDRIVER_OBJECT DriverObject, + IN PDEVICE_OBJECT *DeviceObject, + LONG Instance + ); + +NTSTATUS +Ezusb_ConfigureDevice( + IN PDEVICE_OBJECT DeviceObject + ); + +NTSTATUS +Ezusb_Create( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + +NTSTATUS +Ezusb_Close( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + +NTSTATUS +Ezusb_Read_Write( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + +NTSTATUS +Ezusb_ProcessIOCTL( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + +NTSTATUS +Ezusb_SelectInterfaces( + IN PDEVICE_OBJECT DeviceObject, + IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor, + IN PUSBD_INTERFACE_INFORMATION Interface + ); + +NTSTATUS +Ezusb_ResetPipe( + IN PDEVICE_OBJECT DeviceObject, + IN ULONG PipeNum + ); + +NTSTATUS +Ezusb_AbortPipe( + IN PDEVICE_OBJECT DeviceObject, + IN USBD_PIPE_HANDLE PipeHandle + ); + +ULONG +Ezusb_GetCurrentFrameNumber( + IN PDEVICE_OBJECT DeviceObject + ); + +NTSTATUS +Ezusb_Read_Write_Direct( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp, + IN BOOLEAN Read + ); + +ULONG +Ezusb_DownloadTest( + IN PDEVICE_OBJECT DeviceObject, + IN PVENDOR_REQUEST_IN pVendorRequest + ); + +NTSTATUS +Ezusb_ResetParentPort( + IN IN PDEVICE_OBJECT DeviceObject + ); + +VOID +Ezusb_Cleanup( + PDEVICE_OBJECT DeviceObject + ); + +ULONG +Ezusb_GetDescriptor( + IN PDEVICE_OBJECT fdo, + PVOID DescriptorBuffer, + ULONG BufferLength, + UCHAR DescriptorType + ); + +ULONG +Ezusb_GetDeviceDescriptor( + IN PDEVICE_OBJECT DeviceObject, + PVOID pvOutputBuffer + ); + +ULONG +Ezusb_GetConfigDescriptor( + IN PDEVICE_OBJECT DeviceObject, + PVOID pvOutputBuffer, + ULONG ulngth + ); + +ULONG +Ezusb_VendorRequest( + IN PDEVICE_OBJECT DeviceObject, + PVENDOR_REQUEST_IN pVendorRequest + ); + +ULONG +Ezusb_GetCurrentConfig( + IN PDEVICE_OBJECT DeviceObject, + IN PVENDOR_REQUEST_IN pVendorRequest + ); + +ULONG +Ezusb_GetCurrentInterface( + IN PDEVICE_OBJECT DeviceObject, + IN PVENDOR_REQUEST_IN pVendorRequest + ); + +PUSB_CONFIGURATION_DESCRIPTOR +GetConfigDescriptor( + IN PDEVICE_OBJECT DeviceObject + ); + +NTSTATUS +ConfigureDevice( + IN PDEVICE_OBJECT DeviceObject + ); + +NTSTATUS +SetInterface( + IN PDEVICE_OBJECT DeviceObject, + IN UCHAR InterfaceNumber, + IN UCHAR AlternateSetting + ); + +ULONG +Ezusb_GetStringDescriptor( + IN PDEVICE_OBJECT DeviceObject, + UCHAR Index, + USHORT LanguageId, + PVOID pvOutputBuffer, + ULONG ulLength + ); + +NTSTATUS +Ezusb_VendorRequest2( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + + +NTSTATUS +ForwardAndWait( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + + +NTSTATUS +Ezusb_DefaultPnpHandler( + IN PDEVICE_OBJECT fdo, + IN PIRP Irp + ); + + +NTSTATUS +Ezusb_DispatchPnp( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + +NTSTATUS +Ezusb_DispatchPower( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + +NTSTATUS +Ezusb_HandleStartDevice( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp + ); + +NTSTATUS +Ezusb_HandleRemoveDevice( + IN PDEVICE_OBJECT fdo, + IN PIRP Irp + ); + +NTSTATUS +OnRequestComplete( + IN PDEVICE_OBJECT DeviceObject, + IN PIRP Irp, + IN PKEVENT pev + ); + +NTSTATUS +CompleteRequest( + IN PIRP Irp, + IN NTSTATUS status, + IN ULONG info + ); + +BOOLEAN LockDevice( + IN PDEVICE_OBJECT fdo + ); + +void UnlockDevice( + PDEVICE_OBJECT fdo + ); + + +NTSTATUS InitTransferObject( + IN OUT PISO_STREAM_OBJECT streamObject, + IN ULONG index + ); + +NTSTATUS Ezusb_StartIsoTransfer( + IN PDEVICE_OBJECT fdo, + IN PIRP Irp + ); + +NTSTATUS IsoTransferComplete( + IN PDEVICE_OBJECT fdo, + IN PIRP Irp, + IN PVOID Context + ); + + +NTSTATUS Ezusb_AnchorDownload( + PDEVICE_OBJECT fdo, + WORD offset, + PUCHAR downloadBuffer, + ULONG downloadSize + ); + +NTSTATUS Ezusb_DownloadIntelHex( + PDEVICE_OBJECT fdo, + PINTEL_HEX_RECORD hexRecord + ); + +NTSTATUS Ezusb_8051Reset( + PDEVICE_OBJECT fdo, + UCHAR resetBit + ); + +NTSTATUS Ezusb_StartIsoStream( + IN PDEVICE_OBJECT fdo, + IN PIRP Irp + ); + +BOOLEAN IsFx2( + IN PDEVICE_OBJECT fdo + ); + +NTSTATUS Ezusb_SetFeature( + IN PDEVICE_OBJECT fdo, + IN PSET_FEATURE_CONTROL setFeatureControl + ); + +#endif //DRIVER section + + +/////////////////////////////////////////////////////// +// +// IOCTL Definitions +// +// User mode applications wishing to send IOCTLs to a kernel mode driver +// must use this file to set up the correct type of IOCTL code permissions. +// +// Note: this file depends on the file DEVIOCTL.H which contains the macro +// definition for "CTL_CODE" below. Include that file before you include +// this one in your source code. DEVIOCTL.H is a Microsoft header file. +// DEVIOCTL.H is normally obtained by installing the Windows DDK. +// +/////////////////////////////////////////////////////// + +// +// Set the base of the IOCTL control codes. This is somewhat of an +// arbitrary base number, so you can change this if you want unique +// IOCTL codes. You should consult the Windows NT DDK for valid ranges +// of IOCTL index codes before you choose a base index number. +// + +#define Ezusb_IOCTL_INDEX 0x0800 + + +#define IOCTL_Ezusb_GET_PIPE_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+0,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_GET_DEVICE_DESCRIPTOR CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+1,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_GET_CONFIGURATION_DESCRIPTOR CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+2,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_BULK_OR_INTERRUPT_WRITE CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+3,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_BULK_OR_INTERRUPT_READ CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+4,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_VENDOR_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+5,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_GET_CURRENT_CONFIG CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+6,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_ANCHOR_DOWNLOAD CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+7,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_RESET CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+12,\ + METHOD_IN_DIRECT, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_RESETPIPE CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+13,\ + METHOD_IN_DIRECT, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_ABORTPIPE CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+15,\ + METHOD_IN_DIRECT, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_SETINTERFACE CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+16,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_Ezusb_GET_STRING_DESCRIPTOR CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+17,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + + +// +// Perform an IN transfer over the specified bulk or interrupt pipe. +// +// lpInBuffer: BULK_TRANSFER_CONTROL stucture specifying the pipe number to read from +// nInBufferSize: sizeof(BULK_TRANSFER_CONTROL) +// lpOutBuffer: Buffer to hold data read from the device. +// nOutputBufferSize: size of lpOutBuffer. This parameter determines +// the size of the USB transfer. +// lpBytesReturned: actual number of bytes read +// +#define IOCTL_EZUSB_BULK_READ CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+19,\ + METHOD_OUT_DIRECT, \ + FILE_ANY_ACCESS) + +// +// Perform an OUT transfer over the specified bulk or interrupt pipe. +// +// lpInBuffer: BULK_TRANSFER_CONTROL stucture specifying the pipe number to write to +// nInBufferSize: sizeof(BULK_TRANSFER_CONTROL) +// lpOutBuffer: Buffer of data to write to the device +// nOutputBufferSize: size of lpOutBuffer. This parameter determines +// the size of the USB transfer. +// lpBytesReturned: actual number of bytes written +// +#define IOCTL_EZUSB_BULK_WRITE CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+20,\ + METHOD_IN_DIRECT, \ + FILE_ANY_ACCESS) + +// +// The following IOCTL's are defined as using METHOD_DIRECT_IN buffering. +// This means that the output buffer is directly mapped into system +// space and probed for read access by the driver. This means that it is +// brought into memory if it happens to be paged out to disk. Even though +// the buffer is only probed for read access, it is safe (probably) to +// write to it as well. This read/write capability is used for the loopback +// IOCTL's +// + +// TODO Insert Loopback IOCTL's + +// +// Retrieve the current USB frame number from the Host Controller +// +// lpInBuffer: NULL +// nInBufferSize: 0 +// lpOutBuffer: PULONG to hold current frame number +// nOutputBufferSize: sizeof(PULONG) +// +#define IOCTL_EZUSB_GET_CURRENT_FRAME_NUMBER CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+21,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + + +// +// Performs a vendor or class specific control transfer to EP0. The contents of +// the input parameter determine the type of request. See the USB spec +// for more information on class and vendor control transfers. +// +// lpInBuffer: PVENDOR_OR_CLASS_REQUEST_CONTROL +// nInBufferSize: sizeof(VENDOR_OR_CLASS_REQUEST_CONTROL) +// lpOutBuffer: pointer to a buffer if the request involves a data transfer +// nOutputBufferSize: size of the transfer buffer (corresponds to the wLength +// field of the USB setup packet) +// +#define IOCTL_EZUSB_VENDOR_OR_CLASS_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+22,\ + METHOD_IN_DIRECT, \ + FILE_ANY_ACCESS) + +// +// Retrieves the actual USBD_STATUS code for the most recently failed +// URB. +// +// lpInBuffer: NULL +// nInBufferSize: 0 +// lpOutBuffer: PULONG to hold the URB status +// nOutputBufferSize: sizeof(ULONG) +// + +#define IOCTL_EZUSB_GET_LAST_ERROR CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+23,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +// +// Reads from the specified ISO endpoint. (USB IN Transfer) +// +// lpInBuffer: ISO_TRANSFER_CONTROL +// nInBufferSize: sizeof(ISO_TRANSFER_CONTROL) +// lpOutBuffer: buffer to hold data read from the device +// nOutputBufferSize: size of the read buffer. +// +// +// + +#define IOCTL_EZUSB_ISO_READ CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+25,\ + METHOD_OUT_DIRECT, \ + FILE_ANY_ACCESS) + +// +// Writes to the specified ISO endpoint. (USB OUT Transfer) +// +// lpInBuffer: ISO_TRANSFER_CONTROL +// nInBufferSize: sizeof(ISO_TRANSFER_CONTROL) +// lpOutBuffer: buffer to hold data to write to the device +// nOutputBufferSize: size of the write buffer. +// +// +// + +#define IOCTL_EZUSB_ISO_WRITE CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+26,\ + METHOD_IN_DIRECT, \ + FILE_ANY_ACCESS) + +// +// Performs and Anchor Download. +// +// lpInBuffer: PANCHOR_DOWNLOAD_CONTROL +// nInBufferSize: sizeof(ANCHOR_DOWNLOAD_CONTROL) +// lpOutBuffer: pointer to a buffer of data to download to the device +// nOutputBufferSize: size of the transfer buffer +// +#define IOCTL_EZUSB_ANCHOR_DOWNLOAD CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+27,\ + METHOD_IN_DIRECT, \ + FILE_ANY_ACCESS) + +// +// Returns driver version information +// +// lpInBuffer: NULL +// nInBufferSize: 0 +// lpOutBuffer: PEZUSB_DRIVER_VERSION +// nOutputBufferSize: sizeof(EZUSB_DRIVER_VERSION) +// +#define IOCTL_EZUSB_GET_DRIVER_VERSION CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+29,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_EZUSB_START_ISO_STREAM CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+30,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_EZUSB_STOP_ISO_STREAM CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+31,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) + +#define IOCTL_EZUSB_READ_ISO_BUFFER CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+32,\ + METHOD_OUT_DIRECT, \ + FILE_ANY_ACCESS) + +#define IOCTL_EZUSB_SET_FEATURE CTL_CODE(FILE_DEVICE_UNKNOWN, \ + Ezusb_IOCTL_INDEX+33,\ + METHOD_BUFFERED, \ + FILE_ANY_ACCESS) diff --git a/src/main/ezusb/ezusbsys2.h b/src/main/ezusb/ezusbsys2.h new file mode 100644 index 0000000..a209675 --- /dev/null +++ b/src/main/ezusb/ezusbsys2.h @@ -0,0 +1,39 @@ +#ifndef EZUSB_EZUSBSYS2_H +#define EZUSB_EZUSBSYS2_H + +#include +#include +#include + +#include "ezusbsys.h" + +/* can't seem to #include the requisite DDK headers from usermode code, + so we have to redefine these macros here */ + +#ifndef CTL_CODE +#define CTL_CODE( DeviceType, Function, Method, Access ) ( \ + ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ +) +#endif + +#ifndef FILE_DEVICE_UNKNOWN +#define FILE_DEVICE_UNKNOWN 0x00000022 +#endif + +#ifndef METHOD_BUFFERED +#define METHOD_BUFFERED 0 +#endif + +#ifndef METHOD_IN_DIRECT +#define METHOD_IN_DIRECT 1 +#endif + +#ifndef METHOD_OUT_DIRECT +#define METHOD_OUT_DIRECT 2 +#endif + +#ifndef FILE_ANY_ACCESS +#define FILE_ANY_ACCESS 0 +#endif + +#endif \ No newline at end of file diff --git a/src/main/ezusb/util.c b/src/main/ezusb/util.c new file mode 100644 index 0000000..478e84b --- /dev/null +++ b/src/main/ezusb/util.c @@ -0,0 +1,167 @@ +#include +#include + +#include "ezusb/util.h" + +#include "util/log.h" +#include "util/crc.h" +#include "util/fs.h" +#include "util/mem.h" + +struct ezusb_firmware* ezusb_firmware_load(const char* file) +{ + void* buffer; + size_t buffer_size; + struct ezusb_firmware* fw; + size_t pos; + + log_assert(file); + + pos = 0; + + if (!file_load(file, &buffer, &buffer_size, false)) { + return false; + } + + fw = xmalloc(sizeof(struct ezusb_firmware)); + + fw->crc = ((struct ezusb_firmware*) buffer)->crc; + fw->segment_count = ((struct ezusb_firmware*) buffer)->segment_count; + + pos += sizeof(uint16_t) * 2; + + fw->segments = xmalloc( + sizeof(struct ezusb_firmware_segment) * fw->segment_count); + + for (uint16_t i = 0; i < fw->segment_count; i++) { + uint16_t size = *((uint16_t*) (buffer + pos)); + pos += sizeof(uint16_t); + + uint16_t offset = *((uint16_t*) (buffer + pos)); + pos += sizeof(uint16_t); + + fw->segments[i] = xmalloc(sizeof(uint16_t) * 2 + size); + + fw->segments[i]->size = size; + fw->segments[i]->offset = offset; + memcpy(fw->segments[i]->data, buffer + pos, size); + pos += size; + } + + return fw; +} + +bool ezusb_firmware_save(const char* file, struct ezusb_firmware* fw) +{ + size_t size; + void* buffer; + size_t pos; + bool res; + + log_assert(file); + log_assert(fw); + + size = 0; + pos = 0; + + for (uint16_t i = 0; i < fw->segment_count; i++) { + size += fw->segments[i]->size + sizeof(uint16_t) * 2; + } + + size += sizeof(uint16_t) * 2; + + buffer = xmalloc(size); + + ((struct ezusb_firmware*) buffer)->crc = fw->crc; + ((struct ezusb_firmware*) buffer)->segment_count = fw->segment_count; + + pos += sizeof(uint16_t) * 2; + + for (uint16_t i = 0; i < fw->segment_count; i++) { + memcpy(buffer + pos, fw->segments[i], + sizeof(uint16_t) * 2 + fw->segments[i]->size); + pos += sizeof(uint16_t) * 2 + fw->segments[i]->size; + } + + res = file_save(file, buffer, size); + free(buffer); + + return res; +} + +struct ezusb_firmware* ezusb_firmware_alloc() +{ + struct ezusb_firmware* fw; + + fw = xmalloc(sizeof(struct ezusb_firmware)); + + fw->crc = 0; + fw->segment_count = 0; + fw->segments = NULL; + + return fw; +} + +struct ezusb_firmware_segment* ezusb_firmware_segment_alloc(uint16_t offset, + uint16_t size, void* data) +{ + struct ezusb_firmware_segment* seg; + + log_assert(size > 0); + log_assert(data); + + seg = xmalloc(sizeof(uint16_t) * 2 + size); + + seg->offset = offset; + seg->size = size; + memcpy(seg->data, data, size); + + return seg; +} + +void ezusb_firmware_add_segment(struct ezusb_firmware* fw, + struct ezusb_firmware_segment* segment) +{ + struct ezusb_firmware_segment** tmp; + + log_assert(fw); + log_assert(segment); + + fw->segment_count++; + + tmp = xrealloc(fw->segments, + sizeof(struct ezusb_firmware_segment*) * fw->segment_count); + + if (tmp != NULL) { + fw->segments = tmp; + } + + fw->segments[fw->segment_count - 1] = segment; +} + +uint16_t ezusb_firmware_crc(struct ezusb_firmware* fw) +{ + uint16_t crc; + + log_assert(fw); + + crc = 0; + + for (uint16_t i = 0; i < fw->segment_count; i++) { + crc = crc16(fw->segments[i], + sizeof(uint16_t) * 2 + fw->segments[i]->size, crc); + } + + return crc; +} + +void ezusb_firmware_free(struct ezusb_firmware* fw) +{ + log_assert(fw); + + for (uint16_t i = 0; i < fw->segment_count; i++) { + free(fw->segments[i]); + } + + free(fw); +} \ No newline at end of file diff --git a/src/main/ezusb/util.h b/src/main/ezusb/util.h new file mode 100644 index 0000000..af15593 --- /dev/null +++ b/src/main/ezusb/util.h @@ -0,0 +1,101 @@ +/** + * Utility module for ezusb1 (legacy) and ezusb2 (FX2) device handling. + * + * @author icex2 + */ +#ifndef EZUSB_UTIL_H +#define EZUSB_UTIL_H + +#include +#include + +/** + * Struct with identifier information of the board. + */ +struct ezusb_ident { + char name[256]; + uint16_t pid; + uint16_t vid; +}; + +/** + * A single segment of the firmware. + */ +struct ezusb_firmware_segment { + uint16_t size; + uint16_t offset; + uint8_t data[]; +}; + +/** + * Ezusb firmware struct. Holds the firmware separated into segments. + */ +struct ezusb_firmware { + uint16_t crc; + uint16_t segment_count; + struct ezusb_firmware_segment** segments; +}; + +/** + * Load a firmware binary file into memory. + * + * @oaram file Path to firmware binary file. + * @return Valid firmware structure on success, NULL on failure. + */ +struct ezusb_firmware* ezusb_firmware_load(const char* file); + +/** + * Save the in-memory firmware to a file. + * + * @param file Target file to save to. + * @param fw Valid firmware structure to store to disk. + * @return True on success, false on error. + */ +bool ezusb_firmware_save(const char* file, struct ezusb_firmware* fw); + +/** + * Allocate and initialize a new (empty = no segments) ezusb firmware structure. + * + * @return Allocated and initialized structure. + */ +struct ezusb_firmware* ezusb_firmware_alloc(); + +/** + * Allocate and initialize a new firmware segment. + * + * @param offset Offset of the segment. + * @param size Size of the segment + * @param data Pointer to a buffer with firmware data of at least the + * specified size. + * @return Allocated and initialized structure. + */ +struct ezusb_firmware_segment* ezusb_firmware_segment_alloc(uint16_t offset, + uint16_t size, void* data); + +/** + * Add a segment to a firmware structure. + * + * @param fw Valid firmware structure to add the segment to. + * @param segment Valid segment to add to the end of the segment list of the + * firmware. + */ +void ezusb_firmware_add_segment(struct ezusb_firmware* fw, + struct ezusb_firmware_segment* segment); + +/** + * Calculate the checksum of the provided firmware. + * + * @param fw Firmware to checksum. + * @return CRC16 of firmware. + */ +uint16_t ezusb_firmware_crc(struct ezusb_firmware* fw); + +/** + * Free a previously allocated firmware structure (also cleans up all + * referenced segments). + * + * @param fw Firmware struct to free. + */ +void ezusb_firmware_free(struct ezusb_firmware* fw); + +#endif \ No newline at end of file diff --git a/src/main/ezusb2-dbg-hook/Module.mk b/src/main/ezusb2-dbg-hook/Module.mk new file mode 100644 index 0000000..4290f5c --- /dev/null +++ b/src/main/ezusb2-dbg-hook/Module.mk @@ -0,0 +1,8 @@ +dlls += ezusb2-dbg-hook + +libs_ezusb2-dbg-hook := \ + hook \ + util \ + +src_ezusb2-dbg-hook := \ + main.c \ diff --git a/src/main/ezusb2-dbg-hook/ezusb2-dbg-hook.def b/src/main/ezusb2-dbg-hook/ezusb2-dbg-hook.def new file mode 100644 index 0000000..c3f077d --- /dev/null +++ b/src/main/ezusb2-dbg-hook/ezusb2-dbg-hook.def @@ -0,0 +1,4 @@ +LIBRARY ezusb2-dbg-hook + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/ezusb2-dbg-hook/main.c b/src/main/ezusb2-dbg-hook/main.c new file mode 100644 index 0000000..415381d --- /dev/null +++ b/src/main/ezusb2-dbg-hook/main.c @@ -0,0 +1,375 @@ +#define LOG_MODULE "ezusb2-dbg" + +#include +#include + +#include +#include +#include + +#include "ezusb2/cyioctl.h" + +#include "hook/table.h" + +#include "util/cmdline.h" +#include "util/hex.h" +#include "util/log.h" +#include "util/str.h" + +static HANDLE STDCALL my_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE STDCALL my_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static BOOL STDCALL my_DeviceIoControl( + HANDLE hFile, + uint32_t dwIoControlCode, + void *lpInBuffer, + uint32_t nInBufferSize, + void *lpOutBuffer, + uint32_t nOutBufferSize, + uint32_t *lpBytesReturned, + OVERLAPPED *lpOverlapped); + +static BOOL STDCALL my_CloseHandle(HANDLE fd); + +static HANDLE (STDCALL *real_CreateFileW)( + const wchar_t *filename, + uint32_t access, + uint32_t share, + SECURITY_ATTRIBUTES *sa, + uint32_t creation, + uint32_t flags, + HANDLE tmpl); + +static BOOL (STDCALL *real_DeviceIoControl)( + HANDLE fd, + uint32_t code, + void *in_bytes, + uint32_t in_nbytes, + void *out_bytes, + uint32_t out_nbytes, + uint32_t *out_returned, + OVERLAPPED *ovl); + +static BOOL (STDCALL *real_CloseHandle)(HANDLE fd); + +static struct hook_symbol ezusb2_dbg_hook_syms[] = { + { + .name = "CreateFileA", + .patch = my_CreateFileA, + }, + { + .name = "CreateFileW", + .patch = my_CreateFileW, + .link = (void *) &real_CreateFileW, + }, + { + .name = "DeviceIoControl", + .patch = my_DeviceIoControl, + .link = (void *) &real_DeviceIoControl + }, + { + .name = "CloseHandle", + .patch = my_CloseHandle, + .link = (void *) &real_CloseHandle, + }, +}; + +typedef void (*ezusb2_dbg_hook_handle_ioctl_t)(BOOL* result, HANDLE hFile, + uint32_t dwIoControlCode, + void *lpInBuffer, + uint32_t nInBufferSize, + void *lpOutBuffer, + uint32_t nOutBufferSize, + uint32_t *lpBytesReturned, + OVERLAPPED *lpOverlapped); + +static wchar_t ezusb2_dbg_hook_path[MAX_PATH]; +static HANDLE ezusb2_dbg_hook_handle = INVALID_HANDLE_VALUE; +static ezusb2_dbg_hook_handle_ioctl_t ezusb2_dbg_hook_ioctl_handler; + +static void ezusb2_dbg_hook_handle_ioctl_legacy(BOOL* result, HANDLE hFile, + uint32_t dwIoControlCode, void *lpInBuffer, uint32_t nInBufferSize, + void *lpOutBuffer, uint32_t nOutBufferSize, uint32_t *lpBytesReturned, + OVERLAPPED *lpOverlapped) +{ + log_warning("TODO NOT IMPLEMENTED"); +} + +static void ezusb2_dbg_hook_handle_ioctl_fx2(BOOL* result, HANDLE hFile, + uint32_t dwIoControlCode, void *lpInBuffer, uint32_t nInBufferSize, + void *lpOutBuffer, uint32_t nOutBufferSize, uint32_t *lpBytesReturned, + OVERLAPPED *lpOverlapped) +{ + char data_in[8192]; + char data_out[8192]; + + if (nInBufferSize < sizeof(SINGLE_TRANSFER)) { + log_warning("In buffer size, ioctl_code 0x%X too small: %d", + dwIoControlCode, nInBufferSize); + } + + if (nOutBufferSize < sizeof(SINGLE_TRANSFER)) { + log_warning("Out buffer size, ioctl_code 0x%X too small: %d", + dwIoControlCode, nOutBufferSize); + } + + PSINGLE_TRANSFER trans_in = (PSINGLE_TRANSFER) lpInBuffer; + PSINGLE_TRANSFER trans_out = (PSINGLE_TRANSFER) lpOutBuffer; + + memset(data_in, 0, sizeof(data_in)); + memset(data_out, 0, sizeof(data_out)); + + hex_encode_uc(lpInBuffer + trans_in->BufferOffset, trans_in->BufferLength, + data_in, sizeof(data_in)); + hex_encode_uc(lpOutBuffer + trans_out->BufferOffset, + trans_out->BufferLength, data_out, sizeof(data_out)); + + log_info("[ioctl][res %d, bytes returned %d, overlapped %d][ctrl_code 0x%X]" + "[IN][bmRequest 0x%X, bRequest 0x%X, wValue 0x%X, wIndex 0x%X, " + "wLength %d, ulTimeOut %ld][reserved 0x%X, ucEndpointAddress 0x%X, " + "NtStatus 0x%lX, UsbdStatus 0x%lX, IsoPacketOffset %ld, " + "IsoPacketLength %ld, BufferOffset %ld, BufferLength %ld][%s]" + "[OUT][bmRequest 0x%X, bRequest 0x%X, wValue 0x%X, wIndex 0x%X, " + "wLength %d, ulTimeOut %ld][reserved 0x%X, ucEndpointAddress 0x%X, " + "NtStatus 0x%lX, UsbdStatus 0x%lX, IsoPacketOffset %ld, " + "IsoPacketLength %ld, BufferOffset %ld, BufferLength %ld][%s]", + result ? *result : -1, result ? *lpBytesReturned : -1, + lpOverlapped ? 1 : 0, dwIoControlCode, + trans_in->SetupPacket.bmRequest, trans_in->SetupPacket.bRequest, + trans_in->SetupPacket.wValue, trans_in->SetupPacket.wIndex, + trans_in->SetupPacket.wLength, trans_in->SetupPacket.ulTimeOut, + trans_in->reserved, trans_in->ucEndpointAddress, trans_in->NtStatus, + trans_in->UsbdStatus, trans_in->IsoPacketOffset, + trans_in->IsoPacketLength, trans_in->BufferOffset, + trans_in->BufferLength, data_in, + trans_out->SetupPacket.bmRequest, trans_out->SetupPacket.bRequest, + trans_out->SetupPacket.wValue, trans_out->SetupPacket.wIndex, + trans_out->SetupPacket.wLength, trans_out->SetupPacket.ulTimeOut, + trans_out->reserved, trans_out->ucEndpointAddress, trans_out->NtStatus, + trans_out->UsbdStatus, trans_out->IsoPacketOffset, + trans_out->IsoPacketLength, trans_out->BufferOffset, + trans_out->BufferLength, data_out); +} + +static HANDLE STDCALL my_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + wchar_t *wfilename; + HANDLE fd; + + if (lpFileName == NULL) { + log_warning("%s: lpFileName == NULL", __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return INVALID_HANDLE_VALUE; + } + + wfilename = str_widen(lpFileName); + fd = my_CreateFileW( + wfilename, + dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, + dwCreationDisposition, dwFlagsAndAttributes, + hTemplateFile); + free(wfilename); + + return fd; +} + +static HANDLE STDCALL my_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + char* file_name; + HANDLE handle; + + if (lpFileName == NULL) { + log_warning("%s: lpFileName == NULL", __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return INVALID_HANDLE_VALUE; + } + + wstr_narrow(lpFileName, &file_name); + + /* Don't spam log once we have our device */ + if (ezusb2_dbg_hook_handle == INVALID_HANDLE_VALUE) { + log_misc("Opening: %s", file_name); + } + + handle = real_CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, + hTemplateFile); + + if (!wcscmp(lpFileName, ezusb2_dbg_hook_path)) { + log_info("Hit ezusb device, handle: %p", handle); + + if (ezusb2_dbg_hook_handle != INVALID_HANDLE_VALUE) { + log_warning("Still active ezusb handle but opening new"); + } + + ezusb2_dbg_hook_handle = handle; + } + + return handle; +} + +static BOOL STDCALL my_DeviceIoControl( + HANDLE hFile, + uint32_t dwIoControlCode, + void *lpInBuffer, + uint32_t nInBufferSize, + void *lpOutBuffer, + uint32_t nOutBufferSize, + uint32_t *lpBytesReturned, + OVERLAPPED *lpOverlapped) +{ + BOOL res; + + if (ezusb2_dbg_hook_handle != INVALID_HANDLE_VALUE && + hFile == ezusb2_dbg_hook_handle) { + ezusb2_dbg_hook_ioctl_handler(NULL, hFile, dwIoControlCode, lpInBuffer, + nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, + lpOverlapped); + } + + res = real_DeviceIoControl(hFile, dwIoControlCode, lpInBuffer, + nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, + lpOverlapped); + + if (ezusb2_dbg_hook_handle != INVALID_HANDLE_VALUE && + hFile == ezusb2_dbg_hook_handle) { + ezusb2_dbg_hook_ioctl_handler(&res, hFile, dwIoControlCode, lpInBuffer, + nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, + lpOverlapped); + } + + return res; +} + +static BOOL STDCALL my_CloseHandle(HANDLE hFile) +{ + if (ezusb2_dbg_hook_handle == hFile) { + log_info("Closing ezusb handle: %p", hFile); + ezusb2_dbg_hook_handle = INVALID_HANDLE_VALUE; + } + + return real_CloseHandle(hFile); +} + +static void ezusb2_dbg_hook_terminate_process() +{ + /* Don't use ExitProcess. This might result in deadlocks + on newer games which rely more on multi threading */ + HANDLE hnd; + hnd = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, TRUE, + GetCurrentProcessId()); + TerminateProcess(hnd, 0); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { + FILE* file; + int argc; + char **argv; + wchar_t* buffer; + uint32_t args_success; + + file = fopen("ezusb2_dbg.log", "w+"); + log_to_writer(log_writer_file, file); + + hook_table_apply( + NULL, + "kernel32.dll", + ezusb2_dbg_hook_syms, + lengthof(ezusb2_dbg_hook_syms)); + + if (real_CreateFileW == NULL) { + /* my_CreateFileA requires this to be present */ + real_CreateFileW = (void *) GetProcAddress( + GetModuleHandleA("kernel32.dll"), + "CreateFileW"); + } + + args_recover(&argc, &argv); + + args_success = 0; + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--ezusb2_dbg_path")) { + if (i + 1 >= argc) { + continue; + } + + buffer = str_widen(argv[i + 1]); + + wcscpy(ezusb2_dbg_hook_path, buffer); + free(buffer); + log_info("ezusb device path provided: %s", argv[i + 1]); + args_success++; + } else if (!strcmp(argv[i], "--ezusb2_dbg_type")) { + if (i + 1 >= argc) { + continue; + } + + int type = atoi(argv[i + 1]); + + switch (type) { + case 1: + ezusb2_dbg_hook_ioctl_handler = + ezusb2_dbg_hook_handle_ioctl_legacy; + break; + case 2: + ezusb2_dbg_hook_ioctl_handler = + ezusb2_dbg_hook_handle_ioctl_fx2; + break; + default: + break; + } + + if (ezusb2_dbg_hook_ioctl_handler) { + log_info("ezusb type selected: %d", type); + args_success++; + } + } + } + + if (args_success != 2) { + log_fatal("No ezusb device path provided as arguments"); + log_fatal("ezusb2_dbg-hook usage: --ezusb_path "); + ezusb2_dbg_hook_terminate_process(); + } + + args_free(argc, argv); + } + + return TRUE; +} diff --git a/src/main/ezusb2-emu/Module.mk b/src/main/ezusb2-emu/Module.mk new file mode 100644 index 0000000..fdca67b --- /dev/null +++ b/src/main/ezusb2-emu/Module.mk @@ -0,0 +1,9 @@ +libs += ezusb2-emu + +libs_ezusb2-emu := \ + ezusb \ + +src_ezusb2-emu := \ + desc.c \ + device.c \ + util.c \ diff --git a/src/main/ezusb2-emu/conf.h b/src/main/ezusb2-emu/conf.h new file mode 100644 index 0000000..403de9d --- /dev/null +++ b/src/main/ezusb2-emu/conf.h @@ -0,0 +1,7 @@ +#ifndef EZUSB2_EMU_CONF_H +#define EZUSB_2EMU_CONF_H + +//#define EZUSB2_EMU_DEBUG_DUMP +//#define EZUSB2_EMU_FW_DUMP + +#endif \ No newline at end of file diff --git a/src/main/ezusb2-emu/desc.c b/src/main/ezusb2-emu/desc.c new file mode 100644 index 0000000..1a1ad43 --- /dev/null +++ b/src/main/ezusb2-emu/desc.c @@ -0,0 +1,16 @@ +#include "ezusb2-emu/desc.h" + +const struct ezusb_emu_desc_device ezusb2_emu_desc_device = { + .setupapi = { + .device_guid = { + 0xAE18AA60, + 0x7F6A, + 0x11D4, + { 0x97, 0xDD, 0x00, 0x01, 0x02, 0x29, 0xB9, 0x59 } + }, + .device_desc = "Cypress EZ-USB FX2LP - EEPROM missing", + .device_path = "\\\\.\\Ezusb-0" + }, + .vid = 0x04B4, + .pid = 0x8613, +}; \ No newline at end of file diff --git a/src/main/ezusb2-emu/desc.h b/src/main/ezusb2-emu/desc.h new file mode 100644 index 0000000..21ff401 --- /dev/null +++ b/src/main/ezusb2-emu/desc.h @@ -0,0 +1,11 @@ +#ifndef EZUSB2_EMU_DESC_H +#define EZUSB2_EMU_DESC_H + +#include + +#include "ezusb-emu/desc.h" + +/* IO2 IO board */ +extern const struct ezusb_emu_desc_device ezusb2_emu_desc_device; + +#endif diff --git a/src/main/ezusb2-emu/device.c b/src/main/ezusb2-emu/device.c new file mode 100644 index 0000000..6d38f0f --- /dev/null +++ b/src/main/ezusb2-emu/device.c @@ -0,0 +1,374 @@ +#define LOG_MODULE "ezusb2-emu-device" + +#include +#include +#include + +#include + +#include "ezusb/util.h" + +#include "ezusb2/cyioctl.h" + +#include "ezusb-emu/msg.h" + +#include "ezusb2-emu/conf.h" +#include "ezusb2-emu/desc.h" +#include "ezusb2-emu/device.h" +#include "ezusb2-emu/util.h" + +#include "hook/iohook.h" + +#include "imports/avs.h" + +#include "util/fs.h" +#include "util/hex.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +enum pipe { + PIPE_INT_OUT = 0x01, + PIPE_BULK_OUT = 0x02, + PIPE_INT_IN = 0x81, + /* yes, 0x86 and NOT 0x82... */ + PIPE_BULK_IN = 0x86 +}; + +struct ezusb_usb_string_desc { + uint8_t length; + uint8_t desc_type; + /* Max length? 9 elements set by game... */ + uint16_t unicode_str[9]; +}; + +static HRESULT ezusb_open(struct irp *irp); +static HRESULT ezusb_ioctl(struct irp *irp); + +static HRESULT ezusb_ioctl_ep0( + SINGLE_TRANSFER *usb_req, + struct const_iobuf *write, + struct iobuf *read); + +static HRESULT ezusb_ioctl_epX( + SINGLE_TRANSFER *usb_req, + struct const_iobuf *write, + struct iobuf *read); + +static HRESULT ezusb_get_device_descriptor(struct iobuf *read); +static HRESULT ezusb_get_string_descriptor(struct iobuf *read); +static HRESULT ezusb_reset(struct const_iobuf *write); +static HRESULT ezusb_upload_fw(uint16_t offset, struct const_iobuf *write); + +static HANDLE ezusb_fd; +static struct ezusb_firmware* ezusb_emu_firmware; +static struct ezusb_emu_msg_hook* ezusb_emu_dev_fx2_msg_hook; + +void ezusb2_emu_device_hook_init(struct ezusb_emu_msg_hook* msg_hook) +{ + log_assert(ezusb_fd == NULL); + + ezusb_fd = iohook_open_dummy_fd(); + ezusb_emu_dev_fx2_msg_hook = msg_hook; +} + +void ezusb2_emu_device_hook_fini(void) +{ + if (ezusb_fd != NULL) { + CloseHandle(ezusb_fd); + } + + ezusb_fd = NULL; +} + +/* + * WIN32 I/O AND IOHOOK LAYER + */ + +HRESULT ezusb2_emu_device_dispatch_irp(struct irp *irp) +{ + if (irp->op != IRP_OP_OPEN && irp->fd != ezusb_fd) { + return irp_invoke_next(irp); + } + + /* read/write are not supported, and the game-side EZUSB code constantly + churns through FDs opening and closing them (so we silently acknowledge + CloseHandle calls and don't even log them). */ + + switch (irp->op) { + case IRP_OP_OPEN: return ezusb_open(irp); + case IRP_OP_CLOSE: return S_OK; + case IRP_OP_IOCTL: return ezusb_ioctl(irp); + default: return E_NOTIMPL; + } +} + +static HRESULT ezusb_open(struct irp *irp) +{ + log_assert(irp != NULL); + + if (!wstr_eq(irp->open_filename, L"\\\\.\\Ezusb-0")) { + return irp_invoke_next(irp); + } + + irp->fd = ezusb_fd; + + return S_OK; +} + +static HRESULT ezusb_ioctl(struct irp *irp) +{ + SINGLE_TRANSFER *usb_req; + struct const_iobuf write; + struct iobuf read; + HRESULT hr; + + log_assert(irp != NULL); + +#ifdef EZUSB_EMU_DEBUG_DUMP + /* For debugging */ + ezusb2_emu_util_log_usb_msg("BEFORE", irp); +#endif + + if (irp->write.nbytes < sizeof(SINGLE_TRANSFER)) { + log_warning("SINGLE_TRANSFER out buffer too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + if (irp->read.nbytes < sizeof(SINGLE_TRANSFER)) { + log_warning("SINGLE_TRANSFER in buffer too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + memcpy(irp->read.bytes, irp->write.bytes, sizeof(SINGLE_TRANSFER)); + + write.bytes = irp->write.bytes + sizeof(SINGLE_TRANSFER); + write.nbytes = irp->write.nbytes - sizeof(SINGLE_TRANSFER); + write.pos = 0; + read.bytes = irp->read.bytes + sizeof(SINGLE_TRANSFER); + read.nbytes = irp->read.nbytes - sizeof(SINGLE_TRANSFER); + read.pos = 0; + + usb_req = (SINGLE_TRANSFER *) irp->write.bytes; + + switch (irp->ioctl) { + case IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER: + hr = ezusb_ioctl_ep0(usb_req, &write, &read); + + break; + + case IOCTL_ADAPT_SEND_NON_EP0_TRANSFER: + hr = ezusb_ioctl_epX(usb_req, &write, &read); + + break; + + default: + log_warning("Unknown ioctl %08x", irp->ioctl); + hr = E_INVALIDARG; + + break; + } + + if (FAILED(hr)) { + return hr; + } + + irp->read.pos = read.pos + sizeof(SINGLE_TRANSFER); + +#ifdef EZUSB_EMU_DEBUG_DUMP + /* For debugging */ + ezusb2_emu_util_log_usb_msg("AFTER", irp); +#endif + + return hr; +} + +/* + * USB TRANSFER LAYER + */ + +static HRESULT ezusb_ioctl_ep0( + SINGLE_TRANSFER *usb_req, + struct const_iobuf *write, + struct iobuf *read) +{ + log_assert(usb_req != NULL); + log_assert(write != NULL); + log_assert(read != NULL); + + /* + 0x80: Device -> Host Request, Standard, Recipient Device + 0x40: Host -> Device Request, Class, Recipient Device + */ + + if ( usb_req->SetupPacket.bmRequest == 0x80 && + usb_req->SetupPacket.bRequest == 0x06) { + switch (usb_req->SetupPacket.wValue) { + /* Get usb device descriptor */ + case 0x0100: + /* return data with USB_DEVICE_DESCRIPTOR struct */ + return ezusb_get_device_descriptor(read); + + /* get usb string descriptor */ + case 0x0301: + return ezusb_get_string_descriptor(read); + + default: + log_warning( + "Unsupported standard req (dev->host) value: %04X", + usb_req->SetupPacket.wValue); + + return E_INVALIDARG; + } + } else if ( + usb_req->SetupPacket.bmRequest == 0x40 && + usb_req->SetupPacket.bRequest == 0xA0) { + if (usb_req->SetupPacket.wValue == 0xE600) { + /* reset */ + return ezusb_reset(write); + } else { + /* fw download */ + return ezusb_upload_fw(usb_req->SetupPacket.wValue, write); + } + } else { + log_warning( + "Invalid standard req_type and/or request: %02X %02X", + usb_req->SetupPacket.bmRequest, + usb_req->SetupPacket.bRequest); + + return E_INVALIDARG; + } +} + +static HRESULT ezusb_ioctl_epX( + SINGLE_TRANSFER *usb_req, + struct const_iobuf *write, + struct iobuf *read) +{ + log_assert(usb_req != NULL); + log_assert(write != NULL); + log_assert(read != NULL); + + /* No reason to follow a standard, right? */ + switch (usb_req->ucEndpointAddress) { + case PIPE_INT_OUT: + return ezusb_emu_dev_fx2_msg_hook->interrupt_write(write); + + case PIPE_BULK_OUT: + return ezusb_emu_dev_fx2_msg_hook->bulk_write(write); + + case PIPE_INT_IN: + return ezusb_emu_dev_fx2_msg_hook->interrupt_read(read); + + case PIPE_BULK_IN: + return ezusb_emu_dev_fx2_msg_hook->bulk_read(read); + + default: + log_warning("Unhandled endpoint %02X", usb_req->ucEndpointAddress); + + return E_INVALIDARG; + } +} + +static HRESULT ezusb_get_device_descriptor(struct iobuf *read) +{ + USB_DEVICE_DESCRIPTOR *desc; + + log_assert(read != NULL); + + if (read->nbytes < sizeof(*desc)) { + log_warning("USB_DEVICE_DESCRIPTOR buffer too small: %d", read->nbytes); + + return HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW); + } + + desc = (USB_DEVICE_DESCRIPTOR *) read->bytes; + + /* vid and pid checked, only */ + desc->idVendor = ezusb2_emu_desc_device.vid; + desc->idProduct = ezusb2_emu_desc_device.pid; + + log_misc( + "get_device_descriptor: vid %02x, pid %02x", + desc->idVendor, + desc->idProduct); + + read->pos = sizeof(*desc); + + return S_OK; +} + +static HRESULT ezusb_get_string_descriptor(struct iobuf *read) +{ + struct ezusb_usb_string_desc *desc; + + log_assert(read != NULL); + + if (read->nbytes < sizeof(*desc)) { + log_warning("ezusb_usb_string_desc buffer too small: %d", read->nbytes); + + return HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW); + } + + desc = (struct ezusb_usb_string_desc *) read->bytes; + + desc->length = sizeof(*desc); + desc->desc_type = 0x03; /* Usb spec says so */ + memcpy(desc->unicode_str, L"KONAMI", 12); /* Unicode encoding */ + + read->pos = sizeof(*desc); + + log_misc("get_string_descriptor: KONAMI"); + + return S_OK; +} + +static HRESULT ezusb_reset(struct const_iobuf *write) +{ + log_assert(write != NULL); + + switch (write->bytes[0]) { + case 0x01: + log_misc("reset hold, starting fw download..."); + ezusb_emu_firmware = ezusb_firmware_alloc(); + + return S_OK; + + case 0x00: + log_misc("reset release, finished fw download"); + + ezusb_emu_firmware->crc = ezusb_firmware_crc(ezusb_emu_firmware); + +#ifdef EZUSB_EMU_FW_DUMP + if (!ezusb_firmware_save("ezusb_fx2.bin", ezusb_emu_firmware)) { + log_fatal("Saving dumped firmware failed"); + } else { + log_misc("firmware dumped do ezusb_fx2.bin file"); + } + + free(ezusb_emu_firmware); + ezusb_emu_firmware = NULL; +#endif + + return S_OK; + + default: + log_warning("Unknown reset cmd: %02X", write->bytes[0]); + + return E_FAIL; + } +} + +static HRESULT ezusb_upload_fw(uint16_t offset, struct const_iobuf *write) +{ + log_misc("upload_fw, offset %04X, nbytes %04X", offset, write->nbytes); + + ezusb_firmware_add_segment(ezusb_emu_firmware, + ezusb_firmware_segment_alloc(offset, write->nbytes, + (void*) write->bytes)); + + return S_OK; +} + diff --git a/src/main/ezusb2-emu/device.h b/src/main/ezusb2-emu/device.h new file mode 100644 index 0000000..3cacdd1 --- /dev/null +++ b/src/main/ezusb2-emu/device.h @@ -0,0 +1,32 @@ +#ifndef EZUSB2_EMU_DEVICE_H +#define EZUSB2_EMU_DEVICE_H + +#include + +#include +#include + +#include "ezusb-emu/msg.h" + +#include "hook/iohook.h" + +/** + * Hook IO functions to intercept with EZUSB FX2 (IIDX/Pop'n IO2) communication + * and detour to our emulation code. + * + * @param msg_hook Hook functions to dispatch ezusb interrupt and bulk device + * messages to + */ +void ezusb2_emu_device_hook_init(struct ezusb_emu_msg_hook* msg_hook); + +/** + * Cleanup the hooked IO functions. + */ +void ezusb2_emu_device_hook_fini(void); + +/** + * Iohook interface. + */ +HRESULT ezusb2_emu_device_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/ezusb2-emu/util.c b/src/main/ezusb2-emu/util.c new file mode 100644 index 0000000..3bb54d1 --- /dev/null +++ b/src/main/ezusb2-emu/util.c @@ -0,0 +1,66 @@ +#define LOG_MODULE "ezusb2-emu-util" + +#include + +#include +#include +#include + +#include "ezusb2/cyioctl.h" +#include "ezusb2/ezusb2.h" + +#include "ezusb-emu/util.h" + +#include "util/hex.h" +#include "util/log.h" + +void ezusb2_emu_util_log_usb_msg(const char* prefix, const struct irp *irp) +{ + SINGLE_TRANSFER *usb_req; + const char* ctl_code_str; + char setup_packet_str[4096]; + char single_transfer_str[4096]; + char read_data_str[4096]; + char write_data_str[4096]; + + switch (irp->ioctl) { + // TODO use macros + case IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER: + ctl_code_str = "EP0_CTRL"; + break; + + case IOCTL_ADAPT_SEND_NON_EP0_TRANSFER: + ctl_code_str = "VENDOR"; + break; + + default: + ctl_code_str = "UNKNOWN"; + break; + } + + usb_req = (SINGLE_TRANSFER *) irp->write.bytes; + + sprintf(setup_packet_str, "bmRequest 0x%X, bRequest 0x%X, wValue 0x%X, " + "wIndex 0x%X, wLength %d, ulTimeOut %ld", + usb_req->SetupPacket.bmRequest, usb_req->SetupPacket.bRequest, + usb_req->SetupPacket.wValue, usb_req->SetupPacket.wIndex, + usb_req->SetupPacket.wLength, usb_req->SetupPacket.ulTimeOut); + + sprintf(single_transfer_str, "reserverd 0x%X, ucEndpointAddress 0x%X, " + "NtStatus 0x%lX, UsbdStatus 0x%lX, IsoPacketOffset %ld, " + "IsoPacketLength %ld, BufferOffset %ld, BufferLength %ld", + usb_req->reserved, usb_req->ucEndpointAddress, usb_req->NtStatus, + usb_req->UsbdStatus, usb_req->IsoPacketOffset, usb_req->IsoPacketLength, + usb_req->BufferOffset, usb_req->BufferLength); + + hex_encode_uc(irp->read.bytes, irp->read.nbytes, read_data_str, + sizeof(read_data_str)); + hex_encode_uc(irp->write.bytes, irp->write.nbytes, write_data_str, + sizeof(write_data_str)); + + log_warning("[EZUSB DUMP %s][%s] ctl_code 0x%X, in_len %d, out_len %d ||| " + "setup packet: %s ||| single transfer: %s ||| read data: %s ||| " + "write data: %s", prefix, ctl_code_str, irp->ioctl, irp->read.nbytes, + irp->write.nbytes, setup_packet_str, single_transfer_str, read_data_str, + write_data_str); +} \ No newline at end of file diff --git a/src/main/ezusb2-emu/util.h b/src/main/ezusb2-emu/util.h new file mode 100644 index 0000000..3a24a7b --- /dev/null +++ b/src/main/ezusb2-emu/util.h @@ -0,0 +1,8 @@ +#ifndef EZUSB2_EMU_UTIL_H +#define EZUSB2_EMU_UTIL_H + +#include "hook/iohook.h" + +void ezusb2_emu_util_log_usb_msg(const char* prefix, const struct irp *irp); + +#endif diff --git a/src/main/ezusb2-iidx-emu/Module.mk b/src/main/ezusb2-iidx-emu/Module.mk new file mode 100644 index 0000000..344bc56 --- /dev/null +++ b/src/main/ezusb2-iidx-emu/Module.mk @@ -0,0 +1,8 @@ +libs += ezusb2-iidx-emu + +libs_ezusb2-iidx-emu := \ + ezusb-emu \ + +src_ezusb2-iidx-emu := \ + msg.c \ + diff --git a/src/main/ezusb2-iidx-emu/msg.c b/src/main/ezusb2-iidx-emu/msg.c new file mode 100644 index 0000000..a0663ac --- /dev/null +++ b/src/main/ezusb2-iidx-emu/msg.c @@ -0,0 +1,202 @@ +#define LOG_MODULE "ezusb2-iidx-emu-msg" + +#include +#include +#include + +#include "bemanitools/iidxio.h" + +#include "hook/iohook.h" + +#include "ezusb-emu/msg.h" + +#include "ezusb-iidx-emu/msg.h" +#include "ezusb-iidx-emu/nodes.h" + +#include "ezusb2-iidx/msg.h" + +#include "util/hex.h" +#include "util/log.h" + +/* ------------------------------------------------------------------------ */ + +static HRESULT ezusb2_iidx_emu_msg_interrupt_read(struct iobuf *read); +static HRESULT ezusb2_iidx_emu_msg_interrupt_write(struct const_iobuf *write); +static HRESULT ezusb2_iidx_emu_msg_bulk_read(struct iobuf *read); +static HRESULT ezusb2_iidx_emu_msg_bulk_write(struct const_iobuf *write); + +/* ------------------------------------------------------------------------ */ + +static struct ezusb_emu_msg_hook ezusb2_iidx_emu_msg_hook = { + .interrupt_read = ezusb2_iidx_emu_msg_interrupt_read, + .interrupt_write = ezusb2_iidx_emu_msg_interrupt_write, + .bulk_read = ezusb2_iidx_emu_msg_bulk_read, + .bulk_write = ezusb2_iidx_emu_msg_bulk_write +}; + +/* ------------------------------------------------------------------------ */ + +static const struct ezusb_iidx_emu_node* ezusb2_iidx_emu_msg_nodes[256] = +{ + [EZUSB_IIDX_MSG_NODE_16SEG] = &ezusb_iidx_emu_node_16seg, + [EZUSB_IIDX_MSG_NODE_COIN] = &ezusb_iidx_emu_node_coin, + [EZUSB_IIDX_MSG_NODE_NONE] = &ezusb_iidx_emu_node_none, + [EZUSB_IIDX_MSG_NODE_SECURITY_MEM] = &ezusb_iidx_emu_node_security_mem_v2, + [EZUSB_IIDX_MSG_NODE_SECURITY_PLUG] = &ezusb_iidx_emu_node_security_plug_v2, +}; + +static uint8_t ezusb2_iidx_emu_msg_status = 0; +static uint8_t ezusb2_iidx_emu_msg_seq_no = 0; +static uint8_t ezusb2_iidx_emu_msg_read_cur_node = 0; + +/* ------------------------------------------------------------------------ */ + +struct ezusb_emu_msg_hook* ezusb2_iidx_emu_msg_init(void) +{ + /* Init all nodes */ + for (uint32_t i = 0; i < 256; i++) { + + /* "Constructor" optional */ + if (ezusb2_iidx_emu_msg_nodes[i] && + ezusb2_iidx_emu_msg_nodes[i]->init_node) { + ezusb2_iidx_emu_msg_nodes[i]->init_node(); + } + } + + return &ezusb2_iidx_emu_msg_hook; +} + +static HRESULT ezusb2_iidx_emu_msg_interrupt_read(struct iobuf *read) +{ + struct ezusb2_iidx_msg_interrupt_read_packet* msg_resp = + (struct ezusb2_iidx_msg_interrupt_read_packet*) read->bytes; + + if (!iidx_io_ep2_recv()) { + return E_FAIL; + } + + msg_resp->p1_turntable = iidx_io_ep2_get_turntable(0); + msg_resp->p2_turntable = iidx_io_ep2_get_turntable(1); + + msg_resp->sliders[0] = iidx_io_ep2_get_slider(0) | + (iidx_io_ep2_get_slider(1) << 4); + + msg_resp->sliders[1] = iidx_io_ep2_get_slider(2) | + (iidx_io_ep2_get_slider(3) << 4); + + msg_resp->sliders[2] = iidx_io_ep2_get_slider(4); + + msg_resp->inverted_pad = ((iidx_io_ep2_get_keys() & 0x3FFF) << 16) | + (iidx_io_ep2_get_panel() & 0x0F) | + ((iidx_io_ep2_get_sys() & 0x03) << 4) | + (((iidx_io_ep2_get_sys() >> 2) & 0x01) << 30); + + msg_resp->inverted_pad = ~msg_resp->inverted_pad; + + msg_resp->status = ezusb2_iidx_emu_msg_status; + /* Reset status after delivered (important for eeprom reading) */ + ezusb2_iidx_emu_msg_status = 0; + + msg_resp->seq_no = ezusb2_iidx_emu_msg_seq_no++; + + read->pos = sizeof(*msg_resp); + + return S_OK; +} + +static HRESULT ezusb2_iidx_emu_msg_interrupt_write(struct const_iobuf *write) +{ + const struct ezusb2_iidx_msg_interrupt_write_packet* msg_req = + (const struct ezusb2_iidx_msg_interrupt_write_packet*) write->bytes; + + if (write->nbytes < sizeof(*msg_req)) { + log_warning("Interrupt write message too small"); + + return E_INVALIDARG; + } + + if (!ezusb2_iidx_emu_msg_nodes[msg_req->node]) { + ezusb2_iidx_emu_msg_read_cur_node = 0; + log_warning("Unrecognised node in interrupt message: %02x", + msg_req->node); + + return E_INVALIDARG; + } + + iidx_io_ep1_set_deck_lights(msg_req->deck_lights); + iidx_io_ep1_set_panel_lights(msg_req->panel_lights); + iidx_io_ep1_set_top_lamps(msg_req->top_lamps); + iidx_io_ep1_set_top_neons(msg_req->top_neons); + + if (!iidx_io_ep1_send()) { + return E_FAIL; + } + + /* 16seg data is provided with the request and not handled using a + separate bulk endpoint like on the C02 IO board */ + + if (!iidx_io_ep3_write_16seg((const char*) msg_req->seg16)) { + return E_FAIL; + } + + /* Remember node for next bulk read */ + + ezusb2_iidx_emu_msg_read_cur_node = msg_req->node; + ezusb2_iidx_emu_msg_status = ezusb2_iidx_emu_msg_nodes[msg_req->node]-> + process_cmd(msg_req->cmd, msg_req->cmd_detail[0], + msg_req->cmd_detail[1]); + + return S_OK; +} + +static HRESULT ezusb2_iidx_emu_msg_bulk_read(struct iobuf *read) +{ + struct ezusb_iidx_msg_bulk_packet* pkt = + (struct ezusb_iidx_msg_bulk_packet*) read->bytes; + + if (read->nbytes < sizeof(*pkt)) { + log_warning("Bulk read buffer too small"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + if (!ezusb2_iidx_emu_msg_nodes[ezusb2_iidx_emu_msg_read_cur_node]) { + log_warning("Bulk read unsupported on cur_node = %d", + ezusb2_iidx_emu_msg_read_cur_node); + + return E_NOTIMPL; + } + + if (!ezusb2_iidx_emu_msg_nodes[ezusb2_iidx_emu_msg_read_cur_node]-> + read_packet(pkt)) { + return E_FAIL; + } + + read->pos = sizeof(*pkt); + + return S_OK; +} + +static HRESULT ezusb2_iidx_emu_msg_bulk_write(struct const_iobuf *write) +{ + const struct ezusb_iidx_msg_bulk_packet* pkt = + (const struct ezusb_iidx_msg_bulk_packet*) write->bytes; + + if (write->nbytes < sizeof(*pkt)) { + log_warning("Bulk write packet too small"); + + return E_INVALIDARG; + } + + if (!ezusb2_iidx_emu_msg_nodes[pkt->node]) { + log_warning("Bulk write not supported on pkt->node = %02x", pkt->node); + + return E_NOTIMPL; + } + + if (!ezusb2_iidx_emu_msg_nodes[pkt->node]->write_packet(pkt)) { + return E_FAIL; + } + + return S_OK; +} diff --git a/src/main/ezusb2-iidx-emu/msg.h b/src/main/ezusb2-iidx-emu/msg.h new file mode 100644 index 0000000..8036096 --- /dev/null +++ b/src/main/ezusb2-iidx-emu/msg.h @@ -0,0 +1,19 @@ +#ifndef EZUSB2_IIDX_EMU_MSG_H +#define EZUSB2_IIDX_EMU_MSG_H + +#include +#include + +#include "hook/iohook.h" + +#include "ezusb-emu/msg.h" + +/** + * Init the fully emulated IIDX msg backend for a EZUSB FX2 (IO2) board + * + * @return ezusb_emu_msg_hook structure with hook calls for ezusb msg + * dispatching + */ +struct ezusb_emu_msg_hook* ezusb2_iidx_emu_msg_init(void); + +#endif diff --git a/src/main/ezusb2-iidx/Module.mk b/src/main/ezusb2-iidx/Module.mk new file mode 100644 index 0000000..461e0d6 --- /dev/null +++ b/src/main/ezusb2-iidx/Module.mk @@ -0,0 +1,6 @@ +libs += ezusb2-iidx + +libs_ezusb2-iidx := \ + +src_ezusb2-iidx := \ + ezusb2-iidx.c \ diff --git a/src/main/ezusb2-iidx/ezusb2-iidx.c b/src/main/ezusb2-iidx/ezusb2-iidx.c new file mode 100644 index 0000000..43f96bd --- /dev/null +++ b/src/main/ezusb2-iidx/ezusb2-iidx.c @@ -0,0 +1,28 @@ +#define LOG_MODULE "ezusb2-iidx" + +#include "ezusb2/ezusb2.h" + +#include "msg.h" + +enum ezusb2_iidx_msg_pipe { + EZUSB2_IIDX_MSG_PIPE_INTERRUPT_OUT = 0x01, + EZUSB2_IIDX_MSG_PIPE_BULK_OUT = 0x02, + + EZUSB2_IIDX_MSG_PIPE_INTERRUPT_IN = 0x81, + /* yes, 0x86 and NOT 0x82... */ + EZUSB2_IIDX_MSG_PIPE_BULK_IN = 0x86 +}; + +bool ezusb2_iidx_interrupt_read(HANDLE handle, + struct ezusb2_iidx_msg_interrupt_read_packet* packet) +{ + return ezusb2_endpoint_transfer(handle, EZUSB2_IIDX_MSG_PIPE_INTERRUPT_IN, + (void*) packet, sizeof(struct ezusb2_iidx_msg_interrupt_read_packet)); +} + +bool ezusb2_iidx_interrupt_write(HANDLE handle, + const struct ezusb2_iidx_msg_interrupt_write_packet* packet) +{ + return ezusb2_endpoint_transfer(handle, EZUSB2_IIDX_MSG_PIPE_INTERRUPT_OUT, + (void*) packet, sizeof(struct ezusb2_iidx_msg_interrupt_write_packet)); +} \ No newline at end of file diff --git a/src/main/ezusb2-iidx/ezusb2-iidx.h b/src/main/ezusb2-iidx/ezusb2-iidx.h new file mode 100644 index 0000000..c8a8d31 --- /dev/null +++ b/src/main/ezusb2-iidx/ezusb2-iidx.h @@ -0,0 +1,16 @@ +#ifndef EZUSB2_IIDX_H +#define EZUSB2_IIDX_H + +#include + +#include + +#include "msg.h" + +bool ezusb2_iidx_interrupt_read(HANDLE handle, + struct ezusb2_iidx_msg_interrupt_read_packet* packet); + +bool ezusb2_iidx_interrupt_write(HANDLE handle, + const struct ezusb2_iidx_msg_interrupt_write_packet* packet); + +#endif \ No newline at end of file diff --git a/src/main/ezusb2-iidx/msg.h b/src/main/ezusb2-iidx/msg.h new file mode 100644 index 0000000..58ccbb9 --- /dev/null +++ b/src/main/ezusb2-iidx/msg.h @@ -0,0 +1,102 @@ +#ifndef EZUSB2_IIDX_MSG_H +#define EZUSB2_IIDX_MSG_H + +#include +#include + +#pragma pack(push, 1) +struct ezusb2_iidx_msg_interrupt_write_packet { + uint8_t unk0; + uint8_t unk1; + uint8_t node; + uint8_t cmd; + uint8_t cmd_detail[2]; + uint8_t unk2; + uint8_t unk3; + uint8_t panel_lights; + uint8_t unk4; + uint8_t unk5; + uint16_t deck_lights; + uint8_t unk6; + uint8_t top_lamps; + uint8_t top_neons; + uint8_t seg16[9]; + /* Pad to 64 byte total size because the endpoint expects this buffer size. + Requests are hanging on ioctl calls otherwise. */ + uint8_t padding[39]; +}; +#pragma pack(pop) + +/* Some static asserts to ensure that lights are at correct offsets */ +_Static_assert(offsetof(struct ezusb2_iidx_msg_interrupt_write_packet, + panel_lights) == 8, "panel_lights is at wrong offset for fx2 packet"); +_Static_assert(offsetof(struct ezusb2_iidx_msg_interrupt_write_packet, + deck_lights) == 11, "deck_lights is at wrong offset for fx2 packet"); +_Static_assert(offsetof(struct ezusb2_iidx_msg_interrupt_write_packet, + top_lamps) == 14, "top_lamps is at wrong offset for fx2 packet"); +_Static_assert(offsetof(struct ezusb2_iidx_msg_interrupt_write_packet, + top_neons) == 15, "top_neons is at wrong offset for fx2 packet"); +_Static_assert(offsetof(struct ezusb2_iidx_msg_interrupt_write_packet, + padding[39]) == 64, "Last padding element is at wrong offset for fx2 write packet"); + +struct ezusb2_iidx_msg_interrupt_read_packet { + /* + Pad mappings: + 0: P1_Start + 1: P2_Start + 2: VEFX + 3: Effector + 4: Test + 5: Service + 6: unknown/not used + 7: unknown/not used + + 8: unknown/not used + 9: unknown/not used + 10: unknown/not used + 11: unknown/not used + 12: unknown/not used + 13: unknown/not used + 14: unknown/not used + 15: unknown/not used + + 16: P1_1 + 17: P1_2 + 18: P1_3 + 19: P1_4 + 20: P1_5 + 21: P1_6 + 22: P1_7 + 23: P2_1 + + 24: P2_2 + 25: P2_3 + 26: P2_4 + 27: P2_5 + 28: P2_6 + 29: P2_7 + 30: Coin mech + 31: Not used + */ + uint8_t unk0; + uint8_t unk1; + uint8_t unk2; + uint8_t seq_no; + uint8_t status; + uint8_t unk3; + uint8_t unk4; + uint8_t unk5; + uint32_t inverted_pad; + uint8_t unk6; + uint8_t p2_turntable; + uint8_t p1_turntable; + uint8_t sliders[3]; + /* Pad to 64 byte total size because the endpoint expects this buffer size. + Requests are hanging on ioctl calls otherwise. */ + uint8_t padding[46]; +}; + +_Static_assert(offsetof(struct ezusb2_iidx_msg_interrupt_read_packet, + padding[45]) == 63, "Last padding element is at wrong offset for fx2 read packet"); + +#endif diff --git a/src/main/ezusb2-tool/Module.mk b/src/main/ezusb2-tool/Module.mk new file mode 100644 index 0000000..0611565 --- /dev/null +++ b/src/main/ezusb2-tool/Module.mk @@ -0,0 +1,12 @@ +exes += ezusb2-tool + +ldflags_ezusb2-tool := \ + -lsetupapi \ + +libs_ezusb2-tool := \ + ezusb2 \ + ezusb \ + util \ + +src_ezusb2-tool := \ + main.c \ diff --git a/src/main/ezusb2-tool/main.c b/src/main/ezusb2-tool/main.c new file mode 100644 index 0000000..745acc9 --- /dev/null +++ b/src/main/ezusb2-tool/main.c @@ -0,0 +1,155 @@ +#include + +#include +#include + +#include "ezusb/util.h" +#include "ezusb2/ezusb2.h" + +#include "util/log.h" + +static int scan() +{ + char* path; + + log_misc("Scanning..."); + + path = ezusb2_find(&EZUSB2_GUID); + + if (!path) { + log_fatal("Could not find a connected ezusb fx2 device.\n"); + return -2; + } else { + printf("%s\n", path); + return 0; + } +} + +static int info(const char* dev_path) +{ + HANDLE handle; + struct ezusb_ident ident; + + log_misc("Opening device %s", dev_path); + + handle = ezusb2_open(dev_path); + + if (handle == INVALID_HANDLE_VALUE) { + log_fatal("Could not open device %s", dev_path); + return -3; + } + + if (!ezusb2_get_ident(handle, &ident)) { + log_fatal("Getting ident information failed"); + return -4; + } + + ezusb2_close(handle); + + log_info("Ident information format: ,,"); + printf("%04X,%04X,%s\n", ident.vid, ident.pid, ident.name); + + return 0; +} + +static int flash(const char* dev_path, const char* fw_path) +{ + HANDLE handle; + struct ezusb_firmware* fw; + + fw = ezusb_firmware_load(fw_path); + + if (!fw) { + log_fatal("Loading firmware from file '%s' failed", fw_path); + return -5; + } + + log_misc("Loaded firmware, crc 0x%X, segments %d", fw->crc, + fw->segment_count); + + if (ezusb_firmware_crc(fw) != fw->crc) { + log_fatal("Firmware CRC check failed"); + return -6; + } + + log_misc("Opening device %s", dev_path); + + handle = ezusb2_open(dev_path); + + if (handle == INVALID_HANDLE_VALUE) { + ezusb_firmware_free(fw); + log_fatal("Could not open device %s", dev_path); + return -7; + } + + log_misc("Flashing firmware..."); + + if (!ezusb2_download_firmware(handle, fw)) { + log_fatal("Flashing firmware failed"); + return -8; + } else { + log_info("Flashing firmware successful"); + } + + ezusb2_close(handle); + ezusb_firmware_free(fw); + + return 0; +} + +static void usage(const char* argv0) +{ + printf("ezusb2-tool for EZUSB FX2 hardware, e.g. IIDX/Pop'n IO2, build " + __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: %s [cmd] ...\n" + "Available commands:\n" + " scan: Scan for connected EZUSB FX2 devices and output the " + "device path\n" + " info: Get basic information (vid, pid, name) of a connected " + "device\n" + " flash: Flash a firmware binary\n", argv0); +} + +int main(int argc, char** argv) +{ + int arg_pos; + + if (argc < 2) { + usage(argv[0]); + return -1; + } + + arg_pos = 1; + + log_to_writer(log_writer_stderr, NULL); + + if (!strcmp(argv[arg_pos], "scan")) { + return scan(); + } else if (!strcmp(argv[arg_pos], "info")) { + arg_pos++; + + if (arg_pos >= argc) { + printf("Usage: info [dev_path]\n" + " dev_path: Device path of device to query. Use the 'scan' " + "command to aquire a device path of a connected device\n"); + return -1; + } + + return info(argv[arg_pos]); + } else if (!strcmp(argv[arg_pos], "flash")) { + arg_pos++; + + if (arg_pos + 1 >= argc) { + printf("Usage: flash [dev_path] [fw_path]\n" + " dev_path: Device path of device to flash. Use the 'scan' " + "command to aquire a device path of a connected device\n" + " fw_path: Path to firmware binary file to flash\n"); + return -1; + } + + return flash(argv[arg_pos], argv[arg_pos + 1]); + } else { + usage(argv[0]); + return -1; + } +} \ No newline at end of file diff --git a/src/main/ezusb2/Module.mk b/src/main/ezusb2/Module.mk new file mode 100644 index 0000000..bf64a51 --- /dev/null +++ b/src/main/ezusb2/Module.mk @@ -0,0 +1,9 @@ +libs += ezusb2 + +libs_ezusb := \ + ezusb \ + +ldflags_ezusb2 := \ + +src_ezusb2 := \ + ezusb2.c \ diff --git a/src/main/ezusb2/cyioctl.h b/src/main/ezusb2/cyioctl.h new file mode 100644 index 0000000..386a85a --- /dev/null +++ b/src/main/ezusb2/cyioctl.h @@ -0,0 +1,206 @@ +/* + +Copyright (c) 2011 Cypress Semiconductor Corporation + +Module Name: + + CyIoclt.h + +Additional Notes: + + THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR + PURPOSE. + +*/ +#ifndef __IOCTL_H__ +#define __IOCTL_H__ + + +#ifndef DRIVER + +#ifndef CTL_CODE + #include +#endif + +#ifndef BM_REQUEST_TYPE + #include "usb200.h" +#endif + +#define DIR_HOST_TO_DEVICE 0 +#define DIR_DEVICE_TO_HOST 1 + +#define DEVICE_SPEED_UNKNOWN 0x00000000 +#define DEVICE_SPEED_LOW_FULL 0x00000001 +#define DEVICE_SPEED_HIGH 0x00000002 + +typedef struct _WORD_SPLIT { + UCHAR lowByte; + UCHAR hiByte; +} WORD_SPLIT, *PWORD_SPLIT; + +typedef struct _BM_REQ_TYPE { + UCHAR Recipient:2; + UCHAR Reserved:3; + UCHAR Type:2; + UCHAR Direction:1; +} BM_REQ_TYPE, *PBM_REQ_TYPE; + +typedef struct _SETUP_PACKET { + + union { + BM_REQ_TYPE bmReqType; + UCHAR bmRequest; + }; + + UCHAR bRequest; + + union { + WORD_SPLIT wVal; + USHORT wValue; + }; + + union { + WORD_SPLIT wIndx; + USHORT wIndex; + }; + + union { + WORD_SPLIT wLen; + USHORT wLength; + }; + + ULONG ulTimeOut; + +} SETUP_PACKET, *PSETUP_PACKET; + +#define USB_ISO_ID 0x4945 +#define USB_ISO_CMD_ASAP 0x8000 +#define USB_ISO_CMD_CURRENT_FRAME 0x8001 +#define USB_ISO_CMD_SET_FRAME 0x8002 + +typedef struct _ISO_ADV_PARAMS { + + USHORT isoId; + USHORT isoCmd; + + ULONG ulParam1; + ULONG ulParam2; + +} ISO_ADV_PARAMS, *PISO_ADV_PARAMS; + +typedef struct _ISO_PACKET_INFO { + ULONG Status; + ULONG Length; +} ISO_PACKET_INFO, *PISO_PACKET_INFO; + +/* icex2 changes: force packed struct */ +#pragma pack(push, packing) +#pragma pack(1) +typedef struct _SINGLE_TRANSFER { + union { + SETUP_PACKET SetupPacket; + ISO_ADV_PARAMS IsoParams; + }; + + UCHAR reserved; + + UCHAR ucEndpointAddress; + ULONG NtStatus; + ULONG UsbdStatus; + ULONG IsoPacketOffset; + ULONG IsoPacketLength; + ULONG BufferOffset; + ULONG BufferLength; +} SINGLE_TRANSFER, *PSINGLE_TRANSFER; +#pragma pack(pop, packing) + +#endif // #ifndef DRIVER + +typedef struct _SET_TRANSFER_SIZE_INFO { + UCHAR EndpointAddress; + ULONG TransferSize; +} SET_TRANSFER_SIZE_INFO, *PSET_TRANSFER_SIZE_INFO; + + +// +// Macro to extract function out of the device io control code +// +#ifdef WIN_98_DDK +#define DEVICE_TYPE_FROM_CTL_CODE(ctrlCode) (((ULONG)(ctrlCode & 0xffff0000)) >> 16) +#endif +#define FUNCTION_FROM_CTL_CODE(ctrlCode) (((ULONG)(ctrlCode & 0x00003FFC)) >> 2) +#define ACCESS_FROM_CTL_CODE(ctrlCode) (((ULONG)(ctrlCode & 0x000C0000)) >> 14) +//#define METHOD_FROM_CTL_CODE(ctrlCode) (((ULONG)(ctrlCode & 0x00000003))) + + +#define IOCTL_ADAPT_INDEX 0x0000 + +// Get the driver version +#define IOCTL_ADAPT_GET_DRIVER_VERSION CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Get the current USBDI version +#define IOCTL_ADAPT_GET_USBDI_VERSION CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+1, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Get the current device alt interface settings from driver +#define IOCTL_ADAPT_GET_ALT_INTERFACE_SETTING CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+2, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Set the device interface and alt interface setting +#define IOCTL_ADAPT_SELECT_INTERFACE CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+3, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Get device address from driver +#define IOCTL_ADAPT_GET_ADDRESS CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+4, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Get number of endpoints for current interface and alt interface setting from driver +#define IOCTL_ADAPT_GET_NUMBER_ENDPOINTS CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+5, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Get the current device power state +#define IOCTL_ADAPT_GET_DEVICE_POWER_STATE CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+6, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Set the device power state +#define IOCTL_ADAPT_SET_DEVICE_POWER_STATE CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+7, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Send a raw packet to endpoint 0 +#define IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+8, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Send/receive data to/from nonep0 +#define IOCTL_ADAPT_SEND_NON_EP0_TRANSFER CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+9, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Simulate a disconnect/reconnect +#define IOCTL_ADAPT_CYCLE_PORT CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+10, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Reset the pipe +#define IOCTL_ADAPT_RESET_PIPE CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+11, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Reset the device +#define IOCTL_ADAPT_RESET_PARENT_PORT CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+12, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Get the current transfer size of an endpoint (in number of bytes) +#define IOCTL_ADAPT_GET_TRANSFER_SIZE CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+13, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Set the transfer size of an endpoint (in number of bytes) +#define IOCTL_ADAPT_SET_TRANSFER_SIZE CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+14, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Return the name of the device +#define IOCTL_ADAPT_GET_DEVICE_NAME CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+15, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Return the "Friendly Name" of the device +#define IOCTL_ADAPT_GET_FRIENDLY_NAME CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+16, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Abort all outstanding transfers on the pipe +#define IOCTL_ADAPT_ABORT_PIPE CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+17, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Send/receive data to/from nonep0 w/ direct buffer acccess (no buffering) +#define IOCTL_ADAPT_SEND_NON_EP0_DIRECT CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+18, METHOD_NEITHER, FILE_ANY_ACCESS) + +// Return device speed +#define IOCTL_ADAPT_GET_DEVICE_SPEED CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+19, METHOD_BUFFERED, FILE_ANY_ACCESS) + +// Get the current USB frame number +#define IOCTL_ADAPT_GET_CURRENT_FRAME CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_ADAPT_INDEX+20, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define NUMBER_OF_ADAPT_IOCTLS 21 // Last IOCTL_ADAPT_INDEX + 1 + + +#endif // __IOCTL_H__ \ No newline at end of file diff --git a/src/main/ezusb2/ezusb2.c b/src/main/ezusb2/ezusb2.c new file mode 100644 index 0000000..b181831 --- /dev/null +++ b/src/main/ezusb2/ezusb2.c @@ -0,0 +1,295 @@ +#include +#include + +#include "ezusb/ezusb.h" + +#include "ezusb2/cyioctl.h" +#include "ezusb2/ezusb2.h" + +#include "util/crc.h" +#include "util/fs.h" +#include "util/log.h" +#include "util/str.h" + +#define REQ_TYPE_HOST_TO_DEV 0x40 +#define REQ_TYPE_DEV_TO_HOST 0x80 + +#define REQ_TIMEOUT_SEC 5 +#define REQ_CODE_EP0 0x00 + +#define EP0_GET_DESCRIPTOR 0x06 +#define EP0_EZUSB_WRITE_RAM 0xA0 + +/* nope, it's not 0x01, probably an endianess mistake (?) */ +#define EP0_DESCRIPTOR_TYPE_DEVICE 0x0100 +/* Also not 0x03, 0x0301 from DeviceIoCtl dump */ +#define EP0_DESCRIPTOR_TYPE_STRING 0x0301 + +#define EP0_EZUSB_RAM_CPU_RESET_ADDR 0xE600 + +static int ezusb2_ctrl_transfer(HANDLE handle, uint32_t ioctl_code, + uint8_t endpoint, uint8_t req_type, uint8_t req, uint16_t value, + uint16_t index, void* buf, uint32_t buf_len) +{ + int xmit_buf_size = sizeof(SINGLE_TRANSFER) + buf_len; + char xmit_buf[xmit_buf_size]; + DWORD ret_len; + + memset(xmit_buf, 0, xmit_buf_size); + + PSINGLE_TRANSFER transfer = (PSINGLE_TRANSFER) xmit_buf; + + memset(transfer, 0, sizeof(SINGLE_TRANSFER)); + + transfer->SetupPacket.bmRequest = req_type; + transfer->SetupPacket.bRequest = req; + transfer->SetupPacket.wValue = value; + transfer->SetupPacket.wIndex = index; + transfer->SetupPacket.wLength = buf_len; + /* Timeout is in seconds, wtf ?! */ + transfer->SetupPacket.ulTimeOut = REQ_TIMEOUT_SEC; + transfer->ucEndpointAddress = endpoint; + transfer->BufferOffset = sizeof(SINGLE_TRANSFER); + transfer->BufferLength = buf_len; + /* All other attributes 0 */ + + if (buf) { + memcpy(xmit_buf + sizeof(SINGLE_TRANSFER), buf, buf_len); + } + + if (!DeviceIoControl(handle, ioctl_code, xmit_buf, xmit_buf_size, xmit_buf, + xmit_buf_size, &ret_len, NULL)) { + return -1; + } + + if (buf) { + memcpy(buf, xmit_buf + sizeof(SINGLE_TRANSFER), buf_len); + } + + return ret_len - sizeof(SINGLE_TRANSFER); +} + +static int ezusb2_ep0_transfer(HANDLE handle, uint8_t req_type, uint8_t req, + uint16_t value, uint16_t index, void* buf, uint32_t buf_len) +{ + return ezusb2_ctrl_transfer(handle, IOCTL_ADAPT_SEND_EP0_CONTROL_TRANSFER, + REQ_CODE_EP0, req_type, req, value, index, buf, buf_len); +} + +/* Any other endpoint than 0 */ +static int ezusb2_epx_transfer(HANDLE handle, uint8_t endpoint, + uint8_t req_type, uint8_t req, uint16_t value, uint16_t index, + void* buf, uint32_t buf_len) +{ + return ezusb2_ctrl_transfer(handle, IOCTL_ADAPT_SEND_NON_EP0_TRANSFER, + endpoint, req_type, req, value, index, buf, buf_len); +} + +static char* ezusb2_get_device_name(HANDLE handle) +{ + const int len = 256; + char buf[len]; + DWORD received; + + memset(&buf, 0, len); + + if (!DeviceIoControl(handle, IOCTL_ADAPT_GET_FRIENDLY_NAME, &buf, len, &buf, + len, &received, NULL)) { + return NULL; + } + + return str_dup((const char*) &buf); +} + +static bool ezusb2_get_device_descriptor(HANDLE handle, + USB_DEVICE_DESCRIPTOR* desc) +{ + int res; + + memset(desc, 0, sizeof(USB_DEVICE_DESCRIPTOR)); + + /* 0: not used */ + res = ezusb2_ep0_transfer(handle, REQ_TYPE_DEV_TO_HOST, EP0_GET_DESCRIPTOR, + EP0_DESCRIPTOR_TYPE_DEVICE, 0, desc, sizeof(USB_DEVICE_DESCRIPTOR)); + + if (res != sizeof(USB_DEVICE_DESCRIPTOR)) { + return false; + } + + return true; +} + +static bool ezusb2_write_ram(HANDLE handle, const void* buffer, + uint16_t ram_offset, uint32_t size) +{ + return ezusb2_ep0_transfer(handle, REQ_TYPE_HOST_TO_DEV, EP0_EZUSB_WRITE_RAM, + ram_offset, 0, (void*) buffer, size) == size; +} + +static bool ezusb2_reset(HANDLE handle, bool hold) +{ + uint8_t flag; + + /* Write CPU state */ + flag = hold ? 1 : 0; + + return ezusb2_write_ram(handle, &flag, EP0_EZUSB_RAM_CPU_RESET_ADDR, + sizeof(uint8_t)); +} + +char* ezusb2_find(const GUID* guid) +{ + HDEVINFO info; + SP_DEVICE_INTERFACE_DATA iface; + BOOL result; + ULONG required_len; + PSP_DEVICE_INTERFACE_DETAIL_DATA detail; + ULONG length; + char* res; + + log_assert(guid); + + result = FALSE; + required_len = 0; + detail = NULL; + + info = SetupDiGetClassDevs(guid, NULL, NULL, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (info == INVALID_HANDLE_VALUE) { + return NULL; + } + + /* Enum devices that support the GUID, get first only */ + memset(&iface, 0, sizeof(iface)); + iface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + result = SetupDiEnumDeviceInterfaces(info, NULL, guid, 0, &iface); + + if (!result) { + SetupDiDestroyDeviceInfoList(info); + return NULL; + } + + /* Get information about the found device interface. We don't + know how much memory to allocate to get this information, so + we will ask by passing in a null buffer and location to + receive the size of the buffer needed. */ + result = SetupDiGetDeviceInterfaceDetail(info, &iface, NULL, 0, + &required_len, NULL); + + if (!required_len) { + return NULL; + } + + /* Okay, we got a size back, so let's allocate memory + for the interface detail information we want. */ + detail = (PSP_DEVICE_INTERFACE_DETAIL_DATA) LocalAlloc(LMEM_FIXED, + required_len); + + if (detail == NULL) { + SetupDiDestroyDeviceInfoList(info); + return NULL; + } + + detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + length = required_len; + + result = SetupDiGetDeviceInterfaceDetail(info, &iface, detail, + length, &required_len, NULL); + + if (!result) { + SetupDiDestroyDeviceInfoList(info); + LocalFree(detail); + return NULL; + } + + res = str_dup((const char*) &detail->DevicePath); + LocalFree(detail); + + SetupDiDestroyDeviceInfoList(info); + + return res; +} + +HANDLE ezusb2_open(const char* device_path) +{ + log_assert(device_path); + + return CreateFileA(device_path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, 0, NULL); +} + +bool ezusb2_get_ident(HANDLE handle, struct ezusb_ident* ident) +{ + char* name; + USB_DEVICE_DESCRIPTOR desc; + + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(handle); + log_assert(ident); + + memset(ident, 0, sizeof(struct ezusb_ident)); + + name = ezusb2_get_device_name(handle); + + if (!name) { + return false; + } + + memcpy(ident->name, name, strlen(name)); + free(name); + + if (!ezusb2_get_device_descriptor(handle, &desc)) { + return false; + } + + ident->vid = desc.idVendor; + ident->pid = desc.idProduct; + + return true; +} + +bool ezusb2_download_firmware(HANDLE handle, struct ezusb_firmware* fw) +{ + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(handle); + log_assert(fw); + + if (!ezusb2_reset(handle, true)) { + return false; + } + + for (uint16_t i = 0; i < fw->segment_count; i++) { + /* Important: Writing the full binary does NOT work on the FX2 device + compared to the legacy ezusb device */ + if (!ezusb2_write_ram(handle, fw->segments[i]->data, + fw->segments[i]->offset, fw->segments[i]->size)) { + return false; + } + } + + if (!ezusb2_reset(handle, false)) { + return false; + } + + return true; +} + +bool ezusb2_endpoint_transfer(HANDLE handle, uint8_t endpoint, void* data, + uint32_t size) +{ + if (endpoint == 0) { + return false; + } + + return ezusb2_epx_transfer(handle, endpoint, 0, 0, 0, 0, data, size); +} + +void ezusb2_close(HANDLE handle) +{ + log_assert(handle != INVALID_HANDLE_VALUE); + log_assert(handle); + + CloseHandle(handle); +} diff --git a/src/main/ezusb2/ezusb2.h b/src/main/ezusb2/ezusb2.h new file mode 100644 index 0000000..c559266 --- /dev/null +++ b/src/main/ezusb2/ezusb2.h @@ -0,0 +1,90 @@ +/** + * Module for fundamental communication with an Ezusb FX2 board, e.g. + * IIDX/Pop'n IO2. Credit also goes to willxinc for his initial implemention + * which, for some unknown reason, did not work on my machines. + * + * This module does not implement any sort of higher level protocol which is + * required by the games to exchange data with the device, e.g. getting input + * states of keys and setting light outputs. Further modules are utilizing this + * base layer to implement higher level communication. + * + * Note: This module requires the newer "cyusb3" driver which is compatible + * with WinXP, Win7, Win8 and Win10 (both 32 and 64-bit). The old "cyusb" driver + * does NOT work with this. + * + * @author icex2, willxinc + */ +#ifndef EZUSB2_H +#define EZUSB2_H + +#include +#include + +#include + +#include "ezusb/util.h" + +/** + * GUID of Ezusb FX2 board + */ +static const GUID EZUSB2_GUID = {0xAE18AA60L, 0x7F6A, 0x11D4, + {0x97, 0xDD, 0x00, 0x01, 0x02, 0x29, 0xB9, 0x59}}; + +/** + * Scan for connected devices which match the provided GUID. + * + * @param guid GUID of device to find. + * @return Allocated device path string of first device found matching the GUID + * provided. Caller has to free buffer. + */ +char* ezusb2_find(const GUID* guid); + +/** + * Open a connected Ezusb FX2 device. + * + * @param device_path Path to the Ezusb FX2 device obtained by using + * the ezusb2_find function. + * @return A valid handle to the device on success, INVALID_HANDLE_VALUE on + * error. + */ +HANDLE ezusb2_open(const char* device_path); + +/** + * Get identifier information of the device. + * + * @param handle Valid device handle. + * @param ident Pointer to memory to write the identifier information to. + * @return True if successful and identifier information was written to the + * the struct, false on error. + */ +bool ezusb2_get_ident(HANDLE handle, struct ezusb_ident* ident); + +/** + * Download a firmware to the ezusb device. + * + * @param handle Valid device handle. + * @param fw Firmware to write to the target ezusb device. + * @return True on success, false on failure. + */ +bool ezusb2_download_firmware(HANDLE handle, struct ezusb_firmware* fw); + +/** + * Execute a data transfer to a specific endpoint other than endpoint 0. + * + * @param handle Valid device handle. + * @param endpoint Target endpoint, in and out valid but not 0. + * @param data Buffer with data to transfer or for data to receive. + * @param size Size of buffer. + * @return True on success, false on failure. + */ +bool ezusb2_endpoint_transfer(HANDLE handle, uint8_t endpoint, void* data, + uint32_t size); + +/** + * Close an opened ezusb device handle. + * + * @param handle Valid handle to close. + */ +void ezusb2_close(HANDLE handle); + +#endif \ No newline at end of file diff --git a/src/main/geninput/Module.mk b/src/main/geninput/Module.mk new file mode 100644 index 0000000..1521d80 --- /dev/null +++ b/src/main/geninput/Module.mk @@ -0,0 +1,32 @@ +dlls += geninput + +ldflags_geninput := \ + -lhid \ + -lsetupapi \ + +libs_geninput := \ + util \ + +src_geninput := \ + dev-list.c \ + guid.c \ + hid.c \ + hid-generic.c \ + hid-generic-strings.c \ + hid-meta-in.c \ + hid-meta-out.c \ + hid-mgr.c \ + hid-report-in.c \ + hid-report-out.c \ + hotplug.c \ + input.c \ + io-thread.c \ + kbd.c \ + kbd-data.c \ + mapper.c \ + mapper-s11n.c \ + mouse.c \ + msg-thread.c \ + pacdrive.c \ + ri.c \ + diff --git a/src/main/geninput/dev-list.c b/src/main/geninput/dev-list.c new file mode 100644 index 0000000..ad657e1 --- /dev/null +++ b/src/main/geninput/dev-list.c @@ -0,0 +1,173 @@ +#include +#include +#include + +#include +#include +#include + +#include "geninput/dev-list.h" + +#include "util/log.h" +#include "util/mem.h" + +void dev_list_init(struct dev_list *devs, const GUID *class_guid) +{ + devs->class_guid = (GUID *) class_guid; + devs->infolist = SetupDiGetClassDevs(devs->class_guid, NULL, NULL, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (devs->infolist == NULL) { + log_fatal("SetupDiGetClassDevs failed: %08x", + (unsigned int) GetLastError()); + } + + devs->dev_no = 0; + devs->iface_no = 0; + + devs->detail = NULL; + devs->name = NULL; +} + +bool dev_list_next(struct dev_list *devs) +{ + if (devs->detail != NULL) { + free(devs->detail); + + devs->detail = NULL; + } + + if (devs->name != NULL) { + free(devs->name); + + devs->name = NULL; + } + + for (;;) { + devs->dev.cbSize = sizeof(devs->dev); + + if (!SetupDiEnumDeviceInfo(devs->infolist, devs->dev_no, &devs->dev)) { + if (GetLastError() == ERROR_NO_MORE_ITEMS) { + return false; + } else { + log_fatal("SetupDiEnumDeviceInfo failed: %08x", + (unsigned int) GetLastError()); + } + } + + devs->iface.cbSize = sizeof(devs->iface); + + if (!SetupDiEnumDeviceInterfaces(devs->infolist, &devs->dev, + devs->class_guid, devs->iface_no, &devs->iface)) { + if (GetLastError() == ERROR_NO_MORE_ITEMS) { + devs->dev_no++; + devs->iface_no = 0; + + continue; + } else { + log_fatal("SetupDiEnumDeviceInterfaces failed: %08x", + (unsigned int) GetLastError()); + } + } + + devs->iface_no++; + + return true; + } +} + +const char *dev_list_get_dev_node(struct dev_list *devs) +{ + DWORD detail_size; + + if (devs->detail != NULL) { + return devs->detail->DevicePath; + } + + if (SetupDiGetDeviceInterfaceDetail(devs->infolist, &devs->iface, NULL, 0, + &detail_size, NULL) + || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + log_fatal("SetupDiGetDeviceInterfaceDetail sizing failed: %08x", + (unsigned int) GetLastError()); + } + + devs->detail = xmalloc(detail_size); + devs->detail->cbSize = sizeof(*devs->detail); + + if (!SetupDiGetDeviceInterfaceDetail(devs->infolist, &devs->iface, + devs->detail, detail_size, NULL, NULL)) { + log_fatal("SetupDiGetDeviceInterfaceDetail content failed: %08x", + (unsigned int) GetLastError()); + } + + return devs->detail->DevicePath; +} + +const wchar_t *dev_list_get_dev_name(struct dev_list *devs) +{ + DWORD name_size; + const char* dev_path; + // largest possible product string according to WinAPI docs + wchar_t product_string[126]; + DWORD product_size; + bool product_success; + + if (devs->name != NULL) { + return devs->name; + } + + if (SetupDiGetDeviceRegistryPropertyW(devs->infolist, &devs->dev, + SPDRP_DEVICEDESC, NULL, NULL, 0, &name_size) + || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + log_fatal("SetupDiGetDeviceRegistryPropertyW(SPDRP_DEVICEDESC) sizing " + "failed: %08x", (unsigned int) GetLastError()); + } + + /* This function returns arbitrary data, not just strings. Therefore, we + are actually working in units of bytes and not wchar_t's here for once */ + + devs->name = xmalloc(name_size); + + if (!SetupDiGetDeviceRegistryPropertyW(devs->infolist, &devs->dev, + SPDRP_DEVICEDESC, NULL, (void *) devs->name, name_size, 0)) { + log_fatal("SetupDiGetDeviceRegistryPropertyW(SPDRP_DEVICEDESC) content " + "failed: %08x", (unsigned int) GetLastError()); + } + + /* Also, try and get the product string - this is usually far more human + readable than the registry value, which is often "HID Compliant Thing". + We then append this in parentheses */ + dev_path = dev_list_get_dev_node(devs); + HANDLE hid_hnd = CreateFile(dev_path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if(hid_hnd) { + product_success = HidD_GetProductString(hid_hnd, product_string, sizeof(product_string)); + CloseHandle(hid_hnd); + if(product_success && wcslen(product_string) > 0 && wcscmp(product_string, devs->name)) { + // len + null + 2*parentheses + space separator + product_size = (wcslen(product_string)+4) * sizeof(wchar_t); + devs->name = xrealloc(devs->name, name_size + product_size); + wcscat(devs->name, L" ("); + wcscat(devs->name, product_string); + wcscat(devs->name, L")"); + } + } + + return devs->name; +} + +void dev_list_fini(struct dev_list *devs) +{ + if (devs->detail != NULL) { + free(devs->detail); + } + + if (devs->name != NULL) { + free(devs->name); + } + + if (!SetupDiDestroyDeviceInfoList(devs->infolist)) { + log_fatal("SetupDiDestroyDeviceList failed: %08x", + (unsigned int) GetLastError()); + } +} + diff --git a/src/main/geninput/dev-list.h b/src/main/geninput/dev-list.h new file mode 100644 index 0000000..feca672 --- /dev/null +++ b/src/main/geninput/dev-list.h @@ -0,0 +1,27 @@ +#ifndef GENINPUT_DEV_LIST_H +#define GENINPUT_DEV_LIST_H + +#include +#include + +#include +#include + +struct dev_list { + GUID *class_guid; + HDEVINFO infolist; + SP_DEVINFO_DATA dev; + SP_DEVICE_INTERFACE_DATA iface; + SP_DEVICE_INTERFACE_DETAIL_DATA *detail; + wchar_t *name; + unsigned int dev_no; + unsigned int iface_no; +}; + +void dev_list_init(struct dev_list *devs, const GUID *class_guid); +bool dev_list_next(struct dev_list *devs); +const char *dev_list_get_dev_node(struct dev_list *devs); +const wchar_t *dev_list_get_dev_name(struct dev_list *devs); +void dev_list_fini(struct dev_list *devs); + +#endif diff --git a/src/main/geninput/geninput.def b/src/main/geninput/geninput.def new file mode 100644 index 0000000..e91e82b --- /dev/null +++ b/src/main/geninput/geninput.def @@ -0,0 +1,50 @@ +LIBRARY geninput + +EXPORTS + action_iter_get_mapping + action_iter_get_action + action_iter_get_page + action_iter_is_valid + action_iter_next + action_iter_free + hid_mgr_get_first_stub + hid_mgr_get_named_stub + hid_mgr_get_next_stub + hid_mgr_lock + hid_mgr_unlock + hid_stub_get_name + hid_stub_get_device_usage + hid_stub_get_controls + hid_stub_get_dev_node + hid_stub_get_lights + hid_stub_get_value + hid_stub_is_attached + hid_stub_set_light + input_fini + input_init + input_set_loggers + light_iter_get_mapping + light_iter_get_game_light + light_iter_is_valid + light_iter_next + light_iter_free + mapper_config_load + mapper_config_save + mapper_clear_action_map + mapper_clear_light_map + mapper_get_action_map + mapper_get_analog_map + mapper_get_analog_sensitivity + mapper_get_npages + mapper_is_analog_absolute + mapper_iterate_lights + mapper_iterate_actions + mapper_set_action_map + mapper_set_analog_map + mapper_set_analog_sensitivity + mapper_set_light_map + mapper_set_nanalogs + mapper_set_nlights + mapper_read_analog + mapper_update + mapper_write_light diff --git a/src/main/geninput/guid.c b/src/main/geninput/guid.c new file mode 100644 index 0000000..b4f2dac --- /dev/null +++ b/src/main/geninput/guid.c @@ -0,0 +1,4 @@ +#include +#include + +#include "geninput/hid.h" diff --git a/src/main/geninput/hid-generic-strings.c b/src/main/geninput/hid-generic-strings.c new file mode 100644 index 0000000..cf2bd6f --- /dev/null +++ b/src/main/geninput/hid-generic-strings.c @@ -0,0 +1,49 @@ +#include +#include + +#include + +#include "geninput/hid-generic-strings.h" + +#include "util/defs.h" +#include "util/str.h" + +void hid_generic_strings_init(struct hid_generic_strings *strings, HANDLE fd) +{ + wchar_t wstr[128]; + + wstr_cpy(wstr, lengthof(wstr), L"???"); + HidD_GetProductString(fd, wstr, sizeof(wstr)); + strings->wstr_prod = wstr_dup(wstr); + + if (!wstr_narrow(wstr, &strings->str_prod)) { + strings->str_prod = str_dup("???"); + } + + wstr_cpy(wstr, lengthof(wstr), L"???"); + HidD_GetManufacturerString(fd, wstr, sizeof(wstr)); + strings->wstr_manf = wstr_dup(wstr); + + if (!wstr_narrow(wstr, &strings->str_manf)) { + strings->str_manf = str_dup("???"); + } +} + +const char *hid_generic_strings_get_manf(struct hid_generic_strings *strings) +{ + return strings->str_manf; +} + +const char *hid_generic_strings_get_prod(struct hid_generic_strings *strings) +{ + return strings->str_prod; +} + +void hid_generic_strings_fini(struct hid_generic_strings *strings) +{ + free(strings->str_manf); + free(strings->wstr_manf); + free(strings->str_prod); + free(strings->wstr_prod); +} + diff --git a/src/main/geninput/hid-generic-strings.h b/src/main/geninput/hid-generic-strings.h new file mode 100644 index 0000000..92ed12f --- /dev/null +++ b/src/main/geninput/hid-generic-strings.h @@ -0,0 +1,17 @@ +#ifndef GENINPUT_HID_GENERIC_STRINGS_H +#define GENINPUT_HID_GENERIC_STRINGS_H + +struct hid_generic_strings { + wchar_t *wstr_prod; + wchar_t *wstr_manf; + char *str_prod; + char *str_manf; +}; + +void hid_generic_strings_init(struct hid_generic_strings *strings, HANDLE fd); +const char *hid_generic_strings_get_manf(struct hid_generic_strings *strings); +const char *hid_generic_strings_get_prod(struct hid_generic_strings *strings); +void hid_generic_strings_fini(struct hid_generic_strings *strings); + + +#endif diff --git a/src/main/geninput/hid-generic.c b/src/main/geninput/hid-generic.c new file mode 100644 index 0000000..467445e --- /dev/null +++ b/src/main/geninput/hid-generic.c @@ -0,0 +1,408 @@ +#define LOG_MODULE "hid-generic" + +#include +#include +#include +#include +#include + +#include +#include + +#include "geninput/hid.h" +#include "geninput/hid-generic.h" +#include "geninput/hid-generic-strings.h" +#include "geninput/hid-meta-in.h" +#include "geninput/hid-meta-out.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +struct hid_generic { + struct hid_fd super; + + char *dev_node; + + HANDLE fd; + PHIDP_PREPARSED_DATA ppd; + + OVERLAPPED in_ovl; + OVERLAPPED out_ovl; + + struct hid_generic_strings strings; + struct hid_meta_in meta_in; + struct hid_meta_out meta_out; + + uint8_t *in_buf; + size_t in_nbytes; + + uint8_t *out_buf; + size_t out_nbytes; + bool out_started; + bool out_faulty; +}; + +static bool hid_generic_get_device_usage(const struct hid *hid, + uint32_t *usage); +static bool hid_generic_get_name(const struct hid *hid, wchar_t *chars, + size_t *nchars); +static bool hid_generic_get_controls(const struct hid *hid, + struct hid_control *controls, size_t *ncontrols); +static bool hid_generic_get_lights(const struct hid *hid, + struct hid_light *lights, size_t *nlights); +static bool hid_generic_get_value(struct hid *hid, size_t control_no, + int32_t *out_value); +static bool hid_generic_set_light(struct hid *hid, size_t light_no, + uint32_t intensity); +static bool hid_generic_handle_event(struct hid_fd *super, OVERLAPPED *ovl, + size_t nbytes); +static void hid_generic_close(struct hid *hid); + +static struct hid_fd_vtbl hid_generic_vtbl = {{ + /* .close = */ hid_generic_close, + /* .get_device_usage = */ hid_generic_get_device_usage, + /* .get_name = */ hid_generic_get_name, + /* .get_controls = */ hid_generic_get_controls, + /* .get_lights = */ hid_generic_get_lights, + /* .get_value = */ hid_generic_get_value, + /* .set_light = */ hid_generic_set_light }, + /* .handle_event = */ hid_generic_handle_event +}; + +bool hid_generic_open(struct hid_fd **out, const char *dev_node, HANDLE iocp, + uintptr_t iocp_ctx) +{ + struct hid_generic *hg; + uint32_t tlc_usage; + + hg = xmalloc(sizeof(*hg)); + + hg->fd = CreateFile(dev_node, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + + if (hg->fd == INVALID_HANDLE_VALUE) { + /* Probably a keyboard that's locked by the OS */ + goto open_fail; + } + + if (!HidD_GetPreparsedData(hg->fd, &hg->ppd)) { + log_warning("HidD_GetPreparsedData failed on dev node %s", dev_node); + + goto ppd_fail; + } + + if (!hid_meta_in_init(&hg->meta_in, hg->ppd)) { + goto meta_in_fail; + } + + tlc_usage = hid_meta_in_get_tlc_usage(&hg->meta_in); + + if ((tlc_usage >> 24) == 0xFF) { + log_misc("Skipping vendor-specific HID on dev node %s", dev_node); + + goto vsp_fail; + } + + if (!hid_meta_out_init(&hg->meta_out, hg->ppd, hg->fd)) { + goto meta_out_fail; + } + + hid_generic_strings_init(&hg->strings, hg->fd); + + /* Dump diagnostics */ + + log_misc("Initializing generic HID on dev node %s:", dev_node); + log_misc("... Product: %s", hg->strings.str_prod); + log_misc("... Manufacturer: %s", hg->strings.str_manf); + log_misc("... Device usage: %08x", + hid_meta_in_get_tlc_usage(&hg->meta_in)); + log_misc("... Number of controls: %u", + (unsigned int) hid_meta_in_get_ncontrols(&hg->meta_in)); + log_misc("... Number of \"lights\": %u", + (unsigned int) hid_meta_out_get_nlights(&hg->meta_out)); + + /* Start up input double-buffer chain by reading a report. + We bind to the IO completion port here. */ + + CreateIoCompletionPort(hg->fd, iocp, iocp_ctx, 0); + + hg->in_nbytes = hid_meta_in_get_buffer_size(&hg->meta_in); + hg->in_buf = xmalloc(hg->in_nbytes); + + memset(&hg->in_ovl, 0, sizeof(hg->in_ovl)); + + if (!ReadFile(hg->fd, hg->in_buf, hg->in_nbytes, NULL, + &hg->in_ovl)) { + if (GetLastError() != ERROR_IO_PENDING) { + log_warning("Initial ReadFile failed: %08x: %s", + (unsigned int) GetLastError(), dev_node); + + goto read_fail; + } + } + + /* Allocate OUT report buffers, but don't start sending the reports until + the user tries to set a light. Some HIDs (e.g. Razer devices) freak out + if you send them HID reports that they aren't expecting. + + These OUT reports are probably used for configuration settings or, + worse yet, firmware updates. Configuration changes are what feature + reports are for. As for firmware updates over HID... no. Just, no. */ + + hg->out_nbytes = hid_meta_out_get_buffer_size(&hg->meta_out); + hg->out_buf = xmalloc(hg->out_nbytes); + hg->out_started = false; + + memset(&hg->out_ovl, 0, sizeof(hg->out_ovl)); + + /* (finish up and return success) */ + + hg->dev_node = str_dup(dev_node); + hg->super.vptr = &hid_generic_vtbl; + + *out = &hg->super; + + return true; + +read_fail: + free(hg->in_buf); + + hid_generic_strings_fini(&hg->strings); + hid_meta_out_fini(&hg->meta_out); + +meta_out_fail: +vsp_fail: + hid_meta_in_fini(&hg->meta_in); + +meta_in_fail: + HidD_FreePreparsedData(hg->ppd); + +ppd_fail: + CloseHandle(hg->fd); + +open_fail: + free(hg); + + return false; +} + +static bool hid_generic_get_device_usage(const struct hid *hid, + uint32_t *usage) +{ + const struct hid_generic *hg = containerof(hid, struct hid_generic, super); + + *usage = hid_meta_in_get_tlc_usage(&hg->meta_in); + + return true; +} + +static bool hid_generic_get_name(const struct hid *hid, wchar_t *chars, + size_t *nchars) +{ + const struct hid_generic *hg = containerof(hid, struct hid_generic, super); + size_t len; + + log_assert(nchars != NULL); + + len = wcslen(hg->strings.wstr_prod) + 1; + + if (chars == NULL) { + *nchars = len; + + return true; + } else if (*nchars >= len) { + memcpy(chars, hg->strings.wstr_prod, len * sizeof(wchar_t)); + + return true; + } else { + return false; + } +} + +static bool hid_generic_get_controls(const struct hid *hid, + struct hid_control *controls, size_t *ncontrols) +{ + const struct hid_generic *hg = containerof(hid, struct hid_generic, super); + + const struct hid_control *meta_controls; + size_t meta_ncontrols; + + if (ncontrols == NULL) { + return false; + } + + meta_controls = hid_meta_in_get_controls(&hg->meta_in); + meta_ncontrols = hid_meta_in_get_ncontrols(&hg->meta_in); + + if (controls == NULL) { + *ncontrols = meta_ncontrols; + + return true; + } else if (*ncontrols >= meta_ncontrols) { + *ncontrols = meta_ncontrols; + memcpy(controls, meta_controls, sizeof(*controls) * meta_ncontrols); + + return true; + } else { + return false; + } +} + +static bool hid_generic_get_lights(const struct hid *hid, + struct hid_light *lights, size_t *nlights) +{ + const struct hid_generic *hg = containerof(hid, const struct hid_generic, + super); + + const struct hid_light *meta_lights; + size_t meta_nlights; + + log_assert(nlights != NULL); + + meta_lights = hid_meta_out_get_lights(&hg->meta_out); + meta_nlights = hid_meta_out_get_nlights(&hg->meta_out); + + if (lights == NULL) { + *nlights = meta_nlights; + + return true; + } else if (*nlights >= meta_nlights) { + *nlights = meta_nlights; + memcpy(lights, meta_lights, sizeof(*lights) * meta_nlights); + + return true; + } else { + return false; + } +} + +static bool hid_generic_get_value(struct hid *hid, size_t control_no, + int32_t *out_value) +{ + struct hid_generic *hg = containerof(hid, struct hid_generic, super); + + return hid_meta_in_get_value(&hg->meta_in, control_no, out_value); +} + +static bool hid_generic_set_light(struct hid *hid, size_t light_no, + uint32_t intensity) +{ + struct hid_generic *hg = containerof(hid, struct hid_generic, super); + + size_t nlights; + + nlights = hid_meta_out_get_nlights(&hg->meta_out); + + if (light_no >= nlights) { + return false; + } + + /* Lazily start the lighting double-buffer chain (see hid_generic_init) */ + + if (!hg->out_started && !hg->out_faulty) { + log_misc("Starting light output to %s", hg->dev_node); + hid_meta_out_get_next_report(&hg->meta_out, hg->out_buf); + + if (!WriteFile(hg->fd, hg->out_buf, hg->out_nbytes, NULL, + &hg->out_ovl)) { + if (GetLastError() != ERROR_IO_PENDING) { + log_warning("Initial WriteFile failed: %08x: %s", + (unsigned int) GetLastError(), hg->dev_node); + hg->out_faulty = true; + + return false; + } + } + + hg->out_started = true; + } + + return hid_meta_out_set_light(&hg->meta_out, light_no, intensity); +} + +static bool hid_generic_handle_event(struct hid_fd *hid_fd, OVERLAPPED *ovl, + size_t nbytes) +{ + struct hid_generic *hg = containerof(hid_fd, struct hid_generic, super); + + if (ovl == &hg->out_ovl) { + /* OUT transmit ok, prepare and transmit another */ + + hid_meta_out_get_next_report(&hg->meta_out, hg->out_buf); + memset(&hg->out_ovl, 0, sizeof(hg->out_ovl)); + + if (!WriteFile(hg->fd, hg->out_buf, hg->out_nbytes, NULL, + &hg->out_ovl) && GetLastError() != ERROR_IO_PENDING) { + log_warning("Write error %08x from \"%s\" device on dev node %s", + (unsigned int) GetLastError(), hg->strings.str_prod, + hg->dev_node); + + return false; + } + + } else if (ovl == &hg->in_ovl) { + /* Received an IN report, dispatch it */ + + /* The dispatcher will swap buffers with us, i.e. it will take + ownership of whatever hg->current_in points to, and replace it + with a pointer to a fresh buffer we can use for subsequent I/O */ + + if (!hid_meta_in_dispatch(&hg->meta_in, &hg->in_buf, nbytes)) { + log_warning("Failed to process input report from \"%s\" device" + " on dev node %s", hg->strings.str_prod, hg->dev_node); + + return false; + } + + if (hg->in_buf == NULL) { + /* BEEP BOOP FIRMWARE PROGRAMMED BY SHITLORD DETECTED */ + /* (ok, a less obnoxious explanation: The initial report + solicitation failed, so the report handler did not have a + valid buffer until we gave it one. This means that we have + to allocate one for it to work with manually). */ + + hg->in_buf = xmalloc(hid_meta_in_get_buffer_size(&hg->meta_in)); + } + + /* Event dispatched successfully. Kick off the next async read. */ + + memset(&hg->in_ovl, 0, sizeof(hg->in_ovl)); + + if (!ReadFile(hg->fd, hg->in_buf, hg->in_nbytes, NULL, + &hg->in_ovl) && GetLastError() != ERROR_IO_PENDING) { + log_warning("Read error %08x from \"%s\" device on dev node %s", + (unsigned int) GetLastError(), hg->strings.str_prod, + hg->dev_node); + + return false; + } + } + + return true; +} + +static void hid_generic_close(struct hid *hid) +{ + struct hid_generic *hg = containerof(hid, struct hid_generic, super); + + /* Must close the FD first. I/O might be in flight and NT could potentially + complete an I/O while we're freeing our buffers and completely trash our + process heap by doing so. */ + + CloseHandle(hg->fd); + + free(hg->dev_node); + free(hg->out_buf); + free(hg->in_buf); + hid_meta_out_fini(&hg->meta_out); + hid_meta_in_fini(&hg->meta_in); + hid_generic_strings_fini(&hg->strings); + + HidD_FreePreparsedData(hg->ppd); + + free(hid); +} + diff --git a/src/main/geninput/hid-generic.h b/src/main/geninput/hid-generic.h new file mode 100644 index 0000000..e695dc8 --- /dev/null +++ b/src/main/geninput/hid-generic.h @@ -0,0 +1,14 @@ +#ifndef GENINPUT_HID_GENERIC_H +#define GENINPUT_HID_GENERIC_H + +#include + +#include +#include + +#include "geninput/hid.h" + +bool hid_generic_open(struct hid_fd **hid, const char *dev_node, + HANDLE iocp, uintptr_t iocp_ctx); + +#endif diff --git a/src/main/geninput/hid-meta-in.c b/src/main/geninput/hid-meta-in.c new file mode 100644 index 0000000..88d5bf4 --- /dev/null +++ b/src/main/geninput/hid-meta-in.c @@ -0,0 +1,410 @@ +#define LOG_MODULE "hid-generic" + +#include +#include + +#include +#include +#include +#include + +#include "geninput/hid-meta-in.h" +#include "geninput/hid-report-in.h" + +#include "util/log.h" +#include "util/mem.h" + +static bool hid_meta_in_init_caps(struct hid_meta_in *meta, + PHIDP_PREPARSED_DATA ppd); +static void hid_meta_in_init_arrays(struct hid_meta_in *meta); +static void hid_meta_in_init_buttons(struct hid_meta_in *meta); +static void hid_meta_in_init_values(struct hid_meta_in *meta); +static struct hid_report_in *hid_meta_in_lookup_report( + struct hid_meta_in *meta, uint8_t report_id); +static void hid_meta_in_fini_arrays(struct hid_meta_in *meta); +static void hid_meta_in_fini_caps(struct hid_meta_in *meta); + +bool hid_meta_in_init(struct hid_meta_in *meta, PHIDP_PREPARSED_DATA ppd) +{ + if (!hid_meta_in_init_caps(meta, ppd)) { + return false; + } + + hid_meta_in_init_arrays(meta); + hid_meta_in_init_buttons(meta); + hid_meta_in_init_values(meta); + + return true; +} + +static bool hid_meta_in_init_caps(struct hid_meta_in *meta, + PHIDP_PREPARSED_DATA ppd) +{ + uint16_t len; + NTSTATUS status; + + meta->ppd = ppd; + + status = HidP_GetCaps(meta->ppd, &meta->caps_tlc); + + if (status != HIDP_STATUS_SUCCESS) { + log_warning("Error getting top-level collection caps"); + + goto caps_tlc_fail; + } + + len = meta->caps_tlc.NumberInputButtonCaps; + meta->caps_btn = xmalloc(sizeof(*meta->caps_btn) * len); + + if (len > 0) { + status = HidP_GetButtonCaps(HidP_Input, meta->caps_btn, &len,meta->ppd); + + if (status != HIDP_STATUS_SUCCESS) { + log_warning("Error getting button caps"); + + goto caps_btn_fail; + } + } + + len = meta->caps_tlc.NumberInputValueCaps; + meta->caps_val = xmalloc(sizeof(*meta->caps_val) * len); + + if (len > 0) { + status = HidP_GetValueCaps(HidP_Input, meta->caps_val, &len, meta->ppd); + + if (status != HIDP_STATUS_SUCCESS) { + log_warning("Error getting value caps"); + + goto caps_val_fail; + } + } + + return true; + +caps_val_fail: + free(meta->caps_val); + +caps_btn_fail: + free(meta->caps_btn); + +caps_tlc_fail: + return false; +} + +static void hid_meta_in_init_arrays(struct hid_meta_in *meta) +{ + const HIDP_BUTTON_CAPS *bc; + const HIDP_VALUE_CAPS *vc; + uint16_t *report_btns; + bool *report_presence; + uint16_t count; + unsigned int i; + unsigned int j; + void *report_buf; + + /* Init counters */ + + meta->ncontrols = 0; + meta->nbuttons = 0; + meta->nreports = 0; + + meta->reports_muxed = false; + + /* We have extremely limited stack headroom when called from IIDX for some + reason, gotta do this on the heap... */ + + report_btns = xcalloc(sizeof(*report_btns) * 0x100); + report_presence = xcalloc(sizeof(*report_presence) * 0x100); + + /* Count up buttons, globally and per-report */ + + for (i = 0 ; i < meta->caps_tlc.NumberInputButtonCaps ; i++) { + bc = &meta->caps_btn[i]; + + if (bc->IsRange) { + count = bc->Range.UsageMax - bc->Range.UsageMin + 1; + } else { + count = 1; + } + + if (bc->ReportID != 0) { + meta->reports_muxed = true; + } + + report_btns[bc->ReportID] += count; + + meta->ncontrols += count; + meta->nbuttons += count; + } + + /* Count up values, note all reports that have values */ + + for (i = 0 ; i < meta->caps_tlc.NumberInputValueCaps ; i++) { + vc = &meta->caps_val[i]; + + if (vc->IsRange) { + count = vc->Range.UsageMax - vc->Range.UsageMin + 1; + } else { + count = 1; + } + + if (vc->ReportID != 0) { + meta->reports_muxed = true; + } + + report_presence[vc->ReportID] = true; + + meta->ncontrols += count; + } + + /* Count up total number of reports and initialize them */ + + for (i = 0 ; i < 0x100 ; i++) { + if (report_btns[i] || report_presence[i]) { + meta->nreports++; + } + } + + j = 0; + meta->reports = xmalloc(sizeof(*meta->reports) * meta->nreports); + + for (i = 0 ; i < 0x100 ; i++) { + if (report_btns[i] || report_presence[i]) { + report_buf = xmalloc(meta->caps_tlc.InputReportByteLength); + + hid_report_in_init(&meta->reports[j++], (uint8_t) i, + report_btns[i], report_buf, + meta->caps_tlc.InputReportByteLength, meta->ppd); + } + } + + /* Init control arrays */ + + meta->controls = xmalloc(sizeof(*meta->controls) * meta->ncontrols); + meta->priv_controls = xmalloc(sizeof(*meta->priv_controls) + * meta->ncontrols); + + /* Clean up our scratch space */ + + free(report_presence); + free(report_btns); +} + +static void hid_meta_in_init_buttons(struct hid_meta_in *meta) +{ + const HIDP_BUTTON_CAPS *bc; + struct hid_control_in *priv; + struct hid_control *ctl; + unsigned int pos; + unsigned int i; + int j; + + pos = 0; + + for (i = 0 ; i < meta->caps_tlc.NumberInputButtonCaps ; i++) { + bc = &meta->caps_btn[i]; + + if (bc->IsRange) { + for (j = 0 ; j <= bc->Range.UsageMax - bc->Range.UsageMin ; j++) { + priv = &meta->priv_controls[pos]; + + priv->report = hid_meta_in_lookup_report(meta, bc->ReportID); + priv->collection_id = bc->LinkCollection; + + ctl = &meta->controls[pos]; + + ctl->usage = (bc->UsagePage << 16) | (bc->Range.UsageMin + j); + ctl->value_min = 0; + ctl->value_max = 1; + ctl->flags = 0; + + pos++; + } + } else { + priv = &meta->priv_controls[pos]; + + priv->report = hid_meta_in_lookup_report(meta, bc->ReportID); + priv->collection_id = bc->LinkCollection; + + ctl = &meta->controls[pos]; + + ctl->usage = (bc->UsagePage << 16) | bc->NotRange.Usage; + ctl->value_min = 0; + ctl->value_max = 1; + ctl->flags = 0; + + pos++; + } + } +} + +static void hid_meta_in_init_values(struct hid_meta_in *meta) +{ + const HIDP_VALUE_CAPS *vc; + struct hid_control_in *priv; + struct hid_control *ctl; + unsigned int pos; + unsigned int i; + int j; + + pos = meta->nbuttons; + + for (i = 0 ; i < meta->caps_tlc.NumberInputValueCaps ; i++) { + vc = &meta->caps_val[i]; + + if (vc->IsRange) { + for (j = 0 ; j <= vc->Range.UsageMax - vc->Range.UsageMin ; j++) { + priv = &meta->priv_controls[pos]; + + priv->report = hid_meta_in_lookup_report(meta, vc->ReportID); + priv->collection_id = vc->LinkCollection; + + ctl = &meta->controls[pos]; + + ctl->usage = (vc->UsagePage << 16) | (vc->Range.UsageMin + i); + ctl->value_min = vc->LogicalMin; + ctl->value_max = vc->LogicalMax; + ctl->flags = 0; + + if (vc->HasNull) { + ctl->flags |= HID_FLAG_NULLABLE; + } + + if (!vc->IsAbsolute) { + ctl->flags |= HID_FLAG_RELATIVE; + } + + pos++; + } + } else { + priv = &meta->priv_controls[pos]; + + priv->report = hid_meta_in_lookup_report(meta, vc->ReportID); + priv->collection_id = vc->LinkCollection; + + ctl = &meta->controls[pos]; + + ctl->usage = (vc->UsagePage << 16) | vc->NotRange.Usage; + ctl->value_min = vc->LogicalMin; + ctl->value_max = vc->LogicalMax; + ctl->flags = 0; + + if (vc->HasNull) { + ctl->flags |= HID_FLAG_NULLABLE; + } + + if (!vc->IsAbsolute) { + ctl->flags |= HID_FLAG_RELATIVE; + } + + pos++; + } + } +} + +static struct hid_report_in *hid_meta_in_lookup_report( + struct hid_meta_in *meta, uint8_t report_id) +{ + unsigned int i; + + for (i = 0 ; i < meta->nreports ; i++) { + if (meta->reports[i].id == report_id) { + return &meta->reports[i]; + } + } + + return NULL; +} + +bool hid_meta_in_dispatch(struct hid_meta_in *meta, uint8_t **bytes, + size_t nbytes) +{ + struct hid_report_in *r; + uint8_t report_id; + + if (meta->reports_muxed) { + report_id = **bytes; + } else { + report_id = 0; + } + + r = hid_meta_in_lookup_report(meta, report_id); + + if (r == NULL) { + log_warning("Got spurious report with ID %02x", report_id); + + return false; + } + + return hid_report_in_exchange(r, bytes, nbytes); +} + +size_t hid_meta_in_get_buffer_size(const struct hid_meta_in *meta) +{ + return meta->caps_tlc.InputReportByteLength; +} + +const struct hid_control *hid_meta_in_get_controls( + const struct hid_meta_in *meta) +{ + return meta->controls; +} + +size_t hid_meta_in_get_ncontrols(const struct hid_meta_in *meta) +{ + return meta->ncontrols; +} + +uint32_t hid_meta_in_get_tlc_usage(const struct hid_meta_in *meta) +{ + return (meta->caps_tlc.UsagePage << 16) | meta->caps_tlc.Usage; +} + +bool hid_meta_in_get_value(struct hid_meta_in *meta, + size_t control_no, int32_t *value) +{ + struct hid_control_in *priv; + uint32_t usage; + + if (control_no >= meta->ncontrols) { + return false; + } + + priv = &meta->priv_controls[control_no]; + usage = meta->controls[control_no].usage; + + if (control_no < meta->nbuttons) { + return hid_report_in_get_bit(priv->report, meta->ppd, + priv->collection_id, usage, value); + } else { + return hid_report_in_get_value(priv->report, meta->ppd, + priv->collection_id, usage, value); + } +} + +void hid_meta_in_fini(struct hid_meta_in *meta) +{ + hid_meta_in_fini_arrays(meta); + hid_meta_in_fini_caps(meta); +} + +static void hid_meta_in_fini_arrays(struct hid_meta_in *meta) +{ + unsigned int i; + + free(meta->priv_controls); + free(meta->controls); + + for (i = 0 ; i < meta->nreports ; i++) { + hid_report_in_fini(&meta->reports[i]); + } + + free(meta->reports); +} + +static void hid_meta_in_fini_caps(struct hid_meta_in *meta) +{ + free(meta->caps_val); + free(meta->caps_btn); +} + diff --git a/src/main/geninput/hid-meta-in.h b/src/main/geninput/hid-meta-in.h new file mode 100644 index 0000000..ca9f8c5 --- /dev/null +++ b/src/main/geninput/hid-meta-in.h @@ -0,0 +1,46 @@ +#ifndef GENINPUT_HID_META_IN_H +#define GENINPUT_HID_META_IN_H + +#include +#include + +#include +#include + +#include "geninput/hid.h" +#include "geninput/hid-report-in.h" + +struct hid_control_in { + struct hid_report_in *report; + uint16_t collection_id; +}; + +struct hid_meta_in { + PHIDP_PREPARSED_DATA ppd; + HIDP_CAPS caps_tlc; + HIDP_BUTTON_CAPS *caps_btn; + HIDP_VALUE_CAPS *caps_val; + + struct hid_control *controls; + size_t ncontrols; + size_t nbuttons; + + bool reports_muxed; + struct hid_control_in *priv_controls; + struct hid_report_in *reports; + uint8_t nreports; +}; + +bool hid_meta_in_init(struct hid_meta_in *meta, PHIDP_PREPARSED_DATA ppd); +bool hid_meta_in_dispatch(struct hid_meta_in *meta, uint8_t **bytes, + size_t nbytes); +size_t hid_meta_in_get_buffer_size(const struct hid_meta_in *meta); +const struct hid_control *hid_meta_in_get_controls( + const struct hid_meta_in *meta); +size_t hid_meta_in_get_ncontrols(const struct hid_meta_in *meta); +uint32_t hid_meta_in_get_tlc_usage(const struct hid_meta_in *meta); +bool hid_meta_in_get_value(struct hid_meta_in *meta, + size_t control_no, int32_t *value); +void hid_meta_in_fini(struct hid_meta_in *meta); + +#endif diff --git a/src/main/geninput/hid-meta-out.c b/src/main/geninput/hid-meta-out.c new file mode 100644 index 0000000..6a5a7f0 --- /dev/null +++ b/src/main/geninput/hid-meta-out.c @@ -0,0 +1,402 @@ +#define LOG_MODULE "hid-generic" + +#include +#include + +#include +#include +#include +#include +#include + +#include "geninput/hid-meta-out.h" +#include "geninput/hid-report-out.h" + +#include "util/log.h" +#include "util/mem.h" + +static bool hid_meta_out_init_caps(struct hid_meta_out *meta, + PHIDP_PREPARSED_DATA ppd); +static bool hid_meta_out_init_arrays(struct hid_meta_out *meta); +static void hid_meta_out_init_lights(struct hid_meta_out *meta, HANDLE fd); +static struct hid_report_out *hid_meta_out_lookup_report( + struct hid_meta_out *meta, uint8_t report_id); +static void hid_meta_out_fini_arrays(struct hid_meta_out *meta); +static void hid_meta_out_fini_caps(struct hid_meta_out *meta); + +bool hid_meta_out_init(struct hid_meta_out *meta, PHIDP_PREPARSED_DATA ppd, HANDLE fd) +{ + if (!hid_meta_out_init_caps(meta, ppd)) { + goto caps_fail; + } + + if (!hid_meta_out_init_arrays(meta)) { + goto arrays_fail; + } + + hid_meta_out_init_lights(meta, fd); + + meta->next_report = 0; + + return true; + +arrays_fail: + hid_meta_out_fini_caps(meta); + +caps_fail: + return false; +} + +static bool hid_meta_out_init_caps(struct hid_meta_out *meta, + PHIDP_PREPARSED_DATA ppd) +{ + uint16_t len; + NTSTATUS status; + + meta->ppd = ppd; + + status = HidP_GetCaps(meta->ppd, &meta->caps_tlc); + + if (status != HIDP_STATUS_SUCCESS) { + log_warning("Error getting top-level collection caps"); + + goto caps_tlc_fail; + } + + len = meta->caps_tlc.NumberOutputButtonCaps; + meta->caps_btn = xmalloc(sizeof(*meta->caps_btn) * len); + + if (len > 0) { + status = HidP_GetButtonCaps(HidP_Output, meta->caps_btn, &len, + meta->ppd); + + if (status != HIDP_STATUS_SUCCESS) { + log_warning("Error getting button caps"); + + goto caps_btn_fail; + } + } + + len = meta->caps_tlc.NumberOutputValueCaps; + meta->caps_val = xmalloc(sizeof(*meta->caps_val) * len); + + if (len > 0) { + status = HidP_GetValueCaps(HidP_Output, meta->caps_val, &len, + meta->ppd); + + if (status != HIDP_STATUS_SUCCESS) { + log_warning("Error getting value caps"); + + goto caps_val_fail; + } + } + + return true; + +caps_val_fail: + free(meta->caps_val); + +caps_btn_fail: + free(meta->caps_btn); + +caps_tlc_fail: + return false; +} + +static bool hid_meta_out_init_arrays(struct hid_meta_out *meta) +{ + const HIDP_BUTTON_CAPS *bc; + const HIDP_VALUE_CAPS *vc; + bool *report_presence; + unsigned int nreports; + unsigned int i; + uint8_t *bytes; + size_t nbytes; + size_t count; + + /* N.B. "buttons" here are just one-bit values. You have to use a + completely different API to manipulate these values even though the HID + spec and protocol doesn't really make a distinction between them. */ + + meta->nlights = 0; + meta->nbuttons = 0; + + /* Use heap-based working area */ + + report_presence = xcalloc(sizeof(*report_presence) * 0x100); + + /* Count up buttons, globally and per-report */ + + for (i = 0 ; i < meta->caps_tlc.NumberOutputButtonCaps ; i++) { + bc = &meta->caps_btn[i]; + + if (bc->IsRange) { + count = bc->Range.UsageMax - bc->Range.UsageMin + 1; + } else { + count = 1; + } + + meta->nlights += count; + meta->nbuttons += count; + + report_presence[bc->ReportID] = true; + } + + for (i = 0 ; i < meta->caps_tlc.NumberOutputValueCaps ; i++) { + vc = &meta->caps_val[i]; + + if (vc->IsRange) { + meta->nlights += vc->Range.UsageMax - vc->Range.UsageMin + 1; + } else { + meta->nlights += 1; + } + + report_presence[vc->ReportID] = true; + } + + /* Count up total number of reports and initialize them */ + + nreports = 0; /* Allocated */ + meta->nreports = 0; /* Initialized so far (see unwind below) */ + + for (i = 0 ; i < 0x100 ; i++) { + if (report_presence[i]) { + nreports++; + } + } + + meta->reports = xmalloc(sizeof(*meta->reports) * nreports); + + for (i = 0 ; i < 0x100 ; i++) { + if (report_presence[i]) { + nbytes = meta->caps_tlc.OutputReportByteLength; + bytes = xmalloc(meta->caps_tlc.OutputReportByteLength); + + if (!hid_report_out_init(&meta->reports[meta->nreports], + meta->ppd, (uint8_t) i, nbytes)) { + goto r_init_fail; + } + + meta->nreports++; + } + } + + meta->lights = xcalloc(sizeof(*meta->lights) * meta->nlights); + meta->priv_lights = xmalloc(sizeof(*meta->priv_lights) * meta->nlights); + + /* Clean up our scratch space */ + + free(report_presence); + + return true; + +r_init_fail: + free(bytes); + + for (i = meta->nreports ; i > 0 ; i--) { + hid_report_out_fini(&meta->reports[i - 1]); + } + + free(report_presence); + free(meta->reports); + + return false; +} + +static void hid_meta_out_init_lights(struct hid_meta_out *meta, HANDLE fd) +{ + const HIDP_BUTTON_CAPS *bc; + const HIDP_VALUE_CAPS *vc; + struct hid_out_light *priv; + struct hid_light *light; + unsigned int pos; + unsigned int i; + int j; + + pos = 0; + + for (i = 0 ; i < meta->caps_tlc.NumberOutputButtonCaps ; i++) { + bc = &meta->caps_btn[i]; + + if (bc->IsRange) { + for (j = 0 ; j <= bc->Range.UsageMax - bc->Range.UsageMin ; j++) { + priv = &meta->priv_lights[pos]; + + priv->report = hid_meta_out_lookup_report(meta, bc->ReportID); + priv->collection_id = bc->LinkCollection; + priv->size = 1; + + light = &meta->lights[pos]; + + light->usage = (bc->UsagePage << 16) | (bc->Range.UsageMin + j); + light->value_min = 0; + light->value_max = 1; + + // Devices may specify their own strings for lights + if(bc->IsStringRange && bc->Range.StringMin != 0) { + HidD_GetIndexedString(fd, bc->Range.StringMin + j, light->name, sizeof(light->name)); + } else if(!bc->IsStringRange && bc->NotRange.StringIndex != 0) { + HidD_GetIndexedString(fd, bc->NotRange.StringIndex, light->name, sizeof(light->name)); + } + + pos++; + } + } else { + priv = &meta->priv_lights[pos]; + + priv->report = hid_meta_out_lookup_report(meta, bc->ReportID); + priv->collection_id = bc->LinkCollection; + priv->size = 1; + + light = &meta->lights[pos]; + + light->usage = (bc->UsagePage << 16) | bc->NotRange.Usage; + light->value_min = 0; + light->value_max = 1; + + if(!bc->IsStringRange && bc->NotRange.StringIndex != 0) { + HidD_GetIndexedString(fd, bc->NotRange.StringIndex, light->name, sizeof(light->name)); + } + + pos++; + } + } + + for (i = 0 ; i < meta->caps_tlc.NumberOutputValueCaps ; i++) { + vc = &meta->caps_val[i]; + + if (vc->IsRange) { + for (j = 0 ; j <= vc->Range.UsageMax - vc->Range.UsageMin ; j++) { + priv = &meta->priv_lights[pos]; + + priv->report = hid_meta_out_lookup_report(meta, vc->ReportID); + priv->collection_id = vc->LinkCollection; + priv->size = vc->BitSize; + + light = &meta->lights[pos]; + + light->usage = (vc->UsagePage << 16) | (vc->Range.UsageMin + j); + light->value_min = vc->LogicalMin; + light->value_max = vc->LogicalMax; + + // Devices may specify their own strings for lights + if(vc->IsStringRange && vc->Range.StringMin != 0) { + HidD_GetIndexedString(fd, vc->Range.StringMin + j, light->name, sizeof(light->name)); + } else if(!vc->IsStringRange && vc->NotRange.StringIndex != 0) { + HidD_GetIndexedString(fd, vc->NotRange.StringIndex, light->name, sizeof(light->name)); + } + + pos++; + } + } else { + priv = &meta->priv_lights[pos]; + + priv->report = hid_meta_out_lookup_report(meta, vc->ReportID); + priv->collection_id = vc->LinkCollection; + priv->size = vc->BitSize; + + light = &meta->lights[pos]; + + light->usage = (vc->UsagePage << 16) | vc->NotRange.Usage; + light->value_min = vc->LogicalMin; + light->value_max = vc->LogicalMax; + + if(!vc->IsStringRange && vc->NotRange.StringIndex != 0) { + HidD_GetIndexedString(fd, vc->NotRange.StringIndex, light->name, sizeof(light->name)); + } + + pos++; + } + } +} + +static struct hid_report_out *hid_meta_out_lookup_report( + struct hid_meta_out *meta, uint8_t report_id) +{ + unsigned int i; + + for (i = 0 ; i < meta->nreports ; i++) { + if (meta->reports[i].id == report_id) { + return &meta->reports[i]; + } + } + + return NULL; +} + +size_t hid_meta_out_get_buffer_size(const struct hid_meta_out *meta) +{ + return meta->caps_tlc.OutputReportByteLength; +} + +const struct hid_light *hid_meta_out_get_lights( + const struct hid_meta_out *meta) +{ + return meta->lights; +} + +size_t hid_meta_out_get_nlights(const struct hid_meta_out *meta) +{ + return meta->nlights; +} + +bool hid_meta_out_set_light(struct hid_meta_out *meta, + size_t light_no, uint32_t intensity) +{ + struct hid_out_light *priv; + uint32_t usage; + + if (light_no >= meta->nlights) { + return false; + } + + priv = &meta->priv_lights[light_no]; + usage = meta->lights[light_no].usage; + + if (light_no < meta->nbuttons) { + return hid_report_out_set_bit(priv->report, meta->ppd, + priv->collection_id, usage, intensity != 0); + } else { + return hid_report_out_set_value(priv->report, meta->ppd, + priv->collection_id, usage, intensity); + } +} + +void hid_meta_out_get_next_report(struct hid_meta_out *meta, uint8_t *bytes) +{ + struct hid_report_out *r; + + log_assert(meta->nreports > 0); + + r = &meta->reports[meta->next_report]; + meta->next_report = (meta->next_report + 1) % meta->nreports; + + hid_report_out_get_bytes(r, bytes); +} + +void hid_meta_out_fini(struct hid_meta_out *meta) +{ + hid_meta_out_fini_arrays(meta); + hid_meta_out_fini_caps(meta); +} + +static void hid_meta_out_fini_arrays(struct hid_meta_out *meta) +{ + unsigned int i; + + free(meta->priv_lights); + free(meta->lights); + + for (i = 0 ; i < meta->nreports ; i++) { + hid_report_out_fini(&meta->reports[i]); + } + + free(meta->reports); +} + +static void hid_meta_out_fini_caps(struct hid_meta_out *meta) +{ + free(meta->caps_btn); + free(meta->caps_val); +} + diff --git a/src/main/geninput/hid-meta-out.h b/src/main/geninput/hid-meta-out.h new file mode 100644 index 0000000..b92be00 --- /dev/null +++ b/src/main/geninput/hid-meta-out.h @@ -0,0 +1,46 @@ +#ifndef GENINPUT_HID_META_OUT_H +#define GENINPUT_HID_META_OUT_H + +#include +#include + +#include +#include +#include + +#include "geninput/hid.h" +#include "geninput/hid-report-out.h" + +struct hid_out_light { + struct hid_report_out *report; + uint16_t collection_id; + uint16_t size; +}; + +struct hid_meta_out { + PHIDP_PREPARSED_DATA ppd; + HIDP_CAPS caps_tlc; + HIDP_BUTTON_CAPS *caps_btn; + HIDP_VALUE_CAPS *caps_val; + + struct hid_light *lights; + size_t nlights; + size_t nbuttons; + + struct hid_out_light *priv_lights; + struct hid_report_out *reports; + uint8_t nreports; + uint8_t next_report; +}; + +bool hid_meta_out_init(struct hid_meta_out *meta, PHIDP_PREPARSED_DATA ppd, HANDLE fd); +void hid_meta_out_get_next_report(struct hid_meta_out *meta, uint8_t *bytes); +size_t hid_meta_out_get_buffer_size(const struct hid_meta_out *meta); +const struct hid_light *hid_meta_out_get_lights( + const struct hid_meta_out *meta); +size_t hid_meta_out_get_nlights(const struct hid_meta_out *meta); +bool hid_meta_out_set_light(struct hid_meta_out *meta, + size_t light_no, uint32_t intensity); +void hid_meta_out_fini(struct hid_meta_out *meta); + +#endif diff --git a/src/main/geninput/hid-mgr.c b/src/main/geninput/hid-mgr.c new file mode 100644 index 0000000..aecd5df --- /dev/null +++ b/src/main/geninput/hid-mgr.c @@ -0,0 +1,200 @@ +#include + +#include + +#include "geninput/hid-mgr.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +struct hid_stub { + struct hid_stub *next; + char *dev_node; + struct hid *hid; +}; + +static CRITICAL_SECTION hid_mgr_cs; +static struct hid_stub *hid_stubs; + +static bool hid_stub_check(struct hid_stub *stub, bool ok); + +void hid_mgr_init(void) +{ + InitializeCriticalSection(&hid_mgr_cs); + hid_stubs = NULL; +} + +struct hid_stub *hid_mgr_get_first_stub(void) +{ + return hid_stubs; +} + +struct hid_stub *hid_mgr_get_next_stub(struct hid_stub *stub) +{ + return stub->next; +} + +struct hid_stub *hid_mgr_get_named_stub(const char *dev_node) +{ + struct hid_stub *pos; + + for (pos = hid_stubs ; pos != NULL ; pos = pos->next) { + if (_stricmp(pos->dev_node, dev_node) == 0) { + return pos; + } + } + + pos = xmalloc(sizeof(*pos)); + + pos->next = hid_stubs; + pos->dev_node = str_dup(dev_node); + pos->hid = NULL; + + hid_stubs = pos; + + return pos; +} + +void hid_mgr_lock(void) +{ + EnterCriticalSection(&hid_mgr_cs); +} + +void hid_mgr_unlock(void) +{ + LeaveCriticalSection(&hid_mgr_cs); +} + +void hid_mgr_fini(void) +{ + struct hid_stub *next; + struct hid_stub *pos; + + for (pos = hid_stubs ; pos != NULL ; pos = next) { + next = pos->next; + + if (pos->hid != NULL) { + hid_close(pos->hid); + } + + free(pos->dev_node); + free(pos); + } + + DeleteCriticalSection(&hid_mgr_cs); +} + +static bool hid_stub_check(struct hid_stub *stub, bool ok) +{ + if (!ok) { + log_warning("Detaching erroring HID at %s", stub->dev_node); + hid_stub_detach(stub); + } + + return ok; +} + +void hid_stub_attach(struct hid_stub *stub, struct hid *hid) +{ + if (stub->hid != NULL) { + hid_close(stub->hid); + } + + stub->hid = hid; +} + +void hid_stub_detach(struct hid_stub *stub) +{ + if (stub->hid != NULL) { + hid_close(stub->hid); + } + + stub->hid = NULL; +} + +bool hid_stub_is_attached(struct hid_stub *stub) +{ + return stub->hid != NULL; +} + +bool hid_stub_get_device_usage(struct hid_stub *stub, uint32_t *usage) +{ + if (stub->hid == NULL) { + return false; + } + + return hid_stub_check(stub, + hid_get_device_usage(stub->hid, usage)); +} + +bool hid_stub_get_controls(struct hid_stub *stub, struct hid_control *controls, + size_t *ncontrols) +{ + if (stub->hid == NULL) { + return false; + } + + return hid_stub_check(stub, + hid_get_controls(stub->hid, controls, ncontrols)); +} + +bool hid_stub_get_lights(struct hid_stub *stub, struct hid_light *lights, + size_t *nlights) +{ + if (stub->hid == NULL) { + return false; + } + + return hid_stub_check(stub, + hid_get_lights(stub->hid, lights, nlights)); +} + +const char *hid_stub_get_dev_node(struct hid_stub *stub) +{ + return stub->dev_node; +} + +bool hid_stub_get_name(struct hid_stub *stub, wchar_t *chars, size_t *nchars) +{ + if (stub->hid == NULL) { + return false; + } + + return hid_stub_check(stub, + hid_get_name(stub->hid, chars, nchars)); +} + +bool hid_stub_get_value(struct hid_stub *stub, size_t control_no, + int32_t *out_value) +{ + if (stub->hid == NULL) { + return false; + } + + return hid_stub_check(stub, + hid_get_value(stub->hid, control_no, out_value)); +} + +bool hid_stub_set_light(struct hid_stub *stub, size_t light_no, + uint32_t intensity) +{ + if (stub->hid == NULL) { + return false; + } + + return hid_stub_check(stub, + hid_set_light(stub->hid, light_no, intensity)); +} + +bool hid_stub_handle_completion(struct hid_stub *stub, OVERLAPPED *ovl, + size_t nbytes) +{ + if (stub->hid == NULL) { + return false; + } + + return hid_stub_check(stub, + hid_fd_handle_completion((struct hid_fd *) stub->hid, ovl, nbytes)); +} + diff --git a/src/main/geninput/hid-mgr.h b/src/main/geninput/hid-mgr.h new file mode 100644 index 0000000..17afa0c --- /dev/null +++ b/src/main/geninput/hid-mgr.h @@ -0,0 +1,37 @@ +#ifndef GENINPUT_HID_MGR_H +#define GENINPUT_HID_MGR_H + +#include + +#include +#include +#include + +#include "geninput/hid.h" + +void hid_mgr_init(void); +void hid_mgr_lock(void); +void hid_mgr_unlock(void); +struct hid_stub *hid_mgr_get_first_stub(void); +struct hid_stub *hid_mgr_get_next_stub(struct hid_stub *stub); +struct hid_stub *hid_mgr_get_named_stub(const char *dev_node); +void hid_mgr_fini(void); + +const char *hid_stub_get_dev_node(struct hid_stub *stub); +void hid_stub_attach(struct hid_stub *stub, struct hid *hid); +void hid_stub_detach(struct hid_stub *stub); +bool hid_stub_get_name(struct hid_stub *stub, wchar_t *name, size_t *nchars); +bool hid_stub_is_attached(struct hid_stub *stub); +bool hid_stub_get_device_usage(struct hid_stub *stub, uint32_t *usage); +bool hid_stub_get_controls(struct hid_stub *stub, struct hid_control *controls, + size_t *ncontrols); +bool hid_stub_get_lights(struct hid_stub *stub, struct hid_light *lights, + size_t *nlights); +bool hid_stub_get_value(struct hid_stub *stub, size_t control_no, + int32_t *out_value); +bool hid_stub_set_light(struct hid_stub *stub, size_t light_no, + uint32_t intensity); +bool hid_stub_handle_completion(struct hid_stub *stub, OVERLAPPED *ovl, + size_t nbytes); + +#endif diff --git a/src/main/geninput/hid-report-in.c b/src/main/geninput/hid-report-in.c new file mode 100644 index 0000000..7efb387 --- /dev/null +++ b/src/main/geninput/hid-report-in.c @@ -0,0 +1,110 @@ +#define LOG_MODULE "hid-generic" + +#include +#include + +#include +#include +#include + +#include "geninput/hid-report-in.h" + +#include "util/log.h" +#include "util/mem.h" + +void hid_report_in_init(struct hid_report_in *r, uint8_t report_id, + uint32_t nbuttons, void *bytes, size_t nbytes, + PHIDP_PREPARSED_DATA ppd) +{ + r->id = report_id; + r->nbuttons = nbuttons; + r->buttons = xmalloc(sizeof(*r->buttons) * r->nbuttons); + r->bytes = bytes; + r->nbytes = nbytes; + + if (HidP_InitializeReportForID(HidP_Input, report_id, ppd, bytes, nbytes) + != HIDP_STATUS_SUCCESS) { + log_warning("Error initializing IN report %02X", report_id); + } +} + +uint8_t hid_report_in_get_id(struct hid_report_in *r) +{ + return r->id; +} + +bool hid_report_in_exchange(struct hid_report_in *r, uint8_t **bytes, + uint32_t nbytes) +{ + uint8_t *last_bytes; + + last_bytes = r->bytes; + + r->bytes = *bytes; + r->nbytes = nbytes; + + *bytes = last_bytes; + + return true; +} + +bool hid_report_in_get_bit(struct hid_report_in *r, PHIDP_PREPARSED_DATA ppd, + uint16_t collection_id, uint32_t usage, int32_t *value) +{ + unsigned long i; + unsigned long nbuttons; + NTSTATUS result; + + nbuttons = r->nbuttons; + result = HidP_GetUsagesEx(HidP_Input, collection_id, + (USAGE_AND_PAGE *) r->buttons, &nbuttons, ppd, (PCHAR) r->bytes, + (unsigned long) r->nbytes); + + if (result != HIDP_STATUS_SUCCESS) { + log_warning("HidP_GetUsagesEx failed for report %02X: %08x", + r->id, (unsigned int) result); + + return false; + } + + *value = 0; + + for (i = 0 ; i < nbuttons ; i++) { + if (r->buttons[i] == usage) { + *value = 1; + } + } + + return true; +} + +bool hid_report_in_get_value(struct hid_report_in *r, PHIDP_PREPARSED_DATA ppd, + uint16_t collection_id, uint32_t usage, int32_t *value) +{ + NTSTATUS result; + + if (r->bytes == NULL) { + log_warning("Report %02X not yet valid", r->id); + + return false; + } + + result = HidP_GetUsageValue(HidP_Input, usage >> 16, 0, (uint16_t) usage, + (unsigned long *) value, ppd, (PCHAR) r->bytes, r->nbytes); + + if (result != HIDP_STATUS_SUCCESS) { + log_warning("HidP_GetUsageValue(%08x) failed for report %02X: %08x", + usage, r->id, (unsigned int) result); + + return false; + } + + return true; +} + +void hid_report_in_fini(struct hid_report_in *r) +{ + free(r->buttons); + free(r->bytes); +} + diff --git a/src/main/geninput/hid-report-in.h b/src/main/geninput/hid-report-in.h new file mode 100644 index 0000000..1630c12 --- /dev/null +++ b/src/main/geninput/hid-report-in.h @@ -0,0 +1,30 @@ +#ifndef GENINPUT_HID_REPORT_IN_H +#define GENINPUT_HID_REPORT_IN_H + +#include +#include +#include + +#include + +struct hid_report_in { + uint8_t id; + uint32_t nbuttons; + uint32_t *buttons; + uint8_t *bytes; + uint32_t nbytes; +}; + +void hid_report_in_init(struct hid_report_in *r, uint8_t report_id, + uint32_t nbuttons, void *bytes, size_t nbytes, + PHIDP_PREPARSED_DATA ppd); +uint8_t hid_report_in_get_id(struct hid_report_in *r); +bool hid_report_in_exchange(struct hid_report_in *r, uint8_t **bytes, + uint32_t nbytes); +bool hid_report_in_get_bit(struct hid_report_in *r, PHIDP_PREPARSED_DATA ppd, + uint16_t collection_id, uint32_t usage, int32_t *value); +bool hid_report_in_get_value(struct hid_report_in *r, PHIDP_PREPARSED_DATA ppd, + uint16_t collection_id, uint32_t usage, int32_t *value); +void hid_report_in_fini(struct hid_report_in *r); + +#endif diff --git a/src/main/geninput/hid-report-out.c b/src/main/geninput/hid-report-out.c new file mode 100644 index 0000000..a9b7602 --- /dev/null +++ b/src/main/geninput/hid-report-out.c @@ -0,0 +1,112 @@ +#define LOG_MODULE "hid-generic" + +#include +#include + +#include +#include +#include +#include + +#include "geninput/hid-report-out.h" + +#include "util/log.h" +#include "util/mem.h" + +bool hid_report_out_init(struct hid_report_out *r, PHIDP_PREPARSED_DATA ppd, + uint8_t id, size_t nbytes) +{ + NTSTATUS status; + + r->id = id; + r->nbytes = nbytes; + r->bytes = xmalloc(nbytes); + + status = HidP_InitializeReportForID(HidP_Output, r->id, ppd, + (PCHAR) r->bytes, r->nbytes); + + if (status != HIDP_STATUS_SUCCESS) { + log_warning("Report %02X: HidP_InitializeReportForID failed: %08x", + id, (unsigned int) status); + + goto fail; + } + + return true; + +fail: + free(r->bytes); + + return false; +} + +void hid_report_out_get_bytes(const struct hid_report_out *r, uint8_t *bytes) +{ + memcpy(bytes, r->bytes, r->nbytes); +} + +bool hid_report_out_set_bit(struct hid_report_out *r, + PHIDP_PREPARSED_DATA ppd, uint16_t collection_id, uint32_t usage, + bool value) +{ + NTSTATUS status; + unsigned long count; + uint16_t usage_hi; + uint16_t usage_lo; + + usage_hi = (uint16_t) (usage >> 16); + usage_lo = (uint16_t) (usage >> 0); + count = 1; + + if (value) { + status = HidP_SetUsages(HidP_Output, usage_hi, collection_id, + &usage_lo, &count, ppd, (PCHAR) r->bytes, r->nbytes); + + if (status != HIDP_STATUS_SUCCESS) { + log_warning("HidP_SetUsages failed: %08x", (unsigned int) status); + + return false; + } + } else { + status = HidP_UnsetUsages(HidP_Output, usage_hi, collection_id, + &usage_lo, &count, ppd, (PCHAR) r->bytes, r->nbytes); + + if (status != HIDP_STATUS_SUCCESS + && status != HIDP_STATUS_BUTTON_NOT_PRESSED /* jfc */) { + log_warning("HidP_UnsetUsages failed: %08x", (unsigned int) status); + + return false; + } + } + + return true; +} + +bool hid_report_out_set_value(struct hid_report_out *r, + PHIDP_PREPARSED_DATA ppd, uint16_t collection_id, uint32_t usage, + uint32_t value) +{ + NTSTATUS status; + uint16_t usage_hi; + uint16_t usage_lo; + + usage_hi = (uint16_t) (usage >> 16); + usage_lo = (uint16_t) (usage >> 0); + + status = HidP_SetUsageValue(HidP_Output, usage_hi, collection_id, + usage_lo, value, ppd, (PCHAR) r->bytes, r->nbytes); + + if (status != HIDP_STATUS_SUCCESS) { + log_warning("HidP_SetUsages failed: %08x", (unsigned int) status); + + return false; + } + + return true; +} + +void hid_report_out_fini(struct hid_report_out *r) +{ + free(r->bytes); +} + diff --git a/src/main/geninput/hid-report-out.h b/src/main/geninput/hid-report-out.h new file mode 100644 index 0000000..f25dbb0 --- /dev/null +++ b/src/main/geninput/hid-report-out.h @@ -0,0 +1,27 @@ +#ifndef GENINPUT_HID_REPORT_OUT_H +#define GENINPUT_HID_REPORT_OUT_H + +#include +#include + +#include +#include + +struct hid_report_out { + uint8_t id; + uint8_t *bytes; + size_t nbytes; +}; + +bool hid_report_out_init(struct hid_report_out *r, PHIDP_PREPARSED_DATA ppd, + uint8_t id, size_t nbytes); +void hid_report_out_get_bytes(const struct hid_report_out *r, uint8_t *bytes); +bool hid_report_out_set_bit(struct hid_report_out *r, + PHIDP_PREPARSED_DATA ppd, uint16_t collection_id, uint32_t usage, + bool value); +bool hid_report_out_set_value(struct hid_report_out *r, + PHIDP_PREPARSED_DATA ppd, uint16_t collection_id, uint32_t usage, + uint32_t value); +void hid_report_out_fini(struct hid_report_out *r); + +#endif diff --git a/src/main/geninput/hid.c b/src/main/geninput/hid.c new file mode 100644 index 0000000..e51a6ee --- /dev/null +++ b/src/main/geninput/hid.c @@ -0,0 +1,77 @@ +#include +#include + +#include "geninput/dev-list.h" +#include "geninput/hid.h" + +#include "util/log.h" +#include "util/str.h" + +wchar_t *hid_ri_init_name(const GUID *class_guid, const char *dev_node) +{ + struct dev_list devs; + wchar_t *name; + + dev_list_init(&devs, class_guid); + + name = NULL; + + while (dev_list_next(&devs)) { + if (_stricmp(dev_list_get_dev_node(&devs), dev_node) == 0) { + name = wstr_dup(dev_list_get_dev_name(&devs)); + + break; + } + } + + if (name == NULL) { + /* Shouldn't ever happen, but... */ + name = wstr_dup(L"???"); + } + + dev_list_fini(&devs); + + return name; +} + +bool hid_ri_get_name(wchar_t *chars, size_t *nchars, const wchar_t *src_chars) +{ + size_t len; + + log_assert(nchars != NULL); + + len = src_chars != NULL ? wcslen(src_chars) + 1 : 0; + + if (chars == NULL) { + *nchars = len; + + return true; + } else if (*nchars >= len) { + memcpy(chars, src_chars, len * sizeof(wchar_t)); + + return true; + } else { + return false; + } +} + +bool hid_ri_get_controls(struct hid_control *controls, size_t *ncontrols, + const struct hid_control *src_controls, size_t src_ncontrols) +{ + log_assert(ncontrols != NULL); + + if (controls == NULL) { + *ncontrols = src_ncontrols; + + return true; + } else if (*ncontrols >= src_ncontrols) { + *ncontrols = src_ncontrols; + + memcpy(controls, src_controls, src_ncontrols * sizeof(*src_controls)); + + return true; + } else { + return false; + } +} + diff --git a/src/main/geninput/hid.h b/src/main/geninput/hid.h new file mode 100644 index 0000000..371122d --- /dev/null +++ b/src/main/geninput/hid.h @@ -0,0 +1,115 @@ +#ifndef GENINPUT_HID_H +#define GENINPUT_HID_H + +#include +#include +#include +#include + +#include + +#define HID_FLAG_NULLABLE 0x01 +#define HID_FLAG_RELATIVE 0x02 + +DEFINE_GUID(hid_guid, 0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, \ + 0x11, 0x11, 0x00, 0x00, 0x30); + +/* Alright, I'd take the vtable stuff from C++, because this is just gross. + But you can keep everything else from that trainwreck of a language. */ + +struct hid; + +struct hid_control { + uint32_t usage; + int32_t value_min; + int32_t value_max; + uint32_t flags; +}; + +struct hid_light { + uint32_t usage; + + /* I'm going to go ahead and assume nobody will declare a negative light + intensity on their lighting device. + + I will probably regret this decision. */ + + uint32_t value_min; + uint32_t value_max; + + wchar_t name[126]; +}; + +struct hid_vtbl { + void (*close)(struct hid *hid); + bool (*get_device_usage)(const struct hid *hid, uint32_t *usage); + bool (*get_name)(const struct hid *hid, wchar_t *chars, size_t *nchars); + bool (*get_controls)(const struct hid *hid, struct hid_control *controls, + size_t *ncontrols); + bool (*get_lights)(const struct hid *hid, struct hid_light *lights, + size_t *nlights); + bool (*get_value)(struct hid *hid, size_t control_no, int32_t *out_value); + bool (*set_light)(struct hid *hid, size_t light_no, uint32_t intensity); +}; + +struct hid { + const struct hid_vtbl *vptr; +}; + +#define hid_close(hid) \ + (hid)->vptr->close(hid) + +#define hid_get_device_usage(hid, usage) \ + (hid)->vptr->get_device_usage(hid, usage) + +#define hid_get_name(hid, chars, nchars) \ + (hid)->vptr->get_name(hid, chars, nchars) + +#define hid_get_controls(hid, controls, ncontrols) \ + (hid)->vptr->get_controls(hid, controls, ncontrols) + +#define hid_get_lights(hid, lights, nlights) \ + (hid)->vptr->get_lights(hid, lights, nlights) + +#define hid_get_value(hid, control_no, out_value) \ + (hid)->vptr->get_value(hid, control_no, out_value) + +#define hid_set_light(hid, light_no, illuminated) \ + (hid)->vptr->set_light(hid, light_no, illuminated) + +struct hid_ri; + +struct hid_ri_vtbl { + struct hid_vtbl super; + void (*handle_event)(struct hid_ri *hid, const RAWINPUT *ri); +}; + +struct hid_ri { + const struct hid_ri_vtbl *vptr; +}; + +wchar_t *hid_ri_init_name(const GUID *class_guid, const char *dev_node); +bool hid_ri_get_name(wchar_t *chars, size_t *nchars, + const wchar_t *src_chars); +bool hid_ri_get_controls(struct hid_control *controls, size_t *ncontrols, + const struct hid_control *src_controls, size_t src_ncontrols); + +#define hid_ri_handle_event(hid_ri, ri) \ + (hid_ri)->vptr->handle_event(hid_ri, ri) + +struct hid_fd; + +struct hid_fd_vtbl { + struct hid_vtbl super; + bool (*handle_completion)(struct hid_fd *hid, OVERLAPPED *ovl, + size_t nbytes); +}; + +struct hid_fd { + const struct hid_fd_vtbl *vptr; +}; + +#define hid_fd_handle_completion(hid_fd, ovl, nbytes) \ + (hid_fd)->vptr->handle_completion(hid_fd, ovl, nbytes) + +#endif diff --git a/src/main/geninput/hotplug.c b/src/main/geninput/hotplug.c new file mode 100644 index 0000000..f466a2c --- /dev/null +++ b/src/main/geninput/hotplug.c @@ -0,0 +1,67 @@ +#include +#include + +#include + +#include "geninput/hid.h" +#include "geninput/hotplug.h" +#include "geninput/io-thread.h" +#include "geninput/ri.h" + +#include "util/log.h" + +static HDEVNOTIFY hotplug_handle; + +void hotplug_init(HWND wnd) +{ + DEV_BROADCAST_DEVICEINTERFACE filter; + + memset(&filter, 0, sizeof(filter)); + filter.dbcc_size = sizeof(filter); + filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + filter.dbcc_classguid = hid_guid; + + hotplug_handle = RegisterDeviceNotification(wnd, &filter, + DEVICE_NOTIFY_WINDOW_HANDLE); + + if (hotplug_handle == NULL) { + log_fatal("Failed to register for HID hotplug notifications: %08x", + (unsigned int) GetLastError()); + } +} + +void hotplug_handle_msg(WPARAM wparam, const DEV_BROADCAST_HDR *hdr) +{ + const DEV_BROADCAST_DEVICEINTERFACE *dev; + + if (wparam != DBT_DEVICEARRIVAL) { + return; + } + + if (hdr->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) { + return; + } + + dev = (const DEV_BROADCAST_DEVICEINTERFACE *) hdr; + + if (memcmp(&dev->dbcc_classguid, &hid_guid, sizeof(hid_guid))) { + return; + } + + log_misc("HID hotplug detected: %s", dev->dbcc_name); + io_thread_add_device(dev->dbcc_name); + ri_scan_devices(); +} + +void hotplug_fini(void) +{ + BOOL ok; + + ok = UnregisterDeviceNotification(hotplug_handle); + + if (!ok) { + log_warning("Failed to unregister HID hotplug notifications: %08x", + (unsigned int) GetLastError()); + } +} + diff --git a/src/main/geninput/hotplug.h b/src/main/geninput/hotplug.h new file mode 100644 index 0000000..db09c21 --- /dev/null +++ b/src/main/geninput/hotplug.h @@ -0,0 +1,11 @@ +#ifndef GENINPUT_HOTPLUG_H +#define GENINPUT_HOTPLUG_H + +#include +#include + +void hotplug_init(HWND wnd); +void hotplug_handle_msg(WPARAM wparam, const DEV_BROADCAST_HDR *hdr); +void hotplug_fini(void); + +#endif diff --git a/src/main/geninput/input-config.h b/src/main/geninput/input-config.h new file mode 100644 index 0000000..b9c7536 --- /dev/null +++ b/src/main/geninput/input-config.h @@ -0,0 +1,31 @@ +#ifndef GENINPUT_INPUT_CONFIG_H +#define GENINPUT_INPUT_CONFIG_H + +#include +#include + +#include "bemanitools/input.h" + +#include "geninput/mapper.h" + +void mapper_config_save(const char *game_type); +void mapper_clear_action_map(uint8_t action, uint8_t page); +void mapper_clear_light_map(const struct mapped_light *ml); +bool mapper_get_action_map(uint8_t action, uint8_t page, + struct mapped_action *ma); +bool mapper_get_analog_map(uint8_t analog, struct mapped_analog *ma); +int32_t mapper_get_analog_sensitivity(uint8_t analog); +uint8_t mapper_get_nanalogs(void); +uint8_t mapper_get_npages(void); +bool mapper_is_analog_absolute(uint8_t analog); +action_iter_t mapper_iterate_actions(void); +light_iter_t mapper_iterate_lights(void); +void mapper_set_action_map(uint8_t action, uint8_t page, uint8_t bit, + const struct mapped_action *ma); +bool mapper_set_analog_map(uint8_t analog, const struct mapped_analog *ma); +bool mapper_set_analog_sensitivity(uint8_t analog, int32_t sensitivity); +void mapper_set_nanalogs(uint8_t nanalogs); +void mapper_set_nlights(uint8_t nlights); +void mapper_set_light_map(const struct mapped_light *ml, uint8_t game_light); + +#endif diff --git a/src/main/geninput/input.c b/src/main/geninput/input.c new file mode 100644 index 0000000..8f855a1 --- /dev/null +++ b/src/main/geninput/input.c @@ -0,0 +1,223 @@ +#include +#include +#include + +#include + +#include "bemanitools/glue.h" +#include "bemanitools/input.h" + +#include "geninput/hid.h" +#include "geninput/hid-mgr.h" +#include "geninput/io-thread.h" +#include "geninput/mapper.h" +#include "geninput/mapper-s11n.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/msg-thread.h" +#include "util/str.h" +#include "util/thread.h" + +static HINSTANCE input_hinst; +static volatile long input_init_count; + +static FILE *mapper_config_open(const char *game_type, const char *mode) +{ + char path[MAX_PATH]; + + str_format(path, sizeof(path), "%s_v5_00a07.bin", game_type); + + return fopen_appdata("DJHACKERS", path, mode); +} + +void input_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + log_to_external(misc, info, warning, fatal); +} + +void input_init(thread_create_t create, thread_join_t join, + thread_destroy_t destroy) +{ + if (InterlockedIncrement(&input_init_count) != 1) { + return; + } + + log_info("Generic input subsystem is starting up"); + + hid_mgr_init(); + + mapper_inst = mapper_impl_create(); + + thread_api_init(create, join, destroy); + msg_thread_init(input_hinst); + io_thread_init(); +} + +void input_fini(void) +{ + if (InterlockedDecrement(&input_init_count) != 0) { + return; + } + + msg_thread_fini(); + io_thread_fini(); + + mapper_impl_destroy(mapper_inst); + hid_mgr_fini(); + + log_info("Generic input subsystem has shut down"); +} + +bool mapper_config_load(const char *game_type) +{ + struct mapper *m; + FILE *f; + + f = mapper_config_open(game_type, "rb"); + + if (f == NULL) { + goto open_fail; + } + + m = mapper_impl_config_load(f); + + if (m == NULL) { + goto read_fail; + } + + mapper_impl_destroy(mapper_inst); + mapper_inst = m; + + fclose(f); + + return true; + +read_fail: + fclose(f); + +open_fail: + return false; +} + +void mapper_config_save(const char *game_type) +{ + FILE *f; + + f = mapper_config_open(game_type, "wb"); + + if (f == NULL) { + return; + } + + mapper_impl_config_save(mapper_inst, f); + fclose(f); +} + +void mapper_clear_action_map(uint8_t action, uint8_t page) +{ + mapper_impl_clear_action_map(mapper_inst, action, page); +} + +void mapper_clear_light_map(const struct mapped_light *ml) +{ + mapper_impl_clear_light_map(mapper_inst, ml); +} + +bool mapper_get_action_map(uint8_t action, uint8_t page, + struct mapped_action *ma) +{ + return mapper_impl_get_action_map(mapper_inst, action, page, ma); +} + +bool mapper_get_analog_map(uint8_t analog, struct mapped_analog *ma) +{ + return mapper_impl_get_analog_map(mapper_inst, analog, ma); +} + +int32_t mapper_get_analog_sensitivity(uint8_t analog) +{ + return mapper_impl_get_analog_sensitivity(mapper_inst, analog); +} + +uint8_t mapper_get_nanalogs(void) +{ + return mapper_impl_get_nanalogs(mapper_inst); +} + +uint8_t mapper_get_npages(void) +{ + return mapper_impl_get_npages(mapper_inst); +} + +bool mapper_is_analog_absolute(uint8_t analog) +{ + return mapper_impl_is_analog_absolute(mapper_inst, analog); +} + +action_iter_t mapper_iterate_actions(void) +{ + return mapper_impl_iterate_actions(mapper_inst); +} + +light_iter_t mapper_iterate_lights(void) +{ + return mapper_impl_iterate_lights(mapper_inst); +} + +void mapper_set_action_map(uint8_t action, uint8_t page, uint8_t bit, + const struct mapped_action *ma) +{ + mapper_impl_set_action_map(mapper_inst, action, page, bit, ma); +} + +bool mapper_set_analog_map(uint8_t analog, const struct mapped_analog *ma) +{ + return mapper_impl_set_analog_map(mapper_inst, analog, ma); +} + +bool mapper_set_analog_sensitivity(uint8_t analog, int32_t sensitivity) +{ + return mapper_impl_set_analog_sensitivity(mapper_inst, analog, sensitivity); +} + +void mapper_set_light_map(const struct mapped_light *ml, uint8_t game_light) +{ + mapper_impl_set_light_map(mapper_inst, ml, game_light); +} + +void mapper_set_nanalogs(uint8_t nanalogs) +{ + mapper_impl_set_nanalogs(mapper_inst, nanalogs); +} + +void mapper_set_nlights(uint8_t nlights) +{ + mapper_impl_set_nlights(mapper_inst, nlights); +} + +uint8_t mapper_read_analog(uint8_t analog) +{ + return mapper_impl_read_analog(mapper_inst, analog); +} + +uint64_t mapper_update(void) +{ + return mapper_impl_update(mapper_inst); +} + +void mapper_write_light(uint8_t light, uint8_t intensity) +{ + mapper_impl_write_light(mapper_inst, light, intensity); +} + +BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { + input_hinst = hinst; + } + + return TRUE; +} + diff --git a/src/main/geninput/io-thread.c b/src/main/geninput/io-thread.c new file mode 100644 index 0000000..51ad52b --- /dev/null +++ b/src/main/geninput/io-thread.c @@ -0,0 +1,204 @@ +#include + +#include + +#include "geninput/dev-list.h" +#include "geninput/hid.h" +#include "geninput/hid-generic.h" +#include "geninput/hid-mgr.h" +#include "geninput/io-thread.h" +#include "geninput/pacdrive.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/thread.h" + +enum io_thread_cmd { + IO_THREAD_CMD_STOP, + IO_THREAD_CMD_ADD_DEVICE, +}; + +struct io_thread_msg { + enum io_thread_cmd cmd; + const char *dev_node; + HANDLE barrier; +}; + +static HANDLE io_thread_cp; +static int io_thread_id; + +static void io_thread_proc_add_device(const char *dev_node); +static void io_thread_proc_init(void); +static void io_thread_proc_main_loop(void); +static void io_thread_proc_fini(void); +static int io_thread_proc(void *param); + +static void io_thread_proc_add_device(const char *dev_node) +{ + struct hid_stub *stub; + struct hid_fd *hid_fd; + uintptr_t ctx; + + hid_mgr_lock(); + + stub = hid_mgr_get_named_stub(dev_node); + + if (!hid_stub_is_attached(stub)) { + ctx = (uintptr_t) stub; + + if (pac_open(&hid_fd, dev_node, io_thread_cp, ctx) + || hid_generic_open(&hid_fd, dev_node, io_thread_cp, ctx)) { + hid_stub_attach(stub, (struct hid *) hid_fd); + } + } + + hid_mgr_unlock(); +} + +static void io_thread_proc_init(void) +{ + const char *dev_node; + struct dev_list devs; + + io_thread_cp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + + dev_list_init(&devs, &hid_guid); + + while (dev_list_next(&devs)) { + dev_node = dev_list_get_dev_node(&devs); + io_thread_proc_add_device(dev_node); + } + + dev_list_fini(&devs); + + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + + log_info("USB I/O thread ready, thread id = %d", + (unsigned int) GetCurrentThreadId()); +} + +static void io_thread_proc_main_loop(void) +{ + uintptr_t ctx; + OVERLAPPED *p_ovl; + DWORD nbytes; + struct hid_stub *stub; + struct io_thread_msg *msg; + BOOL ok; + + for (;;) { + ok = GetQueuedCompletionStatus(io_thread_cp, &nbytes, + (ULONG_PTR *) &ctx, &p_ovl, 0); + + if (p_ovl != NULL) { + /* An I/O completed (depending on the value of `ok' either + successfuly or not), either way dispatch the completion event */ + + stub = (struct hid_stub *) ctx; + + hid_mgr_lock(); + + if (!ok) { + log_warning("Async IO returned failure: %08x", + (unsigned int) GetLastError()); + hid_stub_detach(stub); + } else { + ok = hid_stub_handle_completion(stub, p_ovl, nbytes); + } + + hid_mgr_unlock(); + + } else if (ok) { + /* No OVERLAPPED*, so this is a command message queued from a + different thread. */ + + msg = (struct io_thread_msg *) ctx; + + if (msg->cmd == IO_THREAD_CMD_STOP) { + break; + } else if (msg->cmd == IO_THREAD_CMD_ADD_DEVICE) { + io_thread_proc_add_device(msg->dev_node); + SetEvent(msg->barrier); + } + + } else if (GetLastError() == WAIT_TIMEOUT) { + /* Failure, but a benign one: timeout due to no event. + I don't exactly know why, but we need to back off here for a bit + or we end up soaking the CPU (in kernel mode...). It's probably + a bemanitools bug instead of an NT deficiency but I'll have to + track down the root cause later. */ + + Sleep(1); + } else { + /* Otherwise idk what's going on */ + + log_warning("Spurious wakeup in I/O mux: %08x", + (unsigned int) GetLastError()); + } + } +} + +static void io_thread_proc_fini(void) +{ + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); + CloseHandle(io_thread_cp); + + log_misc("USB I/O thread shutting down"); +} + +static int io_thread_proc(void *param) +{ + HANDLE barrier; + + barrier = param; + + io_thread_proc_init(); + SetEvent(barrier); + + io_thread_proc_main_loop(); + + io_thread_proc_fini(); + + return 0; +} + +void io_thread_init(void) +{ + HANDLE barrier; + + barrier = CreateEvent(NULL, TRUE, FALSE, NULL); + + io_thread_id = thread_create(io_thread_proc, barrier, + 16384, 0); + + WaitForSingleObject(barrier, INFINITE); + CloseHandle(barrier); +} + +void io_thread_add_device(const char *dev_node) +{ + struct io_thread_msg msg; + + msg.cmd = IO_THREAD_CMD_ADD_DEVICE; + msg.dev_node = dev_node; + msg.barrier = CreateEvent(NULL, TRUE, FALSE, NULL); + + PostQueuedCompletionStatus(io_thread_cp, 0, (uintptr_t) &msg, NULL); + + WaitForSingleObject(msg.barrier, INFINITE); + CloseHandle(msg.barrier); +} + +void io_thread_fini(void) +{ + struct io_thread_msg msg; + + msg.cmd = IO_THREAD_CMD_STOP; + + PostQueuedCompletionStatus(io_thread_cp, 0, (uintptr_t) &msg, NULL); + + thread_join(io_thread_id, NULL); + thread_destroy(io_thread_id); +} + diff --git a/src/main/geninput/io-thread.h b/src/main/geninput/io-thread.h new file mode 100644 index 0000000..19fa7bf --- /dev/null +++ b/src/main/geninput/io-thread.h @@ -0,0 +1,13 @@ +#ifndef GENINPUT_IO_THREAD_H +#define GENINPUT_IO_THREAD_H + +#include + +#include +#include + +void io_thread_init(void); +void io_thread_add_device(const char *dev_node); +void io_thread_fini(void); + +#endif diff --git a/src/main/geninput/kbd-data.c b/src/main/geninput/kbd-data.c new file mode 100644 index 0000000..ecaf0e9 --- /dev/null +++ b/src/main/geninput/kbd-data.c @@ -0,0 +1,55 @@ +#include +#include + +const size_t kbd_basic_nusages = 243; +const size_t kbd_ext_nusages = 100; + +const uint8_t kbd_basic_usages[] = { + 0x00, 0x29, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x2d, 0x2e, 0x2a, 0x2b, + 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, + 0x12, 0x13, 0x2f, 0x30, 0x28, 0xe0, 0x04, 0x16, + 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x33, + 0x34, 0x35, 0xe1, 0x32, 0x1d, 0x1b, 0x06, 0x19, + 0x05, 0x11, 0x10, 0x36, 0x37, 0x38, 0xe5, 0x55, + 0xe2, 0x2c, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, 0x41, 0x42, 0x43, 0x53, 0x47, 0x5f, + 0x60, 0x61, 0x56, 0x5c, 0x5d, 0x5e, 0x57, 0x59, + 0x5a, 0x5b, 0x62, 0x63, 0x00, 0x00, 0x64, 0x44, + 0x45, 0x67, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x00, + 0x88, 0x00, 0x00, 0x87, 0x00, 0x00, 0x94, 0x93, + 0x92, 0x8a, 0x00, 0x8b, 0x00, 0x89, 0x85, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x91, 0x90, +}; + +const uint8_t kbd_ext_usages[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x58, 0xe4, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x46, + 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, + 0x52, 0x4b, 0x00, 0x50, 0x00, 0x4f, 0x00, 0x4d, + 0x51, 0x4e, 0x49, 0x4c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65, 0xe9, 0xea, + 0x00, 0x00, 0x00, 0xeb, +}; diff --git a/src/main/geninput/kbd-data.h b/src/main/geninput/kbd-data.h new file mode 100644 index 0000000..6a31c4b --- /dev/null +++ b/src/main/geninput/kbd-data.h @@ -0,0 +1,13 @@ +#ifndef GENINPUT_KBD_DATA_H +#define GENINPUT_KBD_DATA_H + +#include +#include + +extern const uint8_t kbd_basic_usages[]; +extern const size_t kbd_basic_nusages; + +extern const size_t kbd_ext_nusages; +extern const uint8_t kbd_ext_usages[]; + +#endif diff --git a/src/main/geninput/kbd.c b/src/main/geninput/kbd.c new file mode 100644 index 0000000..ebb82bf --- /dev/null +++ b/src/main/geninput/kbd.c @@ -0,0 +1,184 @@ +#define LOG_MODULE "kbd" + +#include +#include +#include + +#include +#include +#include + +#include "geninput/hid.h" +#include "geninput/kbd.h" +#include "geninput/kbd-data.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +DEFINE_GUID(kbd_guid, + 0x884B96C3, + 0x56EF, + 0x11D1, + 0xBC, 0x8C, 0x00, 0xA0, 0xC9, 0x14, 0x05, 0xDD); + +static struct hid_control kbd_controls[512]; +static size_t kbd_ncontrols; + +static void kbd_static_init(void); +static bool kbd_get_device_usage(const struct hid *hid, uint32_t *usage); +static bool kbd_get_name(const struct hid *hid, wchar_t *chars, + size_t *nchars); +static bool kbd_get_controls(const struct hid *hid, + struct hid_control *controls, size_t *ncontrols); +static bool kbd_get_lights(const struct hid *hid, + struct hid_light *lights, size_t *nlights); +static bool kbd_get_value(struct hid *hid, size_t control_no, + int32_t *out_value); +static bool kbd_set_light(struct hid *hid, size_t light_no, + uint32_t intensity); +static void kbd_handle_event(struct hid_ri *hid_ri, const RAWINPUT *ri); +static void kbd_free(struct hid *hid); + +static const struct hid_ri_vtbl kbd_vtbl = {{ + /* .free = */ kbd_free, + /* .get_device_usage = */ kbd_get_device_usage, + /* .get_name = */ kbd_get_name, + /* .get_controls = */ kbd_get_controls, + /* .get_lights = */ kbd_get_lights, + /* .get_value = */ kbd_get_value, + /* .set_light = */ kbd_set_light }, + /* .handle_event = */ kbd_handle_event +}; + +struct kbd { + struct hid_ri super; + wchar_t *name; + uint32_t state[512]; +}; + +static void kbd_static_init(void) +{ + uint8_t code; + size_t i; + + if (kbd_ncontrols == 0) { + kbd_ncontrols = kbd_basic_nusages + kbd_ext_nusages; + + for (i = 0 ; i < kbd_ncontrols ; i++) { + if (i < kbd_basic_nusages) { + code = kbd_basic_usages[i]; + } else { + code = kbd_ext_usages[i - kbd_basic_nusages]; + } + + kbd_controls[i].usage = 0x00070000 | code; + kbd_controls[i].value_min = 0; + kbd_controls[i].value_max = 1; + kbd_controls[i].flags = 0; + } + } +} + +void kbd_create(struct hid_ri **hid_ri, const char *dev_node) +{ + struct kbd *kbd; + char *tmp; + + kbd_static_init(); + + kbd = xcalloc(sizeof(*kbd)); + kbd->super.vptr = &kbd_vtbl; + kbd->name = hid_ri_init_name(&kbd_guid, dev_node); + + *hid_ri = &kbd->super; + + /* Output some debugging info */ + if (!wstr_narrow(kbd->name, &tmp)) { + tmp = str_dup("???"); + } + + /* Dump diagnostics */ + log_misc("Opened keyboard on dev node %s", dev_node); + log_misc("... Product: %s", tmp); + + free(tmp); +} + +static bool kbd_get_device_usage(const struct hid *hid, uint32_t *usage) +{ + *usage = KBD_DEVICE_USAGE_KEYBOARD; + + return true; +} + +static bool kbd_get_name(const struct hid *hid, wchar_t *chars, + size_t *nchars) +{ + struct kbd *kbd = containerof(hid, struct kbd, super); + + return hid_ri_get_name(chars, nchars, kbd->name); +} + +static bool kbd_get_controls(const struct hid *hid, + struct hid_control *controls, size_t *ncontrols) +{ + return hid_ri_get_controls(controls, ncontrols, kbd_controls, + kbd_ncontrols); +} + +static bool kbd_get_lights(const struct hid *hid, + struct hid_light *lights, size_t *nlights) +{ + log_assert(nlights != NULL); + + *nlights = 0; + + return true; +} + +static bool kbd_get_value(struct hid *hid, size_t control_no, + int32_t *out_value) +{ + struct kbd *kbd = containerof(hid, struct kbd, super); + + log_assert(control_no < kbd_ncontrols); + + *out_value = kbd->state[control_no]; + + return true; +} + +static bool kbd_set_light(struct hid *hid, size_t light_no, + uint32_t intensity) +{ + return false; +} + +static void kbd_handle_event(struct hid_ri *hid_ri, const RAWINPUT *ri) +{ + struct kbd *kbd = containerof(hid_ri, struct kbd, super); + uint32_t value; + + value = (ri->data.keyboard.Flags & RI_KEY_BREAK) == 0; + + if (ri->data.keyboard.Flags & RI_KEY_E0) { + if (ri->data.keyboard.MakeCode < kbd_ext_nusages) { + kbd->state[ri->data.keyboard.MakeCode + kbd_basic_nusages] = value; + } + } else if (!(ri->data.keyboard.Flags & RI_KEY_E1)) { + if (ri->data.keyboard.MakeCode < kbd_basic_nusages) { + kbd->state[ri->data.keyboard.MakeCode] = value; + } + } +} + +static void kbd_free(struct hid *hid) +{ + struct kbd *kbd = containerof(hid, struct kbd, super); + + free(kbd->name); + free(kbd); +} + diff --git a/src/main/geninput/kbd.h b/src/main/geninput/kbd.h new file mode 100644 index 0000000..56d13dd --- /dev/null +++ b/src/main/geninput/kbd.h @@ -0,0 +1,11 @@ +#ifndef GENINPUT_KBD_H +#define GENINPUT_KBD_H + +#include "geninput/hid.h" + +#define KBD_DEVICE_USAGE_KEYBOARD 0x00010006 +#define KBD_DEVICE_USAGE_KEYPAD 0x00010007 + +void kbd_create(struct hid_ri **hid_ri, const char *dev_node); + +#endif diff --git a/src/main/geninput/mapper-s11n.c b/src/main/geninput/mapper-s11n.c new file mode 100644 index 0000000..611a7fe --- /dev/null +++ b/src/main/geninput/mapper-s11n.c @@ -0,0 +1,275 @@ +#include + +#include "geninput/mapper.h" +#include "geninput/mapper-s11n.h" + +#include "util/fs.h" +#include "util/mem.h" + +static bool mapper_impl_config_load_actions(struct mapper *m, FILE *f); +static bool mapper_impl_config_load_analogs(struct mapper *m, FILE *f); +static bool mapper_impl_config_load_lights(struct mapper *m, FILE *f); +static void mapper_impl_config_save_actions(struct mapper *m, FILE *f); +static void mapper_impl_config_save_analogs(struct mapper *m, FILE *f); +static void mapper_impl_config_save_lights(struct mapper *m, FILE *f); + +struct mapper *mapper_impl_config_load(FILE *f) +{ + struct mapper *m; + + hid_mgr_lock(); /* Need to convert from HID objects to /dev nodes */ + + m = mapper_impl_create(); + + if ( !mapper_impl_config_load_actions(m, f) || + !mapper_impl_config_load_analogs(m, f) || + !mapper_impl_config_load_lights(m, f)) { + mapper_impl_destroy(m); + m = NULL; + } + + hid_mgr_unlock(); + + return m; +} + +static bool mapper_impl_config_load_actions(struct mapper *m, FILE *f) +{ + char *dev_node; + struct mapped_action ma; + uint8_t action; + uint8_t page; + uint8_t bit; + uint32_t count; + uint32_t i; + + if (!read_u32(f, &count)) { + return false; + } + + for (i = 0 ; i < count ; i++) { + memset(&ma, 0, sizeof(ma)); + + if (!read_str(f, &dev_node)) { + return false; + } + + ma.hid = hid_mgr_get_named_stub(dev_node); + free(dev_node); + + if ( !read_u32(f, &ma.control_no) || + !read_u32(f, &ma.value_min) || + !read_u32(f, &ma.value_max) || + !read_u8(f, &action) || + !read_u8(f, &page) || + !read_u8(f, &bit)) { + return false; + } + + mapper_impl_set_action_map(m, action, page, bit, &ma); + } + + return true; +} + +static bool mapper_impl_config_load_analogs(struct mapper *m, FILE *f) +{ + char *dev_node; + struct mapped_analog ma; + int32_t sensitivity; + uint8_t nanalogs; + uint8_t i; + + if (!read_u8(f, &nanalogs)) { + return false; + } + + mapper_impl_set_nanalogs(m, nanalogs); + + for (i = 0 ; i < nanalogs ; i++) { + memset(&ma, 0, sizeof(ma)); + + if (!read_str(f, &dev_node)) { + return false; + } + + if (dev_node[0] != '\0') { + ma.hid = hid_mgr_get_named_stub(dev_node); + + if (!read_u32(f, &ma.control_no)) { + return false; + } + + if (!read_u32(f, &sensitivity)) { + return false; + } + + mapper_impl_set_analog_map(m, i, &ma); + mapper_impl_set_analog_sensitivity(m, i, sensitivity); + } + + free(dev_node); + } + + return true; +} + +static bool mapper_impl_config_load_lights(struct mapper *m, FILE *f) +{ + char *dev_node; + struct mapped_light ml; + uint8_t game_light; + uint8_t nlights; + uint32_t count; + uint32_t i; + + if (!read_u8(f, &nlights)) { + return false; + } + + mapper_impl_set_nlights(m, nlights); + + if (!read_u32(f, &count)) { + return false; + } + + for (i = 0 ; i < count ; i++) { + memset(&ml, 0, sizeof(ml)); + + if (!read_str(f, &dev_node)) { + return false; + } + + ml.hid = hid_mgr_get_named_stub(dev_node); + free(dev_node); + + if (!read_u32(f, &ml.light_no)) { + return false; + } + + if (!read_u8(f, &game_light)) { + return false; + } + + mapper_impl_set_light_map(m, &ml, game_light); + } + + return true; +} + +void mapper_impl_config_save(struct mapper *m, FILE *f) +{ + hid_mgr_lock(); + + mapper_impl_config_save_actions(m, f); + mapper_impl_config_save_analogs(m, f); + mapper_impl_config_save_lights(m, f); + + hid_mgr_unlock(); +} + +static void mapper_impl_config_save_actions(struct mapper *m, FILE *f) +{ + action_iter_t pos; + struct mapped_action ma; + uint32_t count; + uint8_t action; + uint8_t page; + uint8_t bit; + + /* Count up bindings */ + + count = 0; + + for (pos = mapper_impl_iterate_actions(m) + ; action_iter_is_valid(pos) + ; action_iter_next(pos)) { + count++; + } + + action_iter_free(pos); + write_u32(f, &count); + + /* Write out bindings */ + + for (pos = mapper_impl_iterate_actions(m) + ; action_iter_is_valid(pos) + ; action_iter_next(pos)) { + action_iter_get_mapping(pos, &ma); + action = action_iter_get_action(pos); + page = action_iter_get_page(pos); + bit = action_iter_get_bit(pos); + + write_str(f, hid_stub_get_dev_node(ma.hid)); + write_u32(f, &ma.control_no); + write_u32(f, &ma.value_min); + write_u32(f, &ma.value_max); + write_u8(f, &action); + write_u8(f, &page); + write_u8(f, &bit); + } + + action_iter_free(pos); +} + +static void mapper_impl_config_save_analogs(struct mapper *m, FILE *f) +{ + struct mapped_analog ma; + int32_t sensitivity; + uint8_t nanalogs; + uint8_t i; + + nanalogs = mapper_impl_get_nanalogs(m); + write_u8(f, &nanalogs); + + for (i = 0 ; i < nanalogs ; i++) { + mapper_impl_get_analog_map(m, i, &ma); + + if (ma.hid == NULL) { + write_str(f, ""); + } else { + sensitivity = mapper_impl_get_analog_sensitivity(m, i); + + write_str(f, hid_stub_get_dev_node(ma.hid)); + write_u32(f, &ma.control_no); + write_u32(f, &sensitivity); + } + } +} + +static void mapper_impl_config_save_lights(struct mapper *m, FILE *f) +{ + light_iter_t pos; + struct mapped_light ml; + uint8_t game_light; + uint8_t nlights; + uint32_t count; + + nlights = mapper_impl_get_nlights(m); + write_u8(f, &nlights); + + count = 0; + + for (pos = mapper_impl_iterate_lights(m) + ; light_iter_is_valid(pos) + ; light_iter_next(pos)) { + count++; + } + + write_u32(f, &count); + light_iter_free(pos); + + for (pos = mapper_impl_iterate_lights(m) + ; light_iter_is_valid(pos) + ; light_iter_next(pos)) { + light_iter_get_mapping(pos, &ml); + game_light = light_iter_get_game_light(pos); + + write_str(f, hid_stub_get_dev_node(ml.hid)); + write_u32(f, &ml.light_no); + write_u8(f, &game_light); + } + + light_iter_free(pos); +} + diff --git a/src/main/geninput/mapper-s11n.h b/src/main/geninput/mapper-s11n.h new file mode 100644 index 0000000..27e6169 --- /dev/null +++ b/src/main/geninput/mapper-s11n.h @@ -0,0 +1,11 @@ +#ifndef GENINPUT_MAPPER_S11N_H +#define GENINPUT_MAPPER_S11N_H + +#include + +#include "geninput/mapper.h" + +struct mapper *mapper_impl_config_load(FILE *f); +void mapper_impl_config_save(struct mapper *m, FILE *f); + +#endif diff --git a/src/main/geninput/mapper.c b/src/main/geninput/mapper.c new file mode 100644 index 0000000..94071fa --- /dev/null +++ b/src/main/geninput/mapper.c @@ -0,0 +1,626 @@ +#include +#include + +#include "geninput/hid-mgr.h" +#include "geninput/mapper.h" + +#include "util/array.h" +#include "util/log.h" +#include "util/mem.h" + +struct action_iter { + struct mapper *mapper; + size_t i; +}; + +struct action_mapping { + struct mapped_action src; + uint8_t action; + uint8_t page; + uint8_t bit; +}; + +struct analog_mapping { + struct mapped_analog src; + int32_t sensitivity; + bool bound; + bool valid; + bool absolute; + double affine_scale; + double affine_bias; + uint8_t pos; +}; + +struct light_iter { + struct mapper *mapper; + size_t i; +}; + +struct light_mapping { + struct mapped_light dest; + uint8_t game_light; + bool bound; + bool valid; + double affine_scale; + int32_t affine_bias; +}; + +struct mapper { + struct array actions; + struct analog_mapping *analogs; + uint8_t nanalogs; + struct array light_maps; + uint8_t *lights; + uint8_t nlights; +}; + +static uint64_t action_mapping_update(struct action_mapping *am); +static void analog_mapping_bind(struct analog_mapping *am); +static void analog_mapping_update(struct analog_mapping *am); +static void light_mapping_bind(struct light_mapping *lm); +static void light_mapping_send(struct light_mapping *lm, + const struct mapper *m); + +struct mapper *mapper_inst; + +void action_iter_get_mapping(action_iter_t iter, struct mapped_action *ma) +{ + struct action_mapping *am; + + log_assert(action_iter_is_valid(iter)); + + am = array_item(struct action_mapping, &iter->mapper->actions, iter->i); + *ma = am->src; +} + +uint8_t action_iter_get_action(action_iter_t iter) +{ + struct action_mapping *am; + + log_assert(action_iter_is_valid(iter)); + + am = array_item(struct action_mapping, &iter->mapper->actions, iter->i); + + return am->action; +} + +uint8_t action_iter_get_page(action_iter_t iter) +{ + struct action_mapping *am; + + log_assert(action_iter_is_valid(iter)); + + am = array_item(struct action_mapping, &iter->mapper->actions, iter->i); + + return am->page; +} + +uint8_t action_iter_get_bit(action_iter_t iter) +{ + struct action_mapping *am; + + log_assert(action_iter_is_valid(iter)); + + am = array_item(struct action_mapping, &iter->mapper->actions, iter->i); + + return am->bit; +} + +bool action_iter_is_valid(action_iter_t iter) +{ + return iter->i < iter->mapper->actions.nitems; +} + +void action_iter_next(action_iter_t iter) +{ + iter->i++; +} + +void action_iter_free(action_iter_t iter) +{ + free(iter); +} + +static uint64_t action_mapping_update(struct action_mapping *am) +{ + int32_t value; + + if (am->src.hid == NULL) { + return 0; + } + + if (!hid_stub_get_value(am->src.hid, am->src.control_no, &value)) { + return 0; + } + + if (value < am->src.value_min || value > am->src.value_max) { + return 0; + } + + return 1ULL << am->bit; +} + +static void analog_mapping_bind(struct analog_mapping *am) +{ + const struct hid_control *ctl; + struct hid_control *controls; + size_t ncontrols; + + am->bound = true; + am->valid = false; + + if (am->src.hid == NULL) { + goto unbound_fail; + } + + if (!hid_stub_get_controls(am->src.hid, NULL, &ncontrols)) { + goto size_fail; + } + + if (am->src.control_no >= ncontrols) { + goto bounds_fail; + } + + controls = xmalloc(sizeof(*controls) * ncontrols); + + if (!hid_stub_get_controls(am->src.hid, controls, &ncontrols)) { + goto read_fail; + } + + ctl = &controls[am->src.control_no]; + + am->affine_bias = ctl->value_min; + am->affine_scale = ctl->value_max - ctl->value_min; + am->absolute = !(ctl->flags & HID_FLAG_RELATIVE); + am->valid = true; + +read_fail: + free(controls); + +bounds_fail: +size_fail: +unbound_fail: + return; +} + +static void analog_mapping_update(struct analog_mapping *am) +{ + double tmp; + int32_t value; + + if (!am->bound) { + analog_mapping_bind(am); + } + + if (!am->valid) { + return; + } + + if (!hid_stub_get_value(am->src.hid, am->src.control_no, &value)) { + return; + } + + if (am->absolute) { + tmp = (value - am->affine_bias) / am->affine_scale; + am->pos = (uint8_t) ((tmp + 0.5) * 256.0); + } else { + am->pos += (int8_t) (value * exp(am->sensitivity / 256.0)); + } +} + +void light_iter_get_mapping(light_iter_t iter, struct mapped_light *ml) +{ + struct light_mapping *lm; + + log_assert(light_iter_is_valid(iter)); + + lm = array_item(struct light_mapping, &iter->mapper->light_maps, iter->i); + *ml = lm->dest; +} + +uint8_t light_iter_get_game_light(light_iter_t iter) +{ + struct light_mapping *lm; + + log_assert(light_iter_is_valid(iter)); + + lm = array_item(struct light_mapping, &iter->mapper->light_maps, iter->i); + + return lm->game_light; +} + +bool light_iter_is_valid(light_iter_t iter) +{ + return iter->i < iter->mapper->light_maps.nitems; +} + +void light_iter_next(light_iter_t iter) +{ + iter->i++; +} + +void light_iter_free(light_iter_t iter) +{ + free(iter); +} + +static void light_mapping_bind(struct light_mapping *lm) +{ + const struct hid_light *light; + struct hid_light *light_defs; + size_t nlights; + + lm->bound = true; + lm->valid = false; + + if (lm->dest.hid == NULL) { + goto unbound_fail; + } + + if (!hid_stub_get_lights(lm->dest.hid, NULL, &nlights)) { + goto size_fail; + } + + if (lm->dest.light_no >= nlights) { + goto bounds_fail; + } + + light_defs = xmalloc(sizeof(*light_defs) * nlights); + + if (!hid_stub_get_lights(lm->dest.hid, light_defs, &nlights)) { + goto read_fail; + } + + light = &light_defs[lm->dest.light_no]; + + lm->affine_bias = light->value_min; + lm->affine_scale = light->value_max - light->value_min; + lm->valid = true; + +read_fail: + free(light_defs); + +bounds_fail: +size_fail: +unbound_fail: + return; +} + +static void light_mapping_send(struct light_mapping *lm, + const struct mapper *m) +{ + double tmp; + uint32_t value; + uint8_t intensity; + + if (!lm->bound) { + light_mapping_bind(lm); + } + + if (!lm->valid) { + return; + } + + if (lm->game_light >= m->nlights) { + return; + } + + intensity = m->lights[lm->game_light]; + tmp = (intensity / 256.0) * lm->affine_scale; + value = (int32_t) (tmp + 0.5) + lm->affine_bias; + + hid_stub_set_light(lm->dest.hid, lm->dest.light_no, value); +} + +struct mapper *mapper_impl_create(void) +{ + struct mapper *m; + + m = xmalloc(sizeof(*m)); + + array_init(&m->actions); + + m->analogs = NULL; + m->nanalogs = 0; + + array_init(&m->light_maps); + + m->lights = NULL; + m->nlights = 0; + + return m; +} + +void mapper_impl_clear_action_map(struct mapper *m, uint8_t action, + uint8_t page) +{ + struct action_mapping *am; + size_t i; + + for (i = 0 ; i < m->actions.nitems ; i++) { + am = array_item(struct action_mapping, &m->actions, i); + + if (am->action == action && am->page == page) { + array_remove(struct action_mapping, &m->actions, i); + + return; + } + } +} + +void mapper_impl_clear_light_map(struct mapper *m, + const struct mapped_light *ml) +{ + struct light_mapping *lm; + size_t i; + + for (i = 0 ; i < m->light_maps.nitems ; ) { + lm = array_item(struct light_mapping, &m->light_maps, i); + + if (memcmp(&lm->dest, ml, sizeof(*ml)) == 0) { + array_remove(struct light_mapping, &m->light_maps, i); + } else { + i++; + } + } +} + +bool mapper_impl_get_action_map(struct mapper *m, uint8_t action, uint8_t page, + struct mapped_action *ma) +{ + const struct action_mapping *am; + size_t i; + + for (i = 0 ; i < m->actions.nitems ; i++) { + am = array_item(struct action_mapping, &m->actions, i); + + if (am->action == action && am->page == page) { + *ma = am->src; + + return true; + } + } + + return false; +} + +bool mapper_impl_get_analog_map(struct mapper *m, uint8_t analog, + struct mapped_analog *ma) +{ + memset(ma, 0, sizeof(*ma)); + + if (analog >= m->nanalogs) { + return false; + } + + *ma = m->analogs[analog].src; + + return true; +} + +int32_t mapper_impl_get_analog_sensitivity(struct mapper *m, uint8_t analog) +{ + if (analog >= m->nanalogs) { + return 0; + } + + return m->analogs[analog].sensitivity; +} + +uint8_t mapper_impl_get_nanalogs(struct mapper *m) +{ + return m->nanalogs; +} + +uint8_t mapper_impl_get_nlights(struct mapper *m) +{ + return m->nlights; +} + +uint8_t mapper_impl_get_npages(struct mapper *m) +{ + const struct action_mapping *am; + size_t i; + int max_page; + + max_page = -1; + + for (i = 0 ; i < m->actions.nitems ; i++) { + am = array_item(struct action_mapping, &m->actions, i); + + if (max_page < am->page) { + max_page = am->page; + } + } + + return (uint8_t) (max_page + 1); +} + +action_iter_t mapper_impl_iterate_actions(struct mapper *m) +{ + struct action_iter *iter; + + iter = xmalloc(sizeof(*iter)); + iter->mapper = m; + iter->i = 0; + + return iter; +} + +light_iter_t mapper_impl_iterate_lights(struct mapper *m) +{ + struct light_iter *iter; + + iter = xmalloc(sizeof(*iter)); + iter->mapper = m; + iter->i = 0; + + return iter; +} + +bool mapper_impl_is_analog_absolute(struct mapper *m, uint8_t analog) +{ + struct analog_mapping *am; + + if (analog >= m->nanalogs) { + return false; + } + + am = &m->analogs[analog]; + + if (!am->bound) { + analog_mapping_bind(am); + } + + return m->analogs[analog].absolute; +} + +void mapper_impl_set_action_map(struct mapper *m, uint8_t action, uint8_t page, + uint8_t bit, const struct mapped_action *ma) +{ + struct action_mapping *am; + size_t i; + + for (i = 0 ; i < m->actions.nitems ; i++) { + am = array_item(struct action_mapping, &m->actions, i); + + if (am->action == action && am->page == page) { + am->src = *ma; + + return; + } + } + + am = array_append(struct action_mapping, &m->actions); + + am->src = *ma; + am->action = action; + am->page = page; + am->bit = bit; +} + +bool mapper_impl_set_analog_map(struct mapper *m, uint8_t analog, + const struct mapped_analog *ma) +{ + struct analog_mapping *am; + + if (analog >= m->nanalogs) { + return false; + } + + am = &m->analogs[analog]; + + am->src = *ma; + am->bound = false; + + return true; +} + +bool mapper_impl_set_analog_sensitivity(struct mapper *m, uint8_t analog, + int32_t sensitivity) +{ + if (analog >= m->nanalogs) { + return false; + } + + m->analogs[analog].sensitivity = sensitivity; + + return true; +} + +void mapper_impl_set_light_map(struct mapper *m, const struct mapped_light *ml, + uint8_t game_light) +{ + struct light_mapping *lm; + size_t i; + + for (i = 0 ; i < m->light_maps.nitems ; i++) { + lm = array_item(struct light_mapping, &m->light_maps, i); + + if (memcmp(&lm->dest, ml, sizeof(*ml)) == 0) { + lm->game_light = game_light; + + return; + } + } + + lm = array_append(struct light_mapping, &m->light_maps); + + memset(lm, 0, sizeof(*lm)); + + lm->dest = *ml; + lm->game_light = game_light; +} + +void mapper_impl_set_nanalogs(struct mapper *m, uint8_t nanalogs) +{ + free(m->analogs); + m->analogs = xcalloc(sizeof(*m->analogs) * nanalogs); + m->nanalogs = nanalogs; +} + +void mapper_impl_set_nlights(struct mapper *m, uint8_t nlights) +{ + free(m->lights); + m->lights = xcalloc(sizeof(*m->lights) * nlights); + m->nlights = nlights; +} + +uint8_t mapper_impl_read_analog(struct mapper *m, uint8_t analog) +{ + if (analog >= m->nanalogs) { + return 0; + } + + return m->analogs[analog].pos; +} + +uint64_t mapper_impl_update(struct mapper *m) +{ + struct action_mapping *am; + struct light_mapping *lm; + size_t i; + uint64_t result; + + hid_mgr_lock(); + + for (i = 0 ; i < m->light_maps.nitems ; i++) { + lm = array_item(struct light_mapping, &m->light_maps, i); + light_mapping_send(lm, m); + } + + for (i = 0 ; i < m->nanalogs ; i++) { + analog_mapping_update(&m->analogs[i]); + } + + result = 0; + + for (i = 0 ; i < m->actions.nitems ; i++) { + am = array_item(struct action_mapping, &m->actions, i); + result |= action_mapping_update(am); + } + + hid_mgr_unlock(); + + return result; +} + +void mapper_impl_write_light(struct mapper *m, uint8_t light, + uint8_t intensity) +{ + if (light >= m->nlights) { + return; + } + + m->lights[light] = intensity; +} + +void mapper_impl_destroy(struct mapper *m) +{ + free(m->lights); + array_fini(&m->light_maps); + free(m->analogs); + array_fini(&m->actions); + free(m); +} + diff --git a/src/main/geninput/mapper.h b/src/main/geninput/mapper.h new file mode 100644 index 0000000..6d51462 --- /dev/null +++ b/src/main/geninput/mapper.h @@ -0,0 +1,82 @@ +#ifndef GENINPUT_MAPPER_H +#define GENINPUT_MAPPER_H + +#include +#include +#include + +#include "geninput/hid-mgr.h" + +#include "util/defs.h" + +typedef struct action_iter *action_iter_t; +typedef struct light_iter *light_iter_t; + +struct mapper; + +struct mapped_action { + struct hid_stub *hid; + uint32_t control_no; + int32_t value_min; + int32_t value_max; +}; + +struct mapped_analog { + struct hid_stub *hid; + size_t control_no; +}; + +struct mapped_light { + struct hid_stub *hid; + size_t light_no; +}; + +extern struct mapper *mapper_inst; + +void action_iter_get_mapping(action_iter_t iter, struct mapped_action *ma); +uint8_t action_iter_get_action(action_iter_t iter); +uint8_t action_iter_get_page(action_iter_t iter); +uint8_t action_iter_get_bit(action_iter_t iter); +bool action_iter_is_valid(action_iter_t iter); +void action_iter_next(action_iter_t iter); +void action_iter_free(action_iter_t iter); + +void light_iter_get_mapping(light_iter_t iter, struct mapped_light *ml); +uint8_t light_iter_get_game_light(light_iter_t iter); +bool light_iter_is_valid(light_iter_t iter); +void light_iter_next(light_iter_t iter); +void light_iter_free(light_iter_t iter); + +struct mapper *mapper_impl_create(void); +void mapper_impl_clear_action_map(struct mapper *m, uint8_t action, + uint8_t page); +void mapper_impl_clear_light_map(struct mapper *m, + const struct mapped_light *ml); +bool mapper_impl_get_analog_map(struct mapper *m, uint8_t analog, + struct mapped_analog *ma); +bool mapper_impl_get_action_map(struct mapper *m, uint8_t action, uint8_t page, + struct mapped_action *ma); +int32_t mapper_impl_get_analog_sensitivity(struct mapper *m, uint8_t analog); +uint8_t mapper_impl_get_nanalogs(struct mapper *m); +uint8_t mapper_impl_get_nlights(struct mapper *m); +uint8_t mapper_impl_get_npages(struct mapper *m); +bool mapper_impl_is_analog_absolute(struct mapper *m, uint8_t analog); +action_iter_t mapper_impl_iterate_actions(struct mapper *m); +light_iter_t mapper_impl_iterate_lights(struct mapper *m); +void mapper_impl_set_action_map(struct mapper *m, uint8_t action, uint8_t page, + uint8_t bit, const struct mapped_action *ma); +bool mapper_impl_set_analog_map(struct mapper *m, uint8_t analog, + const struct mapped_analog *ma); +bool mapper_impl_set_analog_sensitivity(struct mapper *m, uint8_t analog, + int32_t sensitivity); +void mapper_impl_set_light_map(struct mapper *m, const struct mapped_light *ml, + uint8_t game_light); +void mapper_impl_set_nanalogs(struct mapper *m, uint8_t nanalogs); +void mapper_impl_set_nlights(struct mapper *m, uint8_t nlights); +uint8_t mapper_impl_read_analog(struct mapper *m, uint8_t analog); +uint64_t mapper_impl_update(struct mapper *m); +void mapper_impl_write_light(struct mapper *m, uint8_t light, + uint8_t intensity); +void mapper_impl_destroy(struct mapper *m); + +#endif diff --git a/src/main/geninput/mouse.c b/src/main/geninput/mouse.c new file mode 100644 index 0000000..f03e75a --- /dev/null +++ b/src/main/geninput/mouse.c @@ -0,0 +1,257 @@ +#define LOG_MODULE "mouse" + +#include +#include + +#include +#include +#include +#include +#include + +#include "geninput/hid.h" +#include "geninput/mouse.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +enum mouse_ctl_no { + MOUSE_CTL_BTN1, + MOUSE_CTL_BTN2, + MOUSE_CTL_BTN3, + MOUSE_CTL_BTN4, + MOUSE_CTL_BTN5, + MOUSE_CTL_X, + MOUSE_CTL_Y, + MOUSE_CTL_WHEEL, + + MOUSE_CTL_COUNT +}; + +struct mouse { + struct hid_ri super; + wchar_t *name; + long last_x; + long last_y; + long values[MOUSE_CTL_COUNT]; +}; + +static const struct hid_control mouse_controls[MOUSE_CTL_COUNT] = { + /* Left, right, middle, AUX1, AUX2 buttons */ + { 0x00090001, 0x00000000, 0x00000001, 0 }, + { 0x00090002, 0x00000000, 0x00000001, 0 }, + { 0x00090003, 0x00000000, 0x00000001, 0 }, + { 0x00090004, 0x00000000, 0x00000001, 0 }, + { 0x00090005, 0x00000000, 0x00000001, 0 }, + + /* X axis */ + { 0x00010030, 0xC0000000, 0x3FFFFFFF, HID_FLAG_RELATIVE }, + + /* Y axis */ + { 0x00010031, 0xC0000000, 0x3FFFFFFF, HID_FLAG_RELATIVE }, + + /* Mouse wheel */ + { 0x00010038, 0xFFFF8000, 0x00007FFF, HID_FLAG_RELATIVE }, +}; + +static const uint16_t mouse_down_flg[] = { + RI_MOUSE_BUTTON_1_DOWN, + RI_MOUSE_BUTTON_2_DOWN, + RI_MOUSE_BUTTON_3_DOWN, + RI_MOUSE_BUTTON_4_DOWN, + RI_MOUSE_BUTTON_5_DOWN, +}; + +static const uint16_t mouse_up_flg[] = { + RI_MOUSE_BUTTON_1_UP, + RI_MOUSE_BUTTON_2_UP, + RI_MOUSE_BUTTON_3_UP, + RI_MOUSE_BUTTON_4_UP, + RI_MOUSE_BUTTON_5_UP, +}; + +DEFINE_GUID(mouse_guid, 0x378DE44C, 0x56EF, 0x11D1, 0xBC, 0x8C, \ + 0x00, 0xA0, 0xC9, 0x14, 0x05, 0xDD); + +static bool mouse_get_device_usage(const struct hid *hid, uint32_t *usage); +static bool mouse_get_name(const struct hid *hid, wchar_t *chars, + size_t *nchars); +static bool mouse_get_controls(const struct hid *hid, + struct hid_control *controls, size_t *ncontrols); +static bool mouse_get_lights(const struct hid *hid, struct hid_light *lights, + size_t *nlights); +static bool mouse_get_value(struct hid *hid, size_t control_no, + int32_t *out_value); +static bool mouse_set_light(struct hid *hid, size_t light_no, + uint32_t intensity); +static void mouse_handle_event(struct hid_ri *hid_ri, const RAWINPUT *ri); +static void mouse_close(struct hid *hid); + +static const struct hid_ri_vtbl mouse_vtbl = {{ + /* .close = */ mouse_close, + /* .get_device_usage = */ mouse_get_device_usage, + /* .get_name = */ mouse_get_name, + /* .get_controls = */ mouse_get_controls, + /* .get_lights = */ mouse_get_lights, + /* .get_value = */ mouse_get_value, + /* .set_light = */ mouse_set_light }, + /* .handle_event = */ mouse_handle_event +}; + +void mouse_create(struct hid_ri **hid_ri, const char *dev_node) +{ + struct mouse *m; + char *tmp; + + m = xcalloc(sizeof(*m)); + m->super.vptr = &mouse_vtbl; + m->name = hid_ri_init_name(&mouse_guid, dev_node); + + *hid_ri = &m->super; + + if (!wstr_narrow(m->name, &tmp)) { + tmp = str_dup("???"); + } + + log_misc("Opened mouse on dev node %s", dev_node); + log_misc("... Product: %s", tmp); + + free(tmp); +} + +static bool mouse_get_device_usage(const struct hid *hid, uint32_t *usage) +{ + *usage = MOUSE_DEVICE_USAGE; + + return true; +} + +static bool mouse_get_name(const struct hid *hid, wchar_t *chars, + size_t *nchars) +{ + struct mouse *m = containerof(hid, struct mouse, super); + + return hid_ri_get_name(chars, nchars, m->name); +} + +static bool mouse_get_controls(const struct hid *hid, + struct hid_control *controls, size_t *ncontrols) +{ + return hid_ri_get_controls(controls, ncontrols, mouse_controls, + MOUSE_CTL_COUNT); +} + +static bool mouse_get_lights(const struct hid *hid, + struct hid_light *lights, size_t *nlights) +{ + log_assert(nlights != NULL); + + if (lights == NULL) { + *nlights = 0; + } + + return true; +} + +static bool mouse_get_value(struct hid *hid, size_t control_no, + int32_t *out_value) +{ + struct mouse *m = containerof(hid, struct mouse, super); + long tmp; + + log_assert(control_no < MOUSE_CTL_COUNT); + + switch (control_no) { + case MOUSE_CTL_BTN1: + case MOUSE_CTL_BTN2: + case MOUSE_CTL_BTN3: + case MOUSE_CTL_BTN4: + case MOUSE_CTL_BTN5: + *out_value = m->values[control_no]; + + return true; + + case MOUSE_CTL_X: + case MOUSE_CTL_Y: + case MOUSE_CTL_WHEEL: + do { + tmp = m->values[control_no]; + } while (InterlockedCompareExchange( + &m->values[control_no], 0, tmp) != tmp); + + *out_value = tmp; + + return true; + + default: + return false; + } +} + +static bool mouse_set_light(struct hid *hid, size_t light_no, + uint32_t intensity) +{ + return false; +} + +static void mouse_handle_event(struct hid_ri *hid_ri, const RAWINPUT *ri) +{ + struct mouse *m = containerof(hid_ri, struct mouse, super); + long tmp; + long dwheel; + long dx; + long dy; + int i; + + if (ri->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + dx = ri->data.mouse.lLastX - m->last_x; + dy = ri->data.mouse.lLastY - m->last_y; + + m->last_x = ri->data.mouse.lLastX; + m->last_y = ri->data.mouse.lLastY; + } else { + dx = ri->data.mouse.lLastX; + dy = ri->data.mouse.lLastY; + + m->last_x += dx; + m->last_y += dy; + } + + do { + tmp = m->values[MOUSE_CTL_X]; + } while (InterlockedCompareExchange( + &m->values[MOUSE_CTL_X], tmp + dx, tmp) != tmp); + + do { + tmp = m->values[MOUSE_CTL_Y]; + } while (InterlockedCompareExchange( + &m->values[MOUSE_CTL_Y], tmp + dy, tmp) != tmp); + + if (ri->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) { + dwheel = ri->data.mouse.usButtonData; + + do { + tmp = m->values[MOUSE_CTL_WHEEL]; + } while (InterlockedCompareExchange( + &m->values[MOUSE_CTL_WHEEL], tmp + dwheel, tmp) != tmp); + } + + for (i = 0 ; i < 5 ; i++) { + if (ri->data.mouse.usButtonFlags & mouse_down_flg[i]) { + m->values[i] = 1; + } else if (ri->data.mouse.usButtonFlags & mouse_up_flg[i]) { + m->values[i] = 0; + } + } +} + +static void mouse_close(struct hid *hid) +{ + struct mouse *m = containerof(hid, struct mouse, super); + + free(m->name); + free(m); +} + diff --git a/src/main/geninput/mouse.h b/src/main/geninput/mouse.h new file mode 100644 index 0000000..2138baa --- /dev/null +++ b/src/main/geninput/mouse.h @@ -0,0 +1,10 @@ +#ifndef GENINPUT_MOUSE_H +#define GENINPUT_MOUSE_H + +#include "geninput/hid.h" + +#define MOUSE_DEVICE_USAGE 0x00010002 + +void mouse_create(struct hid_ri **hid_ri, const char *dev_node); + +#endif diff --git a/src/main/geninput/msg-thread.c b/src/main/geninput/msg-thread.c new file mode 100644 index 0000000..cad36be --- /dev/null +++ b/src/main/geninput/msg-thread.c @@ -0,0 +1,51 @@ +#include +#include + +#include + +#include "geninput/hotplug.h" +#include "geninput/ri.h" + +#include "util/log.h" +#include "util/msg-thread.h" + +void msg_window_setup(HWND hwnd) +{ + ri_init(hwnd); + hotplug_init(hwnd); + + log_info("Message pump thread ready, thread id = %d", + (unsigned int) GetCurrentThreadId()); + + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); +} + +LRESULT WINAPI msg_window_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_INPUT: + ri_handle_msg((HRAWINPUT) lparam); + + return 0; + + case WM_DEVICECHANGE: + hotplug_handle_msg(wparam, (DEV_BROADCAST_HDR *) lparam); + + return TRUE; + + default: + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} + +void msg_window_teardown(HWND hwnd) +{ + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); + + log_info("Message pump thread shutting down"); + + hotplug_fini(); + ri_fini(); +} + diff --git a/src/main/geninput/pacdrive.c b/src/main/geninput/pacdrive.c new file mode 100644 index 0000000..c7fe5c4 --- /dev/null +++ b/src/main/geninput/pacdrive.c @@ -0,0 +1,235 @@ +#define LOG_MODULE "pacdrive" + +#include +#include + +#include +#include +#include +#include + +#include "geninput/hid.h" +#include "geninput/io-thread.h" +#include "geninput/pacdrive.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" + +/* The PacDrive appears to have a malformed descriptor for its OUT report. + Since PacDrives are fairly prevalent, we have to have a special-case HID + driver for them that uses a hard-coded report structure. */ + +struct pac_report { + uint8_t report_id; + uint8_t static_00; + uint8_t static_dd; + uint8_t leds_hi; + uint8_t leds_lo; +}; + +struct pac { + struct hid_fd super; + HANDLE fd; + OVERLAPPED ovl; + struct pac_report io_buf; + uint16_t leds; +}; + +static struct hid_light pac_lights[16]; +static const wchar_t pac_name[] = L"PacDrive Shim"; + +static bool pac_get_device_usage(const struct hid *hid, uint32_t *usage); +static bool pac_get_name(const struct hid *hid, wchar_t *chars, + size_t *nchars); +static bool pac_get_controls(const struct hid *hid, + struct hid_control *controls, size_t *ncontrols); +static bool pac_get_lights(const struct hid *hid, struct hid_light *lights, + size_t *nlights); +static bool pac_get_value(struct hid *hid, size_t control_no, int32_t *value); +static bool pac_set_light(struct hid *hid, size_t light_no, + uint32_t intensity); +static bool pac_handle_event(struct hid_fd *hid_fd, OVERLAPPED *ovl, + size_t nbytes); +static void pac_close(struct hid *hid); + +static const struct hid_fd_vtbl pac_vtbl = {{ + /* .close = */ pac_close, + /* .get_device_usage = */ pac_get_device_usage, + /* .get_name = */ pac_get_name, + /* .get_controls = */ pac_get_controls, + /* .get_lights = */ pac_get_lights, + /* .get_value = */ pac_get_value, + /* .set_light = */ pac_set_light }, + /* .handle_event = */ pac_handle_event +}; + +bool pac_open(struct hid_fd **hid_out, const char *dev_node, HANDLE iocp, + uintptr_t iocp_ctx) +{ + HIDD_ATTRIBUTES attrs; + HANDLE fd; + struct pac *pac; + int i; + + fd = CreateFile(dev_node, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + + if (fd == INVALID_HANDLE_VALUE) { + goto open_fail; + } + + if (!HidD_GetAttributes(fd, &attrs)) { + goto attrs_fail; + } + + if (attrs.VendorID != 0xD209 || (attrs.ProductID & 0xFFF8) != 0x1500) { + goto id_fail; + } + + pac = xcalloc(sizeof(*pac)); + + pac->super.vptr = &pac_vtbl; + pac->fd = fd; + + CreateIoCompletionPort(fd, iocp, iocp_ctx, 0); + + if (!WriteFile(fd, &pac->io_buf, sizeof(pac->io_buf), NULL, &pac->ovl) + && GetLastError() != ERROR_IO_PENDING) { + log_warning("%s: Initial WriteFile failed: %08x", + dev_node, (unsigned int) GetLastError()); + + goto write_fail; + } + + for (i = 0 ; i < lengthof(pac_lights) ; i++) { + pac_lights[i].usage = 0x0008004B; /* LED, Generic Indicator */ + pac_lights[i].value_min = 0; + pac_lights[i].value_max = 1; + } + + log_misc("Opened PacDrive on %s", dev_node); + + *hid_out = &pac->super; + + return true; + +write_fail: + free(pac); + +id_fail: +attrs_fail: + CloseHandle(fd); + +open_fail: + return false; +} + +static bool pac_get_device_usage(const struct hid *hid, uint32_t *usage) +{ + *usage = 0x00010000; + + return true; +} + +static bool pac_get_name(const struct hid *hid, wchar_t *chars, + size_t *nchars) +{ + log_assert(nchars != NULL); + + if (chars != NULL) { + if (*nchars < lengthof(pac_name)) { + return false; + } + + memcpy(chars, pac_name, sizeof(pac_name)); + } + + *nchars = lengthof(pac_name); + + return true; +} + +static bool pac_get_controls(const struct hid *hid, + struct hid_control *controls, size_t *ncontrols) +{ + log_assert(ncontrols != NULL); + + *ncontrols = 0; + + return true; +} + +static bool pac_get_lights(const struct hid *hid, struct hid_light *lights, + size_t *nlights) +{ + log_assert(nlights != NULL); + + if (lights != NULL) { + if (*nlights < lengthof(pac_lights)) { + return false; + } + + memcpy(lights, pac_lights, sizeof(pac_lights)); + } + + *nlights = lengthof(pac_lights); + + return true; +} + +static bool pac_get_value(struct hid *hid, size_t control_no, int32_t *value) +{ + return false; +} + +static bool pac_set_light(struct hid *hid, size_t light_no, + uint32_t intensity) +{ + struct pac *pac = containerof(hid, struct pac, super); + + if (light_no >= lengthof(pac_lights)) { + return false; + } + + if (intensity != 0) { + pac->leds |= (1 << light_no); + } else { + pac->leds &= ~(1 << light_no); + } + + return true; +} + +static bool pac_handle_event(struct hid_fd *hid_fd, OVERLAPPED *ovl, + size_t nbytes) +{ + struct pac *pac = containerof(hid_fd, struct pac, super); + + /* OUT report successfully sent, send another */ + + pac->io_buf.leds_hi = HIBYTE(pac->leds); + pac->io_buf.leds_lo = LOBYTE(pac->leds); + + memset(&pac->ovl, 0, sizeof(pac->ovl)); + + if (!WriteFile(pac->fd, &pac->io_buf, sizeof(pac->io_buf), NULL, + &pac->ovl) && GetLastError() != ERROR_IO_PENDING) { + log_warning("WriteFile failed: %08x", (unsigned int) GetLastError()); + + return false; + } + + return true; +} + +static void pac_close(struct hid *hid) +{ + struct pac *pac = containerof(hid, struct pac, super); + + CloseHandle(pac->fd); + + free(pac); +} + diff --git a/src/main/geninput/pacdrive.h b/src/main/geninput/pacdrive.h new file mode 100644 index 0000000..f5cb321 --- /dev/null +++ b/src/main/geninput/pacdrive.h @@ -0,0 +1,12 @@ +#ifndef GENINPUT_PACDRIVE_H +#define GENINPUT_PACDRIVE_H + +#include +#include + +#include "geninput/io-thread.h" + +bool pac_open(struct hid_fd **hid_out, const char *dev_node, HANDLE iocp, + uintptr_t iocp_ctx); + +#endif diff --git a/src/main/geninput/ri.c b/src/main/geninput/ri.c new file mode 100644 index 0000000..c78b36a --- /dev/null +++ b/src/main/geninput/ri.c @@ -0,0 +1,187 @@ +#include + +#include "geninput/hid.h" +#include "geninput/hid-mgr.h" +#include "geninput/kbd.h" +#include "geninput/mouse.h" +#include "geninput/ri.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" + +struct ri_handle { + HANDLE fake_fd; + struct hid_ri *hid_ri; +}; + +static struct ri_handle *ri_handles; +static unsigned int ri_ndevs; + +void ri_init(HWND hwnd) +{ + RAWINPUTDEVICE filter[3]; + + memset(filter, 0, sizeof(filter)); + + filter[0].usUsagePage = KBD_DEVICE_USAGE_KEYBOARD >> 16; + filter[0].usUsage = (uint16_t) KBD_DEVICE_USAGE_KEYBOARD; + filter[0].dwFlags = RIDEV_INPUTSINK; + filter[0].hwndTarget = hwnd; + + filter[1].usUsagePage = KBD_DEVICE_USAGE_KEYPAD >> 16; + filter[1].usUsage = (uint16_t) KBD_DEVICE_USAGE_KEYPAD; + filter[1].dwFlags = RIDEV_INPUTSINK; + filter[1].hwndTarget = hwnd; + + filter[2].usUsagePage = MOUSE_DEVICE_USAGE >> 16; + filter[2].usUsage = (uint16_t) MOUSE_DEVICE_USAGE; + filter[2].dwFlags = RIDEV_INPUTSINK; + filter[2].hwndTarget = hwnd; + + if (!RegisterRawInputDevices(filter, lengthof(filter), sizeof(filter[0]))) { + log_fatal("RegisterRawInputDevices failed: %x", + (unsigned int) GetLastError()); + } + + ri_scan_devices(); +} + +void ri_scan_devices(void) +{ + char *dev_node; + struct hid_stub *stub; + RAWINPUTDEVICELIST *ridl; + unsigned int ridl_size; + unsigned int size; + unsigned int i; + + hid_mgr_lock(); + + GetRawInputDeviceList(NULL, &ri_ndevs, sizeof(*ridl)); + + /* Param 2 of GetRawInputDeviceList changes meaning depending on whether or + not param 1 is NULL */ + + ridl_size = sizeof(*ridl) * ri_ndevs; + ridl = xmalloc(ridl_size); + + if (GetRawInputDeviceList(ridl, &ridl_size, sizeof(*ridl)) == -1) { + log_fatal("GetRawInputDeviceList data failed"); + } + + ri_handles = xrealloc(ri_handles, sizeof(*ri_handles) * ri_ndevs); + + for (i = 0 ; i < ri_ndevs ; i++) { + ri_handles[i].fake_fd = NULL; + ri_handles[i].hid_ri = NULL; + + size = 0; + GetRawInputDeviceInfo(ridl[i].hDevice, RIDI_DEVICENAME, NULL, &size); + + dev_node = xmalloc(size); + + if (GetRawInputDeviceInfo(ridl[i].hDevice, RIDI_DEVICENAME, dev_node, + &size) == -1) { + log_warning("GetRawInputDeviceInfo(RIDI_DEVICENAME) data failed"); + + goto skip; + } + + if (size > 1 && dev_node[0] == '\\' && dev_node[1] == '?') { + /* WinXP (and only WinXP, not Vista or up) return device nodes + starting with \??\ here instead of \\?\ like every other part + of Windows does. I'm assuming this is a bug. */ + dev_node[1] = '\\'; + } + + stub = hid_mgr_get_named_stub(dev_node); + + /* NOTE: hid_mgr owns the ridev, not us. Raw Input devices do not + get destroyed until geninput shuts down, they merely cease to + update if the corresponding physical device is unplugged. */ + + if (ridl[i].dwType == RIM_TYPEKEYBOARD) { + ri_handles[i].fake_fd = ridl[i].hDevice; + kbd_create(&ri_handles[i].hid_ri, dev_node); + + hid_stub_attach(stub, (struct hid *) ri_handles[i].hid_ri); + } else if (ridl[i].dwType == RIM_TYPEMOUSE) { + ri_handles[i].fake_fd = ridl[i].hDevice; + mouse_create(&ri_handles[i].hid_ri, dev_node); + + hid_stub_attach(stub, (struct hid *) ri_handles[i].hid_ri); + } + +skip: + free(dev_node); + } + + free(ridl); + + hid_mgr_unlock(); +} + +void ri_handle_msg(HRAWINPUT msg) +{ + unsigned int i; + unsigned int nbytes; + RAWINPUT ri; + + nbytes = sizeof(ri); + + if (GetRawInputData(msg, RID_INPUT, &ri, &nbytes, sizeof(ri.header)) + == (UINT) -1) { + log_warning("GetRawInputData failed: %x", + (unsigned int) GetLastError()); + + return; + } + + if (ri.header.hDevice == NULL) { + /* WTF?? I've seen this happen while remote-controlling someone's + desktop using TeamViewer, possibly due to an API hook injected by TV + doing something strange. However, if it's actually me misusing the + API somehow then I'd like to trap this case so that it doesn't blow + up my RawInput dispatcher. */ + return; + } + + for (i = 0 ; i < ri_ndevs ; i++) { + if (ri_handles[i].fake_fd == ri.header.hDevice) { + hid_ri_handle_event(ri_handles[i].hid_ri, &ri); + + return; + } + } +} + +void ri_fini(void) +{ + RAWINPUTDEVICE filter[3]; + + free(ri_handles); + ri_handles = NULL; + ri_ndevs = 0; + + filter[0].usUsagePage = KBD_DEVICE_USAGE_KEYBOARD >> 16; + filter[0].usUsage = (uint16_t) KBD_DEVICE_USAGE_KEYBOARD; + filter[0].dwFlags = RIDEV_REMOVE; + filter[0].hwndTarget = NULL; + + filter[1].usUsagePage = KBD_DEVICE_USAGE_KEYPAD >> 16; + filter[1].usUsage = (uint16_t) KBD_DEVICE_USAGE_KEYPAD; + filter[1].dwFlags = RIDEV_REMOVE; + filter[1].hwndTarget = NULL; + + filter[2].usUsagePage = MOUSE_DEVICE_USAGE >> 16; + filter[2].usUsage = (uint16_t) MOUSE_DEVICE_USAGE; + filter[2].dwFlags = RIDEV_REMOVE; + filter[2].hwndTarget = NULL; + + if (!RegisterRawInputDevices(filter, lengthof(filter), sizeof(filter[0]))) { + log_warning("RegisterRawInputDevices(RIDEV_REMOVE) failed: %x", + (unsigned int) GetLastError()); + } +} + diff --git a/src/main/geninput/ri.h b/src/main/geninput/ri.h new file mode 100644 index 0000000..81bb0cd --- /dev/null +++ b/src/main/geninput/ri.h @@ -0,0 +1,11 @@ +#ifndef GENINPUT_RI_H +#define GENINPUT_RI_H + +#include + +void ri_init(HWND hwnd); +void ri_scan_devices(void); +void ri_handle_msg(HRAWINPUT msg); +void ri_fini(void); + +#endif diff --git a/src/main/hook/Module.mk b/src/main/hook/Module.mk new file mode 100644 index 0000000..7c328e0 --- /dev/null +++ b/src/main/hook/Module.mk @@ -0,0 +1,9 @@ +libs += hook + +src_hook := \ + com-proxy.c \ + iohook.c \ + pe.c \ + peb.c \ + table.c \ + diff --git a/src/main/hook/com-proxy.c b/src/main/hook/com-proxy.c new file mode 100644 index 0000000..186402f --- /dev/null +++ b/src/main/hook/com-proxy.c @@ -0,0 +1,145 @@ +#include +#include + +#include + +#include "hook/com-proxy.h" + +#include "util/defs.h" +#include "util/mem.h" + +#ifdef _WIN64 + + /***** 64-BIT TRAMPOLINE *****/ + +#define SLOT_OFFSET 0x0A +static const uint8_t com_proxy_tramp[] = { + /* mov rcx, [rcx+8] ; Replace this with this->real */ + 0x48, 0x8B, 0x49, 0x08, + + /* mov rax, [rcx] ; Get real->vtbl */ + 0x48, 0x8B, 0x01, + + /* mov rax, [rax+XX] ; Get vtbl->slot_XX */ + 0x48, 0x8B, 0x80, -1, -1, -1, -1, + + /* jmp rax ; Continue to slot_XX */ + 0xFF, 0xE0, +}; + +#else + + /***** 32-BIT TRAMPOLINE *****/ + +#define SLOT_OFFSET 0x0F +static const uint8_t com_proxy_tramp[] = { + /* mov eax, [esp+4] ; Get this */ + 0x8B, 0x44, 0x24, 0x04, + + /* mov eax, [eax+4] ; Get this->real */ + 0x8B, 0x40, 0x04, + + /* mov [esp+4], eax ; Replace this with this->real on stack */ + 0x89, 0x44, 0x24, 0x04, + + /* mov ecx, [eax] ; Get real->vtbl */ + 0x8B, 0x08, + + /* mov ecx, [ecx+XX] ; Get vtbl->slot_XX */ + 0x8B, 0x89, -1, -1, -1, -1, + + /* jmp ecx ; Continue to slot_XX */ + 0xFF, 0xE1 +}; + +#endif + +static HRESULT STDCALL com_proxy_query_interface(IUnknown *ptr, REFIID iid, + void **iface) +{ + struct com_proxy *self = (struct com_proxy *) ptr; + IUnknown *obj = self->real; /* Not necessarily the real IUnknown* */ + + /* To some extent, COM is designed to support shennanigans like these. + We can safely pass the call straight through to the underlying + interface pointer because of the following: + + "It is specifically not the case that queries for interfaces other + than IUnknown (even the same interface through the same pointer) + must return the same pointer value." + + - MSDN documentation for IUnknown::QueryInterface() + + Of course, pretty much everyone screws up COM's conventions (probably + including me in this very module to be honest), so if someone ends up + relying on broken assumptions then this could well get a lot more + complicated. */ + + return IUnknown_QueryInterface(obj, iid, iface); +} + +static ULONG STDCALL com_proxy_addref(IUnknown *ptr) +{ + struct com_proxy *self = (struct com_proxy *) ptr; + IUnknown *obj = self->real; + + return IUnknown_AddRef(obj); +} + +static ULONG STDCALL com_proxy_release(IUnknown *ptr) +{ + struct com_proxy *self = (struct com_proxy *) ptr; + IUnknown *obj = self->real; + ULONG result; + + result = IUnknown_Release(obj); + + if (!result) { + /* Last ref to underlying object released */ + VirtualFree(self->tramps, 0, MEM_RELEASE); + free(self->vptr); + free(self); + } + + return result; +} + +struct com_proxy *com_proxy_wrap(void *iface, size_t vtbl_size) +{ + struct com_proxy *self; + void **vtbl; + uint8_t *cur_tramp; + uint32_t nslots; + uint32_t i; + + nslots = vtbl_size / sizeof(void *); + + self = xmalloc(sizeof(*self)); + self->vptr = xmalloc(vtbl_size); + self->real = iface; + self->tramps = VirtualAlloc(NULL, sizeof(com_proxy_tramp) * nslots, + MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); + + /* Set up proxied IUnknown impl */ + vtbl = self->vptr; + vtbl[0] = com_proxy_query_interface; + vtbl[1] = com_proxy_addref; + vtbl[2] = com_proxy_release; + + /* Populate trampoline code for remaining vtbl entries */ + for (i = 3 /* Skip IUnknown */ ; i < nslots ; i++) { + cur_tramp = self->tramps + i * sizeof(com_proxy_tramp); + + /* Copy template */ + memcpy(cur_tramp, com_proxy_tramp, sizeof(com_proxy_tramp)); + + /* Patch XX into vtbl lookup (see definition of tramp) */ + *((uint32_t *) (cur_tramp + SLOT_OFFSET)) = i * sizeof(void *); + + /* Set vtable entry */ + vtbl[i] = cur_tramp; + } + + return self; +} + diff --git a/src/main/hook/com-proxy.h b/src/main/hook/com-proxy.h new file mode 100644 index 0000000..49c0a74 --- /dev/null +++ b/src/main/hook/com-proxy.h @@ -0,0 +1,54 @@ +#ifndef HOOK_COM_PROXY_H +#define HOOK_COM_PROXY_H + +#include +#include + +/* N.B. Here be dragons. You really ought to be fairly familiar with COM + before using this, otherwise you risk ignoring the subtler issues at your + peril. */ + +#define COM_PROXY_UNWRAP(self) (((struct com_proxy *) self)->real) + +struct com_proxy { + /* Pointer to vtable filled with trampolines. Edit these as you please. + Each com_proxy has its own independent vtable. */ + void *vptr; + + /* Interface pointer wrapped by this proxy. */ + void *real; + + /* Dynamically generated x86 trampoline code. The initial vtable entries + all point into code located here. */ + uint8_t *tramps; +}; + +/* Wrap a COM interface pointer in a proxy. This is an object that acts just + like the object that it wraps, but has a freely editable vtable, which you + can modify in order to intercept a subset of the interface's method calls. + + By default, all the vtable slots contain dynamically generated trampolines + which pass method calls onwards to the corresponding methods in the + underlying object's vtable. + + NOTE! This does not AddRef the underlying interface. + + NOTE! This function wraps COM POINTERS, not COM OBJECTS (since the latter + is, in general, impossible). If you're insufficiently versed in COM to + understand the difference... well, you really should be, but the following + observations are a start: + + 1. Do not wrap IUnknown pointers with this function. This will break the + IUnknown::QueryInterface contract. This refers to _the_ unique + IUnknown* for the object, not for any other interface (which necessarily + extends IUnknown). Wrapping the unique IUnknown* for an object will + cause it to no longer be unique. + + 2. Callers can "jailbreak" your wrapper using IUnknown::QueryInterface. + + Generally this isn't an issue for DirectX objects, since nobody ever seems + to use QueryInterface with them. */ + +struct com_proxy *com_proxy_wrap(void *iface, size_t vtbl_size); + +#endif diff --git a/src/main/hook/iohook.c b/src/main/hook/iohook.c new file mode 100644 index 0000000..a0fee58 --- /dev/null +++ b/src/main/hook/iohook.c @@ -0,0 +1,816 @@ +#define LOG_MODULE "iohook" + +#include + +#include +#include + +#include "hook/iohook.h" +#include "hook/table.h" + +#include "util/hr.h" +#include "util/log.h" +#include "util/str.h" + +/* Helpers */ + +static BOOL iohook_overlapped_result( + uint32_t *syncout, + OVERLAPPED *ovl, + uint32_t value); + +static HRESULT irp_invoke_real(struct irp *irp); +static HRESULT irp_invoke_real_open(struct irp *irp); +static HRESULT irp_invoke_real_close(struct irp *irp); +static HRESULT irp_invoke_real_read(struct irp *irp); +static HRESULT irp_invoke_real_write(struct irp *irp); +static HRESULT irp_invoke_real_seek(struct irp *irp); +static HRESULT irp_invoke_real_fsync(struct irp *irp); +static HRESULT irp_invoke_real_ioctl(struct irp *irp); + +/* API hooks */ + +static BOOL STDCALL my_CloseHandle(HANDLE fd); + +static HANDLE STDCALL my_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE STDCALL my_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static BOOL STDCALL my_ReadFile( + HANDLE hFile, + void *lpBuffer, + uint32_t nNumberOfBytesToRead, + uint32_t *lpNumberOfBytesRead, + OVERLAPPED *lpOverlapped); + +static BOOL STDCALL my_WriteFile( + HANDLE hFile, + const void *lpBuffer, + uint32_t nNumberOfBytesToWrite, + uint32_t *lpNumberOfBytesWritten, + OVERLAPPED *lpOverlapped); + +static DWORD STDCALL my_SetFilePointer( + HANDLE hFile, + int32_t lDistanceToMove, + int32_t *lpDistanceToMoveHigh, + uint32_t dwMoveMethod); + +static BOOL STDCALL my_FlushFileBuffers(HANDLE hFile); + +static BOOL STDCALL my_DeviceIoControl( + HANDLE hFile, + uint32_t dwIoControlCode, + void *lpInBuffer, + uint32_t nInBufferSize, + void *lpOutBuffer, + uint32_t nOutBufferSize, + uint32_t *lpBytesReturned, + OVERLAPPED *lpOverlapped); + +/* Links */ + +static BOOL (STDCALL *real_CloseHandle)(HANDLE fd); + +static HANDLE (STDCALL *real_CreateFileW)( + const wchar_t *filename, + uint32_t access, + uint32_t share, + SECURITY_ATTRIBUTES *sa, + uint32_t creation, + uint32_t flags, + HANDLE tmpl); + +static BOOL (STDCALL *real_DeviceIoControl)( + HANDLE fd, + uint32_t code, + void *in_bytes, + uint32_t in_nbytes, + void *out_bytes, + uint32_t out_nbytes, + uint32_t *out_returned, + OVERLAPPED *ovl); + +static BOOL (STDCALL *real_ReadFile)( + HANDLE fd, + void *buf, + uint32_t nbytes, + uint32_t *nread, + OVERLAPPED *ovl); + +static BOOL (STDCALL *real_WriteFile)( + HANDLE fd, + const void *buf, + uint32_t nbytes, + uint32_t *nwrit, + OVERLAPPED *ovl); + +static DWORD (STDCALL *real_SetFilePointer)( + HANDLE hFile, + int32_t lDistanceToMove, + int32_t *lpDistanceToMoveHigh, + uint32_t dwMoveMethod); + +static BOOL (STDCALL *real_FlushFileBuffers)(HANDLE fd); + +/* Hook table */ + +static struct hook_symbol iohook_kernel32_syms[] = { + /* Basic IO */ + + { + .name = "CloseHandle", + .patch = my_CloseHandle, + .link = (void *) &real_CloseHandle, + }, + { + .name = "CreateFileA", + .patch = my_CreateFileA, + }, + { + .name = "CreateFileW", + .patch = my_CreateFileW, + .link = (void *) &real_CreateFileW, + }, + { + .name = "DeviceIoControl", + .patch = my_DeviceIoControl, + .link = (void *) &real_DeviceIoControl, + }, + { + .name = "ReadFile", + .patch = my_ReadFile, + .link = (void *) &real_ReadFile, + }, + { + .name = "WriteFile", + .patch = my_WriteFile, + .link = (void *) &real_WriteFile, + }, + { + .name = "SetFilePointer", + .patch = my_SetFilePointer, + .link = (void *) &real_SetFilePointer, + }, + { + .name = "FlushFileBuffers", + .patch = my_FlushFileBuffers, + .link = (void *) &real_FlushFileBuffers, + }, +}; + +static const irp_handler_t irp_real_handlers[] = { + [IRP_OP_OPEN] = irp_invoke_real_open, + [IRP_OP_CLOSE] = irp_invoke_real_close, + [IRP_OP_READ] = irp_invoke_real_read, + [IRP_OP_WRITE] = irp_invoke_real_write, + [IRP_OP_SEEK] = irp_invoke_real_seek, + [IRP_OP_FSYNC] = irp_invoke_real_fsync, + [IRP_OP_IOCTL] = irp_invoke_real_ioctl, +}; + +static const irp_handler_t *iohook_handlers; +static size_t iohook_nhandlers; + +void iohook_init(const irp_handler_t *handlers, size_t nhandlers) +{ + log_assert(handlers != NULL); + log_assert(iohook_handlers == NULL); + + iohook_handlers = handlers; + iohook_nhandlers = nhandlers; + + hook_table_apply( + NULL, + "kernel32.dll", + iohook_kernel32_syms, + lengthof(iohook_kernel32_syms)); + + if (real_CreateFileW == NULL) { + /* my_CreateFileA requires this to be present */ + real_CreateFileW = (void *) GetProcAddress( + GetModuleHandleA("kernel32.dll"), + "CreateFileW"); + } + + log_info("IO Hook subsystem initialized"); +} + +HANDLE iohook_open_dummy_fd(void) +{ + HANDLE fd; + + fd = real_CreateFileW( + L"NUL", + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + + if (fd == INVALID_HANDLE_VALUE) { + log_fatal("Failed to open dummy fd: %08x", (uint32_t) GetLastError()); + } + + return fd; +} + +static BOOL iohook_overlapped_result( + uint32_t *syncout, + OVERLAPPED *ovl, + uint32_t value) +{ + if (ovl != NULL) { + ovl->Internal = 0; // (NTSTATUS) STATUS_SUCCESS + ovl->InternalHigh = value; + + if (ovl->hEvent != NULL) { + SetEvent(ovl->hEvent); + } + } + + if (syncout != NULL) { + *syncout = value; + SetLastError(ERROR_SUCCESS); + + return TRUE; + } else { + SetLastError(ERROR_IO_PENDING); + + return FALSE; + } +} + +HRESULT irp_invoke_next(struct irp *irp) +{ + irp_handler_t handler; + HRESULT hr; + + log_assert(irp != NULL); + log_assert(irp->next_handler <= iohook_nhandlers); + + if (irp->next_handler < iohook_nhandlers) { + handler = iohook_handlers[irp->next_handler++]; + hr = handler(irp); + + if (FAILED(hr)) { + irp->next_handler = (size_t) -1; + } + } else { + irp->next_handler = (size_t) -1; + hr = irp_invoke_real(irp); + } + + return hr; +} + +static HRESULT irp_invoke_real(struct irp *irp) +{ + irp_handler_t handler; + + log_assert(irp != NULL); + log_assert(irp->op < lengthof(irp_real_handlers)); + + handler = irp_real_handlers[irp->op]; + + log_assert(handler != NULL); + + return handler(irp); +} + +static HRESULT irp_invoke_real_open(struct irp *irp) +{ + HANDLE fd; + + log_assert(irp != NULL); + + fd = real_CreateFileW( + irp->open_filename, + irp->open_access, + irp->open_share, + irp->open_sa, + irp->open_creation, + irp->open_flags, + irp->open_tmpl); + + if (fd == INVALID_HANDLE_VALUE) { + return hr_from_win32(); + } + + irp->fd = fd; + + return S_OK; +} + +static HRESULT irp_invoke_real_close(struct irp *irp) +{ + BOOL ok; + + log_assert(irp != NULL); + + ok = real_CloseHandle(irp->fd); + + if (!ok) { + return hr_from_win32(); + } + + return S_OK; +} + +static HRESULT irp_invoke_real_read(struct irp *irp) +{ + uint32_t nread; + BOOL ok; + + log_assert(irp != NULL); + + ok = real_ReadFile( + irp->fd, + &irp->read.bytes[irp->read.pos], + irp->read.nbytes - irp->read.pos, + &nread, + irp->ovl); + + if (!ok) { + return hr_from_win32(); + } + + irp->read.pos += nread; + + return S_OK; +} + +static HRESULT irp_invoke_real_write(struct irp *irp) +{ + uint32_t nwrit; + BOOL ok; + + log_assert(irp != NULL); + + ok = real_WriteFile( + irp->fd, + &irp->write.bytes[irp->write.pos], + irp->write.nbytes - irp->write.pos, + &nwrit, + irp->ovl); + + if (!ok) { + return hr_from_win32(); + } + + irp->write.pos += nwrit; + + return S_OK; +} + +static HRESULT irp_invoke_real_seek(struct irp *irp) +{ + int32_t hi; + int32_t lo; + HRESULT hr; + + log_assert(irp != NULL); + + hi = (uint32_t) (irp->seek_offset >> 32); + lo = (uint32_t) (irp->seek_offset ); + + lo = real_SetFilePointer(irp->fd, (int32_t) lo, hi == 0 ? NULL : &hi, + irp->seek_origin); + + if (lo == INVALID_SET_FILE_POINTER) { + hr = hr_from_win32(); + + if (FAILED(hr)) { + return hr; + } + } + + irp->seek_pos = (((uint64_t) hi) << 32) | ((uint32_t) lo); + + return S_OK; +} + +static HRESULT irp_invoke_real_fsync(struct irp *irp) +{ + BOOL ok; + + log_assert(irp != NULL); + + ok = real_FlushFileBuffers(irp->fd); + + if (!ok) { + return hr_from_win32(); + } + + return S_OK; +} + +static HRESULT irp_invoke_real_ioctl(struct irp *irp) +{ + uint32_t nread; + BOOL ok; + + log_assert(irp != NULL); + + /* ioctl in/out params tend to be structs, so it probably does not make + sense to concatenate a synthetic result with a pass-through result in + the same way as one might do with read/write. */ + + log_assert(irp->write.pos == 0); + log_assert(irp->read.pos == 0); + + ok = real_DeviceIoControl( + irp->fd, + irp->ioctl, + (void *) irp->write.bytes, // Cast off const + irp->write.nbytes, + irp->read.bytes, + irp->read.nbytes, + &nread, + irp->ovl); + + if (!ok) { + return hr_from_win32(); + } + + irp->read.pos = nread; + + return S_OK; +} + +static HANDLE STDCALL my_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + wchar_t *wfilename; + HANDLE fd; + + if (lpFileName == NULL) { + log_warning("%s: lpFileName == NULL", __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return INVALID_HANDLE_VALUE; + } + + wfilename = str_widen(lpFileName); + fd = my_CreateFileW( + wfilename, + dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, + dwCreationDisposition, dwFlagsAndAttributes, + hTemplateFile); + free(wfilename); + + return fd; +} + +static HANDLE STDCALL my_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + struct irp irp; + HRESULT hr; + + if (lpFileName == NULL) { + log_warning("%s: lpFileName == NULL", __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return INVALID_HANDLE_VALUE; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_OPEN; + irp.fd = INVALID_HANDLE_VALUE; + irp.open_filename = lpFileName; + irp.open_access = dwDesiredAccess; + irp.open_share = dwShareMode; + irp.open_sa = lpSecurityAttributes; + irp.open_creation = dwCreationDisposition; + irp.open_flags = dwFlagsAndAttributes; + irp.open_tmpl = hTemplateFile; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + return hr_propagate_win32(hr, INVALID_HANDLE_VALUE); + } + + SetLastError(ERROR_SUCCESS); + + return irp.fd; +} + +static BOOL STDCALL my_CloseHandle(HANDLE hFile) +{ + struct irp irp; + HRESULT hr; + + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { + log_warning("%s: Invalid file descriptor %p", __func__, hFile); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_CLOSE; + irp.fd = hFile; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + return hr_propagate_win32(hr, FALSE); + } + + SetLastError(ERROR_SUCCESS); + + return TRUE; +} + +static BOOL STDCALL my_ReadFile( + HANDLE hFile, + void *lpBuffer, + uint32_t nNumberOfBytesToRead, + uint32_t *lpNumberOfBytesRead, + OVERLAPPED *lpOverlapped) +{ + struct irp irp; + HRESULT hr; + + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { + log_warning("%s: Invalid file descriptor %p", __func__, hFile); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + if (lpBuffer == NULL) { + log_warning("%s: lpBuffer == NULL", __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + if (lpOverlapped == NULL) { + if (lpNumberOfBytesRead == NULL) { + log_warning( "%s: lpNumberOfBytesRead must be supplied in " + "synchronous mode", + __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + *lpNumberOfBytesRead = 0; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_READ; + irp.fd = hFile; + irp.ovl = lpOverlapped; + irp.read.bytes = lpBuffer; + irp.read.nbytes = nNumberOfBytesToRead; + irp.read.pos = 0; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + return hr_propagate_win32(hr, FALSE); + } + + log_assert(irp.read.pos <= irp.read.nbytes); + + return iohook_overlapped_result( + lpNumberOfBytesRead, + lpOverlapped, + irp.read.pos); +} + +static BOOL STDCALL my_WriteFile( + HANDLE hFile, + const void *lpBuffer, + uint32_t nNumberOfBytesToWrite, + uint32_t *lpNumberOfBytesWritten, + OVERLAPPED *lpOverlapped) +{ + struct irp irp; + HRESULT hr; + + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { + /* Don't log this because iidx14 calls WriteFile with a NULL handle */ + // log_warning("%s: Invalid file descriptor %p", __func__, hFile); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + if (lpBuffer == NULL) { + log_warning("%s: lpBuffer == NULL", __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + if (lpOverlapped == NULL) { + if (lpNumberOfBytesWritten == NULL) { + log_warning( "%s: lpNumberOfBytesWritten must be supplied in " + "synchronous mode", + __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + *lpNumberOfBytesWritten = 0; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_WRITE; + irp.fd = hFile; + irp.ovl = lpOverlapped; + irp.write.bytes = lpBuffer; + irp.write.nbytes = nNumberOfBytesToWrite; + irp.write.pos = 0; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + return hr_propagate_win32(hr, FALSE); + } + + log_assert(irp.write.pos <= irp.write.nbytes); + + return iohook_overlapped_result( + lpNumberOfBytesWritten, + lpOverlapped, + irp.write.pos); +} + +static DWORD STDCALL my_SetFilePointer( + HANDLE hFile, + int32_t lDistanceToMove, + int32_t *lpDistanceToMoveHigh, + uint32_t dwMoveMethod) +{ + struct irp irp; + HRESULT hr; + + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { + log_warning("%s: Invalid file descriptor %p", __func__, hFile); + SetLastError(ERROR_INVALID_PARAMETER); + + return INVALID_SET_FILE_POINTER; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_SEEK; + irp.fd = hFile; + irp.seek_origin = dwMoveMethod; + + /* This is a clumsy API. In 32-bit mode lDistanceToMove is a signed 32-bit + int, but in 64-bit mode it is a 32-bit UNsigned int. Care must be taken + with sign-extension vs zero-extension here. */ + + if (lpDistanceToMoveHigh != NULL) { + irp.seek_offset = ((( int64_t) *lpDistanceToMoveHigh) << 32) + | ((uint64_t) lDistanceToMove); + } else { + irp.seek_offset = (int64_t) lDistanceToMove; + } + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + return hr_propagate_win32(hr, INVALID_SET_FILE_POINTER); + } + + SetLastError(ERROR_SUCCESS); + + if (lpDistanceToMoveHigh != NULL) { + *lpDistanceToMoveHigh = irp.seek_pos >> 32; + } + + return (DWORD) irp.seek_pos; +} + +static BOOL STDCALL my_FlushFileBuffers(HANDLE hFile) +{ + struct irp irp; + HRESULT hr; + + /* Some of the old games using acio (e.g. DistorteD) are calling + FlushFileBuffers. If that call is not hooked, the game will write a bunch + of 0 data to the device and stop doing any read/write calls after that */ + + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { + log_warning("%s: Invalid file descriptor %p", __func__, hFile); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_FSYNC; + irp.fd = hFile; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + return hr_propagate_win32(hr, FALSE); + } + + SetLastError(ERROR_SUCCESS); + + return TRUE; +} + +static BOOL STDCALL my_DeviceIoControl( + HANDLE hFile, + uint32_t dwIoControlCode, + void *lpInBuffer, + uint32_t nInBufferSize, + void *lpOutBuffer, + uint32_t nOutBufferSize, + uint32_t *lpBytesReturned, + OVERLAPPED *lpOverlapped) +{ + struct irp irp; + HRESULT hr; + + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { + log_warning("%s: Invalid file descriptor %p", __func__, hFile); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + if (lpOverlapped == NULL) { + if (lpBytesReturned == NULL) { + log_warning( + "%s: lpBytesReturned must be supplied in synchronous mode", + __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + *lpBytesReturned = 0; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = hFile; + irp.ovl = lpOverlapped; + irp.ioctl = dwIoControlCode; + + if (lpInBuffer != NULL) { + irp.write.bytes = lpInBuffer; + irp.write.nbytes = nInBufferSize; + } + + if (lpOutBuffer != NULL) { + irp.read.bytes = lpOutBuffer; + irp.read.nbytes = nOutBufferSize; + } + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + return hr_propagate_win32(hr, FALSE); + } + + log_assert(irp.write.pos <= irp.write.nbytes); + log_assert(irp.read.pos <= irp.read.nbytes); + + return iohook_overlapped_result( + lpBytesReturned, + lpOverlapped, + irp.read.pos); +} + diff --git a/src/main/hook/iohook.h b/src/main/hook/iohook.h new file mode 100644 index 0000000..a0ba10b --- /dev/null +++ b/src/main/hook/iohook.h @@ -0,0 +1,47 @@ +#ifndef HOOK_IOHOOK_H +#define HOOK_IOHOOK_H + +#include + +#include +#include + +#include "util/iobuf.h" + +enum irp_op { + IRP_OP_OPEN, + IRP_OP_CLOSE, + IRP_OP_READ, + IRP_OP_WRITE, + IRP_OP_IOCTL, + IRP_OP_FSYNC, + IRP_OP_SEEK, +}; + +struct irp { + enum irp_op op; + size_t next_handler; + HANDLE fd; + OVERLAPPED *ovl; + struct const_iobuf write; + struct iobuf read; + uint32_t ioctl; + const wchar_t *open_filename; + uint32_t open_access; + uint32_t open_share; + SECURITY_ATTRIBUTES *open_sa; + uint32_t open_creation; + uint32_t open_flags; + HANDLE *open_tmpl; + uint32_t seek_origin; + int64_t seek_offset; + uint64_t seek_pos; +}; + +typedef HRESULT (*irp_handler_t)(struct irp *irp); + +void iohook_init(const irp_handler_t *handlers, size_t nhandlers); +HANDLE iohook_open_dummy_fd(void); +HRESULT irp_invoke_next(struct irp *irp); + +#endif diff --git a/src/main/hook/pe.c b/src/main/hook/pe.c new file mode 100644 index 0000000..f20ca74 --- /dev/null +++ b/src/main/hook/pe.c @@ -0,0 +1,286 @@ +#include + +#include +#include +#include + +#include "hook/pe.h" + +#include "util/log.h" + +typedef BOOL (WINAPI *dll_main_t)(HMODULE, uint32_t, void *); + +static const IMAGE_NT_HEADERS *pe_get_nt_header(HMODULE pe); +static uint32_t pe_get_virtual_size(const IMAGE_SECTION_HEADER *sh, + int nsections); +static void *pe_offset(void *ptr, size_t off); +static const void *pe_offsetc(const void *ptr, size_t off); + +static void *pe_offset(void *ptr, size_t off) +{ + uint8_t *base; + + if (off == 0) { + return NULL; + } + + base = ptr; + + return base + off; +} + +static const void *pe_offsetc(const void *ptr, size_t off) +{ + const uint8_t *base; + + if (off == 0) { + return NULL; + } + + base = ptr; + + return base + off; +} + +static uint32_t pe_get_virtual_size(const IMAGE_SECTION_HEADER *sh, + int nsections) +{ + uint32_t sec_end; + uint32_t size; + int i; + + size = 0; + + for (i = 0 ; i < nsections ; i++) { + sec_end = sh[i].VirtualAddress + sh[i].Misc.VirtualSize; + + if (size < sec_end) { + size = sec_end; + } + } + + return size; +} + +static const IMAGE_NT_HEADERS *pe_get_nt_header(HMODULE pe) +{ + const IMAGE_DOS_HEADER *dh; + const IMAGE_NT_HEADERS *nth; + + dh = (IMAGE_DOS_HEADER *) pe; + nth = pe_offsetc(pe, dh->e_lfanew); + + return nth; +} + +const pe_iid_t *pe_iid_get_first(HMODULE pe) +{ + const IMAGE_NT_HEADERS *nth; + const IMAGE_IMPORT_DESCRIPTOR *iid; + + nth = pe_get_nt_header(pe); + iid = pe_offsetc(pe, nth->OptionalHeader + .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + + if (iid == NULL || iid->Name == 0) { + return NULL; + } + + return iid; +} + +const char *pe_iid_get_name(HMODULE pe, const pe_iid_t *iid) +{ + return pe_offsetc(pe, iid->Name); +} + +const pe_iid_t *pe_iid_get_next(HMODULE pe, const pe_iid_t *iid) +{ + const IMAGE_IMPORT_DESCRIPTOR *iid_next; + + iid_next = iid + 1; + + if (iid_next->Name != 0) { + return iid_next; + } else { + return NULL; + } +} + +bool pe_iid_get_iat_entry(HMODULE pe, const pe_iid_t *iid, size_t n, + struct pe_iat_entry *entry) +{ + const IMAGE_IMPORT_BY_NAME *import; + uintptr_t *import_rvas; + void **pointers; + + if (iid->OriginalFirstThunk == 0) { + log_warning("OriginalFirstThunk == 0"); + } + + import_rvas = pe_offset(pe, iid->OriginalFirstThunk); + + if (import_rvas[n] == 0) { + /* End of imports */ + entry->name = NULL; + entry->ordinal = 0; + entry->ppointer = NULL; + + return false; + } else if (import_rvas[n] & INTPTR_MIN) { + /* Ordinal import */ + entry->name = NULL; + entry->ordinal = (uint16_t) import_rvas[n]; + } else { + /* Named import */ + import = pe_offsetc(pe, import_rvas[n]); + entry->name = (const char *) import->Name; /* Not an RVA */ + entry->ordinal = 0; + } + + pointers = pe_offset(pe, iid->FirstThunk); + entry->ppointer = &pointers[n]; + + return true; +} + +void pe_patch_pointer(void **ppointer, void *new_value) +{ + DWORD old_protect; + + VirtualProtect(ppointer, sizeof(void*), PAGE_EXECUTE_READWRITE, &old_protect); + *ppointer = new_value; + VirtualProtect(ppointer, sizeof(void*), old_protect, &old_protect); +} + +HMODULE pe_explode(const uint8_t *bytes, uint32_t nbytes) +{ + HMODULE base; + const IMAGE_DOS_HEADER *dh; + const IMAGE_NT_HEADERS *nth; + const IMAGE_SECTION_HEADER *sh; + uint32_t virtual_size; + uint32_t vflags; + int i; + + dh = (IMAGE_DOS_HEADER *) bytes; + nth = pe_offsetc(bytes, dh->e_lfanew); + sh = pe_offsetc(bytes, dh->e_lfanew + sizeof(*nth)); + + virtual_size = pe_get_virtual_size(sh, nth->FileHeader.NumberOfSections); + base = (HMODULE) VirtualAlloc((void *) nth->OptionalHeader.ImageBase, + virtual_size, MEM_RESERVE, PAGE_NOACCESS); + + if (base == NULL) { + /* Try again, allowing any base address */ + base = (HMODULE) VirtualAlloc(NULL, virtual_size, MEM_RESERVE, + PAGE_NOACCESS); + + if (base == NULL) { + /* Aargh */ + log_fatal("Failed to VirtualAlloc %#x bytes of address space", + virtual_size); + } + } + + log_misc("Exploding PE, base %p actual %p", + (void *) nth->OptionalHeader.ImageBase, + base); + + /* Commit header region */ + VirtualAlloc((void *) base, nth->OptionalHeader.SizeOfHeaders, MEM_COMMIT, + PAGE_READWRITE); + + memcpy(base, dh, sizeof(*dh)); + memcpy(pe_offset(base, dh->e_lfanew), nth, sizeof(*nth)); + memcpy(pe_offset(base, dh->e_lfanew + sizeof(*nth)), sh, + sizeof(*sh) * nth->FileHeader.NumberOfSections); + + for (i = 0 ; i < nth->FileHeader.NumberOfSections ; i++) { + vflags = sh[i].Characteristics & 0x20000000 + ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; + + VirtualAlloc( + pe_offset(base, sh[i].VirtualAddress), + sh[i].Misc.VirtualSize, + MEM_COMMIT, + vflags); + + memcpy( pe_offset(base, sh[i].VirtualAddress), + pe_offsetc(bytes, sh[i].PointerToRawData), + sh[i].SizeOfRawData); + } + + return base; +} + +void pe_relocate(HMODULE pe) +{ + const IMAGE_NT_HEADERS *nth; + const IMAGE_DATA_DIRECTORY *dde; + const IMAGE_BASE_RELOCATION *chunk; + intptr_t delta_va; + const uint16_t *reloc; + uintptr_t *addr_ptr; + + nth = pe_get_nt_header(pe); + delta_va = (intptr_t) pe - nth->OptionalHeader.ImageBase; + dde = nth->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC; + + for (chunk = pe_offsetc(pe, dde->VirtualAddress) + ; (void *) chunk < pe_offsetc(pe, dde->VirtualAddress + dde->Size) + ; chunk = pe_offsetc(chunk, chunk->SizeOfBlock)) { + for (reloc = (uint16_t *) (chunk + 1) + ; (void *) reloc < pe_offsetc(chunk, chunk->SizeOfBlock) + ; reloc++) { + if (*reloc >> 12 == IMAGE_REL_BASED_HIGHLOW) { + addr_ptr = pe_offset( + pe, + chunk->VirtualAddress + (*reloc & 0x0FFF)); + *addr_ptr += delta_va; + } + } + } +} + +void *pe_get_export(HMODULE pe, const char *name, uint16_t ord) +{ + const IMAGE_NT_HEADERS *nth; + const IMAGE_EXPORT_DIRECTORY *ied; + const uint32_t *name_rvas; + const uint32_t *target_rvas; + DWORD i; + + nth = pe_get_nt_header(pe); + ied = pe_offsetc(pe, nth->OptionalHeader + .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + + name_rvas = pe_offsetc(pe, ied->AddressOfNames); + target_rvas = pe_offsetc(pe, ied->AddressOfFunctions); + + if (name != NULL) { + for (i = 0 ; i < ied->NumberOfNames ; i++) { + if (name_rvas[i] != 0 + && strcmp(pe_offsetc(pe, name_rvas[i]), name) == 0) { + return pe_offset(pe, target_rvas[i]); + } + } + + return NULL; + } else if (ord - ied->Base < ied->NumberOfFunctions) { + return pe_offset(pe, target_rvas[ord - ied->Base]); + } else { + return NULL; + } +} + +BOOL pe_call_dll_main(HMODULE pe, uint32_t reason, void *ctx) +{ + const IMAGE_NT_HEADERS *nth; + dll_main_t dll_main; + + nth = pe_get_nt_header(pe); + dll_main = pe_offset(pe, nth->OptionalHeader.AddressOfEntryPoint); + + return dll_main(pe, reason, ctx); +} diff --git a/src/main/hook/pe.h b/src/main/hook/pe.h new file mode 100644 index 0000000..45eb080 --- /dev/null +++ b/src/main/hook/pe.h @@ -0,0 +1,30 @@ +#ifndef HOOK_PE_H +#define HOOK_PE_H + +#include + +#include +#include + +typedef IMAGE_IMPORT_DESCRIPTOR pe_iid_t; + +struct pe_iat_entry { + const char *name; + uint16_t ordinal; + void **ppointer; +}; + +const pe_iid_t *pe_iid_get_first(HMODULE pe); +const char *pe_iid_get_name(HMODULE pe, const pe_iid_t *iid); +const pe_iid_t *pe_iid_get_next(HMODULE pe, const pe_iid_t *iid); +bool pe_iid_get_iat_entry(HMODULE pe, const pe_iid_t *iid, size_t n, + struct pe_iat_entry *entry); +void *pe_get_export(HMODULE pe, const char *name, uint16_t ord); +BOOL pe_call_dll_main(HMODULE pe, uint32_t reason, void *ctx); + +void pe_patch_pointer(void **ppointer, void *new_value); + +HMODULE pe_explode(const uint8_t *bytes, uint32_t nbytes); +void pe_relocate(HMODULE pe); + +#endif diff --git a/src/main/hook/peb.c b/src/main/hook/peb.c new file mode 100644 index 0000000..1f18b7b --- /dev/null +++ b/src/main/hook/peb.c @@ -0,0 +1,68 @@ +#include +#include + +#include "hook/peb.h" + +#include "util/defs.h" +#include "util/str.h" + +static const PEB *peb_get(void) +{ +#ifdef __amd64 + return (const PEB *) __readgsqword(0x60); +#else + return (const PEB *) __readfsdword(0x30); +#endif + +} + +const peb_dll_t *peb_dll_get_first(void) +{ + const PEB *peb; + const LIST_ENTRY *node; + + peb = peb_get(); + node = peb->Ldr->InMemoryOrderModuleList.Flink; + + return containerof(node, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); +} + +const peb_dll_t *peb_dll_get_next(const peb_dll_t *dll) +{ + const PEB *peb; + const LIST_ENTRY *node; + + peb = peb_get(); + node = dll->InMemoryOrderLinks.Flink; + + if (node == peb->Ldr->InMemoryOrderModuleList.Flink) { + return NULL; + } + + return containerof(node, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); +} + +HMODULE peb_dll_get_base(const peb_dll_t *dll) +{ + return dll->DllBase; +} + +char *peb_dll_dup_name(const peb_dll_t *dll) +{ + const UNICODE_STRING *wstr; + char *name; + size_t i; + + wstr = &dll->FullDllName; + + for (i = wstr->Length / 2 - 1 ; i > 0 ; i--) { + if (wstr->Buffer[i] == L'\\') { + wstr_narrow(&wstr->Buffer[i + 1], &name); + + return name; + } + } + + return NULL; +} + diff --git a/src/main/hook/peb.h b/src/main/hook/peb.h new file mode 100644 index 0000000..f12b45d --- /dev/null +++ b/src/main/hook/peb.h @@ -0,0 +1,14 @@ +#ifndef HOOK_PEB_H +#define HOOK_PEB_H + +#include +#include + +typedef LDR_DATA_TABLE_ENTRY peb_dll_t; + +const peb_dll_t *peb_dll_get_first(void); +const peb_dll_t *peb_dll_get_next(const peb_dll_t *dll); +HMODULE peb_dll_get_base(const peb_dll_t *dll); +char *peb_dll_dup_name(const peb_dll_t *dll); + +#endif diff --git a/src/main/hook/table.c b/src/main/hook/table.c new file mode 100644 index 0000000..861723b --- /dev/null +++ b/src/main/hook/table.c @@ -0,0 +1,121 @@ +#include + +#include +#include +#include + +#include "hook/pe.h" +#include "hook/peb.h" +#include "hook/table.h" + +#include "util/mem.h" + +static void hook_table_apply_to_all( + const char *depname, + const struct hook_symbol *syms, + size_t nsyms); + +static void hook_table_apply_to_iid( + HMODULE target, + const pe_iid_t *iid, + const struct hook_symbol *syms, + size_t nsyms); + +static bool hook_table_match_proc( + const struct pe_iat_entry *iate, + const struct hook_symbol *sym); + +static void hook_table_apply_to_all( + const char *depname, + const struct hook_symbol *syms, + size_t nsyms) +{ + const peb_dll_t *dll; + HMODULE pe; + + for (dll = peb_dll_get_first() + ; dll != NULL + ; dll = peb_dll_get_next(dll)) { + pe = peb_dll_get_base(dll); + + if (pe == NULL) { + /* wtf? */ + continue; + } + + hook_table_apply(pe, depname, syms, nsyms); + } +} + +void hook_table_apply( + HMODULE target, + const char *depname, + const struct hook_symbol *syms, + size_t nsyms) +{ + const pe_iid_t *iid; + const char *iid_name; + + if (target == NULL) { + /* Call out, which will then call us back repeatedly. Awkward, but + viewed from the outside it's good for usability. */ + + hook_table_apply_to_all(depname, syms, nsyms); + } else { + for ( iid = pe_iid_get_first(target) ; + iid != NULL ; + iid = pe_iid_get_next(target, iid)) { + iid_name = pe_iid_get_name(target, iid); + + if (_stricmp(iid_name, depname) == 0) { + hook_table_apply_to_iid(target, iid, syms, nsyms); + } + } + } +} + +static void hook_table_apply_to_iid( + HMODULE target, + const pe_iid_t *iid, + const struct hook_symbol *syms, + size_t nsyms) +{ + struct pe_iat_entry iate; + size_t i; + size_t j; + const struct hook_symbol *sym; + + i = 0; + + while (pe_iid_get_iat_entry(target, iid, i++, &iate)) { + for (j = 0 ; j < nsyms ; j++) { + sym = &syms[j]; + + if (hook_table_match_proc(&iate, sym)) { + if (sym->link != NULL && *sym->link == NULL) { + *sym->link = *iate.ppointer; + } + + pe_patch_pointer(iate.ppointer, sym->patch); + } + } + } +} + +static bool hook_table_match_proc( + const struct pe_iat_entry *iate, + const struct hook_symbol *sym) +{ + if ( sym->name != NULL && + iate->name != NULL && + strcmp(sym->name, iate->name) == 0) { + return true; + } + + if (sym->ordinal != 0 && sym->ordinal == iate->ordinal) { + return true; + } + + return false; +} + diff --git a/src/main/hook/table.h b/src/main/hook/table.h new file mode 100644 index 0000000..90dbc4f --- /dev/null +++ b/src/main/hook/table.h @@ -0,0 +1,22 @@ +#ifndef HOOK_TABLE_H +#define HOOK_TABLE_H + +#include + +#include +#include + +struct hook_symbol { + const char *name; + uint16_t ordinal; + void *patch; + void **link; +}; + +void hook_table_apply( + HMODULE target, + const char *depname, + const struct hook_symbol *syms, + size_t nsyms); + +#endif diff --git a/src/main/hooklib/Module.mk b/src/main/hooklib/Module.mk new file mode 100644 index 0000000..1527390 --- /dev/null +++ b/src/main/hooklib/Module.mk @@ -0,0 +1,9 @@ +libs += hooklib + +src_hooklib := \ + acp.c \ + app.c \ + adapter.c \ + rs232.c \ + setupapi.c \ + diff --git a/src/main/hooklib/acp.c b/src/main/hooklib/acp.c new file mode 100644 index 0000000..c07a91a --- /dev/null +++ b/src/main/hooklib/acp.c @@ -0,0 +1,62 @@ +#include + +#include +#include + +#include +#include + +#include "hook/table.h" + +#include "hooklib/acp.h" + +#include "util/defs.h" +#include "util/codepage.h" +#include "util/log.h" + +static NTSTATUS NTAPI my_RtlMultiByteToUnicodeN( + wchar_t *dest, unsigned long destsz, unsigned long *out_destsz, + const char *src, unsigned long srcsz); + +static const struct hook_symbol acp_hook_syms[] = { + { + .name = "RtlMultiByteToUnicodeN", + .patch = my_RtlMultiByteToUnicodeN, + }, +}; + +static NTSTATUS NTAPI my_RtlMultiByteToUnicodeN( + wchar_t *dest, unsigned long destsz, unsigned long *out_destsz, + const char *src, unsigned long srcsz) +{ + unsigned long result; + + result = MultiByteToWideChar(CP_SHIFT_JIS, 0, src, srcsz, dest, destsz); + + if (result == 0) { + switch (GetLastError()) { + case ERROR_INSUFFICIENT_BUFFER: + return STATUS_BUFFER_TOO_SMALL; + case ERROR_INVALID_FLAGS: + case ERROR_INVALID_PARAMETER: + return STATUS_INVALID_PARAMETER; + case ERROR_NO_UNICODE_TRANSLATION: + return STATUS_UNMAPPABLE_CHARACTER; + default: + return STATUS_UNSUCCESSFUL; + } + } + + if (out_destsz != NULL) { + *out_destsz = result * sizeof(wchar_t); + } + + return STATUS_SUCCESS; +} + +void acp_hook_init(void) +{ + hook_table_apply(NULL, "ntdll.dll", acp_hook_syms, lengthof(acp_hook_syms)); + log_info("ACP Hook enabled"); +} + diff --git a/src/main/hooklib/acp.h b/src/main/hooklib/acp.h new file mode 100644 index 0000000..b429996 --- /dev/null +++ b/src/main/hooklib/acp.h @@ -0,0 +1,6 @@ +#ifndef HOOKLIB_ACP_H +#define HOOKLIB_ACP_H + +void acp_hook_init(void); + +#endif diff --git a/src/main/hooklib/adapter.c b/src/main/hooklib/adapter.c new file mode 100644 index 0000000..4daf1fb --- /dev/null +++ b/src/main/hooklib/adapter.c @@ -0,0 +1,81 @@ +#include +#include +#include /* Required by mingw for some reason */ +#include + +#include "hook/table.h" + +#include "hooklib/adapter.h" + +#include "util/defs.h" +#include "util/codepage.h" + +static DWORD WINAPI my_GetAdaptersInfo( + PIP_ADAPTER_INFO adapter_info, + PULONG out_buf_len); + +static DWORD(WINAPI *real_GetAdaptersInfo)(PIP_ADAPTER_INFO adapter_info, PULONG out_buf_len); + +static const struct hook_symbol adapter_hook_syms[] = { + { + .name = "GetAdaptersInfo", + .patch = my_GetAdaptersInfo, + .link = (void *)&real_GetAdaptersInfo + }, +}; + +static DWORD WINAPI my_GetAdaptersInfo( + PIP_ADAPTER_INFO adapter_info, + PULONG out_buf_len) +{ + DWORD ret; + PMIB_IPFORWARDTABLE ip_fwd_table; + DWORD table_size; + DWORD best_adapter; + PIP_ADAPTER_INFO info; + + ret = real_GetAdaptersInfo(adapter_info, out_buf_len); + + if (ret != 0) { + return ret; + } + + ip_fwd_table = (MIB_IPFORWARDTABLE *)malloc(sizeof(MIB_IPFORWARDTABLE)); + table_size = 0; + + if (GetIpForwardTable(ip_fwd_table, &table_size, 1) == + ERROR_INSUFFICIENT_BUFFER) { + free(ip_fwd_table); + ip_fwd_table = (MIB_IPFORWARDTABLE *)malloc(table_size); + } + + if (GetIpForwardTable(ip_fwd_table, &table_size, 1) != NO_ERROR || + ip_fwd_table->dwNumEntries == 0) { + return ret; + } + + best_adapter = ip_fwd_table->table[0].dwForwardIfIndex; + free(ip_fwd_table); + + info = adapter_info; + + while (info) { + if (info->Index == best_adapter) { + memcpy(adapter_info, info, sizeof(*info)); + adapter_info->Next = 0; + return ret; + } + info = info->Next; + } + + return ret; +} + +void adapter_hook_init(void) +{ + hook_table_apply( + NULL, + "iphlpapi.dll", + adapter_hook_syms, + lengthof(adapter_hook_syms)); +} diff --git a/src/main/hooklib/adapter.h b/src/main/hooklib/adapter.h new file mode 100644 index 0000000..b6a83ce --- /dev/null +++ b/src/main/hooklib/adapter.h @@ -0,0 +1,6 @@ +#ifndef HOOKLIB_ADAPTER_H +#define HOOKLIB_ADAPTER_H + +void adapter_hook_init(void); + +#endif diff --git a/src/main/hooklib/app.c b/src/main/hooklib/app.c new file mode 100644 index 0000000..3a2a1a0 --- /dev/null +++ b/src/main/hooklib/app.c @@ -0,0 +1,79 @@ +#include + +#include "hook/table.h" + +#include "hooklib/app.h" + +#include "imports/avs.h" +#include "imports/eapki.h" + +#include "util/log.h" +#include "util/str.h" + +static dll_entry_init_t hook_dll_entry_init; +static dll_entry_main_t hook_dll_entry_main; +static dll_entry_init_t next_dll_entry_init; +static dll_entry_main_t next_dll_entry_main; +static void *(STDCALL *next_GetProcAddress)(HMODULE mod, const char *sym); + +static void * STDCALL my_GetProcAddress(HMODULE mod, const char *sym); + +static const struct hook_symbol mod_hooks[] = { + { + .name = "GetProcAddress", + .patch = my_GetProcAddress, + .link = (void **) &next_GetProcAddress, + }, +}; + +static void * STDCALL my_GetProcAddress(HMODULE mod, const char *sym) +{ + void *next; + + next = next_GetProcAddress(mod, sym); + + if (next == NULL || ((intptr_t) sym) <= UINT16_MAX) { + return next; + } + + if (str_eq(sym, "dll_entry_init")) { + next_dll_entry_init = next; + + if (hook_dll_entry_init != NULL) { + return hook_dll_entry_init; + } else { + return next; + } + } else if (str_eq(sym, "dll_entry_main")) { + next_dll_entry_main = next; + + if (hook_dll_entry_main != NULL) { + return hook_dll_entry_main; + } else { + return next; + } + } else { + return next; + } +} + +void app_hook_init(dll_entry_init_t init, dll_entry_main_t main_) +{ + hook_dll_entry_init = init; + hook_dll_entry_main = main_; + hook_table_apply(NULL, "kernel32.dll", mod_hooks, lengthof(mod_hooks)); +} + +bool app_hook_invoke_init(char *sidcode, struct property_node *config) +{ + log_assert(sidcode != NULL); + log_assert(config != NULL); + + return next_dll_entry_init(sidcode, config); +} + +bool app_hook_invoke_main(void) +{ + return next_dll_entry_main(); +} + diff --git a/src/main/hooklib/app.h b/src/main/hooklib/app.h new file mode 100644 index 0000000..60c2e67 --- /dev/null +++ b/src/main/hooklib/app.h @@ -0,0 +1,13 @@ +#ifndef HOOKLIB_APP_H +#define HOOKLIB_APP_H + +#include + +#include "imports/avs.h" +#include "imports/eapki.h" + +void app_hook_init(dll_entry_init_t init, dll_entry_main_t main_); +bool app_hook_invoke_init(char *sidcode, struct property_node *config); +bool app_hook_invoke_main(void); + +#endif diff --git a/src/main/hooklib/rs232.c b/src/main/hooklib/rs232.c new file mode 100644 index 0000000..ed72280 --- /dev/null +++ b/src/main/hooklib/rs232.c @@ -0,0 +1,703 @@ +#include /* Usermode API */ + +#include /* Kernel-mode API for ioctls */ +#include +#include + +#include + +#include "hook/iohook.h" +#include "hook/table.h" + +#include "util/hr.h" +#include "util/log.h" + +/* RS232 API hooks */ + +static BOOL STDCALL my_ClearCommError( + HANDLE fd, + uint32_t *errors, + COMSTAT *status); + +static BOOL STDCALL my_EscapeCommFunction(HANDLE fd, uint32_t func); +static BOOL STDCALL my_GetCommState(HANDLE fd, DCB *dcb); +static BOOL STDCALL my_PurgeComm(HANDLE fd, uint32_t flags); +static BOOL STDCALL my_SetCommMask(HANDLE fd, uint32_t mask); +static BOOL STDCALL my_SetCommState(HANDLE fd, const DCB *dcb); +static BOOL STDCALL my_SetCommTimeouts(HANDLE fd, COMMTIMEOUTS *timeouts); +static BOOL STDCALL my_SetupComm(HANDLE fd, uint32_t in_q, uint32_t out_q); +static BOOL STDCALL my_SetCommBreak(HANDLE fd); +static BOOL STDCALL my_ClearCommBreak(HANDLE fd); + +static struct hook_symbol rs232_syms[] = { + { + .name = "ClearCommError", + .patch = my_ClearCommError, + }, + { + .name = "EscapeCommFunction", + .patch = my_EscapeCommFunction, + }, + { + .name = "GetCommState", + .patch = my_GetCommState, + }, + { + .name = "PurgeComm", + .patch = my_PurgeComm, + }, + { + .name = "SetCommMask", + .patch = my_SetCommMask, + }, + { + .name = "SetCommState", + .patch = my_SetCommState, + }, + { + .name = "SetCommTimeouts", + .patch = my_SetCommTimeouts, + }, + { + .name = "SetupComm", + .patch = my_SetupComm, + }, + { + .name = "SetCommBreak", + .patch = my_SetCommBreak, + }, + { + .name = "ClearCommBreak", + .patch = my_ClearCommBreak, + }, +}; + +void rs232_hook_init(void) +{ + hook_table_apply(NULL, "kernel32.dll", rs232_syms, lengthof(rs232_syms)); + log_info("IO Hook RS232 ioctl subsystem initialized"); +} + +static BOOL STDCALL my_ClearCommError( + HANDLE fd, + uint32_t *errors, + COMSTAT *status) +{ + struct irp irp; + SERIAL_STATUS llstatus; + HRESULT hr; + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_GET_COMMSTATUS; + irp.read.bytes = (uint8_t *) &llstatus; + irp.read.nbytes = sizeof(llstatus); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning( + "%s: IOCTL_SERIAL_GET_COMMSTATUS failed: %lx", + __func__, + hr); + + return hr_propagate_win32(hr, FALSE); + } + + /* Here we just translate between two structures that carry essentially the + same information, because Windows. */ + + if (errors != NULL) { + *errors = 0; + + if (llstatus.Errors & SERIAL_ERROR_QUEUEOVERRUN) { + *errors |= CE_OVERRUN; + } + + if (llstatus.Errors & SERIAL_ERROR_OVERRUN) { + *errors |= CE_RXOVER; + } + + if (llstatus.Errors & SERIAL_ERROR_BREAK) { + *errors |= CE_BREAK; + } + + if (llstatus.Errors & SERIAL_ERROR_PARITY) { + *errors |= CE_RXPARITY; + } + + if (llstatus.Errors & SERIAL_ERROR_FRAMING) { + *errors |= CE_FRAME; + } + } + + if (status != NULL) { + memset(status, 0, sizeof(*status)); + + if (llstatus.HoldReasons & SERIAL_TX_WAITING_FOR_CTS) { + status->fCtsHold = 1; + } + + if (llstatus.HoldReasons & SERIAL_TX_WAITING_FOR_DSR) { + status->fDsrHold = 1; + } + + if (llstatus.HoldReasons & SERIAL_TX_WAITING_FOR_DCD) { + status->fRlsdHold = 1; + } + + if (llstatus.HoldReasons & SERIAL_TX_WAITING_FOR_XON) { + status->fXoffHold = 1; + } + + if (llstatus.HoldReasons & SERIAL_TX_WAITING_ON_BREAK) { + /* hrm. No corresponding (documented field). */ + } + + if (llstatus.HoldReasons & SERIAL_TX_WAITING_XOFF_SENT) { + status->fXoffSent = 1; + } + + if (llstatus.EofReceived) { + status->fEof = 1; + } + + if (llstatus.WaitForImmediate) { + status->fTxim = 1; + } + + status->cbInQue = llstatus.AmountInInQueue; + status->cbOutQue = llstatus.AmountInOutQueue; + } + + return TRUE; +} + +static BOOL STDCALL my_EscapeCommFunction(HANDLE fd, uint32_t cmd) +{ + struct irp irp; + uint32_t ioctl; + HRESULT hr; + + switch (cmd) { + case CLRBREAK: ioctl = IOCTL_SERIAL_SET_BREAK_OFF; break; + case CLRDTR: ioctl = IOCTL_SERIAL_CLR_DTR; break; + case CLRRTS: ioctl = IOCTL_SERIAL_CLR_RTS; break; + case SETBREAK: ioctl = IOCTL_SERIAL_SET_BREAK_ON; break; + case SETDTR: ioctl = IOCTL_SERIAL_SET_RTS; break; + case SETRTS: ioctl = IOCTL_SERIAL_SET_RTS; break; + case SETXOFF: ioctl = IOCTL_SERIAL_SET_XOFF; break; + case SETXON: ioctl = IOCTL_SERIAL_SET_XON; break; + default: + log_warning("%s: Invalid comm function %08x", __func__, cmd); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = ioctl; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: ioctl failed: %lx", __func__, hr); + + return hr_propagate_win32(hr, FALSE); + } + + return TRUE; +} + +static BOOL STDCALL my_GetCommState(HANDLE fd, DCB *dcb) +{ + struct irp irp; + SERIAL_BAUD_RATE baud; + SERIAL_CHARS chars; + SERIAL_HANDFLOW handflow; + SERIAL_LINE_CONTROL line_control; + HRESULT hr; + + /* + * Validate params + */ + + if (dcb == NULL) { + log_warning("%s: DCB pointer is NULL", __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + if (dcb->DCBlength != sizeof(*dcb)) { + /* This struct has evolved in the past, but those were the Windows 95 + days. So we only support the latest size of this struct. */ + + log_warning( + "%s: dcb->DCBlength = %d, expected %d", + __func__, + (int) dcb->DCBlength, + (unsigned int) sizeof(*dcb)); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + /* + * Issue ioctls + */ + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_GET_BAUD_RATE; + irp.read.bytes = (uint8_t *) &baud; + irp.read.nbytes = sizeof(baud); + memset(&baud, 0, sizeof(baud)); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_GET_BAUD_RATE failed: %lx", __func__, hr); + + return hr_propagate_win32(hr, FALSE); + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_GET_HANDFLOW; + irp.read.bytes = (uint8_t *) &handflow; + irp.read.nbytes = sizeof(handflow); + memset(&handflow, 0, sizeof(handflow)); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_GET_HANDFLOW failed: %lx", __func__, hr); + + return hr_propagate_win32(hr, FALSE); + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_GET_LINE_CONTROL; + irp.read.bytes = (uint8_t *) &line_control; + irp.read.nbytes = sizeof(line_control); + memset(&line_control, 0, sizeof(line_control)); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning( + "%s: IOCTL_SERIAL_GET_LINE_CONTROL failed: %lx", + __func__, + hr); + + return hr_propagate_win32(hr, FALSE); + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_GET_CHARS; + irp.read.bytes = (uint8_t *) &chars; + irp.read.nbytes = sizeof(chars); + memset(&chars, 0, sizeof(chars)); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_GET_CHARS failed: %lx", __func__, hr); + + return hr_propagate_win32(hr, FALSE); + } + + /* + * Populate output struct + */ + + memset(dcb, 0, sizeof(*dcb)); + dcb->DCBlength = sizeof(*dcb); + dcb->fBinary = 1; + dcb->BaudRate = baud.BaudRate; + + /* Populate fParity somehow? */ + + if (handflow.ControlHandShake & SERIAL_CTS_HANDSHAKE) { + dcb->fOutxCtsFlow = 1; + } + + if (handflow.ControlHandShake & SERIAL_DSR_HANDSHAKE) { + dcb->fOutxDsrFlow = 1; + } + + if (handflow.ControlHandShake & SERIAL_DTR_CONTROL) { + dcb->fDtrControl = DTR_CONTROL_ENABLE; + } + + if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) { + dcb->fDtrControl = DTR_CONTROL_HANDSHAKE; + } + + if (handflow.ControlHandShake & SERIAL_DSR_SENSITIVITY) { + dcb->fDsrSensitivity = 1; + } + + if (handflow.ControlHandShake & SERIAL_XOFF_CONTINUE) { + dcb->fTXContinueOnXoff = 1; + } + + if (handflow.ControlHandShake & SERIAL_RTS_CONTROL) { + dcb->fRtsControl = RTS_CONTROL_ENABLE; + } + + if (handflow.ControlHandShake & SERIAL_RTS_HANDSHAKE) { + dcb->fRtsControl = RTS_CONTROL_HANDSHAKE; + } + + if (handflow.ControlHandShake & SERIAL_ERROR_ABORT) { + dcb->fAbortOnError = 1; + } + + if (handflow.ControlHandShake & SERIAL_ERROR_CHAR) { + dcb->fErrorChar = 1; + } + + if (handflow.ControlHandShake & SERIAL_NULL_STRIPPING) { + dcb->fNull = 1; + } + + dcb->XonLim = handflow.XonLimit; + dcb->XoffLim = handflow.XoffLimit; + dcb->ByteSize = line_control.WordLength; + dcb->Parity = line_control.Parity; + dcb->StopBits = line_control.StopBits; + dcb->XonChar = chars.XonChar; + dcb->XoffChar = chars.XoffChar; + dcb->ErrorChar = chars.ErrorChar; + dcb->EofChar = chars.EofChar; + dcb->EvtChar = chars.EventChar; + + SetLastError(ERROR_SUCCESS); + + return TRUE; +} + +static BOOL STDCALL my_PurgeComm(HANDLE fd, uint32_t flags) +{ + struct irp irp; + HRESULT hr; + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_PURGE; + irp.write.bytes = (uint8_t *) &flags; + irp.write.nbytes = sizeof(flags); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_PURGE failed: %lx", __func__, hr); + } + + return hr_propagate_win32(hr, SUCCEEDED(hr)); +} + + +static BOOL STDCALL my_SetCommMask(HANDLE fd, uint32_t mask) +{ + struct irp irp; + HRESULT hr; + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_SET_WAIT_MASK; + irp.write.bytes = (uint8_t *) &mask; + irp.write.nbytes = sizeof(mask); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_SET_WAIT_MASK failed: %lx", __func__, hr); + } + + return hr_propagate_win32(hr, SUCCEEDED(hr)); +} + +static BOOL STDCALL my_SetCommState(HANDLE fd, const DCB *dcb) +{ + struct irp irp; + SERIAL_BAUD_RATE baud; + SERIAL_CHARS chars; + SERIAL_HANDFLOW handflow; + SERIAL_LINE_CONTROL line_control; + HRESULT hr; + + if (dcb == NULL) { + log_warning("%s: DCB pointer is NULL", __func__); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + if (dcb->DCBlength != sizeof(*dcb)) { + /* This struct has evolved in the past, but those were the Windows 95 + days. So we only support the latest size of this struct. */ + + log_warning( + "%s: dcb->DCBlength = %d, expected %d", + __func__, + (int) dcb->DCBlength, + (unsigned int) sizeof(*dcb)); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + memset(&baud, 0, sizeof(baud)); + baud.BaudRate = dcb->BaudRate; + + memset(&handflow, 0, sizeof(handflow)); + + if (dcb->fOutxCtsFlow) { + handflow.ControlHandShake |= SERIAL_CTS_HANDSHAKE; + } + + if (dcb->fOutxDsrFlow) { + handflow.ControlHandShake |= SERIAL_DSR_HANDSHAKE; + } + + switch (dcb->fDtrControl) { + case DTR_CONTROL_DISABLE: + break; + + case DTR_CONTROL_ENABLE: + handflow.ControlHandShake |= SERIAL_DTR_CONTROL; + + break; + + case DTR_CONTROL_HANDSHAKE: + handflow.ControlHandShake |= SERIAL_DTR_HANDSHAKE; + + break; + + default: + log_warning("%s: dcb->fDtrControl value %d is invalid", + __func__, dcb->fDtrControl); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + if (dcb->fDsrSensitivity) { + handflow.ControlHandShake |= SERIAL_DSR_SENSITIVITY; + } + + if (dcb->fTXContinueOnXoff) { + handflow.ControlHandShake |= SERIAL_XOFF_CONTINUE; + } + + switch (dcb->fRtsControl) { + case RTS_CONTROL_DISABLE: + break; + + case RTS_CONTROL_ENABLE: + handflow.ControlHandShake |= SERIAL_RTS_CONTROL; + + break; + + case RTS_CONTROL_HANDSHAKE: + handflow.ControlHandShake |= SERIAL_RTS_HANDSHAKE; + + break; + + default: + log_warning( + "%s: dcb->fRtsControl value %d is invalid", + __func__, + dcb->fRtsControl); + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + memset(&line_control, 0, sizeof(line_control)); + line_control.WordLength = dcb->ByteSize; + line_control.Parity = dcb->Parity; + line_control.StopBits = dcb->StopBits; + + memset(&chars, 0, sizeof(chars)); + chars.XonChar = dcb->XonChar; + chars.XoffChar = dcb->XoffChar; + chars.ErrorChar = dcb->ErrorChar; + chars.EofChar = dcb->EofChar; + chars.EventChar = dcb->EvtChar; + + /* Parameters populated and validated, commit new settings */ + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_SET_BAUD_RATE; + irp.write.bytes = (uint8_t *) &baud; + irp.write.nbytes = sizeof(baud); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_SET_BAUD_RATE failed: %lx", __func__, hr); + + return hr_propagate_win32(hr, FALSE); + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_SET_HANDFLOW; + irp.write.bytes = (uint8_t *) &handflow; + irp.write.nbytes = sizeof(handflow); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_SET_HANDFLOW failed: %lx", __func__, hr); + + return hr_propagate_win32(hr, FALSE); + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_SET_LINE_CONTROL; + irp.write.bytes = (uint8_t *) &line_control; + irp.write.nbytes = sizeof(line_control); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning( + "%s: IOCTL_SERIAL_SET_LINE_CONTROL failed: %lx", + __func__, + hr); + + return hr_propagate_win32(hr, FALSE); + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_SET_CHARS; + irp.write.bytes = (uint8_t *) &chars; + irp.write.nbytes = sizeof(chars); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_SET_CHARS failed: %lx", __func__, hr); + + return hr_propagate_win32(hr, FALSE); + } + + SetLastError(ERROR_SUCCESS); + + return TRUE; +} + +static BOOL STDCALL my_SetCommTimeouts(HANDLE fd, COMMTIMEOUTS *src) +{ + struct irp irp; + SERIAL_TIMEOUTS dest; + HRESULT hr; + + dest.ReadIntervalTimeout = src->ReadIntervalTimeout; + dest.ReadTotalTimeoutMultiplier = src->ReadTotalTimeoutMultiplier; + dest.ReadTotalTimeoutConstant = src->ReadTotalTimeoutConstant; + dest.WriteTotalTimeoutMultiplier = src->WriteTotalTimeoutMultiplier; + dest.WriteTotalTimeoutConstant = src->WriteTotalTimeoutConstant; + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_SET_TIMEOUTS; + irp.write.bytes = (uint8_t *) &dest; + irp.write.nbytes = sizeof(dest); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_SET_TIMEOUTS failed: %lx", __func__, hr); + } + + return hr_propagate_win32(hr, SUCCEEDED(hr)); +} + +static BOOL STDCALL my_SetupComm(HANDLE fd, uint32_t in_q, uint32_t out_q) +{ + struct irp irp; + SERIAL_QUEUE_SIZE qs; + HRESULT hr; + + qs.InSize = in_q; + qs.OutSize = out_q; + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_SET_QUEUE_SIZE; + irp.write.bytes = (uint8_t *) &qs; + irp.write.nbytes = sizeof(qs); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_SET_QUEUE_SIZE failed: %lx",__func__, hr); + } + + return hr_propagate_win32(hr, SUCCEEDED(hr)); +} + +static BOOL STDCALL my_SetCommBreak(HANDLE fd) +{ + struct irp irp; + HRESULT hr; + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_SET_BREAK_ON; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_SET_BREAK_ON failed: %lx", __func__, hr); + } + + return hr_propagate_win32(hr, SUCCEEDED(hr)); +} + +static BOOL STDCALL my_ClearCommBreak(HANDLE fd) +{ + struct irp irp; + HRESULT hr; + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_SET_BREAK_OFF; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("%s: IOCTL_SERIAL_SET_BREAK_OFF failed: %lx", __func__, hr); + } + + return hr_propagate_win32(hr, SUCCEEDED(hr)); +} + diff --git a/src/main/hooklib/rs232.h b/src/main/hooklib/rs232.h new file mode 100644 index 0000000..f0beddf --- /dev/null +++ b/src/main/hooklib/rs232.h @@ -0,0 +1,6 @@ +#ifndef HOOKLIB_RS232_H +#define HOOKLIB_RS232_H + +void rs232_hook_init(void); + +#endif diff --git a/src/main/hooklib/setupapi.c b/src/main/hooklib/setupapi.c new file mode 100644 index 0000000..0eed34d --- /dev/null +++ b/src/main/hooklib/setupapi.c @@ -0,0 +1,330 @@ +#define LOG_MODULE "hook-setupapi" + +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/setupapi.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" + +/* my hooks */ + +static BOOL STDCALL my_SetupDiDestroyDeviceInfoList(HDEVINFO dev_info); + +static BOOL STDCALL my_SetupDiEnumDeviceInfo( + HDEVINFO dev_info, DWORD index, SP_DEVINFO_DATA *info_data); + +static BOOL STDCALL my_SetupDiEnumDeviceInterfaces( + HDEVINFO dev_info, SP_DEVINFO_DATA *info_data, const GUID *iface_guid, + DWORD index, SP_DEVICE_INTERFACE_DATA *ifd); + +static HDEVINFO STDCALL my_SetupDiGetClassDevsA( + const GUID *class_guid, const char *enumerator, HWND hwnd, + DWORD flags); + +static HDEVINFO STDCALL my_SetupDiGetClassDevsW( + const GUID *class_guid, PCWSTR enumerator, HWND hwnd, + DWORD flags); + +static BOOL STDCALL my_SetupDiGetDeviceInterfaceDetailA( + HDEVINFO dev_info, SP_DEVICE_INTERFACE_DATA *ifd, + SP_DEVICE_INTERFACE_DETAIL_DATA_A *detail, DWORD size, + DWORD *required_size, SP_DEVINFO_DATA *info_data); + +static BOOL STDCALL my_SetupDiGetDeviceInterfaceDetailW( + HDEVINFO dev_info, SP_DEVICE_INTERFACE_DATA *ifd, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail, DWORD size, + DWORD *required_size, SP_DEVINFO_DATA *info_data); + +static BOOL STDCALL my_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO dev_info, SP_DEVINFO_DATA *info_data, DWORD prop_id, + DWORD *prop_type, void *bytes, DWORD nbytes, DWORD *required_nbytes); + +/* real calls */ + +static BOOL (STDCALL *real_SetupDiDestroyDeviceInfoList)( + HDEVINFO dev_info); + +static BOOL (STDCALL *real_SetupDiEnumDeviceInfo)( + HDEVINFO dev_info, DWORD index, SP_DEVINFO_DATA *info_data); + +static BOOL (STDCALL *real_SetupDiEnumDeviceInterfaces)( + HDEVINFO dev_info, SP_DEVINFO_DATA *info_data, const GUID *iface_guid, + DWORD index, SP_DEVICE_INTERFACE_DATA *ifd); + +static HDEVINFO (STDCALL *real_SetupDiGetClassDevsA)( + const GUID *class_guid, const char *enumerator, HWND hwnd, + DWORD flags); + +static HDEVINFO (STDCALL *real_SetupDiGetClassDevsW)( + const GUID *class_guid, PCWSTR enumerator, HWND hwnd, + DWORD flags); + +static BOOL (STDCALL *real_SetupDiGetDeviceInterfaceDetailA)( + HDEVINFO dev_info, SP_DEVICE_INTERFACE_DATA *ifd, + SP_DEVICE_INTERFACE_DETAIL_DATA_A *detail, DWORD size, + DWORD *required_size, SP_DEVINFO_DATA *info_data); + +static BOOL (STDCALL *real_SetupDiGetDeviceInterfaceDetailW)( + HDEVINFO dev_info, SP_DEVICE_INTERFACE_DATA *ifd, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail, DWORD size, + DWORD *required_size, SP_DEVINFO_DATA *info_data); + +static BOOL (STDCALL *real_SetupDiGetDeviceRegistryPropertyA)( + HDEVINFO dev_info, SP_DEVINFO_DATA *info_data, DWORD prop_id, + DWORD *prop_type, void *bytes, DWORD nbytes, DWORD *required_nbytes); + +static const struct hook_symbol setupapi_hook_syms[] = { + { + .name = "SetupDiDestroyDeviceInfoList", + .patch = my_SetupDiDestroyDeviceInfoList, + .link = (void **) &real_SetupDiDestroyDeviceInfoList + }, + { + .name = "SetupDiEnumDeviceInfo", + .patch = my_SetupDiEnumDeviceInfo, + .link = (void **) &real_SetupDiEnumDeviceInfo + }, + { + .name = "SetupDiEnumDeviceInterfaces", + .patch = my_SetupDiEnumDeviceInterfaces, + .link = (void **) &real_SetupDiEnumDeviceInterfaces + }, + { + .name = "SetupDiGetClassDevsA", + .patch = my_SetupDiGetClassDevsA, + .link = (void **) &real_SetupDiGetClassDevsA + }, + { + .name = "SetupDiGetClassDevsW", + .patch = my_SetupDiGetClassDevsW, + .link = (void **) &real_SetupDiGetClassDevsW + }, + { + .name = "SetupDiGetDeviceInterfaceDetailA", + .patch = my_SetupDiGetDeviceInterfaceDetailA, + .link = (void **) &real_SetupDiGetDeviceInterfaceDetailA + }, + { + .name = "SetupDiGetDeviceInterfaceDetailW", + .patch = my_SetupDiGetDeviceInterfaceDetailW, + .link = (void **) &real_SetupDiGetDeviceInterfaceDetailW + }, + { + .name = "SetupDiGetDeviceRegistryPropertyA", + .patch = my_SetupDiGetDeviceRegistryPropertyA, + .link = (void **) &real_SetupDiGetDeviceRegistryPropertyA + }, +}; + +static int hook_setupapi_fake_handle; +static const struct hook_setupapi_data* hook_setupapi_data; + +static HDEVINFO STDCALL my_SetupDiGetClassDevsA( + const GUID *class_guid, const char *enumerator, HWND hwnd, + DWORD flags) +{ + /* That's how iidx 9-13 detected the old C02 IO. That doesn't work + on iidx 14 anymore... */ + /* + if (class_guid != NULL + && memcmp(class_guid, &hook_setupapi_data->device_guid, + sizeof(hook_setupapi_data->device_guid)) == 0) { + */ + if ((class_guid != NULL + && !memcmp(class_guid, &hook_setupapi_data->device_guid, + sizeof(hook_setupapi_data->device_guid))) || + (enumerator != NULL && !strcmp(enumerator, "USB"))) { + SetLastError(ERROR_SUCCESS); + + log_misc("SetupDiGetClassDevsA: %s", hook_setupapi_data->device_path); + + return &hook_setupapi_fake_handle; + } else { + return real_SetupDiGetClassDevsA(class_guid, enumerator, hwnd, flags); + } +} + +static HDEVINFO STDCALL my_SetupDiGetClassDevsW( + const GUID *class_guid, PCWSTR enumerator, HWND hwnd, + DWORD flags) +{ + if (class_guid != NULL + && memcmp(class_guid, &hook_setupapi_data->device_guid, + sizeof(hook_setupapi_data->device_guid)) == 0) { + SetLastError(ERROR_SUCCESS); + + log_misc("SetupDiGetClassDevsW: %s", hook_setupapi_data->device_path); + + return &hook_setupapi_fake_handle; + } else { + return real_SetupDiGetClassDevsW(class_guid, enumerator, hwnd, flags); + } +} + +static BOOL STDCALL my_SetupDiEnumDeviceInfo( + HDEVINFO dev_info, DWORD index, SP_DEVINFO_DATA *info_data) +{ + if (dev_info == &hook_setupapi_fake_handle) { + if (index == 0) { + SetLastError(ERROR_SUCCESS); + + log_misc("SetupDiEnumDeviceInfo"); + + return TRUE; + } else { + SetLastError(ERROR_NO_MORE_ITEMS); + + return FALSE; + } + } else { + return real_SetupDiEnumDeviceInfo(dev_info, index, info_data); + } +} + +static BOOL STDCALL my_SetupDiEnumDeviceInterfaces( + HDEVINFO dev_info, SP_DEVINFO_DATA *info_data, const GUID *iface_guid, + DWORD index, SP_DEVICE_INTERFACE_DATA *ifd) +{ + if (dev_info == &hook_setupapi_fake_handle) { + SetLastError(ERROR_SUCCESS); + + log_misc("SetupDiEnumDeviceInterfaces"); + + return index == 0; + } else { + return real_SetupDiEnumDeviceInterfaces(dev_info, info_data, + iface_guid, index, ifd); + } +} + +static BOOL STDCALL my_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO dev_info, SP_DEVINFO_DATA *info_data, DWORD prop_id, + DWORD *prop_type, void *bytes, DWORD nbytes, DWORD *required_nbytes) +{ + if (dev_info == &hook_setupapi_fake_handle) { + if (prop_id != SPDRP_DEVICEDESC) { + SetLastError(ERROR_BAD_ARGUMENTS); + + return FALSE; + } + + if (nbytes < MAX_PATH) { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + *required_nbytes = MAX_PATH; + + return FALSE; + } else { + SetLastError(ERROR_SUCCESS); + + *prop_type = REG_SZ; + + if (hook_setupapi_data->device_desc) { + log_misc("SetupDiGetDeviceRegistryPropertyA: %s", + hook_setupapi_data->device_desc); + str_cpy(bytes, nbytes, hook_setupapi_data->device_desc); + } + + return TRUE; + } + + } else { + return real_SetupDiGetDeviceRegistryPropertyA( + dev_info, info_data, prop_id, prop_type, bytes, nbytes, + required_nbytes); + } +} + +static BOOL STDCALL my_SetupDiGetDeviceInterfaceDetailA( + HDEVINFO dev_info, SP_DEVICE_INTERFACE_DATA *ifd, + SP_DEVICE_INTERFACE_DETAIL_DATA_A *detail, DWORD size, + DWORD *required_size, SP_DEVINFO_DATA *info_data) +{ + if (dev_info == &hook_setupapi_fake_handle) { + if (size == 0) { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + *required_size = sizeof(DWORD) + sizeof(char) * MAX_PATH; + + return FALSE; + } else { + SetLastError(ERROR_SUCCESS); + + log_misc("SetupDiGetDeviceInterfaceDetailA: %s", + hook_setupapi_data->device_path); + + detail->cbSize = strlen(hook_setupapi_data->device_path); + memcpy(detail->DevicePath, hook_setupapi_data->device_path, + detail->cbSize); + detail->DevicePath[detail->cbSize] = '\0'; + + return TRUE; + } + } else { + return real_SetupDiGetDeviceInterfaceDetailA(dev_info, ifd, detail, + size, required_size, info_data); + } +} + +static BOOL STDCALL my_SetupDiGetDeviceInterfaceDetailW( + HDEVINFO dev_info, SP_DEVICE_INTERFACE_DATA *ifd, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail, DWORD size, + DWORD *required_size, SP_DEVINFO_DATA *info_data) +{ + if (dev_info == &hook_setupapi_fake_handle) { + if (size == 0) { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + *required_size = sizeof(DWORD) + sizeof(wchar_t) * MAX_PATH; + + return FALSE; + } else { + SetLastError(ERROR_SUCCESS); + + log_misc("SetupDiGetDeviceInterfaceDetailW: %s", + hook_setupapi_data->device_path); + + wchar_t* wstr_path = str_widen(hook_setupapi_data->device_path); + + detail->cbSize = strlen(hook_setupapi_data->device_path); + memcpy(detail->DevicePath, wstr_path, (wcslen(wstr_path) + 1) * + sizeof(wchar_t)); + detail->DevicePath[detail->cbSize] = '\0'; + + free(wstr_path); + + return TRUE; + } + + } else { + return real_SetupDiGetDeviceInterfaceDetailW(dev_info, ifd, detail, + size, required_size, info_data); + } +} + +static BOOL STDCALL my_SetupDiDestroyDeviceInfoList(HDEVINFO dev_info) +{ + if (dev_info == &hook_setupapi_fake_handle) { + log_misc("SetupDiDestroyDeviceInfoList"); + + return TRUE; + } else { + return real_SetupDiDestroyDeviceInfoList(dev_info); + } +} + +void hook_setupapi_init(const struct hook_setupapi_data* data) +{ + hook_table_apply( + NULL, + "setupapi.dll", + setupapi_hook_syms, + lengthof(setupapi_hook_syms)); + + hook_setupapi_data = data; + log_info("Hooked setupapi for %s, %s", data->device_path, + data->device_desc); +} diff --git a/src/main/hooklib/setupapi.h b/src/main/hooklib/setupapi.h new file mode 100644 index 0000000..44b9fd9 --- /dev/null +++ b/src/main/hooklib/setupapi.h @@ -0,0 +1,15 @@ +#ifndef HOOKLIB_SETUPAPI_H +#define HOOKLIB_SETUPAPI_H + +#include +#include + +struct hook_setupapi_data { + GUID device_guid; + const char* device_desc; + const char* device_path; +}; + +void hook_setupapi_init(const struct hook_setupapi_data* data); + +#endif diff --git a/src/main/iidx-ezusb-exit-hook/Module.mk b/src/main/iidx-ezusb-exit-hook/Module.mk new file mode 100644 index 0000000..4050ebe --- /dev/null +++ b/src/main/iidx-ezusb-exit-hook/Module.mk @@ -0,0 +1,9 @@ +dlls += iidx-ezusb-exit-hook + +libs_iidx-ezusb-exit-hook := \ + ezusb-iidx \ + hook \ + util \ + +src_iidx-ezusb-exit-hook := \ + main.c \ diff --git a/src/main/iidx-ezusb-exit-hook/iidx-ezusb-exit-hook.def b/src/main/iidx-ezusb-exit-hook/iidx-ezusb-exit-hook.def new file mode 100644 index 0000000..53dc1a3 --- /dev/null +++ b/src/main/iidx-ezusb-exit-hook/iidx-ezusb-exit-hook.def @@ -0,0 +1,4 @@ +LIBRARY iidx-ezusb-exit-hook + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/iidx-ezusb-exit-hook/main.c b/src/main/iidx-ezusb-exit-hook/main.c new file mode 100644 index 0000000..a4b7c3e --- /dev/null +++ b/src/main/iidx-ezusb-exit-hook/main.c @@ -0,0 +1,158 @@ +#include +#include + +#include +#include + +#include "ezusb/ezusbsys2.h" + +#include "ezusb-iidx/msg.h" +#include "ezusb-iidx/seg16-cmd.h" + +#include "hook/table.h" + +#include "util/defs.h" + +static BOOL STDCALL my_DeviceIoControl( + HANDLE fd, uint32_t code, void *in_bytes, uint32_t in_nbytes, + void *out_bytes, uint32_t out_nbytes, uint32_t *out_returned, + OVERLAPPED *ovl); + +static BOOL (STDCALL *real_DeviceIoControl)( + HANDLE fd, uint32_t code, void *in_bytes, uint32_t in_nbytes, + void *out_bytes, uint32_t out_nbytes, uint32_t *out_returned, + OVERLAPPED *ovl); + +static struct hook_symbol iidxfx_hook_syms[] = { + { + .name = "DeviceIoControl", + .patch = my_DeviceIoControl, + .link = (void *) &real_DeviceIoControl + }, +}; + +static bool interrupt_write(HANDLE handle, + const struct ezusb_iidx_msg_interrupt_write_packet* packet) +{ + BULK_TRANSFER_CONTROL transfer; + uint32_t outpkt; + + transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_INTERRUPT_OUT; + + return real_DeviceIoControl( + handle, IOCTL_EZUSB_BULK_WRITE, &transfer, + sizeof(transfer), (void*) packet, + sizeof(struct ezusb_iidx_msg_interrupt_write_packet), &outpkt, NULL); +} + +static bool bulk_write(HANDLE handle, + const struct ezusb_iidx_msg_bulk_packet* packet) +{ + BULK_TRANSFER_CONTROL transfer; + uint32_t outpkt; + + transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_BULK_OUT; + + return real_DeviceIoControl( + handle, IOCTL_EZUSB_BULK_WRITE, &transfer, + sizeof(transfer), (void*) packet, + sizeof(struct ezusb_iidx_msg_bulk_packet), &outpkt, NULL); +} + +static void turn_off_lights(HANDLE fd) +{ + struct ezusb_iidx_msg_interrupt_write_packet data; + struct ezusb_iidx_msg_bulk_packet bulk; + + data.deck_lights = 0; + data.node = EZUSB_IIDX_MSG_NODE_16SEG; + data.cmd = EZUSB_IIDX_16SEG_CMD_WRITE; + data.cmd_detail[0] = 0; + data.cmd_detail[1] = 1; + data.top_lamps = 0; + data.top_neons = 0; + data.fpga_run = 1; + + OutputDebugString("Switching off lights...\n"); + + /* avoid crashing the IO because of short request bursts */ + Sleep(10); + + if (!interrupt_write(fd, &data)) { + OutputDebugString("Switching off lights failed\n"); + } + + bulk.node = EZUSB_IIDX_MSG_NODE_16SEG; + bulk.page = 0; + memset(bulk.payload, ' ', sizeof(bulk.payload)); + + /* avoid crashing the IO because of short request bursts */ + Sleep(10); + + if (!bulk_write(fd, &bulk)) { + OutputDebugString("Switching off lights (16seg) failed\n"); + } +} + +static BOOL STDCALL my_DeviceIoControl( + HANDLE fd, uint32_t code, void *in_bytes, uint32_t in_nbytes, + void *out_bytes, uint32_t out_nbytes, uint32_t *out_returned, + OVERLAPPED *ovl) +{ + BOOL result; + + /* Call real first to get the input data */ + result = real_DeviceIoControl(fd, code, in_bytes, in_nbytes, out_bytes, + out_nbytes, out_returned, ovl); + + if (code == IOCTL_EZUSB_BULK_READ) { + const BULK_TRANSFER_CONTROL *ctl = in_bytes; + + /* Pipe Interrupt In */ + if (ctl->pipeNum == 1) { + struct ezusb_iidx_msg_interrupt_read_packet* msg_resp = + (struct ezusb_iidx_msg_interrupt_read_packet*) out_bytes; + + uint32_t pad = ~msg_resp->inverted_pad; + + /* First (invalid) data we get from a real C02 IO board. Must be + filtered to avoid instant exit trigger on boot */ + if (pad != 0xFFFFFF00) { + + /* Start P1 + Start P2 + VEFX + Effect */ + if (((pad >> 24) & 0x0F) == 0x0F) { + OutputDebugString("Triggered exit hook\n"); + + turn_off_lights(fd); + + OutputDebugString("Done exit hook\n"); + + Sleep(100); + + /* Don't use ExitProcess. This might result in deadlocks + on newer games which rely more on multi threading */ + HANDLE hnd; + hnd = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, TRUE, + GetCurrentProcessId()); + TerminateProcess(hnd, 0); + } + } + } + } + + return result; +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { + hook_table_apply( + NULL, + "kernel32.dll", + iidxfx_hook_syms, + lengthof(iidxfx_hook_syms)); + + } + + return TRUE; +} diff --git a/src/main/iidx-ezusb2-exit-hook/Module.mk b/src/main/iidx-ezusb2-exit-hook/Module.mk new file mode 100644 index 0000000..7828a97 --- /dev/null +++ b/src/main/iidx-ezusb2-exit-hook/Module.mk @@ -0,0 +1,8 @@ +dlls += iidx-ezusb2-exit-hook + +libs_iidx-ezusb2-exit-hook := \ + hook \ + util \ + +src_iidx-ezusb2-exit-hook := \ + main.c \ diff --git a/src/main/iidx-ezusb2-exit-hook/iidx-ezusb2-exit-hook.def b/src/main/iidx-ezusb2-exit-hook/iidx-ezusb2-exit-hook.def new file mode 100644 index 0000000..0332028 --- /dev/null +++ b/src/main/iidx-ezusb2-exit-hook/iidx-ezusb2-exit-hook.def @@ -0,0 +1,4 @@ +LIBRARY iidx-ezusb2-exit-hook + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/iidx-ezusb2-exit-hook/main.c b/src/main/iidx-ezusb2-exit-hook/main.c new file mode 100644 index 0000000..8efc654 --- /dev/null +++ b/src/main/iidx-ezusb2-exit-hook/main.c @@ -0,0 +1,121 @@ +#include +#include + +#include +#include + +#include "ezusb2/cyioctl.h" + +#include "ezusb2-iidx/msg.h" + +#include "hook/table.h" + +#include "util/defs.h" + +#define IOCTL_VEND_USB_REQ 0x220024 + +static BOOL STDCALL my_DeviceIoControl( + HANDLE fd, uint32_t code, void *in_bytes, uint32_t in_nbytes, + void *out_bytes, uint32_t out_nbytes, uint32_t *out_returned, + OVERLAPPED *ovl); + +static BOOL (STDCALL *real_DeviceIoControl)( + HANDLE fd, uint32_t code, void *in_bytes, uint32_t in_nbytes, + void *out_bytes, uint32_t out_nbytes, uint32_t *out_returned, + OVERLAPPED *ovl); + +static struct hook_symbol exit_hook_syms[] = { + { + .name = "DeviceIoControl", + .patch = my_DeviceIoControl, + .link = (void *) &real_DeviceIoControl + }, +}; + +static bool interrupt_write(HANDLE handle, + const struct ezusb2_iidx_msg_interrupt_write_packet* packet) +{ + SINGLE_TRANSFER usb_req; + uint32_t outpkt; + + /* PIPE_INT_OUT */ + usb_req.SetupPacket.bmRequest = 0x01; + + return real_DeviceIoControl( + handle, IOCTL_VEND_USB_REQ, &usb_req, + sizeof(usb_req), (void*) packet, + sizeof(struct ezusb2_iidx_msg_interrupt_write_packet), &outpkt, NULL); +} + +static void turn_off_lights(HANDLE fd) +{ + struct ezusb2_iidx_msg_interrupt_write_packet data; + + memset(&data, 0, sizeof(data)); + + for (uint8_t i = 0; i < 9; i++) { + data.seg16[i] = ' '; + } + + OutputDebugString("Switching off lights...\n"); + + if (!interrupt_write(fd, &data)) { + OutputDebugString("Switching off lights failed\n"); + } +} + +static BOOL STDCALL my_DeviceIoControl( + HANDLE fd, uint32_t code, void *in_bytes, uint32_t in_nbytes, + void *out_bytes, uint32_t out_nbytes, uint32_t *out_returned, + OVERLAPPED *ovl) +{ + BOOL result; + + /* Call real first to get the input data */ + result = real_DeviceIoControl(fd, code, in_bytes, in_nbytes, out_bytes, + out_nbytes, out_returned, ovl); + + if (code == IOCTL_VEND_USB_REQ) { + PSINGLE_TRANSFER usb_req = in_bytes; + + /* Interrupt in endpoint */ + if (usb_req->SetupPacket.bmRequest == 0x81) { + struct ezusb2_iidx_msg_interrupt_read_packet* msg_resp = + (struct ezusb2_iidx_msg_interrupt_read_packet*) + (out_bytes + sizeof(SINGLE_TRANSFER)); + + uint32_t pad = ~msg_resp->inverted_pad; + + /* Start P1 + Start P2 + VEFX + Effect */ + if ((pad & 0x0F) == 0x0F) { + OutputDebugString("Triggered exit hook\n"); + + turn_off_lights(fd); + + OutputDebugString("Done exit hook\n"); + + /* Don't use ExitProcess. This might result in deadlocks + on newer games which rely more on multi threading */ + HANDLE hnd; + hnd = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, TRUE, + GetCurrentProcessId()); + TerminateProcess(hnd, 0); + } + } + } + + return result; +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { + hook_table_apply( + NULL, + "kernel32.dll", + exit_hook_syms, + lengthof(exit_hook_syms)); + } + + return TRUE; +} diff --git a/src/main/iidx-irbeat-patch/Module.mk b/src/main/iidx-irbeat-patch/Module.mk new file mode 100644 index 0000000..df42cd3 --- /dev/null +++ b/src/main/iidx-irbeat-patch/Module.mk @@ -0,0 +1,7 @@ +exes += iidx-irbeat-patch + +libs_iidx-irbeat-patch := \ + util \ + +src_iidx-irbeat-patch := \ + main.c \ diff --git a/src/main/iidx-irbeat-patch/main.c b/src/main/iidx-irbeat-patch/main.c new file mode 100644 index 0000000..4a932b2 --- /dev/null +++ b/src/main/iidx-irbeat-patch/main.c @@ -0,0 +1,213 @@ +#include + +#include +#include + +#include "util/fs.h" + +static const uint32_t offset_size_data_area = 4; +static const uint32_t offset_data_area_checksum = 8; +static const uint32_t offset_data_area = 12; +static const uint32_t offset_file_header_checksum = 0; +static const uint32_t size_file_header = 8; + +static uint32_t checksum_settings_data_09(const uint8_t* buffer) +{ + uint32_t sum = 0; + + for (uint32_t i = 0; i < 10; i++) { + sum += ((uint8_t) i) ^ buffer[i]; + } + sum += 10; + + for (uint32_t i = 0; i < 0x34; i++) { + sum += ((uint8_t) i) ^ buffer[12 + i]; + } + sum += 0x34; + + for (uint32_t i = 0; i < 4; i++) { + sum += ((uint8_t) i) ^ buffer[64 + i]; + } + sum += 4; + + for (uint32_t i = 0; i < 0x1E; i++) { + sum += ((uint8_t) i) ^ buffer[68 + i]; + } + sum += 0x1E; + + for (uint32_t i = 0; i < 4; i++) { + sum += ((uint8_t) i) ^ buffer[100 + i]; + } + sum += 4; + + for (uint32_t i = 0; i < 0xA; i++) { + sum += ((uint8_t) i) ^ buffer[108 + i]; + } + sum += 0xA; + + for (uint32_t i = 0; i < 0x438; i++) { + sum += ((uint8_t) i) ^ buffer[120 + i]; + } + sum += 0x438; + + for (uint32_t i = 0; i < 0x690; i++) { + sum += ((uint8_t) i) ^ buffer[1200 + i]; + } + sum += 0x690; + + return sum; +} + +static uint32_t checksum_settings_data_10(const uint8_t* buffer) +{ + uint32_t sum = 0; + + for (uint32_t i = 0; i < 10; i++) { + sum += ((uint8_t) i) ^ buffer[i]; + } + sum += 10; + + for (uint32_t i = 0; i < 0x34; i++) { + sum += ((uint8_t) i) ^ buffer[12 + i]; + } + sum += 0x34; + + for (uint32_t i = 0; i < 4; i++) { + sum += ((uint8_t) i) ^ buffer[64 + i]; + } + sum += 4; + + for (uint32_t i = 0; i < 0x1E; i++) { + sum += ((uint8_t) i) ^ buffer[68 + i]; + } + sum += 0x1E; + + for (uint32_t i = 0; i < 4; i++) { + sum += ((uint8_t) i) ^ buffer[100 + i]; + } + sum += 4; + + for (uint32_t i = 0; i < 0xA; i++) { + sum += ((uint8_t) i) ^ buffer[108 + i]; + } + sum += 0xA; + + for (uint32_t i = 0; i < 0x49C; i++) { + sum += ((uint8_t) i) ^ buffer[120 + i]; + } + sum += 0x49C; + + for (uint32_t i = 0; i < 0x690; i++) { + sum += ((uint8_t) i) ^ buffer[1300 + i]; + } + sum += 0x690; + + return sum; +} + +uint32_t checksum(const uint8_t* buffer, size_t length) +{ + uint32_t sum = 0; + const uint32_t* ptr = (const uint32_t*) buffer; + + for (uint32_t i = 0; i < length / 4; i++) { + sum += *ptr ^ i; + ptr++; + } + + return sum; +} + +static void patch_09(uint8_t* buffer, uint32_t size_data_area, uint8_t irbeat) +{ + /* patch beat phase */ + buffer[0x49] = irbeat; + + // settings data checksum */ + *((uint32_t*) &buffer[0x74]) = + checksum_settings_data_09(&buffer[offset_data_area]); + + /* update data area checksum */ + *((uint32_t*) &buffer[offset_data_area_checksum]) = + checksum(&buffer[offset_data_area], size_data_area); + + /* update file checksum */ + *((uint32_t*) &buffer[offset_file_header_checksum]) = + checksum(&buffer[offset_size_data_area], size_file_header); +} + +static void patch_10(uint8_t* buffer, uint32_t size_data_area, uint8_t irbeat) +{ + /* patch beat phase */ + buffer[0x49] = irbeat; + + // settings data checksum */ + *((uint32_t*) &buffer[0x74]) = + checksum_settings_data_10(&buffer[offset_data_area]); + + /* update data area checksum */ + *((uint32_t*) &buffer[offset_data_area_checksum]) = + checksum(&buffer[offset_data_area], size_data_area); + + /* update file checksum */ + *((uint32_t*) &buffer[offset_file_header_checksum]) = + checksum(&buffer[offset_size_data_area], size_file_header); +} + +int main(int argc, char **argv) +{ + uint8_t version; + uint8_t irbeat; + const char* settings; + uint8_t* buffer; + size_t size; + uint32_t size_data_area; + + if (argc < 4) { + printf("Patches the IRBeat phase of 9th or 10th Style\n"); + fprintf(stderr, "Usage: %s \n", argv[0]); + return -1; + } + + version = atoi(argv[1]); + irbeat = atoi(argv[2]); + settings = argv[3]; + + if (version != 9 && version != 10) { + fprintf(stderr, "Invalid game version %d\n", version); + return -2; + } + + if (irbeat > 2) { + fprintf(stderr, "Invalid IR beat phase %d\n", irbeat); + return -3; + } + + printf("Setting IRBeat phase %d on settings file %s\n", irbeat + 1, settings); + + if (!file_load(settings, (void**) &buffer, &size, false)) { + fprintf(stderr, "Loading file %s failed\n", settings); + return -4; + } + + size_data_area = *((uint32_t*) &buffer[offset_size_data_area]); + + switch (version) { + case 9: + patch_09(buffer, size_data_area, irbeat); + break; + case 10: + patch_10(buffer, size_data_area, irbeat); + break; + default: + break; + } + + if (!file_save(settings, buffer, size)) { + fprintf(stderr, "Saving to file %s failed\n", settings); + return -5; + } + + printf("Patching successful\n"); + return 0; +} \ No newline at end of file diff --git a/src/main/iidxhook-util/Module.mk b/src/main/iidxhook-util/Module.mk new file mode 100644 index 0000000..05b02ee --- /dev/null +++ b/src/main/iidxhook-util/Module.mk @@ -0,0 +1,19 @@ +libs += iidxhook-util + +libs_iidxhook-util := \ + util \ + +src_iidxhook-util := \ + acio.c \ + chart-patch.c \ + clock.c \ + config-eamuse.c \ + config-gfx.c \ + config-misc.c \ + config-sec.c \ + d3d8.c \ + d3d9.c \ + eamuse.c \ + effector.c \ + log-server.c \ + settings.c \ diff --git a/src/main/iidxhook-util/acio.c b/src/main/iidxhook-util/acio.c new file mode 100644 index 0000000..a1bc8c2 --- /dev/null +++ b/src/main/iidxhook-util/acio.c @@ -0,0 +1,103 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "acioemu/addr.h" +#include "acioemu/emu.h" +#include "acioemu/icca.h" + +#include "hook/iohook.h" + +#include "iidxhook-util/acio.h" + +#include "imports/avs.h" + +#include "util/defs.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +static struct ac_io_emu iidxhook_util_acio_emu; +static struct ac_io_emu_icca iidxhook_util_acio_emu_icca[2]; + +void iidxhook_util_acio_init(bool legacy_mode) +{ + uint8_t i; + + if (legacy_mode) { + ac_io_legacy_mode(); + } + + ac_io_emu_init(&iidxhook_util_acio_emu, L"COM1"); + + for (i = 0 ; i < lengthof(iidxhook_util_acio_emu_icca) ; i++) { + ac_io_emu_icca_init(&iidxhook_util_acio_emu_icca[i], + &iidxhook_util_acio_emu, i); + } +} + +void iidxhook_util_acio_fini(void) +{ + ac_io_emu_fini(&iidxhook_util_acio_emu); +} + +HRESULT iidxhook_util_acio_dispatch_irp(struct irp *irp) +{ + const struct ac_io_message *msg; + HRESULT hr; + + log_assert(irp != NULL); + + if (!ac_io_emu_match_irp(&iidxhook_util_acio_emu, irp)) { + return irp_invoke_next(irp); + } + + for (;;) { + hr = ac_io_emu_dispatch_irp(&iidxhook_util_acio_emu, irp); + + if (hr != S_OK) { + return hr; + } + + msg = ac_io_emu_request_peek(&iidxhook_util_acio_emu); + + switch (msg->addr) { + case 0: + ac_io_emu_cmd_assign_addrs(&iidxhook_util_acio_emu, msg, 2); + + break; + + case 1: + ac_io_emu_icca_dispatch_request(&iidxhook_util_acio_emu_icca[0], + msg); + + break; + + case 2: + ac_io_emu_icca_dispatch_request(&iidxhook_util_acio_emu_icca[1], + msg); + + break; + + case AC_IO_BROADCAST: + log_warning("Broadcast(?) message on IIDX ACIO bus?"); + + break; + + default: + log_warning("ACIO message on unhandled bus address: %d", + msg->addr); + + break; + } + + ac_io_emu_request_pop(&iidxhook_util_acio_emu); + } +} diff --git a/src/main/iidxhook-util/acio.h b/src/main/iidxhook-util/acio.h new file mode 100644 index 0000000..fa6db73 --- /dev/null +++ b/src/main/iidxhook-util/acio.h @@ -0,0 +1,32 @@ +#ifndef IIDXHOOK_UTIL_ACIO_H +#define IIDXHOOK_UTIL_ACIO_H + +#include + +#include + +#include "hook/iohook.h" + +/** + * Initialize an emulated ACIO on COM1 for IIDX. + * + * @param legacy_mode Set to true if running on slotted readers on iidx 13-18. + * Old games using slotted readers might use an old libacio version that + * does not support handling of multi messages per package. Instead, + * legacy mode responds with one message per acio package. The game + * logs MiCmd Retry errors with the code 0x00000002 if this option is + * not set correctly. + */ +void iidxhook_util_acio_init(bool legacy_mode); + +/** + * Cleanup the ACIO emulation layer. + */ +void iidxhook_util_acio_fini(void); + +/** + * iohook dispatch function. Needs to be installed. + */ +HRESULT iidxhook_util_acio_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/iidxhook-util/chart-patch.c b/src/main/iidxhook-util/chart-patch.c new file mode 100644 index 0000000..6014b73 --- /dev/null +++ b/src/main/iidxhook-util/chart-patch.c @@ -0,0 +1,536 @@ +#define LOG_MODULE "chart-patch" + +#include +#include +#include + +#include "hook/iohook.h" + +#include "util/crc.h" +#include "util/defs.h" +#include "util/fs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +struct chart_patch_file { + HANDLE fd; + struct const_iobuf state; + bool active; +}; + +struct chart_toc_entry { + uint32_t offset; + uint32_t length; +} __attribute__((__packed__)); + +struct chart_event { + uint32_t time; + uint8_t type; + uint8_t data1; + uint16_t data2; +} __attribute__((__packed__)); + +struct ospreybin_entry { + char filename[32]; + uint32_t checksum1; + uint32_t checksum2; + uint32_t offset_in_file; + uint32_t checksum_length; + uint32_t filesize; +} __attribute__((__packed__)); + +struct ospreybin { + /* IIDX */ + char magic[4]; + /* e.g. 17 for Sirius */ + uint32_t version; + /* seems to be unused */ + uint32_t num_entries; + uint32_t checksum_type; +}; + +static bool chart_patch_initted; + +static double chart_patch_hz; +static double chart_patch_timebase; + +static bool chart_patch_file_detour; +static char chart_patch_file_1_name[6]; +static struct chart_patch_file chart_patch_file_1; +static struct chart_patch_file chart_patch_file_checksum; +static CRITICAL_SECTION chart_patch_lock; + +static bool chart_patch_file_load(struct irp* irp_open, + struct chart_patch_file *file, const wchar_t* path) +{ + struct irp irp; + void *bytes; + size_t nbytes; + HANDLE fd; + HRESULT hr; + char* tmp; + + /* Reset file to ensure consistent not-available state */ + memset(file, 0, sizeof(struct chart_patch_file)); + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_OPEN; + irp.next_handler = irp_open->next_handler; + irp.open_filename = path; + irp.open_access = GENERIC_READ; + irp.open_creation = OPEN_EXISTING; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + return false; + } + + fd = irp.fd; + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_SEEK; + irp.fd = fd; + irp.seek_origin = FILE_END; + irp.seek_offset = 0; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_fatal("Seeking end failed"); + } + + nbytes = (size_t) irp.seek_pos; + bytes = xmalloc(nbytes); + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_SEEK; + irp.fd = fd; + irp.seek_origin = FILE_BEGIN; + irp.seek_offset = 0; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_fatal("Seeking begin failed"); + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_READ; + irp.fd = fd; + irp.read.bytes = bytes; + irp.read.nbytes = nbytes; + irp.read.pos = 0; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_fatal("Reading failed"); + } + + if (irp.read.pos != nbytes) { + hr = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF); + + log_fatal("Reading entire file failed"); + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_CLOSE; + irp.fd = fd; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_fatal("Closing failed"); + } + + file->fd = iohook_open_dummy_fd(); + memset(&file->state, 0, sizeof(struct const_iobuf)); + file->state.bytes = bytes; + file->state.nbytes = nbytes; + file->active = false; + + wstr_narrow(path, &tmp); + log_misc("Loading %s successful, fd %p", tmp, file->fd); + free(tmp); + + return true; +} + +static bool chart_patch_is_chart_id(const wchar_t* str) +{ + for (int i = 0; i < 4; i++) { + if (!(str[i] >= L'0' && str[i] <= L'9')) { + return false; + } + } + + return true; +} + +static void chart_patch_execute_patch_1() +{ + double factor; + struct chart_toc_entry *toc; + struct chart_event *event; + size_t chart_offset; + size_t i; + + log_assert(chart_patch_file_1.fd != NULL); + log_assert(chart_patch_hz > 0); + log_assert(chart_patch_timebase > 0); + + factor = chart_patch_hz / chart_patch_timebase; + toc = (struct chart_toc_entry *) chart_patch_file_1.state.bytes; + + log_misc("Patching chart: orig %f, target %f, factor %f", + chart_patch_timebase, chart_patch_hz, factor); + + for (i = 0 ; i < 12 ; i++) { + if (toc[i].offset == 0) { + continue; /* No such chart */ + } + + for ( chart_offset = toc[i].offset ; + chart_offset < toc[i].offset + toc[i].length ; + chart_offset += sizeof(struct chart_event)) { + event = (struct chart_event *) + &chart_patch_file_1.state.bytes[chart_offset]; + + /* Don't clobber the end-of-chart marker */ + + if (event->time != 0x7FFFFFFF) { + event->time = (uint32_t) round(event->time * factor); + } + } + } +} + +static void chart_patch_execute_patch_checksum() +{ + const struct ospreybin *header; + struct ospreybin_entry *entries; + struct ospreybin_entry *entry; + const void *span_start; + size_t span_len; + uint32_t checksum; + size_t i; + + header = (struct ospreybin *) &chart_patch_file_checksum.state.bytes[0]; + entries = (struct ospreybin_entry *) + &chart_patch_file_checksum.state.bytes[sizeof(*header)]; + + for (i = 0 ; i < header->num_entries ; i++) { + entry = &entries[i]; + + if (!str_eq(chart_patch_file_1_name, entry->filename)) { + continue; + } + + span_start = + &chart_patch_file_1.state.bytes[entry->offset_in_file]; + span_len = entry->checksum_length; + checksum = crc32(span_start, span_len, 0); + + entry->checksum1 = checksum; + entry->checksum2 = checksum; + + return; + } + + log_warning("Could not find checksum row for %s", chart_patch_file_1_name); +} + +static bool chart_patch_file_trap(struct irp* irp) +{ + wchar_t buffer_1_path[1024]; + wchar_t buffer_checksum_path[1024]; + wchar_t chart_id[5]; + wchar_t chart_file[7]; + bool is_1_file; + bool is_checksum_file; + char* tmp; + + /* This has to trap three different scenarios: + 1. open .1 chart file only + 2. open .1 chart file first, then osprey.bin + 3. open osprey.bin first, then .1 chart file + In the end, we have to serve either just the .1 file or both the .1 + and checksum file from memory until the application closes one or both + files */ + if (swscanf(irp->open_filename, L"..\\data\\sd_data%*[\\/]%c%c%c%c%*[\\/]", + &chart_id[0], &chart_id[1], &chart_id[2], &chart_id[3]) == 4 && + chart_patch_is_chart_id(chart_id)) { + chart_id[4] = '\0'; + + wstr_narrow(irp->open_filename, &tmp); + log_misc("Trapping %s...", tmp); + free(tmp); + + memcpy(chart_file, chart_id, sizeof(chart_id)); + chart_file[4] = L'.'; + chart_file[5] = L'1'; + chart_file[6] = L'\0'; + + is_1_file = wstr_ends_with(irp->open_filename, chart_file); + is_checksum_file = wstr_ends_with(irp->open_filename, L"osprey.bin"); + + /* Block a second open call to either .1 or checksum file if detouring + is already active */ + if (chart_patch_file_detour) { + if (is_1_file) { + irp->fd = chart_patch_file_1.fd; + chart_patch_file_1.active = true; + } else if (is_checksum_file) { + irp->fd = chart_patch_file_checksum.fd; + chart_patch_file_checksum.active = true; + } else { + /* other file, like *.2dx */ + return false; + } + + log_misc("Data already prepared"); + + return true; + } + + if (is_1_file || is_checksum_file) { + wstr_narrow(chart_id, &tmp); + log_misc("Preparing in-memory chart %s...", tmp); + free(tmp); + + wsprintfW(buffer_1_path, L"..\\data\\sd_data\\\\%s\\%s.1", chart_id, + chart_id); + wsprintfW(buffer_checksum_path, + L"..\\data\\sd_data\\%s\\osprey.bin", chart_id); + + if (!chart_patch_file_load(irp, &chart_patch_file_1, + buffer_1_path)) { + wstr_narrow(buffer_1_path, &tmp); + log_fatal("Cannot load .1 file %s", tmp); + free(tmp); + } + + chart_patch_execute_patch_1(); + + wstr_narrow(chart_file, &tmp); + memcpy(chart_patch_file_1_name, tmp, + sizeof(chart_patch_file_1_name)); + free(tmp); + + /* Optional on some games */ + if (chart_patch_file_load(irp, &chart_patch_file_checksum, + buffer_checksum_path)) { + chart_patch_execute_patch_checksum(); + } + + /* Return dummy handle */ + if (is_1_file) { + irp->fd = chart_patch_file_1.fd; + chart_patch_file_1.active = true; + } else if (is_checksum_file) { + irp->fd = chart_patch_file_checksum.fd; + chart_patch_file_checksum.active = true; + } else { + log_fatal("Illegal state 2"); + } + + wstr_narrow(chart_id, &tmp); + log_misc("Patch for %s prepared in memory", tmp); + free(tmp); + + chart_patch_file_detour = true; + + return true; + } + } + + return false; +} + +static HRESULT chart_patch_file_close_irp( + struct chart_patch_file *file, + struct irp *irp_close) +{ + HRESULT hr; + struct irp irp; + + file->active = false; + + // log_misc("chart_patch_file_close_irp %d %p %d %p", + // chart_patch_file_1.active, + // chart_patch_file_1.fd, + // chart_patch_file_checksum.active, + // chart_patch_file_checksum.fd); + + /* Check if both files are closed and free resources */ + if (!chart_patch_file_1.active && !chart_patch_file_checksum.active) { + log_misc("Closing fd %p and cleaning up", file->fd); + + if (chart_patch_file_1.fd != NULL) { + memcpy(&irp, irp_close, sizeof(struct irp)); + irp.fd = chart_patch_file_1.fd; + + hr = irp_invoke_next(&irp); + + if (hr != S_OK) { + log_warning("Closing dummy .1 file handle failed"); + } + + free((void*) chart_patch_file_1.state.bytes); + memset(&chart_patch_file_1, 0, sizeof(struct chart_patch_file)); + } + + if (chart_patch_file_checksum.fd != NULL) { + memcpy(&irp, irp_close, sizeof(struct irp)); + irp.fd = chart_patch_file_checksum.fd; + + hr = irp_invoke_next(&irp); + + if (hr != S_OK) { + log_warning("Closing dummy checksum file handle failed"); + } + + free((void*) chart_patch_file_checksum.state.bytes); + memset(&chart_patch_file_checksum, 0, + sizeof(struct chart_patch_file)); + } + + chart_patch_file_detour = false; + } else { + log_misc("Closing fd %p", file->fd); + } + + return S_OK; +} + +static HRESULT chart_patch_file_read( + struct chart_patch_file *file, + struct irp *irp) +{ + // log_misc("chart_patch_file_read %p", file->fd); + + iobuf_move(&irp->read, &file->state); + + return S_OK; +} + +static HRESULT chart_patch_file_seek( + struct chart_patch_file *file, + struct irp *irp) +{ + ssize_t pos; + + switch (irp->seek_origin) { + case FILE_BEGIN: + pos = irp->seek_offset; + + break; + + case FILE_CURRENT: + pos = irp->seek_offset + file->state.pos; + + break; + + case FILE_END: + pos = irp->seek_offset + file->state.nbytes; + + break; + + default: + return E_INVALIDARG; + } + + file->state.pos = max(0, min(pos, file->state.nbytes)); + irp->seek_pos = file->state.pos; + + return S_OK; +} + +static HRESULT chart_patch_file_dispatch_irp( + struct chart_patch_file *file, + struct irp *irp) +{ + // log_misc("chart_patch_file_dispatch_irp %p", file->fd); + + switch (irp->op) { + case IRP_OP_CLOSE: return chart_patch_file_close_irp(file, irp); + case IRP_OP_READ: return chart_patch_file_read(file, irp); + case IRP_OP_WRITE: return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); + case IRP_OP_SEEK: return chart_patch_file_seek(file, irp); + default: return E_NOTIMPL; + } +} + +void iidxhook_util_chart_patch_init(double orig_timebase) +{ + log_assert(!chart_patch_initted); + + chart_patch_file_detour = false; + + chart_patch_timebase = orig_timebase; + + chart_patch_file_1.fd = INVALID_HANDLE_VALUE; + chart_patch_file_checksum.fd = INVALID_HANDLE_VALUE; + + InitializeCriticalSection(&chart_patch_lock); + + chart_patch_initted = true; + + log_info("Initialized with orig_timebase %f", orig_timebase); +} + +void iidxhook_util_chart_patch_fini(void) +{ + log_assert(chart_patch_initted); + + DeleteCriticalSection(&chart_patch_lock); + chart_patch_initted = false; + + log_info("Fini"); +} + +void iidxhook_util_chart_patch_set_refresh_rate(double hz) +{ + chart_patch_hz = hz; + + log_info("Set target refresh rate %f", chart_patch_hz); +} + +HRESULT iidxhook_util_chart_patch_dispatch_irp(struct irp *irp) +{ + HRESULT hr; + + if (chart_patch_initted) { + EnterCriticalSection(&chart_patch_lock); + + hr = S_FALSE; + + if (irp->op == IRP_OP_OPEN) { + if (chart_patch_file_trap(irp)) { + hr = S_OK; + } else { + hr = irp_invoke_next(irp); + } + } else { + if (irp->fd != INVALID_HANDLE_VALUE && irp->fd != NULL) { + if (irp->fd == chart_patch_file_1.fd) { + hr = chart_patch_file_dispatch_irp(&chart_patch_file_1, + irp); + } else if (irp->fd == chart_patch_file_checksum.fd) { + hr = chart_patch_file_dispatch_irp( + &chart_patch_file_checksum, irp); + } else { + hr = irp_invoke_next(irp); + } + } + } + + LeaveCriticalSection(&chart_patch_lock); + + return hr; + } else { + return irp_invoke_next(irp); + } +} \ No newline at end of file diff --git a/src/main/iidxhook-util/chart-patch.h b/src/main/iidxhook-util/chart-patch.h new file mode 100644 index 0000000..1812190 --- /dev/null +++ b/src/main/iidxhook-util/chart-patch.h @@ -0,0 +1,44 @@ +#ifndef IIDXHOOK_UTIL_CHART_PATCH_H +#define IIDXHOOK_UTIL_CHART_PATCH_H + +#include + +#include "hook/iohook.h" + +/* 9th to DD */ +#define IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_9_TO_13 59.95 +/* GOLD to Resort Anthem */ +#define IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_14_TO_18_VGA 60.05 + +/** + * Install hooks to intercept chart file loading. This allows you to patch + * the charts in-memory to modify their timestamps and making the game run + * on-sync on non-stock setups with different display refresh rates than + * 59.94 or 60.04 (depending on game version). + * + * @param orig_timebase The original refresh rate (timebase value) of the game. + * Use one of the defined macros. + */ +void iidxhook_util_chart_patch_init(double orig_timebase); + +/** + * Shut down this module. + */ +void iidxhook_util_chart_patch_fini(void); + +/** + * Set the target refresh rate of the display. Determine this value either + * by using the d3d8 or d3d9 monitor check of bemanitools or get it from another + * source (e.g. one of the newer games in-built monitor checks). + * + * @param hz Refresh rate of the display. Ensure your display provides a + * constant value. + */ +void iidxhook_util_chart_patch_set_refresh_rate(double hz); + +/** + * iohook dispatch function. Needs to be installed. + */ +HRESULT iidxhook_util_chart_patch_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/iidxhook-util/clock.c b/src/main/iidxhook-util/clock.c new file mode 100644 index 0000000..e489e89 --- /dev/null +++ b/src/main/iidxhook-util/clock.c @@ -0,0 +1,37 @@ +#define LOG_MODULE "clock-hook" + +#include + +#include "hook/table.h" + +#include "util/defs.h" +#include "util/log.h" + +static BOOL STDCALL my_SetLocalTime(const SYSTEMTIME* lpSystemTime); +static BOOL (STDCALL *real_SetLocalTime)(const SYSTEMTIME* lpSystemTime); + +static const struct hook_symbol clock_hook_syms[] = { + { + .name = "SetLocalTime", + .patch = my_SetLocalTime, + .link = (void **) &real_SetLocalTime + }, +}; + +static BOOL STDCALL my_SetLocalTime(const SYSTEMTIME* lpSystemTime) +{ + /* Stub, don't mess with system clock */ + log_misc("Blocked setting system clock time"); + return TRUE; +} + +void iidxhook_util_clock_hook_init(void) +{ + hook_table_apply( + NULL, + "kernel32.dll", + clock_hook_syms, + lengthof(clock_hook_syms)); + + log_info("Inserted clock hooks"); +} diff --git a/src/main/iidxhook-util/clock.h b/src/main/iidxhook-util/clock.h new file mode 100644 index 0000000..28c08e5 --- /dev/null +++ b/src/main/iidxhook-util/clock.h @@ -0,0 +1,11 @@ +#ifndef IIDXHOOK_UTIL_CLOCK_H +#define IIDXHOOK_UTIL_CLOCK_H + +/** + * Hook clock related calls to avoid chaning the system clock + * time from the operator menu. 9th style is a special case here as it + * allows a max. year of 2009, only. + */ +void iidxhook_util_clock_hook_init(void); + +#endif diff --git a/src/main/iidxhook-util/config-eamuse.c b/src/main/iidxhook-util/config-eamuse.c new file mode 100644 index 0000000..5b950b7 --- /dev/null +++ b/src/main/iidxhook-util/config-eamuse.c @@ -0,0 +1,129 @@ +#include + +#include "cconfig/cconfig-util.h" + +#include "iidxhook-util/config-eamuse.h" + +#include "security/mcode.h" + +#include "util/log.h" + +#define IIDXHOOK_CONFIG_EAMUSE_CARD_TYPE_KEY "eamuse.card_type" +#define IIDXHOOK_CONFIG_EAMUSE_SERVER_KEY "eamuse.server" +#define IIDXHOOK_CONFIG_EAMUSE_PCBID_KEY "eamuse.pcbid" +#define IIDXHOOK_CONFIG_EAMUSE_EAMID_KEY "eamuse.eamid" + +#define IIDXHOOK_CONFIG_EAMUSE_DEFAULT_CARD_TYPE_VALUE SECURITY_MCODE_GAME_IIDX_9 +#define IIDXHOOK_CONFIG_EAMUSE_DEFAULT_SERVER_VALUE "localhost:80" +#define IIDXHOOK_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE security_id_default +#define IIDXHOOK_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE_LEN sizeof(security_id_default) +#define IIDXHOOK_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE security_id_default +#define IIDXHOOK_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE_LEN sizeof(security_id_default) + +const struct net_addr iidxhook_eamuse_default_server = { + .type = NET_ADDR_TYPE_HOSTNAME, + .hostname.host = "localhost", + .hostname.port = 80, +}; + +void iidxhook_util_config_eamuse_init(struct cconfig* config) +{ + cconfig_util_set_str(config, + IIDXHOOK_CONFIG_EAMUSE_CARD_TYPE_KEY, + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_CARD_TYPE_VALUE, + "Magnetic card type, format XXX, 3 digit string (supports: C02, D01, " + "E11, ECO)"); + + cconfig_util_set_str(config, + IIDXHOOK_CONFIG_EAMUSE_SERVER_KEY, + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_SERVER_VALUE, + "URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 " + "(e.g. 127.0.0.1:80) of the target eamuse server. The port is optional " + "but defaults to 80."); + + cconfig_util_set_data(config, IIDXHOOK_CONFIG_EAMUSE_PCBID_KEY, + (uint8_t*) &IIDXHOOK_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE, + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE_LEN, "PCBID"); + + cconfig_util_set_data(config, IIDXHOOK_CONFIG_EAMUSE_EAMID_KEY, + (uint8_t*) &IIDXHOOK_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE, + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE_LEN, "EAMID"); +} + +void iidxhook_util_config_eamuse_get( + struct iidxhook_util_config_eamuse* config_eamuse, + struct cconfig* config) +{ + char server_url[1024]; + char* tmp; + char* tmp2; + + memset(config_eamuse, 0, sizeof(struct iidxhook_util_config_eamuse)); + + if (!cconfig_util_get_str(config, IIDXHOOK_CONFIG_EAMUSE_CARD_TYPE_KEY, + config_eamuse->card_type, sizeof(config_eamuse->card_type) - 1, + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_CARD_TYPE_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_EAMUSE_CARD_TYPE_KEY, + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_CARD_TYPE_VALUE); + } + + if (!cconfig_util_get_str(config, IIDXHOOK_CONFIG_EAMUSE_SERVER_KEY, + server_url, sizeof(server_url), + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_SERVER_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_EAMUSE_SERVER_KEY, + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_SERVER_VALUE); + } + + if (!net_str_parse(server_url, &config_eamuse->server)) { + memcpy(&config_eamuse->server, &iidxhook_eamuse_default_server, + sizeof(config_eamuse->server)); + tmp = net_addr_to_str(&config_eamuse->server); + log_warning("Invalid value for key '%s' specified, fallback " + "to default", tmp); + free(tmp); + } + + if (!cconfig_util_get_data(config, IIDXHOOK_CONFIG_EAMUSE_PCBID_KEY, + (uint8_t*) &config_eamuse->pcbid, + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE_LEN, + (uint8_t*) &IIDXHOOK_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE)) { + tmp = security_id_to_str(&IIDXHOOK_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE, + false); + log_warning("Invalid value for key '%s' specified, fallback " + "to default", tmp); + free(tmp); + } + + if (!security_id_verify(&config_eamuse->pcbid)) { + tmp = security_id_to_str(&IIDXHOOK_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE, + false); + tmp2 = security_id_to_str(&config_eamuse->pcbid, false); + log_warning("PCBID verification of '%s' failed, fallback to default " + "PCBID '%s'", tmp2, tmp); + free(tmp); + free(tmp2); + } + + if (!cconfig_util_get_data(config, IIDXHOOK_CONFIG_EAMUSE_EAMID_KEY, + (uint8_t*) &config_eamuse->eamid, + IIDXHOOK_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE_LEN, + (uint8_t*) &IIDXHOOK_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE)) { + tmp = security_id_to_str(&IIDXHOOK_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE, + false); + log_warning("Invalid value for key '%s' specified, fallback " + "to default", tmp); + free(tmp); + } + + if (!security_id_verify(&config_eamuse->eamid)) { + tmp = security_id_to_str(&IIDXHOOK_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE, + false); + tmp2 = security_id_to_str(&config_eamuse->eamid, false); + log_warning("EAMID verification of '%s' failed, fallback to default " + "EAMID '%s'", tmp2, tmp); + free(tmp); + free(tmp2); + } +} diff --git a/src/main/iidxhook-util/config-eamuse.h b/src/main/iidxhook-util/config-eamuse.h new file mode 100644 index 0000000..589a882 --- /dev/null +++ b/src/main/iidxhook-util/config-eamuse.h @@ -0,0 +1,23 @@ +#ifndef IIDXHOOK_UTIL_CONFIG_EAMUSE_H +#define IIDXHOOK_UTIL_CONFIG_EAMUSE_H + +#include "cconfig/cconfig.h" + +#include "security/id.h" + +#include "util/net.h" + +struct iidxhook_util_config_eamuse { + char card_type[4]; + struct net_addr server; + struct security_id pcbid; + struct security_id eamid; +}; + +void iidxhook_util_config_eamuse_init(struct cconfig* config); + +void iidxhook_util_config_eamuse_get( + struct iidxhook_util_config_eamuse* config_eamuse, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/iidxhook-util/config-gfx.c b/src/main/iidxhook-util/config-gfx.c new file mode 100644 index 0000000..0f90cfe --- /dev/null +++ b/src/main/iidxhook-util/config-gfx.c @@ -0,0 +1,235 @@ +#include + +#include "cconfig/cconfig-util.h" + +#include "iidxhook-util/config-gfx.h" + +#include "util/log.h" + +#define IIDXHOOK_CONFIG_GFX_BGVIDEO_UV_FIX_KEY "gfx.bgvideo_uv_fix" +#define IIDXHOOK_CONFIG_GFX_FRAMED_KEY "gfx.framed" +#define IIDXHOOK_CONFIG_GFX_FRAME_RATE_LIMIT_KEY "gfx.frame_rate_limit" +#define IIDXHOOK_CONFIG_GFX_MONITOR_CHECK_KEY "gfx.monitor_check" +#define IIDXHOOK_CONFIG_GFX_PCI_ID_KEY "gfx.pci_id" +#define IIDXHOOK_CONFIG_GFX_WINDOWED_KEY "gfx.windowed" +#define IIDXHOOK_CONFIG_GFX_WINDOW_WIDTH_KEY "gfx.window_width" +#define IIDXHOOK_CONFIG_GFX_WINDOW_HEIGHT_KEY "gfx.window_height" +#define IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_WIDTH_KEY "gfx.scale_back_buffer_width" +#define IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_HEIGHT_KEY "gfx.scale_back_buffer_height" +#define IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_FILTER_KEY "gfx.scale_back_buffer_filter" + +#define IIDXHOOK_CONFIG_GFX_DEFAULT_BGVIDEO_UV_FIX_VALUE false +#define IIDXHOOK_CONFIG_GFX_DEFAULT_FRAMED_VALUE false +#define IIDXHOOK_CONFIG_GFX_DEFAULT_FRAME_RATE_LIMIT_VALUE 0.0 +#define IIDXHOOK_CONFIG_GFX_DEFAULT_MONITOR_CHECK_VALUE -1.0 +#define IIDXHOOK_CONFIG_GFX_DEFAULT_PCI_ID_VALUE "1002:7146" +#define IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOWED_VALUE false +#define IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOW_WIDTH_VALUE -1 +#define IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOW_HEIGHT_VALUE -1 +#define IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_WIDTH_VALUE 0 +#define IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_HEIGHT_VALUE 0 +#define IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_FILTER_VALUE "none" + +void iidxhook_config_gfx_init(struct cconfig* config) +{ + cconfig_util_set_bool(config, + IIDXHOOK_CONFIG_GFX_BGVIDEO_UV_FIX_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_BGVIDEO_UV_FIX_VALUE, + "Fix stretched BG videos on newer GPUs. Might appear on Red and newer"); + + cconfig_util_set_bool(config, + IIDXHOOK_CONFIG_GFX_FRAMED_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_FRAMED_VALUE, + "Run the game in a framed window (requires windowed option)"); + + cconfig_util_set_float(config, + IIDXHOOK_CONFIG_GFX_FRAME_RATE_LIMIT_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_FRAME_RATE_LIMIT_VALUE, + "Software limit the frame rate of the rendering loop in hz, e.g. 60 or 59.95 (0.0 = no software limit)"); + + cconfig_util_set_float(config, + IIDXHOOK_CONFIG_GFX_MONITOR_CHECK_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_MONITOR_CHECK_VALUE, + "Enable/disable software monitor check/auto timebase or set " + "a pre-determined refresh value. -1 disables this feature. 0 enables " + "auto detecting the current refresh rate on startup. Setting any " + "positive value > 0 allows you to set a pre-determined refresh rate " + "(e.g. retrieved from the monitor check on newer IIDX games). Either " + "the auto detected value or pre-determined value is used to patch any " + "chart files in-memory to fix song synchronization issues. Requires " + "constant refresh rate!!!"); + + cconfig_util_set_str(config, + IIDXHOOK_CONFIG_GFX_PCI_ID_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_PCI_ID_VALUE, + "Patch the GPU device ID detection (leave empty to" + " disable), format XXXX:YYYY, two 4 digit hex numbers (vid:pid)." + " Examples: 1002:7146 (RV515, Radeon X1300), 1002:95C5 (RV620 LE," + " Radeon HD3450)"); + + cconfig_util_set_bool(config, + IIDXHOOK_CONFIG_GFX_WINDOWED_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOWED_VALUE, + "Run the game windowed"); + + cconfig_util_set_int(config, + IIDXHOOK_CONFIG_GFX_WINDOW_WIDTH_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOW_WIDTH_VALUE, + "Windowed width, -1 for default size"); + + cconfig_util_set_int(config, + IIDXHOOK_CONFIG_GFX_WINDOW_HEIGHT_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOW_HEIGHT_VALUE, + "Windowed height, -1 for default size"); + + cconfig_util_set_int(config, + IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_WIDTH_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_WIDTH_VALUE, + "Up-/downscale the back buffer's width. This does not change the game's rendering resolution but scales the " + "final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad " + "image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in " + "combination with the corresponding height parameter."); + + cconfig_util_set_int(config, + IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_HEIGHT_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_HEIGHT_VALUE, + "Up-/downscale the back buffer's height. This does not change the game's rendering resolution but scales the " + "final frame. Use this to target the native resolution of your monitor/TV, e.g. to avoid over-/underscan, bad " + "image quality or latency caused by the monitors internal upscaler. 0 to disable this feature. Must be set in " + "combination with the corresponding width parameter."); + + cconfig_util_set_str(config, + IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_FILTER_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_FILTER_VALUE, + "Filter type to use for up-/downscaling the back buffer. Only used if scaling feature was enabled by setting " + "the scaling width and height parameters. Available types: none, linear, point (refer to D3DTEXTUREFILTERTYPE " + " for explanation)."); +} + +void iidxhook_config_gfx_get(struct iidxhook_config_gfx* config_gfx, + struct cconfig* config) +{ + char tmp[10]; + char* vid; + char* pid; + int32_t tmp_int; + + if (!cconfig_util_get_bool(config, IIDXHOOK_CONFIG_GFX_BGVIDEO_UV_FIX_KEY, + &config_gfx->bgvideo_uv_fix, + IIDXHOOK_CONFIG_GFX_DEFAULT_BGVIDEO_UV_FIX_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_GFX_BGVIDEO_UV_FIX_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_BGVIDEO_UV_FIX_VALUE); + } + + if (!cconfig_util_get_bool(config, IIDXHOOK_CONFIG_GFX_FRAMED_KEY, + &config_gfx->framed, + IIDXHOOK_CONFIG_GFX_DEFAULT_FRAMED_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_GFX_FRAMED_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_FRAMED_VALUE); + } + + if (!cconfig_util_get_float(config, IIDXHOOK_CONFIG_GFX_FRAME_RATE_LIMIT_KEY, + &config_gfx->frame_rate_limit, + IIDXHOOK_CONFIG_GFX_DEFAULT_FRAME_RATE_LIMIT_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%f'", IIDXHOOK_CONFIG_GFX_FRAME_RATE_LIMIT_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_FRAME_RATE_LIMIT_VALUE); + } + + if (!cconfig_util_get_float(config, IIDXHOOK_CONFIG_GFX_MONITOR_CHECK_KEY, + &config_gfx->monitor_check, + IIDXHOOK_CONFIG_GFX_DEFAULT_MONITOR_CHECK_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%f'", IIDXHOOK_CONFIG_GFX_MONITOR_CHECK_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_MONITOR_CHECK_VALUE); + } + + if (!cconfig_util_get_str(config, IIDXHOOK_CONFIG_GFX_PCI_ID_KEY, + tmp, sizeof(tmp) - 1, + IIDXHOOK_CONFIG_GFX_DEFAULT_PCI_ID_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_GFX_PCI_ID_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_PCI_ID_VALUE); + } + + if (tmp[4] != ':') { + log_warning("Invalid format for value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_GFX_PCI_ID_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_PCI_ID_VALUE); + strcpy(tmp, IIDXHOOK_CONFIG_GFX_DEFAULT_PCI_ID_VALUE); + } + + tmp[4] = '\0'; + vid = tmp; + pid = &tmp[5]; + config_gfx->pci_id_vid = strtol(vid, NULL, 16); + config_gfx->pci_id_pid = strtol(pid, NULL, 16); + + if (!cconfig_util_get_bool(config, IIDXHOOK_CONFIG_GFX_WINDOWED_KEY, + &config_gfx->windowed, + IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOWED_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_GFX_WINDOWED_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOWED_VALUE); + } + + if (!cconfig_util_get_int(config, IIDXHOOK_CONFIG_GFX_WINDOW_WIDTH_KEY, + &config_gfx->window_width, IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOW_WIDTH_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_GFX_WINDOW_WIDTH_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOW_WIDTH_VALUE); + } + + if (!cconfig_util_get_int(config, IIDXHOOK_CONFIG_GFX_WINDOW_HEIGHT_KEY, + &config_gfx->window_height, IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOW_HEIGHT_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_GFX_WINDOW_HEIGHT_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_WINDOW_HEIGHT_VALUE); + } + + if (!cconfig_util_get_int(config, IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_WIDTH_KEY, + &tmp_int, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_WIDTH_VALUE) || + tmp_int < 0 || tmp_int > 0xFFFF) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_WIDTH_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_WIDTH_VALUE); + } + + config_gfx->scale_back_buffer_width = (uint16_t) tmp_int; + + if (!cconfig_util_get_int(config, IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_HEIGHT_KEY, + &tmp_int, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_HEIGHT_VALUE) || + tmp_int < 0 || tmp_int > 0xFFFF) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_HEIGHT_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_HEIGHT_VALUE); + } + + config_gfx->scale_back_buffer_height = (uint16_t) tmp_int; + + if (!cconfig_util_get_str(config, IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_FILTER_KEY, + tmp, sizeof(tmp) - 1, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_FILTER_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_FILTER_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_FILTER_VALUE); + } + + if (!strcmp(tmp, "none")) { + config_gfx->scale_back_buffer_filter = D3D9_BACK_BUFFER_SCALE_FILTER_NONE; + } else if (!strcmp(tmp, "linear")) { + config_gfx->scale_back_buffer_filter = D3D9_BACK_BUFFER_SCALE_FILTER_LINEAR; + } else if (!strcmp(tmp, "point")) { + config_gfx->scale_back_buffer_filter = D3D9_BACK_BUFFER_SCALE_FILTER_POINT; + } else { + config_gfx->scale_back_buffer_filter = D3D9_BACK_BUFFER_SCALE_FILTER_NONE; + + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_GFX_SCALE_BACK_BUFFER_FILTER_KEY, + IIDXHOOK_CONFIG_GFX_DEFAULT_SCALE_BACK_BUFFER_FILTER_VALUE); + } +} diff --git a/src/main/iidxhook-util/config-gfx.h b/src/main/iidxhook-util/config-gfx.h new file mode 100644 index 0000000..370bb3e --- /dev/null +++ b/src/main/iidxhook-util/config-gfx.h @@ -0,0 +1,28 @@ +#ifndef IIDXHOOK_CONFIG_GFX_H +#define IIDXHOOK_CONFIG_GFX_H + +#include "cconfig/cconfig.h" + +#include "iidxhook-util/d3d9.h" + +struct iidxhook_config_gfx { + bool bgvideo_uv_fix; + bool framed; + float frame_rate_limit; + float monitor_check; + uint16_t pci_id_vid; + uint16_t pci_id_pid; + bool windowed; + int32_t window_width; + int32_t window_height; + uint16_t scale_back_buffer_width; + uint16_t scale_back_buffer_height; + enum d3d9_back_buffer_scale_filter scale_back_buffer_filter; +}; + +void iidxhook_config_gfx_init(struct cconfig* config); + +void iidxhook_config_gfx_get(struct iidxhook_config_gfx* config_gfx, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/iidxhook-util/config-misc.c b/src/main/iidxhook-util/config-misc.c new file mode 100644 index 0000000..4267a43 --- /dev/null +++ b/src/main/iidxhook-util/config-misc.c @@ -0,0 +1,45 @@ +#include "cconfig/cconfig-util.h" + +#include "iidxhook-util/config-misc.h" + +#include "util/log.h" + +#define IIDXHOOK_CONFIG_MISC_DISABLE_CLOCK_SET_KEY "misc.disable_clock_set" +#define IIDXHOOK_CONFIG_MISC_RTEFFECT_STUB_KEY "misc.rteffect_stub" + +#define IIDXHOOK_CONFIG_MISC_DEFAULT_DISABLE_CLOCK_SET_VALUE false +#define IIDXHOOK_CONFIG_MISC_DEFAULT_RTEFFECT_STUB_VALUE false + +void iidxhook_config_misc_init(struct cconfig* config) +{ + cconfig_util_set_bool(config, + IIDXHOOK_CONFIG_MISC_DISABLE_CLOCK_SET_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_DISABLE_CLOCK_SET_VALUE, + "Disable operator clock setting system clock time"); + + cconfig_util_set_bool(config, + IIDXHOOK_CONFIG_MISC_RTEFFECT_STUB_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_RTEFFECT_STUB_VALUE, + "Stub calls to rteffect.dll (10th to DistorteD)"); +} + +void iidxhook_config_misc_get(struct iidxhook_config_misc* config_misc, + struct cconfig* config) +{ + if (!cconfig_util_get_bool(config, + IIDXHOOK_CONFIG_MISC_DISABLE_CLOCK_SET_KEY, + &config_misc->disable_clock_set, + IIDXHOOK_CONFIG_MISC_DEFAULT_DISABLE_CLOCK_SET_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_MISC_DISABLE_CLOCK_SET_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_DISABLE_CLOCK_SET_VALUE); + } + + if (!cconfig_util_get_bool(config, IIDXHOOK_CONFIG_MISC_RTEFFECT_STUB_KEY, + &config_misc->rteffect_stub, + IIDXHOOK_CONFIG_MISC_DEFAULT_RTEFFECT_STUB_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_MISC_RTEFFECT_STUB_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_RTEFFECT_STUB_VALUE); + } +} diff --git a/src/main/iidxhook-util/config-misc.h b/src/main/iidxhook-util/config-misc.h new file mode 100644 index 0000000..2a312c3 --- /dev/null +++ b/src/main/iidxhook-util/config-misc.h @@ -0,0 +1,16 @@ +#ifndef IIDXHOOK_CONFIG_MISC_H +#define IIDXHOOK_CONFIG_MISC_H + +#include "cconfig/cconfig.h" + +struct iidxhook_config_misc { + bool disable_clock_set; + bool rteffect_stub; +}; + +void iidxhook_config_misc_init(struct cconfig* config); + +void iidxhook_config_misc_get(struct iidxhook_config_misc* config_misc, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/iidxhook-util/config-sec.c b/src/main/iidxhook-util/config-sec.c new file mode 100644 index 0000000..4414816 --- /dev/null +++ b/src/main/iidxhook-util/config-sec.c @@ -0,0 +1,172 @@ +#include +#include + +#include "cconfig/cconfig-util.h" + +#include "iidxhook-util/config-sec.h" + +#include "security/mcode.h" +#include "security/rp.h" + +#include "util/log.h" +#include "util/mem.h" + +#define IIDXHOOK_CONFIG_SEC_BOOT_VERSION_KEY "sec.boot_version" +#define IIDXHOOK_CONFIG_SEC_BOOT_SEEDS_KEY "sec.boot_seeds" +#define IIDXHOOK_CONFIG_SEC_BLACK_PLUG_MCODE_KEY "sec.black_plug_mcode" + +/* Use C02 defaults */ +#define IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_VERSION_VALUE iidxhook_security_default_boot_version +#define IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_SEEDS_VALUE iidxhook_security_default_boot_seeds +#define IIDXHOOK_CONFIG_SEC_DEFAULT_BLACK_PLUG_MCODE_VALUE iidxhook_security_default_black_plug_mcode + +/* IIDX 09 default */ +static const struct security_mcode iidxhook_security_default_boot_version = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_E, + .game = SECURITY_MCODE_GAME_IIDX_9, + .region = SECURITY_MCODE_FIELD_BLANK, + .cabinet = SECURITY_MCODE_FIELD_BLANK, + .revision = SECURITY_MCODE_FIELD_BLANK, +}; + +/* IIDX 09 default */ +static const uint32_t iidxhook_security_default_boot_seeds[3] = {0, 0, 0}; + +/* IIDX 09 default */ +static const struct security_mcode iidxhook_security_default_black_plug_mcode = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_9, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static char* iidxhook_config_boot_seeds_to_str(const uint32_t* seeds) +{ + char* res; + size_t len; + + len = snprintf(NULL, 0, "%d:%d:%d", seeds[0], seeds[1], seeds[2]); + res = xmalloc(len + 1); + sprintf(res, "%d:%d:%d", seeds[0], seeds[1], seeds[2]); + + return res; +} + +static bool iidxhook_config_boot_seeds_parse(const char* str, uint32_t* seeds) +{ + if (strlen(str) < 5) { + return false; + } + + if (str[1] != ':' || str[3] != ':') { + return false; + } else { + seeds[0] = str[0] - '0'; + seeds[1] = str[2] - '0'; + seeds[2] = str[4] - '0'; + return true; + } +} + +void iidxhook_config_sec_init(struct cconfig* config) +{ + char* tmp; + + tmp = security_mcode_to_str( + &IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_VERSION_VALUE); + + cconfig_util_set_str(config, + IIDXHOOK_CONFIG_SEC_BOOT_VERSION_KEY, tmp, + "Security boot version (e.g. GEC02)."); + + free(tmp); + + tmp = iidxhook_config_boot_seeds_to_str( + IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_SEEDS_VALUE); + + cconfig_util_set_str(config, + IIDXHOOK_CONFIG_SEC_BOOT_SEEDS_KEY, tmp, + "Security boot seeds for ezusb, format: X:X:X where X is a number of " + "0-9 (e.g. 0:0:0)."); + + free(tmp); + + tmp = security_mcode_to_str( + &IIDXHOOK_CONFIG_SEC_DEFAULT_BLACK_PLUG_MCODE_VALUE); + + cconfig_util_set_str(config, + IIDXHOOK_CONFIG_SEC_BLACK_PLUG_MCODE_KEY, tmp, + "Security black plug mcode id string (e.g. GQC02JAA)."); + + free(tmp); +} + +void iidxhook_config_sec_get(struct iidxhook_config_sec* config_sec, + struct cconfig* config) +{ + char tmp_seeds[6]; + char tmp_mcode[sizeof(struct security_mcode) + 1]; + char* tmp_str; + + tmp_str = security_mcode_to_str( + &IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_VERSION_VALUE); + + if (!cconfig_util_get_str(config, IIDXHOOK_CONFIG_SEC_BOOT_VERSION_KEY, + tmp_mcode, sizeof(tmp_mcode) - 1, tmp_str)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_SEC_BOOT_VERSION_KEY, tmp_str); + } + + if (!security_mcode_parse(tmp_mcode, &config_sec->boot_version)) { + log_warning("Invalid format for value of key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_SEC_BOOT_VERSION_KEY, tmp_str); + memcpy(&config_sec->boot_version, + &IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_VERSION_VALUE, + sizeof(IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_VERSION_VALUE)); + } + + free(tmp_str); + + tmp_str = iidxhook_config_boot_seeds_to_str( + IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_SEEDS_VALUE); + + if (!cconfig_util_get_str(config, IIDXHOOK_CONFIG_SEC_BOOT_SEEDS_KEY, + tmp_seeds, sizeof(tmp_seeds) - 1, tmp_str)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_SEC_BOOT_SEEDS_KEY, tmp_str); + } + + if (!iidxhook_config_boot_seeds_parse(tmp_seeds, config_sec->boot_seeds)) { + log_warning("Invalid format for value of key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_SEC_BOOT_SEEDS_KEY, tmp_str); + memcpy(config_sec->boot_seeds, + IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_SEEDS_VALUE, + sizeof(IIDXHOOK_CONFIG_SEC_DEFAULT_BOOT_SEEDS_VALUE)); + } + + free(tmp_str); + + tmp_str = security_mcode_to_str( + &IIDXHOOK_CONFIG_SEC_DEFAULT_BLACK_PLUG_MCODE_VALUE); + + if (!cconfig_util_get_str(config, IIDXHOOK_CONFIG_SEC_BLACK_PLUG_MCODE_KEY, + tmp_mcode, sizeof(tmp_mcode) - 1, tmp_str)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_SEC_BLACK_PLUG_MCODE_KEY, + tmp_str); + } + + if (!security_mcode_parse(tmp_mcode, &config_sec->black_plug_mcode)) { + log_warning("Invalid format for value of key '%s' specified, fallback " + "to default '%s'", IIDXHOOK_CONFIG_SEC_BLACK_PLUG_MCODE_KEY, + tmp_str); + memcpy(&config_sec->black_plug_mcode, + &IIDXHOOK_CONFIG_SEC_DEFAULT_BLACK_PLUG_MCODE_VALUE, + sizeof(IIDXHOOK_CONFIG_SEC_DEFAULT_BLACK_PLUG_MCODE_VALUE)); + } + + free(tmp_str); +} diff --git a/src/main/iidxhook-util/config-sec.h b/src/main/iidxhook-util/config-sec.h new file mode 100644 index 0000000..0827ba3 --- /dev/null +++ b/src/main/iidxhook-util/config-sec.h @@ -0,0 +1,19 @@ +#ifndef IIDXHOOK_CONFIG_SEC_H +#define IIDXHOOK_CONFIG_SEC_H + +#include "cconfig/cconfig.h" + +#include "security/mcode.h" + +struct iidxhook_config_sec { + struct security_mcode boot_version; + uint32_t boot_seeds[3]; + struct security_mcode black_plug_mcode; +}; + +void iidxhook_config_sec_init(struct cconfig* config); + +void iidxhook_config_sec_get(struct iidxhook_config_sec* config_sec, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/iidxhook-util/d3d8.c b/src/main/iidxhook-util/d3d8.c new file mode 100644 index 0000000..bf252cc --- /dev/null +++ b/src/main/iidxhook-util/d3d8.c @@ -0,0 +1,600 @@ +#define LOG_MODULE "d3d8-hook" + +#include +#include + +#include +#include +#include +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "iidxhook-util/d3d8.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" +#include "util/time.h" + +/* ------------------------------------------------------------------------- */ + +struct d3d8_vertex { + float x; + float y; + float z; + uint32_t color; + uint32_t unknown; + float tu; + float tv; +}; + +/* ------------------------------------------------------------------------- */ + +static HWND STDCALL my_CreateWindowExA( + DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam); + +static HRESULT STDCALL my_CreateDevice( + IDirect3D8 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, + DWORD flags, D3DPRESENT_PARAMETERS *pp, IDirect3DDevice8 **pdev); + +static HRESULT STDCALL my_SetRenderState(IDirect3DDevice8* self, + D3DRENDERSTATETYPE State, DWORD Value); + +static HRESULT STDCALL my_DrawPrimitiveUP( + IDirect3DDevice8* self, D3DPRIMITIVETYPE primitive_type, + UINT primitive_count, const void *data, UINT stride); + +static HRESULT STDCALL my_Present(IDirect3DDevice8* self, + CONST RECT *pSourceRect, CONST RECT *pDestRect, + HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion); + +static IDirect3D8* STDCALL my_Direct3DCreate8(UINT sdk_ver); + +static void execute_monitor_check(IDirect3DDevice8Vtbl* api_vtbl, + IDirect3DDevice8* pdev); + +static void calc_win_size_with_framed(HWND hwnd, DWORD x, DWORD y, + DWORD width, DWORD height, LPWINDOWPOS wp); + +/* ------------------------------------------------------------------------- */ + +static bool gfx_windowed = false; +static int32_t gfx_window_width = -1; +static int32_t gfx_window_height = -1; +static bool gfx_window_framed = false; +static float gfx_frame_rate_limit = 0.0f; +static bool gfx_fix_stretched_bg_videos = false; +static bool gfx_fix_iidx_12_fog = false; +static bool gfx_fix_iidx_13_lighting = false; +static d3d8_monitor_check_result_callback_t gfx_monitor_check_cb = NULL; + +/* ------------------------------------------------------------------------- */ + +static IDirect3D8* (STDCALL *real_Direct3DCreate8)(UINT sdk_ver); +static HWND (STDCALL *real_CreateWindowExA)( + DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam); +static HRESULT (STDCALL* real_SetRenderState)(IDirect3DDevice8* self, + D3DRENDERSTATETYPE State, DWORD Value); +static HRESULT (STDCALL *real_DrawPrimitiveUP)( + IDirect3DDevice8* self, D3DPRIMITIVETYPE primitive_type, + UINT primitive_count, const void *data, UINT stride); +static HRESULT (STDCALL* real_Present)(IDirect3DDevice8* self, + CONST RECT *pSourceRect, CONST RECT *pDestRect, + HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion); + +static const struct hook_symbol d3d8_hook_syms[] = { + { + .name = "Direct3DCreate8", + .patch = my_Direct3DCreate8, + .link = (void **) &real_Direct3DCreate8, + }, +}; + +static const struct hook_symbol d3d8_hook_user32_syms[] = { + { + .name = "CreateWindowExA", + .patch = my_CreateWindowExA, + .link = (void **) &real_CreateWindowExA, + }, +}; + +/* ------------------------------------------------------------------------- */ + +static bool float_equal(float a, float b, float eps) +{ + return fabs(a - b) < eps; +} + +static HWND STDCALL my_CreateWindowExA( + DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam) +{ + if (gfx_windowed && gfx_window_framed) { + /* use a different style */ + dwStyle |= WS_OVERLAPPEDWINDOW; + /* also show mouse cursor */ + ShowCursor(TRUE); + } + + if (gfx_window_width != -1 && gfx_window_height != -1) { + log_misc("Overriding window size from %dx%d with %dx%d", nWidth, + nHeight, gfx_window_width, gfx_window_height); + + nWidth = gfx_window_width; + nHeight = gfx_window_height; + } + + HWND hwnd = real_CreateWindowExA(dwExStyle, lpClassName, lpWindowName, + dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, + lpParam); + + if (hwnd == INVALID_HANDLE_VALUE) { + return hwnd; + } + + if (gfx_windowed && gfx_window_framed) { + /* we have to adjust the window size, because the window needs to be a + slightly bigger than the rendering resolution (window caption and + stuff is included in the window size) */ + WINDOWPOS wp; + calc_win_size_with_framed(hwnd, X, Y, nWidth, nHeight, &wp); + SetWindowPos(hwnd, 0, wp.x, wp.y, wp.cx, wp.cy, 0); + X = wp.x; + Y = wp.y; + nWidth = wp.cx; + nHeight = wp.cy; + } + + return hwnd; +} + +static HRESULT STDCALL my_CreateDevice( + IDirect3D8 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, DWORD flags, + D3DPRESENT_PARAMETERS *pp, IDirect3DDevice8 **pdev) +{ + IDirect3D8 *real = COM_PROXY_UNWRAP(self); + HRESULT hr; + IDirect3DDevice8* api; + IDirect3DDevice8Vtbl *api_vtbl; + struct com_proxy *api_proxy; + char* error; + + log_info("Direct3D8 CreateDevice hook hit"); + + /* Fix a long-standing bug in IIDX */ + + if (flags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) { + flags &= ~D3DCREATE_PUREDEVICE; + } + + if (gfx_windowed) { + log_misc("Window mode"); + + pp->hDeviceWindow = 0; + pp->Windowed = TRUE; + pp->FullScreen_RefreshRateInHz = 0; + pp->FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + } + + /* If we don't do this, the game will crash on some versions of windows + on either fullscreen or windowed or even both. Also, further reports + about textures with green glowing borders are gone as well when applying + this */ + D3DDISPLAYMODE mode; + IDirect3D8_GetAdapterDisplayMode(real, adapter, &mode); + pp->BackBufferFormat = mode.Format; + + hr = IDirect3D8_CreateDevice(real, adapter, type, hwnd, flags, pp, pdev); + + if (hr != S_OK) { + switch (hr) { + case D3DERR_INVALIDCALL: + error = "invalid call"; + break; + + case D3DERR_NOTAVAILABLE: + error = "not available"; + break; + + case D3DERR_OUTOFVIDEOMEMORY: + error = "out of video memory"; + break; + + default: + error = "unknown"; + break; + } + + log_warning("Creating D3D8 device failed: %lX, %s", hr, error); + + return hr; + } + + api = *pdev; + api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl)); + api_vtbl = api_proxy->vptr; + + real_DrawPrimitiveUP = api_vtbl->DrawPrimitiveUP; + api_vtbl->DrawPrimitiveUP = my_DrawPrimitiveUP; + + real_SetRenderState = api_vtbl->SetRenderState; + api_vtbl->SetRenderState = my_SetRenderState; + + real_Present = api_vtbl->Present; + api_vtbl->Present = my_Present; + + *pdev = (IDirect3DDevice8*) api_proxy; + + if (gfx_monitor_check_cb) { + execute_monitor_check(api_vtbl, *pdev); + } + + return hr; +} + +static IDirect3D8 *STDCALL my_Direct3DCreate8(UINT sdk_ver) +{ + IDirect3D8 *api; + IDirect3D8Vtbl *api_vtbl; + struct com_proxy *api_proxy; + + log_info("Direct3DCreate8 hook hit"); + + api = real_Direct3DCreate8(sdk_ver); + api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl)); + api_vtbl = api_proxy->vptr; + + api_vtbl->CreateDevice = my_CreateDevice; + + return (IDirect3D8 *) api_proxy; +} + +static HRESULT STDCALL my_SetRenderState(IDirect3DDevice8* self, + D3DRENDERSTATETYPE State, DWORD Value) +{ + if (gfx_fix_iidx_12_fog) { + if (State == D3DRS_FOGENABLE) { + Value = FALSE; + } + } + + if (gfx_fix_iidx_13_lighting) { + if (State == D3DRS_LIGHTING) { + Value = FALSE; + } + } + + return real_SetRenderState(self, State, Value); +} + +static HRESULT STDCALL my_DrawPrimitiveUP( + IDirect3DDevice8* self, D3DPRIMITIVETYPE primitive_type, + UINT primitive_count, const void *data, UINT stride) +{ + /* trap the background video texture to fix the UVs for newer hardware + background videos are rendered to a 512x512 texture, but the videos + are 312x416 in size (on IIDX RED) + the old hardware supported this to render correctly + (VMs might work as well) with the UVs set in the code but on newer + hardware, the video frame gets stretched to the full 512x512 texture + The tex UVs are only set to a subsection of that texture thus just a + subsection is rendered (video appears to be stretched) + to fix this, we simply set the UVs to apply to the full texture + Quad for BG Video (triangle fan!): + 164/0 ------- 476/0 + | | + | | + | | + | | + | | + | | + 164/416 ------- 476/416 + Original UVs for the quad + 0/0,8125 ------- 0,609375/0,812500 + | | + | | + | | + | | + | | + | | + 0/0 ------- 0,609375/0 + */ + if ( gfx_fix_stretched_bg_videos && primitive_type == 6 && + primitive_count == 2 && stride == 28) { + + struct d3d8_vertex* vertices = (struct d3d8_vertex*) data; + + /* + log_info("Video Tex: %f/%f %f/%f %f/%f %f/%f", + vertices[0].x, vertices[0].y, + vertices[1].x, vertices[1].y, + vertices[2].x, vertices[2].y, + vertices[3].x, vertices[3].y); + */ + + /* Fix full screen background videos (e.g. DistorteD intro sequence) */ + if ( vertices[0].x >= 0.0f && vertices[0].x < 1.0f && + vertices[0].y >= 0.0f && vertices[0].y < 1.0f && + vertices[1].x > 639.0f && vertices[1].x < 641.0f && + vertices[1].y >= 0.0f && vertices[1].y < 1.0f && + vertices[2].x > 639.0f && vertices[2].x < 641.0f && + vertices[2].y > 479.0f && vertices[2].y < 481.0f && + vertices[3].x >= 0.0f && vertices[3].x < 1.0f && + vertices[3].y > 479.0f && vertices[3].y < 481.0f) { + /* fix UVs + 1.0f / 640 or 480 fixes the diagonal seam connecting the two + triangles which is visible on some GPUs (why? idk) + */ + vertices[0].tu = 0.0f + 1.0f / 640; + vertices[0].tv = 1.0f; + vertices[1].tu = 1.0f; + vertices[1].tv = 1.0f; + vertices[2].tu = 1.0f; + vertices[2].tv = 0.0f + 1.0f / 480; + vertices[3].tu = 0.0f + 1.0f / 640; + vertices[3].tv = 0.0f + 1.0f / 480; + } else + + /* another identifier, because there are other textures with 512x512 size + make sure we got the bg video only to not mess up anything else */ + /* different versions have different themes and position the bg video + on slightly different positions (good job...) */ + if ( /* single */ + ((vertices[0].x >= 164.0f && vertices[0].x <= 168.0f && + float_equal(vertices[0].y, 0.0f, 0.1f)) && + (vertices[1].x >= 472.0f && vertices[1].x <= 476.0f && + float_equal(vertices[1].y, 0.0f, 0.1f)) && + (vertices[2].x >= 472.0f && vertices[2].x <= 476.0f && + float_equal(vertices[2].y, 416.0f, 0.1f)) && + (vertices[3].x >= 164.0f && vertices[3].x <= 168.0f && + float_equal(vertices[3].y, 416.0f, 0.1f))) || + /* double top left */ + ((float_equal(vertices[0].x, 6.0f, 0.1f) && + vertices[0].y >= 24.0f && vertices[0].y <= 28.0f) && + (float_equal(vertices[1].x, 147.0f, 0.1f) && + vertices[1].y >= 24.0f && vertices[1].y <= 28.0f) && + (float_equal(vertices[2].x, 147.0f, 0.1f) && + vertices[2].y >= 212.0f && vertices[2].y <= 216.0f) && + (float_equal(vertices[3].x, 6.0f, 0.1f) && + vertices[3].y >= 212.0f && vertices[3].y <= 216.0f)) || + /* double bottom left */ + ((float_equal(vertices[0].x, 6.0f, 0.1f) && + vertices[0].y >= 216.0f && vertices[0].y <= 220.0f) && + (float_equal(vertices[1].x, 147.0f, 0.1f) && + vertices[1].y >= 216.0f && vertices[1].y <= 220.0f) && + (float_equal(vertices[2].x, 147.0f, 0.1) && + vertices[2].y >= 404.0f && vertices[2].y <= 408.0f) && + (float_equal(vertices[3].x, 6.0f, 0.1f) && + vertices[3].y >= 404.0f && vertices[3].y <= 408.0f)) || + /* double top right */ + ((vertices[0].x >= 493.0f && vertices[0].x <= 494.0f && + vertices[0].y >= 24.0f && vertices[0].y <= 28.0f) && + (vertices[1].x >= 634.0f && vertices[1].x <= 635.0f && + vertices[1].y >= 24.0f && vertices[1].y <= 28.0f) && + (vertices[2].x >= 634.0f && vertices[2].x <= 635.0f && + vertices[2].y >= 212.0f && vertices[2].y <= 216.0f) && + (vertices[3].x >= 493.0f && vertices[3].x <= 494.0f && + vertices[3].y >= 212.0f && vertices[3].y <= 216.0f)) || + /* double bottom right */ + ((vertices[0].x >= 493.0f && vertices[0].x <= 494.0f && + vertices[0].y >= 216.0f && vertices[0].y <= 220.0f) && + (vertices[1].x >= 634.0f && vertices[1].x <= 635.0f && + vertices[1].y >= 216.0f && vertices[1].y <= 220.0f) && + (vertices[2].x >= 634.0f && vertices[2].x <= 635.0f && + vertices[2].y >= 404.0f && vertices[2].y <= 408.0f) && + (vertices[3].x >= 493.0f && vertices[3].x <= 494.0f && + vertices[3].y >= 404.0f && vertices[3].y <= 408.0f))) + { + /* fix UVs + 1.0f / 512 fixes the diagonal seam connecting the two triangles + which is visible on some GPUs (why? idk) + */ + vertices[0].tu = 0.0f + 1.0f / 512; + vertices[0].tv = 1.0f; + vertices[1].tu = 1.0f; + vertices[1].tv = 1.0f; + vertices[2].tu = 1.0f; + vertices[2].tv = 0.0f + 1.0f / 512; + vertices[3].tu = 0.0f + 1.0f / 512; + vertices[3].tv = 0.0f + 1.0f / 512; + } + + } + + return real_DrawPrimitiveUP(self, primitive_type, primitive_count, + data, stride); +} + +static HRESULT STDCALL my_Present(IDirect3DDevice8* self, + CONST RECT *pSourceRect, CONST RECT *pDestRect, + HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion) +{ + static uint64_t current_time = 0; + + HRESULT hr = real_Present(self, pSourceRect, pDestRect, hDestWindowOverride, + pDirtyRegion); + + if (gfx_frame_rate_limit > 0.0f) { + if (current_time == 0) { + current_time = time_get_counter(); + } else { + uint64_t frame_time = 1000000 / gfx_frame_rate_limit; + + uint64_t dt = time_get_elapsed_us( + time_get_counter() - current_time); + + while (dt < frame_time) { + /* waste some cpu time by polling + because we can't sleep for X us */ + dt = time_get_elapsed_us(time_get_counter() - current_time); + } + + current_time = time_get_counter(); + } + } + + return hr; +} + +/* ------------------------------------------------------------------------- */ + +void d3d8_hook_init(void) +{ + hook_table_apply( + NULL, + "d3d8.dll", + d3d8_hook_syms, + lengthof(d3d8_hook_syms)); + + hook_table_apply( + NULL, + "user32.dll", + d3d8_hook_user32_syms, + lengthof(d3d8_hook_user32_syms)); + + log_info("Inserted d3d8 graphics hooks"); +} + +void d3d8_set_windowed(bool framed, int32_t width, int32_t height) +{ + gfx_windowed = true; + gfx_window_framed = framed; + gfx_window_width = width; + gfx_window_height = height; +} + +void d3d8_set_frame_rate_limit(float limit) +{ + if (limit < 0.0f) { + limit = 0.0f; + } + + log_info("Limiting rendering frame rate to %f frames", limit); + gfx_frame_rate_limit = limit; +} + +void d3d8_enable_monitor_check(d3d8_monitor_check_result_callback_t cb) +{ + log_assert(cb); + + gfx_monitor_check_cb = cb; + log_info("Enabled monitor check"); +} + +void d3d8_iidx_fix_stretched_bg_videos(void) +{ + gfx_fix_stretched_bg_videos = true; + log_info("Fixing UVs of stretched BG videos"); +} + +void d3d8_iidx_fix_12_song_select_bg(void) +{ + gfx_fix_iidx_12_fog = true; + log_info("Fixing Happy Sky bugged music select 3D background"); +} + +void d3d8_iidx_fix_13_song_select_bg(void) +{ + gfx_fix_iidx_13_lighting = true; + log_info("Fixing DistorteD music select 3D background"); +} + +/* ------------------------------------------------------------------------- */ + +static void execute_monitor_check(IDirect3DDevice8Vtbl* api_vtbl, + IDirect3DDevice8* pdev) +{ + log_info("Running monitor check..."); + + /* d3d8 does not provide anything like d3d9 with DrawText */ + + const uint32_t max_iterations = 60 * 30; + const uint32_t skip_frames = 60 * 1; + + uint64_t accu_us = 0; + + double result = 0; + uint32_t iterations = 0; + + while (iterations < max_iterations) { + iterations++; + uint64_t start = time_get_counter(); + + api_vtbl->Clear(pdev, 0, NULL, D3DCLEAR_TARGET, + D3DCOLOR_XRGB(0xFF, 0xFF, 0xFF), 0.0f, 0); + api_vtbl->BeginScene(pdev); + + api_vtbl->EndScene(pdev); + api_vtbl->Present(pdev, NULL, NULL, NULL, NULL); + + /* Skip the first inaccurate values */ + if (iterations > skip_frames) { + accu_us += time_get_elapsed_us(time_get_counter() - start); + + result = ((double) (iterations - skip_frames)) / + (accu_us / 1000.0 / 1000.0); + } + } + + log_info("Monitor check done (total iterations %d), refesh rate: %f hz", + iterations, result); + + /* Sanity check to ensure people notice that their current refresh rate + is way off. */ + if (result < 55 || result > 65) { + log_warning("Monitor check result (%f hz) is not even near the " + "intended refresh rate of 60 hz. Fix your setup to ensure a " + "constant and as close to as possible 60 hz refresh rate.", result); + } + + log_assert(gfx_monitor_check_cb); + + gfx_monitor_check_cb(result); +} + +static void calc_win_size_with_framed(HWND hwnd, DWORD x, DWORD y, DWORD width, + DWORD height, LPWINDOWPOS wp) +{ + /* taken from dxwnd */ + RECT rect; + DWORD style; + int max_x, max_y; + HMENU menu; + + rect.left = x; + rect.top = y; + max_x = width; + max_y = height; + rect.right = x + max_x; + rect.bottom = y + max_y; + + style = GetWindowLong(hwnd, GWL_STYLE); + menu = GetMenu(hwnd); + AdjustWindowRect(&rect, style, (menu != NULL)); + + /* shift down-right so that the border is visible + and also update the iPosX,iPosY upper-left coordinates + of the client area */ + + if (rect.left < 0) { + rect.right -= rect.left; + rect.left = 0; + } + + if (rect.top < 0) { + rect.bottom -= rect.top; + rect.top = 0; + } + + wp->x = rect.left; + wp->y = rect.top; + wp->cx = rect.right - rect.left; + wp->cy = rect.bottom - rect.top; +} diff --git a/src/main/iidxhook-util/d3d8.h b/src/main/iidxhook-util/d3d8.h new file mode 100644 index 0000000..79200b6 --- /dev/null +++ b/src/main/iidxhook-util/d3d8.h @@ -0,0 +1,73 @@ +#ifndef IIDXHOOK_D3D8_H +#define IIDXHOOK_D3D8_H + +#include + +/** + * Callback function to receive resulting refresh rate of monitor check. + */ +typedef void (*d3d8_monitor_check_result_callback_t)(double refresh_rate); + +/** + * Hook some d3d8 related functions to patch gfx related stuff + * like enabling window mode. + * + * Games using d3d8: + * - IIDX: 9th to DD + */ +void d3d8_hook_init(void); + +/** + * Set the game to window mode. + * + * @param framed True to add a window frame and make the window + * movable, resizable, minizable. False for no frame. + * @param width Width of the window. -1 to keep original width. + * @param height Height of the window. -1 to keep original height. + */ +void d3d8_set_windowed(bool framed, int32_t width, int32_t height); + +/** + * Set a framerate limit for the rendering loop. + * + * Use this if the game won't sync up properly with vsync enabled. + * + * @limit Number of frames to limit the rendering loop to + */ +void d3d8_set_frame_rate_limit(float limit); + +/** + * Enable a software a "monitor check" which determines the (avg.) refresh rate + * of the game. Ensure that your GPU is outputting a stable refresh rate to + * your monitor. Otherwise, the resulting values fluctuate a lot + * + * @param cb Callback to a function that receives the result of the monitor + * check once it completed. + */ +void d3d8_enable_monitor_check(d3d8_monitor_check_result_callback_t cb); + +/** + * Fixes UV coordinates for background videos on + * IIDX Red to DD. Use this if you experience the game + * stretching the background videos + * (see implementation for further details) + */ +void d3d8_iidx_fix_stretched_bg_videos(void); + +/** + * Fixes the 3D background displayed during song select by disabling fog. + * On some machines/GPUs the background is rendered completely white which + * is caused by buggy fog rendering. Fog is used to hide the rings appearing + * but that's quite 'far away' so disabling fog doesn't look bad. + */ +void d3d8_iidx_fix_12_song_select_bg(void); + +/** + * Fixes the 3D background displayed during song select by disabling lighting. + * On some machines/GPUs the background is rendered completely black which + * is caused by buggy lighting. Disabling (dynamic) lighting sets the whole + * scene to ambient lighting which displays everything properly. + */ +void d3d8_iidx_fix_13_song_select_bg(void); + +#endif diff --git a/src/main/iidxhook-util/d3d9.c b/src/main/iidxhook-util/d3d9.c new file mode 100644 index 0000000..efea60c --- /dev/null +++ b/src/main/iidxhook-util/d3d9.c @@ -0,0 +1,907 @@ +#define LOG_MODULE "d3d9-hook" + +#include +#include +#include + +#include +#include +#include +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "iidxhook-util/d3d9.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" +#include "util/time.h" + +/* ------------------------------------------------------------------------- */ + +struct d3d8_vertex { + float x; + float y; + float z; + uint32_t color; + uint32_t unknown; + float tu; + float tv; +}; + +/* ------------------------------------------------------------------------- */ + +/* Device IDs recognised by IIDX <20. Hex chars must be capital letters. + + 7146: RV515 [Radeon X1300] + 95C5: RV620 LE [Radeon HD 3450] + + VEN is always 1002 (ATi) */ + +static HWND STDCALL my_CreateWindowExA( + DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam); + +static BOOL STDCALL my_GetClientRect(HWND hWnd, LPRECT lpRect); + +static HRESULT STDCALL my_CreateDevice( + IDirect3D9 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, + DWORD flags, D3DPRESENT_PARAMETERS *pp, IDirect3DDevice9 **pdev); + +static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver); + +static BOOL STDCALL my_EnumDisplayDevicesA( + const char *dev_name, DWORD dev_num, DISPLAY_DEVICEA *info, + DWORD flags); + +static HRESULT STDCALL my_CreateTexture(IDirect3DDevice9* self, UINT Width, + UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, + IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle); + +static HRESULT STDCALL my_SetRenderState(IDirect3DDevice9* self, + D3DRENDERSTATETYPE State, DWORD Value); + +static HRESULT STDCALL my_DrawPrimitiveUP( + IDirect3DDevice9* self, D3DPRIMITIVETYPE primitive_type, + UINT primitive_count, const void *data, UINT stride); + +static HRESULT STDCALL my_Present(IDirect3DDevice9* self, + CONST RECT *pSourceRect, CONST RECT *pDestRect, + HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion); + +static HRESULT STDCALL my_BeginScene(IDirect3DDevice9* self); + +static HRESULT STDCALL my_EndScene(IDirect3DDevice9* self); + +static void calc_win_size_with_framed(HWND hwnd, DWORD x, DWORD y, + DWORD width, DWORD height, LPWINDOWPOS wp); + +static HWND (STDCALL *real_CreateWindowExA)( + DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam); + +static BOOL (STDCALL* real_GetClientRect)(HWND hWnd, LPRECT lpRect); + +static IDirect3D9 * (STDCALL *real_Direct3DCreate9)( + UINT sdk_ver); + +static BOOL (STDCALL *real_EnumDisplayDevicesA)( + const char *dev_name, DWORD dev_num, DISPLAY_DEVICEA *info, + DWORD flags); + +static HRESULT (STDCALL *real_CreateTexture)(IDirect3DDevice9* self, UINT Width, + UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, + IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle); + +static HRESULT (STDCALL* real_SetRenderState)(IDirect3DDevice9* self, + D3DRENDERSTATETYPE State, DWORD Value); + +static HRESULT (STDCALL *real_DrawPrimitiveUP)( + IDirect3DDevice9* self, D3DPRIMITIVETYPE primitive_type, + UINT primitive_count, const void *data, UINT stride); + +static HRESULT (STDCALL* real_Present)(IDirect3DDevice9* self, + CONST RECT *pSourceRect, CONST RECT *pDestRect, + HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion); + +static HRESULT (STDCALL* real_BeginScene)(IDirect3DDevice9* self); + +static HRESULT (STDCALL* real_EndScene)(IDirect3DDevice9* self); + +static HRESULT (STDCALL* real_GetRenderTarget)(IDirect3DDevice9* self, DWORD RenderTargetIndex, + IDirect3DSurface9 **ppRenderTarget); + +static HRESULT (STDCALL* real_SetRenderTarget)(IDirect3DDevice9* self, DWORD RenderTargetIndex, + IDirect3DSurface9 *pRenderTarget); + +static HRESULT (STDCALL* real_StretchRect)(IDirect3DDevice9* self, IDirect3DSurface9 *pSourceSurface, + const RECT *pSourceRect, IDirect3DSurface9 *pDestSurface, const RECT *pDestRect, D3DTEXTUREFILTERTYPE Filter); + +typedef HRESULT WINAPI (*func_D3DXCreateFontA)(struct IDirect3DDevice9 *device, + INT height, UINT width, UINT weight, UINT miplevels, BOOL italic, + DWORD charset, DWORD precision, DWORD quality, DWORD pitchandfamily, + const char *facename, struct ID3DXFont **font); + +/* ------------------------------------------------------------------------- */ + +static char d3d9_pci_id[32]; +static bool d3d9_windowed; +static int32_t d3d9_window_width = -1; +static int32_t d3d9_window_height = -1; +static bool d3d9_window_framed; +static d3d9_monitor_check_result_callback_t d3d9_monitor_check_cb; +static bool d3d9_fix_iidx_12_fog = false; +static bool d3d9_fix_iidx_13_lighting = false; +static bool d3d9_nvidia_fix; +static bool d3d9_bg_video_fix; +static float d3d9_frame_rate_limit = 0.0f; +static uint16_t d3d9_scale_back_buffer_width; +static uint16_t d3d9_scale_back_buffer_height; + +static D3DTEXTUREFILTERTYPE d3d9_scale_back_buffer_filter; +static IDirect3DSurface9* d3d9_original_back_buffer; +static IDirect3DSurface9* d3d9_scale_intermediate_render_target; +static uint16_t d3d9_original_back_buffer_width; +static uint16_t d3d9_original_back_buffer_height; + +static void execute_monitor_check(IDirect3DDevice9* api, + IDirect3DDevice9Vtbl* api_vtbl, IDirect3DDevice9* pdev); + +/* ------------------------------------------------------------------------- */ + +static const struct hook_symbol d3d9_hook_syms[] = { + { + .name = "Direct3DCreate9", + .patch = my_Direct3DCreate9, + .link = (void **) &real_Direct3DCreate9 + }, +}; + +static const struct hook_symbol d3d9_hook_user32_syms[] = { + { + .name = "EnumDisplayDevicesA", + .patch = my_EnumDisplayDevicesA, + .link = (void **) &real_EnumDisplayDevicesA + }, + { + .name = "CreateWindowExA", + .patch = my_CreateWindowExA, + .link = (void **) &real_CreateWindowExA + }, + { + .name = "GetClientRect", + .patch = my_GetClientRect, + .link = (void **) &real_GetClientRect + }, +}; + +/* ------------------------------------------------------------------------- */ + +static bool float_equal(float a, float b, float eps) +{ + return fabs(a - b) < eps; +} + +static HWND STDCALL my_CreateWindowExA( + DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, + int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, + HINSTANCE hInstance, LPVOID lpParam) +{ + if (d3d9_windowed && d3d9_window_framed) { + /* use a different style */ + dwStyle |= WS_OVERLAPPEDWINDOW; + /* also show mouse cursor */ + ShowCursor(TRUE); + } + + if (d3d9_window_width != -1 && d3d9_window_height != -1) { + log_misc("Overriding window size from %dx%d with %dx%d", nWidth, + nHeight, d3d9_window_width, d3d9_window_height); + + nWidth = d3d9_window_width; + nHeight = d3d9_window_height; + } + + HWND hwnd = real_CreateWindowExA(dwExStyle, lpClassName, lpWindowName, + dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, + lpParam); + + if (hwnd == INVALID_HANDLE_VALUE) { + return hwnd; + } + + if (d3d9_windowed && d3d9_window_framed) { + /* we have to adjust the window size, because the window needs to be a + slightly bigger than the rendering resolution (window caption and + stuff is included in the window size) */ + WINDOWPOS wp; + calc_win_size_with_framed(hwnd, X, Y, nWidth, nHeight, &wp); + SetWindowPos(hwnd, 0, wp.x, wp.y, wp.cx, wp.cy, 0); + X = wp.x; + Y = wp.y; + nWidth = wp.cx; + nHeight = wp.cy; + } + + return hwnd; +} + +static BOOL STDCALL my_GetClientRect(HWND hWnd, LPRECT lpRect) +{ + /* IIDX 9-13 (at least) use this call to get the size of rectangle for setting the viewport size... */ + + /* Always use the original requested back buffer size. If scaling is active, using the scaled + values leads to (3D) scenes getting render to a viewport with incorrect sized. */ + lpRect->left = 0; + lpRect->top = 0; + lpRect->right = d3d9_original_back_buffer_width; + lpRect->bottom = d3d9_original_back_buffer_height; + + return TRUE; +} + +static HRESULT STDCALL my_CreateDevice( + IDirect3D9 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, DWORD flags, + D3DPRESENT_PARAMETERS *pp, IDirect3DDevice9 **pdev) +{ + IDirect3D9 *real = COM_PROXY_UNWRAP(self); + IDirect3DDevice9* api; + IDirect3DDevice9Vtbl *api_vtbl; + struct com_proxy *api_proxy; + HRESULT hr; + char* error; + + log_misc("CreateDevice parameters: adapter %d, type %d, hwnd %p, flags %lX, pdev %p", + adapter, type, hwnd, flags, pdev); + + log_misc("D3D9 presenter parameters: BackBufferWidth %d, BackBufferHeight %d, BackBufferFormat %d, " + "BackBufferCount %d, MultiSampleType %d, SwapEffect %d, hDeviceWindow %p, Windowed %d, EnableAutoDepthStencil " + "%d, AutoDepthStencilFormat %d, Flags %lX, FullScreen_RefreshRateInHz %d", + pp->BackBufferWidth, pp->BackBufferHeight, pp->BackBufferFormat, pp->BackBufferCount, pp->MultiSampleType, + pp->SwapEffect, pp->hDeviceWindow, pp->Windowed, pp->EnableAutoDepthStencil, pp->AutoDepthStencilFormat, + pp->Flags, pp->FullScreen_RefreshRateInHz); + + /* Fix a long-standing bug in IIDX */ + if (flags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) { + flags &= ~D3DCREATE_PUREDEVICE; + } + + if (d3d9_windowed) { + log_misc("Window mode"); + + pp->Windowed = TRUE; + pp->FullScreen_RefreshRateInHz = 0; + } + + /* Store these so we can apply scaling further down */ + d3d9_original_back_buffer_width = pp->BackBufferWidth; + d3d9_original_back_buffer_height = pp->BackBufferHeight; + + if (d3d9_scale_back_buffer_width > 0 && d3d9_scale_back_buffer_height > 0) { + log_misc("Scale back buffer from %dx%d -> %dx%d", d3d9_original_back_buffer_width, + d3d9_original_back_buffer_height, d3d9_scale_back_buffer_width, d3d9_scale_back_buffer_height); + + pp->BackBufferWidth = d3d9_scale_back_buffer_width; + pp->BackBufferHeight = d3d9_scale_back_buffer_height; + } + + /* Same fix as on D3D8, orz... + If we don't do this, some games one certain platforms (e.g. iidx 14/15 on Windows 10). + CreateDevice fails with an "invalid call" on either fullscreen or windowed or even both. + Also, further reports about textures with green glowing borders are gone as well when applying this */ + D3DDISPLAYMODE mode; + IDirect3D9_GetAdapterDisplayMode(real, adapter, &mode); + pp->BackBufferFormat = mode.Format; + + hr = IDirect3D9_CreateDevice(real, adapter, type, hwnd, flags, pp, pdev); + + if (hr != S_OK) { + switch (hr) { + case D3DERR_DEVICELOST: + error = "device lost"; + break; + + case D3DERR_INVALIDCALL: + error = "invalid call"; + break; + + case D3DERR_NOTAVAILABLE: + error = "not available"; + break; + + case D3DERR_OUTOFVIDEOMEMORY: + error = "out of video memory"; + break; + + default: + error = "unknown"; + break; + } + + log_warning("Creating D3D9 device failed: %lX, %s", hr, error); + + return hr; + } + + api = *pdev; + api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl)); + api_vtbl = api_proxy->vptr; + + real_CreateTexture = api_vtbl->CreateTexture; + api_vtbl->CreateTexture = my_CreateTexture; + + real_DrawPrimitiveUP = api_vtbl->DrawPrimitiveUP; + api_vtbl->DrawPrimitiveUP = my_DrawPrimitiveUP; + + real_SetRenderState = api_vtbl->SetRenderState; + api_vtbl->SetRenderState = my_SetRenderState; + + real_Present = api_vtbl->Present; + api_vtbl->Present = my_Present; + + real_BeginScene = api_vtbl->BeginScene; + api_vtbl->BeginScene = my_BeginScene; + + real_EndScene = api_vtbl->EndScene; + api_vtbl->EndScene = my_EndScene; + + real_SetRenderTarget = api_vtbl->SetRenderTarget; + real_GetRenderTarget = api_vtbl->GetRenderTarget; + real_StretchRect = api_vtbl->StretchRect; + + *pdev = (IDirect3DDevice9*) api_proxy; + + if (d3d9_scale_back_buffer_width > 0 && d3d9_scale_back_buffer_height > 0) { + hr = api_vtbl->CreateRenderTarget(*pdev, d3d9_scale_back_buffer_width, d3d9_scale_back_buffer_height, + pp->BackBufferFormat, pp->MultiSampleType, 0, false, &d3d9_scale_intermediate_render_target, NULL); + + if (hr != D3D_OK) { + log_warning("Creating intermediate render target for scaling back buffer failed: %ld", hr); + return hr; + } + } + + if (d3d9_monitor_check_cb) { + execute_monitor_check(api, api_vtbl, *pdev); + } + + /* Avoid returning our scaled values because the application might use them, e.g. calculate + sprite positions. */ + pp->BackBufferWidth = d3d9_original_back_buffer_width; + pp->BackBufferHeight = d3d9_original_back_buffer_height; + + return hr; +} + +static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver) +{ + IDirect3D9 *api; + IDirect3D9Vtbl *api_vtbl; + struct com_proxy *api_proxy; + + log_info("Direct3DCreate9 hook hit"); + + api = real_Direct3DCreate9(sdk_ver); + api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl)); + api_vtbl = api_proxy->vptr; + + api_vtbl->CreateDevice = my_CreateDevice; + + return (IDirect3D9 *) api_proxy; +} + +static BOOL STDCALL my_EnumDisplayDevicesA( + const char *dev_name, DWORD dev_num, DISPLAY_DEVICEA *info, + DWORD flags) +{ + BOOL ok; + + ok = real_EnumDisplayDevicesA(dev_name, dev_num, info, flags); + + if (ok && d3d9_pci_id[0] != '\0') { + /* Apparently Konami didn't read the "Not Used" message in the MSDN + docs for DISPLAY_DEVICE */ + log_misc("Replacing device ID %s with %s", + info->DeviceID, d3d9_pci_id); + + str_cpy(info->DeviceID, sizeof(info->DeviceID), d3d9_pci_id); + } + + return ok; +} + +static HRESULT STDCALL my_CreateTexture(IDirect3DDevice9* self, UINT Width, + UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, + IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle) +{ + /* Fix non ATI cards not working on Gold (and DJT?). There is a check that + creates textures when starting the game. This check fails which results + in a message box being shown mentioning something about an aep-lib error. + Seems like a combination of parameters is not supported by + non ATI cards and throws an error + (Fix taken from old Gold cracks by ahnada and tau) + */ + if (d3d9_nvidia_fix && Width == 256 && Height == 256 && Levels == 1 && + Usage == 1 && Format == 25 && Pool == 0 && pSharedHandle == 0) { + /* log_misc("CreateTexture: Patching texture format..."); */ + Format = 21; + } + + return real_CreateTexture(self, Width, Height, Levels, Usage, Format, Pool, + ppTexture, pSharedHandle); +} + +static HRESULT STDCALL my_SetRenderState(IDirect3DDevice9* self, + D3DRENDERSTATETYPE State, DWORD Value) +{ + if (d3d9_fix_iidx_12_fog) { + if (State == D3DRS_FOGENABLE) { + Value = FALSE; + } + } + + if (d3d9_fix_iidx_13_lighting) { + if (State == D3DRS_LIGHTING) { + Value = FALSE; + } + } + + return real_SetRenderState(self, State, Value); +} + +static HRESULT STDCALL my_DrawPrimitiveUP( + IDirect3DDevice9* self, D3DPRIMITIVETYPE primitive_type, + UINT primitive_count, const void *data, UINT stride) +{ + /* same code taken from the d3d8 module. but, this just fixes the quad + seam issue as there are no reports of streched bg videos */ + if ( d3d9_bg_video_fix && primitive_type == 6 && primitive_count == 2 && + stride == 28) { + + struct d3d8_vertex* vertices = (struct d3d8_vertex*) data; + + /* + log_info("Video Tex: %f/%f %f/%f %f/%f %f/%f", + vertices[0].x, vertices[0].y, + vertices[1].x, vertices[1].y, + vertices[2].x, vertices[2].y, + vertices[3].x, vertices[3].y); + */ + + /* Fix full screen background videos (e.g. DistorteD intro sequence) */ + if ( vertices[0].x >= 0.0f && vertices[0].x < 1.0f && + vertices[0].y >= 0.0f && vertices[0].y < 1.0f && + vertices[1].x > 639.0f && vertices[1].x < 641.0f && + vertices[1].y >= 0.0f && vertices[1].y < 1.0f && + vertices[2].x > 639.0f && vertices[2].x < 641.0f && + vertices[2].y > 479.0f && vertices[2].y < 481.0f && + vertices[3].x >= 0.0f && vertices[3].x < 1.0f && + vertices[3].y > 479.0f && vertices[3].y < 481.0f) { + /* fix UVs + 1.0f / 640 or 480 fixes the diagonal seam connecting the two + triangles which is visible on some GPUs (why? idk) + */ + vertices[0].tu = 0.0f + 1.0f / 640; + vertices[0].tv = 1.0f; + vertices[1].tu = 1.0f; + vertices[1].tv = 1.0f; + vertices[2].tu = 1.0f; + vertices[2].tv = 0.0f + 1.0f / 480; + vertices[3].tu = 0.0f + 1.0f / 640; + vertices[3].tv = 0.0f + 1.0f / 480; + } else + + /* another identifier, because there are other textures with 512x512 size + make sure we got the bg video only to not mess up anything else */ + /* different versions have different themes and position the bg video + on slightly different positions (good job...) */ + if ( /* single */ + ((vertices[0].x >= 164.0f && vertices[0].x <= 168.0f && + float_equal(vertices[0].y, 0.0f, 0.1f)) && + (vertices[1].x >= 472.0f && vertices[1].x <= 476.0f && + float_equal(vertices[1].y, 0.0f, 0.1f)) && + (vertices[2].x >= 472.0f && vertices[2].x <= 476.0f && + float_equal(vertices[2].y, 416.0f, 0.1f)) && + (vertices[3].x >= 164.0f && vertices[3].x <= 168.0f && + float_equal(vertices[3].y, 416.0f, 0.1f))) || + /* double top left */ + ((float_equal(vertices[0].x, 6.0f, 0.1f) && + vertices[0].y >= 24.0f && vertices[0].y <= 28.0f) && + (float_equal(vertices[1].x, 147.0f, 0.1f) && + vertices[1].y >= 24.0f && vertices[1].y <= 28.0f) && + (float_equal(vertices[2].x, 147.0f, 0.1f) && + vertices[2].y >= 212.0f && vertices[2].y <= 216.0f) && + (float_equal(vertices[3].x, 6.0f, 0.1f) && + vertices[3].y >= 212.0f && vertices[3].y <= 216.0f)) || + /* double bottom left */ + ((float_equal(vertices[0].x, 6.0f, 0.1f) && + vertices[0].y >= 216.0f && vertices[0].y <= 220.0f) && + (float_equal(vertices[1].x, 147.0f, 0.1f) && + vertices[1].y >= 216.0f && vertices[1].y <= 220.0f) && + (float_equal(vertices[2].x, 147.0f, 0.1) && + vertices[2].y >= 404.0f && vertices[2].y <= 408.0f) && + (float_equal(vertices[3].x, 6.0f, 0.1f) && + vertices[3].y >= 404.0f && vertices[3].y <= 408.0f)) || + /* double top right */ + ((vertices[0].x >= 493.0f && vertices[0].x <= 494.0f && + vertices[0].y >= 24.0f && vertices[0].y <= 28.0f) && + (vertices[1].x >= 634.0f && vertices[1].x <= 635.0f && + vertices[1].y >= 24.0f && vertices[1].y <= 28.0f) && + (vertices[2].x >= 634.0f && vertices[2].x <= 635.0f && + vertices[2].y >= 212.0f && vertices[2].y <= 216.0f) && + (vertices[3].x >= 493.0f && vertices[3].x <= 494.0f && + vertices[3].y >= 212.0f && vertices[3].y <= 216.0f)) || + /* double bottom right */ + ((vertices[0].x >= 493.0f && vertices[0].x <= 494.0f && + vertices[0].y >= 216.0f && vertices[0].y <= 220.0f) && + (vertices[1].x >= 634.0f && vertices[1].x <= 635.0f && + vertices[1].y >= 216.0f && vertices[1].y <= 220.0f) && + (vertices[2].x >= 634.0f && vertices[2].x <= 635.0f && + vertices[2].y >= 404.0f && vertices[2].y <= 408.0f) && + (vertices[3].x >= 493.0f && vertices[3].x <= 494.0f && + vertices[3].y >= 404.0f && vertices[3].y <= 408.0f))) + { + /* fix UVs + 1.0f / 512 fixes the diagonal seam connecting the two triangles + which is visible on some GPUs (why? idk) + */ + vertices[0].tu = 0.0f + 1.0f / 512; + vertices[0].tv = 1.0f; + vertices[1].tu = 1.0f; + vertices[1].tv = 1.0f; + vertices[2].tu = 1.0f; + vertices[2].tv = 0.0f + 1.0f / 512; + vertices[3].tu = 0.0f + 1.0f / 512; + vertices[3].tv = 0.0f + 1.0f / 512; + } + + } + + return real_DrawPrimitiveUP(self, primitive_type, primitive_count, + data, stride); +} + +static HRESULT STDCALL my_Present(IDirect3DDevice9* self, + CONST RECT *pSourceRect, CONST RECT *pDestRect, + HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion) +{ + static uint64_t current_time = 0; + + HRESULT hr = real_Present(self, pSourceRect, pDestRect, hDestWindowOverride, + pDirtyRegion); + + if (d3d9_frame_rate_limit > 0.0f) { + if (current_time == 0) { + current_time = time_get_counter(); + } else { + uint64_t frame_time = 1000000 / d3d9_frame_rate_limit; + + uint64_t dt = time_get_elapsed_us( + time_get_counter() - current_time); + + while (dt < frame_time) { + /* waste some cpu time by polling + because we can't sleep for X us */ + dt = time_get_elapsed_us(time_get_counter() - current_time); + } + + current_time = time_get_counter(); + } + } + + return hr; +} + +static HRESULT STDCALL my_BeginScene(IDirect3DDevice9* self) +{ + HRESULT res; + + if (d3d9_scale_back_buffer_width > 0 && d3d9_scale_back_buffer_height > 0) { + res = real_GetRenderTarget(self, 0, &d3d9_original_back_buffer); + + if (res != D3D_OK) { + log_warning("Getting back buffer render target failed: %ld", res); + return res; + } + + res = real_SetRenderTarget(self, 0, d3d9_scale_intermediate_render_target); + + if (res != D3D_OK) { + log_warning("Setting intermediate render target failed: %ld", res); + return res; + } + } + + return real_BeginScene(self); +} + +static HRESULT STDCALL my_EndScene(IDirect3DDevice9* self) +{ + HRESULT res = real_EndScene(self); + + if (res == D3D_OK) { + if (d3d9_scale_back_buffer_width > 0 && d3d9_scale_back_buffer_height > 0) { + res = real_SetRenderTarget(self, 0, d3d9_original_back_buffer); + + if (res != D3D_OK) { + log_warning("Setting back back buffer render target failed: %ld", res); + return res; + } + + RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = d3d9_original_back_buffer_width; + rect.bottom = d3d9_original_back_buffer_height; + + /* Must be called outside of begin-end scene block */ + res = real_StretchRect(self, d3d9_scale_intermediate_render_target, &rect, d3d9_original_back_buffer, NULL, + d3d9_scale_back_buffer_filter); + + if (res != D3D_OK) { + log_warning("Stretching immediate render target to back buffer failed: %ld", res); + return res; + } + } + } + + return res; +} + +void d3d9_hook_init(void) +{ + hook_table_apply( + NULL, + "d3d9.dll", + d3d9_hook_syms, + lengthof(d3d9_hook_syms)); + + hook_table_apply( + NULL, + "user32.dll", + d3d9_hook_user32_syms, + lengthof(d3d9_hook_user32_syms)); + + log_info("Inserted graphics hooks"); +} + +void d3d9_set_windowed(bool framed, int32_t width, int32_t height) +{ + d3d9_windowed = true; + d3d9_window_framed = framed; + d3d9_window_width = width; + d3d9_window_height = height; +} + +void d3d9_set_pci_id(uint16_t vid, uint16_t pid) +{ + str_format(d3d9_pci_id, sizeof(d3d9_pci_id), "PCI\\VEN_%04X&DEV_%04X", + vid, pid); +} + +void d3d9_set_frame_rate_limit(float limit) +{ + if (limit < 0.0f) { + limit = 0.0f; + } + + log_info("Limiting rendering frame rate to %f frames", limit); + d3d9_frame_rate_limit = limit; +} + +void d3d9_enable_monitor_check(d3d9_monitor_check_result_callback_t cb) +{ + log_assert(cb); + + d3d9_monitor_check_cb = cb; + log_info("Enabled monitor check"); +} + +void d3d9_iidx_fix_stretched_bg_videos(void) +{ + /* Same as the seam fix */ + d3d9_bg_video_fix = true; + log_info("Fixing UVs of stretched BG videos"); +} + +void d3d9_iidx_fix_12_song_select_bg(void) +{ + d3d9_fix_iidx_12_fog = true; + log_info("Fixing Happy Sky bugged music select 3D background"); +} + +void d3d9_iidx_fix_13_song_select_bg(void) +{ + d3d9_fix_iidx_13_lighting = true; + log_info("Fixing DistorteD music select 3D background"); +} + +void d3d9_enable_nvidia_fix(void) +{ + d3d9_nvidia_fix = true; + log_info("Enabled NVIDIA fix"); +} + +void d3d9_bg_video_seams_fix(void) +{ + d3d9_bg_video_fix = true; + log_info("Enabled BG video seam fix"); +} + +void d3d9_scale_back_buffer(uint16_t width, uint16_t height, enum d3d9_back_buffer_scale_filter filter) +{ + d3d9_scale_back_buffer_width = width; + d3d9_scale_back_buffer_height = height; + + switch (filter) { + case D3D9_BACK_BUFFER_SCALE_FILTER_NONE: + d3d9_scale_back_buffer_filter = D3DTEXF_NONE; + break; + + case D3D9_BACK_BUFFER_SCALE_FILTER_LINEAR: + d3d9_scale_back_buffer_filter = D3DTEXF_LINEAR; + break; + + case D3D9_BACK_BUFFER_SCALE_FILTER_POINT: + d3d9_scale_back_buffer_filter = D3DTEXF_POINT; + break; + + default: + log_fatal("Illegal state"); + break; + } + + if (d3d9_scale_back_buffer_width > 0 && d3d9_scale_back_buffer_height > 0) { + log_info("Enable scaling of back buffer, target size %dx%d, filter %d", width, height, filter); + } else { + d3d9_scale_back_buffer_width = 0; + d3d9_scale_back_buffer_height = 0; + } +} + +/* ------------------------------------------------------------------------- */ + +static void execute_monitor_check(IDirect3DDevice9* api, + IDirect3DDevice9Vtbl* api_vtbl, IDirect3DDevice9* pdev) +{ + log_info("Running monitor check..."); + + /* All games include the _24 version anyway, so leave that hardcoded here */ + HMODULE d3d9 = GetModuleHandleA("d3dx9_24.dll"); + + if (d3d9 == NULL) { + log_fatal("monitor check: failed to load d3dx9_24.dll"); + } + + func_D3DXCreateFontA d3dxCreateFontA = + (func_D3DXCreateFontA) GetProcAddress(d3d9, "D3DXCreateFontA"); + + if (d3dxCreateFontA == NULL) { + log_fatal("monitor check: failed to find function D3DXCreateFontA"); + } + + RECT fRectangle; + ID3DXFont* font = NULL; + struct com_proxy *font_api_proxy; + ID3DXFontVtbl *font_api_vtbl; + + HRESULT hr = d3dxCreateFontA(api, 22, 0, FW_NORMAL, 1, false, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE, + "Arial", &font); + + font_api_proxy = com_proxy_wrap(font, sizeof(*font->lpVtbl)); + font_api_vtbl = font_api_proxy->vptr; + + font = (ID3DXFont*) font_api_proxy; + SetRect(&fRectangle, 20, 20, 640, 480); + + if (hr != S_OK) { + log_fatal("monitor check: Creating font failed"); + } + + if (font == NULL) { + log_fatal("monitor check: Loading font failed"); + } + + const uint32_t max_iterations = 60 * 30; + const uint32_t skip_frames = 60 * 1; + + uint64_t accu_us = 0; + + double result = 0; + uint32_t iterations = 0; + + char text_buffer[256]; + + while (iterations < max_iterations) { + sprintf(text_buffer, + "Monitor check...\n" + "Elapsed iterations: %d/%d\n" + "Refresh rate: %f", + iterations + 1, max_iterations, result); + + iterations++; + uint64_t start = time_get_counter(); + + api_vtbl->Clear(pdev, 0, NULL, D3DCLEAR_TARGET, + D3DCOLOR_XRGB(0, 0, 0), 0.0f, 0); + api_vtbl->BeginScene(pdev); + + font_api_vtbl->DrawTextA(font, NULL, text_buffer, -1, &fRectangle, + DT_LEFT, D3DCOLOR_XRGB(0xFF, 0xFF, 0xFF)); + + api_vtbl->EndScene(pdev); + api_vtbl->Present(pdev, NULL, NULL, NULL, NULL); + + /* Skip the first inaccurate values */ + if (iterations > skip_frames) { + accu_us += time_get_elapsed_us(time_get_counter() - start); + + result = ((double) (iterations - skip_frames)) / + (accu_us / 1000.0 / 1000.0); + } + } + + log_info("Monitor check done (total iterations %d), refesh rate: %f hz", + iterations, result); + + /* Sanity check to ensure people notice that their current refresh rate + is way off. */ + if (result < 55 || result > 65) { + log_warning("Monitor check result (%f hz) is not even near the " + "intended refresh rate of 60 hz. Fix your setup to ensure a " + "constant and as close to as possible 60 hz refresh rate.", result); + } + + /* Leave results of monitor check on screen for a moment */ + Sleep(2000); + + log_assert(d3d9_monitor_check_cb); + + d3d9_monitor_check_cb(result); +} + +static void calc_win_size_with_framed(HWND hwnd, DWORD x, DWORD y, DWORD width, + DWORD height, LPWINDOWPOS wp) +{ + /* taken from dxwnd */ + RECT rect; + DWORD style; + int max_x, max_y; + HMENU menu; + + rect.left = x; + rect.top = y; + max_x = width; + max_y = height; + rect.right = x + max_x; + rect.bottom = y + max_y; + + style = GetWindowLong(hwnd, GWL_STYLE); + menu = GetMenu(hwnd); + AdjustWindowRect(&rect, style, (menu != NULL)); + + /* shift down-right so that the border is visible + and also update the iPosX,iPosY upper-left coordinates + of the client area */ + + if (rect.left < 0) { + rect.right -= rect.left; + rect.left = 0; + } + + if (rect.top < 0) { + rect.bottom -= rect.top; + rect.top = 0; + } + + wp->x = rect.left; + wp->y = rect.top; + wp->cx = rect.right - rect.left; + wp->cy = rect.bottom - rect.top; +} + diff --git a/src/main/iidxhook-util/d3d9.h b/src/main/iidxhook-util/d3d9.h new file mode 100644 index 0000000..1d95424 --- /dev/null +++ b/src/main/iidxhook-util/d3d9.h @@ -0,0 +1,123 @@ +#ifndef IIDXHOOK_D3D9_H +#define IIDXHOOK_D3D9_H + +#include + +/** + * Filter types for scaling back buffer, e.g. upscaling for higher resolution monitors. + */ +enum d3d9_back_buffer_scale_filter { + D3D9_BACK_BUFFER_SCALE_FILTER_NONE = 0, + D3D9_BACK_BUFFER_SCALE_FILTER_LINEAR = 1, + D3D9_BACK_BUFFER_SCALE_FILTER_POINT = 2, +}; + +/** + * Callback function to receive resulting refresh rate of monitor check. + */ +typedef void (*d3d9_monitor_check_result_callback_t)(double refresh_rate); + +/** + * Hook some d3d9 functions to patch gfx related stuff + * like enabling window mode. + * + * Games using d3d9: + * - IIDX: Gold to Copula + */ +void d3d9_hook_init(void); + +/** + * Set the game to window mode. + * + * @param framed True to add a window frame and make the window + * movable, resizable, minizable. False for no frame. + * @param width Width of the window. -1 to keep original width. + * @param height Height of the window. -1 to keep original height. + */ +void d3d9_set_windowed(bool framed, int32_t width, int32_t height); + +/** + * Patch the GPU device ID detection to allow running the game with + * other GPUs than the ones it is locked to. + * + * @param vid Vendor ID to patch. + * @param pid Product ID to patch. + */ +void d3d9_set_pci_id(uint16_t vid, uint16_t pid); + +/** + * Set a framerate limit for the rendering loop. + * + * Use this if the game won't sync up properly with vsync enabled. + * + * @limit Number of frames to limit the rendering loop to + */ +void d3d9_set_frame_rate_limit(float limit); + +/** + * Enable a software a "monitor check" which determines the (avg.) refresh rate + * of the game. Ensure that your GPU is outputting a stable refresh rate to + * your monitor. Otherwise, the resulting values fluctuate a lot + * + * @param cb Callback to a function that receives the result of the monitor + * check once it completed. + */ +void d3d9_enable_monitor_check(d3d9_monitor_check_result_callback_t cb); + +/** + * Ported from d3d8 module to enable this when using d3d8to9. + * + * Fixes UV coordinates for background videos on + * IIDX Red to DD. Use this if you experience the game + * stretching the background videos + * (see implementation for further details) + */ +void d3d9_iidx_fix_stretched_bg_videos(void); + +/** + * Ported from d3d8 module to enable this when using d3d8to9. + * + * Fixes the 3D background displayed during song select by disabling fog. + * On some machines/GPUs the background is rendered completely white which + * is caused by buggy fog rendering. Fog is used to hide the rings appearing + * but that's quite 'far away' so disabling fog doesn't look bad. + */ +void d3d9_iidx_fix_12_song_select_bg(void); + +/** + * Ported from d3d8 module to enable this when using d3d8to9. + * + * Fixes the 3D background displayed during song select by disabling lighting. + * On some machines/GPUs the background is rendered completely black which + * is caused by buggy lighting. Disabling (dynamic) lighting sets the whole + * scene to ambient lighting which displays everything properly. + */ +void d3d9_iidx_fix_13_song_select_bg(void); + +/** + * Fixes a parameter on CreateTexture calls causing the game to crash with + * NVIDIA cards (on GOLD and possible newer) + */ +void d3d9_enable_nvidia_fix(void); + +/** + * Fixes UV coordinates for background videos on + * (similar to the d3d8 games). But, this fixes the quad seam issue on 14 to + * 17, only. + */ +void d3d9_bg_video_seams_fix(void); + +/** + * Scale the back buffer after a frame got rendered. This allows you to up-/downscale the final frame to display + * arbitrary resolutions, e.g. 640x480 -> 1920x1080 to allow displaying the game in the monitor's native resolution. + * This avoids over-/underscan (yes, that's still a thing with todays TVs) or leaving the upscaling to monitors/TVs + * that are doing a terrible job regarding picture quality or latency wise. + * + * @param width Target width to scale the frame to. 0 disables scaling entirely. + * @param height Target height to scale the frame to. 0 disables scaling entirely. + * @param filter Filtering method to use for scaling. Depending on how you scale, this can have an impact on the final + * image quality. + */ +void d3d9_scale_back_buffer(uint16_t width, uint16_t height, enum d3d9_back_buffer_scale_filter filter); + +#endif diff --git a/src/main/iidxhook-util/eamuse.c b/src/main/iidxhook-util/eamuse.c new file mode 100644 index 0000000..b8c9afc --- /dev/null +++ b/src/main/iidxhook-util/eamuse.c @@ -0,0 +1,190 @@ +#define LOG_MODULE "eamuse-hook" + +#include +#include +#include + +#include +#include +#include + +#include "hook/table.h" + +#include "iidxhook-util/eamuse.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" + +/* ------------------------------------------------------------------------- */ + +static unsigned long STDCALL my_inet_addr(const char *cp); +static int STDCALL my_connect(SOCKET s, const struct sockaddr* addr, + int addrlen); +static struct hostent FAR* STDCALL my_gethostbyname(const char *nameB); + +static unsigned long (STDCALL *real_inet_addr)(const char *cp); +static int (STDCALL *real_connect)(SOCKET s, const struct sockaddr* addr, + int addrlen); +static struct hostent FAR* (STDCALL *real_gethostbyname)(const char *nameB); +/* ------------------------------------------------------------------------- */ + +static const struct hook_symbol eamuse_hook_syms[] = { + /* WS2_32.DLL's SDK import lib generates ordinal imports, so these ordinals + are a frozen aspect of the Win32 ABI. */ + + { + .name = "inet_addr", + .ordinal = 11, + .patch = my_inet_addr, + .link = (void **) &real_inet_addr + }, + { + .name = "connect", + .ordinal = 4, + .patch = my_connect, + .link = (void **) &real_connect + }, + { + .name = "gethostbyname", + .ordinal = 52, + .patch = my_gethostbyname, + .link = (void **) &real_gethostbyname + }, +}; + +/* ------------------------------------------------------------------------- */ + +static struct net_addr eamuse_server_addr; +static struct net_addr eamuse_server_addr_resolved; + +/* ------------------------------------------------------------------------- */ + +static unsigned long STDCALL my_inet_addr(const char *cp) +{ + char* tmp; + + /* for a stock machine connected to the eamuse router, + the game wants to connect to the standard domain + services.konami.eamuse.fun + depending on the router you got, it will be services.hostname.my.router + so we catch that and turn it into any ip we want */ + + /* bugfix win10: don't just catch services.konami... because + win10 is doing some weird stuff and this call also contains + various IP addresses. Always return the server address */ + tmp = net_addr_to_str(&eamuse_server_addr_resolved); + log_misc("my_inet_addr: '%s' -> %s", cp, tmp); + free(tmp); + + return eamuse_server_addr_resolved.ipv4.addr; +} + +static int STDCALL my_connect(SOCKET s, const struct sockaddr* addr, int addrlen) +{ + char* tmp; + + if (addr->sa_family == AF_INET) { + /* check if we got our server's address */ + struct sockaddr_in* addr_in = (struct sockaddr_in*) addr; + + if (addr_in->sin_addr.S_un.S_addr == + eamuse_server_addr_resolved.ipv4.addr) { + tmp = net_addr_to_str(&eamuse_server_addr_resolved); + log_misc("Patching port '%d' of %s to %d", ntohs(addr_in->sin_port), + tmp, eamuse_server_addr_resolved.ipv4.port); + free(tmp); + + addr_in->sin_port = htons(eamuse_server_addr_resolved.ipv4.port); + } + } + + return real_connect(s, addr, addrlen); +} + +static struct hostent FAR* STDCALL my_gethostbyname(const char* name) +{ + char* tmp; + + /* for doc, checkout the other detour of inetaddr above + this call is used starting GOLD (not used on pre GOLD) */ + + /* bugfix win10: don't just catch services.konami... because + win10 is doing some weird stuff and this call also contains + various IP addresses. Always return the server address */ + tmp = net_addr_to_str(&eamuse_server_addr_resolved); + log_misc("my_gethostbyname: '%s' to ip %s", name, tmp); + free(tmp); + + /* very ugly hack to get this working. + don't bother with dynamic allocations and have stuff staticly alloc'd + just populate what's touched by the game */ + static struct hostent ret; + static uint32_t addr; + static char* arr[2]; + static bool first = true; + + if (first) { + ret.h_length = 4; + ret.h_addr_list = (char**) &arr; + ret.h_addr_list[0] = (char*) &addr; + ret.h_addr_list[1] = NULL; + ret.h_aliases = NULL; + ret.h_name = NULL; + } + + addr = eamuse_server_addr_resolved.ipv4.addr; + + return &ret; +} + +/* ------------------------------------------------------------------------- */ + +void eamuse_hook_init(void) +{ + hook_table_apply( + NULL, + "ws2_32.dll", + eamuse_hook_syms, + lengthof(eamuse_hook_syms)); + + log_info("Inserted eamuse hooks"); +} + +void eamuse_set_addr(const struct net_addr* addr) +{ + char* tmp_str; + char* tmp_str2; + + log_assert(addr); + + memcpy(&eamuse_server_addr, addr, sizeof(struct net_addr)); + + tmp_str = net_addr_to_str(&eamuse_server_addr); + + eamuse_server_addr_resolved.type = NET_ADDR_TYPE_IPV4; + + if (!net_resolve_hostname_net_addr(&eamuse_server_addr, + &eamuse_server_addr_resolved.ipv4)) { + log_fatal("Failed to resolve eamuse server address: %s", tmp_str); + free(tmp_str); + return; + } + + tmp_str2 = net_addr_to_str(&eamuse_server_addr_resolved); + + log_info("Target eamuse server %s resolved to %s", tmp_str, tmp_str2); + + free(tmp_str); + free(tmp_str2); +} + +void eamuse_check_connection() +{ + if (!net_check_remote_connection(&eamuse_server_addr_resolved, 5000)) { + log_info("Target eamuse server reachable."); + } else { + log_warning("Target eamuse server is not reachable. Game might throw " + "network errors/warnings if eamuse is enabled."); + } +} \ No newline at end of file diff --git a/src/main/iidxhook-util/eamuse.h b/src/main/iidxhook-util/eamuse.h new file mode 100644 index 0000000..7cea97a --- /dev/null +++ b/src/main/iidxhook-util/eamuse.h @@ -0,0 +1,25 @@ +#ifndef IIDXHOOK_EAMUSE_H +#define IIDXHOOK_EAMUSE_H + +#include "util/net.h" + +/** + * Hook various calls resolving the service address to connect to + * the eamuse server for the old IIDX games (9th to Sirius) + */ +void eamuse_hook_init(void); + +/** + * Set a net_addr to a eamuse server for the game to connect to. + * + * @param addr net_addr struct with address to the server. + */ +void eamuse_set_addr(const struct net_addr* addr); + +/** + * Check if the target eamuse server is reachable. Reports success/error using + * the logger. + */ +void eamuse_check_connection(); + +#endif diff --git a/src/main/iidxhook-util/effector.c b/src/main/iidxhook-util/effector.c new file mode 100644 index 0000000..5dd9b8c --- /dev/null +++ b/src/main/iidxhook-util/effector.c @@ -0,0 +1,85 @@ +#define LOG_MODULE "effector-hook" + +#include + +#include "hook/table.h" + +#include "iidxhook-util/effector.h" + +#include "util/defs.h" +#include "util/log.h" + +static BOOL my_EnableEqualizer(int a1); +static BOOL my_GetEqualizerStatus(LPVOID buffer); +static BOOL my_SetEqualizerGain(int a1, int a2); +static BOOL my_SetGlobalEnvironment(int a1); +static BOOL my_SetSpeakerMode(int direct_sound_struct, int a2); + +static const struct hook_symbol effector_hook_syms[] = { + { + .name = "EnableEqualizer", + .patch = my_EnableEqualizer, + }, + { + .name = "GetEqualizerStatus", + .patch = my_GetEqualizerStatus, + }, + { + .name = "SetEqualizerGain", + .patch = my_SetEqualizerGain, + }, + { + .name = "SetGlobalEnvironment", + .patch = my_SetGlobalEnvironment, + }, + { + .name = "SetSpeakerMode", + .patch = my_SetSpeakerMode, + }, +}; + +static BOOL my_EnableEqualizer(int a1) +{ + log_misc("EnableEqualizer"); + // stub + return TRUE; +} + +static BOOL my_GetEqualizerStatus(LPVOID buffer) +{ + log_misc("GetEqualizerStatus"); + // stub + return TRUE; +} + +static BOOL my_SetEqualizerGain(int a1, int a2) +{ + log_misc("SetEqualizerGain"); + // stub + return TRUE; +} + +static BOOL my_SetGlobalEnvironment(int a1) +{ + log_misc("SetGlobalEnvironment"); + // stub + return TRUE; +} + +static BOOL my_SetSpeakerMode(int direct_sound_struct, int a2) +{ + log_misc("SetSpeakerMode"); + // stub + return TRUE; +} + +void effector_hook_init(void) +{ + hook_table_apply( + NULL, + "rteffect.dll", + effector_hook_syms, + lengthof(effector_hook_syms)); + + log_info("Inserted rteffect hooks"); +} diff --git a/src/main/iidxhook-util/effector.h b/src/main/iidxhook-util/effector.h new file mode 100644 index 0000000..4a9592e --- /dev/null +++ b/src/main/iidxhook-util/effector.h @@ -0,0 +1,11 @@ +#ifndef IIDXHOOK_EFFECTOR_H +#define IIDXHOOK_EFFECTOR_H + +#include + +/** + * Hook rteffects.dll exports (10th to DD) + */ +void effector_hook_init(void); + +#endif diff --git a/src/main/iidxhook-util/log-server.c b/src/main/iidxhook-util/log-server.c new file mode 100644 index 0000000..5e2866b --- /dev/null +++ b/src/main/iidxhook-util/log-server.c @@ -0,0 +1,161 @@ +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "iidxhook-util/log-server.h" + +#include "imports/avs.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" +#include "util/thread.h" + +static int log_thread_proc(void *ctx); +static void log_post(char level, const char *module, const char *fmt, + va_list ap); +static void log_post_misc(const char *module, const char *fmt, ...); +static void log_post_info(const char *module, const char *fmt, ...); +static void log_post_warning(const char *module, const char *fmt, ...); +static void log_post_fatal(const char *module, const char *fmt, ...); + +static int log_thread_id; +static HANDLE log_rv_producer; +static HANDLE log_rv_consumer; +static char log_rv_level; +static const char *log_rv_module; +static char log_rv_buffer[8192]; + +void log_server_init(void) +{ + HANDLE ready; + + log_rv_producer = CreateSemaphore(NULL, 1, 1, NULL); + log_rv_consumer = CreateSemaphore(NULL, 0, 1, NULL); + ready = CreateEvent(NULL, TRUE, FALSE, NULL); + + log_to_external(log_post_misc, log_post_info, log_post_warning, + log_post_fatal); + + log_thread_id = avs_thread_create(log_thread_proc, ready, 16384, 0); + + if (WaitForSingleObject(ready, INFINITE)) { + // can't do any logging here, yet. + fprintf(stderr, "ERROR log_server_init: WaitForSingleObject failed: %08x", (unsigned int) GetLastError()); + return; + } + + CloseHandle(ready); + + log_misc("Started log server thread"); +} + +static int log_thread_proc(void *ctx) +{ + bool run; + + SetEvent((HANDLE) ctx); + + run = true; + + log_body_misc(LOG_MODULE, "Log server thread is running"); + + while (run) { + + if (WaitForSingleObject(log_rv_consumer, INFINITE)) { + log_fatal("WaitForSingleObject failed: %08x", + (unsigned int) GetLastError()); + } + + switch (log_rv_level) { + case '\0': + run = false; + + break; + + case 'M': + log_body_misc(log_rv_module, "%s", log_rv_buffer); + + break; + + case 'I': + log_body_info(log_rv_module, "%s", log_rv_buffer); + + break; + + case 'W': + log_body_warning(log_rv_module, "%s", log_rv_buffer); + + break; + + case 'F': + log_body_fatal(log_rv_module, "%s", log_rv_buffer); + + break; + } + + if (!ReleaseSemaphore(log_rv_producer, 1, NULL)) { + log_fatal("ReleaseSemaphore failed: %08x", + (unsigned int) GetLastError()); + } + } + + log_body_misc(LOG_MODULE, "Log server thread is exiting"); + + return 0; +} + +static void log_post(char level, const char *module, const char *fmt, + va_list ap) +{ + if (WaitForSingleObject(log_rv_producer, INFINITE)) { + return; + } + + log_rv_level = level; + log_rv_module = module; + str_vformat(log_rv_buffer, sizeof(log_rv_buffer), fmt, ap); + + ReleaseSemaphore(log_rv_consumer, 1, NULL); +} + +#define LOG_POST_IMPL(name, level) \ + static void name(const char *module, const char *fmt, ...) \ + { \ + va_list ap; \ + \ + va_start(ap, fmt); \ + log_post(level, module, fmt, ap); \ + va_end(ap); \ + } + +LOG_POST_IMPL(log_post_misc, 'M') +LOG_POST_IMPL(log_post_info, 'I') +LOG_POST_IMPL(log_post_warning, 'W') +LOG_POST_IMPL(log_post_fatal, 'F') + +void log_server_fini(void) +{ + int thread_result; + + log_misc("Stopping log server thread"); + + WaitForSingleObject(log_rv_producer, INFINITE); + log_rv_level = '\0'; + ReleaseSemaphore(log_rv_consumer, 1, NULL); + + avs_thread_join(log_thread_id, &thread_result); + avs_thread_destroy(log_thread_id); + + CloseHandle(log_rv_producer); + CloseHandle(log_rv_consumer); + + log_rv_producer = NULL; + log_rv_consumer = NULL; +} + diff --git a/src/main/iidxhook-util/log-server.h b/src/main/iidxhook-util/log-server.h new file mode 100644 index 0000000..72ac49c --- /dev/null +++ b/src/main/iidxhook-util/log-server.h @@ -0,0 +1,19 @@ +#ifndef IIDXHOOK_LOG_SERVER_H +#define IIDXHOOK_LOG_SERVER_H + +/** + * Initialize the log server which creates a dedicated thread and a log message queue. + * + * Log messages are posted to the queue by the application and the background thread writes them to the target output. + * Required for newer iidx and pop'n games using the avs log api (e.g. iidxhook4 to 7) due to a quirk of the ezusb + * driver. For a full breakdown about the logging on avs (and non avs) based konami games, + * see doc/logging-breadkdown-avs.md. + */ +void log_server_init(void); + +/** + * Shut down the log server. + */ +void log_server_fini(void); + +#endif diff --git a/src/main/iidxhook-util/settings.c b/src/main/iidxhook-util/settings.c new file mode 100644 index 0000000..d893398 --- /dev/null +++ b/src/main/iidxhook-util/settings.c @@ -0,0 +1,107 @@ +#define LOG_MODULE "settings-hook" + +#include + +#include +#include +#include +#include + +#include "hook/iohook.h" +#include "hook/table.h" + +#include "util/defs.h" +#include "util/fs.h" +#include "util/log.h" +#include "util/str.h" + +/* ------------------------------------------------------------------------- */ + +static BOOL WINAPI my_CreateDirectoryA(LPCSTR lpPathName, + LPSECURITY_ATTRIBUTES lpSecurityAttributes); + +static BOOL (WINAPI *real_CreateDirectoryA)(LPCSTR lpPathName, + LPSECURITY_ATTRIBUTES lpSecurityAttributes); + +/* ------------------------------------------------------------------------- */ + +static const struct hook_symbol settings_hook_syms[] = { + { + .name = "CreateDirectoryA", + .patch = my_CreateDirectoryA, + .link = (void **) &real_CreateDirectoryA + }, +}; + +static bool settings_folders_checked; + +/* ------------------------------------------------------------------------- */ + +BOOL WINAPI my_CreateDirectoryA(LPCSTR lpPathName, + LPSECURITY_ATTRIBUTES lpSecurityAttributes) +{ + if ( lpPathName != NULL && + (lpPathName[0] == 'd' || lpPathName[0] == 'e' || lpPathName[0] == 'f') && + lpPathName[1] == ':') { + char new_path[MAX_PATH]; + + strcpy(new_path, lpPathName); + new_path[1] = '\\'; + log_misc("(CreateDir) Remapped settings path %s", new_path); + + return real_CreateDirectoryA(new_path, lpSecurityAttributes); + } + + return real_CreateDirectoryA(lpPathName, lpSecurityAttributes); +} + + +/* ------------------------------------------------------------------------- */ + +void settings_hook_init(void) +{ + hook_table_apply( + NULL, + "kernel32.dll", + settings_hook_syms, + lengthof(settings_hook_syms)); + + log_info("Inserted settings hooks"); +} + +HRESULT settings_hook_dispatch_irp(struct irp *irp) +{ + if (irp->op == IRP_OP_OPEN && + (irp->open_filename[0] == L'd' || irp->open_filename[0] == L'e' || + irp->open_filename[0] == L'f') && irp->open_filename[1] == L':') { + char* log_str; + + ((wchar_t*) irp->open_filename)[1] = L'\\'; + + wstr_narrow(irp->open_filename, &log_str); + log_misc("Remapped settings path %s", log_str); + free(log_str); + + /* Create local settings folders if not available */ + if (!settings_folders_checked) { + settings_folders_checked = true; + + for (char c = 'd'; c <= 'f'; c++) { + char str[3]; + + str[0] = c; + str[1] = '\\'; + str[2] = '\0'; + + if (!path_exists(str)) { + log_misc("Creating local settings folder %s\\", str); + CreateDirectoryA(str, NULL); + } + } + } + + return irp_invoke_next(irp); + } + + return irp_invoke_next(irp); +} diff --git a/src/main/iidxhook-util/settings.h b/src/main/iidxhook-util/settings.h new file mode 100644 index 0000000..17dce31 --- /dev/null +++ b/src/main/iidxhook-util/settings.h @@ -0,0 +1,18 @@ +#ifndef IIDXHOOK_SETTINGS_H +#define IIDXHOOK_SETTINGS_H + +#include "hook/iohook.h" + +/** + * Remaps the paths for the settings drives d:\, e:\ and f:\ + * to local folders e\ and f\. + * Needed on IIDX 9th to Sirius. + */ +void settings_hook_init(void); + +/** + * iohook dispatch function + */ +HRESULT settings_hook_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/iidxhook1/Module.mk b/src/main/iidxhook1/Module.mk new file mode 100644 index 0000000..952e9d0 --- /dev/null +++ b/src/main/iidxhook1/Module.mk @@ -0,0 +1,24 @@ +dlls += iidxhook1 + +ldflags_iidxhook1 := \ + -lws2_32 \ + -liphlpapi \ + +libs_iidxhook1 := \ + iidxhook-util \ + ezusb-emu \ + ezusb-iidx-emu \ + security \ + eamio \ + acioemu \ + hook \ + hooklib \ + iidxio \ + cconfig \ + util \ + ezusb \ + +src_iidxhook1 := \ + config-iidxhook1.c \ + dllmain.c \ + log-ezusb.c \ diff --git a/src/main/iidxhook1/config-iidxhook1.c b/src/main/iidxhook1/config-iidxhook1.c new file mode 100644 index 0000000..6119156 --- /dev/null +++ b/src/main/iidxhook1/config-iidxhook1.c @@ -0,0 +1,49 @@ +#include "cconfig/cconfig-util.h" + +#include "iidxhook1/config-iidxhook1.h" + +#include "util/log.h" + +#define IIDXHOOK_CONFIG_MISC_HAPPY_SKY_MS_BG_FIX_KEY "misc.happy_sky_ms_bg_fix" +#define IIDXHOOK_CONFIG_MISC_USE_D3D9_HOOKS_KEY "misc.use_d3d9_hooks" + +#define IIDXHOOK_CONFIG_MISC_DEFAULT_HAPPY_SKY_MS_BG_FIX_VALUE false +#define IIDXHOOK_CONFIG_MISC_DEFAULT_USE_D3D9_HOOKS_VALUE false + +void iidxhook_config_iidxhook1_init(struct cconfig* config) +{ + cconfig_util_set_bool(config, + IIDXHOOK_CONFIG_MISC_HAPPY_SKY_MS_BG_FIX_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_HAPPY_SKY_MS_BG_FIX_VALUE, + "Fix broken 3D background on Happy Sky's music select (if appearing " + "completely white)"); + + cconfig_util_set_bool(config, + IIDXHOOK_CONFIG_MISC_USE_D3D9_HOOKS_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_USE_D3D9_HOOKS_VALUE, + "Use d3d9 hooks instead of d3d8 to enable d3d9 hook features not available on d3d8 (e.g. upscaling). Requires " + " d3d8to9 wrapper library to be used with this game."); +} + +void iidxhook_config_iidxhook1_get( + struct iidxhook_config_iidxhook1* config_iidxhook1, + struct cconfig* config) +{ + if (!cconfig_util_get_bool(config, + IIDXHOOK_CONFIG_MISC_HAPPY_SKY_MS_BG_FIX_KEY, + &config_iidxhook1->happy_sky_ms_bg_fix, + IIDXHOOK_CONFIG_MISC_DEFAULT_HAPPY_SKY_MS_BG_FIX_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_MISC_HAPPY_SKY_MS_BG_FIX_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_HAPPY_SKY_MS_BG_FIX_VALUE); + } + + if (!cconfig_util_get_bool(config, + IIDXHOOK_CONFIG_MISC_USE_D3D9_HOOKS_KEY, + &config_iidxhook1->use_d3d9_hooks, + IIDXHOOK_CONFIG_MISC_DEFAULT_USE_D3D9_HOOKS_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_MISC_USE_D3D9_HOOKS_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_USE_D3D9_HOOKS_VALUE); + } +} \ No newline at end of file diff --git a/src/main/iidxhook1/config-iidxhook1.h b/src/main/iidxhook1/config-iidxhook1.h new file mode 100644 index 0000000..a39a7a5 --- /dev/null +++ b/src/main/iidxhook1/config-iidxhook1.h @@ -0,0 +1,17 @@ +#ifndef IIDXHOOK_CONFIG_IIDXHOOK1_H +#define IIDXHOOK_CONFIG_IIDXHOOK1_H + +#include "cconfig/cconfig.h" + +struct iidxhook_config_iidxhook1 { + bool happy_sky_ms_bg_fix; + bool use_d3d9_hooks; +}; + +void iidxhook_config_iidxhook1_init(struct cconfig* config); + +void iidxhook_config_iidxhook1_get( + struct iidxhook_config_iidxhook1* config_iidxhook1, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/iidxhook1/dllmain.c b/src/main/iidxhook1/dllmain.c new file mode 100644 index 0000000..a710e26 --- /dev/null +++ b/src/main/iidxhook1/dllmain.c @@ -0,0 +1,309 @@ +#include + +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/iidxio.h" + +#include "cconfig/cconfig-hook.h" + +#include "ezusb-emu/desc.h" +#include "ezusb-emu/device.h" + +#include "ezusb-iidx-emu/msg.h" +#include "ezusb-iidx-emu/node-security-plug.h" +#include "ezusb-iidx-emu/node-serial.h" +#include "ezusb-iidx-emu/nodes.h" + +#include "hook/iohook.h" +#include "hook/table.h" + +#include "hooklib/acp.h" +#include "hooklib/adapter.h" +#include "hooklib/setupapi.h" + +#include "iidxhook-util/chart-patch.h" +#include "iidxhook-util/clock.h" +#include "iidxhook-util/config-eamuse.h" +#include "iidxhook-util/config-gfx.h" +#include "iidxhook-util/config-misc.h" +#include "iidxhook-util/config-sec.h" +#include "iidxhook-util/d3d8.h" +#include "iidxhook-util/d3d9.h" +#include "iidxhook-util/eamuse.h" +#include "iidxhook-util/effector.h" +#include "iidxhook-util/settings.h" + +#include "iidxhook1/config-iidxhook1.h" +#include "iidxhook1/log-ezusb.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/thread.h" + +#define IIDXHOOK1_INFO_HEADER \ + "iidxhook for 9th Style, 10th Style, RED and HAPPY SKY" \ + ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) +#define IIDXHOOK1_CMD_USAGE \ + "Usage: inject.exe iidxhook1.dll [options...]" + +static const irp_handler_t iidxhook_handlers[] = { + ezusb_emu_device_dispatch_irp, + iidxhook_util_chart_patch_dispatch_irp, + settings_hook_dispatch_irp, +}; + +static HANDLE STDCALL my_OpenProcess(DWORD, BOOL, DWORD); +static HANDLE (STDCALL *real_OpenProcess)(DWORD, BOOL, DWORD); + +static bool iidxhook_init_check; + +static const struct hook_symbol init_hook_syms[] = { + { + .name = "OpenProcess", + .patch = my_OpenProcess, + .link = (void **) &real_OpenProcess, + }, +}; + +/** + * This seems to be a good entry point to intercept + * before the game calls anything important + */ +static HANDLE STDCALL my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, + DWORD dwProcessId) +{ + struct cconfig* config; + + struct iidxhook_util_config_eamuse config_eamuse; + struct iidxhook_config_gfx config_gfx; + struct iidxhook_config_iidxhook1 config_iidxhook1; + struct iidxhook_config_misc config_misc; + struct iidxhook_config_sec config_sec; + + if (iidxhook_init_check) { + goto skip; + } + + iidxhook_init_check = true; + + log_info("-------------------------------------------------------------"); + log_info("--------------- Begin iidxhook my_OpenProcess ---------------"); + log_info("-------------------------------------------------------------"); + + config = cconfig_init(); + + iidxhook_util_config_eamuse_init(config); + iidxhook_config_gfx_init(config); + iidxhook_config_iidxhook1_init(config); + iidxhook_config_misc_init(config); + iidxhook_config_sec_init(config); + + if (!cconfig_hook_config_init(config, IIDXHOOK1_INFO_HEADER "\n" IIDXHOOK1_CMD_USAGE, CCONFIG_CMD_USAGE_OUT_DBG)) { + cconfig_finit(config); + exit(EXIT_FAILURE); + } + + iidxhook_util_config_eamuse_get(&config_eamuse, config); + iidxhook_config_gfx_get(&config_gfx, config); + iidxhook_config_iidxhook1_get(&config_iidxhook1, config); + iidxhook_config_misc_get(&config_misc, config); + iidxhook_config_sec_get(&config_sec, config); + + cconfig_finit(config); + + log_info(IIDXHOOK1_INFO_HEADER); + log_info("Initializing iidxhook..."); + + /* Round plug security */ + + ezusb_iidx_emu_node_security_plug_set_boot_version( + &config_sec.boot_version); + ezusb_iidx_emu_node_security_plug_set_boot_seeds(config_sec.boot_seeds); + ezusb_iidx_emu_node_security_plug_set_plug_black_mcode( + &config_sec.black_plug_mcode); + ezusb_iidx_emu_node_security_plug_set_pcbid(&config_eamuse.pcbid); + + /* Magnetic card reader (9th to HS) */ + + ezusb_iidx_emu_node_serial_set_card_attributes(0, true, + config_eamuse.card_type); + + /* eAmusement server IP */ + + eamuse_set_addr(&config_eamuse.server); + eamuse_check_connection(); + + /* Patch rteffect.dll calls */ + + if (config_misc.rteffect_stub) { + effector_hook_init(); + } + + /* Direct3D and USER32 hooks */ + + /* There are a few features that cannot be implemented using d3d8, + e.g. GPU based upscaling with filtering. When using d3d8to9, allow + using the d3d9 hooks instead because the game is running on d3d9. */ + if (config_iidxhook1.use_d3d9_hooks) { + log_warning("d3d9 hook modules enabled. Requires d3d8to9!"); + + d3d9_hook_init(); + + if (config_gfx.bgvideo_uv_fix) { + /* Red, HS, DistorteD, only */ + d3d9_iidx_fix_stretched_bg_videos(); + } + + if (config_gfx.windowed) { + d3d9_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d9_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.monitor_check == 0) { + log_info("Auto monitor check enabled"); + d3d9_enable_monitor_check(iidxhook_util_chart_patch_set_refresh_rate); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_9_TO_13); + } else if (config_gfx.monitor_check > 0) { + log_info("Manual monitor check, resulting refresh rate: %f", + config_gfx.monitor_check); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_9_TO_13); + iidxhook_util_chart_patch_set_refresh_rate(config_gfx.monitor_check); + } + + /* Fix Happy Sky 3D background */ + + if (config_iidxhook1.happy_sky_ms_bg_fix) { + d3d9_iidx_fix_12_song_select_bg(); + } + + if (config_gfx.scale_back_buffer_width > 0 && config_gfx.scale_back_buffer_height > 0) { + d3d9_scale_back_buffer(config_gfx.scale_back_buffer_width, config_gfx.scale_back_buffer_height, + config_gfx.scale_back_buffer_filter); + } + } else { + d3d8_hook_init(); + + if (config_gfx.bgvideo_uv_fix) { + /* Red, HS, DistorteD, only */ + d3d8_iidx_fix_stretched_bg_videos(); + } + + if (config_gfx.windowed) { + d3d8_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d8_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.monitor_check == 0) { + log_info("Auto monitor check enabled"); + d3d8_enable_monitor_check(iidxhook_util_chart_patch_set_refresh_rate); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_9_TO_13); + } else if (config_gfx.monitor_check > 0) { + log_info("Manual monitor check, resulting refresh rate: %f", + config_gfx.monitor_check); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_9_TO_13); + iidxhook_util_chart_patch_set_refresh_rate(config_gfx.monitor_check); + } + + /* Fix Happy Sky 3D background */ + + if (config_iidxhook1.happy_sky_ms_bg_fix) { + d3d8_iidx_fix_12_song_select_bg(); + } + } + + /* Disable operator menu clock setting system clock time */ + + if (config_misc.disable_clock_set) { + iidxhook_util_clock_hook_init(); + } + + /* Start up IIDXIO.DLL */ + + log_info("Starting IIDX IO backend"); + iidx_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!iidx_io_init(thread_create, thread_join, thread_destroy)) { + log_fatal("Initializing IIDX IO backend failed"); + } + + /* Start up EAMIO.DLL */ + + log_misc("Initializing card reader backend"); + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(thread_create, thread_join, thread_destroy)) { + log_fatal("Initializing card reader backend failed"); + } + + /* Set up IO emulation hooks _after_ IO API setup to allow + API implementations with real IO devices */ + iohook_init(iidxhook_handlers, lengthof(iidxhook_handlers)); + + hook_setupapi_init(&ezusb_emu_desc_device.setupapi); + ezusb_emu_device_hook_init(ezusb_iidx_emu_msg_init()); + + log_info("-------------------------------------------------------------"); + log_info("---------------- End iidxhook my_OpenProcess ----------------"); + log_info("-------------------------------------------------------------"); + +skip: + return real_OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); +} + +/** + * Hook library for 9th to Happy Sky + */ +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { +#ifdef DEBUG_HOOKING + FILE* file = fopen("iidxhook.dllmain.log", "w+"); + log_to_writer(log_writer_file, file); +#else + log_to_writer(log_writer_null, NULL); +#endif + + /* Bootstrap hook for further init tasks (see above) */ + + hook_table_apply( + NULL, + "kernel32.dll", + init_hook_syms, + lengthof(init_hook_syms)); + + /* Actual hooks for game specific stuff */ + + acp_hook_init(); + adapter_hook_init(); + eamuse_hook_init(); + settings_hook_init(); + +#ifdef DEBUG_HOOKING + fflush(file); + fclose(file); +#endif + + /* Logging to file and other destinations is handled by inject */ + log_to_writer(log_writer_debug, NULL); + } + + return TRUE; +} + diff --git a/src/main/iidxhook1/iidxhook1.def b/src/main/iidxhook1/iidxhook1.def new file mode 100644 index 0000000..eb7b5ff --- /dev/null +++ b/src/main/iidxhook1/iidxhook1.def @@ -0,0 +1,5 @@ +LIBRARY iidxhook1 + +EXPORTS + DllMain@12 @1 NONAME + \ No newline at end of file diff --git a/src/main/iidxhook1/log-ezusb.c b/src/main/iidxhook1/log-ezusb.c new file mode 100644 index 0000000..49dc3ad --- /dev/null +++ b/src/main/iidxhook1/log-ezusb.c @@ -0,0 +1,55 @@ +#define LOG_MODULE "logger-hook" + +#include + +#include +#include +#include + +#include "hook/table.h" + +#include "iidxhook1/log-ezusb.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" + +/* ------------------------------------------------------------------------- */ + +static int STDCALL my_wvsprintfA(LPSTR lpOut, LPCSTR lpFmt, va_list args); + +static int (STDCALL *real_wvsprintfA)(LPSTR lpOut, LPCSTR lpFmt, va_list args); + +/* ------------------------------------------------------------------------- */ + +static const struct hook_symbol ezusb_log_hook_syms[] = { + { + .name = "wvsprintfA", + .patch = my_wvsprintfA, + .link = (void **) &real_wvsprintfA + }, +}; + +/* ------------------------------------------------------------------------- */ + +static int STDCALL my_wvsprintfA(LPSTR lpOut, LPCSTR lpFmt, va_list args) +{ + char buf[5192]; + vsprintf(buf, lpFmt, args); + log_info("%s", buf); + + return real_wvsprintfA(lpOut, lpFmt, args); +} + +/* ------------------------------------------------------------------------- */ + +void ezusb_log_hook_init(void) +{ + hook_table_apply( + NULL, + "user32.dll", + ezusb_log_hook_syms, + lengthof(ezusb_log_hook_syms)); + + log_info("Inserted ezusb log hooks"); +} diff --git a/src/main/iidxhook1/log-ezusb.h b/src/main/iidxhook1/log-ezusb.h new file mode 100644 index 0000000..1aa5ec6 --- /dev/null +++ b/src/main/iidxhook1/log-ezusb.h @@ -0,0 +1,10 @@ +#ifndef IIDXHOOK_LOG_EZUSB_H +#define IIDXHOOK_LOG_EZUSB_H + +/** + * Hook into the logger call of the built in ezusb logger + * (this works on 9th to HappySky, only). + */ +void ezusb_log_hook_init(void); + +#endif diff --git a/src/main/iidxhook2/Module.mk b/src/main/iidxhook2/Module.mk new file mode 100644 index 0000000..aa91d25 --- /dev/null +++ b/src/main/iidxhook2/Module.mk @@ -0,0 +1,23 @@ +avsdlls += iidxhook2 + +ldflags_iidxhook2 := \ + -lws2_32 \ + -liphlpapi \ + +libs_iidxhook2 := \ + iidxhook-util \ + ezusb-emu \ + ezusb-iidx-emu \ + security \ + eamio \ + acioemu \ + hook \ + hooklib \ + iidxio \ + cconfig \ + util \ + ezusb \ + +src_iidxhook2 := \ + config-iidxhook2.c \ + dllmain.c \ diff --git a/src/main/iidxhook2/config-iidxhook2.c b/src/main/iidxhook2/config-iidxhook2.c new file mode 100644 index 0000000..f2859d4 --- /dev/null +++ b/src/main/iidxhook2/config-iidxhook2.c @@ -0,0 +1,49 @@ +#include "cconfig/cconfig-util.h" + +#include "iidxhook2/config-iidxhook2.h" + +#include "util/log.h" + +#define IIDXHOOK_CONFIG_MISC_DISTORTED_MS_BG_FIX_KEY "misc.distorted_ms_bg_fix" +#define IIDXHOOK_CONFIG_MISC_USE_D3D9_HOOKS_KEY "misc.use_d3d9_hooks" + +#define IIDXHOOK_CONFIG_MISC_DEFAULT_DISTORTED_MS_BG_FIX_VALUE false +#define IIDXHOOK_CONFIG_MISC_DEFAULT_USE_D3D9_HOOKS_VALUE false + +void iidxhook_config_iidxhook2_init(struct cconfig* config) +{ + cconfig_util_set_bool(config, + IIDXHOOK_CONFIG_MISC_DISTORTED_MS_BG_FIX_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_DISTORTED_MS_BG_FIX_VALUE, + "Fix broken 3D background on DistorteD's music select (if appearing " + "completely black)"); + + cconfig_util_set_bool(config, + IIDXHOOK_CONFIG_MISC_USE_D3D9_HOOKS_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_USE_D3D9_HOOKS_VALUE, + "Use d3d9 hooks instead of d3d8 to enable d3d9 hook features not available on d3d8 (e.g. upscaling). Requires " + " d3d8to9 wrapper library to be used with this game."); +} + +void iidxhook_config_iidxhook2_get( + struct iidxhook_config_iidxhook2* config_iidxhook2, + struct cconfig* config) +{ + if (!cconfig_util_get_bool(config, + IIDXHOOK_CONFIG_MISC_DISTORTED_MS_BG_FIX_KEY, + &config_iidxhook2->distorted_ms_bg_fix, + IIDXHOOK_CONFIG_MISC_DEFAULT_DISTORTED_MS_BG_FIX_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_MISC_DISTORTED_MS_BG_FIX_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_DISTORTED_MS_BG_FIX_VALUE); + } + + if (!cconfig_util_get_bool(config, + IIDXHOOK_CONFIG_MISC_USE_D3D9_HOOKS_KEY, + &config_iidxhook2->use_d3d9_hooks, + IIDXHOOK_CONFIG_MISC_DEFAULT_USE_D3D9_HOOKS_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK_CONFIG_MISC_USE_D3D9_HOOKS_KEY, + IIDXHOOK_CONFIG_MISC_DEFAULT_USE_D3D9_HOOKS_VALUE); + } +} \ No newline at end of file diff --git a/src/main/iidxhook2/config-iidxhook2.h b/src/main/iidxhook2/config-iidxhook2.h new file mode 100644 index 0000000..ce1f4a5 --- /dev/null +++ b/src/main/iidxhook2/config-iidxhook2.h @@ -0,0 +1,17 @@ +#ifndef IIDXHOOK_CONFIG_IIDXHOOK2_H +#define IIDXHOOK_CONFIG_IIDXHOOK2_H + +#include "cconfig/cconfig.h" + +struct iidxhook_config_iidxhook2 { + bool distorted_ms_bg_fix; + bool use_d3d9_hooks; +}; + +void iidxhook_config_iidxhook2_init(struct cconfig* config); + +void iidxhook_config_iidxhook2_get( + struct iidxhook_config_iidxhook2* config_iidxhook2, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/iidxhook2/dllmain.c b/src/main/iidxhook2/dllmain.c new file mode 100644 index 0000000..32c01f6 --- /dev/null +++ b/src/main/iidxhook2/dllmain.c @@ -0,0 +1,308 @@ +#include + +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/iidxio.h" + +#include "cconfig/cconfig-hook.h" + +#include "ezusb-emu/desc.h" +#include "ezusb-emu/device.h" + +#include "ezusb-iidx-emu/msg.h" +#include "ezusb-iidx-emu/node-security-plug.h" +#include "ezusb-iidx-emu/node-serial.h" +#include "ezusb-iidx-emu/nodes.h" + +#include "hook/iohook.h" +#include "hook/table.h" + +#include "hooklib/adapter.h" +#include "hooklib/acp.h" +#include "hooklib/rs232.h" +#include "hooklib/setupapi.h" + +#include "iidxhook-util/acio.h" +#include "iidxhook-util/chart-patch.h" +#include "iidxhook-util/clock.h" +#include "iidxhook-util/config-eamuse.h" +#include "iidxhook-util/config-gfx.h" +#include "iidxhook-util/config-misc.h" +#include "iidxhook-util/config-sec.h" +#include "iidxhook-util/d3d8.h" +#include "iidxhook-util/eamuse.h" +#include "iidxhook-util/effector.h" +#include "iidxhook-util/settings.h" + +#include "iidxhook2/config-iidxhook2.h" + +#include "util/log.h" +#include "util/thread.h" + +#define IIDXHOOK2_INFO_HEADER \ + "iidxhook for DistorteD" \ + ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) +#define IIDXHOOK2_CMD_USAGE \ + "Usage: inject.exe iidxhook2.dll [options...]" + +static const irp_handler_t iidxhook_handlers[] = { + ezusb_emu_device_dispatch_irp, + iidxhook_util_acio_dispatch_irp, + iidxhook_util_chart_patch_dispatch_irp, + settings_hook_dispatch_irp, +}; + +static HANDLE STDCALL my_OpenProcess(DWORD, BOOL, DWORD); +static HANDLE (STDCALL *real_OpenProcess)(DWORD, BOOL, DWORD); +static bool iidxhook_init_check; + +static const struct hook_symbol init_hook_syms[] = { + { + .name = "OpenProcess", + .patch = my_OpenProcess, + .link = (void **) &real_OpenProcess + }, +}; + +/** + * This seems to be a good entry point to intercept + * before the game calls anything important + */ +HANDLE STDCALL my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, + DWORD dwProcessId) +{ + struct cconfig* config; + + struct iidxhook_util_config_eamuse config_eamuse; + struct iidxhook_config_gfx config_gfx; + struct iidxhook_config_iidxhook2 config_iidxhook2; + struct iidxhook_config_misc config_misc; + struct iidxhook_config_sec config_sec; + + if (iidxhook_init_check) { + goto skip; + } + + iidxhook_init_check = true; + + log_info("-------------------------------------------------------------"); + log_info("--------------- Begin iidxhook my_OpenProcess ---------------"); + log_info("-------------------------------------------------------------"); + + config = cconfig_init(); + + iidxhook_util_config_eamuse_init(config); + iidxhook_config_gfx_init(config); + iidxhook_config_iidxhook2_init(config); + iidxhook_config_misc_init(config); + iidxhook_config_sec_init(config); + + if (!cconfig_hook_config_init(config, IIDXHOOK2_INFO_HEADER "\n" IIDXHOOK2_CMD_USAGE, CCONFIG_CMD_USAGE_OUT_DBG)) { + cconfig_finit(config); + exit(EXIT_FAILURE); + } + + iidxhook_util_config_eamuse_get(&config_eamuse, config); + iidxhook_config_gfx_get(&config_gfx, config); + iidxhook_config_iidxhook2_get(&config_iidxhook2, config); + iidxhook_config_misc_get(&config_misc, config); + iidxhook_config_sec_get(&config_sec, config); + + cconfig_finit(config); + + log_info(IIDXHOOK2_INFO_HEADER); + log_info("Initializing iidxhook..."); + + /* Round plug security */ + + ezusb_iidx_emu_node_security_plug_set_boot_version( + &config_sec.boot_version); + ezusb_iidx_emu_node_security_plug_set_boot_seeds(config_sec.boot_seeds); + ezusb_iidx_emu_node_security_plug_set_plug_black_mcode( + &config_sec.black_plug_mcode); + ezusb_iidx_emu_node_security_plug_set_pcbid(&config_eamuse.pcbid); + + /* eAmusement server IP */ + + eamuse_set_addr(&config_eamuse.server); + eamuse_check_connection(); + + /* Patch rteffect.dll calls */ + + if (config_misc.rteffect_stub) { + effector_hook_init(); + } + + /* Direct3D and USER32 hooks */ + + /* There are a few features that cannot be implemented using d3d8, + e.g. GPU based upscaling with filtering. When using d3d8to9, allow + using the d3d9 hooks instead because the game is running on d3d9. */ + if (config_iidxhook2.use_d3d9_hooks) { + log_warning("d3d9 hook modules enabled. Requires d3d8to9!"); + + d3d9_hook_init(); + + if (config_gfx.bgvideo_uv_fix) { + /* Red, HS, DistorteD, only */ + d3d9_iidx_fix_stretched_bg_videos(); + } + + if (config_gfx.windowed) { + d3d9_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d9_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.monitor_check == 0) { + log_info("Auto monitor check enabled"); + d3d9_enable_monitor_check(iidxhook_util_chart_patch_set_refresh_rate); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_9_TO_13); + } else if (config_gfx.monitor_check > 0) { + log_info("Manual monitor check, resulting refresh rate: %f", + config_gfx.monitor_check); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_9_TO_13); + iidxhook_util_chart_patch_set_refresh_rate(config_gfx.monitor_check); + } + + /* Fix DistorteD 3D background on music select screen */ + + if (config_iidxhook2.distorted_ms_bg_fix) { + d3d9_iidx_fix_13_song_select_bg(); + } + + if (config_gfx.scale_back_buffer_width > 0 && config_gfx.scale_back_buffer_height > 0) { + d3d9_scale_back_buffer(config_gfx.scale_back_buffer_width, config_gfx.scale_back_buffer_height, + config_gfx.scale_back_buffer_filter); + } + } else { + d3d8_hook_init(); + + if (config_gfx.bgvideo_uv_fix) { + /* Red, HS, DistorteD, only */ + d3d8_iidx_fix_stretched_bg_videos(); + } + + if (config_gfx.windowed) { + d3d8_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d8_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.monitor_check == 0) { + log_info("Auto monitor check enabled"); + d3d8_enable_monitor_check(iidxhook_util_chart_patch_set_refresh_rate); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_9_TO_13); + } else if (config_gfx.monitor_check > 0) { + log_info("Manual monitor check, resulting refresh rate: %f", + config_gfx.monitor_check); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_9_TO_13); + iidxhook_util_chart_patch_set_refresh_rate(config_gfx.monitor_check); + } + + /* Fix DistorteD 3D background on music select screen */ + + if (config_iidxhook2.distorted_ms_bg_fix) { + d3d8_iidx_fix_13_song_select_bg(); + } + } + + /* Disable operator menu clock setting system clock time */ + + if (config_misc.disable_clock_set) { + iidxhook_util_clock_hook_init(); + } + + /* Start up IIDXIO.DLL */ + + log_info("Starting IIDX IO backend"); + iidx_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!iidx_io_init(thread_create, thread_join, thread_destroy)) { + log_fatal("Initializing IIDX IO backend failed"); + } + + /* Start up EAMIO.DLL */ + + log_misc("Initializing card reader backend"); + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(thread_create, thread_join, thread_destroy)) { + log_fatal("Initializing card reader backend failed"); + } + + /* Set up IO emulation hooks _after_ IO API setup to allow + API implementations with real IO devices */ + iohook_init(iidxhook_handlers, lengthof(iidxhook_handlers)); + + hook_setupapi_init(&ezusb_emu_desc_device.setupapi); + ezusb_emu_device_hook_init(ezusb_iidx_emu_msg_init()); + + /* Card reader emulation, same issue with hooking as IO emulation */ + rs232_hook_init(); + + iidxhook_util_acio_init(true); + + log_info("-------------------------------------------------------------"); + log_info("---------------- End iidxhook my_OpenProcess ----------------"); + log_info("-------------------------------------------------------------"); + +skip: + return real_OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); +} + +/** + * Hook library for DistorteD + */ +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { +#ifdef DEBUG_HOOKING + FILE* file = fopen("iidxhook.dllmain.log", "w+"); + log_to_writer(log_writer_file, file); +#else + log_to_writer(log_writer_null, NULL); +#endif + + /* Bootstrap hook for further init tasks (see above) */ + + hook_table_apply( + NULL, + "kernel32.dll", + init_hook_syms, + lengthof(init_hook_syms)); + + /* Actual hooks for game specific stuff */ + + acp_hook_init(); + adapter_hook_init(); + eamuse_hook_init(); + settings_hook_init(); + +#ifdef DEBUG_HOOKING + fflush(file); + fclose(file); +#endif + + /* Logging to file and other destinations is handled by inject */ + log_to_writer(log_writer_debug, NULL); + } + + return TRUE; +} + diff --git a/src/main/iidxhook2/iidxhook2.def b/src/main/iidxhook2/iidxhook2.def new file mode 100644 index 0000000..76b59ec --- /dev/null +++ b/src/main/iidxhook2/iidxhook2.def @@ -0,0 +1,5 @@ +LIBRARY iidxhook2 + +EXPORTS + DllMain@12 @1 NONAME + \ No newline at end of file diff --git a/src/main/iidxhook3/Module.mk b/src/main/iidxhook3/Module.mk new file mode 100644 index 0000000..246f4ee --- /dev/null +++ b/src/main/iidxhook3/Module.mk @@ -0,0 +1,24 @@ +avsdlls += iidxhook3 + +ldflags_iidxhook3 := \ + -lws2_32 \ + -liphlpapi \ + +libs_iidxhook3 := \ + iidxhook-util \ + ezusb-emu \ + ezusb2-emu \ + ezusb2-iidx-emu \ + ezusb-iidx-emu \ + security \ + acioemu \ + eamio \ + hook \ + hooklib \ + iidxio \ + cconfig \ + util \ + ezusb \ + +src_iidxhook3 := \ + dllmain.c \ diff --git a/src/main/iidxhook3/dllmain.c b/src/main/iidxhook3/dllmain.c new file mode 100644 index 0000000..c9d8731 --- /dev/null +++ b/src/main/iidxhook3/dllmain.c @@ -0,0 +1,258 @@ +#include + +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/iidxio.h" + +#include "cconfig/cconfig-hook.h" + +#include "ezusb-iidx-emu/node-security-plug.h" +#include "ezusb-iidx-emu/node-serial.h" +#include "ezusb-iidx-emu/nodes.h" + +#include "ezusb2-emu/desc.h" +#include "ezusb2-emu/device.h" + +#include "ezusb2-iidx-emu/msg.h" + +#include "hook/iohook.h" +#include "hook/table.h" + +#include "hooklib/adapter.h" +#include "hooklib/acp.h" +#include "hooklib/rs232.h" +#include "hooklib/setupapi.h" + +#include "iidxhook-util/acio.h" +#include "iidxhook-util/chart-patch.h" +#include "iidxhook-util/clock.h" +#include "iidxhook-util/config-eamuse.h" +#include "iidxhook-util/config-gfx.h" +#include "iidxhook-util/config-misc.h" +#include "iidxhook-util/config-sec.h" +#include "iidxhook-util/d3d9.h" +#include "iidxhook-util/eamuse.h" +#include "iidxhook-util/settings.h" + +#include "util/log.h" +#include "util/str.h" +#include "util/thread.h" + +#define IIDXHOOK3_INFO_HEADER \ + "iidxhook for Gold, DJTroopers, Empress and Sirius" \ + ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) +#define IIDXHOOK3_CMD_USAGE \ + "Usage: inject.exe iidxhook3.dll [options...]" + +static const irp_handler_t iidxhook_handlers[] = { + ezusb2_emu_device_dispatch_irp, + iidxhook_util_acio_dispatch_irp, + iidxhook_util_chart_patch_dispatch_irp, + settings_hook_dispatch_irp, +}; + +static HANDLE STDCALL my_OpenProcess(DWORD, BOOL, DWORD); +static HANDLE (STDCALL *real_OpenProcess)(DWORD, BOOL, DWORD); +static bool iidxhook_init_check; + +static const struct hook_symbol init_hook_syms[] = { + { + .name = "OpenProcess", + .patch = my_OpenProcess, + .link = (void **) &real_OpenProcess + }, +}; + +/** + * This seems to be a good entry point to intercept + * before the game calls anything important + */ +HANDLE STDCALL my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, + DWORD dwProcessId) +{ + struct cconfig* config; + + struct iidxhook_util_config_eamuse config_eamuse; + struct iidxhook_config_gfx config_gfx; + struct iidxhook_config_misc config_misc; + struct iidxhook_config_sec config_sec; + + if (iidxhook_init_check) { + goto skip; + } + + iidxhook_init_check = true; + + log_info("-------------------------------------------------------------"); + log_info("--------------- Begin iidxhook my_OpenProcess ---------------"); + log_info("-------------------------------------------------------------"); + + config = cconfig_init(); + + iidxhook_util_config_eamuse_init(config); + iidxhook_config_gfx_init(config); + iidxhook_config_misc_init(config); + iidxhook_config_sec_init(config); + + if (!cconfig_hook_config_init(config, IIDXHOOK3_INFO_HEADER "\n" IIDXHOOK3_CMD_USAGE, CCONFIG_CMD_USAGE_OUT_DBG)) { + cconfig_finit(config); + exit(EXIT_FAILURE); + } + + iidxhook_util_config_eamuse_get(&config_eamuse, config); + iidxhook_config_gfx_get(&config_gfx, config); + iidxhook_config_misc_get(&config_misc, config); + iidxhook_config_sec_get(&config_sec, config); + + cconfig_finit(config); + + log_info(IIDXHOOK3_INFO_HEADER); + log_info("Initializing iidxhook..."); + + /* Round plug security */ + + ezusb_iidx_emu_node_security_plug_set_boot_version( + &config_sec.boot_version); + ezusb_iidx_emu_node_security_plug_set_boot_seeds(config_sec.boot_seeds); + ezusb_iidx_emu_node_security_plug_set_plug_black_mcode( + &config_sec.black_plug_mcode); + ezusb_iidx_emu_node_security_plug_set_plug_white_mcode( + &security_mcode_eamuse); + ezusb_iidx_emu_node_security_plug_set_pcbid(&config_eamuse.pcbid); + ezusb_iidx_emu_node_security_plug_set_eamid(&config_eamuse.eamid); + + /* eAmusement server IP */ + + eamuse_set_addr(&config_eamuse.server); + eamuse_check_connection(); + + /* Direct3D and USER32 hooks */ + + if (config_gfx.windowed) { + d3d9_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + /* Empress onwards */ + if (config_gfx.pci_id_pid != 0 && config_gfx.pci_id_vid != 0) { + d3d9_set_pci_id(config_gfx.pci_id_pid, config_gfx.pci_id_vid); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d9_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.monitor_check == 0) { + log_info("Auto monitor check enabled"); + d3d9_enable_monitor_check(iidxhook_util_chart_patch_set_refresh_rate); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_14_TO_18_VGA); + } else if (config_gfx.monitor_check > 0) { + log_info("Manual monitor check, resulting refresh rate: %f", + config_gfx.monitor_check); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_14_TO_18_VGA); + iidxhook_util_chart_patch_set_refresh_rate(config_gfx.monitor_check); + } + + if (config_gfx.scale_back_buffer_width > 0 && config_gfx.scale_back_buffer_height > 0) { + d3d9_scale_back_buffer(config_gfx.scale_back_buffer_width, config_gfx.scale_back_buffer_height, + config_gfx.scale_back_buffer_filter); + } + + /* Required for GOLD (and newer?) to not crash with NVIDIA cards */ + + d3d9_enable_nvidia_fix(); + d3d9_bg_video_seams_fix(); + + /* Disable operator menu clock setting system clock time */ + + if (config_misc.disable_clock_set) { + iidxhook_util_clock_hook_init(); + } + + /* Start up IIDXIO.DLL */ + + log_info("Starting IIDX IO backend"); + iidx_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!iidx_io_init(thread_create, thread_join, thread_destroy)) { + log_fatal("Initializing IIDX IO backend failed"); + } + + /* Start up EAMIO.DLL */ + + log_misc("Initializing card reader backend"); + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(thread_create, thread_join, thread_destroy)) { + log_fatal("Initializing card reader backend failed"); + } + + /* Set up IO emulation hooks _after_ IO API setup to allow + API implementations with real IO devices */ + iohook_init(iidxhook_handlers, lengthof(iidxhook_handlers)); + + hook_setupapi_init(&ezusb2_emu_desc_device.setupapi); + ezusb2_emu_device_hook_init(ezusb2_iidx_emu_msg_init()); + + /* Card reader emulation, same issue with hooking as IO emulation */ + rs232_hook_init(); + + iidxhook_util_acio_init(true); + + log_info("-------------------------------------------------------------"); + log_info("---------------- End iidxhook my_OpenProcess ----------------"); + log_info("-------------------------------------------------------------"); + +skip: + return real_OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); +} + +/** + * Hook library for Gold to Sirius + */ +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { +#ifdef DEBUG_HOOKING + FILE* file = fopen("iidxhook.dllmain.log", "w+"); + log_to_writer(log_writer_file, file); +#else + log_to_writer(log_writer_null, NULL); +#endif + /* Bootstrap hook for further init tasks (see above) */ + + hook_table_apply( + NULL, + "kernel32.dll", + init_hook_syms, + lengthof(init_hook_syms)); + + /* Actual hooks for game specific stuff */ + /* Some hooks are setting dependent and can only be applied later in + the bootstrap hook */ + + acp_hook_init(); + adapter_hook_init(); + d3d9_hook_init(); + eamuse_hook_init(); + settings_hook_init(); + +#ifdef DEBUG_HOOKING + fflush(file); + fclose(file); +#endif + + /* Logging to file and other destinations is handled by inject */ + log_to_writer(log_writer_debug, NULL); + } + + return TRUE; +} + diff --git a/src/main/iidxhook3/iidxhook3.def b/src/main/iidxhook3/iidxhook3.def new file mode 100644 index 0000000..f687ba1 --- /dev/null +++ b/src/main/iidxhook3/iidxhook3.def @@ -0,0 +1,5 @@ +LIBRARY iidxhook3 + +EXPORTS + DllMain@12 @1 NONAME + \ No newline at end of file diff --git a/src/main/iidxhook4/Module.mk b/src/main/iidxhook4/Module.mk new file mode 100644 index 0000000..513f1e3 --- /dev/null +++ b/src/main/iidxhook4/Module.mk @@ -0,0 +1,26 @@ +avsdlls += iidxhook4 + +ldflags_iidxhook4 := \ + -liphlpapi \ + +deplibs_iidxhook4 := \ + avs \ + +libs_iidxhook4 := \ + iidxhook-util \ + ezusb-emu \ + ezusb2-emu \ + ezusb2-iidx-emu \ + ezusb-iidx-emu \ + security \ + eamio \ + acioemu \ + hook \ + hooklib \ + iidxio \ + cconfig \ + util \ + ezusb \ + +src_iidxhook4 := \ + dllmain.c \ diff --git a/src/main/iidxhook4/dllmain.c b/src/main/iidxhook4/dllmain.c new file mode 100644 index 0000000..cadf89d --- /dev/null +++ b/src/main/iidxhook4/dllmain.c @@ -0,0 +1,192 @@ +#include + +#include +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/iidxio.h" + +#include "cconfig/cconfig-hook.h" + +#include "ezusb-iidx-emu/nodes.h" + +#include "ezusb2-emu/desc.h" +#include "ezusb2-emu/device.h" + +#include "ezusb2-iidx-emu/msg.h" + +#include "hooklib/acp.h" +#include "hooklib/adapter.h" +#include "hooklib/app.h" +#include "hooklib/rs232.h" +#include "hooklib/setupapi.h" + +#include "iidxhook-util/acio.h" +#include "iidxhook-util/chart-patch.h" +#include "iidxhook-util/config-gfx.h" +#include "iidxhook-util/d3d9.h" +#include "iidxhook-util/log-server.h" +#include "iidxhook-util/settings.h" + +#include "imports/avs.h" + +#include "util/log.h" +#include "util/str.h" +#include "util/thread.h" + +#define IIDXHOOK4_INFO_HEADER \ + "iidxhook for Resort Anthem" \ + ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) +#define IIDXHOOK4_CMD_USAGE \ + "Usage: launcher.exe -K iidxhook4.dll [options...]" + +static const irp_handler_t iidxhook_handlers[] = { + ezusb2_emu_device_dispatch_irp, + iidxhook_util_acio_dispatch_irp, + iidxhook_util_chart_patch_dispatch_irp, + settings_hook_dispatch_irp, +}; + +static bool my_dll_entry_init(char *sidcode, struct property_node *param) +{ + struct cconfig* config; + + struct iidxhook_config_gfx config_gfx; + + log_server_init(); + log_info("-------------------------------------------------------------"); + log_info("--------------- Begin iidxhook dll_entry_init ---------------"); + log_info("-------------------------------------------------------------"); + + config = cconfig_init(); + + iidxhook_config_gfx_init(config); + + if (!cconfig_hook_config_init(config, IIDXHOOK4_INFO_HEADER "\n" IIDXHOOK4_CMD_USAGE, CCONFIG_CMD_USAGE_OUT_DBG)) { + cconfig_finit(config); + log_server_fini(); + exit(EXIT_FAILURE); + } + + iidxhook_config_gfx_get(&config_gfx, config); + + cconfig_finit(config); + + log_info(IIDXHOOK4_INFO_HEADER); + log_info("Initializing iidxhook..."); + + if (config_gfx.windowed) { + d3d9_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + if (config_gfx.pci_id_pid != 0 && config_gfx.pci_id_vid != 0) { + d3d9_set_pci_id(config_gfx.pci_id_pid, config_gfx.pci_id_vid); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d9_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.monitor_check == 0) { + log_info("Auto monitor check enabled"); + d3d9_enable_monitor_check(iidxhook_util_chart_patch_set_refresh_rate); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_14_TO_18_VGA); + } else if (config_gfx.monitor_check > 0) { + log_info("Manual monitor check, resulting refresh rate: %f", + config_gfx.monitor_check); + iidxhook_util_chart_patch_init( + IIDXHOOK_UTIL_CHART_PATCH_TIMEBASE_14_TO_18_VGA); + iidxhook_util_chart_patch_set_refresh_rate(config_gfx.monitor_check); + } + + if (config_gfx.scale_back_buffer_width > 0 && config_gfx.scale_back_buffer_height > 0) { + d3d9_scale_back_buffer(config_gfx.scale_back_buffer_width, config_gfx.scale_back_buffer_height, + config_gfx.scale_back_buffer_filter); + } + + /* Start up IIDXIO.DLL */ + log_info("Starting IIDX IO backend"); + iidx_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!iidx_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing IIDX IO backend failed"); + } + + /* Start up EAMIO.DLL */ + log_misc("Initializing card reader backend"); + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing card reader backend failed"); + } + + /* Set up IO emulation hooks _after_ IO API setup to allow + API implementations with real IO devices */ + iohook_init(iidxhook_handlers, lengthof(iidxhook_handlers)); + + hook_setupapi_init(&ezusb2_emu_desc_device.setupapi); + ezusb2_emu_device_hook_init(ezusb2_iidx_emu_msg_init()); + + /* Card reader emulation, same issue with hooking as IO emulation */ + rs232_hook_init(); + + iidxhook_util_acio_init(true); + + log_info("-------------------------------------------------------------"); + log_info("---------------- End iidxhook dll_entry_init ----------------"); + log_info("-------------------------------------------------------------"); + + return app_hook_invoke_init(sidcode, param); +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + iidxhook_util_chart_patch_fini(); + + log_misc("Shutting down card reader backend"); + eam_io_fini(); + + log_misc("Shutting down IIDX IO backend"); + iidx_io_fini(); + + log_server_fini(); + + return result; +} + +/** + * Hook library for Resort Anthem + */ +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason != DLL_PROCESS_ATTACH) { + goto end; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + + acp_hook_init(); + adapter_hook_init(); + d3d9_hook_init(); + settings_hook_init(); + +end: + return TRUE; +} + diff --git a/src/main/iidxhook4/iidxhook4.def b/src/main/iidxhook4/iidxhook4.def new file mode 100644 index 0000000..f4b03bd --- /dev/null +++ b/src/main/iidxhook4/iidxhook4.def @@ -0,0 +1,5 @@ +LIBRARY iidxhook4 + +EXPORTS + DllMain@12 @1 NONAME + \ No newline at end of file diff --git a/src/main/iidxhook5/Module.mk b/src/main/iidxhook5/Module.mk new file mode 100644 index 0000000..4cf08c5 --- /dev/null +++ b/src/main/iidxhook5/Module.mk @@ -0,0 +1,26 @@ +avsdlls += iidxhook5 + +ldflags_iidxhook5 := \ + -liphlpapi \ + +deplibs_iidxhook5 := \ + avs \ + +libs_iidxhook5 := \ + iidxhook-util \ + ezusb-emu \ + ezusb2-emu \ + ezusb2-iidx-emu \ + ezusb-iidx-emu \ + security \ + eamio \ + acioemu \ + hook \ + hooklib \ + iidxio \ + cconfig \ + util \ + ezusb \ + +src_iidxhook5 := \ + dllmain.c \ diff --git a/src/main/iidxhook5/dllmain.c b/src/main/iidxhook5/dllmain.c new file mode 100644 index 0000000..05f3f75 --- /dev/null +++ b/src/main/iidxhook5/dllmain.c @@ -0,0 +1,176 @@ +#include + +#include +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/iidxio.h" + +#include "cconfig/cconfig-hook.h" + +#include "ezusb-iidx-emu/nodes.h" + +#include "ezusb2-emu/desc.h" +#include "ezusb2-emu/device.h" + +#include "ezusb2-iidx-emu/msg.h" + +#include "hooklib/acp.h" +#include "hooklib/adapter.h" +#include "hooklib/app.h" +#include "hooklib/rs232.h" +#include "hooklib/setupapi.h" + +#include "iidxhook-util/acio.h" +#include "iidxhook-util/config-gfx.h" +#include "iidxhook-util/d3d9.h" +#include "iidxhook-util/log-server.h" +#include "iidxhook-util/settings.h" + +#include "imports/avs.h" + +#include "util/log.h" +#include "util/str.h" +#include "util/thread.h" + +#define IIDXHOOK5_INFO_HEADER \ + "iidxhook for Lincle" \ + ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) +#define IIDXHOOK5_CMD_USAGE \ + "Usage: launcher.exe -K iidxhook5.dll [options...]" + +static const irp_handler_t iidxhook_handlers[] = { + ezusb2_emu_device_dispatch_irp, + iidxhook_util_acio_dispatch_irp, + settings_hook_dispatch_irp, +}; + +static bool my_dll_entry_init(char *sidcode, struct property_node *param) +{ + struct cconfig* config; + + struct iidxhook_config_gfx config_gfx; + + log_server_init(); + log_info("-------------------------------------------------------------"); + log_info("--------------- Begin iidxhook dll_entry_init ---------------"); + log_info("-------------------------------------------------------------"); + + config = cconfig_init(); + + iidxhook_config_gfx_init(config); + + if (!cconfig_hook_config_init(config, IIDXHOOK5_INFO_HEADER "\n" IIDXHOOK5_CMD_USAGE, CCONFIG_CMD_USAGE_OUT_DBG)) { + cconfig_finit(config); + log_server_fini(); + exit(EXIT_FAILURE); + } + + iidxhook_config_gfx_get(&config_gfx, config); + + cconfig_finit(config); + + log_info(IIDXHOOK5_INFO_HEADER); + log_info("Initializing iidxhook..."); + + if (config_gfx.windowed) { + d3d9_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + if (config_gfx.pci_id_pid != 0 && config_gfx.pci_id_vid != 0) { + d3d9_set_pci_id(config_gfx.pci_id_pid, config_gfx.pci_id_vid); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d9_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.scale_back_buffer_width > 0 && config_gfx.scale_back_buffer_height > 0) { + d3d9_scale_back_buffer(config_gfx.scale_back_buffer_width, config_gfx.scale_back_buffer_height, + config_gfx.scale_back_buffer_filter); + } + + /* Start up IIDXIO.DLL */ + log_info("Starting IIDX IO backend"); + iidx_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!iidx_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing IIDX IO backend failed"); + } + + /* Start up EAMIO.DLL */ + log_misc("Initializing card reader backend"); + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing card reader backend failed"); + } + + /* Set up IO emulation hooks _after_ IO API setup to allow + API implementations with real IO devices */ + iohook_init(iidxhook_handlers, lengthof(iidxhook_handlers)); + + hook_setupapi_init(&ezusb2_emu_desc_device.setupapi); + ezusb2_emu_device_hook_init(ezusb2_iidx_emu_msg_init()); + + /* Card reader emulation, same issue with hooking as IO emulation */ + rs232_hook_init(); + + /* Do not use legacy mode, first version with wave pass readers */ + iidxhook_util_acio_init(false); + + log_info("-------------------------------------------------------------"); + log_info("---------------- End iidxhook dll_entry_init ----------------"); + log_info("-------------------------------------------------------------"); + + return app_hook_invoke_init(sidcode, param); +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + log_misc("Shutting down card reader backend"); + eam_io_fini(); + + log_misc("Shutting down IIDX IO backend"); + iidx_io_fini(); + + log_server_fini(); + + return result; +} + +/** + * Hook library for Lincle + */ +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason != DLL_PROCESS_ATTACH) { + goto end; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + + acp_hook_init(); + adapter_hook_init(); + d3d9_hook_init(); + settings_hook_init(); + +end: + return TRUE; +} + diff --git a/src/main/iidxhook5/iidxhook5.def b/src/main/iidxhook5/iidxhook5.def new file mode 100644 index 0000000..7235fdb --- /dev/null +++ b/src/main/iidxhook5/iidxhook5.def @@ -0,0 +1,5 @@ +LIBRARY iidxhook5 + +EXPORTS + DllMain@12 @1 NONAME + \ No newline at end of file diff --git a/src/main/iidxhook6/Module.mk b/src/main/iidxhook6/Module.mk new file mode 100644 index 0000000..97e6121 --- /dev/null +++ b/src/main/iidxhook6/Module.mk @@ -0,0 +1,26 @@ +avsdlls += iidxhook6 + +ldflags_iidxhook6 := \ + -liphlpapi \ + +deplibs_iidxhook6 := \ + avs \ + +libs_iidxhook6 := \ + iidxhook-util \ + ezusb-emu \ + ezusb2-emu \ + ezusb2-iidx-emu \ + ezusb-iidx-emu \ + security \ + eamio \ + acioemu \ + hook \ + hooklib \ + iidxio \ + cconfig \ + util \ + ezusb \ + +src_iidxhook6 := \ + dllmain.c \ diff --git a/src/main/iidxhook6/dllmain.c b/src/main/iidxhook6/dllmain.c new file mode 100644 index 0000000..1e2393a --- /dev/null +++ b/src/main/iidxhook6/dllmain.c @@ -0,0 +1,173 @@ +#include + +#include +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/iidxio.h" + +#include "cconfig/cconfig-hook.h" + +#include "ezusb-iidx-emu/nodes.h" + +#include "ezusb2-emu/desc.h" +#include "ezusb2-emu/device.h" + +#include "ezusb2-iidx-emu/msg.h" + +#include "hooklib/acp.h" +#include "hooklib/adapter.h" +#include "hooklib/app.h" +#include "hooklib/rs232.h" +#include "hooklib/setupapi.h" + +#include "iidxhook-util/acio.h" +#include "iidxhook-util/config-gfx.h" +#include "iidxhook-util/d3d9.h" +#include "iidxhook-util/log-server.h" + +#include "imports/avs.h" + +#include "util/log.h" +#include "util/str.h" +#include "util/thread.h" + +#define IIDXHOOK6_INFO_HEADER \ + "iidxhook for Tricoro" \ + ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) +#define IIDXHOOK6_CMD_USAGE \ + "Usage: launcher.exe -K iidxhook6.dll [options...]" + +static const irp_handler_t iidxhook_handlers[] = { + ezusb2_emu_device_dispatch_irp, + iidxhook_util_acio_dispatch_irp, +}; + +static bool my_dll_entry_init(char *sidcode, struct property_node *param) +{ + struct cconfig* config; + + struct iidxhook_config_gfx config_gfx; + + log_server_init(); + log_info("-------------------------------------------------------------"); + log_info("--------------- Begin iidxhook dll_entry_init ---------------"); + log_info("-------------------------------------------------------------"); + + config = cconfig_init(); + + iidxhook_config_gfx_init(config); + + if (!cconfig_hook_config_init(config, IIDXHOOK6_INFO_HEADER "\n" IIDXHOOK6_CMD_USAGE, CCONFIG_CMD_USAGE_OUT_DBG)) { + cconfig_finit(config); + log_server_fini(); + exit(EXIT_FAILURE); + } + + iidxhook_config_gfx_get(&config_gfx, config); + + cconfig_finit(config); + + log_info(IIDXHOOK6_INFO_HEADER); + log_info("Initializing iidxhook..."); + + if (config_gfx.windowed) { + d3d9_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + if (config_gfx.pci_id_pid != 0 && config_gfx.pci_id_vid != 0) { + d3d9_set_pci_id(config_gfx.pci_id_pid, config_gfx.pci_id_vid); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d9_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.scale_back_buffer_width > 0 && config_gfx.scale_back_buffer_height > 0) { + d3d9_scale_back_buffer(config_gfx.scale_back_buffer_width, config_gfx.scale_back_buffer_height, + config_gfx.scale_back_buffer_filter); + } + + /* Start up IIDXIO.DLL */ + log_info("Starting IIDX IO backend"); + iidx_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!iidx_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing IIDX IO backend failed"); + } + + /* Start up EAMIO.DLL */ + log_misc("Initializing card reader backend"); + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing card reader backend failed"); + } + + /* Set up IO emulation hooks _after_ IO API setup to allow + API implementations with real IO devices */ + iohook_init(iidxhook_handlers, lengthof(iidxhook_handlers)); + + hook_setupapi_init(&ezusb2_emu_desc_device.setupapi); + ezusb2_emu_device_hook_init(ezusb2_iidx_emu_msg_init()); + + /* Card reader emulation, same issue with hooking as IO emulation */ + rs232_hook_init(); + + /* Do not use legacy mode, wave pass readers */ + iidxhook_util_acio_init(false); + + log_info("-------------------------------------------------------------"); + log_info("---------------- End iidxhook dll_entry_init ----------------"); + log_info("-------------------------------------------------------------"); + + return app_hook_invoke_init(sidcode, param); +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + log_misc("Shutting down card reader backend"); + eam_io_fini(); + + log_misc("Shutting down IIDX IO backend"); + iidx_io_fini(); + + log_server_fini(); + + return result; +} + +/** + * Hook library for Tricoro + */ +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason != DLL_PROCESS_ATTACH) { + goto end; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + + acp_hook_init(); + adapter_hook_init(); + d3d9_hook_init(); + +end: + return TRUE; +} + diff --git a/src/main/iidxhook6/iidxhook6.def b/src/main/iidxhook6/iidxhook6.def new file mode 100644 index 0000000..a0da401 --- /dev/null +++ b/src/main/iidxhook6/iidxhook6.def @@ -0,0 +1,5 @@ +LIBRARY iidxhook6 + +EXPORTS + DllMain@12 @1 NONAME + \ No newline at end of file diff --git a/src/main/iidxhook7/Module.mk b/src/main/iidxhook7/Module.mk new file mode 100644 index 0000000..5eb4a4b --- /dev/null +++ b/src/main/iidxhook7/Module.mk @@ -0,0 +1,26 @@ +avsdlls += iidxhook7 + +ldflags_iidxhook7 := \ + -liphlpapi \ + +deplibs_iidxhook7 := \ + avs \ + +libs_iidxhook7 := \ + iidxhook-util \ + cconfig \ + ezusb-emu \ + ezusb2-emu \ + ezusb2-iidx-emu \ + ezusb-iidx-emu \ + security \ + eamio \ + acioemu \ + hook \ + hooklib \ + iidxio \ + util \ + ezusb \ + +src_iidxhook7 := \ + dllmain.c \ diff --git a/src/main/iidxhook7/dllmain.c b/src/main/iidxhook7/dllmain.c new file mode 100644 index 0000000..928b426 --- /dev/null +++ b/src/main/iidxhook7/dllmain.c @@ -0,0 +1,173 @@ +#include + +#include +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/iidxio.h" + +#include "cconfig/cconfig-hook.h" + +#include "ezusb-iidx-emu/nodes.h" + +#include "ezusb2-emu/desc.h" +#include "ezusb2-emu/device.h" + +#include "ezusb2-iidx-emu/msg.h" + +#include "hooklib/acp.h" +#include "hooklib/adapter.h" +#include "hooklib/app.h" +#include "hooklib/rs232.h" +#include "hooklib/setupapi.h" + +#include "iidxhook-util/acio.h" +#include "iidxhook-util/config-gfx.h" +#include "iidxhook-util/d3d9.h" +#include "iidxhook-util/log-server.h" + +#include "imports/avs.h" + +#include "util/log.h" +#include "util/str.h" +#include "util/thread.h" + +#define IIDXHOOK7_INFO_HEADER \ + "iidxhook for SPADA, PENDUAL, copula and SINOBUZ" \ + ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) +#define IIDXHOOK7_CMD_USAGE \ + "Usage: launcher.exe -K iidxhook7.dll [options...]" + +static const irp_handler_t iidxhook_handlers[] = { + ezusb2_emu_device_dispatch_irp, + iidxhook_util_acio_dispatch_irp, +}; + +static bool my_dll_entry_init(char *sidcode, struct property_node *param) +{ + struct cconfig* config; + + struct iidxhook_config_gfx config_gfx; + + log_server_init(); + log_info("-------------------------------------------------------------"); + log_info("--------------- Begin iidxhook dll_entry_init ---------------"); + log_info("-------------------------------------------------------------"); + + config = cconfig_init(); + + iidxhook_config_gfx_init(config); + + if (!cconfig_hook_config_init(config, IIDXHOOK7_INFO_HEADER "\n" IIDXHOOK7_CMD_USAGE, CCONFIG_CMD_USAGE_OUT_DBG)) { + cconfig_finit(config); + log_server_fini(); + exit(EXIT_FAILURE); + } + + iidxhook_config_gfx_get(&config_gfx, config); + + cconfig_finit(config); + + log_info(IIDXHOOK7_INFO_HEADER); + log_info("Initializing iidxhook..."); + + if (config_gfx.windowed) { + d3d9_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + if (config_gfx.pci_id_pid != 0 && config_gfx.pci_id_vid != 0) { + d3d9_set_pci_id(config_gfx.pci_id_pid, config_gfx.pci_id_vid); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d9_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.scale_back_buffer_width > 0 && config_gfx.scale_back_buffer_height > 0) { + d3d9_scale_back_buffer(config_gfx.scale_back_buffer_width, config_gfx.scale_back_buffer_height, + config_gfx.scale_back_buffer_filter); + } + + /* Start up IIDXIO.DLL */ + log_info("Starting IIDX IO backend"); + iidx_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!iidx_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing IIDX IO backend failed"); + } + + /* Start up EAMIO.DLL */ + log_misc("Initializing card reader backend"); + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing card reader backend failed"); + } + + /* Set up IO emulation hooks _after_ IO API setup to allow + API implementations with real IO devices */ + iohook_init(iidxhook_handlers, lengthof(iidxhook_handlers)); + + hook_setupapi_init(&ezusb2_emu_desc_device.setupapi); + ezusb2_emu_device_hook_init(ezusb2_iidx_emu_msg_init()); + + /* Card reader emulation, same issue with hooking as IO emulation */ + rs232_hook_init(); + + /* Do not use legacy mode, wave pass readers */ + iidxhook_util_acio_init(false); + + log_info("-------------------------------------------------------------"); + log_info("---------------- End iidxhook dll_entry_init ----------------"); + log_info("-------------------------------------------------------------"); + + return app_hook_invoke_init(sidcode, param); +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + log_misc("Shutting down card reader backend"); + eam_io_fini(); + + log_misc("Shutting down IIDX IO backend"); + iidx_io_fini(); + + log_server_fini(); + + return result; +} + +/** + * Hook library for SPADA, PENDUAL, copula and SINOBUZ + */ +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason != DLL_PROCESS_ATTACH) { + goto end; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + + acp_hook_init(); + adapter_hook_init(); + d3d9_hook_init(); + +end: + return TRUE; +} + diff --git a/src/main/iidxhook7/iidxhook7.def b/src/main/iidxhook7/iidxhook7.def new file mode 100644 index 0000000..39872f2 --- /dev/null +++ b/src/main/iidxhook7/iidxhook7.def @@ -0,0 +1,4 @@ +LIBRARY iidxhook7 + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/iidxhook8/Module.mk b/src/main/iidxhook8/Module.mk new file mode 100644 index 0000000..1f0b269 --- /dev/null +++ b/src/main/iidxhook8/Module.mk @@ -0,0 +1,31 @@ +avsdlls += iidxhook8 + +ldflags_iidxhook8 := \ + -liphlpapi \ + -lsetupapi \ + -lcfgmgr32 \ + -lmf \ + -lmfplat \ + -lole32 \ + +deplibs_iidxhook8 := \ + avs \ + +libs_iidxhook8 := \ + iidxhook-util \ + acioemu \ + iidxio \ + hook \ + hooklib \ + cconfig \ + util \ + eamio \ + +src_iidxhook8 := \ + bi2a.c \ + bio2.c \ + cam.c \ + config-cam.c \ + config-io.c \ + dllmain.c \ + setupapi.c \ diff --git a/src/main/iidxhook8/bi2a.c b/src/main/iidxhook8/bi2a.c new file mode 100644 index 0000000..7caca87 --- /dev/null +++ b/src/main/iidxhook8/bi2a.c @@ -0,0 +1,251 @@ +#define LOG_MODULE "bio2emu-bi2a" + +#include "iidxhook8/bi2a.h" + +#include /* for _BitScanForward */ + +#include +#include +#include + +#include "acioemu/emu.h" + +#include "bemanitools/iidxio.h" + +static int get_default_slider_valid(size_t idx); +static void bio2_emu_bi2a_cmd_send_version(const struct ac_io_message *req); +static void bio2_emu_bi2a_send_state(const struct ac_io_message *req); +static void bio2_emu_bi2a_send_empty(const struct ac_io_message *req); +static void bio2_emu_bi2a_send_status(const struct ac_io_message *req, uint8_t status); + +static struct ac_io_emu *bi2a_bio2; +static int default_sliders[5]; +static bool poll_delay; + +int get_default_slider_valid(size_t idx) { + if (default_sliders[idx] >= 0 && default_sliders[idx] <= 15) { + return 1; + } else { + return 0; + } +} + +void bio2_emu_bi2a_init(struct ac_io_emu *bio2_emu, bool disable_poll_limiter) +{ + FILE* f; + bi2a_bio2 = bio2_emu; + poll_delay = !disable_poll_limiter; + if (!poll_delay) { + log_warning("bio2_emu_bi2a_init: poll_delay has been disabled"); + } + + for (size_t i = 0; i < 5; ++i) { + default_sliders[i] = -1; + } + + f = fopen("vefx.txt", "r"); + if (f) { + fscanf(f, "%d %d %d %d %d", &default_sliders[0], &default_sliders[1], &default_sliders[2], &default_sliders[3], &default_sliders[4]); + fclose(f); + } + +} + +void bio2_emu_bi2a_dispatch_request(const struct ac_io_message *req) +{ + uint16_t cmd_code; + + cmd_code = ac_io_u16(req->cmd.code); + + switch (cmd_code) { + case BIO2_BI2A_CMD_UNK_0100: + case BIO2_BI2A_CMD_UNK_0120: + log_misc("BIO2_BI2A_CMD_UNK_%04X(%d)", cmd_code, req->addr); + bio2_emu_bi2a_send_status(req, 0x00); + break; + + case BIO2_BI2A_CMD_POLL: + // log_misc("BIO2_BI2A_CMD_POLL"); + bio2_emu_bi2a_send_state(req); + break; + + case AC_IO_CMD_GET_VERSION: + log_misc("BIO2_CMD_GET_VERSION(%d)", req->addr); + bio2_emu_bi2a_cmd_send_version(req); + break; + + case AC_IO_CMD_START_UP: + log_misc("BIO2_CMD_START_UP(%d)", req->addr); + bio2_emu_bi2a_send_status(req, 0x00); + break; + + case AC_IO_CMD_KEEPALIVE: + log_misc("BIO2_CMD_KEEPALIVE(%d)", req->addr); + bio2_emu_bi2a_send_empty(req); + break; + + default: + log_warning("Unknown BIO2 message %04x on BI2A node, addr=%d", cmd_code, req->addr); + break; + } +} + +static void bio2_emu_bi2a_cmd_send_version(const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.version); + resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_BI2A); + resp.cmd.version.flag = 0x00; + resp.cmd.version.major = 0x04; + resp.cmd.version.minor = 0x00; + resp.cmd.version.revision = 0x04; + memcpy(resp.cmd.version.product_code, "BI2A", + sizeof(resp.cmd.version.product_code)); + strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date)); + strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time)); + + ac_io_emu_response_push(bi2a_bio2, &resp, 0); +} + +static void bio2_emu_bi2a_send_empty(const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = 0; + + ac_io_emu_response_push(bi2a_bio2, &resp, 0); +} + +static void bio2_emu_bi2a_send_status(const struct ac_io_message *req, uint8_t status) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.status); + resp.cmd.status = status; + + ac_io_emu_response_push(bi2a_bio2, &resp, 0); +} + + +static void bio2_emu_bi2a_send_state(const struct ac_io_message *req) +{ + struct ac_io_message resp; + struct bio2_bi2a_state *body; + struct bio2_bi2a_state_in *req_bi2a; + + uint8_t input_sys = 0; + uint8_t input_panel = 0; + uint16_t input_keys = 0; + + struct { + uint8_t panel_lights; + uint16_t deck_lights; + uint8_t top_lamps; + } packed_lights; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(struct bio2_bi2a_state); + req_bi2a = (struct bio2_bi2a_state_in*)req->cmd.raw; + + packed_lights.panel_lights = 0; + for (size_t i = 0; i < 4; ++i) { + if (req_bi2a->PANEL[i].l_state) { + packed_lights.panel_lights |= (1 << i); + } + } + packed_lights.deck_lights = 0; + for (size_t i = 0; i < 14; ++i) { + if (req_bi2a->DECKSW[i].l_state) { + packed_lights.deck_lights |= (1 << i); + } + } + packed_lights.top_lamps = 0; + for (size_t i = 0; i < 4; ++i) { + if (req_bi2a->SPOTLIGHT1[i].l_state) { + packed_lights.top_lamps |= (1 << i); + } + } + for (size_t i = 0; i < 4; ++i) { + if (req_bi2a->SPOTLIGHT2[i].l_state) { + packed_lights.top_lamps |= (1 << (i + 4)); + } + } + + iidx_io_ep1_set_deck_lights(packed_lights.deck_lights); + iidx_io_ep1_set_panel_lights(packed_lights.panel_lights); + iidx_io_ep1_set_top_lamps(packed_lights.top_lamps); + iidx_io_ep1_set_top_neons(req_bi2a->NEONLAMP.l_state); + + if (!iidx_io_ep1_send()) { + log_warning("BIO2: iidx_io_ep1_send error"); + return bio2_emu_bi2a_send_status(req, 0); + } + + if (!iidx_io_ep3_write_16seg((const char*) req_bi2a->SEG16)) { + log_warning("BIO2: iidx_io_ep3_write_16seg error"); + return bio2_emu_bi2a_send_status(req, 0); + } + + body = (struct bio2_bi2a_state *)&resp.cmd.raw; + memset(body, 0, sizeof(struct bio2_bi2a_state)); + + // IIDX25 polls really really fast, this limits it to 1000Hz + if (poll_delay) { + Sleep(1); + } + + if (!iidx_io_ep2_recv()) { + log_warning("BIO2: iidx_io_ep2_recv error"); + return bio2_emu_bi2a_send_status(req, 0); + } + + body->TURNTABLE1 = iidx_io_ep2_get_turntable(0); + body->TURNTABLE2 = iidx_io_ep2_get_turntable(1); + + body->SLIDER1.s_val = get_default_slider_valid(0) ? default_sliders[0] : iidx_io_ep2_get_slider(0); + body->SLIDER2.s_val = get_default_slider_valid(1) ? default_sliders[1] : iidx_io_ep2_get_slider(1); + body->SLIDER3.s_val = get_default_slider_valid(2) ? default_sliders[2] : iidx_io_ep2_get_slider(2); + body->SLIDER4.s_val = get_default_slider_valid(3) ? default_sliders[3] : iidx_io_ep2_get_slider(3); + body->SLIDER5.s_val = get_default_slider_valid(4) ? default_sliders[4] : iidx_io_ep2_get_slider(4); + + input_keys = iidx_io_ep2_get_keys(); + input_sys = iidx_io_ep2_get_sys(); + input_panel = iidx_io_ep2_get_panel(); + body->P1SW1.b_val = (input_keys >> IIDX_IO_KEY_P1_1) & 1; + body->P1SW2.b_val = (input_keys >> IIDX_IO_KEY_P1_2) & 1; + body->P1SW3.b_val = (input_keys >> IIDX_IO_KEY_P1_3) & 1; + body->P1SW4.b_val = (input_keys >> IIDX_IO_KEY_P1_4) & 1; + body->P1SW5.b_val = (input_keys >> IIDX_IO_KEY_P1_5) & 1; + body->P1SW6.b_val = (input_keys >> IIDX_IO_KEY_P1_6) & 1; + body->P1SW7.b_val = (input_keys >> IIDX_IO_KEY_P1_7) & 1; + body->P2SW1.b_val = (input_keys >> IIDX_IO_KEY_P2_1) & 1; + body->P2SW2.b_val = (input_keys >> IIDX_IO_KEY_P2_2) & 1; + body->P2SW3.b_val = (input_keys >> IIDX_IO_KEY_P2_3) & 1; + body->P2SW4.b_val = (input_keys >> IIDX_IO_KEY_P2_4) & 1; + body->P2SW5.b_val = (input_keys >> IIDX_IO_KEY_P2_5) & 1; + body->P2SW6.b_val = (input_keys >> IIDX_IO_KEY_P2_6) & 1; + body->P2SW7.b_val = (input_keys >> IIDX_IO_KEY_P2_7) & 1; + + body->PANEL.y_start1 = (input_panel >> IIDX_IO_PANEL_P1_START) & 1; + body->PANEL.y_start2 = (input_panel >> IIDX_IO_PANEL_P2_START) & 1; + body->PANEL.y_vefx = (input_panel >> IIDX_IO_PANEL_VEFX) & 1; + body->PANEL.y_effect = (input_panel >> IIDX_IO_PANEL_EFFECT) & 1; + + body->SYSTEM.v_test = (input_sys >> IIDX_IO_SYS_TEST) & 1; + body->SYSTEM.v_service = (input_sys >> IIDX_IO_SYS_SERVICE) & 1; + body->SYSTEM.v_coin = (input_sys >> IIDX_IO_SYS_COIN) & 1; + + ac_io_emu_response_push(bi2a_bio2, &resp, 0); +} diff --git a/src/main/iidxhook8/bi2a.h b/src/main/iidxhook8/bi2a.h new file mode 100644 index 0000000..3654ed3 --- /dev/null +++ b/src/main/iidxhook8/bi2a.h @@ -0,0 +1,115 @@ +#ifndef IIDXHOOK_BI2A_H +#define IIDXHOOK_BI2A_H + +#include +#include +#include + +#include "acioemu/emu.h" + +enum bio2_bi2a_cmd { + // Custom Stuff + BIO2_BI2A_CMD_UNK_0100 = 0x0100, + BIO2_BI2A_CMD_UNK_0120 = 0x0120, + BIO2_BI2A_CMD_POLL = 0x0152, +}; + +#pragma pack(push, 1) +struct _slider { + uint8_t s_unk : 4; + uint8_t s_val : 4; +}; + +struct _system { + uint8_t v_unk1 : 1; + uint8_t v_coin : 1; + uint8_t v_service : 1; + uint8_t v_test : 1; + uint8_t v_unk2 : 4; +}; + +struct _button { + uint8_t b_unk : 7; + uint8_t b_val : 1; +}; + +struct _panel { + uint8_t y_unk : 4; + uint8_t y_effect : 1; + uint8_t y_vefx : 1; + uint8_t y_start2 : 1; + uint8_t y_start1 : 1; +}; + +struct bio2_bi2a_state { + struct _slider SLIDER1; + struct _system SYSTEM; + struct _slider SLIDER2; + uint8_t UNK1; + struct _slider SLIDER3; + uint8_t UNK2; + struct _slider SLIDER4; + struct _slider SLIDER5; + uint8_t UNK3; // coin mech? + struct _panel PANEL; + uint8_t UNK4; + uint8_t UNK5; + uint8_t UNK6; + uint8_t UNK7; + uint8_t UNK8; + uint8_t UNK9; + uint8_t TURNTABLE1; + uint8_t TURNTABLE2; + struct _button P1SW1; + uint8_t UNK11; + struct _button P1SW2; + uint8_t UNK12; + struct _button P1SW3; + uint8_t UNK13; + struct _button P1SW4; + uint8_t UNK14; + struct _button P1SW5; + uint8_t UNK15; + struct _button P1SW6; + uint8_t UNK16; + struct _button P1SW7; + uint8_t UNK17; + struct _button P2SW1; + uint8_t UNK21; + struct _button P2SW2; + uint8_t UNK22; + struct _button P2SW3; + uint8_t UNK23; + struct _button P2SW4; + uint8_t UNK24; + struct _button P2SW5; + uint8_t UNK25; + struct _button P2SW6; + uint8_t UNK26; + struct _button P2SW7; + uint8_t UNK27; +}; + +struct _light { + uint8_t l_unk : 7; + uint8_t l_state : 1; +}; + +struct bio2_bi2a_state_in { + struct _light UNK1[3]; + struct _light PANEL[4]; + struct _light DECKSW[14]; + uint8_t UNK2[2]; + uint8_t SEG16[9]; + struct _light SPOTLIGHT1[4]; + struct _light NEONLAMP; + struct _light SPOTLIGHT2[4]; + uint8_t UNK3[7]; +}; +_Static_assert(sizeof(struct bio2_bi2a_state_in) == 48, "bio2_bi2a_state_in is the wrong size"); +#pragma pack(pop) + +void bio2_emu_bi2a_init(struct ac_io_emu *in, bool disable_poll_limiter); +void bio2_emu_bi2a_dispatch_request(const struct ac_io_message *req); + +#endif diff --git a/src/main/iidxhook8/bio2.c b/src/main/iidxhook8/bio2.c new file mode 100644 index 0000000..82c1149 --- /dev/null +++ b/src/main/iidxhook8/bio2.c @@ -0,0 +1,81 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "acioemu/addr.h" +#include "acioemu/emu.h" + +#include "hook/iohook.h" + +#include "iidxhook8/bi2a.h" + +#include "imports/avs.h" + +#include "util/defs.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +static struct ac_io_emu bio2_emu; + +void bio2_port_init(bool disable_poll_limiter) +{ + // yes I'm using acio + // they use the same framing + ac_io_emu_init(&bio2_emu, L"COM4"); + bio2_emu_bi2a_init(&bio2_emu, disable_poll_limiter); +} + +void bio2_port_fini(void) +{ + ac_io_emu_fini(&bio2_emu); +} + +HRESULT bio2_port_dispatch_irp(struct irp *irp) +{ + const struct ac_io_message *msg; + HRESULT hr; + + log_assert(irp != NULL); + + if (!ac_io_emu_match_irp(&bio2_emu, irp)) { + return irp_invoke_next(irp); + } + + for (;;) { + hr = ac_io_emu_dispatch_irp(&bio2_emu, irp); + + if (hr != S_OK) { + return hr; + } + + msg = ac_io_emu_request_peek(&bio2_emu); + + switch (msg->addr) { + case 0: + ac_io_emu_cmd_assign_addrs(&bio2_emu, msg, 1); + break; + + case 1: + bio2_emu_bi2a_dispatch_request(msg); + break; + + case AC_IO_BROADCAST: + log_warning("Broadcast(?) message on IIDX BIO2 bus?"); + break; + + default: + log_warning("BIO2 message on unhandled bus address: %d", msg->addr); + break; + } + + ac_io_emu_request_pop(&bio2_emu); + } +} diff --git a/src/main/iidxhook8/bio2.h b/src/main/iidxhook8/bio2.h new file mode 100644 index 0000000..1894fac --- /dev/null +++ b/src/main/iidxhook8/bio2.h @@ -0,0 +1,14 @@ +#ifndef IIDXHOOK_BIO2_H +#define IIDXHOOK_BIO2_H + +#include + +#include + +#include "hook/iohook.h" + +void bio2_port_init(bool disable_poll_limiter); +void bio2_port_fini(void); +HRESULT bio2_port_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/iidxhook8/cam.c b/src/main/iidxhook8/cam.c new file mode 100644 index 0000000..83f923b --- /dev/null +++ b/src/main/iidxhook8/cam.c @@ -0,0 +1,731 @@ +#define LOG_MODULE "cam-hook" + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "iidxhook8/cam.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" +#include "util/time.h" + +#define CAMERA_DATA_STRING_SIZE 0x100 + +EXTERN_GUID(MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, 0xc60ac5fe, 0x252a, 0x478f, 0xa0, 0xef, 0xbc, 0x8f, 0xa5, 0xf7, 0xca, 0xd3); +EXTERN_GUID(MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID, 0x8ac3587a, 0x4ae7, 0x42d8, 0x99, 0xe0, 0x0a, 0x60, 0x13, 0xee, 0xf9, 0x0f); +EXTERN_GUID(MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, 0x58f0aad8, 0x22bf, 0x4f8a, 0xbb, 0x3d, 0xd2, 0xc4, 0x97, 0x8c, 0x6e, 0x2f); +// define ourselves cause mingw has these wrong + +struct CameraData { + bool setup; + char name[CAMERA_DATA_STRING_SIZE]; + char deviceInstancePath[CAMERA_DATA_STRING_SIZE]; + wchar_t deviceSymbolicLink[CAMERA_DATA_STRING_SIZE]; + char extra_upper[CAMERA_DATA_STRING_SIZE]; + int address; + char parent_name[CAMERA_DATA_STRING_SIZE]; + char parent_deviceInstancePath[CAMERA_DATA_STRING_SIZE]; + int parent_address; +}; + +static struct CameraData camData[2]; + + +static CONFIGRET my_CM_Locate_DevNodeA( + PDEVINST pdnDevInst, + DEVINSTID_A pDeviceID, + ULONG ulFlags +); + +static CONFIGRET my_CM_Get_Parent( + PDEVINST pdnDevInst, + DEVINST dnDevInst, + ULONG ulFlags +); + +static CONFIGRET my_CM_Get_Device_IDA( + DEVINST dnDevInst, + PSTR Buffer, + ULONG BufferLen, + ULONG ulFlags +); + +static CONFIGRET(*real_CM_Locate_DevNodeA)( + PDEVINST pdnDevInst, + DEVINSTID_A pDeviceID, + ULONG ulFlags +); + +static CONFIGRET(*real_CM_Get_Parent)( + PDEVINST pdnDevInst, + DEVINST dnDevInst, + ULONG ulFlags +); + +static CONFIGRET(*real_CM_Get_Device_IDA)( + DEVINST dnDevInst, + PSTR Buffer, + ULONG BufferLen, + ULONG ulFlags +); + +static HRESULT my_MFEnumDeviceSources( + IMFAttributes *pAttributes, + IMFActivate ***pppSourceActivate, + UINT32 *pcSourceActivate +); + +static HRESULT (*real_MFEnumDeviceSources)( + IMFAttributes *pAttributes, + IMFActivate ***pppSourceActivate, + UINT32 *pcSourceActivate +); + +static BOOL my_SetupDiDestroyDeviceInfoList( + HDEVINFO DeviceInfoSet +); + +static BOOL (*real_SetupDiDestroyDeviceInfoList)( + HDEVINFO DeviceInfoSet +); + +static BOOL my_SetupDiEnumDeviceInfo( + HDEVINFO DeviceInfoSet, + DWORD MemberIndex, + PSP_DEVINFO_DATA DeviceInfoData +); + +static BOOL (*real_SetupDiEnumDeviceInfo)( + HDEVINFO DeviceInfoSet, + DWORD MemberIndex, + PSP_DEVINFO_DATA DeviceInfoData +); + +static BOOL my_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + DWORD Property, + PDWORD PropertyRegDataType, + PBYTE PropertyBuffer, + DWORD PropertyBufferSize, + PDWORD RequiredSize +); + +static BOOL (*real_SetupDiGetDeviceRegistryPropertyA)( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + DWORD Property, + PDWORD PropertyRegDataType, + PBYTE PropertyBuffer, + DWORD PropertyBufferSize, + PDWORD RequiredSize +); + +static HDEVINFO my_SetupDiGetClassDevsA( + CONST GUID *ClassGuid, + PCSTR Enumerator, + HWND hwndParent, + DWORD Flags +); + +static HDEVINFO(*real_SetupDiGetClassDevsA)( + CONST GUID *ClassGuid, + PCSTR Enumerator, + HWND hwndParent, + DWORD Flags +); + +static const struct hook_symbol iidxhook5_cfgmgr32_syms[] = { + { + .name = "CM_Locate_DevNodeA", + .patch = my_CM_Locate_DevNodeA, + .link = (void **) &real_CM_Locate_DevNodeA + }, + { + .name = "CM_Get_Parent", + .patch = my_CM_Get_Parent, + .link = (void **) &real_CM_Get_Parent + }, + { + .name = "CM_Get_Device_IDA", + .patch = my_CM_Get_Device_IDA, + .link = (void **) &real_CM_Get_Device_IDA + }, + { + .name = "SetupDiDestroyDeviceInfoList", + .patch = my_SetupDiDestroyDeviceInfoList, + .link = (void **) &real_SetupDiDestroyDeviceInfoList + }, + { + .name = "SetupDiEnumDeviceInfo", + .patch = my_SetupDiEnumDeviceInfo, + .link = (void **) &real_SetupDiEnumDeviceInfo + }, + { + .name = "SetupDiGetDeviceRegistryPropertyA", + .patch = my_SetupDiGetDeviceRegistryPropertyA, + .link = (void **) &real_SetupDiGetDeviceRegistryPropertyA + }, + { + .name = "SetupDiGetClassDevsA", + .patch = my_SetupDiGetClassDevsA, + .link = (void **) &real_SetupDiGetClassDevsA + }, +}; + +static const struct hook_symbol iidxhook5_mf_syms[] = { + { + .name = "MFEnumDeviceSources", + .patch = my_MFEnumDeviceSources, + .link = (void **) &real_MFEnumDeviceSources + }, +}; + +#define CUSTOM_DEV_NODE1 0x04040004 +#define CUSTOM_DEV_NODE2 0x04040008 + +#define CUSTOM_DEV_PARENT_NODE1 0x04040014 +#define CUSTOM_DEV_PARENT_NODE2 0x04040018 + +static CONFIGRET my_CM_Locate_DevNodeA( + PDEVINST pdnDevInst, + DEVINSTID_A pDeviceID, + ULONG ulFlags +){ + log_info("Inside: %s", __FUNCTION__); + char builtString1[CAMERA_DATA_STRING_SIZE] = {0}; + char builtString2[CAMERA_DATA_STRING_SIZE] = {0}; + + if (camData[0].setup) { + snprintf(builtString1, CAMERA_DATA_STRING_SIZE, "USB\\VID_288C&PID_0002&MI_00\\%s", camData[0].extra_upper); + if(camData[1].setup) { + snprintf(builtString2, CAMERA_DATA_STRING_SIZE, "USB\\VID_288C&PID_0002&MI_00\\%s", camData[1].extra_upper); + } + } else if(camData[1].setup) { + snprintf(builtString1, CAMERA_DATA_STRING_SIZE, "USB\\VID_288C&PID_0002&MI_00\\%s", camData[1].extra_upper); + } + + if (pdnDevInst) { + if (strcmp(pDeviceID, builtString1) == 0) { + log_info("Injecting custom device 1"); + *pdnDevInst = CUSTOM_DEV_NODE1; + return CR_SUCCESS; + } + if (strcmp(pDeviceID, builtString2) == 0) { + log_info("Injecting custom device 2"); + *pdnDevInst = CUSTOM_DEV_NODE2; + return CR_SUCCESS; + } + } + return real_CM_Locate_DevNodeA(pdnDevInst, pDeviceID, ulFlags); +} + +static CONFIGRET my_CM_Get_Parent( + PDEVINST pdnDevInst, + DEVINST dnDevInst, + ULONG ulFlags +){ + log_info("Inside: %s", __FUNCTION__); + + if (pdnDevInst) { + if (dnDevInst == CUSTOM_DEV_NODE1) { + log_info("Injecting custom parent 1"); + *pdnDevInst = CUSTOM_DEV_PARENT_NODE1; + return CR_SUCCESS; + } + if (dnDevInst == CUSTOM_DEV_NODE2) { + log_info("Injecting custom parent 2"); + *pdnDevInst = CUSTOM_DEV_PARENT_NODE2; + return CR_SUCCESS; + } + } + + return real_CM_Get_Parent(pdnDevInst, dnDevInst, ulFlags); +} + +static CONFIGRET my_CM_Get_Device_IDA( + DEVINST dnDevInst, + PSTR Buffer, + ULONG BufferLen, + ULONG ulFlags +){ + log_info("Inside: %s", __FUNCTION__); + + if (Buffer) { + if (dnDevInst == CUSTOM_DEV_PARENT_NODE1) { + log_info("Injecting custom parent 1 ID"); + strncpy(Buffer, "USB\\VEN_1022&DEV_7908", BufferLen); + Buffer[BufferLen - 1] = '\0'; + log_info("%s", Buffer); + return CR_SUCCESS; + } + if (dnDevInst == CUSTOM_DEV_PARENT_NODE2) { + log_info("Injecting custom parent 2 ID"); + strncpy(Buffer, "USB\\VEN_1022&DEV_7914", BufferLen); + Buffer[BufferLen - 1] = '\0'; + log_info("%s", Buffer); + return CR_SUCCESS; + } + } + return real_CM_Get_Device_IDA(dnDevInst, Buffer, BufferLen, ulFlags); +} + + +static HRESULT (STDCALL *real_GetAllocatedString)( + IMFActivate* self, + REFGUID guidKey, + LPWSTR *ppwszValue, + UINT32 *pcchLength); + +HRESULT my_GetAllocatedString( + IMFActivate* self, + REFGUID guidKey, + LPWSTR *ppwszValue, + UINT32 *pcchLength +){ + HRESULT ret; + log_info("Inside: %s", __FUNCTION__); + + // should probably check GUID, oh well + ret = real_GetAllocatedString(self, guidKey, ppwszValue, pcchLength); + log_info("Obtained: %ls", *ppwszValue); + + wchar_t *pwc = NULL; + + if (camData[0].setup) { + pwc = wcsstr(*ppwszValue, camData[0].deviceSymbolicLink); + } + if (camData[1].setup) { + if (!pwc) { + pwc = wcsstr(*ppwszValue, camData[1].deviceSymbolicLink); + } + } + + if (pwc){ + // \\?\usb#vid_288c&pid_0002&mi_00 + pwc[12] = L'2'; + pwc[13] = L'8'; + pwc[14] = L'8'; + pwc[15] = L'c'; + + pwc[21] = L'0'; + pwc[22] = L'0'; + pwc[23] = L'0'; + pwc[24] = L'2'; + + pwc[29] = L'0'; + pwc[30] = L'0'; + log_info("Replaced: %ls", *ppwszValue); + } + + return ret; + +} + +static HRESULT my_MFEnumDeviceSources( + IMFAttributes *pAttributes, + IMFActivate ***pppSourceActivate, + UINT32 *pcSourceActivate +){ + IMFActivate *api; + IMFActivateVtbl *api_vtbl; + struct com_proxy *api_proxy; + + UINT32 nsrcs; + + HRESULT ret; + + + log_info("Inside: %s", __FUNCTION__); + ret = real_MFEnumDeviceSources(pAttributes, pppSourceActivate, pcSourceActivate); + nsrcs = *pcSourceActivate; + + for (UINT32 i = 0; i < nsrcs; ++i) { + api = (*pppSourceActivate)[i]; + api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl)); + api_vtbl = api_proxy->vptr; + + real_GetAllocatedString = api_vtbl->GetAllocatedString; + api_vtbl->GetAllocatedString = my_GetAllocatedString; + + (*pppSourceActivate)[i] = (IMFActivate *)api_proxy; + } + + + return ret; +} + +#define CUSTOM_DEVICE_HANDLE (void*)0x04041110 +static HDEVINFO RealReplacedHandle; + +static BOOL my_SetupDiDestroyDeviceInfoList( + HDEVINFO DeviceInfoSet +){ + if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE){ + log_info("Inside: %s", __FUNCTION__); + DeviceInfoSet = RealReplacedHandle; + RealReplacedHandle = NULL; + + return real_SetupDiDestroyDeviceInfoList(DeviceInfoSet); + } + return real_SetupDiDestroyDeviceInfoList(DeviceInfoSet); +} + +static BOOL my_SetupDiEnumDeviceInfo( + HDEVINFO DeviceInfoSet, + DWORD MemberIndex, + PSP_DEVINFO_DATA DeviceInfoData +){ + if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE) { + // log_info("Inside: %s", __FUNCTION__); + return real_SetupDiEnumDeviceInfo(RealReplacedHandle, MemberIndex, DeviceInfoData); + } + return real_SetupDiEnumDeviceInfo(DeviceInfoSet, MemberIndex, DeviceInfoData); +} + +static BOOL my_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + DWORD Property, + PDWORD PropertyRegDataType, + PBYTE PropertyBuffer, + DWORD PropertyBufferSize, + PDWORD RequiredSize +){ + if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE) { + BOOL ret = real_SetupDiGetDeviceRegistryPropertyA(RealReplacedHandle, DeviceInfoData, Property, PropertyRegDataType, PropertyBuffer, PropertyBufferSize, RequiredSize); + + if (Property == SPDRP_DEVICEDESC) { + if (PropertyBuffer){ + if (camData[0].setup) { + if (strcmp((char*)PropertyBuffer, camData[0].parent_name) == 0) { + log_info("%s: replacing %s", __FUNCTION__, camData[0].parent_name); + strncpy((char*)PropertyBuffer, "USB Composite Device", PropertyBufferSize); + } + } + if (camData[1].setup) { + if (strcmp((char*)PropertyBuffer, camData[1].parent_name) == 0) { + log_info("%s: replacing %s", __FUNCTION__, camData[1].parent_name); + strncpy((char*)PropertyBuffer, "USB Composite Device", PropertyBufferSize); + } + } + } + + return ret; + } else if (Property == SPDRP_ADDRESS) { + if (PropertyBuffer){ + int addr = *(int*)PropertyBuffer; + + if (camData[0].setup) { + if (addr == camData[0].parent_address) { + log_info("%s: replacing addr1", __FUNCTION__); + *(int*)PropertyBuffer = 1; + } else if (camData[1].setup) { + if (addr == camData[1].parent_address) { + log_info("%s: replacing addr7", __FUNCTION__); + *(int*)PropertyBuffer = 7; + } + } + } else if (camData[1].setup) { + if (addr == camData[1].parent_address) { + log_info("%s: replacing addr1 (alt)", __FUNCTION__); + *(int*)PropertyBuffer = 1; + } + } + } + + return ret; + } + } + return real_SetupDiGetDeviceRegistryPropertyA(DeviceInfoSet, DeviceInfoData, Property, PropertyRegDataType, PropertyBuffer, PropertyBufferSize, RequiredSize); +} + +static HDEVINFO my_SetupDiGetClassDevsA( + CONST GUID *ClassGuid, + PCSTR Enumerator, + HWND hwndParent, + DWORD Flags +){ + if (ClassGuid == NULL && Enumerator != NULL && hwndParent == NULL && Flags == (DIGCF_PRESENT | DIGCF_ALLCLASSES)) { + if (RealReplacedHandle) { + log_info("Replacement handle is already set?"); + } + if (strcmp(Enumerator, "USB") == 0) { + log_info("Inside: %s", __FUNCTION__); + RealReplacedHandle = real_SetupDiGetClassDevsA(ClassGuid, Enumerator, hwndParent, Flags); + if (RealReplacedHandle == INVALID_HANDLE_VALUE) { + return INVALID_HANDLE_VALUE; + } + return CUSTOM_DEVICE_HANDLE; + } + } + return real_SetupDiGetClassDevsA(ClassGuid, Enumerator, hwndParent, Flags); +} + +bool check_four(const char inA[4], const char inB[4]) { + return (*(uint32_t*)inA == *(uint32_t*)inB); +} + +char* grab_next_camera_id(char* buffer, size_t bsz) { + static size_t gotten = 0; + + IMFAttributes *pAttributes = NULL; + IMFActivate **ppDevices = NULL; + + buffer[0] = '\0'; + + HRESULT hr = MFCreateAttributes(&pAttributes, 1); + if (FAILED(hr)) { + log_info("MFCreateAttributes failed: %ld", hr); + goto done; + } + + hr = pAttributes->lpVtbl->SetGUID(pAttributes, &MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + if (FAILED(hr)) { + log_info("SetGUID failed: %ld", hr); + goto done; + } + + UINT32 count; + hr = MFEnumDeviceSources(pAttributes, &ppDevices, &count); + if (FAILED(hr)) { + log_info("MFEnumDeviceSources failed: %ld", hr); + goto done; + } + + if (count <= gotten) { + log_info("gotten failed: %d < %d", count, (int)gotten); + // not enough remaining + goto done; + } + + wchar_t wSymLink[CAMERA_DATA_STRING_SIZE]; + UINT32 sz; + + hr = ppDevices[gotten]->lpVtbl->GetString(ppDevices[gotten], &MY_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, wSymLink, CAMERA_DATA_STRING_SIZE, &sz); + if (FAILED(hr)) { + log_info("GetString failed: %ld", hr); + goto done; + } + log_info("Detected webcam: %s\n", buffer); + wcstombs(buffer, wSymLink, bsz); + ++gotten; + +done: + if (pAttributes) { + pAttributes->lpVtbl->Release(pAttributes); + } + + for (DWORD i = 0; i < count; i++) { + if (ppDevices != NULL && ppDevices[i]) { + ppDevices[i]->lpVtbl->Release(ppDevices[i]); + } + } + CoTaskMemFree(ppDevices); + + return buffer; +} + +bool convert_sym_to_path(const char* sym, char* path) { + HDEVINFO DeviceInfoSet = SetupDiCreateDeviceInfoList(NULL, NULL); + if (DeviceInfoSet == INVALID_HANDLE_VALUE) { + log_info("Could not open SetupDiCreateDeviceInfoList\n"); + return 0; + } + + SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = {0}; + DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + if (!SetupDiOpenDeviceInterfaceA(DeviceInfoSet, sym, 0, &DeviceInterfaceData)) { + log_info("Could not SetupDiOpenDeviceInterfaceA\n"); + return 0; + } + + SP_DEVINFO_DATA DeviceInfoData = {0}; + DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + + if (!SetupDiGetDeviceInterfaceDetailA(DeviceInfoSet, &DeviceInterfaceData, NULL, 0, NULL, &DeviceInfoData)) { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + log_info("Could not SetupDiGetDeviceInterfaceDetailA\n"); + return 0; + } + } + + DWORD sz; + if (!SetupDiGetDeviceInstanceIdA(DeviceInfoSet, &DeviceInfoData, path, CAMERA_DATA_STRING_SIZE, &sz)){ + log_info("Could not SetupDiGetDeviceInstanceIdA\n"); + return 0; + } + + if (DeviceInfoSet != INVALID_HANDLE_VALUE){ + if (!SetupDiDeleteDeviceInterfaceData(DeviceInfoSet, &DeviceInterfaceData)){ + log_info("Could not SetupDiDeleteDeviceInterfaceData\n"); + return 0; + } + SetupDiDestroyDeviceInfoList(DeviceInfoSet); + } + + return 1; +} + +void strtolower(char* str) { + for (size_t i = 0; str[i]; i++){ + str[i] = tolower(str[i]); + } +} + +bool convert_path_to_fakesym(const char* path, wchar_t* sym, char* extra_o) { + char root[16] = {0}; + char vidstr[16] = {0}; + char pidstr[16] = {0}; + char mistr[16] = {0}; + char extra[64] = {0}; + sscanf(path, "%[^\\]\\%[^&]&%[^&]&%[^\\]\\%s", root, vidstr, pidstr, mistr, extra); + strcpy(extra_o, extra); + + strtolower(root); + strtolower(vidstr); + strtolower(pidstr); + strtolower(mistr); + strtolower(extra); + + swprintf(sym, CAMERA_DATA_STRING_SIZE, L"\\\\?\\%S#%S&%S&%S#%S", root, vidstr, pidstr, mistr, extra); + + return true; +} + +void fill_cam_struct(struct CameraData* data, const char* devid) +{ + char buffer[CAMERA_DATA_STRING_SIZE]; + + data->setup = false; + if (!devid || strlen(devid) == 0) { + devid = grab_next_camera_id(buffer, CAMERA_DATA_STRING_SIZE); + } + if (!devid || strlen(devid) == 0) { + // no more cameras remain? + return; + } + + if (strlen(devid) >= CAMERA_DATA_STRING_SIZE) { + // error probably log something? + return; + } + // detect input type + if (check_four(devid, "\\\\?\\")) { + // SYMBOLIC_LINK + if (!convert_sym_to_path(devid, data->deviceInstancePath)){ + log_info("Could not convert %s to path", devid); + return; + } + } else if (check_four(devid, "USB\\")) { + // Device instance path + strcpy(data->deviceInstancePath, devid); + // continue + } else { + // UNKNOWN ENTRY + log_info("UNK: %s", devid); + log_info("Please enter the device instance path"); + return; + } + + if (!convert_path_to_fakesym(data->deviceInstancePath, data->deviceSymbolicLink, data->extra_upper)){ + log_info("Could not convert %s to sym", data->deviceInstancePath); + return; + } + log_info("dev path: %s", data->deviceInstancePath); + + + // locate device nodes + DEVINST dnDevInst; + DEVINST parentDev; + CONFIGRET cmret; + + cmret = CM_Locate_DevNodeA(&dnDevInst, data->deviceInstancePath, CM_LOCATE_DEVNODE_NORMAL); + if (cmret != CR_SUCCESS) { + log_info("CM_Locate_DevNodeA fail: %s", data->deviceInstancePath); + return; + } + + cmret = CM_Get_Parent(&parentDev, dnDevInst, 0); + if (cmret != CR_SUCCESS) { + log_info("CM_Get_Parent fail: %s", data->deviceInstancePath); + return; + } + cmret = CM_Get_Device_IDA(parentDev, data->parent_deviceInstancePath, CAMERA_DATA_STRING_SIZE, 0); + if (cmret != CR_SUCCESS) { + log_info("CM_Get_Device_IDA parent fail: %s", data->deviceInstancePath); + return; + } + + ULONG szAddr; + ULONG szDesc; + + szAddr = 4; + szDesc = CAMERA_DATA_STRING_SIZE; + cmret = CM_Get_DevNode_Registry_PropertyA(dnDevInst, CM_DRP_ADDRESS, NULL, &data->address, &szAddr, 0); + if (cmret != CR_SUCCESS) { + log_info("CM_Get_DevNode_Registry_PropertyA fail: %s", data->deviceInstancePath); + return; + } + cmret = CM_Get_DevNode_Registry_PropertyA(dnDevInst, CM_DRP_DEVICEDESC, NULL, &data->name, &szDesc, 0); + if (cmret != CR_SUCCESS) { + log_info("CM_Get_DevNode_Registry_PropertyA fail: %s", data->deviceInstancePath); + return; + } + + szAddr = 4; + szDesc = CAMERA_DATA_STRING_SIZE; + cmret = CM_Get_DevNode_Registry_PropertyA(parentDev, CM_DRP_ADDRESS, NULL, &data->parent_address, &szAddr, 0); + if (cmret != CR_SUCCESS) { + log_info("CM_Get_DevNode_Registry_PropertyA parent fail: %s", data->deviceInstancePath); + return; + } + cmret = CM_Get_DevNode_Registry_PropertyA(parentDev, CM_DRP_DEVICEDESC, NULL, &data->parent_name, &szDesc, 0); + if (cmret != CR_SUCCESS) { + log_info("CM_Get_DevNode_Registry_PropertyA parent fail: %s", data->deviceInstancePath); + return; + } + + log_info("Found %s @ %d", data->name, data->address); + log_info("Parent %s @ %d", data->parent_name, data->parent_address); + data->setup = true; +} + +void cam_hook_init(const char* devID1, const char* devID2) +{ + // fill before applying hooks + fill_cam_struct(&camData[0], devID1); + fill_cam_struct(&camData[1], devID2); + + if (camData[0].setup || camData[1].setup) { + hook_table_apply( + NULL, + "setupapi.dll", + iidxhook5_cfgmgr32_syms, + lengthof(iidxhook5_cfgmgr32_syms)); + hook_table_apply( + NULL, + "Mf.dll", + iidxhook5_mf_syms, + lengthof(iidxhook5_mf_syms)); + + log_info("Inserted cam hooks"); + } else { + log_info("No cams detected, not hooking"); + } +} + diff --git a/src/main/iidxhook8/cam.h b/src/main/iidxhook8/cam.h new file mode 100644 index 0000000..a7d0e1e --- /dev/null +++ b/src/main/iidxhook8/cam.h @@ -0,0 +1,6 @@ +#ifndef IIDXHOOK5_CAM_H +#define IIDXHOOK5_CAM_H + +void cam_hook_init(const char* devID1, const char* devID2); + +#endif diff --git a/src/main/iidxhook8/config-cam.c b/src/main/iidxhook8/config-cam.c new file mode 100644 index 0000000..10d2650 --- /dev/null +++ b/src/main/iidxhook8/config-cam.c @@ -0,0 +1,61 @@ +#include "cconfig/cconfig-util.h" + +#include "iidxhook8/config-cam.h" + +#include "util/log.h" + +#define IIDXHOOK8_CONFIG_CAM_DISABLE_EMU_KEY "cam.disable_emu" +#define IIDXHOOK8_CONFIG_CAM_DEVICE_ID1_KEY "cam.device_id1" +#define IIDXHOOK8_CONFIG_CAM_DEVICE_ID2_KEY "cam.device_id2" + +#define IIDXHOOK8_CONFIG_CAM_DEFAULT_DISABLE_EMU_VALUE false +#define IIDXHOOK8_CONFIG_CAM_DEFAULT_DEVICE_ID1_VALUE "" +#define IIDXHOOK8_CONFIG_CAM_DEFAULT_DEVICE_ID2_VALUE "" + +void iidxhook8_config_cam_init(struct cconfig* config) +{ + cconfig_util_set_bool(config, + IIDXHOOK8_CONFIG_CAM_DISABLE_EMU_KEY, + IIDXHOOK8_CONFIG_CAM_DEFAULT_DISABLE_EMU_VALUE, + "Disables the camera emulation"); + + cconfig_util_set_str(config, + IIDXHOOK8_CONFIG_CAM_DEVICE_ID1_KEY, + IIDXHOOK8_CONFIG_CAM_DEFAULT_DEVICE_ID1_VALUE, + "Override camera device ID 1 detection (copy from device manager, do " + "not escape)"); + + cconfig_util_set_str(config, + IIDXHOOK8_CONFIG_CAM_DEVICE_ID2_KEY, + IIDXHOOK8_CONFIG_CAM_DEFAULT_DEVICE_ID2_VALUE, + "Override camera device ID 2 detection (copy from device manager, do " + "not escape)"); +} + +void iidxhook8_config_cam_get(struct iidxhook8_config_cam* config_cam, + struct cconfig* config) +{ + if (!cconfig_util_get_bool(config, IIDXHOOK8_CONFIG_CAM_DISABLE_EMU_KEY, + &config_cam->disable_emu, + IIDXHOOK8_CONFIG_CAM_DEFAULT_DISABLE_EMU_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK8_CONFIG_CAM_DISABLE_EMU_KEY, + IIDXHOOK8_CONFIG_CAM_DEFAULT_DISABLE_EMU_VALUE); + } + + if (!cconfig_util_get_str(config, IIDXHOOK8_CONFIG_CAM_DEVICE_ID1_KEY, + config_cam->device_id1, sizeof(config_cam->device_id1) - 1, + IIDXHOOK8_CONFIG_CAM_DEFAULT_DEVICE_ID1_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK8_CONFIG_CAM_DEVICE_ID1_KEY, + IIDXHOOK8_CONFIG_CAM_DEFAULT_DEVICE_ID1_VALUE); + } + + if (!cconfig_util_get_str(config, IIDXHOOK8_CONFIG_CAM_DEVICE_ID2_KEY, + config_cam->device_id2, sizeof(config_cam->device_id2) - 1, + IIDXHOOK8_CONFIG_CAM_DEFAULT_DEVICE_ID2_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", IIDXHOOK8_CONFIG_CAM_DEVICE_ID2_KEY, + IIDXHOOK8_CONFIG_CAM_DEFAULT_DEVICE_ID2_VALUE); + } +} diff --git a/src/main/iidxhook8/config-cam.h b/src/main/iidxhook8/config-cam.h new file mode 100644 index 0000000..7251aee --- /dev/null +++ b/src/main/iidxhook8/config-cam.h @@ -0,0 +1,19 @@ +#ifndef IIDXHOOK8_CONFIG_CAM_H +#define IIDXHOOK8_CONFIG_CAM_H + +#include + +#include "cconfig/cconfig.h" + +struct iidxhook8_config_cam { + bool disable_emu; + char device_id1[MAX_PATH]; + char device_id2[MAX_PATH]; +}; + +void iidxhook8_config_cam_init(struct cconfig* config); + +void iidxhook8_config_cam_get(struct iidxhook8_config_cam* config_cam, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/iidxhook8/config-io.c b/src/main/iidxhook8/config-io.c new file mode 100644 index 0000000..3fc97cd --- /dev/null +++ b/src/main/iidxhook8/config-io.c @@ -0,0 +1,63 @@ +#include "cconfig/cconfig-util.h" + +#include "iidxhook8/config-io.h" + +#include "util/log.h" + +#define IIDXHOOK8_CONFIG_IO_DISABLE_CARD_READER_EMU_KEY "io.disable_card_reader_emu" +#define IIDXHOOK8_CONFIG_IO_DISABLE_BIO2_EMU_KEY "io.disable_bio2_emu" +#define IIDXHOOK8_CONFIG_IO_DISABLE_POLL_LIMITER_KEY "io.disable_poll_limiter" + +#define IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_CARD_READER_EMU_VALUE false +#define IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_BIO2_EMU_VALUE false +#define IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_POLL_LIMITER_VALUE false + +void iidxhook8_config_io_init(struct cconfig* config) +{ + cconfig_util_set_bool(config, + IIDXHOOK8_CONFIG_IO_DISABLE_CARD_READER_EMU_KEY, + IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_CARD_READER_EMU_VALUE, + "Disable card reader emulation and enable usage of real card reader " + "hardware on COM0 (for games supporting slotted readers)"); + + cconfig_util_set_bool(config, + IIDXHOOK8_CONFIG_IO_DISABLE_BIO2_EMU_KEY, + IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_BIO2_EMU_VALUE, + "Disable BIO2 emulation and enable usage of real BIO2 hardware"); + + cconfig_util_set_bool(config, + IIDXHOOK8_CONFIG_IO_DISABLE_POLL_LIMITER_KEY, + IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_POLL_LIMITER_VALUE, + "Disables the poll limiter, warning very high CPU usage may arise"); +} + +void iidxhook8_config_io_get(struct iidxhook8_config_io* config_io, + struct cconfig* config) +{ + if (!cconfig_util_get_bool(config, + IIDXHOOK8_CONFIG_IO_DISABLE_CARD_READER_EMU_KEY, + &config_io->disable_card_reader_emu, + IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_CARD_READER_EMU_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK8_CONFIG_IO_DISABLE_CARD_READER_EMU_KEY, + IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_CARD_READER_EMU_VALUE); + } + + if (!cconfig_util_get_bool(config, + IIDXHOOK8_CONFIG_IO_DISABLE_BIO2_EMU_KEY, + &config_io->disable_bio2_emu, + IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_BIO2_EMU_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK8_CONFIG_IO_DISABLE_BIO2_EMU_KEY, + IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_BIO2_EMU_VALUE); + } + + if (!cconfig_util_get_bool(config, + IIDXHOOK8_CONFIG_IO_DISABLE_POLL_LIMITER_KEY, + &config_io->disable_poll_limiter, + IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_POLL_LIMITER_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", IIDXHOOK8_CONFIG_IO_DISABLE_POLL_LIMITER_KEY, + IIDXHOOK8_CONFIG_IO_DEFAULT_DISABLE_POLL_LIMITER_VALUE); + } +} diff --git a/src/main/iidxhook8/config-io.h b/src/main/iidxhook8/config-io.h new file mode 100644 index 0000000..52b8957 --- /dev/null +++ b/src/main/iidxhook8/config-io.h @@ -0,0 +1,19 @@ +#ifndef IIDXHOOK8_CONFIG_IO_H +#define IIDXHOOK8_CONFIG_IO_H + +#include + +#include "cconfig/cconfig.h" + +struct iidxhook8_config_io { + bool disable_card_reader_emu; + bool disable_bio2_emu; + bool disable_poll_limiter; +}; + +void iidxhook8_config_io_init(struct cconfig* config); + +void iidxhook8_config_io_get(struct iidxhook8_config_io* config_io, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/iidxhook8/dllmain.c b/src/main/iidxhook8/dllmain.c new file mode 100644 index 0000000..47178d7 --- /dev/null +++ b/src/main/iidxhook8/dllmain.c @@ -0,0 +1,194 @@ +#include + +#include +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/iidxio.h" + +#include "cconfig/cconfig-hook.h" + +#include "hooklib/acp.h" +#include "hooklib/adapter.h" +#include "hooklib/app.h" +#include "hooklib/rs232.h" +#include "hooklib/setupapi.h" + +#include "iidxhook-util/acio.h" +#include "iidxhook-util/config-gfx.h" +#include "iidxhook-util/d3d9.h" +#include "iidxhook-util/log-server.h" + +#include "iidxhook8/bio2.h" +#include "iidxhook8/cam.h" +#include "iidxhook8/config-cam.h" +#include "iidxhook8/config-io.h" +#include "iidxhook8/setupapi.h" +#include "iidxhook8/cam.h" + +#include "imports/avs.h" + +#include "util/log.h" +#include "util/str.h" +#include "util/thread.h" + +#define IIDXHOOK8_INFO_HEADER \ + "iidxhook for Cannon Ballers" \ + ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" +#define IIDXHOOK8_CMD_USAGE \ + "Usage: launcher.exe -K iidxhook8.dll [options...]" + +static const irp_handler_t iidxhook_handlers[] = { + iidxhook_util_acio_dispatch_irp, + bio2_port_dispatch_irp, +}; + +struct iidxhook8_config_io iidxhook8_config_io; + +static bool my_dll_entry_init(char *sidcode, struct property_node *param) +{ + struct cconfig* config; + + struct iidxhook_config_gfx config_gfx; + struct iidxhook8_config_cam config_cam; + + // log_server_init is not required anymore + + log_info("-------------------------------------------------------------"); + log_info("--------------- Begin iidxhook dll_entry_init ---------------"); + log_info("-------------------------------------------------------------"); + + config = cconfig_init(); + + iidxhook_config_gfx_init(config); + iidxhook8_config_cam_init(config); + iidxhook8_config_io_init(config); + + if (!cconfig_hook_config_init(config, IIDXHOOK8_INFO_HEADER "\n" IIDXHOOK8_CMD_USAGE, CCONFIG_CMD_USAGE_OUT_DBG)) { + cconfig_finit(config); + log_server_fini(); + exit(EXIT_FAILURE); + } + + iidxhook_config_gfx_get(&config_gfx, config); + iidxhook8_config_cam_get(&config_cam, config); + iidxhook8_config_io_get(&iidxhook8_config_io, config); + + cconfig_finit(config); + + log_info(IIDXHOOK8_INFO_HEADER); + log_info("Initializing iidxhook..."); + + if (config_gfx.windowed) { + d3d9_set_windowed(config_gfx.framed, config_gfx.window_width, + config_gfx.window_height); + } + + if (config_gfx.pci_id_pid != 0 && config_gfx.pci_id_vid != 0) { + d3d9_set_pci_id(config_gfx.pci_id_pid, config_gfx.pci_id_vid); + } + + if (config_gfx.frame_rate_limit > 0) { + d3d9_set_frame_rate_limit(config_gfx.frame_rate_limit); + } + + if (config_gfx.scale_back_buffer_width > 0 && config_gfx.scale_back_buffer_height > 0) { + d3d9_scale_back_buffer(config_gfx.scale_back_buffer_width, config_gfx.scale_back_buffer_height, + config_gfx.scale_back_buffer_filter); + } + + /* Start up IIDXIO.DLL */ + if (!iidxhook8_config_io.disable_bio2_emu) { + log_info("Starting IIDX IO backend"); + iidx_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!iidx_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing IIDX IO backend failed"); + } + } + + /* Start up EAMIO.DLL */ + if (!iidxhook8_config_io.disable_card_reader_emu) { + log_misc("Initializing card reader backend"); + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy)) { + log_fatal("Initializing card reader backend failed"); + } + } + + /* iohooks are okay, even if emu is diabled since the fake handlers won't be used */ + /* Set up IO emulation hooks _after_ IO API setup to allow + API implementations with real IO devices */ + iohook_init(iidxhook_handlers, lengthof(iidxhook_handlers)); + rs232_hook_init(); + + if (!iidxhook8_config_io.disable_bio2_emu) { + bio2_port_init(iidxhook8_config_io.disable_poll_limiter); + setupapi_hook_init(); + } + + if (!iidxhook8_config_io.disable_card_reader_emu) { + iidxhook_util_acio_init(false); + } + + // camera hooks + if (!config_cam.disable_emu) { + cam_hook_init(config_cam.device_id1, config_cam.device_id2); + } + + log_info("-------------------------------------------------------------"); + log_info("---------------- End iidxhook dll_entry_init ----------------"); + log_info("-------------------------------------------------------------"); + + return app_hook_invoke_init(sidcode, param); +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + if (!iidxhook8_config_io.disable_card_reader_emu) { + log_misc("Shutting down card reader backend"); + eam_io_fini(); + } + + if (!iidxhook8_config_io.disable_bio2_emu) { + log_misc("Shutting down IIDX IO backend"); + iidx_io_fini(); + } + + return result; +} + +/** + * Hook library Resort Anthem onwards + */ +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason != DLL_PROCESS_ATTACH) { + goto end; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + + acp_hook_init(); + adapter_hook_init(); + d3d9_hook_init(); + +end: + return TRUE; +} + diff --git a/src/main/iidxhook8/iidxhook8.def b/src/main/iidxhook8/iidxhook8.def new file mode 100644 index 0000000..8451e8a --- /dev/null +++ b/src/main/iidxhook8/iidxhook8.def @@ -0,0 +1,4 @@ +LIBRARY iidxhook8 + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/iidxhook8/setupapi.c b/src/main/iidxhook8/setupapi.c new file mode 100644 index 0000000..e790b3e --- /dev/null +++ b/src/main/iidxhook8/setupapi.c @@ -0,0 +1,287 @@ +#define LOG_MODULE "setupapi-hook" + +#include +#include +#include + +#include "hook/table.h" + +#include "iidxhook8/setupapi.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" +#include "util/time.h" + +static BOOL my_SetupDiDestroyDeviceInfoList( + HDEVINFO DeviceInfoSet +); + +static BOOL (*real_SetupDiDestroyDeviceInfoList)( + HDEVINFO DeviceInfoSet +); + +static BOOL my_SetupDiEnumDeviceInfo( + HDEVINFO DeviceInfoSet, + DWORD MemberIndex, + PSP_DEVINFO_DATA DeviceInfoData +); + +static BOOL (*real_SetupDiEnumDeviceInfo)( + HDEVINFO DeviceInfoSet, + DWORD MemberIndex, + PSP_DEVINFO_DATA DeviceInfoData +); + +static HKEY my_SetupDiOpenDevRegKey( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + DWORD Scope, + DWORD HwProfile, + DWORD KeyType, + REGSAM samDesired +); + +static HKEY (*real_SetupDiOpenDevRegKey)( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + DWORD Scope, + DWORD HwProfile, + DWORD KeyType, + REGSAM samDesired +); + +static BOOL my_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + DWORD Property, + PDWORD PropertyRegDataType, + PBYTE PropertyBuffer, + DWORD PropertyBufferSize, + PDWORD RequiredSize +); + +static BOOL (*real_SetupDiGetDeviceRegistryPropertyA)( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + DWORD Property, + PDWORD PropertyRegDataType, + PBYTE PropertyBuffer, + DWORD PropertyBufferSize, + PDWORD RequiredSize +); + +static BOOL my_SetupDiGetDeviceInfoListDetailA( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_LIST_DETAIL_DATA_A DeviceInfoSetDetailData +); + +static BOOL (*real_SetupDiGetDeviceInfoListDetailA)( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_LIST_DETAIL_DATA_A DeviceInfoSetDetailData +); + +static HDEVINFO my_SetupDiGetClassDevsA( + CONST GUID *ClassGuid, + PCSTR Enumerator, + HWND hwndParent, + DWORD Flags +); + +static HDEVINFO(*real_SetupDiGetClassDevsA)( + CONST GUID *ClassGuid, + PCSTR Enumerator, + HWND hwndParent, + DWORD Flags +); + +static const struct hook_symbol iidxhook5_setupapi_syms[] = { + { + .name = "SetupDiDestroyDeviceInfoList", + .patch = my_SetupDiDestroyDeviceInfoList, + .link = (void **) &real_SetupDiDestroyDeviceInfoList + }, + { + .name = "SetupDiEnumDeviceInfo", + .patch = my_SetupDiEnumDeviceInfo, + .link = (void **) &real_SetupDiEnumDeviceInfo + }, + { + .name = "SetupDiOpenDevRegKey", + .patch = my_SetupDiOpenDevRegKey, + .link = (void **) &real_SetupDiOpenDevRegKey + }, + { + .name = "SetupDiGetDeviceRegistryPropertyA", + .patch = my_SetupDiGetDeviceRegistryPropertyA, + .link = (void **) &real_SetupDiGetDeviceRegistryPropertyA + }, + { + .name = "SetupDiGetDeviceInfoListDetailA", + .patch = my_SetupDiGetDeviceInfoListDetailA, + .link = (void **) &real_SetupDiGetDeviceInfoListDetailA + }, + { + .name = "SetupDiGetClassDevsA", + .patch = my_SetupDiGetClassDevsA, + .link = (void **) &real_SetupDiGetClassDevsA + }, +}; + +static LSTATUS my_RegQueryValueExA( + HKEY hKey, + LPCSTR lpValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData +); +static LSTATUS (*real_RegQueryValueExA)( + HKEY hKey, + LPCSTR lpValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData +); + +static const struct hook_symbol iidxhook5_Advapi32_syms[] = { + { + .name = "RegQueryValueExA", + .patch = my_RegQueryValueExA, + .link = (void **) &real_RegQueryValueExA + }, +}; + +#define CUSTOM_DEVICE_HANDLE (void*)0x12341230 +#define CUSTOM_REGISTRY_HANDLE (void*)0x4242ccc0 + +static BOOL my_SetupDiDestroyDeviceInfoList( + HDEVINFO DeviceInfoSet +){ + if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE){ + log_info("Inside: %s", __FUNCTION__); + return true; + } + return real_SetupDiDestroyDeviceInfoList(DeviceInfoSet); +} + +static BOOL my_SetupDiEnumDeviceInfo( + HDEVINFO DeviceInfoSet, + DWORD MemberIndex, + PSP_DEVINFO_DATA DeviceInfoData +){ + if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE){ + log_info("Inside: %s", __FUNCTION__); + return true; + } + return real_SetupDiEnumDeviceInfo(DeviceInfoSet, MemberIndex, DeviceInfoData); +} + +static HKEY my_SetupDiOpenDevRegKey( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + DWORD Scope, + DWORD HwProfile, + DWORD KeyType, + REGSAM samDesired +){ + if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE){ + log_info("Inside: %s", __FUNCTION__); + return CUSTOM_REGISTRY_HANDLE; + } + return real_SetupDiOpenDevRegKey(DeviceInfoSet, DeviceInfoData, Scope, HwProfile, KeyType, samDesired); +} + +static BOOL my_SetupDiGetDeviceRegistryPropertyA( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_DATA DeviceInfoData, + DWORD Property, + PDWORD PropertyRegDataType, + PBYTE PropertyBuffer, + DWORD PropertyBufferSize, + PDWORD RequiredSize +){ + if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE){ + log_info("Inside: %s", __FUNCTION__); + log_info("%s: Found CUSTOM HANDLE", __FUNCTION__); + if (PropertyBuffer && (PropertyBufferSize >= 12)){ + log_info("%s: Copying property name (%ld)", __FUNCTION__, PropertyBufferSize); + strncpy((char*)PropertyBuffer, "BIO2(VIDEO)", PropertyBufferSize); + log_info("%s: Done copying property name", __FUNCTION__); + // Sleep(500); + } else { + log_info("%s: Returning size", __FUNCTION__); + *RequiredSize = 12; + } + log_info("%s: STUB RETURN", __FUNCTION__); + return true; + } + return real_SetupDiGetDeviceRegistryPropertyA(DeviceInfoSet, DeviceInfoData, Property, PropertyRegDataType, PropertyBuffer, PropertyBufferSize, RequiredSize); +} + +static BOOL my_SetupDiGetDeviceInfoListDetailA( + HDEVINFO DeviceInfoSet, + PSP_DEVINFO_LIST_DETAIL_DATA_A DeviceInfoSetDetailData +){ + if (DeviceInfoSet == CUSTOM_DEVICE_HANDLE){ + log_info("Inside: %s", __FUNCTION__); + return true; + } + return real_SetupDiGetDeviceInfoListDetailA(DeviceInfoSet, DeviceInfoSetDetailData); +} + +DEFINE_GUID(GUID_COM_BUS_ENUMERATOR, + 0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18); + +static HDEVINFO my_SetupDiGetClassDevsA( + CONST GUID *ClassGuid, + PCSTR Enumerator, + HWND hwndParent, + DWORD Flags +){ + if (ClassGuid) { + if (IsEqualGUID(ClassGuid, &GUID_COM_BUS_ENUMERATOR)){ + log_info("Inside: %s", __FUNCTION__); + return CUSTOM_DEVICE_HANDLE; + } + } + return real_SetupDiGetClassDevsA(ClassGuid, Enumerator, hwndParent, Flags); +} + +static LSTATUS my_RegQueryValueExA( + HKEY hKey, + LPCSTR lpValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData +){ + if (hKey == CUSTOM_REGISTRY_HANDLE){ + if (strcmp(lpValueName, "PortName") == 0) { + log_info("Inside: %s", __FUNCTION__); + if (lpData){ + strncpy((char*)lpData, "COM4", *lpcbData); + return ERROR_SUCCESS; + } + return ERROR_MORE_DATA; + } + } + return real_RegQueryValueExA(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); +} + +void setupapi_hook_init(void) +{ + hook_table_apply( + NULL, + "setupapi.dll", + iidxhook5_setupapi_syms, + lengthof(iidxhook5_setupapi_syms)); + hook_table_apply( + NULL, + "Advapi32.dll", + iidxhook5_Advapi32_syms, + lengthof(iidxhook5_Advapi32_syms)); + + log_info("Inserted setupapi hooks"); +} diff --git a/src/main/iidxhook8/setupapi.h b/src/main/iidxhook8/setupapi.h new file mode 100644 index 0000000..97a6381 --- /dev/null +++ b/src/main/iidxhook8/setupapi.h @@ -0,0 +1,6 @@ +#ifndef IIDXHOOK_SETUPAPI_H +#define IIDXHOOK_SETUPAPI_H + +void setupapi_hook_init(void); + +#endif diff --git a/src/main/iidxio-ezusb/Module.mk b/src/main/iidxio-ezusb/Module.mk new file mode 100644 index 0000000..ce9982c --- /dev/null +++ b/src/main/iidxio-ezusb/Module.mk @@ -0,0 +1,14 @@ +dlls += iidxio-ezusb + +ldflags_iidxio-ezusb := \ + -lwinmm \ + -lsetupapi \ + +libs_iidxio-ezusb := \ + ezusb \ + ezusb2 \ + ezusb-iidx \ + util \ + +src_iidxio-ezusb := \ + iidxio.c \ diff --git a/src/main/iidxio-ezusb/iidxio-ezusb.def b/src/main/iidxio-ezusb/iidxio-ezusb.def new file mode 100644 index 0000000..403900d --- /dev/null +++ b/src/main/iidxio-ezusb/iidxio-ezusb.def @@ -0,0 +1,18 @@ +LIBRARY iidxio + +EXPORTS + iidx_io_ep1_send + iidx_io_ep1_set_deck_lights + iidx_io_ep1_set_panel_lights + iidx_io_ep1_set_top_lamps + iidx_io_ep1_set_top_neons + iidx_io_ep2_get_keys + iidx_io_ep2_get_panel + iidx_io_ep2_get_sys + iidx_io_ep2_get_slider + iidx_io_ep2_get_turntable + iidx_io_ep2_recv + iidx_io_ep3_write_16seg + iidx_io_fini + iidx_io_init + iidx_io_set_loggers diff --git a/src/main/iidxio-ezusb/iidxio.c b/src/main/iidxio-ezusb/iidxio.c new file mode 100644 index 0000000..0b595dc --- /dev/null +++ b/src/main/iidxio-ezusb/iidxio.c @@ -0,0 +1,215 @@ +#include +#include + +#include +#include +#include + +#include "bemanitools/iidxio.h" + +#include "ezusb/ezusb.h" +#include "ezusb/ezusbsys2.h" + +#include "ezusb-iidx/ezusb-iidx.h" +#include "ezusb-iidx/fpga.h" +#include "ezusb-iidx/seg16-cmd.h" + +#include "util/fs.h" +#include "util/time.h" + +#define log_misc(...) iidx_io_log_misc("iidxio-ezusb", __VA_ARGS__) +#define log_info(...) iidx_io_log_info("iidxio-ezusb", __VA_ARGS__) +#define log_warning(...) iidx_io_log_warning("iidxio-ezusb", __VA_ARGS__) +#define log_fatal(...) iidx_io_log_fatal("iidxio-ezusb", __VA_ARGS__) + +static log_formatter_t iidx_io_log_misc; +static log_formatter_t iidx_io_log_info; +static log_formatter_t iidx_io_log_warning; +static log_formatter_t iidx_io_log_fatal; + +static HANDLE iidx_io_ezusb_handle; + +static struct ezusb_iidx_msg_interrupt_read_packet iidx_io_ezusb_read_packet; +static struct ezusb_iidx_msg_interrupt_write_packet iidx_io_ezusb_write_packet; +static bool iidxio_io_ezusb_16seg_rts; + +void iidx_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + iidx_io_log_misc = misc; + iidx_io_log_info = info; + iidx_io_log_warning = warning; + iidx_io_log_fatal = fatal; +} + +bool iidx_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + struct ezusb_ident ident; + + iidxio_io_ezusb_16seg_rts = false; + + log_info("!!! IMPORTANT: Ensure that you have flashed the correct firmware " + "to your hardware and the FPGA BEFORE running this !!!"); + + log_misc("Opening device path %s...", EZUSB_DEVICE_PATH); + + iidx_io_ezusb_handle = ezusb_open(EZUSB_DEVICE_PATH); + + if (iidx_io_ezusb_handle == INVALID_HANDLE_VALUE) { + log_fatal("Opening ezusb device failed"); + return false; + } else { + if (!ezusb_get_ident(iidx_io_ezusb_handle, &ident)) { + log_fatal("Getting ezusb ident failed"); + return false; + } else { + log_info("Connected ezusb: vid 0x%X, pid 0x%X", ident.vid, + ident.pid); + return true; + } + } +} + +void iidx_io_fini(void) +{ + ezusb_close(iidx_io_ezusb_handle); + iidx_io_ezusb_handle = INVALID_HANDLE_VALUE; +} + +/* Total number of light bits is 33. That's slightly annoying. So, we pack + the neons bit into an unused start btns light. The entire 32-bit word is + then sent to geninput for output light mapping. */ + +void iidx_io_ep1_set_deck_lights(uint16_t deck_lights) +{ + iidx_io_ezusb_write_packet.deck_lights = deck_lights; +} + +void iidx_io_ep1_set_panel_lights(uint8_t panel_lights) +{ + iidx_io_ezusb_write_packet.panel_lights = panel_lights; +} + +void iidx_io_ep1_set_top_lamps(uint8_t top_lamps) +{ + iidx_io_ezusb_write_packet.top_lamps = top_lamps; +} + +void iidx_io_ep1_set_top_neons(bool top_neons) +{ + iidx_io_ezusb_write_packet.top_neons = top_neons ? 1 : 0; +} + +bool iidx_io_ep1_send(void) +{ + BULK_TRANSFER_CONTROL transfer; + uint32_t outpkt; + + iidx_io_ezusb_write_packet.node = EZUSB_IIDX_MSG_NODE_16SEG; + iidx_io_ezusb_write_packet.cmd = EZUSB_IIDX_16SEG_CMD_WRITE; + iidx_io_ezusb_write_packet.cmd_detail[0] = 0; + iidx_io_ezusb_write_packet.cmd_detail[1] = 1; + + transfer.pipeNum = EZUSB_IIDX_MSG_PIPE_INTERRUPT_OUT; + + if (!ezusb_iidx_ioctl(iidx_io_ezusb_handle, IOCTL_EZUSB_BULK_WRITE, + &transfer, sizeof(transfer), &iidx_io_ezusb_write_packet, + sizeof(iidx_io_ezusb_write_packet), &outpkt)) { + log_fatal("Failed to write interrupt endpoint of ezusb"); + return false; + } else { + return true; + } +} + +bool iidx_io_ep2_recv(void) +{ + if (!ezusb_iidx_interrupt_read(iidx_io_ezusb_handle, + &iidx_io_ezusb_read_packet)) { + log_fatal("Failed to read interrupt endpoints of ezusb"); + return false; + } + + // Wait for board to be ready to receive data on bulk endpoint + // On older Windows platforms, just writing the the 16seg bulk endpoint all the + // time was fine, probably because the driver was overall slower than on newer platforms, e.g. Windows 10. + // There, you crash the C02 firmware AND the kernel mode driver if you don't wait for the bulk endpoint + // to become ready before writing to it. + if (iidx_io_ezusb_read_packet.status == EZUSB_IIDX_16SEG_CMD_STATUS_OK) { + iidxio_io_ezusb_16seg_rts = true; + } else { + iidxio_io_ezusb_16seg_rts = false; + } + + return true; +} + +uint8_t iidx_io_ep2_get_turntable(uint8_t player_no) +{ + switch (player_no) { + case 0: + return iidx_io_ezusb_read_packet.p1_turntable; + case 1: + return iidx_io_ezusb_read_packet.p2_turntable; + default: + return 0; + } +} + +uint8_t iidx_io_ep2_get_slider(uint8_t slider_no) +{ + switch (slider_no) { + case 0: + return iidx_io_ezusb_read_packet.sliders[0] & 0xF; + case 1: + return (iidx_io_ezusb_read_packet.sliders[0] >> 4) & 0xF; + case 2: + return iidx_io_ezusb_read_packet.sliders[1] & 0xF; + case 3: + return (iidx_io_ezusb_read_packet.sliders[1] >> 4) & 0xF; + case 4: + return iidx_io_ezusb_read_packet.sliders[2] & 0xF; + default: + return 0; + } +} + +uint8_t iidx_io_ep2_get_sys(void) +{ + return ((~iidx_io_ezusb_read_packet.inverted_pad) >> 28) & 0x03; +} + +uint8_t iidx_io_ep2_get_panel(void) +{ + return ((~iidx_io_ezusb_read_packet.inverted_pad) >> 24) & 0x0F; +} + +uint16_t iidx_io_ep2_get_keys(void) +{ + return ((~iidx_io_ezusb_read_packet.inverted_pad) >> 8) & 0x3FFF; +} + +bool iidx_io_ep3_write_16seg(const char *text) +{ + struct ezusb_iidx_msg_bulk_packet pkg; + + if (iidxio_io_ezusb_16seg_rts) { + pkg.node = EZUSB_IIDX_MSG_NODE_16SEG; + pkg.page = 0; + memset(pkg.payload, ' ', sizeof(pkg.payload)); + memcpy(pkg.payload, text, 9); + + iidxio_io_ezusb_16seg_rts = false; + + Sleep(1); + + if (!ezusb_iidx_bulk_write(iidx_io_ezusb_handle, &pkg)) { + log_fatal("Writing 16seg bulk package failed"); + return false; + } + } + + return true; +} + diff --git a/src/main/iidxio-ezusb2/Module.mk b/src/main/iidxio-ezusb2/Module.mk new file mode 100644 index 0000000..f1f1963 --- /dev/null +++ b/src/main/iidxio-ezusb2/Module.mk @@ -0,0 +1,14 @@ +dlls += iidxio-ezusb2 + +ldflags_iidxio-ezusb2 := \ + -lwinmm \ + -lsetupapi \ + +libs_iidxio-ezusb2 := \ + ezusb2 \ + ezusb \ + ezusb2-iidx \ + util \ + +src_iidxio-ezusb2 := \ + iidxio.c \ diff --git a/src/main/iidxio-ezusb2/iidxio-ezusb2.def b/src/main/iidxio-ezusb2/iidxio-ezusb2.def new file mode 100644 index 0000000..403900d --- /dev/null +++ b/src/main/iidxio-ezusb2/iidxio-ezusb2.def @@ -0,0 +1,18 @@ +LIBRARY iidxio + +EXPORTS + iidx_io_ep1_send + iidx_io_ep1_set_deck_lights + iidx_io_ep1_set_panel_lights + iidx_io_ep1_set_top_lamps + iidx_io_ep1_set_top_neons + iidx_io_ep2_get_keys + iidx_io_ep2_get_panel + iidx_io_ep2_get_sys + iidx_io_ep2_get_slider + iidx_io_ep2_get_turntable + iidx_io_ep2_recv + iidx_io_ep3_write_16seg + iidx_io_fini + iidx_io_init + iidx_io_set_loggers diff --git a/src/main/iidxio-ezusb2/iidxio.c b/src/main/iidxio-ezusb2/iidxio.c new file mode 100644 index 0000000..f5aa698 --- /dev/null +++ b/src/main/iidxio-ezusb2/iidxio.c @@ -0,0 +1,197 @@ +#include +#include +#include + +#include "bemanitools/iidxio.h" + +#include "ezusb/util.h" +#include "ezusb2/ezusb2.h" + +#include "ezusb2-iidx/ezusb2-iidx.h" + +#include "util/fs.h" +#include "util/time.h" + +#define EZUSB2_FIND_TIMEOUT_MS 10000 + +#define log_misc(...) iidx_io_log_misc("iidxio-ezusb2", __VA_ARGS__) +#define log_info(...) iidx_io_log_info("iidxio-ezusb2", __VA_ARGS__) +#define log_warning(...) iidx_io_log_warning("iidxio-ezusb2", __VA_ARGS__) +#define log_fatal(...) iidx_io_log_fatal("iidxio-ezusb2", __VA_ARGS__) + +static log_formatter_t iidx_io_log_misc; +static log_formatter_t iidx_io_log_info; +static log_formatter_t iidx_io_log_warning; +static log_formatter_t iidx_io_log_fatal; + +static HANDLE iidx_io_ezusb2_handle; + +static struct ezusb2_iidx_msg_interrupt_read_packet iidx_io_ezusb2_read_packet; +static struct ezusb2_iidx_msg_interrupt_write_packet iidx_io_ezusb2_write_packet; + +void iidx_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + iidx_io_log_misc = misc; + iidx_io_log_info = info; + iidx_io_log_warning = warning; + iidx_io_log_fatal = fatal; +} + +bool iidx_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + struct ezusb_ident ident; + char* device_path; + uint64_t time_start; + + log_info("!!! IMPORTANT: Ensure that you have flashed the correct firmware " + "to your hardware BEFORE running this !!!"); + + log_misc("Finding connected ezusb2..."); + + time_start = time_get_counter(); + + do { + device_path = ezusb2_find(&EZUSB2_GUID); + + if (device_path) { + break; + } + + log_misc("Failed to find connected ezusb2 device, retry..."); + Sleep(1000); + } while (time_get_elapsed_ms(time_get_counter() - time_start) < + EZUSB2_FIND_TIMEOUT_MS); + + if (!device_path) { + log_fatal("Could not find a connected ezusb2 device"); + return false; + } + + log_misc("Found ezusb2 device at '%s', opening...", device_path); + + iidx_io_ezusb2_handle = ezusb2_open(device_path); + + free(device_path); + + if (iidx_io_ezusb2_handle == INVALID_HANDLE_VALUE) { + log_fatal("Opening ezusb2 device failed"); + return false; + } else { + if (!ezusb2_get_ident(iidx_io_ezusb2_handle, &ident)) { + log_fatal("Getting ezusb2 ident failed"); + return false; + } else { + log_info("Connected ezusb2: name %s, vid 0x%X, pid 0x%X", + ident.name, ident.vid, ident.pid); + return true; + } + } +} + +void iidx_io_fini(void) +{ + ezusb2_close(iidx_io_ezusb2_handle); + iidx_io_ezusb2_handle = INVALID_HANDLE_VALUE; +} + +/* Total number of light bits is 33. That's slightly annoying. So, we pack + the neons bit into an unused start btns light. The entire 32-bit word is + then sent to geninput for output light mapping. */ + +void iidx_io_ep1_set_deck_lights(uint16_t deck_lights) +{ + iidx_io_ezusb2_write_packet.deck_lights = deck_lights; +} + +void iidx_io_ep1_set_panel_lights(uint8_t panel_lights) +{ + iidx_io_ezusb2_write_packet.panel_lights = panel_lights; +} + +void iidx_io_ep1_set_top_lamps(uint8_t top_lamps) +{ + iidx_io_ezusb2_write_packet.top_lamps = top_lamps; +} + +void iidx_io_ep1_set_top_neons(bool top_neons) +{ + iidx_io_ezusb2_write_packet.top_neons = top_neons ? 1 : 0; +} + +bool iidx_io_ep1_send(void) +{ + if (!ezusb2_iidx_interrupt_write(iidx_io_ezusb2_handle, + &iidx_io_ezusb2_write_packet)) { + log_fatal("Failed to write interrupt endpoint of ezusb"); + return false; + } else { + return true; + } +} + +bool iidx_io_ep2_recv(void) +{ + if (!ezusb2_iidx_interrupt_read(iidx_io_ezusb2_handle, + &iidx_io_ezusb2_read_packet)) { + log_fatal("Failed to read interrupt endpoint of ezusb"); + return false; + } else { + return true; + } +} + +uint8_t iidx_io_ep2_get_turntable(uint8_t player_no) +{ + switch (player_no) { + case 0: + return iidx_io_ezusb2_read_packet.p1_turntable; + case 1: + return iidx_io_ezusb2_read_packet.p2_turntable; + default: + return 0; + } +} + +uint8_t iidx_io_ep2_get_slider(uint8_t slider_no) +{ + switch (slider_no) { + case 0: + return iidx_io_ezusb2_read_packet.sliders[0] & 0xF; + case 1: + return (iidx_io_ezusb2_read_packet.sliders[0] >> 4) & 0xF; + case 2: + return iidx_io_ezusb2_read_packet.sliders[1] & 0xF; + case 3: + return (iidx_io_ezusb2_read_packet.sliders[1] >> 4) & 0xF; + case 4: + return iidx_io_ezusb2_read_packet.sliders[2] & 0xF; + default: + return 0; + } +} + +uint8_t iidx_io_ep2_get_sys(void) +{ + return (((~iidx_io_ezusb2_read_packet.inverted_pad) >> 4) & 0x03) | + ((((~iidx_io_ezusb2_read_packet.inverted_pad) >> 30) & 1) << 2); +} + +uint8_t iidx_io_ep2_get_panel(void) +{ + return ((~iidx_io_ezusb2_read_packet.inverted_pad) >> 0) & 0x0F; +} + +uint16_t iidx_io_ep2_get_keys(void) +{ + return ((~iidx_io_ezusb2_read_packet.inverted_pad) >> 16) & 0x3FFF; +} + +bool iidx_io_ep3_write_16seg(const char *text) +{ + /* 16seg writing to device done in ep2 */ + memcpy(iidx_io_ezusb2_write_packet.seg16, text, 9); + return true; +} + diff --git a/src/main/iidxio/Module.mk b/src/main/iidxio/Module.mk new file mode 100644 index 0000000..33aa7e2 --- /dev/null +++ b/src/main/iidxio/Module.mk @@ -0,0 +1,11 @@ +dlls += iidxio + +ldflags_iidxio := \ + -lwinmm + +libs_iidxio := \ + geninput \ + vefxio \ + +src_iidxio := \ + iidxio.c \ diff --git a/src/main/iidxio/iidxio.c b/src/main/iidxio/iidxio.c new file mode 100644 index 0000000..90a637d --- /dev/null +++ b/src/main/iidxio/iidxio.c @@ -0,0 +1,318 @@ +/* This is the source code for the IIDXIO.DLL that ships with Bemanitools 5. + + If you want to add on some minor functionality like a custom 16seg display + or a customer slider board then see vefxio. + + If you want to make a completely custom IO board that handles all input and + lighting then you'd be better off writing your own from scratch. Consult + the "bemanitools" header files included by this source file for detailed + information about the API you'll need to implement. */ + +#include +#include + +#include +#include + +#include "bemanitools/iidxio.h" +#include "bemanitools/vefxio.h" +#include "bemanitools/input.h" + +#define MSEC_PER_NOTCH 8 + +enum iidx_io_pad_bit { + /* Synthetic inputs stuffed into unused bits in the pad word. + + These are not real inputs on the real IO board. Instead, these are + convenience inputs returned from geninput, provided for the benefit of + users who do not have analog turntables. */ + + IIDX_IO_P1_TT_UP = 0x00, + IIDX_IO_P1_TT_DOWN = 0x01, + IIDX_IO_P1_TT_STAB = 0x02, + + IIDX_IO_P2_TT_UP = 0x03, + IIDX_IO_P2_TT_DOWN = 0x04, + IIDX_IO_P2_TT_STAB = 0x05, + + /* mapper_update() bit mappings (0x08 - 0x20) */ + + IIDX_IO_P1_1 = 0x08, + IIDX_IO_P1_2 = 0x09, + IIDX_IO_P1_3 = 0x0A, + IIDX_IO_P1_4 = 0x0B, + IIDX_IO_P1_5 = 0x0C, + IIDX_IO_P1_6 = 0x0D, + IIDX_IO_P1_7 = 0x0E, + + IIDX_IO_P2_1 = 0x0F, + IIDX_IO_P2_2 = 0x10, + IIDX_IO_P2_3 = 0x11, + IIDX_IO_P2_4 = 0x12, + IIDX_IO_P2_5 = 0x13, + IIDX_IO_P2_6 = 0x14, + IIDX_IO_P2_7 = 0x15, + + IIDX_IO_P1_START = 0x18, + IIDX_IO_P2_START = 0x19, + IIDX_IO_VEFX = 0x1A, + IIDX_IO_EFFECT = 0x1B, + IIDX_IO_TEST = 0x1C, + IIDX_IO_SERVICE = 0x1D, +}; + +struct iidx_io_tt_inputs { + uint8_t tt_up; + uint8_t tt_down; + uint8_t tt_stab; + uint8_t start; +}; + +struct iidx_io_tt { + uint32_t last_notch; + uint8_t pos; + uint8_t analog_pos; + int8_t stab_dir; + int8_t last_dir; +}; + +static void iidx_io_tt_update(uint32_t now, uint64_t pad, int i); + +static const struct iidx_io_tt_inputs iidx_io_tt_inputs[2] = { +{ IIDX_IO_P1_TT_UP, IIDX_IO_P1_TT_DOWN, IIDX_IO_P1_TT_STAB, IIDX_IO_P1_START }, +{ IIDX_IO_P2_TT_UP, IIDX_IO_P2_TT_DOWN, IIDX_IO_P2_TT_STAB, IIDX_IO_P2_START }, +}; + +static struct iidx_io_tt iidx_io_tt[2]; +static uint8_t iidx_io_sys; +static uint8_t iidx_io_panel; +static uint16_t iidx_io_keys; + +/* Uncomment these if you need them. */ + +#if 0 +static log_formatter_t iidx_io_log_misc; +static log_formatter_t iidx_io_log_info; +static log_formatter_t iidx_io_log_warning; +static log_formatter_t iidx_io_log_fatal; +#endif + +void iidx_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + /* Pass logger functions on to geninput so that it has somewhere to write + its own log output. */ + + input_set_loggers(misc, info, warning, fatal); + + /* Pass logger functions on to vefx_io so that it has somewhere to write + its own log output. */ + + vefx_io_set_loggers(misc, info, warning, fatal); + + /* Uncomment this block if you have something you'd like to log. + + You should probably return false from the appropriate function instead + of calling the fatal logger yourself though. */ + +#if 0 + iidx_io_log_misc = misc; + iidx_io_log_info = info; + iidx_io_log_warning = warning; + iidx_io_log_fatal = fatal; +#endif +} + +bool iidx_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + vefx_io_init(thread_create, thread_join, thread_destroy); + timeBeginPeriod(1); + + input_init(thread_create, thread_join, thread_destroy); + mapper_config_load("iidx"); + + iidx_io_tt[0].stab_dir = +1; + iidx_io_tt[1].stab_dir = +1; + + /* Initialize your own IO devices here. Log something and then return + false if the initialization fails. */ + + return true; +} + +void iidx_io_fini(void) +{ + /* This function gets called as IIDX shuts down after an Alt-F4. Close your + connections to your IO devices here. */ + + input_fini(); + vefx_io_fini(); + timeEndPeriod(1); +} + +/* Total number of light bits is 33. That's slightly annoying. So, we pack + the neons bit into an unused start btns light. The entire 32-bit word is + then sent to geninput for output light mapping. */ + +void iidx_io_ep1_set_deck_lights(uint16_t deck_lights) +{ + uint8_t i; + + for (i = 0x00 ; i < 0x0E ; i++) { + mapper_write_light(i, deck_lights & (1 << i) ? 255 : 0); + } +} + +void iidx_io_ep1_set_panel_lights(uint8_t panel_lights) +{ + uint8_t i; + + for (i = 0x00 ; i < 0x04 ; i++) { + mapper_write_light(0x18 + i, panel_lights & (1 << i) ? 255 : 0); + } +} + +void iidx_io_ep1_set_top_lamps(uint8_t top_lamps) +{ + uint8_t i; + + for (i = 0x00 ; i < 0x08 ; i++) { + mapper_write_light(0x10 + i, top_lamps & (1 << i) ? 255 : 0); + } +} + +void iidx_io_ep1_set_top_neons(bool top_neons) +{ + mapper_write_light(0x1F, top_neons ? 255 : 0); +} + +bool iidx_io_ep1_send(void) +{ + /* The generic input stack currently initiates lighting sends and input + reads simultaneously, though this might change later. Perform all of our + I/O immediately before reading out the inputs so that the input state is + as fresh as possible. */ + + return true; +} + +bool iidx_io_ep2_recv(void) +{ + uint32_t now; + uint64_t pad; + int i; + + /* Update all of our input state here. */ + + now = timeGetTime(); + pad = (uint64_t) mapper_update(); + vefx_io_recv(&pad); + + for (i = 0 ; i < 2 ; i++) { + iidx_io_tt_update(now, pad, i); + } + + + /* Mask out the stuff provided by geninput and store the pad state for + later retrieval via iidx_io_ep2_get_pad() */ + + iidx_io_sys = (pad >> IIDX_IO_TEST) & 0x03; + iidx_io_panel = (pad >> IIDX_IO_P1_START) & 0x0F; + iidx_io_keys = (pad >> IIDX_IO_P1_1) & 0x3FFF; + + return true; +} + +static void iidx_io_tt_update(uint32_t now, uint64_t pad, int i) +{ + uint32_t delta_t; + uint8_t notches; + int8_t tt_dir; + int8_t brake; + + /* Determine current turntable direction */ + + if (pad & (1 << iidx_io_tt_inputs[i].tt_up)) { + tt_dir = -1; + } else if (pad & (1 << iidx_io_tt_inputs[i].tt_down)) { + tt_dir = +1; + } else if (pad & (1 << iidx_io_tt_inputs[i].tt_stab)) { + tt_dir = iidx_io_tt[i].stab_dir; + + if (iidx_io_tt[i].last_dir == 0) { + iidx_io_tt[i].stab_dir = -iidx_io_tt[i].stab_dir; + } + } else { + tt_dir = 0; + } + + /* Apply brakes if a start button is held */ + + if (pad & (1 << iidx_io_tt_inputs[i].start)) { + brake = 4; + } else { + brake = 1; + } + + /* Update turntable based on current direction */ + + if (tt_dir != iidx_io_tt[i].last_dir) { + /* Just started (or stopped). Give the TT a big push to make sure + it begins to register straight from the first frame */ + + iidx_io_tt[i].pos += 4 * tt_dir; + iidx_io_tt[i].last_notch = now; + } else if (tt_dir != 0) { + /* Roll TT forward by an appropriate number of notches, given the + elapsed time. Roll `last_notch' forward by an appropriate number + of msec to ensure partial notches get counted properly */ + + delta_t = now - iidx_io_tt[i].last_notch; + notches = delta_t / MSEC_PER_NOTCH / brake; + + iidx_io_tt[i].pos += tt_dir * notches; + iidx_io_tt[i].last_notch += notches * MSEC_PER_NOTCH; + } + + iidx_io_tt[i].last_dir = tt_dir; + + /* Snapshot analog spinner state as well. */ + + iidx_io_tt[i].analog_pos = mapper_read_analog(i); +} + +uint8_t iidx_io_ep2_get_turntable(uint8_t player_no) +{ + if (player_no > 1) { + return 0; + } + + return iidx_io_tt[player_no].pos + iidx_io_tt[player_no].analog_pos; +} + +uint8_t iidx_io_ep2_get_slider(uint8_t slider_no) +{ + return vefx_io_get_slider(slider_no); +} + +uint8_t iidx_io_ep2_get_sys(void) +{ + return iidx_io_sys; +} + +uint8_t iidx_io_ep2_get_panel(void) +{ + return iidx_io_panel; +} + +uint16_t iidx_io_ep2_get_keys(void) +{ + return iidx_io_keys; +} + +bool iidx_io_ep3_write_16seg(const char *text) +{ + return vefx_io_write_16seg(text); +} + diff --git a/src/main/iidxio/iidxio.def b/src/main/iidxio/iidxio.def new file mode 100644 index 0000000..403900d --- /dev/null +++ b/src/main/iidxio/iidxio.def @@ -0,0 +1,18 @@ +LIBRARY iidxio + +EXPORTS + iidx_io_ep1_send + iidx_io_ep1_set_deck_lights + iidx_io_ep1_set_panel_lights + iidx_io_ep1_set_top_lamps + iidx_io_ep1_set_top_neons + iidx_io_ep2_get_keys + iidx_io_ep2_get_panel + iidx_io_ep2_get_sys + iidx_io_ep2_get_slider + iidx_io_ep2_get_turntable + iidx_io_ep2_recv + iidx_io_ep3_write_16seg + iidx_io_fini + iidx_io_init + iidx_io_set_loggers diff --git a/src/main/iidxiotest/Module.mk b/src/main/iidxiotest/Module.mk new file mode 100644 index 0000000..ce0536c --- /dev/null +++ b/src/main/iidxiotest/Module.mk @@ -0,0 +1,8 @@ +exes += iidxiotest \ + +libs_iidxiotest := \ + iidxio \ + util \ + +src_iidxiotest := \ + main.c \ diff --git a/src/main/iidxiotest/main.c b/src/main/iidxiotest/main.c new file mode 100644 index 0000000..27858ff --- /dev/null +++ b/src/main/iidxiotest/main.c @@ -0,0 +1,295 @@ +#include +#include +#include +#include + +#include + +#include "bemanitools/iidxio.h" + +#include "util/log.h" +#include "util/thread.h" + +/** + * Tool to test your implementations of iidxio. + */ +int main(int argc, char** argv) +{ + log_to_writer(log_writer_stdout, NULL); + + iidx_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!iidx_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + printf("Initializing iidxio failed\n"); + return -1; + } + + printf(">>> Initializing iidxio successful, press enter to continue <<<\n"); + + if (getchar() != '\n') { + return 0; + } + + /* inputs */ + uint8_t input_sys = 0; + uint8_t input_panel = 0; + uint16_t input_keys = 0; + uint8_t turn_table[2] = {0, 0}; + uint8_t slider[5] = {0, 0, 0, 0, 0}; + + /* outputs */ + uint16_t deck_lights = 0; + uint8_t panel_lights = 0; + uint8_t top_lamps = 0; + bool top_neons = false; + char text_16seg[9] = " "; + + bool loop = true; + uint8_t cnt = 0; + bool all_on = false; + while (loop) { + + if (!iidx_io_ep2_recv()) { + printf("ERROR: Receive ep2 failed\n"); + return -2; + } + + /* get inputs */ + input_sys = iidx_io_ep2_get_sys(); + input_panel = iidx_io_ep2_get_panel(); + input_keys = iidx_io_ep2_get_keys(); + turn_table[0] = iidx_io_ep2_get_turntable(0); + turn_table[1] = iidx_io_ep2_get_turntable(1); + for (uint8_t i = 0; i < 5; ++i) { + slider[i] = iidx_io_ep2_get_slider(i); + } + + system("cls"); + printf( + "Press escape to open menu\n" + "%d\n" + "|---------------------------------------|\n" + "| R Y G B Neons B G Y R |\n" + "| %d %d %d %d %d %d %d %d %d |\n" + "|---------------------------------------|\n" + "| NOW PLAYING: %c%c%c%c%c%c%c%c%c |\n" + "|---------------------------------------|\n" + "| Effect %d S1 S2 S3 S4 S5 Test %d|\n" + "|StartP1 %d %02d %02d %02d %02d %02d StartP2 %d|\n" + "| VEFX %d Service %d|\n" + "_________________________________________\n" + "| __ __ |\n" + "| / \\ _ / \\ |\n" + "| | %03d| %d %d %d |%d| %d %d %d | %03d| |\n" + "| \\___/ %d %d %d %d |_| %d %d %d %d \\___/ |\n" + "| |\n" + "|---------------------------------------|\n" + "|---------------------------------------|\n", + cnt, + + (top_lamps & (1 << 0)) > 0, + (top_lamps & (1 << 1)) > 0, + (top_lamps & (1 << 2)) > 0, + (top_lamps & (1 << 3)) > 0, + top_neons, + (top_lamps & (1 << 4)) > 0, + (top_lamps & (1 << 5)) > 0, + (top_lamps & (1 << 6)) > 0, + (top_lamps & (1 << 7)) > 0, + + text_16seg[0], text_16seg[1], text_16seg[2], text_16seg[3], text_16seg[4], + text_16seg[5], text_16seg[6], text_16seg[7], text_16seg[8], + (input_panel >> IIDX_IO_PANEL_EFFECT) & 1, + (input_sys >> IIDX_IO_SYS_TEST) & 1, + + (input_panel >> IIDX_IO_PANEL_P1_START) & 1, + slider[0], + slider[1], + slider[2], + slider[3], + slider[4], + (input_panel >> IIDX_IO_PANEL_P2_START) & 1, + + (input_panel >> IIDX_IO_PANEL_VEFX) & 1, + (input_sys >> IIDX_IO_SYS_SERVICE) & 1, + + turn_table[0], + (input_keys >> IIDX_IO_KEY_P1_2) & 1, + (input_keys >> IIDX_IO_KEY_P1_4) & 1, + (input_keys >> IIDX_IO_KEY_P1_6) & 1, + (input_sys >> IIDX_IO_SYS_COIN), + (input_keys >> IIDX_IO_KEY_P2_2) & 1, + (input_keys >> IIDX_IO_KEY_P2_4) & 1, + (input_keys >> IIDX_IO_KEY_P2_6) & 1, + turn_table[1], + (input_keys >> IIDX_IO_KEY_P1_1) & 1, + (input_keys >> IIDX_IO_KEY_P1_3) & 1, + (input_keys >> IIDX_IO_KEY_P1_5) & 1, + (input_keys >> IIDX_IO_KEY_P1_7) & 1, + + (input_keys >> IIDX_IO_KEY_P2_1) & 1, + (input_keys >> IIDX_IO_KEY_P2_3) & 1, + (input_keys >> IIDX_IO_KEY_P2_5) & 1, + (input_keys >> IIDX_IO_KEY_P2_7) & 1); + + /* set outputs */ + if (all_on) { + deck_lights = 0x3FFF; + panel_lights = 0xF; + top_lamps = 0xFF; + top_neons = true; + memset(text_16seg, '*', 9); + } + + iidx_io_ep1_set_deck_lights(deck_lights); + iidx_io_ep1_set_panel_lights(panel_lights); + iidx_io_ep1_set_top_lamps(top_lamps); + iidx_io_ep1_set_top_neons(top_neons); + + /* light up keys when pressed */ + if (!all_on) { + deck_lights = input_keys; + panel_lights = input_panel; + } else { + + /* disable all on when a single button is pressed */ + if (input_keys || input_panel) { + all_on = false; + deck_lights = 0; + panel_lights = 0; + top_lamps = 0; + top_neons = false; + memset(text_16seg, ' ', 9); + } + + } + + if (!iidx_io_ep1_send()) { + printf("ERROR: Sending ep1 failed\n"); + return -4; + } + + if (!iidx_io_ep3_write_16seg(text_16seg)) { + printf("ERROR: Sending ep3 failed\n"); + return -3; + } + + /* avoid CPU banging */ + Sleep(5); + ++cnt; + + /* process menu */ + if ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) { + system("cls"); + Sleep(5); + printf( + "Menu options:\n" + " 0: Exit menu and continue loop\n" + " 1: Exit\n" + " 2: Enter text for 16seg display\n" + " 3: Set neon state\n" + " 4: Set top lamp state\n" + " 5: Set all outputs on (cleared by pressing any IIDX button)\n" + " 6: Clear all outputs\n" + "Waiting for input: "); + char c = getchar(); + + switch (c) { + case '1': + { + /* one last update to turn off the lights */ + iidx_io_ep1_set_deck_lights(0); + iidx_io_ep1_set_panel_lights(0); + iidx_io_ep1_set_top_lamps(0); + iidx_io_ep1_set_top_neons(false); + + if (!iidx_io_ep3_write_16seg(" ")) { + printf("ERROR: Sending ep3 failed\n"); + return -3; + } + + if (!iidx_io_ep1_send()) { + printf("ERROR: Sending ep1 failed\n"); + return -4; + } + + loop = false; + break; + } + + case '2': + { + char buf[10]; + printf("Enter 16seg text (max 9 chars): "); + int n = scanf("%9s", buf); + + if (n > 0) { + n = strlen(buf); + + if (n > 9) { + n = 9; + } + + memset(text_16seg, ' ', sizeof(text_16seg)); + memcpy(text_16seg, buf, n); + } + + break; + } + + case '3': + { + int state; + printf("Enter neon state (0/1): "); + int n = scanf("%d", &state); + + if (n > 0) { + top_neons = state > 0; + } + + break; + } + + case '4': + { + char buf[9]; + printf("Enter top lamp state, chain of 0/1s: "); + int n = scanf("%8s", buf); + + if (n > 0) { + top_lamps = 0; + for (int i = 0; i < 8; ++i) { + if (buf[i] == '1') { + top_lamps |= (1 << i); + } + } + } + + break; + } + + case '5': + { + all_on = true; + break; + } + + case '6': + { + all_on = false; + break; + } + + case '0': + default: + break; + } + } + } + + system("cls"); + iidx_io_fini(); + + return 0; +} \ No newline at end of file diff --git a/src/main/inject/Module.mk b/src/main/inject/Module.mk new file mode 100644 index 0000000..f2b4b2d --- /dev/null +++ b/src/main/inject/Module.mk @@ -0,0 +1,11 @@ +exes += inject + +ldflags_inject := \ + -mconsole \ + +libs_inject := \ + util \ + +src_inject := \ + main.c \ + options.c \ diff --git a/src/main/inject/main.c b/src/main/inject/main.c new file mode 100644 index 0000000..b5cea0d --- /dev/null +++ b/src/main/inject/main.c @@ -0,0 +1,436 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "cconfig/cconfig-util.h" +#include "cconfig/cmd.h" + +#include "inject/options.h" + +#include "util/cmdline.h" +#include "util/mem.h" +#include "util/str.h" + +static FILE* log_file = NULL; + +static bool inject_dll(PROCESS_INFORMATION pi, const char* arg_dll); +static bool debug(HANDLE process, uint32_t pid); +static bool debug_wstr(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi); +static bool debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi); + +int main(int argc, char **argv) +{ + struct options options; + char *cmd_line; + char dll_path[MAX_PATH]; + DWORD dll_path_length; + PROCESS_INFORMATION pi; + STARTUPINFO si; + BOOL ok; + BOOL debug_ok; + int hooks; + int exec_arg_pos; + + options_init(&options); + + if (argc < 3 || !options_read_cmdline(&options, argc, argv)) { + options_print_usage(); + goto usage_fail; + } + + /* Open log file for logging if parameter specified */ + + if (strlen(options.log_file) > 0) { + log_file = fopen(options.log_file, "w+"); + + if (!log_file) { + fprintf(stderr, "Opening log file %s failed: %s\n", + options.log_file, strerror(errno)); + goto log_file_open_fail; + } + + printf("Log file: %s\n", options.log_file); + } + + /* Count hook dlls */ + hooks = 0; + exec_arg_pos = 0; + + for (int i = 1; i < argc; i++) { + if (str_ends_with(argv[i], "dll")) { + hooks++; + } else if (str_ends_with(argv[i], "exe")) { + exec_arg_pos = i; + break; + } + } + + if (!hooks) { + fprintf(stderr, "No Hook DLL(s) specified before executable\n"); + + goto hook_count_fail; + } + + if (!exec_arg_pos) { + fprintf(stderr, "No executable specified\n"); + + goto find_exec_fail; + } + + for (int i = 0; i < hooks; i++) { + dll_path_length = SearchPath(NULL, argv[i + 1], NULL, MAX_PATH, + dll_path, NULL); + + if (dll_path_length == 0) { + fprintf(stderr, "Hook DLL not found: %08x\n", + (unsigned int) GetLastError()); + + goto search_fail; + } + } + + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + + cmd_line = args_join(argc - exec_arg_pos, argv + exec_arg_pos); + + ok = CreateProcess(argv[exec_arg_pos], cmd_line, NULL, NULL, FALSE, + CREATE_SUSPENDED, NULL, NULL, &si, &pi); + + if (!ok) { + fprintf(stderr, "Failed to launch hooked EXE: %08x\n", + (unsigned int) GetLastError()); + + goto start_fail; + } + + free(cmd_line); + cmd_line = NULL; + + for (int i = 0; i < hooks; i++) { + if (!inject_dll(pi, argv[i + 1])) { + goto inject_fail; + } + } + + debug_ok = false; + + if (options.debug && !options.remote_debugger) { + debug_ok = DebugActiveProcess(pi.dwProcessId); + + if (!debug_ok) { + fprintf(stderr, "DebugActiveProcess failed: %08x\n", + (unsigned int) GetLastError()); + } else { + printf("Debug active process\n"); + } + } + + if (options.remote_debugger) { + printf("Waiting until debugger attaches to remote process...\n"); + + while (true) { + BOOL res = FALSE; + + if (!CheckRemoteDebuggerPresent(pi.hProcess, &res)) { + fprintf(stderr, "CheckRemoteDebuggerPresent failed: %08x\n", + (unsigned int) GetLastError()); + } + + if (res) { + printf("Debugger attached, resuming\n"); + break; + } + + Sleep(1000); + } + } + + printf("Resuming remote process...\n"); + + if (ResumeThread(pi.hThread) == -1) { + fprintf(stderr, "Error restarting hooked process: %08x\n", + (unsigned int) GetLastError()); + + goto restart_fail; + } + + CloseHandle(pi.hThread); + + if (options.debug) { + if (!debug_ok || !debug(pi.hProcess, pi.dwProcessId)) { + WaitForSingleObject(pi.hProcess, INFINITE); + } + } + + CloseHandle(pi.hProcess); + + return EXIT_SUCCESS; + +inject_fail: +restart_fail: + TerminateProcess(pi.hProcess, EXIT_FAILURE); + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + +start_fail: + if (cmd_line != NULL) { + free(cmd_line); + } + +hook_count_fail: +find_exec_fail: +search_fail: + if (log_file) { + fclose(log_file); + } + +log_file_open_fail: +usage_fail: + + return EXIT_FAILURE; +} + +static bool inject_dll(PROCESS_INFORMATION pi, const char* arg_dll) +{ + char dll_path[MAX_PATH]; + DWORD dll_path_length; + void *remote_addr; + BOOL ok; + HANDLE remote_thread; + + printf("Injecting: %s\n", arg_dll); + + dll_path_length = SearchPath(NULL, arg_dll, NULL, MAX_PATH, dll_path, + NULL); + + dll_path_length++; + + remote_addr = VirtualAllocEx(pi.hProcess, NULL, dll_path_length, + MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + + if (!remote_addr) { + fprintf(stderr, "VirtualAllocEx failed: %08x\n", + (unsigned int) GetLastError()); + + goto alloc_fail; + } + + ok = WriteProcessMemory(pi.hProcess, remote_addr, dll_path, + dll_path_length, NULL); + + if (!ok) { + fprintf(stderr, "WriteProcessMemory failed: %08x\n", + (unsigned int) GetLastError()); + + goto write_fail; + } + + remote_thread = CreateRemoteThread(pi.hProcess, NULL, 0, + (LPTHREAD_START_ROUTINE) LoadLibrary, remote_addr, 0, NULL); + + if (remote_thread == NULL) { + fprintf(stderr, "CreateRemoteThread failed: %08x\n", + (unsigned int) GetLastError()); + + goto inject_fail; + } + + WaitForSingleObject(remote_thread, INFINITE); + CloseHandle(remote_thread); + + ok = VirtualFreeEx(pi.hProcess, remote_addr, 0, MEM_RELEASE); + remote_addr = NULL; + + if (!ok) { + fprintf(stderr, "VirtualFreeEx failed: %08x\n", + (unsigned int) GetLastError()); + } + + return true; + +inject_fail: +write_fail: + if (remote_addr != NULL) { + VirtualFreeEx(pi.hProcess, remote_addr, 0, MEM_RELEASE); + } + +alloc_fail: + return false; +} + +static bool debug(HANDLE process, uint32_t pid) +{ + DEBUG_EVENT de; + BOOL ok; + + for (;;) { + ok = WaitForDebugEvent(&de, INFINITE); + + if (!ok) { + fprintf(stderr, "WaitForDebugEvent failed: %08x\n", + (unsigned int) GetLastError()); + + return false; + } + + switch (de.dwDebugEventCode) { + case CREATE_PROCESS_DEBUG_EVENT: + CloseHandle(de.u.CreateProcessInfo.hFile); + + break; + + case EXIT_PROCESS_DEBUG_EVENT: + if (de.dwProcessId == pid) { + return true; + } + + break; + + case LOAD_DLL_DEBUG_EVENT: + CloseHandle(de.u.LoadDll.hFile); + + break; + + case OUTPUT_DEBUG_STRING_EVENT: + if (de.dwProcessId == pid) { + if (de.u.DebugString.fUnicode) { + if (!debug_wstr(process, &de.u.DebugString)) { + return false; + } + } else { + if (!debug_str(process, &de.u.DebugString)) { + return false; + } + } + } + + break; + } + + if (de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT) { + ok = ContinueDebugEvent(de.dwProcessId, de.dwThreadId, + DBG_CONTINUE); + } else { + ok = ContinueDebugEvent(de.dwProcessId, de.dwThreadId, + DBG_EXCEPTION_NOT_HANDLED); + } + + if (!ok) { + fprintf(stderr, "ContinueDebugEvent failed: %08x\n", + (unsigned int) GetLastError()); + + return false; + } + } +} + +static char console_get_color(char* str) +{ + /* Add some color to make spotting warnings/errors easier. + Based on debug output level identifier. */ + + /* Avoids colored output on strings like "Windows" */ + if (str[1] != ':') { + return 15; + } + + switch (str[0]) { + /* green */ + case 'M': + return 10; + /* blue */ + case 'I': + return 9; + /* yellow */ + case 'W': + return 14; + /* red */ + case 'F': + return 12; + /* default console color */ + default: + return 15; + } +} + +static bool debug_wstr(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi) +{ + char *str; + wchar_t *wstr; + uint32_t nbytes; + BOOL ok; + + nbytes = odsi->nDebugStringLength * sizeof(wchar_t); + wstr = xmalloc(nbytes); + + ok = ReadProcessMemory(process, odsi->lpDebugStringData, wstr, nbytes, + NULL); + + if (ok) { + if (wstr_narrow(wstr, &str)) { + str[odsi->nDebugStringLength - 1] = '\0'; + + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), + console_get_color(str)); + printf("%s", str); + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15); + + if (log_file) { + fprintf(log_file, "%s", str); + } + + free(str); + } else { + fprintf(stderr, "OutputDebugStringW: UTF-16 conversion failed\n"); + } + } else { + fprintf(stderr, "ReadProcessMemory failed: %08x\n", + (unsigned int) GetLastError()); + + return false; + } + + free(wstr); + + return (bool) ok; +} + +static bool debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi) +{ + char *str; + BOOL ok; + + str = xmalloc(odsi->nDebugStringLength); + + ok = ReadProcessMemory(process, odsi->lpDebugStringData, str, + odsi->nDebugStringLength, NULL); + + if (ok) { + str[odsi->nDebugStringLength - 1] = '\0'; + + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), + console_get_color(str)); + printf("%s", str); + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15); + + if (log_file) { + fprintf(log_file, "%s", str); + } + } else { + fprintf(stderr, "ReadProcessMemory failed: %08x\n", + (unsigned int) GetLastError()); + } + + free(str); + + return (bool) ok; +} + diff --git a/src/main/inject/options.c b/src/main/inject/options.c new file mode 100644 index 0000000..8abd615 --- /dev/null +++ b/src/main/inject/options.c @@ -0,0 +1,62 @@ +#include +#include + +#include "inject/options.h" + +#include "util/defs.h" + +void options_init(struct options *options) +{ + options->debug = false; + options->remote_debugger = false; + memset(options->log_file, 0, sizeof(options->log_file)); +} + +bool options_read_cmdline(struct options *options, int argc, char **argv) +{ + for (int i = 1 ; i < argc ; i++) { + if (argv[i][0] == '-') { + switch (argv[i][1]) { + case 'D': + options->debug = true; + + break; + + case 'Y': + if (i + 1 >= argc) { + return false; + } + + strcpy(options->log_file, argv[++i]); + + break; + + case 'R': + options->remote_debugger = true; + + break; + + default: + break; + } + } + } + + return true; +} + +void options_print_usage(void) +{ + fprintf(stderr, +"inject build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" +"Usage: inject hook.dll... app.exe [hooks options...]\n" +"You can specify one or multiple hook.dll files, e.g. inject.exe " +"hook1.dll hook2.dll app.exe" +"\n" +" The following options can be specified after the exe path:\n" +"\n" +" -D Enable debugging output\n" +" -R Halt the injected process until a debugger is attached\n" +" -Y [filename] Log to a file in addition to the console\n" + ); +} diff --git a/src/main/inject/options.h b/src/main/inject/options.h new file mode 100644 index 0000000..0abc45e --- /dev/null +++ b/src/main/inject/options.h @@ -0,0 +1,20 @@ +#ifndef INJECT_OPTIONS_H +#define INJECT_OPTIONS_H + +#include +#include +#include + +#include "util/array.h" + +struct options { + bool debug; + bool remote_debugger; + char log_file[MAX_PATH]; +}; + +void options_init(struct options *options); +bool options_read_cmdline(struct options *options, int argc, char **argv); +void options_print_usage(void); + +#endif diff --git a/src/main/jbhook/Module.mk b/src/main/jbhook/Module.mk new file mode 100644 index 0000000..5ea146d --- /dev/null +++ b/src/main/jbhook/Module.mk @@ -0,0 +1,24 @@ +avsdlls += jbhook + +deplibs_jbhook := \ + avs \ + +ldflags_jbhook := \ + -lws2_32 \ + +libs_jbhook := \ + acioemu \ + eamio \ + jbio \ + p4ioemu \ + hook \ + hooklib \ + util \ + +src_jbhook := \ + acio.c \ + dllmain.c \ + eamuse.c \ + gfx.c \ + options.c \ + io.c diff --git a/src/main/jbhook/acio.c b/src/main/jbhook/acio.c new file mode 100644 index 0000000..c4d8a5e --- /dev/null +++ b/src/main/jbhook/acio.c @@ -0,0 +1,95 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "acioemu/addr.h" +#include "acioemu/emu.h" +#include "acioemu/h44b.h" +#include "acioemu/iccb.h" + +#include "hook/iohook.h" + +#include "jbhook/acio.h" + +#include "imports/avs.h" + +#include "util/defs.h" +#include "util/hex.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +static struct ac_io_emu ac_io_emu; +static struct ac_io_emu_iccb ac_io_emu_iccb; +static struct ac_io_emu_h44b ac_io_emu_h44b; + +void ac_io_port_init(void) +{ + ac_io_emu_init(&ac_io_emu, L"COM2"); + ac_io_emu_iccb_init(&ac_io_emu_iccb, &ac_io_emu, 0); + ac_io_emu_h44b_init(&ac_io_emu_h44b, &ac_io_emu, 1); +} + +void ac_io_port_fini(void) +{ + ac_io_emu_fini(&ac_io_emu); +} + +HRESULT ac_io_port_dispatch_irp(struct irp *irp) +{ + const struct ac_io_message *msg; + HRESULT hr; + + log_assert(irp != NULL); + + if (!ac_io_emu_match_irp(&ac_io_emu, irp)) { + return irp_invoke_next(irp); + } + + for (;;) { + hr = ac_io_emu_dispatch_irp(&ac_io_emu, irp); + + if (hr != S_OK) { + return hr; + } + + msg = ac_io_emu_request_peek(&ac_io_emu); + + switch (msg->addr) { + case 0: + ac_io_emu_cmd_assign_addrs(&ac_io_emu, msg, 2); + + break; + + case 1: + ac_io_emu_iccb_dispatch_request(&ac_io_emu_iccb, msg); + + break; + + case 2: + ac_io_emu_h44b_dispatch_request(&ac_io_emu_h44b, msg); + + break; + + case AC_IO_BROADCAST: + log_warning("Broadcast(?) message on jubeat ACIO bus?"); + + break; + + default: + log_warning("ACIO message on unhandled bus address: %d", + msg->addr); + + break; + } + + ac_io_emu_request_pop(&ac_io_emu); + } +} diff --git a/src/main/jbhook/acio.h b/src/main/jbhook/acio.h new file mode 100644 index 0000000..c12fd37 --- /dev/null +++ b/src/main/jbhook/acio.h @@ -0,0 +1,8 @@ +#ifndef JBHOOK_ACIO_H +#define JBHOOK_ACIO_H + +void ac_io_port_init(void); +void ac_io_port_fini(void); +HRESULT ac_io_port_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/jbhook/dllmain.c b/src/main/jbhook/dllmain.c new file mode 100644 index 0000000..a209926 --- /dev/null +++ b/src/main/jbhook/dllmain.c @@ -0,0 +1,162 @@ +#include + +#include +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/jbio.h" + +#include "hook/iohook.h" + +#include "hooklib/app.h" +#include "hooklib/rs232.h" +#include "hooklib/setupapi.h" + +#include "imports/avs.h" + +#include "jbhook/acio.h" +#include "jbhook/eamuse.h" +#include "jbhook/gfx.h" +#include "jbhook/io.h" +#include "jbhook/options.h" + +#include "p4ioemu/device.h" +#include "p4ioemu/setupapi.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/thread.h" + +static struct options options; + +static const irp_handler_t jbhook_handlers[] = { + p4ioemu_dispatch_irp, + ac_io_port_dispatch_irp, +}; + +static bool my_dll_entry_init(char *sidcode, struct property_node *param) +{ + bool eam_io_ok; + bool jb_io_ok; + + eam_io_ok = false; + jb_io_ok = false; + + log_info("--- Begin jbhook dll_entry_init ---"); + + if (!options.disable_p4ioemu) { + p4ioemu_init(jbhook_io_init()); + + log_info("Starting up jubeat IO backend"); + + jb_io_set_loggers( + log_impl_misc, + log_impl_info, + log_impl_warning, + log_impl_fatal); + + jb_io_ok = jb_io_init( + avs_thread_create, + avs_thread_join, + avs_thread_destroy); + + if (!jb_io_ok) { + goto fail; + } + } + + if (!options.disable_cardemu) { + ac_io_port_init(); + + log_info("Starting up card reader backend"); + + eam_io_set_loggers( + log_impl_misc, + log_impl_info, + log_impl_warning, + log_impl_fatal); + + eam_io_ok = eam_io_init( + avs_thread_create, + avs_thread_join, + avs_thread_destroy); + + if (!eam_io_ok) { + goto fail; + } + } + + log_info("--- End jbhook dll_entry_init ---"); + + return app_hook_invoke_init(sidcode, param); + +fail: + if (eam_io_ok) { + eam_io_fini(); + } + + if (jb_io_ok) { + jb_io_fini(); + } + + return false; +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + log_info("Shutting down card reader backend"); + eam_io_fini(); + + log_info("Shutting down Jubeat IO backend"); + jb_io_fini(); + + if (!options.disable_cardemu) { + ac_io_port_fini(); + } + + if (!options.disable_p4ioemu) { + p4ioemu_fini(); + } + + options_fini(&options); + + return result; +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason != DLL_PROCESS_ATTACH) { + return TRUE; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + options_init_from_cmdline(&options); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + iohook_init(jbhook_handlers, lengthof(jbhook_handlers)); + + if (!options.disable_cardemu) { + rs232_hook_init(); + } + + if (!options.disable_p4ioemu) { + hook_setupapi_init(&p4ioemu_setupapi_data); + } + + gfx_hook_init(); + jbhook_eamuse_hook_init(); + + return TRUE; +} + diff --git a/src/main/jbhook/eamuse.c b/src/main/jbhook/eamuse.c new file mode 100644 index 0000000..6543527 --- /dev/null +++ b/src/main/jbhook/eamuse.c @@ -0,0 +1,59 @@ +#define LOG_MODULE "eamuse-hook" + +#include +#include +#include + +#include +#include +#include + +#include "hook/table.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" + +static const char* jbhook_eamuse_konami_url = "eamuse.konami."; + +static int (STDCALL *real_getaddrinfo)(PCSTR pNodeName, PCSTR pServiceName, + const ADDRINFOA *pHints, PADDRINFOA *ppResult); + +static int STDCALL my_getaddrinfo(PCSTR pNodeName, PCSTR pServiceName, + const ADDRINFOA *pHints, PADDRINFOA *ppResult); + +static const struct hook_symbol eamuse_hook_syms[] = { + { + .name = "getaddrinfo", + .ordinal = 176, + .patch = my_getaddrinfo, + .link = (void **) &real_getaddrinfo, + }, +}; + +static int STDCALL my_getaddrinfo(PCSTR pNodeName, PCSTR pServiceName, + const ADDRINFOA *pHints, PADDRINFOA *ppResult) +{ + log_misc("my_getaddrinfo: %s, %s", pNodeName, pServiceName); + + /* resolve eamuse.konami.fun/com to 127.0.0.1 to avoid lag spikes every + 150 seconds which might happen in-game as well */ + if (!strncmp(pNodeName, jbhook_eamuse_konami_url, + strlen(jbhook_eamuse_konami_url))) { + log_info("Resolve konami.eamuse.XXX -> localhost"); + pNodeName = "localhost"; + } + + return real_getaddrinfo(pNodeName, pServiceName, pHints, ppResult); +} + +void jbhook_eamuse_hook_init(void) +{ + hook_table_apply( + NULL, + "ws2_32.dll", + eamuse_hook_syms, + lengthof(eamuse_hook_syms)); + + log_info("Inserted eamuse hooks"); +} diff --git a/src/main/jbhook/eamuse.h b/src/main/jbhook/eamuse.h new file mode 100644 index 0000000..5f4707e --- /dev/null +++ b/src/main/jbhook/eamuse.h @@ -0,0 +1,6 @@ +#ifndef JBHOOK_EAMUSE_H +#define JBHOOK_EAMUSE_H + +void jbhook_eamuse_hook_init(void); + +#endif diff --git a/src/main/jbhook/gfx.c b/src/main/jbhook/gfx.c new file mode 100644 index 0000000..b9ab965 --- /dev/null +++ b/src/main/jbhook/gfx.c @@ -0,0 +1,74 @@ +#define LOG_MODULE "jbhook-gfx" + +#include + +#include +#include +#include +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "jbhook/gfx.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" +#include "util/time.h" + +static DWORD STDCALL my_GetGlyphOutline(HDC hdc, UINT uChar, UINT uFormat, + LPGLYPHMETRICS lpgm, DWORD cbBuffer, LPVOID lpvBuffer, + const MAT2* lpmat2); + +static DWORD (STDCALL* real_GetGlyphOutline)(HDC hdc, UINT uChar, UINT uFormat, + LPGLYPHMETRICS lpgm, DWORD cbBuffer, LPVOID lpvBuffer, + const MAT2* lpmat2); + +static const struct hook_symbol gfx_hook_syms[] = { + { + .name = "GetGlyphOutlineA", + .patch = my_GetGlyphOutline, + .link = (void **) &real_GetGlyphOutline + }, + { + .name = "IsDBCSLeadByteEx", + .patch = my_GetGlyphOutline, // !?? + .link = (void **) &real_GetGlyphOutline // !?? + }, +}; + +static DWORD STDCALL my_GetGlyphOutline(HDC hdc, UINT uChar, UINT uFormat, + LPGLYPHMETRICS lpgm, DWORD cbBuffer, LPVOID lpvBuffer, + const MAT2* lpmat2) +{ + if (IsDBCSLeadByteEx(CP_ACP, uChar & 0xFF)) { + log_warning("!!!!!!!!"); + + char tmp[2]; + + tmp[0] = uChar & 0xFF; + tmp[1] = (uChar >> 8) & 0xFF; + + MultiByteToWideChar(CP_ACP, MB_USEGLYPHCHARS, tmp, 2, lpvBuffer, cbBuffer); + } + + return real_GetGlyphOutline(hdc, uChar, uFormat, lpgm, cbBuffer, lpvBuffer, + lpmat2); +} + +void gfx_hook_init(void) +{ + hook_table_apply( + NULL, + "gdi32.dll", + gfx_hook_syms, + lengthof(gfx_hook_syms)); + + log_info("Inserted gfx hooks"); +} + +void gfx_set_windowed(bool framed) +{ +} + diff --git a/src/main/jbhook/gfx.h b/src/main/jbhook/gfx.h new file mode 100644 index 0000000..0bd9123 --- /dev/null +++ b/src/main/jbhook/gfx.h @@ -0,0 +1,20 @@ +#ifndef JBHOOK_D3D8_H +#define JBHOOK_D3D8_H + +#include + +/** + * Hook some gfx related functions to patch gfx related stuff + * like enabling window mode. + */ +void gfx_hook_init(void); + +/** + * Set the game to window mode. + * + * @param framed True to add a window frame and make the window + * movable, resizable, minizable. False for no frame. + */ +void gfx_set_windowed(bool framed); + +#endif diff --git a/src/main/jbhook/io.c b/src/main/jbhook/io.c new file mode 100644 index 0000000..f3d318c --- /dev/null +++ b/src/main/jbhook/io.c @@ -0,0 +1,149 @@ +#define LOG_MODULE "jbhook-io" + +#include + +#include "bemanitools/jbio.h" + +#include "imports/avs.h" + +#include "jbhook/io.h" + +#include "util/log.h" + +enum jbhook_io_p4io_command { + JUHOOK_IO_P4IO_CMD_OUTPUTS = 0x18, +}; + +struct jbhook_io_p4io_outputs { + uint32_t outputs; +}; + +static void jbhook_io_jamma2_read(void* resp, uint32_t nbytes); +static uint32_t jbhook_command_handle(uint8_t cmd, const void* payload, + uint32_t payload_len, void* resp, uint32_t resp_max_len); + +static const struct p4ioemu_device_msg_hook jbhook_io_msg = { + .jamma2_read = jbhook_io_jamma2_read, + .command_handle = jbhook_command_handle, + .roundplug_read_id = NULL, + .roundplug_read_mem = NULL +}; + +/* + 0:0 ??? + 0:1 b2 + 0:2 b6 + 0:3 b10 + 0:4 b14 + 0:5 b1 + 0:6 b5 + 0:7 b9 + + 1:0 ??? + 1:1 b4 + 1:2 b8 + 1:3 b12 + 1:4 b16 + 1:5 b3 + 1:6 b7 + 1:7 b11 + + 2:0 b13 + 2:1 ??? + 2:2 ??? + 2:3 ??? + 2:4 b15 + 2:5 ??? + 2:6 ??? + 2:7 ??? + + 3:0 coin + 3:1 service + 3:2 ??? + 3:3 ??? + 3:4 test + 3:5 ??? + 3:6 ??? + 3:7 ??? +*/ +static const uint32_t jbhook_io_panel_mappings[] = { + (1 << 5), + (1 << 1), + (1 << 13), + (1 << 9), + (1 << 6), + (1 << 2), + (1 << 14), + (1 << 10), + (1 << 7), + (1 << 3), + (1 << 15), + (1 << 11), + (1 << 16), + (1 << 4), + (1 << 20), + (1 << 12), +}; + +static const uint32_t jbhook_io_sys_button_mappings[] = { + (1 << 28), + (1 << 25), +}; + +static void jbhook_io_jamma2_read(void* resp, uint32_t nbytes) +{ + uint32_t* inputs = (uint32_t*) resp; + uint16_t panels; + uint8_t buttons; + + /* lower three bytes low active, highest byte high active */ + *inputs = 0x00FFFFFF; + + if (!jb_io_read_inputs()) { + log_warning("Reading inputs from jbio failed"); + return; + } + + panels = jb_io_get_panel_inputs(); + buttons = jb_io_get_sys_inputs(); + + for (uint8_t i = 0; i < 16; i++) { + if (panels & (1 << i)) { + *inputs &= ~jbhook_io_panel_mappings[i]; + } + } + + for (uint8_t i = 0; i < 2; i++) { + if (buttons & (1 << i)) { + *inputs |= jbhook_io_sys_button_mappings[i]; + } + } +} + +static uint32_t jbhook_command_handle(uint8_t cmd, const void* payload, + uint32_t payload_len, void* resp, uint32_t resp_max_len) +{ + switch (cmd) { + case JUHOOK_IO_P4IO_CMD_OUTPUTS: + { + // const struct jbhook_io_p4io_outputs* req = + // (const struct jbhook_io_p4io_outputs*) payload; + + //log_misc("JUHOOK_IO_P4IO_CMD_OUTPUTS: 0x%X", req->outputs); + + /* coin blocker: off 0x20, on 0x00 */ + + memset(resp, 0, 4); + + return 4; + } + + default: + return 0xFFFFFFFF; + } +} + +const struct p4ioemu_device_msg_hook* jbhook_io_init(void) +{ + return &jbhook_io_msg; +} diff --git a/src/main/jbhook/io.h b/src/main/jbhook/io.h new file mode 100644 index 0000000..f944c3c --- /dev/null +++ b/src/main/jbhook/io.h @@ -0,0 +1,8 @@ +#ifndef JBHOOK_IO_H +#define JBHOOK_IO_H + +#include "p4ioemu/device.h" + +const struct p4ioemu_device_msg_hook* jbhook_io_init(void); + +#endif \ No newline at end of file diff --git a/src/main/jbhook/jbhook.def b/src/main/jbhook/jbhook.def new file mode 100644 index 0000000..2bb53a1 --- /dev/null +++ b/src/main/jbhook/jbhook.def @@ -0,0 +1,4 @@ +LIBRARY jbhook + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/jbhook/options.c b/src/main/jbhook/options.c new file mode 100644 index 0000000..044cfef --- /dev/null +++ b/src/main/jbhook/options.c @@ -0,0 +1,109 @@ +#include "jbhook/options.h" + +#include + +#include +#include +#include + +#include "util/cmdline.h" +#include "util/defs.h" +#include "util/hex.h" +#include "util/log.h" +#include "util/str.h" + +void options_init_from_cmdline(struct options* options) +{ + int argc; + char **argv; + bool ok; + + args_recover(&argc, &argv); + + options_init(options); + + ok = options_read_cmdline(options, argc, (const char**) argv); + + args_free(argc, argv); + + if (!ok) { + exit(0); + } +} + +void options_init(struct options* options) +{ + options->windowed = false; + options->window_framed = false; + options->disable_p4ioemu = false; + options->disable_cardemu = false; +} + +bool options_read_cmdline(struct options* options, int argc, + const char **argv) +{ + int i; + + for (i = 0; i < argc; i++) { + + if (argv[i][0] != '-') { + continue; + } + + switch (argv[i][1]) { + case 'h': + { + options_print_usage(); + return false; + } + + case 'w': + { + options->windowed = true; + break; + } + + case 'f': + { + options->window_framed = true; + break; + } + + case 'c': + { + options->disable_cardemu = true; + break; + } + + case 'p': + { + options->disable_p4ioemu = true; + break; + } + } + } + + return true; +} + +void options_print_usage(void) +{ + OutputDebugStringA( +"jbhook for jubeat, build " __DATE__ " " __TIME__ ", gitrev " +STRINGIFY(GITREV) "\n" +"Usage: launcher.exe -K jbhook.dll [game exec] \n" +"\n" +" The following options can be specified after the game exec path:\n" +"\n" +" -h Print this usage message\n" +" -w Run the game windowed\n" +" -f Run the game in a framed window (needs -w option)\n" +" -c Disable card emulation (e.g. when running on a real cab)\n" +" -p Disable p4io emulation (e.g. when running on a real cab or on a bare p4io)\n" + ); +} + +void options_fini(struct options* options) +{ + +} diff --git a/src/main/jbhook/options.h b/src/main/jbhook/options.h new file mode 100644 index 0000000..56317a0 --- /dev/null +++ b/src/main/jbhook/options.h @@ -0,0 +1,22 @@ +#ifndef JBHOOK_OPTIONS_H +#define JBHOOK_OPTIONS_H + +#include +#include + +struct options { + bool windowed; + bool window_framed; + bool disable_p4ioemu; + bool disable_cardemu; +}; + +void options_init_from_cmdline(struct options* options); + +void options_init(struct options* options); +bool options_read_cmdline(struct options* options, + int argc, const char** argv); +void options_print_usage(void); +void options_fini(struct options* options); + +#endif diff --git a/src/main/jbhook1/Module.mk b/src/main/jbhook1/Module.mk new file mode 100644 index 0000000..d76ab6e --- /dev/null +++ b/src/main/jbhook1/Module.mk @@ -0,0 +1,30 @@ +avsdlls += jbhook1 + +deplibs_jbhook1 := \ + avs \ + +ldflags_jbhook1 := \ + -lws2_32 \ + -liphlpapi \ + +libs_jbhook1 := \ + acioemu \ + cconfig \ + eamio \ + jbio \ + p3ioemu \ + p3io \ + hook \ + hooklib \ + security \ + util \ + +src_jbhook1 := \ + acio.c \ + avs-boot.c \ + config-gfx.c \ + config-eamuse.c \ + config-security.c \ + dllmain.c \ + log-gftools.c \ + p3io.c \ diff --git a/src/main/jbhook1/acio.c b/src/main/jbhook1/acio.c new file mode 100644 index 0000000..892ae72 --- /dev/null +++ b/src/main/jbhook1/acio.c @@ -0,0 +1,95 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "acioemu/addr.h" +#include "acioemu/emu.h" +#include "acioemu/h44b.h" +#include "acioemu/icca.h" + +#include "hook/iohook.h" + +#include "jbhook/acio.h" + +#include "imports/avs.h" + +#include "util/defs.h" +#include "util/hex.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +static struct ac_io_emu ac_io_emu; +static struct ac_io_emu_icca ac_io_emu_icca; +static struct ac_io_emu_h44b ac_io_emu_h44b; + +void ac_io_port_init(void) +{ + ac_io_emu_init(&ac_io_emu, L"COM1"); + ac_io_emu_icca_init(&ac_io_emu_icca, &ac_io_emu, 0); + ac_io_emu_h44b_init(&ac_io_emu_h44b, &ac_io_emu, 1); +} + +void ac_io_port_fini(void) +{ + ac_io_emu_fini(&ac_io_emu); +} + +HRESULT ac_io_port_dispatch_irp(struct irp *irp) +{ + const struct ac_io_message *msg; + HRESULT hr; + + log_assert(irp != NULL); + + if (!ac_io_emu_match_irp(&ac_io_emu, irp)) { + return irp_invoke_next(irp); + } + + for (;;) { + hr = ac_io_emu_dispatch_irp(&ac_io_emu, irp); + + if (hr != S_OK) { + return hr; + } + + msg = ac_io_emu_request_peek(&ac_io_emu); + + switch (msg->addr) { + case 0: + ac_io_emu_cmd_assign_addrs(&ac_io_emu, msg, 2); + + break; + + case 1: + ac_io_emu_icca_dispatch_request(&ac_io_emu_icca, msg); + + break; + + case 2: + ac_io_emu_h44b_dispatch_request(&ac_io_emu_h44b, msg); + + break; + + case AC_IO_BROADCAST: + log_warning("Broadcast(?) message on jubeat ACIO bus?"); + + break; + + default: + log_warning("ACIO message on unhandled bus address: %d", + msg->addr); + + break; + } + + ac_io_emu_request_pop(&ac_io_emu); + } +} diff --git a/src/main/jbhook1/acio.h b/src/main/jbhook1/acio.h new file mode 100644 index 0000000..efbf7e0 --- /dev/null +++ b/src/main/jbhook1/acio.h @@ -0,0 +1,22 @@ +#ifndef JBHOOK1_ACIO_H +#define JBHOOK1_ACIO_H + +#include "hook/iohook.h" + +/** + * Initialize the ACIO backend for jubeat. + */ +void ac_io_port_init(void); + +/** + * Shutdown the ACIO backend. + */ +void ac_io_port_fini(void); + +/** + * ACIO backend dispatch irp function. This needs to be hooked up to the iohook + * module in order to receive system calls to dispatch for emulation. + */ +HRESULT ac_io_port_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/jbhook1/avs-boot.c b/src/main/jbhook1/avs-boot.c new file mode 100644 index 0000000..24a907f --- /dev/null +++ b/src/main/jbhook1/avs-boot.c @@ -0,0 +1,157 @@ +#define LOG_MODULE "avs-boot" + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "imports/avs.h" + +#include "jbhook1/avs-boot.h" + +#include "util/log.h" + +static void (*real_avs_boot)( + struct property_node *config, void *std_heap, size_t sz_std_heap, + void *avs_heap, size_t sz_avs_heap, avs_log_writer_t log_writer, + void *log_context); +static int (*real_log_change_level)(int level); +static int (*real_ea3_boot)(struct property_node *config); + +static void my_avs_boot( + struct property_node *config, void *std_heap, size_t sz_std_heap, + void *avs_heap, size_t sz_avs_heap, avs_log_writer_t log_writer, + void *log_context); +static int my_log_change_level(int level); +static int my_ea3_boot(struct property_node *config); + +static struct net_addr jbhook1_avs_boot_eamuse_server_addr; + +static const struct hook_symbol jbhook1_log_gftools_hook_syms[] = { + { + .name = "avs_boot", + .patch = my_avs_boot, + .link = (void **) &real_avs_boot + }, + { + .name = "log_change_level", + .patch = my_log_change_level, + .link = (void **) &real_log_change_level + }, +}; + +static const struct hook_symbol jbhook1_log_gftools_hook_syms2[] = { + { + .name = "ea3_boot", + .patch = my_ea3_boot, + .link = (void **) &real_ea3_boot + }, +}; + +static void avs_boot_replace_property_uint32(struct property_node* node, + const char* name, uint32_t val) +{ + struct property_node* tmp; + + tmp = property_search(NULL, node, name); + + if (tmp) { + property_node_remove(tmp); + } + + property_node_create(NULL, node, PSMAP_TYPE_U32, name, val); +} + +static void avs_boot_replace_property_str(struct property_node* node, + const char* name, const char* val) +{ + struct property_node* tmp; + + tmp = property_search(NULL, node, name); + + if (tmp) { + property_node_remove(tmp); + } + + tmp = property_node_create(NULL, node, PSMAP_TYPE_STR_LEGACY, name, val); + + if (tmp) { + property_node_datasize(tmp); + } +} + +static void my_avs_boot( + struct property_node *config, void *std_heap, size_t sz_std_heap, + void *avs_heap, size_t sz_avs_heap, avs_log_writer_t log_writer, + void *log_context) +{ + log_info("Called my_avs_boot"); + + avs_boot_replace_property_uint32(config, "log/level", 4); + avs_boot_replace_property_str(config, "/config/fs/nvram/device", + "./CONF/NVRAM"); + avs_boot_replace_property_str(config, "/config/fs/raw/device", + "./CONF/RAW"); + + real_avs_boot(config, std_heap, sz_std_heap, avs_heap, sz_avs_heap, + log_writer_debug, NULL); +} + +static int my_ea3_boot(struct property_node *config) +{ + char* server_addr; + + log_info("Called my_ea3_boot"); + + if (jbhook1_avs_boot_eamuse_server_addr.type != NET_ADDR_TYPE_INVALID) { + log_misc("Injecting network server address"); + + server_addr = net_addr_to_str(&jbhook1_avs_boot_eamuse_server_addr); + + avs_boot_replace_property_str(config, "network/services", server_addr); + + free(server_addr); + } + + return real_ea3_boot(config); +} + +static int my_log_change_level(int level) +{ + log_misc("Log change level: %d", level); + + return real_log_change_level(level); +} + +void jbhook1_avs_boot_init() +{ + hook_table_apply( + NULL, + "libavs-win32.dll", + jbhook1_log_gftools_hook_syms, + lengthof(jbhook1_log_gftools_hook_syms)); + + hook_table_apply( + NULL, + "libavs-win32-ea3.dll", + jbhook1_log_gftools_hook_syms2, + lengthof(jbhook1_log_gftools_hook_syms2)); + + memset(&jbhook1_avs_boot_eamuse_server_addr, 0, sizeof(struct net_addr)); + + log_info("Inserted avs log hooks"); +} + +void jbhook1_avs_boot_set_eamuse_addr(const struct net_addr* server_addr) +{ + char* str; + + str = net_addr_to_str(server_addr); + log_info("Setting eamuse server: %s", str); + free(str); + + memcpy(&jbhook1_avs_boot_eamuse_server_addr, server_addr, + sizeof(struct net_addr)); +} \ No newline at end of file diff --git a/src/main/jbhook1/avs-boot.h b/src/main/jbhook1/avs-boot.h new file mode 100644 index 0000000..464fc7f --- /dev/null +++ b/src/main/jbhook1/avs-boot.h @@ -0,0 +1,19 @@ +#ifndef JBHOOK1_AVS_BOOT_H +#define JBHOOK1_AVS_BOOT_H + +#include "util/net.h" + +/** + * Initialize hooking of avs_boot and ea3_boot. This re-enables avs logging + * and injects a few important settings. + */ +void jbhook1_avs_boot_init(); + +/** + * Set the target eamuse server address. + * + * @param server_addr Address to target eamuse server. + */ +void jbhook1_avs_boot_set_eamuse_addr(const struct net_addr* server_addr); + +#endif diff --git a/src/main/jbhook1/config-eamuse.c b/src/main/jbhook1/config-eamuse.c new file mode 100644 index 0000000..61405e9 --- /dev/null +++ b/src/main/jbhook1/config-eamuse.c @@ -0,0 +1,111 @@ +#include + +#include "cconfig/cconfig-util.h" + +#include "jbhook1/config-eamuse.h" + +#include "util/log.h" +#include "util/net.h" + +#define JBHOOK1_CONFIG_EAMUSE_SERVER_KEY "eamuse.server" +#define JBHOOK1_CONFIG_EAMUSE_PCBID_KEY "eamuse.pcbid" +#define JBHOOK1_CONFIG_EAMUSE_EAMID_KEY "eamuse.eamid" + +#define JBHOOK1_CONFIG_EAMUSE_DEFAULT_SERVER_VALUE "localhost:80" +#define JBHOOK1_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE security_id_default +#define JBHOOK1_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE_LEN sizeof(security_id_default) +#define JBHOOK1_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE security_id_default +#define JBHOOK1_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE_LEN sizeof(security_id_default) + +const struct net_addr jbhook1_eamuse_default_server = { + .type = NET_ADDR_TYPE_HOSTNAME, + .hostname.host = "localhost", + .hostname.port = 80, +}; + +void jbhook1_config_eamuse_init(struct cconfig* config) +{ + cconfig_util_set_str(config, + JBHOOK1_CONFIG_EAMUSE_SERVER_KEY, + JBHOOK1_CONFIG_EAMUSE_DEFAULT_SERVER_VALUE, + "URL (e.g. http://my.eamuse.server:80/whatever) or IPV4 " + "(e.g. 127.0.0.1:80) of the target eamuse server. The port is optional " + "but defaults to 80."); + + cconfig_util_set_data(config, JBHOOK1_CONFIG_EAMUSE_PCBID_KEY, + (uint8_t*) &JBHOOK1_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE, + JBHOOK1_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE_LEN, "PCBID"); + + cconfig_util_set_data(config, JBHOOK1_CONFIG_EAMUSE_EAMID_KEY, + (uint8_t*) &JBHOOK1_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE, + JBHOOK1_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE_LEN, "EAMID"); +} + +void jbhook1_config_eamuse_get(struct jbhook1_config_eamuse* config_eamuse, + struct cconfig* config) +{ + char server_url[1024]; + char* tmp; + char* tmp2; + + memset(config_eamuse, 0, sizeof(struct jbhook1_config_eamuse)); + + if (!cconfig_util_get_str(config, JBHOOK1_CONFIG_EAMUSE_SERVER_KEY, + server_url, sizeof(server_url), + JBHOOK1_CONFIG_EAMUSE_DEFAULT_SERVER_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", JBHOOK1_CONFIG_EAMUSE_SERVER_KEY, + JBHOOK1_CONFIG_EAMUSE_DEFAULT_SERVER_VALUE); + } + + if (!net_str_parse(server_url, &config_eamuse->server)) { + memcpy(&config_eamuse->server, &jbhook1_eamuse_default_server, + sizeof(config_eamuse->server)); + tmp = net_addr_to_str(&config_eamuse->server); + log_warning("Invalid value for key '%s' specified, fallback " + "to default", tmp); + free(tmp); + } + + if (!cconfig_util_get_data(config, JBHOOK1_CONFIG_EAMUSE_PCBID_KEY, + (uint8_t*) &config_eamuse->pcbid, + JBHOOK1_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE_LEN, + (uint8_t*) &JBHOOK1_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE)) { + tmp = security_id_to_str(&JBHOOK1_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE, + false); + log_warning("Invalid value for key '%s' specified, fallback " + "to default", tmp); + free(tmp); + } + + if (!security_id_verify(&config_eamuse->pcbid)) { + tmp = security_id_to_str(&JBHOOK1_CONFIG_EAMUSE_DEFAULT_PCBID_VALUE, + false); + tmp2 = security_id_to_str(&config_eamuse->pcbid, false); + log_warning("PCBID verification of '%s' failed, fallback to default " + "PCBID '%s'", tmp2, tmp); + free(tmp); + free(tmp2); + } + + if (!cconfig_util_get_data(config, JBHOOK1_CONFIG_EAMUSE_EAMID_KEY, + (uint8_t*) &config_eamuse->eamid, + JBHOOK1_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE_LEN, + (uint8_t*) &JBHOOK1_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE)) { + tmp = security_id_to_str(&JBHOOK1_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE, + false); + log_warning("Invalid value for key '%s' specified, fallback " + "to default", tmp); + free(tmp); + } + + if (!security_id_verify(&config_eamuse->eamid)) { + tmp = security_id_to_str(&JBHOOK1_CONFIG_EAMUSE_DEFAULT_EAMID_VALUE, + false); + tmp2 = security_id_to_str(&config_eamuse->eamid, false); + log_warning("EAMID verification of '%s' failed, fallback to default " + "EAMID '%s'", tmp2, tmp); + free(tmp); + free(tmp2); + } +} diff --git a/src/main/jbhook1/config-eamuse.h b/src/main/jbhook1/config-eamuse.h new file mode 100644 index 0000000..42044e2 --- /dev/null +++ b/src/main/jbhook1/config-eamuse.h @@ -0,0 +1,36 @@ +#ifndef JBHOOK1_CONFIG_EAMUSE_H +#define JBHOOK1_CONFIG_EAMUSE_H + +#include "cconfig/cconfig.h" + +#include "security/id.h" + +#include "util/net.h" + +/** + * Struct holding configuration values for eamuse related items. + */ +struct jbhook1_config_eamuse { + struct net_addr server; + struct security_id pcbid; + struct security_id eamid; +}; + +/** + * Initialize a cconfig structure with the basic structure and default values + * of this configuration. + */ +void jbhook1_config_eamuse_init(struct cconfig* config); + +/** + * Read the module specific config struct values from the provided cconfig + * struct. + * + * @param config_eamuse Target module specific struct to read configuration + * values to. + * @param config cconfig struct holding the intermediate data to read from. + */ +void jbhook1_config_eamuse_get(struct jbhook1_config_eamuse* config_eamuse, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/jbhook1/config-gfx.c b/src/main/jbhook1/config-gfx.c new file mode 100644 index 0000000..f9efbc8 --- /dev/null +++ b/src/main/jbhook1/config-gfx.c @@ -0,0 +1,31 @@ +#include + +#include "cconfig/cconfig-util.h" + +#include "jbhook1/config-gfx.h" + +#include "util/log.h" + +#define JBHOOK1_CONFIG_GFX_WINDOWED_KEY "gfx.windowed" + +#define JBHOOK1_CONFIG_GFX_DEFAULT_WINDOWED_VALUE false + +void jbhook1_config_gfx_init(struct cconfig* config) +{ + cconfig_util_set_bool(config, + JBHOOK1_CONFIG_GFX_WINDOWED_KEY, + JBHOOK1_CONFIG_GFX_DEFAULT_WINDOWED_VALUE, + "Run the game windowed"); +} + +void jbhook1_config_gfx_get(struct jbhook1_config_gfx* config_gfx, + struct cconfig* config) +{ + if (!cconfig_util_get_bool(config, JBHOOK1_CONFIG_GFX_WINDOWED_KEY, + &config_gfx->windowed, + JBHOOK1_CONFIG_GFX_DEFAULT_WINDOWED_VALUE)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%d'", JBHOOK1_CONFIG_GFX_WINDOWED_KEY, + JBHOOK1_CONFIG_GFX_DEFAULT_WINDOWED_VALUE); + } +} diff --git a/src/main/jbhook1/config-gfx.h b/src/main/jbhook1/config-gfx.h new file mode 100644 index 0000000..cc98dfe --- /dev/null +++ b/src/main/jbhook1/config-gfx.h @@ -0,0 +1,30 @@ +#ifndef JBHOOK1_CONFIG_GFX_H +#define JBHOOK1_CONFIG_GFX_H + +#include "cconfig/cconfig.h" + +/** + * Struct holding configuration values for GFX related items. + */ +struct jbhook1_config_gfx { + bool windowed; +}; + +/** + * Initialize a cconfig structure with the basic structure and default values + * of this configuration. + */ +void jbhook1_config_gfx_init(struct cconfig* config); + +/** + * Read the module specific config struct values from the provided cconfig + * struct. + * + * @param config_gfx Target module specific struct to read configuration + * values to. + * @param config cconfig struct holding the intermediate data to read from. + */ +void jbhook1_config_gfx_get(struct jbhook1_config_gfx* config_gfx, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/jbhook1/config-security.c b/src/main/jbhook1/config-security.c new file mode 100644 index 0000000..6a76841 --- /dev/null +++ b/src/main/jbhook1/config-security.c @@ -0,0 +1,66 @@ +#include + +#include "cconfig/cconfig-util.h" + +#include "jbhook1/config-security.h" + +#include "security/mcode.h" + +#include "util/log.h" +#include "util/net.h" + +#define JBHOOK1_CONFIG_SECURITY_MCODE_KEY "security.mcode" + +#define JBHOOK1_CONFIG_SECURITY_DEFAULT_MCODE_VALUE jbhook1_config_security_default_mcode + +static const struct security_mcode jbhook1_config_security_default_mcode = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_C, + .game = SECURITY_MCODE_GAME_JB_1, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_B, +}; + +void jbhook1_config_security_init(struct cconfig* config) +{ + char* tmp; + + tmp = security_mcode_to_str(&JBHOOK1_CONFIG_SECURITY_DEFAULT_MCODE_VALUE); + + cconfig_util_set_str(config, + JBHOOK1_CONFIG_SECURITY_MCODE_KEY, + tmp, + "Mcode of the game to run."); + + free(tmp); +} + +void jbhook1_config_security_get( + struct jbhook1_config_security* config_security, struct cconfig* config) +{ + char* tmp_default; + char mcode[9]; + + tmp_default = + security_mcode_to_str(&JBHOOK1_CONFIG_SECURITY_DEFAULT_MCODE_VALUE); + + if (!cconfig_util_get_str(config, JBHOOK1_CONFIG_SECURITY_MCODE_KEY, + mcode, sizeof(mcode) - 1, tmp_default)) { + log_warning("Invalid value for key '%s' specified, fallback " + "to default '%s'", JBHOOK1_CONFIG_SECURITY_MCODE_KEY, tmp_default); + } + + mcode[8] = '\0'; + + if (!security_mcode_parse(mcode, &config_security->mcode)) { + log_warning("Invalid mcode '%s' specified, fallback to default '%s'", + mcode, tmp_default); + + memcpy(&config_security->mcode, + &JBHOOK1_CONFIG_SECURITY_DEFAULT_MCODE_VALUE, + sizeof(struct security_mcode)); + } + + free(tmp_default); +} diff --git a/src/main/jbhook1/config-security.h b/src/main/jbhook1/config-security.h new file mode 100644 index 0000000..266b514 --- /dev/null +++ b/src/main/jbhook1/config-security.h @@ -0,0 +1,35 @@ +#ifndef JBHOOK1_CONFIG_SECURITY_H +#define JBHOOK1_CONFIG_SECURITY_H + +#include "cconfig/cconfig.h" + +#include "security/mcode.h" + +#include "util/net.h" + +/** + * Struct holding configuration values for security related items. + */ +struct jbhook1_config_security { + struct security_mcode mcode; +}; + +/** + * Initialize a cconfig structure with the basic structure and default values + * of this configuration. + */ +void jbhook1_config_security_init(struct cconfig* config); + +/** + * Read the module specific config struct values from the provided cconfig + * struct. + * + * @param config_security Target module specific struct to read configuration + * values to. + * @param config cconfig struct holding the intermediate data to read from. + */ +void jbhook1_config_security_get( + struct jbhook1_config_security* config_security, + struct cconfig* config); + +#endif \ No newline at end of file diff --git a/src/main/jbhook1/dllmain.c b/src/main/jbhook1/dllmain.c new file mode 100644 index 0000000..c836e00 --- /dev/null +++ b/src/main/jbhook1/dllmain.c @@ -0,0 +1,175 @@ +#include + +#include +#include +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/jbio.h" + +#include "cconfig/cconfig-hook.h" + +#include "hook/table.h" + +#include "hooklib/adapter.h" +#include "hooklib/acp.h" +#include "hooklib/rs232.h" + +#include "jbhook1/avs-boot.h" +#include "jbhook1/acio.h" +#include "jbhook1/config-gfx.h" +#include "jbhook1/config-eamuse.h" +#include "jbhook1/config-security.h" +#include "jbhook1/log-gftools.h" +#include "jbhook1/p3io.h" + +#include "p3ioemu/devmgr.h" +#include "p3ioemu/emu.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/thread.h" + +#define JBHOOK1_INFO_HEADER \ + "jbhook1 for jubeat" \ + ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) +#define JBHOOK1_CMD_USAGE \ + "Usage: inject.exe jbhook1.dll [options...]" + +static const irp_handler_t jbhook1_handlers[] = { + p3io_emu_dispatch_irp, + ac_io_port_dispatch_irp, +}; + +static HWND CDECL my_mwindow_create(HINSTANCE, void*, const char*, DWORD, + DWORD, BOOL); +static HWND (CDECL *real_mwindow_create)(HINSTANCE, void*, const char*, DWORD, + DWORD, BOOL); + +static const struct hook_symbol init_hook_syms[] = { + { + .name = "mwindow_create", + .patch = my_mwindow_create, + .link = (void **) &real_mwindow_create, + }, +}; + +/** + * This seems to be a good entry point to intercept before the game calls + * anything important (very close to the start of WinMain). + */ +static HWND CDECL my_mwindow_create(HINSTANCE hinstance, void* callback, + const char* window_title, DWORD window_width, DWORD window_height, + BOOL fullscreen) +{ + struct cconfig* config; + + struct jbhook1_config_gfx config_gfx; + struct jbhook1_config_eamuse config_eamuse; + struct jbhook1_config_security config_security; + + log_info("-------------------------------------------------------------"); + log_info("---------------- Begin jbhook mwindow_create ----------------"); + log_info("-------------------------------------------------------------"); + + config = cconfig_init(); + + jbhook1_config_gfx_init(config); + jbhook1_config_eamuse_init(config); + jbhook1_config_security_init(config); + + if (!cconfig_hook_config_init(config, JBHOOK1_INFO_HEADER "\n" JBHOOK1_CMD_USAGE, CCONFIG_CMD_USAGE_OUT_DBG)) { + cconfig_finit(config); + exit(EXIT_FAILURE); + } + + jbhook1_config_gfx_get(&config_gfx, config); + jbhook1_config_eamuse_get(&config_eamuse, config); + jbhook1_config_security_get(&config_security, config); + + cconfig_finit(config); + + log_info(JBHOOK1_INFO_HEADER); + log_info("Initializing jbhook..."); + + jbhook1_avs_boot_init(); + jbhook1_avs_boot_set_eamuse_addr(&config_eamuse.server); + jbhook1_log_gftools_init(); + + fullscreen = !config_gfx.windowed; + + log_info("Starting up jubeat IO backend"); + + jb_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!jb_io_init(thread_create, thread_join, thread_destroy)) { + log_fatal("Initializing jb IO backend failed"); + } + + log_info("Starting up card reader backend"); + + eam_io_set_loggers(log_impl_misc, log_impl_info, log_impl_warning, + log_impl_fatal); + + if (!eam_io_init(thread_create, thread_join, thread_destroy)) { + log_fatal("Initializing card reader backend failed"); + } + + iohook_init(jbhook1_handlers, lengthof(jbhook1_handlers)); + + rs232_hook_init(); + ac_io_port_init(); + + p3io_setupapi_insert_hooks(NULL); + jbhook1_p3io_init(&config_security.mcode, &config_eamuse.pcbid, + &config_eamuse.eamid); + + log_info("-------------------------------------------------------------"); + log_info("----------------- End jbhook mwindow_create -----------------"); + log_info("-------------------------------------------------------------"); + + return real_mwindow_create(hinstance, callback, window_title, window_width, + window_height, fullscreen); +} + +/** + * Hook library for jubeat (1) + */ +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { +#ifdef DEBUG_HOOKING + FILE* file = fopen("jbhook.dllmain.log", "w+"); + log_to_writer(log_writer_file, file); +#else + log_to_writer(log_writer_null, NULL); +#endif + + /* Bootstrap hook for further init tasks (see above) */ + + hook_table_apply( + NULL, + "mwindow.dll", + init_hook_syms, + lengthof(init_hook_syms)); + + + /* Actual hooks for game specific stuff */ + + acp_hook_init(); + adapter_hook_init(); + +#ifdef DEBUG_HOOKING + fflush(file); + fclose(file); +#endif + + /* Logging to file and other destinations is handled by inject */ + log_to_writer(log_writer_debug, NULL); + } + + return TRUE; +} + diff --git a/src/main/jbhook1/jbhook1.def b/src/main/jbhook1/jbhook1.def new file mode 100644 index 0000000..3f6d499 --- /dev/null +++ b/src/main/jbhook1/jbhook1.def @@ -0,0 +1,4 @@ +LIBRARY jbhook1 + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/jbhook1/log-gftools.c b/src/main/jbhook1/log-gftools.c new file mode 100644 index 0000000..6974ef6 --- /dev/null +++ b/src/main/jbhook1/log-gftools.c @@ -0,0 +1,43 @@ +#define LOG_MODULE "log-gftools" + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "util/log.h" + +static int CDECL my_GFReportPuts(int level, const char* source_file, int a3, + const char* func, const char* msg); + +static int (CDECL *real_GFReportPuts)(int level, const char* source_file, + int a3, const char* func, const char* msg); + +static const struct hook_symbol jbhook1_log_gftools_hook_syms[] = { + { + .name = "GFReportPuts", + .patch = my_GFReportPuts, + .link = (void **) &real_GFReportPuts + }, +}; + +static int CDECL my_GFReportPuts(int level, const char* source_file, int a3, + const char* func, const char* msg) +{ + log_misc("[%d][%s][%d][%s]: %s", level, source_file, a3, func, msg); + + return real_GFReportPuts(level, source_file, a3, func, msg); +} + +void jbhook1_log_gftools_init(void) +{ + hook_table_apply( + NULL, + "gftools.dll", + jbhook1_log_gftools_hook_syms, + lengthof(jbhook1_log_gftools_hook_syms)); + + log_info("Inserted gftools log hooks"); +} \ No newline at end of file diff --git a/src/main/jbhook1/log-gftools.h b/src/main/jbhook1/log-gftools.h new file mode 100644 index 0000000..af5b1d7 --- /dev/null +++ b/src/main/jbhook1/log-gftools.h @@ -0,0 +1,10 @@ +#ifndef JBHOOK1_LOG_GFTOOLS_H +#define JBHOOK1_LOG_GFTOOLS_H + +/** + * Initialize the gftools hook module. This hooks a function which is used + * to report errors and redirects the output to our logger. + */ +void jbhook1_log_gftools_init(void); + +#endif diff --git a/src/main/jbhook1/p3io.c b/src/main/jbhook1/p3io.c new file mode 100644 index 0000000..0c0838e --- /dev/null +++ b/src/main/jbhook1/p3io.c @@ -0,0 +1,163 @@ +#include + +#include +#include + +#include "bemanitools/jbio.h" + +#include "jbhook1/p3io.h" + +#include "p3ioemu/emu.h" +#include "p3ioemu/uart.h" + +#include "security/rp3.h" +#include "security/rp-sign-key.h" + +#include "util/log.h" + +static HRESULT jbhook1_p3io_read_jamma(void *ctx, uint32_t *state); +static HRESULT jbhook1_p3io_get_roundplug(void *ctx, uint8_t plug_id, + uint8_t* rom, uint8_t* eeprom); + +/* + 0:0 b13 + 0:1 - + 0:2 b15 + 0:3 - + 0:4 test + 0:5 coin + 0:6 service + 0:7 - + + 1:0 - + 1:1 b4 + 1:2 b8 + 1:3 b12 + 1:4 b16 + 1:5 b3 + 1:6 b7 + 1:7 b11 + + 2:0 - + 2:1 b2 + 2:2 b6 + 2:3 b10 + 2:4 b14 + 2:5 b1 + 2:6 b5 + 2:7 b9 + + 3:0 - + 3:1 - + 3:2 - + 3:3 - + 3:4 - + 3:5 - + 3:6 - + 3:7 - +*/ +static const uint32_t jbhook1_p3io_panel_mappings[] = { + (1 << 21), + (1 << 17), + (1 << 13), + (1 << 9), + (1 << 22), + (1 << 18), + (1 << 14), + (1 << 10), + (1 << 23), + (1 << 19), + (1 << 15), + (1 << 11), + (1 << 0), + (1 << 20), + (1 << 2), + (1 << 12), +}; + +static const uint32_t jbhook1_p3io_sys_button_mappings[] = { + (1 << 4), + (1 << 6), + (1 << 5), +}; + +static struct security_mcode jbhook1_p3io_mcode; +static struct security_id jbhook1_p3io_pcbid; +static struct security_id jbhook1_p3io_eamid; + +static const struct p3io_ops p3io_ddr_ops = { + .read_jamma = jbhook1_p3io_read_jamma, + .get_roundplug = jbhook1_p3io_get_roundplug, +}; + +void jbhook1_p3io_init(const struct security_mcode* mcode, + const struct security_id* pcbid, const struct security_id* eamid) +{ + memcpy(&jbhook1_p3io_mcode, mcode, sizeof(struct security_mcode)); + memcpy(&jbhook1_p3io_pcbid, pcbid, sizeof(struct security_id)); + memcpy(&jbhook1_p3io_eamid, eamid, sizeof(struct security_id)); + + p3io_emu_init(&p3io_ddr_ops, NULL); +} + +void jbhook1_p3io_finit(void) +{ + p3io_emu_fini(); +} + +static HRESULT jbhook1_p3io_read_jamma(void *ctx, uint32_t *state) +{ + uint16_t panels; + uint8_t buttons; + + log_assert(state != NULL); + + /* lower three bytes low active, highest byte high active */ + *state = 0; + + if (!jb_io_read_inputs()) { + log_warning("Reading inputs from jbio failed"); + return E_FAIL; + } + + panels = jb_io_get_panel_inputs(); + buttons = jb_io_get_sys_inputs(); + + for (uint8_t i = 0; i < 16; i++) { + if (panels & (1 << i)) { + *state |= jbhook1_p3io_panel_mappings[i]; + } + } + + for (uint8_t i = 0; i < 2; i++) { + if (buttons & (1 << i)) { + *state |= jbhook1_p3io_sys_button_mappings[i]; + } + } + + return S_OK; +} + +static HRESULT jbhook1_p3io_get_roundplug(void *ctx, uint8_t plug_id, + uint8_t* rom, uint8_t* eeprom) +{ + struct security_rp3_eeprom eeprom_out; + + if (plug_id == 0) { + /* black */ + memcpy(rom, jbhook1_p3io_pcbid.id, sizeof(jbhook1_p3io_pcbid.id)); + security_rp3_generate_signed_eeprom_data( + SECURITY_RP_UTIL_RP_TYPE_BLACK, &security_rp_sign_key_black_gfdmv4, + &jbhook1_p3io_mcode, &jbhook1_p3io_pcbid, &eeprom_out); + } else { + /* white */ + memcpy(rom, jbhook1_p3io_eamid.id, sizeof(jbhook1_p3io_eamid.id)); + security_rp3_generate_signed_eeprom_data( + SECURITY_RP_UTIL_RP_TYPE_WHITE, &security_rp_sign_key_white_eamuse, + &security_mcode_eamuse, &jbhook1_p3io_eamid, &eeprom_out); + } + + memcpy(eeprom, &eeprom_out, sizeof(struct security_rp3_eeprom)); + + return S_OK; +} diff --git a/src/main/jbhook1/p3io.h b/src/main/jbhook1/p3io.h new file mode 100644 index 0000000..383d261 --- /dev/null +++ b/src/main/jbhook1/p3io.h @@ -0,0 +1,26 @@ +#ifndef JBHOOK1_P3IO_H +#define JBHOOK1_P3IO_H + +#include + +#include "hook/iohook.h" + +#include "security/id.h" +#include "security/mcode.h" + +/** + * Initialize the jbhook1 specific p3io emulation backend. + * + * @param mcode Mcode of the target game to run. Required for dongle emulation. + * @param pcbid PCBDID + * @param eamid EAMID + */ +void jbhook1_p3io_init(const struct security_mcode* mcode, + const struct security_id* pcbid, const struct security_id* eamid); + +/** + * Shutdown the p3io emulation backend. + */ +void jbhook1_p3io_fini(void); + +#endif diff --git a/src/main/jbio/Module.mk b/src/main/jbio/Module.mk new file mode 100644 index 0000000..e859879 --- /dev/null +++ b/src/main/jbio/Module.mk @@ -0,0 +1,11 @@ +dlls += jbio + +ldflags_jbio := \ + -lwinmm + +libs_jbio := \ + geninput + +src_jbio := \ + jbio.c \ + diff --git a/src/main/jbio/jbio.c b/src/main/jbio/jbio.c new file mode 100644 index 0000000..f793aba --- /dev/null +++ b/src/main/jbio/jbio.c @@ -0,0 +1,122 @@ +/* This is the source code for the JBIO.DLL that ships with Bemanitools 5. + + If you want to add on some minor functionality like a custom RGB LED setup + then feel free to extend this code with support for your custom device. + + If you want to make a completely custom IO board that handles all input and + lighting then you'd be better off writing your own from scratch. Consult + the "bemanitools" header files included by this source file for detailed + information about the API you'll need to implement. */ + +#include +#include + +#include "bemanitools/input.h" +#include "bemanitools/jbio.h" + +static uint16_t jb_io_panels; +static uint8_t jb_io_sys_buttons; + +/* Uncomment these if you need them. */ + +#if 0 +static log_formatter_t jb_io_log_misc; +static log_formatter_t jb_io_log_info; +static log_formatter_t jb_io_log_warning; +static log_formatter_t jb_io_log_fatal; +#endif + +void jb_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + /* Pass logger functions on to geninput so that it has somewhere to write + its own log output. */ + + input_set_loggers(misc, info, warning, fatal); + + /* Uncomment this block if you have something you'd like to log. + + You should probably return false from the appropriate function instead + of calling the fatal logger yourself though. */ + +#if 0 + jb_io_log_misc = misc; + jb_io_log_info = info; + jb_io_log_warning = warning; + jb_io_log_fatal = fatal; +#endif +} + +bool jb_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + timeBeginPeriod(1); + + input_init(thread_create, thread_join, thread_destroy); + mapper_config_load("jb"); + + /* Initialize your own IO devices here. Log something and then return + false if the initialization fails. */ + + return true; +} + +void jb_io_fini(void) +{ + /* This function gets called as JB shuts down after an Alt-F4. Close your + connections to your IO devices here. */ + + input_fini(); + timeEndPeriod(1); +} + +bool jb_io_read_inputs(void) +{ + uint32_t buttons; + /* Sleep first: input is timestamped immediately AFTER the ioctl returns. + + Which is the right thing to do, for once. We sleep here because + the game polls input in a tight loop. Can't complain, at there isn't + an artificial limit on the poll frequency. */ + + Sleep(1); + + /* Update all of our input state here. */ + + buttons = (uint32_t) mapper_update(); + + /* Mask out the stuff provided by geninput and store the panel/button state + for later retrieval via jb_io_get_buttons() */ + + jb_io_panels = buttons & 0xFFFF; + jb_io_sys_buttons = (buttons >> 16) & 0x03; + + return true; +} + +bool jb_io_write_outputs(void) +{ + /* The generic input stack currently initiates lighting sends and input + reads simultaneously, though this might change later. Perform all of our + I/O immediately before reading out the inputs so that the input state is + as fresh as possible. */ + + return true; +} + +uint8_t jb_io_get_sys_inputs(void) +{ + return jb_io_sys_buttons; +} + +uint16_t jb_io_get_panel_inputs(void) +{ + return jb_io_panels; +} + +void jb_io_set_rgb_led(enum jb_io_rgb_led unit, uint8_t r, uint8_t g, uint8_t b) +{ + mapper_write_light(unit * 3, r); + mapper_write_light(unit * 3 + 1, g); + mapper_write_light(unit * 3 + 2, b); +} \ No newline at end of file diff --git a/src/main/jbio/jbio.def b/src/main/jbio/jbio.def new file mode 100644 index 0000000..4b8f183 --- /dev/null +++ b/src/main/jbio/jbio.def @@ -0,0 +1,11 @@ +LIBRARY jbio + +EXPORTS + jb_io_fini + jb_io_get_panel_inputs + jb_io_get_sys_inputs + jb_io_init + jb_io_read_inputs + jb_io_set_loggers + jb_io_set_rgb_led + jb_io_write_outputs \ No newline at end of file diff --git a/src/main/launcher/Module.mk b/src/main/launcher/Module.mk new file mode 100644 index 0000000..96f735c --- /dev/null +++ b/src/main/launcher/Module.mk @@ -0,0 +1,23 @@ +avsexes += launcher +rc_launcher := launcher.rc + +ldflags_launcher := \ + -mconsole \ + +deplibs_launcher := \ + avs \ + avs-ea3 \ + +libs_launcher := \ + hook \ + util \ + +src_launcher := \ + avs-context.c \ + ea3-config.c \ + main.c \ + module.c \ + options.c \ + property.c \ + stubs.c \ + diff --git a/src/main/launcher/avs-context.c b/src/main/launcher/avs-context.c new file mode 100644 index 0000000..23ac331 --- /dev/null +++ b/src/main/launcher/avs-context.c @@ -0,0 +1,60 @@ +#include + +#include +#include +#include + +#include "imports/avs.h" + +#include "launcher/avs-context.h" + +#include "util/log.h" + +static void *avs_heap; + +#ifdef AVS_HAS_STD_HEAP +static void *std_heap; +#endif + +void avs_context_init(struct property_node *config, + uint32_t avs_heap_size, uint32_t std_heap_size, + avs_log_writer_t log_writer, void *log_writer_ctx) +{ + avs_heap = VirtualAlloc(NULL, avs_heap_size, MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); + + if (avs_heap == NULL) { + log_fatal("Failed to VirtualAlloc %d byte AVS heap: %08x", + avs_heap_size, (unsigned int) GetLastError()); + } + +#ifdef AVS_HAS_STD_HEAP + std_heap = VirtualAlloc(NULL, std_heap_size, MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); + + if (std_heap == NULL) { + log_fatal("Failed to VirtualAlloc %d byte \"std\" heap: %08x", + std_heap_size, (unsigned int) GetLastError()); + } +#endif + +#ifdef AVS_HAS_STD_HEAP + avs_boot(config, std_heap, std_heap_size, avs_heap, avs_heap_size, + log_writer, log_writer_ctx); +#else + /* AVS v2.16.xx and I suppose onward uses a unified heap */ + avs_boot(config, avs_heap, avs_heap_size, NULL, log_writer, log_writer_ctx); +#endif +} + +void avs_context_fini(void) +{ + avs_shutdown(); + +#ifdef AVS_HAS_STD_HEAP + VirtualFree(std_heap, 0, MEM_RELEASE); +#endif + + VirtualFree(avs_heap, 0, MEM_RELEASE); +} + diff --git a/src/main/launcher/avs-context.h b/src/main/launcher/avs-context.h new file mode 100644 index 0000000..551e324 --- /dev/null +++ b/src/main/launcher/avs-context.h @@ -0,0 +1,17 @@ +#ifndef LAUNCHER_AVS_CONTEXT_H +#define LAUNCHER_AVS_CONTEXT_H + +#include + +#include "imports/avs.h" + +#if AVS_VERSION < 1600 +#define AVS_HAS_STD_HEAP +#endif + +void avs_context_init(struct property_node *config, + uint32_t avs_heap_size, uint32_t std_heap_size, + avs_log_writer_t log_writer, void *log_writer_ctx); +void avs_context_fini(void); + +#endif diff --git a/src/main/launcher/ea3-config.c b/src/main/launcher/ea3-config.c new file mode 100644 index 0000000..4ed31d3 --- /dev/null +++ b/src/main/launcher/ea3-config.c @@ -0,0 +1,122 @@ +#include + +#include "imports/avs.h" + +#include "launcher/ea3-config.h" +#include "launcher/module.h" + +#include "util/defs.h" +#include "util/hex.h" +#include "util/log.h" +#include "util/str.h" + +PSMAP_BEGIN(ea3_ident_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident, softid,"/ea3/id/softid","") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident, hardid,"/ea3/id/hardid","") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident, pcbid, "/ea3/id/pcbid", "") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, model, "/ea3/soft/model") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, dest, "/ea3/soft/dest") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, spec, "/ea3/soft/spec") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, rev, "/ea3/soft/rev") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, ext, "/ea3/soft/ext") +PSMAP_END + +void ea3_ident_init(struct ea3_ident *ident) +{ + memset(ident, 0, sizeof(*ident)); +} + +bool ea3_ident_from_property(struct ea3_ident *ident, + struct property *ea3_config) +{ + return property_psmap_import(ea3_config, NULL, ident, ea3_ident_psmap); +} + +void ea3_ident_hardid_from_ethernet(struct ea3_ident *ident) +{ + struct avs_net_interface netif; + int result; + + result = avs_net_ctrl(1, &netif, sizeof(netif)); + + if (result < 0) { + log_fatal("avs_net_ctrl call to get MAC address returned error: %d", + result); + } + + ident->hardid[0] = '0'; + ident->hardid[1] = '1'; + ident->hardid[2] = '0'; + ident->hardid[3] = '0'; + + hex_encode_uc(netif.mac_addr, sizeof(netif.mac_addr), + ident->hardid + 4, sizeof(ident->hardid) - 4); +} + +bool ea3_ident_invoke_module_init(struct ea3_ident *ident, + const struct module_context *module, struct property_node *app_config) +{ + char sidcode_short[17]; + char sidcode_long[21]; + char security_code[9]; + bool ok; + + /* Set up security env vars */ + + str_format(security_code, lengthof(security_code), "G*%s%s%s%s", + ident->model, ident->dest, ident->spec, ident->rev); + + std_setenv("/env/boot/version", "0.0.0"); + std_setenv("/env/profile/security_code", security_code); + std_setenv("/env/profile/system_id", ident->pcbid); + std_setenv("/env/profile/account_id", ident->pcbid); + std_setenv("/env/profile/license_id", ident->softid); + std_setenv("/env/profile/software_id", ident->softid); + std_setenv("/env/profile/hardware_id", ident->hardid); + + /* Set up the short sidcode string, let dll_entry_init mangle it */ + + str_format(sidcode_short, lengthof(sidcode_short), "%s%s%s%s%s", + ident->model, ident->dest, ident->spec, ident->rev, ident->ext); + + ok = module_context_invoke_init(module, sidcode_short, app_config); + + if (!ok) { + return false; + } + + /* Back-propagate sidcode */ + + memcpy(ident->model, sidcode_short + 0, sizeof(ident->model) - 1); + ident->dest[0] = sidcode_short[3]; + ident->spec[0] = sidcode_short[4]; + ident->rev[0] = sidcode_short[5]; + memcpy(ident->ext, sidcode_short + 6, sizeof(ident->ext)); + + /* Set up long-form sidcode env var */ + + str_format(sidcode_long, lengthof(sidcode_long), "%s:%s:%s:%s:%s", + ident->model, ident->dest, ident->spec, ident->rev, ident->ext); + + std_setenv("/env/profile/soft_id_code", sidcode_long); + + return true; +} + +void ea3_ident_to_property(const struct ea3_ident *ident, + struct property *ea3_config) +{ + struct property_node *node; + int i; + + for (i = 0 ; ea3_ident_psmap[i].type != 0xFF ; i++) { + node = property_search(ea3_config, 0, ea3_ident_psmap[i].path); + + if (node != NULL) { + property_node_remove(node); + } + } + + property_psmap_export(ea3_config, NULL, ident, ea3_ident_psmap); +} + diff --git a/src/main/launcher/ea3-config.h b/src/main/launcher/ea3-config.h new file mode 100644 index 0000000..72a1775 --- /dev/null +++ b/src/main/launcher/ea3-config.h @@ -0,0 +1,40 @@ +#ifndef LAUNCHER_EA3_CONFIG_H +#define LAUNCHER_EA3_CONFIG_H + +#include "imports/avs.h" + +#include "launcher/module.h" + +/* N.B. even though this might look like a Konami ABI, this is purely an + internal data structure. */ + +struct ea3_ident { + /* psmapped structure offset can't be zero for some stupid reason */ + + uint32_t dummy; + + /* Initialized from ea3-config.xml, then fed back from sidcode_short */ + + char model[4]; + char dest[4]; + char spec[4]; + char rev[4]; + char ext[11]; + + /* Initialized from ea3-config.xml (hardware_id defaults to MAC addr) */ + + char softid[24]; + char hardid[24]; + char pcbid[24]; +}; + +void ea3_ident_init(struct ea3_ident *ident); +bool ea3_ident_from_property(struct ea3_ident *ident, + struct property *ea3_config); +void ea3_ident_hardid_from_ethernet(struct ea3_ident *ident); +bool ea3_ident_invoke_module_init(struct ea3_ident *ident, + const struct module_context *module, struct property_node *app_config); +void ea3_ident_to_property(const struct ea3_ident *ident, + struct property *ea3_config); + +#endif diff --git a/src/main/launcher/launcher.exe.manifest b/src/main/launcher/launcher.exe.manifest new file mode 100644 index 0000000..863d1a4 --- /dev/null +++ b/src/main/launcher/launcher.exe.manifest @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/main/launcher/launcher.rc b/src/main/launcher/launcher.rc new file mode 100644 index 0000000..0e4c794 --- /dev/null +++ b/src/main/launcher/launcher.rc @@ -0,0 +1,2 @@ +#include +1 RT_MANIFEST launcher.exe.manifest diff --git a/src/main/launcher/main.c b/src/main/launcher/main.c new file mode 100644 index 0000000..609a4f5 --- /dev/null +++ b/src/main/launcher/main.c @@ -0,0 +1,276 @@ +#include + +#include +#include +#include +#include + +#include "imports/avs.h" +#include "imports/avs-ea3.h" + +#include "launcher/avs-context.h" +#include "launcher/ea3-config.h" +#include "launcher/module.h" +#include "launcher/options.h" +#include "launcher/property.h" +#include "launcher/stubs.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" +#include "util/codepage.h" + +/* Gratuitous API changes orz */ +static AVS_LOG_WRITER(log_callback, chars, nchars, ctx) +{ + wchar_t *utf16; + char *utf8; + int utf16_len; + int utf8_len; + int result; + DWORD nwritten; + HANDLE console; + HANDLE file; + + /* Ignore existing NUL terminator */ + + nchars--; + + /* Transcode shit_jis to UTF-8 */ + + utf16_len = MultiByteToWideChar(CP_SHIFT_JIS, 0, chars, nchars, NULL, 0); + + if (utf16_len == 0) { + abort(); + } + + utf16 = xmalloc(sizeof(*utf16) * utf16_len); + result = MultiByteToWideChar(CP_SHIFT_JIS, 0, chars, nchars, utf16, + utf16_len); + + if (result == 0) { + abort(); + } + + utf8_len = WideCharToMultiByte(CP_UTF8, 0, utf16, utf16_len, NULL, 0, + NULL, NULL); + + if (utf8_len == 0) { + abort(); + } + + utf8 = xmalloc(utf8_len + 2); + result = WideCharToMultiByte(CP_UTF8, 0, utf16, utf16_len, utf8, utf8_len, + NULL, NULL); + + if (result == 0) { + abort(); + } + +#if AVS_VERSION >= 1500 + utf8[utf8_len + 0] = '\r'; + utf8[utf8_len + 1] = '\n'; + + utf8_len += 2; +#endif + + /* Write to console and log file */ + + file = (HANDLE) ctx; + console = GetStdHandle(STD_OUTPUT_HANDLE); + + if (ctx != INVALID_HANDLE_VALUE) { + WriteFile(file, utf8, utf8_len, &nwritten, NULL); + } + + WriteFile(console, utf8, utf8_len, &nwritten, NULL); + + /* Clean up */ + + free(utf8); + free(utf16); +} + +int main(int argc, const char **argv) +{ + int i; + const char *hook_dll; + bool ok; + HANDLE logfile; + + struct ea3_ident ea3; + struct module_context module; + struct options options; + + struct property *app_config; + struct property *avs_config; + struct property *ea3_config; + + struct property_node *app_config_root; + struct property_node *avs_config_root; + struct property_node *ea3_config_root; + + log_to_writer(log_writer_file, stdout); + + /* Read command line */ + + options_init(&options); + + if (!options_read_cmdline(&options, argc, argv)) { + options_print_usage(); + + return EXIT_FAILURE; + } + + /* If enabled, wait for a remote debugger to attach. Spawning launcher + with a debugger crashes it for some reason (e.g. on jubeat08). However, + starting the launcher separately and attaching a remote debugger works */ + + if (options.remote_debugger) { + log_info("Waiting until debugger attaches to remote process..."); + + while (true) { + BOOL res = FALSE; + if (!CheckRemoteDebuggerPresent(GetCurrentProcess(), &res)) { + log_fatal("CheckRemoteDebuggerPresent failed: %08x", + (unsigned int) GetLastError()); + } + + if (res) { + log_info("Debugger attached, resuming"); + break; + } + + Sleep(1000); + } + } + + /* Start up AVS */ + + if (options.logfile != NULL) { + logfile = CreateFileA(options.logfile, GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_ALWAYS, 0, NULL); + } else { + logfile = INVALID_HANDLE_VALUE; + } + + avs_config = boot_property_load(options.avs_config_path); + avs_config_root = property_search(avs_config, 0, "/config"); + + if (avs_config_root == NULL) { + log_fatal("%s: /config missing", options.avs_config_path); + } + + avs_context_init(avs_config_root, + options.avs_heap_size, options.std_heap_size, + log_callback, logfile); + + boot_property_free(avs_config); + + log_to_external(log_body_misc, log_body_info, log_body_warning, + log_body_fatal); + + /* Load game DLL */ + + module_context_init(&module, options.module); + + /* Load hook DLLs */ + + for (i = 0 ; i < options.hook_dlls.nitems ; i++) { + hook_dll = *array_item(char *, &options.hook_dlls, i); + + if (LoadLibraryA(hook_dll) == NULL) { + LPSTR buffer; + + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &buffer, 0, NULL); + + log_fatal("%s: Failed to load hook DLL: %s", hook_dll, buffer); + + LocalFree(buffer); + } + } + + /* Inject GetModuleHandle hooks */ + + stubs_init(); + + /* Prepare ea3 config */ + + ea3_config = boot_property_load(options.ea3_config_path); + ea3_config_root = property_search(ea3_config, 0, "/ea3"); + + if (ea3_config_root == NULL) { + log_fatal("%s: /ea3 missing", options.ea3_config_path); + } + + ea3_ident_init(&ea3); + + if (!ea3_ident_from_property(&ea3, ea3_config)) { + log_fatal("%s: Error reading IDs from config file", + options.ea3_config_path); + } + + if (options.softid != NULL) { + str_cpy(ea3.softid, lengthof(ea3.softid), options.softid); + } + + if (options.pcbid != NULL) { + str_cpy(ea3.pcbid, lengthof(ea3.pcbid), options.pcbid); + } + + if (!ea3.hardid[0]) { + ea3_ident_hardid_from_ethernet(&ea3); + } + + /* Invoke dll_entry_init */ + + app_config = boot_property_load(options.app_config_path); + app_config_root = property_search(app_config, 0, "/param"); + + if (app_config_root == NULL) { + log_fatal("%s: /param missing", options.app_config_path); + } + + if (IsDebuggerPresent()) { + /* Opportunity for breakpoint setup etc */ + DebugBreak(); + } + + ok = ea3_ident_invoke_module_init(&ea3, &module, app_config_root); + + if (!ok) { + log_fatal("%s: dll_module_init() returned failure", options.module); + } + + boot_property_free(app_config); + + ea3_ident_to_property(&ea3, ea3_config); + + /* Start up e-Amusement client */ + + ea3_boot(ea3_config_root); + boot_property_free(ea3_config); + + /* Run application */ + + module_context_invoke_main(&module); + + /* Shut down */ + + ea3_shutdown(); + + log_to_writer(log_writer_file, stdout); + avs_context_fini(); + + if (logfile != INVALID_HANDLE_VALUE) { + CloseHandle(logfile); + } + + module_context_fini(&module); + options_fini(&options); + + return EXIT_SUCCESS; +} + diff --git a/src/main/launcher/module.c b/src/main/launcher/module.c new file mode 100644 index 0000000..8003fbb --- /dev/null +++ b/src/main/launcher/module.c @@ -0,0 +1,82 @@ +#include + +#include "imports/avs.h" +#include "imports/eapki.h" + +#include "launcher/module.h" + +#include "util/log.h" +#include "util/str.h" + +void module_context_init(struct module_context *module, const char *path) +{ + log_assert(module != NULL); + log_assert(path != NULL); + + module->dll = LoadLibrary(path); + + if (module->dll == NULL) { + LPSTR buffer; + + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &buffer, 0, NULL); + + log_fatal("%s: Failed to load game DLL: %s", path, buffer); + + LocalFree(buffer); + } + + module->path = str_dup(path); +} + +bool module_context_invoke_init(const struct module_context *module, + char *sidcode, struct property_node *app_config) +{ + dll_entry_init_t init; + + log_assert(module != NULL); + log_assert(sidcode != NULL); + log_assert(app_config != NULL); + + init = (void *) GetProcAddress(module->dll, "dll_entry_init"); + + if (init == NULL) { + log_fatal( + "%s: dll_entry_init not found. Is this a game DLL?", + module->path); + } + + return init(sidcode, app_config); +} + +bool module_context_invoke_main(const struct module_context *module) +{ + /* GCC warns if you call a variable "main" */ + dll_entry_main_t main_; + + log_assert(module != NULL); + + main_ = (void *) GetProcAddress(module->dll, "dll_entry_main"); + + if (main_ == NULL) { + log_fatal( + "%s: dll_entry_main not found. Is this a game DLL?", + module->path); + } + + return main_(); +} + +void module_context_fini(struct module_context *module) +{ + if (module == NULL) { + return; + } + + free(module->path); + + if (module->dll != NULL) { + FreeLibrary(module->dll); + } +} + diff --git a/src/main/launcher/module.h b/src/main/launcher/module.h new file mode 100644 index 0000000..c93944b --- /dev/null +++ b/src/main/launcher/module.h @@ -0,0 +1,19 @@ +#ifndef LAUNCHER_MODULE_H +#define LAUNCHER_MODULE_H + +#include + +#include "imports/avs.h" + +struct module_context { + HMODULE dll; + char *path; +}; + +void module_context_init(struct module_context *module, const char *path); +bool module_context_invoke_init(const struct module_context *module, + char *sidcode, struct property_node *app_config); +bool module_context_invoke_main(const struct module_context *module); +void module_context_fini(struct module_context *module); + +#endif diff --git a/src/main/launcher/options.c b/src/main/launcher/options.c new file mode 100644 index 0000000..a0ee95e --- /dev/null +++ b/src/main/launcher/options.c @@ -0,0 +1,173 @@ +#include +#include + +#include "imports/avs.h" + +#include "launcher/options.h" + +#include "util/array.h" +#include "util/str.h" + +#define DEFAULT_HEAP_SIZE 16777216 + +void options_init(struct options *options) +{ + options->std_heap_size = DEFAULT_HEAP_SIZE; + options->avs_heap_size = DEFAULT_HEAP_SIZE; + options->app_config_path = "prop/app-config.xml"; + options->avs_config_path = "prop/avs-config.xml"; + options->ea3_config_path = "prop/ea3-config.xml"; + options->softid = NULL; + options->pcbid = NULL; + options->module = NULL; + options->logfile = NULL; + options->remote_debugger = false; + array_init(&options->hook_dlls); +} + +bool options_read_cmdline(struct options *options, int argc, const char **argv) +{ + for (int i = 1 ; i < argc ; i++) { + if (argv[i][0] == '-') { + switch (argv[i][1]) { + case 'A': + if (i + 1 >= argc) { + return false; + } + + options->app_config_path = argv[++i]; + + break; + + case 'E': + if (i + 1 >= argc) { + return false; + } + + options->ea3_config_path = argv[++i]; + + break; + + case 'V': + if (i + 1 >= argc) { + return false; + } + + options->avs_config_path = argv[++i]; + + break; + + case 'P': + if (i + 1 >= argc) { + return false; + } + + options->pcbid = argv[++i]; + + break; + + case 'R': + if (i + 1 >= argc) { + return false; + } + + options->softid = argv[++i]; + + break; + + case 'H': + if (i + 1 >= argc) { + return false; + } + + options->avs_heap_size = (size_t)strtol(argv[++i],NULL,0); + + if (options->avs_heap_size == 0) { + return false; + } + + break; + +#ifdef AVS_HAS_STD_HEAP + case 'T': + if (i + 1 >= argc) { + return false; + } + + options->std_heap_size = (size_t)strtol(argv[++i],NULL,0); + + if (options->std_heap_size == 0) { + return false; + } + + break; +#endif + + case 'K': + if (i + 1 >= argc) { + return false; + } + + *array_append(const char *,&options->hook_dlls)=argv[++i]; + + break; + + case 'Y': + if (i + 1 >= argc) { + return false; + } + + options->logfile = argv[++i]; + + break; + + case 'D': + options->remote_debugger = true; + + break; + + default: + break; + } + } else { + if (!options->module) { + options->module = argv[i]; + } + } + } + + if (options->module) { + return true; + } else { + return false; + } +} + +void options_print_usage(void) +{ + fprintf(stderr, +"Usage: launcher.exe [launcher options...] [hooks options...] \n" +"\n" +" The following options can be specified before the app DLL path:\n" +"\n" +" -A [filename] App configuration file (default: prop/app-config.xml)\n" +" -V [filename] AVS configuration file (default: prop/avs-config.xml)\n" +" -E [filename] ea3 configuration file (default: prop/ea3-config.xml)\n" +" -H [bytes] AVS heap size (default: 16777216)\n" +#ifdef AVS_HAS_STD_HEAP +" -T [bytes] 'std' heap size (default 16777216)\n" +#endif +" -P [pcbid] Specify PCBID (default: use ea3 config)\n" +" -R [pcbid] Specify Soft ID (default: use ea3 config)\n" +" -K [filename] Load hook DLL (can be specified multiple times)\n" +" -Y [filename] Log to a file in addition to the console\n" +" -D Halt the launcher before bootstrapping AVS until a" +" remote debugger is attached\n" + ); +} + +void options_fini(struct options *options) +{ + array_fini(&options->hook_dlls); +} + diff --git a/src/main/launcher/options.h b/src/main/launcher/options.h new file mode 100644 index 0000000..30ebe5b --- /dev/null +++ b/src/main/launcher/options.h @@ -0,0 +1,28 @@ +#ifndef LAUNCHER_OPTIONS_H +#define LAUNCHER_OPTIONS_H + +#include +#include + +#include "util/array.h" + +struct options { + size_t std_heap_size; + size_t avs_heap_size; + const char *app_config_path; + const char *avs_config_path; + const char *ea3_config_path; + const char *softid; + const char *pcbid; + const char *module; + const char *logfile; + struct array hook_dlls; + bool remote_debugger; +}; + +void options_init(struct options *options); +bool options_read_cmdline(struct options *options, int argc, const char **argv); +void options_print_usage(void); +void options_fini(struct options *options); + +#endif diff --git a/src/main/launcher/property.c b/src/main/launcher/property.c new file mode 100644 index 0000000..6079158 --- /dev/null +++ b/src/main/launcher/property.c @@ -0,0 +1,79 @@ +#include + +#include +#include +#include + +#include "imports/avs.h" + +#include "launcher/property.h" + +#include "util/log.h" +#include "util/mem.h" + +static int boot_property_fread(uint32_t context, void *bytes, size_t nbytes) +{ + FILE *f; + + f = TlsGetValue(context); + + return fread(bytes, 1, nbytes, f); +} + +struct property *boot_property_load(const char *filename) +{ + struct property *prop; + void *buffer; + int nbytes; + FILE *f; + uint32_t f_keyhole; + + /* AVS callbacks are only given a 32-bit context parameter, even in 64-bit + builds of AVS. We allocate a 32-bit TLS key and pass the context in this + manner instead. Inefficient, but it works. */ + + f = fopen(filename, "r"); + + f_keyhole = TlsAlloc(); + TlsSetValue(f_keyhole, f); + + if (f == NULL) { + log_fatal("%s: Error opening configuration file", filename); + } + + nbytes = property_read_query_memsize(boot_property_fread, f_keyhole, 0, 0); + + if (nbytes < 0) { + log_fatal("%s: Error parsing configuration file", filename); + } + + buffer = xmalloc(nbytes); + prop = property_create( + PROPERTY_FLAG_READ | + PROPERTY_FLAG_WRITE | + PROPERTY_FLAG_CREATE | + PROPERTY_FLAG_APPEND, + buffer, + nbytes); + rewind(f); + + if (!property_insert_read(prop, 0, boot_property_fread, f_keyhole)) { + log_fatal("%s: Error parsing configuration file", filename); + } + + TlsFree(f_keyhole); + + fclose(f); + + return prop; +} + +void boot_property_free(struct property *prop) +{ + void *buffer; + + buffer = property_desc_to_buffer(prop); + property_destroy(prop); + free(buffer); +} + diff --git a/src/main/launcher/property.h b/src/main/launcher/property.h new file mode 100644 index 0000000..ed7a132 --- /dev/null +++ b/src/main/launcher/property.h @@ -0,0 +1,9 @@ +#ifndef LAUNCHER_PROPERTY_H +#define LAUNCHER_PROPERTY_H + +#include "imports/avs.h" + +struct property *boot_property_load(const char *filename); +void boot_property_free(struct property *prop); + +#endif diff --git a/src/main/launcher/stubs.c b/src/main/launcher/stubs.c new file mode 100644 index 0000000..ca269b1 --- /dev/null +++ b/src/main/launcher/stubs.c @@ -0,0 +1,167 @@ +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "launcher/stubs.h" + +#include "util/defs.h" +#include "util/log.h" + +struct ikey_status { + uint32_t field_0; + uint8_t field_4; + uint8_t field_5; + uint8_t field_6; + uint8_t field_7; + uint32_t cert_valid_start; + uint32_t cert_valid_end; +}; + +struct stub { + const char *name; + void *proc; +}; + +typedef void (*bt_get_fucked_t)(uint32_t /* even in 64-bit */ context); + +static HMODULE STDCALL my_GetModuleHandleA(const char *name); +static HMODULE STDCALL my_GetModuleHandleW(const wchar_t *name); +static void * STDCALL my_GetProcAddress(HMODULE dll, const char *name); + +static void bt_get_ikey_status(struct ikey_status *ik); + +#if AVS_VERSION >= 1600 +static void bt_get_fucked(bt_get_fucked_t callback, uint32_t ctx); +#endif + +static void bt_fcheck_init(void); +static void *bt_fcheck_main(void *unknown); +static void bt_fcheck_finish(void); + +static HMODULE (STDCALL *real_GetModuleHandleA)(const char *name); +static HMODULE (STDCALL *real_GetModuleHandleW)(const wchar_t *name); +static void * (STDCALL *real_GetProcAddress)(HMODULE dll, const char *name); + +static struct stub stub_list[] = { +#if AVS_VERSION >= 1600 + { "k_bt0001", bt_get_ikey_status }, + { "k_bt0002", bt_get_fucked }, +#else + { "bt_get_ikey_status", bt_get_ikey_status }, +#endif + { "bt_fcheck_init", bt_fcheck_init }, + { "bt_fcheck_main", bt_fcheck_main }, + { "bt_fcheck_finish", bt_fcheck_finish }, +}; + +static struct hook_symbol stub_hook_syms[] = { + { + .name = "GetModuleHandleA", + .patch = my_GetModuleHandleA, + .link = (void *)&real_GetModuleHandleA + }, + { + .name = "GetModuleHandleW", + .patch = my_GetModuleHandleW, + .link = (void *)&real_GetModuleHandleW + }, + { + .name = "GetProcAddress", + .patch = my_GetProcAddress, + .link = (void *)&real_GetProcAddress + }, +}; + +static HMODULE STDCALL my_GetModuleHandleA(const char *name) +{ + /* Check name for null because winio32 calls this with null parameters + when the module is loaded on win xp (doesn't happen on win 7) */ + if ( name != NULL && + (_stricmp(name, "kbt.dll") == 0 || + _stricmp(name, "kld.dll") == 0)) { + name = NULL; + } + + return real_GetModuleHandleA(name); +} + +static HMODULE STDCALL my_GetModuleHandleW(const wchar_t *name) +{ + /* Check name for null because winio32 calls this with null parameters + when the module is loaded on win xp (doesn't happen on win 7) */ + if ( name != NULL && + (wcsicmp(name, L"kbt.dll") == 0 || + wcsicmp(name, L"kld.dll") == 0)) { + name = NULL; + } + + return real_GetModuleHandleW(name); +} + +static void * STDCALL my_GetProcAddress(HMODULE dll, const char *name) +{ + size_t i; + + if (dll != real_GetModuleHandleA(NULL)) { + return real_GetProcAddress(dll, name); + } + + for (i = 0 ; i < lengthof(stub_list) ; i++) { + if (strcmp(stub_list[i].name, name) == 0) { + return stub_list[i].proc; + } + } + + log_warning("Request for unknown stub %s", name); + + return NULL; +} + +void stubs_init(void) +{ + hook_table_apply( + NULL, + "kernel32.dll", + stub_hook_syms, + lengthof(stub_hook_syms)); +} + +static void bt_get_ikey_status(struct ikey_status *ik) +{ + memset(ik, 0, sizeof(*ik) * 2); + + ik[0].field_4 = true; + ik[0].field_6 = true; + ik[1].field_4 = true; + ik[1].field_6 = true; + + /* Not strictly necessary */ + ik[0].cert_valid_end = -1; + ik[1].cert_valid_end = -1; +} + +#if AVS_VERSION >= 1600 +static void bt_get_fucked(bt_get_fucked_t callback, uint32_t ctx) +{ + log_info(">>> k_bt0002"); + callback(ctx); + log_info("<<< k_bt0002"); +} +#endif + +static void bt_fcheck_init(void) +{} + +static void *bt_fcheck_main(void *unknown) +{ + return 0; +} + +static void bt_fcheck_finish(void) +{} + diff --git a/src/main/launcher/stubs.h b/src/main/launcher/stubs.h new file mode 100644 index 0000000..3ab6419 --- /dev/null +++ b/src/main/launcher/stubs.h @@ -0,0 +1,6 @@ +#ifndef LAUNCHER_STUBS_H +#define LAUNCHER_STUBS_H + +void stubs_init(void); + +#endif diff --git a/src/main/mempatch-hook/Module.mk b/src/main/mempatch-hook/Module.mk new file mode 100644 index 0000000..9785efa --- /dev/null +++ b/src/main/mempatch-hook/Module.mk @@ -0,0 +1,7 @@ +dlls += mempatch-hook + +libs_mempatch-hook := \ + util \ + +src_mempatch-hook := \ + main.c \ diff --git a/src/main/mempatch-hook/main.c b/src/main/mempatch-hook/main.c new file mode 100644 index 0000000..e395242 --- /dev/null +++ b/src/main/mempatch-hook/main.c @@ -0,0 +1,278 @@ +#include + +#include +#include + +#include "util/cmdline.h" +#include "util/fs.h" +#include "util/hex.h" +#include "util/log.h" + +static bool patch_memory_check_data(uint32_t base_address, uint32_t address, + uint8_t* data_expected, size_t len) +{ + uint8_t* dest = (uint8_t*) (base_address + address); + bool success = true; + + for (size_t i = 0; i < len; i++) { + if (dest[i] != data_expected[i]) { + log_warning("Memcheck error: base %X + address %X + offset %d: " + "expected %X, found %X", base_address, address, i, + data_expected[i], dest[i]); + success = false; + } + } + + return success; +} + +static bool patch_memory_data(uint32_t base_address, uint32_t address, + uint8_t* data, size_t len) +{ + DWORD old_protect; + + uint32_t dest = base_address + address; + + if (!VirtualProtect((void*) dest, sizeof(uint8_t) * len, + PAGE_EXECUTE_READWRITE, &old_protect)) { + log_warning("VirtualProtect %X (%d) failed: %d", dest, len, + (int) GetLastError()); + return false; + } + + memcpy((void*) (base_address + address), data, len); + + if (!VirtualProtect((void*) dest, sizeof(uint8_t) * len, old_protect, + &old_protect)) { + log_warning("VirtualProtect (2) %X (%d) failed: %d", dest, len, + (int) GetLastError()); + return false; + } + + return true; +} + +static bool patch_memory_apply(char* script) +{ + char* pos_lines; + char* ctx_lines; + uint32_t line_cnt; + + char* pos_line; + char* ctx_line; + uint32_t line_tokens; + + char* module; + char* address_str = NULL; + char* data_str = NULL; + size_t data_str_len; + char* data_expected_str = NULL; + size_t data_expected_str_len; + + uint32_t address; + uint32_t address_base; + HMODULE hmodule; + uint8_t data[4096]; + size_t data_len; + uint8_t data_expected[4096]; + size_t data_expected_len; + + bool error; + + error = false; + + line_cnt = 0; + pos_lines = strtok_r(script, "\n", &ctx_lines); + + while (pos_lines != NULL) { + /* ignore comments and empty lines */ + if (strlen(pos_lines) > 0 && pos_lines[0] != '#') { + log_misc("[%d] Parsing: %s", line_cnt, pos_lines); + + pos_line = strtok_r(pos_lines, " ", &ctx_line); + line_tokens = 0; + + while (pos_line != NULL) { + switch (line_tokens) { + case 0: + module = pos_line; + break; + + case 1: + address_str = pos_line; + break; + + case 2: + data_str = pos_line; + break; + + case 3: + data_expected_str = pos_line; + break; + + default: + break; + } + + pos_line = strtok_r(NULL, " ", &ctx_line); + line_tokens++; + } + + if (line_tokens != 3 && line_tokens != 4) { + log_warning("[%d] Invalid number of elements in line '%s' skipping", line_cnt, pos_lines); + goto error_next_line; + } + + if (!strcmp(module, "-")) { + address_base = 0; + } else { + hmodule = GetModuleHandleA(module); + + if (hmodule == NULL) { + log_warning("[%d] Could not find module %s", line_cnt, module); + goto error_next_line; + } + + address_base = (uint32_t) hmodule; + } + + address = strtol(address_str, NULL, 16); + + /* Most likely an error but we don't know for sure */ + if (address == 0) { + log_warning("[%d] Address specified is 0", line_cnt); + } + + data_str_len = strlen(data_str); + data_len = data_str_len; + + if (data_len % 2 != 0) { + log_warning("[%d] Data length %d mod 2 != 0: %s", line_cnt, + data_len, data_str); + goto error_next_line; + } + + data_len /= 2; + + if (line_tokens == 4) { + data_expected_str_len = strlen(data_expected_str); + data_expected_len = data_expected_str_len; + + if (data_expected_len % 2 != 0) { + log_warning("[%d] Data expected length %d mod 2 != 0: %s", + line_cnt, data_len, data_str); + goto error_next_line; + } + } else { + data_expected_len = 0; + } + + data_expected_len /= 2; + + if (!hex_decode(data, sizeof(data), data_str, data_str_len)) { + log_warning("[%d] Decoding data (item 3) failed: %s", line_cnt, + data_str); + goto error_next_line; + } + + if (line_tokens == 4) { + if (!hex_decode(data_expected, sizeof(data_expected), + data_expected_str, data_expected_str_len)) { + log_warning( + "[%d] Decoding expected data (item 4) failed: %s", + line_cnt, data_expected_str); + goto error_next_line; + } + } + + if (data_expected_len) { + if (!patch_memory_check_data(address_base, address, + data_expected, data_expected_len)) { + log_warning("[%d] Memcheck failed, skipping", line_cnt); + goto error_next_line; + } + } + + if (!patch_memory_data(address_base, address, data, data_len)) { + log_warning("[%d] Patching failed, skipping", line_cnt); + goto error_next_line; + } + + log_info("[%d] Patch ok", line_cnt); + } + + goto next_line; + +error_next_line: + error = true; + +next_line: + pos_lines = strtok_r(NULL, "\n", &ctx_lines); + line_cnt++; + } + + return !error; +} + +static void patch_memory_from_file(const char* filepath) +{ + char* buffer; + size_t len; + + if (!file_load(filepath, (void**) &buffer, &len, true)) { + log_fatal("Load script file %s failed", filepath); + } else { + if (!patch_memory_apply(buffer)) { + log_fatal("Applying one or multiple patches failed, see log output " + "for details"); + } + } + + free(buffer); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) +{ + if (reason == DLL_PROCESS_ATTACH) { + int argc; + char** argv; + char* filepath; + FILE* logfile; + bool patched; + + patched = false; + logfile = fopen("mempatch.log", "w+"); + log_to_writer(log_writer_file, logfile); + + args_recover(&argc, &argv); + + for (int i = 0; i < argc; i++) { + /* allow specifying multiple mempatch files */ + if (!strcmp(argv[i], "--mempatch") && i + 1 < argc) { + filepath = argv[i + 1]; + log_info("Loading memory patch file: %s", filepath); + + patch_memory_from_file("test.mph"); + + log_info("Finished patching with file %s", filepath); + i++; + patched = true; + } + } + + if (!patched) { + log_info("No files specified for patching. Add the parameter " + "--mempatch to your application arguments. This can " + "be specified multiple times, e.g. --mempatch file1.mph " + "--mempatch file2.mph. The patches are executed in the order " + "they are specified."); + } else { + log_info("Patching done"); + } + + fflush(logfile); + fclose(logfile); + } + + return TRUE; +} \ No newline at end of file diff --git a/src/main/mempatch-hook/mempatch-hook.def b/src/main/mempatch-hook/mempatch-hook.def new file mode 100644 index 0000000..9c83a24 --- /dev/null +++ b/src/main/mempatch-hook/mempatch-hook.def @@ -0,0 +1,4 @@ +LIBRARY mempatch-hook + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/mm/Module.mk b/src/main/mm/Module.mk new file mode 100644 index 0000000..5ffd406 --- /dev/null +++ b/src/main/mm/Module.mk @@ -0,0 +1,12 @@ +libs += mm + +ldflags_mm := \ + -lhid \ + -lsetupapi \ + +libs_mm := \ + util \ + +src_mm := \ + mm.c \ + diff --git a/src/main/mm/Sources b/src/main/mm/Sources new file mode 100644 index 0000000..7062a2f --- /dev/null +++ b/src/main/mm/Sources @@ -0,0 +1,6 @@ +!include ../Common.inc + +TARGETNAME = libmm +TARGETTYPE = LIBRARY +SOURCES = mm.c + diff --git a/src/main/mm/ddrio.def b/src/main/mm/ddrio.def new file mode 100644 index 0000000..6a8cf27 --- /dev/null +++ b/src/main/mm/ddrio.def @@ -0,0 +1,8 @@ +LIBRARY ddrio + +EXPORTS + ddr_io_fini + ddr_io_init + ddr_io_read_pad + ddr_io_set_lights_extio + ddr_io_set_lights_p3io diff --git a/src/main/mm/mm.c b/src/main/mm/mm.c new file mode 100644 index 0000000..26bc8f6 --- /dev/null +++ b/src/main/mm/mm.c @@ -0,0 +1,275 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "mm/mm.h" + +#include "util/cmdline.h" +#include "util/defs.h" +#include "util/log.h" +#include "util/mem.h" + +DEFINE_GUID(hid_guid, 0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, \ + 0x11, 0x11, 0x00, 0x00, 0x30); + +static HANDLE mm_fd; + +static struct mm_input *mm_in_bufs; +static int mm_in_buf_count; +static int mm_in_buf_pos; +static DWORD mm_in_xferred; +static OVERLAPPED mm_in_ovl; + +static struct mm_output mm_out_buf; +static OVERLAPPED mm_out_ovl; +static bool mm_out_pending; + +static HANDLE mm_open_device(void); +static HANDLE mm_try_device(HDEVINFO dev_info, + SP_DEVICE_INTERFACE_DATA *iface_data); + +static HANDLE mm_open_device(void) +{ + HDEVINFO dev_info; + SP_DEVICE_INTERFACE_DATA iface_data; + HANDLE cur_fd; + DWORD i; + BOOL ok; + + dev_info = SetupDiGetClassDevsW( + &hid_guid, + NULL, + NULL, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (dev_info == NULL) { + log_fatal("SetupDiGetClassDevs failed"); + } + + for (i = 0, cur_fd = NULL ; cur_fd == NULL ; i++) { + iface_data.cbSize = sizeof(iface_data); + + ok = SetupDiEnumDeviceInterfaces( + dev_info, + NULL, + &hid_guid, + i, + &iface_data); + + if (!ok) { + if (GetLastError() == ERROR_NO_MORE_ITEMS) { + break; + } else { + log_fatal( + "SetupDiEnumDeviceInterfaces failed: %#x", + (int) GetLastError()); + } + } + + cur_fd = mm_try_device(dev_info, &iface_data); + } + + SetupDiDestroyDeviceInfoList(dev_info); + + return cur_fd; +} + +static HANDLE mm_try_device(HDEVINFO dev_info, + SP_DEVICE_INTERFACE_DATA *iface_data) +{ + SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail; + HIDD_ATTRIBUTES hid_attrs; + HANDLE cur_fd; + DWORD detail_size; + BOOL ok; + + ok = SetupDiGetDeviceInterfaceDetailW( + dev_info, + iface_data, + NULL, + 0, + &detail_size, + NULL); + + if (!ok && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + log_fatal( + "SetupDiGetDeviceInterfaceDetail sizing failed: %#x", + (int) GetLastError()); + } + + detail = xmalloc(detail_size); + detail->cbSize = sizeof(*detail); + + ok = SetupDiGetDeviceInterfaceDetailW( + dev_info, + iface_data, + detail, + detail_size, + NULL, + NULL); + + if (!ok) { + log_fatal( + "SetupDiGetDeviceInterfaceDetail failed: %#x", + (int) GetLastError()); + } + + cur_fd = CreateFileW( + detail->DevicePath, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + + free(detail); + + if (cur_fd == INVALID_HANDLE_VALUE) { + goto open_fail; + } + + ok = HidD_GetAttributes(cur_fd, &hid_attrs); + + if (!ok) { + log_fatal("HidD_GetAttributes failed"); + } + + if ( hid_attrs.VendorID != MM_VENDOR_ID || + hid_attrs.ProductID != MM_PRODUCT_ID) { + goto id_fail; + } + + return cur_fd; + +id_fail: + CloseHandle(cur_fd); + +open_fail: + return NULL; +} + +bool mm_init(uint32_t in_buf_count) +{ + mm_in_buf_pos = 0; + mm_in_buf_count = in_buf_count; + mm_in_bufs = xcalloc(sizeof(*mm_in_bufs) * in_buf_count); + + log_info("Searching for MiniMaid ..."); + + mm_fd = mm_open_device(); + + if (mm_fd == NULL) { + log_warning("MiniMaid not found"); + + return false; + } + + if (!HidD_SetNumInputBuffers(mm_fd, mm_in_buf_count)) { + log_fatal("HidD_SetNumInputBuffers failed"); + } + + log_info("MiniMaid connected"); + + memset(&mm_in_ovl, 0, sizeof(mm_in_ovl)); + mm_in_ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + memset(&mm_out_ovl, 0, sizeof(mm_out_ovl)); + mm_out_ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + /* Start up buffer chain */ + + mm_in_xferred = sizeof(struct mm_input); + + do { + mm_in_buf_pos = (mm_in_buf_pos + 1) % mm_in_buf_count; + } while (ReadFile(mm_fd, &mm_in_bufs[mm_in_buf_pos], sizeof(*mm_in_bufs), + NULL, &mm_in_ovl)); + + if (GetLastError() != ERROR_IO_PENDING) { + log_fatal("USB IN failed: %#x", (int) GetLastError()); + } + + return true; +} + +void mm_update(const struct mm_output *out, struct mm_input *in) +{ + DWORD xferred; + + /* Lights go first; we defer the input read as much as possible in order + to return the freshest possible pad state */ + + if (mm_out_pending) { + /* Only try to send lights once. If the OUT transfer is still in + flight then we'll retire the IO on the next poll cycle. */ + + if (GetOverlappedResult(mm_fd, &mm_out_ovl, &xferred, FALSE)) { + ResetEvent(mm_out_ovl.hEvent); + + mm_out_pending = false; + } else { + if (GetLastError() != ERROR_IO_INCOMPLETE) { + log_fatal( + "GetOverlappedResult[OUT] failed: %#x", + (int) GetLastError()); + } + } + } + + /* Kick off an async write if one isn't already in progress. We try to + maintain a continuous transfer of OUT reports (even if no lights have + changed) in order to keep the bus utilisation consistent and hence + minimise timing jitter. */ + + if (!mm_out_pending) { + memcpy(&mm_out_buf, out, sizeof(*out)); + + if (!WriteFile(mm_fd, &mm_out_buf, sizeof(mm_out_buf), NULL, + &mm_out_ovl)) { + if (GetLastError() != ERROR_IO_PENDING) { + log_fatal("USB OUT failed: %#x", (int) GetLastError()); + } + } + + mm_out_pending = true; + } + + /* Last TRUE here means that we block until an IN report is available. */ + + if (!GetOverlappedResult(mm_fd, &mm_in_ovl, &mm_in_xferred, TRUE)) { + log_fatal("GetOverlappedResult[IN] failed: %#x", (int) GetLastError()); + } + + /* Pump whatever backlog of reports is available until we block */ + + do { + mm_in_buf_pos = (mm_in_buf_pos + 1) % mm_in_buf_count; + } while (ReadFile(mm_fd, &mm_in_bufs[mm_in_buf_pos], sizeof(*mm_in_bufs), + NULL, &mm_in_ovl)); + + /* (did we actually block or was there an IO error?) */ + + if (GetLastError() != ERROR_IO_PENDING) { + log_fatal("USB IN failed: %#x", (int) GetLastError()); + } + + memcpy(in, &mm_in_bufs[(mm_in_buf_pos + 1) % mm_in_buf_count], sizeof(*in)); +} + +void mm_fini(void) +{ + CloseHandle(mm_fd); + CloseHandle(mm_out_ovl.hEvent); + CloseHandle(mm_in_ovl.hEvent); + + log_info("Closed MiniMaid"); +} + diff --git a/src/main/mm/mm.h b/src/main/mm/mm.h new file mode 100644 index 0000000..410989b --- /dev/null +++ b/src/main/mm/mm.h @@ -0,0 +1,34 @@ +#ifndef LIBMM_MM_H +#define LIBMM_MM_H + +#include +#include + +#define MM_VENDOR_ID 0xBEEF +#define MM_PRODUCT_ID 0x5730 + +#pragma pack(push, 1) + +struct mm_input { + uint8_t report_id; + uint8_t dip_switches; + uint32_t jamma; + uint8_t ext_in; +}; + +struct mm_output { + uint8_t report_id; + uint8_t ext_output; + _Atomic uint32_t lights; + uint8_t blue_led; + uint8_t kbd_enable; + uint8_t aux_flags; +}; + +#pragma pack(pop) + +bool mm_init(uint32_t in_buf_count); +void mm_update(const struct mm_output *out, struct mm_input *in); +void mm_fini(void); + +#endif diff --git a/src/main/p3io/Module.mk b/src/main/p3io/Module.mk new file mode 100644 index 0000000..e4d0fec --- /dev/null +++ b/src/main/p3io/Module.mk @@ -0,0 +1,7 @@ +libs += p3io + +src_p3io := \ + cmd.c \ + guid.c \ + frame.c \ + diff --git a/src/main/p3io/cmd.c b/src/main/p3io/cmd.c new file mode 100644 index 0000000..13e022a --- /dev/null +++ b/src/main/p3io/cmd.c @@ -0,0 +1,30 @@ +#include + +#include "p3io/cmd.h" + +#include "util/log.h" + +uint8_t p3io_req_cmd(const union p3io_req_any *src) +{ + log_assert(src != NULL); + + /* In requests, the command byte is the first byte after the header. */ + + return src->raw[sizeof(struct p3io_hdr)]; +} + +void p3io_resp_init( + struct p3io_hdr *dest, + size_t nbytes, + const struct p3io_hdr *req) +{ + log_assert(dest != NULL); + log_assert(req != NULL); + log_assert(nbytes < 0x100); + + /* Length byte in this packet format counts everything from the length + byte onwards. The length byte itself occurs at the start of the frame. */ + + dest->nbytes = nbytes - 1; + dest->seq_no = req->seq_no; +} diff --git a/src/main/p3io/cmd.h b/src/main/p3io/cmd.h new file mode 100644 index 0000000..1df30dc --- /dev/null +++ b/src/main/p3io/cmd.h @@ -0,0 +1,177 @@ +#ifndef P3IO_CMD_H +#define P3IO_CMD_H + +#include + +#include "p3io/frame.h" + +enum { + P3IO_CMD_GET_VERSION = 0x01, + P3IO_CMD_SET_WATCHDOG = 0x05, + P3IO_CMD_POWEROFF = 0x22, + P3IO_CMD_SET_OUTPUTS = 0x24, + P3IO_CMD_READ_PLUG = 0x25, + P3IO_CMD_GET_CAB_TYPE_OR_DIPSW = 0x27, + P3IO_CMD_GET_VIDEO_FREQ = 0x29, + P3IO_CMD_SET_MODE = 0x2F, + P3IO_CMD_GET_COINSTOCK = 0x31, + P3IO_CMD_SET_COINCOUNTER = 0x32, + P3IO_CMD_RS232_OPEN_CLOSE = 0x38, + P3IO_CMD_RS232_WRITE = 0x3A, + P3IO_CMD_RS232_READ = 0x3B, +}; + +enum { + P3IO_RS232_CMD_OPEN = 0x00, + P3IO_RS232_CMD_CLOSE = 0xFF, +}; + +enum { + P3IO_RS232_BAUD_19200 = 0x02, + P3IO_RS232_BAUD_38400 = 0x03, + P3IO_RS232_BAUD_57600 = 0x04, +}; + +#pragma pack(push, 1) + +struct p3io_hdr { + uint8_t nbytes; + uint8_t seq_no; +}; + +struct p3io_req_u8 { + struct p3io_hdr hdr; + uint8_t cmd; + uint8_t u8; +}; + +struct p3io_req_get_cab_type_or_dipsw { + struct p3io_hdr hdr; + uint8_t cmd; + uint8_t cab_type_or_dipsw; +}; + +struct p3io_req_set_coin_counter { + struct p3io_hdr hdr; + uint8_t cmd; + uint8_t coin_counter[2]; +}; + +struct p3io_req_set_outputs { + struct p3io_hdr hdr; + uint8_t cmd; + uint8_t unk_00; + uint32_t outputs; +}; + +struct p3io_req_read_plug { + struct p3io_hdr hdr; + uint8_t cmd; + uint8_t flags; +}; + +struct p3io_req_rs232_open_close { + struct p3io_hdr hdr; + uint8_t cmd; + uint8_t port_no; + uint8_t subcmd; + uint8_t baud_code; +}; + +struct p3io_req_rs232_read { + struct p3io_hdr hdr; + uint8_t cmd; + uint8_t port_no; + uint8_t nbytes; +}; + +struct p3io_req_rs232_write { + struct p3io_hdr hdr; + uint8_t cmd; + uint8_t port_no; + uint8_t nbytes; + uint8_t bytes[128]; +}; + +union p3io_req_any { + struct p3io_hdr hdr; + struct p3io_req_u8 u8; + struct p3io_req_get_cab_type_or_dipsw cab_type_or_dipsw; + struct p3io_req_set_coin_counter set_coin_counter; + struct p3io_req_set_outputs set_outputs; + struct p3io_req_read_plug read_plug; + struct p3io_req_rs232_open_close rs232_open_close; + struct p3io_req_rs232_read rs232_read; + struct p3io_req_rs232_write rs232_write; + uint8_t raw[128]; +}; + +struct p3io_resp_u8 { + struct p3io_hdr hdr; + uint8_t status; + uint8_t u8; +}; + +struct p3io_resp_get_cab_type_or_dipsw { + struct p3io_hdr hdr; + uint8_t status; +}; + +struct p3io_resp_coin_stock { + struct p3io_hdr hdr; + uint8_t status; + uint8_t error; + uint16_t slots[2]; +}; + +struct p3io_resp_read_plug { + struct p3io_hdr hdr; + uint8_t status; + uint8_t present; + uint8_t rom[8]; + uint8_t eeprom[32]; +}; + +struct p3io_resp_rs232_read { + struct p3io_hdr hdr; + uint8_t status; + uint8_t nbytes; + uint8_t bytes[126]; +}; + +struct p3io_resp_rs232_write { + struct p3io_hdr hdr; + uint8_t status; + uint8_t nbytes; +}; + +struct p3io_resp_version { + struct p3io_hdr hdr; + uint8_t status; + char str[4]; + uint32_t major; + uint32_t minor; + uint32_t patch; +}; + +union p3io_resp_any { + struct p3io_hdr hdr; + struct p3io_resp_u8 u8; + struct p3io_resp_get_cab_type_or_dipsw cab_type_or_dipsw; + struct p3io_resp_coin_stock coin_stock; + struct p3io_resp_read_plug read_plug; + struct p3io_resp_rs232_read rs232_read; + struct p3io_resp_rs232_write rs232_write; + struct p3io_resp_version version; +}; + +#pragma pack(pop) + +uint8_t p3io_req_cmd(const union p3io_req_any *src); + +void p3io_resp_init( + struct p3io_hdr *dest, + size_t nbytes, + const struct p3io_hdr *req); + +#endif diff --git a/src/main/p3io/frame.c b/src/main/p3io/frame.c new file mode 100644 index 0000000..e32cfea --- /dev/null +++ b/src/main/p3io/frame.c @@ -0,0 +1,108 @@ +#include + +#include +#include + +#include "p3io/frame.h" + +#include "util/iobuf.h" +#include "util/log.h" + +HRESULT p3io_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *bytes; + uint8_t b; + size_t i; + + log_assert(dest != NULL); + log_assert(ptr != NULL); + + bytes = ptr; + + if (dest->pos >= dest->nbytes) { + goto trunc; + } + + dest->bytes[dest->pos++] = 0xAA; + + for (i = 0 ; i < nbytes ; i++) { + b = bytes[i]; + + if (b == 0xAA || b == 0xFF) { + if (dest->pos + 1 >= dest->nbytes) { + goto trunc; + } + + dest->bytes[dest->pos++] = 0xFF; + dest->bytes[dest->pos++] = ~b; + } else { + if (dest->pos >= dest->nbytes) { + goto trunc; + } + + dest->bytes[dest->pos++] = b; + } + } + + if (i < nbytes) { + goto trunc; + } + + return S_OK; + +trunc: + return E_FAIL; +} + +HRESULT p3io_frame_decode( + struct iobuf *dest, + struct const_iobuf *src) +{ + bool escape; + uint8_t b; + + log_assert(dest != NULL); + log_assert(src != NULL); + + if (src->pos >= src->nbytes || src->bytes[src->pos] != 0xAA) { + return E_FAIL; + } + + src->pos++; + escape = false; + + while (src->pos < src->nbytes) { + if (dest->pos >= dest->nbytes) { + return E_FAIL; + } + + b = src->bytes[src->pos++]; + + if (b == 0xAA) { + return E_FAIL; + } else if (b == 0xFF) { + if (escape) { + return E_FAIL; + } + + escape = true; + } else { + if (escape) { + dest->bytes[dest->pos++] = ~b; + } else { + dest->bytes[dest->pos++] = b; + } + + escape = false; + } + } + + if (escape) { + return E_FAIL; + } + + return S_OK; +} diff --git a/src/main/p3io/frame.h b/src/main/p3io/frame.h new file mode 100644 index 0000000..626d823 --- /dev/null +++ b/src/main/p3io/frame.h @@ -0,0 +1,19 @@ +#ifndef P3IO_P3IO_H +#define P3IO_P3IO_H + +#include + +#include + +#include "util/iobuf.h" + +HRESULT p3io_frame_encode( + struct iobuf *dest, + const void *bytes, + size_t nbytes); + +HRESULT p3io_frame_decode( + struct iobuf *dest, + struct const_iobuf *src); + +#endif diff --git a/src/main/p3io/guid.c b/src/main/p3io/guid.c new file mode 100644 index 0000000..4e131c1 --- /dev/null +++ b/src/main/p3io/guid.c @@ -0,0 +1,4 @@ +#include +#include + +#include "p3io/guid.h" diff --git a/src/main/p3io/guid.h b/src/main/p3io/guid.h new file mode 100644 index 0000000..9940c1e --- /dev/null +++ b/src/main/p3io/guid.h @@ -0,0 +1,13 @@ +#ifndef P3IO_GUID_H +#define P3IO_GUID_H + +#include + +DEFINE_GUID( + p3io_guid, + 0x1FA4A480, + 0xAC60, + 0x40C7, + 0xA7, 0xAC, 0x52, 0x79, 0x0F, 0x34, 0x57, 0x5A); + +#endif diff --git a/src/main/p3io/ioctl.h b/src/main/p3io/ioctl.h new file mode 100644 index 0000000..2c70b01 --- /dev/null +++ b/src/main/p3io/ioctl.h @@ -0,0 +1,8 @@ +#ifndef P3IO_IOCTL_H +#define P3IO_IOCTL_H + +enum { + P3IO_IOCTL_READ_JAMMA = 0x00222068, +}; + +#endif diff --git a/src/main/p3ioemu/Module.mk b/src/main/p3ioemu/Module.mk new file mode 100644 index 0000000..7b9d1da --- /dev/null +++ b/src/main/p3ioemu/Module.mk @@ -0,0 +1,7 @@ +libs += p3ioemu + +src_p3ioemu := \ + devmgr.c \ + emu.c \ + uart.c \ + diff --git a/src/main/p3ioemu/devmgr.c b/src/main/p3ioemu/devmgr.c new file mode 100644 index 0000000..07f71a1 --- /dev/null +++ b/src/main/p3ioemu/devmgr.c @@ -0,0 +1,266 @@ +#include +#include + +#include +#include + +#include "hook/table.h" + +#include "p3io/guid.h" + +#include "p3ioemu/devmgr.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" + +/* Link pointers */ + +static HDEVINFO (WINAPI * next_SetupDiGetClassDevsW)( + const GUID *class_guid, + const wchar_t *enumerator, + HWND hwnd, + DWORD flags); + +static HDEVINFO (WINAPI * next_SetupDiGetClassDevsA)( + const GUID *class_guid, + const char *enumerator, + HWND hwnd, + DWORD flags); + +static BOOL (WINAPI *next_SetupDiEnumDeviceInterfaces)( + HDEVINFO dev_info, + SP_DEVINFO_DATA *info_data, + const GUID *iface_guid, + DWORD index, + SP_DEVICE_INTERFACE_DATA *ifd); + +static BOOL (WINAPI *next_SetupDiGetDeviceInterfaceDetailW)( + HDEVINFO dev_info, + SP_DEVICE_INTERFACE_DATA *ifd, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail, + DWORD size, + DWORD *required_size, + SP_DEVINFO_DATA *info_data); + +static BOOL (WINAPI *next_SetupDiDestroyDeviceInfoList)(HDEVINFO dev_info); + +/* API hooks */ + +static HDEVINFO WINAPI my_SetupDiGetClassDevsW( + const GUID *class_guid, + const wchar_t *enumerator, + HWND hwnd, + DWORD flags); + +static HDEVINFO WINAPI my_SetupDiGetClassDevsA( + const GUID *class_guid, + const char *enumerator, + HWND hwnd, + DWORD flags); + +static BOOL WINAPI my_SetupDiEnumDeviceInterfaces( + HDEVINFO dev_info, + SP_DEVINFO_DATA *info_data, + const GUID *iface_guid, + DWORD index, + SP_DEVICE_INTERFACE_DATA *ifd); + +static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailW( + HDEVINFO dev_info, + SP_DEVICE_INTERFACE_DATA *ifd, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail, + DWORD size, + DWORD *required_size, + SP_DEVINFO_DATA *info_data); + +static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO dev_info); + +static const struct hook_symbol p3io_setupapi_syms[] = { + { + .name = "SetupDiGetClassDevsW", + .patch = my_SetupDiGetClassDevsW, + .link = (void **) &next_SetupDiGetClassDevsW, + }, { + .name = "SetupDiGetClassDevsA", + .patch = my_SetupDiGetClassDevsA, + .link = (void **) &next_SetupDiGetClassDevsA, + }, { +#if 0 + .name = "SetupDiEnumDeviceInfo", + .patch = my_SetupDiEnumDeviceInfo, + .link = (void **) &next_SetupDiEnumDeviceInfo, + }, { +#endif + .name = "SetupDiEnumDeviceInterfaces", + .patch = my_SetupDiEnumDeviceInterfaces, + .link = (void **) &next_SetupDiEnumDeviceInterfaces, + }, { + .name = "SetupDiGetDeviceInterfaceDetailW", + .patch = my_SetupDiGetDeviceInterfaceDetailW, + .link = (void **) &next_SetupDiGetDeviceInterfaceDetailW, + }, { + .name = "SetupDiDestroyDeviceInfoList", + .patch = my_SetupDiDestroyDeviceInfoList, + .link = (void **) &next_SetupDiDestroyDeviceInfoList, + }, +}; + +/* p3iolib appends \p3io to whatever path is returned by SETUPAPI */ + +static const wchar_t p3io_path_prefix[] = L"$p3io"; +static const wchar_t p3io_path[] = L"$p3io\\p3io"; + +static HDEVINFO p3io_hdevinfo; + +static HDEVINFO WINAPI my_SetupDiGetClassDevsA( + const GUID *class_guid, + const char *enumerator, + HWND hwnd, + DWORD flags) +{ + HDEVINFO result; + + result = next_SetupDiGetClassDevsA(class_guid, enumerator, hwnd, flags); + + if (result != INVALID_HANDLE_VALUE && IsEqualGUID(class_guid, &p3io_guid)) { + p3io_hdevinfo = result; + } + + return result; +} + +static HDEVINFO WINAPI my_SetupDiGetClassDevsW( + const GUID *class_guid, + const wchar_t *enumerator, + HWND hwnd, + DWORD flags) +{ + HDEVINFO result; + + result = next_SetupDiGetClassDevsW(class_guid, enumerator, hwnd, flags); + + if (result != INVALID_HANDLE_VALUE && IsEqualGUID(class_guid, &p3io_guid)) { + p3io_hdevinfo = result; + } + + return result; +} + +static BOOL WINAPI my_SetupDiEnumDeviceInterfaces( + HDEVINFO dev_info, + SP_DEVINFO_DATA *info_data, + const GUID *iface_guid, + DWORD index, + SP_DEVICE_INTERFACE_DATA *ifd) +{ + if (dev_info != p3io_hdevinfo) { + return next_SetupDiEnumDeviceInterfaces( + dev_info, + info_data, + iface_guid, + index, + ifd); + } + + /* Not implemented */ + + log_assert(info_data == NULL); + + /* Class GUID is not the same thing as interface GUID but whatever. p3iolib + treats them as the same thing. */ + + if (index > 0) { + SetLastError(ERROR_NO_MORE_ITEMS); + + return FALSE; + } + + if (ifd == NULL || ifd->cbSize != sizeof(*ifd)) { + SetLastError(ERROR_INVALID_USER_BUFFER); + + return FALSE; + } + + memcpy(&ifd->InterfaceClassGuid, &p3io_guid, sizeof(GUID)); + ifd->Flags = SPINT_ACTIVE | SPINT_DEFAULT; + ifd->Reserved = 0; + + SetLastError(ERROR_SUCCESS); + + return TRUE; +} + +static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailW( + HDEVINFO dev_info, + SP_DEVICE_INTERFACE_DATA *ifd, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail, + DWORD size, + DWORD *required_size, + SP_DEVINFO_DATA *info_data) +{ + if (dev_info != p3io_hdevinfo) { + return next_SetupDiGetDeviceInterfaceDetailW( + dev_info, + ifd, + detail, + size, + required_size, + info_data); + } + + /* Not implemented */ + + log_assert(info_data == NULL); + + if (required_size != NULL) { + *required_size = sizeof(*detail) + sizeof(p3io_path_prefix); + } + + if (detail != NULL) { + if (size < sizeof(*detail) + sizeof(p3io_path_prefix)) { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + + return FALSE; + } + + if (detail->cbSize != sizeof(*detail)) { + SetLastError(ERROR_INVALID_USER_BUFFER); + + return FALSE; + } + + memcpy( detail->DevicePath, + p3io_path_prefix, + sizeof(p3io_path_prefix)); + } + + SetLastError(ERROR_SUCCESS); + + return TRUE; +} + +static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO dev_info) +{ + if (dev_info == p3io_hdevinfo) { + p3io_hdevinfo = NULL; + } + + return next_SetupDiDestroyDeviceInfoList(dev_info); +} + +void p3io_setupapi_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "setupapi.dll", + p3io_setupapi_syms, + lengthof(p3io_setupapi_syms)); + + log_info("Inserted P3IO setupapi hooks into %p", target); +} + +bool p3io_setupapi_match_path(const wchar_t *path) +{ + return wstr_eq(path, p3io_path); +} diff --git a/src/main/p3ioemu/devmgr.h b/src/main/p3ioemu/devmgr.h new file mode 100644 index 0000000..00a3d6f --- /dev/null +++ b/src/main/p3ioemu/devmgr.h @@ -0,0 +1,14 @@ +#ifndef P3IO_DEVMGR_H +#define P3IO_DEVMGR_H + +/* Not called setupapi.h so that we don't collide with the real setupapi.h */ + +#include + +#include +#include + +void p3io_setupapi_insert_hooks(HMODULE target); +bool p3io_setupapi_match_path(const wchar_t *path); + +#endif diff --git a/src/main/p3ioemu/emu.c b/src/main/p3ioemu/emu.c new file mode 100644 index 0000000..0a3283d --- /dev/null +++ b/src/main/p3ioemu/emu.c @@ -0,0 +1,554 @@ +#define LOG_MODULE "p3ioemu" + +#include +#include +#include +#include +#include +#include + +#include "hook/iohook.h" + +#include "p3io/cmd.h" +#include "p3io/frame.h" +#include "p3io/ioctl.h" + +#include "p3ioemu/devmgr.h" +#include "p3ioemu/emu.h" +#include "p3ioemu/uart.h" + +#include "util/iobuf.h" +#include "util/log.h" + +static HANDLE p3io_emu_fd; +static uint8_t p3io_emu_resp_bytes[256]; +static struct iobuf p3io_emu_resp; +static const struct p3io_ops *p3io_ops; +static void *p3io_ops_ctx; + +static HRESULT p3io_emu_handle_open(struct irp *irp); +static HRESULT p3io_emu_handle_close(struct irp *irp); +static HRESULT p3io_emu_handle_ioctl(struct irp *irp); +static HRESULT p3io_emu_handle_read(struct irp *irp); +static HRESULT p3io_emu_handle_write(struct irp *irp); + +static HRESULT p3io_cmd_dispatch(const union p3io_req_any *req); + +static void p3io_cmd_get_version( + const struct p3io_hdr *req, + struct p3io_resp_version *resp); + +static void p3io_cmd_set_watchdog( + const struct p3io_req_u8 *req, + struct p3io_resp_u8 *resp); + +static void p3io_cmd_set_outputs( + const struct p3io_req_set_outputs *req, + struct p3io_resp_u8 *resp); + +static void p3io_cmd_read_plug( + const struct p3io_req_read_plug *req, + struct p3io_resp_read_plug *resp); + +static void p3io_cmd_get_cab_type_or_dipsw( + const struct p3io_req_get_cab_type_or_dipsw *req, + struct p3io_resp_get_cab_type_or_dipsw *resp); + +static void p3io_cmd_get_video_freq( + const struct p3io_req_u8 *req, + struct p3io_resp_u8 *resp); + +static void p3io_cmd_set_mode( + const struct p3io_req_u8 *req, + struct p3io_resp_u8 *resp); + +static void p3io_cmd_get_coinstock( + const struct p3io_req_u8 *req, + struct p3io_resp_coin_stock *resp); + +static void p3io_cmd_set_coin_counter( + const struct p3io_req_set_coin_counter *req, + struct p3io_resp_u8 *resp); + +static void p3io_cmd_unknown( + const union p3io_req_any *req, + struct p3io_resp_u8 *resp); + +void p3io_emu_init(const struct p3io_ops *ops, void *ctx) +{ + log_assert(p3io_emu_fd == NULL); + log_assert(ops != NULL); + + p3io_emu_resp.bytes = p3io_emu_resp_bytes; + p3io_emu_resp.nbytes = sizeof(p3io_emu_resp_bytes); + p3io_emu_resp.pos = 0; + + p3io_ops = ops; + p3io_ops_ctx = ctx; + + p3io_emu_fd = iohook_open_dummy_fd(); +} + +void p3io_emu_fini(void) +{ + if (p3io_emu_fd == NULL) { + return; + } + + CloseHandle(p3io_emu_fd); + p3io_emu_fd = NULL; + + if (p3io_ops->close != NULL) { + p3io_ops->close(p3io_ops_ctx); + } +} + +HRESULT p3io_emu_dispatch_irp(struct irp *irp) +{ + log_assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != p3io_emu_fd) { + return irp_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return p3io_emu_handle_open(irp); + case IRP_OP_CLOSE: return p3io_emu_handle_close(irp); + case IRP_OP_READ: return p3io_emu_handle_read(irp); + case IRP_OP_WRITE: return p3io_emu_handle_write(irp); + case IRP_OP_IOCTL: return p3io_emu_handle_ioctl(irp); + default: return E_NOTIMPL; + } +} + +static HRESULT p3io_emu_handle_open(struct irp *irp) +{ + if (!p3io_setupapi_match_path(irp->open_filename)) { + return irp_invoke_next(irp); + } + + log_info("P3IO device opened"); + irp->fd = p3io_emu_fd; + + return S_OK; +} + +static HRESULT p3io_emu_handle_close(struct irp *irp) +{ + log_info("P3IO device closed"); + + return S_OK; +} + +static HRESULT p3io_emu_handle_ioctl(struct irp *irp) +{ + uint32_t *dest; + uint32_t pad; + HRESULT hr; + + if (irp->ioctl != P3IO_IOCTL_READ_JAMMA) { + log_warning("Unknown ioctl: %x", irp->ioctl); + + return E_NOTIMPL; + } + + if (irp->read.nbytes < sizeof(uint32_t)) { + log_warning("Insufficient ioctl response buffer space"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + pad = 0; + + if (p3io_ops->read_jamma != NULL) { + hr = p3io_ops->read_jamma(p3io_ops_ctx, &pad); + + if (FAILED(hr)) { + return hr; + } + } + + dest = (uint32_t *) irp->read.bytes; + *dest = ~_byteswap_ulong(pad); + irp->read.pos = sizeof(pad); + + return S_OK; +} + +static HRESULT p3io_emu_handle_read(struct irp *irp) +{ + struct const_iobuf tmp; + + iobuf_flip(&tmp, &p3io_emu_resp); + p3io_emu_resp.pos = 0; + + return iobuf_move(&irp->read, &tmp); +} + +static HRESULT p3io_emu_handle_write(struct irp *irp) +{ + union p3io_req_any req; + struct iobuf deframe; + HRESULT hr; + + memset(&req, 0, sizeof(req)); + + deframe.bytes = req.raw; + deframe.nbytes = sizeof(req.raw); + deframe.pos = 0; + + hr = p3io_frame_decode(&deframe, &irp->write); + + if (FAILED(hr)) { + return hr; + } + + if (deframe.pos < sizeof(req.hdr)) { + log_warning("Received truncated P3IO frame"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + p3io_emu_resp.pos = 0; + + return p3io_cmd_dispatch(&req); +} + +static HRESULT p3io_cmd_dispatch(const union p3io_req_any *req) +{ + union p3io_resp_any resp; + uint8_t cmd; + + cmd = p3io_req_cmd(req); + + switch (cmd) { + case P3IO_CMD_GET_VERSION: + p3io_cmd_get_version(&req->hdr, &resp.version); + + break; + + case P3IO_CMD_SET_WATCHDOG: + p3io_cmd_set_watchdog(&req->u8, &resp.u8); + + break; + + case P3IO_CMD_SET_OUTPUTS: + p3io_cmd_set_outputs(&req->set_outputs, &resp.u8); + + break; + + case P3IO_CMD_READ_PLUG: + p3io_cmd_read_plug(&req->read_plug, &resp.read_plug); + + break; + + case P3IO_CMD_GET_CAB_TYPE_OR_DIPSW: + p3io_cmd_get_cab_type_or_dipsw(&req->cab_type_or_dipsw, + &resp.cab_type_or_dipsw); + + break; + + case P3IO_CMD_GET_VIDEO_FREQ: + p3io_cmd_get_video_freq(&req->u8, &resp.u8); + + break; + + case P3IO_CMD_SET_MODE: + p3io_cmd_set_mode(&req->u8, &resp.u8); + + break; + + case P3IO_CMD_GET_COINSTOCK: + p3io_cmd_get_coinstock(&req->u8, &resp.coin_stock); + + break; + + case P3IO_CMD_SET_COINCOUNTER: + p3io_cmd_set_coin_counter(&req->set_coin_counter, &resp.u8); + + break; + + case P3IO_CMD_RS232_OPEN_CLOSE: + p3io_uart_cmd_open_close(&req->rs232_open_close, &resp.u8); + + break; + + case P3IO_CMD_RS232_READ: + p3io_uart_cmd_read(&req->rs232_read, &resp.rs232_read); + + break; + + case P3IO_CMD_RS232_WRITE: + p3io_uart_cmd_write(&req->rs232_write, &resp.rs232_write); + + break; + + default: + p3io_cmd_unknown(req, &resp.u8); + + break; + } + + p3io_frame_encode(&p3io_emu_resp, &resp, resp.hdr.nbytes + 1); + + return S_OK; +} + +static void p3io_cmd_get_version( + const struct p3io_hdr *req, + struct p3io_resp_version *resp) +{ + log_misc("%s", __func__); + + p3io_resp_init(&resp->hdr, sizeof(resp), req); + resp->status = 0; + resp->str[0] = 'H'; + resp->str[1] = 'D'; + resp->str[2] = 'X'; + resp->str[3] = '\0'; + resp->major = 1; + resp->minor = 2; + resp->patch = 3; +} + +static void p3io_cmd_set_watchdog( + const struct p3io_req_u8 *req, + struct p3io_resp_u8 *resp) +{ + log_misc("%s(%02x)", __func__, req->u8); + + p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + resp->status = 0; + resp->u8 = 0; +} + +static void p3io_cmd_set_outputs( + const struct p3io_req_set_outputs *req, + struct p3io_resp_u8 *resp) +{ + uint32_t outputs; + HRESULT hr; + + if (p3io_ops->set_outputs != NULL) { + outputs = _byteswap_ulong(req->outputs); + hr = p3io_ops->set_outputs(p3io_ops_ctx, outputs); + } else { + hr = S_OK; + } + + p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + resp->status = FAILED(hr); + resp->u8 = 0; +} + +static void p3io_cmd_read_plug( + const struct p3io_req_read_plug *req, + struct p3io_resp_read_plug *resp) +{ + HRESULT hr; + + log_misc("%s(%02x)", __func__, req->flags); + + p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + resp->present = 1; + + /* + * Some notes about the flags available here: + * 0x10: black plug rom read + * 0x12: black plug eeprom read + * 0x00: white plug rom read + * 0x02: white plug eeprom read + * + * The structure of the buffer is the same for all flags set and the game + * just grabs whatever it currently needs from the buffer. i.e. if we + * send everything on every read (rom + eeprom) always, the game doesn't + * care + */ + + if (req->flags & 0x10) { + if (p3io_ops->get_roundplug) { + hr = p3io_ops->get_roundplug(p3io_ops_ctx, 0, resp->rom, + resp->eeprom); + } else { + log_warning("Reading black roundplug but no function available"); + memset(resp->rom, 0, 8); + memset(resp->eeprom, 0, 32); + hr = S_OK; + } + } else { + if (p3io_ops->get_roundplug) { + hr = p3io_ops->get_roundplug(p3io_ops_ctx, 1, resp->rom, + resp->eeprom); + } else { + log_warning("Reading white roundplug but no function available"); + memset(resp->rom, 0, 8); + memset(resp->eeprom, 0, 32); + hr = S_OK; + } + } + + log_misc("Reading %s roundplug, success 0x%lX, rom %02X%02X%02X%02X%02X%02X" + "%02X%02X, eeprom sig %X%X%X%X%X%X", + req->flags & 0x10 ? "black" : "white", hr, resp->rom[0], resp->rom[1], + resp->rom[2], resp->rom[3], resp->rom[4], resp->rom[5], resp->rom[6], + resp->rom[7], resp->eeprom[0], resp->eeprom[1], resp->eeprom[2], + resp->eeprom[3], resp->eeprom[4], resp->eeprom[5]); + + resp->status = FAILED(hr); +} + +static void p3io_cmd_get_cab_type_or_dipsw( + const struct p3io_req_get_cab_type_or_dipsw *req, + struct p3io_resp_get_cab_type_or_dipsw *resp) +{ + HRESULT hr; + uint8_t dipsw; + enum p3io_cab_type type; + + p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + + hr = S_OK; + + if (req->cab_type_or_dipsw == 0) { + if (p3io_ops->get_cab_type) { + hr = p3io_ops->get_cab_type(p3io_ops_ctx, &type); + + if (hr == S_OK) { + switch (type) { + case P3IO_CAB_TYPE_SD: + log_misc("%s: Returning cab type SD", __func__); + resp->status = 1; + break; + + case P3IO_CAB_TYPE_HD: + log_misc("%s: Returning cab type HD", __func__); + resp->status = 2; + break; + + default: + log_assert(false); + break; + } + } else { + resp->status = 0; + } + } else { + resp->status = 0; + } + } else if (req->cab_type_or_dipsw == 1) { + if (p3io_ops->get_dipsw) { + hr = p3io_ops->get_dipsw(p3io_ops_ctx, &dipsw); + + if (hr == S_OK) { + resp->status = dipsw; + } else { + resp->status = 0; + } + } else { + resp->status = 0; + } + } else { + log_warning("Unknown value for cab type or dipsw: %d", + req->cab_type_or_dipsw); + resp->status = 0; + } +} + +static void p3io_cmd_get_video_freq( + const struct p3io_req_u8 *req, + struct p3io_resp_u8 *resp) +{ + HRESULT hr; + enum p3io_video_freq freq; + + p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + + if (p3io_ops->get_video_freq != NULL) { + hr = p3io_ops->get_video_freq(p3io_ops_ctx, &freq); + } else { + hr = S_OK; + freq = P3IO_VIDEO_FREQ_31KHZ; + } + + if (hr == S_OK) { + switch (freq) { + case P3IO_VIDEO_FREQ_15KHZ: + log_misc("%s: Returning 15 kHz", __func__); + resp->status = 0; + resp->u8 = 0x00; + + break; + + case P3IO_VIDEO_FREQ_31KHZ: + log_misc("%s: Returning 31 kHz", __func__); + resp->status = 0; + resp->u8 = 0x80; + + break; + + default: + log_assert(false); + break; + } + } else { + log_misc("%s: Returning error! (hr=%x)", __func__, (int) hr); + resp->status = 1; + resp->u8 = 0x00; + } +} + +static void p3io_cmd_set_mode( + const struct p3io_req_u8 *req, + struct p3io_resp_u8 *resp) +{ + log_misc("%s(%02x)", __func__, req->u8); + + p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + resp->status = 0; + resp->u8 = 0; +} + +static void p3io_cmd_get_coinstock( + const struct p3io_req_u8 *req, + struct p3io_resp_coin_stock *resp) +{ + uint16_t slots[2]; + HRESULT hr; + + memset(slots, 0, sizeof(slots)); + + if (p3io_ops->get_coinstock != NULL) { + hr = p3io_ops->get_coinstock(p3io_ops_ctx, slots, lengthof(slots)); + } else { + hr = S_OK; + } + + p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + resp->status = 0; + resp->error = FAILED(hr); + resp->slots[0] = _byteswap_ushort(slots[0]); + resp->slots[1] = _byteswap_ushort(slots[1]); +} + +static void p3io_cmd_set_coin_counter( + const struct p3io_req_set_coin_counter *req, + struct p3io_resp_u8 *resp) +{ + log_misc("%s(%02x %02x)", __func__, req->coin_counter[0], + req->coin_counter[1]); + + p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + resp->status = 0; + resp->u8 = 0; +} + +static void p3io_cmd_unknown( + const union p3io_req_any *req, + struct p3io_resp_u8 *resp) +{ + log_warning("Unsupported P3IO command: %02x", p3io_req_cmd(req)); + + p3io_resp_init(&resp->hdr, sizeof(*resp), &req->hdr); + resp->status = 1; + resp->u8 = 0; +} diff --git a/src/main/p3ioemu/emu.h b/src/main/p3ioemu/emu.h new file mode 100644 index 0000000..a3183cd --- /dev/null +++ b/src/main/p3ioemu/emu.h @@ -0,0 +1,66 @@ +#ifndef P3IO_EMU_H +#define P3IO_EMU_H + +#include + +#include +#include + +#include "hook/iohook.h" + +/** + * Enum for available video frequencies to select from. Depending on the + * game, this feature is used or unused. + */ +enum p3io_video_freq { + P3IO_VIDEO_FREQ_15KHZ = 0, + P3IO_VIDEO_FREQ_31KHZ = 1, +}; + +/** + * Enum for available cabinet/display types to select from. Depending on the + * game, this feature is used or unused. + */ +enum p3io_cab_type { + P3IO_CAB_TYPE_SD = 0, + P3IO_CAB_TYPE_HD = 1, +}; + +/** + * P3IO operation dispatching table. Many operations are optional and not used + * by every game that's running on that type of hardware which means you can + * ommit hooking up implementations for them. + */ +struct p3io_ops { + void (*close)(void *ctx); + HRESULT (*read_jamma)(void *ctx, uint32_t *state); + HRESULT (*set_outputs)(void *ctx, uint32_t state); + HRESULT (*get_dipsw)(void *ctx, uint8_t *state); + HRESULT (*get_cab_type)(void *ctx, enum p3io_cab_type *type); + HRESULT (*get_video_freq)(void *ctx, enum p3io_video_freq *freq); + HRESULT (*get_coinstock)(void *ctx, uint16_t *slots, size_t nslots); + HRESULT (*get_roundplug)(void *ctx, uint8_t plug_id, uint8_t* rom, + uint8_t* eeprom); +}; + +/** + * Initialize the p3io emulation backend. + * + * @param ops Dispatch table with operation hooks for your target game. + * @param ctx A context which is passed along with every call to a function of + * your provided dispatch table. + */ +void p3io_emu_init(const struct p3io_ops *ops, void *ctx); + +/** + * Shutdown the p3io emulation backend. + */ +void p3io_emu_fini(void); + +/** + * p3io emulation dispatch irp call. This needs to be hooked to the iohook + * module in order to drive the emulation backend with detoured system calls. + */ +HRESULT p3io_emu_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/p3ioemu/uart.c b/src/main/p3ioemu/uart.c new file mode 100644 index 0000000..ccaffda --- /dev/null +++ b/src/main/p3ioemu/uart.c @@ -0,0 +1,445 @@ +#define LOG_MODULE "p3ioemu-uart" + +#include + +#include +#include +#include + +#include + +#include "hook/iohook.h" + +#include "p3io/cmd.h" + +#include "p3ioemu/uart.h" + +#include "util/iobuf.h" +#include "util/log.h" + +static HRESULT p3io_uart_open( + const wchar_t *path, + uint32_t baud_rate, + HANDLE *fd); +static HRESULT p3io_uart_close(HANDLE fd); +static HRESULT p3io_uart_read(HANDLE fd, struct iobuf *iobuf); +static HRESULT p3io_uart_write(HANDLE fd, struct const_iobuf *iobuf); + +static const wchar_t *p3io_uart_paths[2]; +static HANDLE p3io_uart_fds[2]; + +static const uint32_t p3io_uart_baud_codes[] = { + 0, 0, 19200, 38400, 57600 +}; + +void p3io_uart_set_path(size_t uart_no, const wchar_t *path) +{ + log_assert(uart_no <= 1); + + p3io_uart_paths[uart_no] = path; +} + +void p3io_uart_cmd_open_close( + const struct p3io_req_rs232_open_close *req, + struct p3io_resp_u8 *resp) +{ + const wchar_t *path; + uint32_t baud_rate; + HRESULT hr; + + log_assert(req != NULL); + log_assert(resp != NULL); + + if (req->port_no > 1) { + log_warning("Invalid UART number %i", req->port_no); + hr = E_INVALIDARG; + + goto end; + } + + switch (req->subcmd) { + case P3IO_RS232_CMD_OPEN: + log_info("Opening remote RS232 port #%d", req->port_no); + + if (req->baud_code < lengthof(p3io_uart_baud_codes)) { + baud_rate = p3io_uart_baud_codes[req->baud_code]; + } else { + baud_rate = 0; + } + + if (baud_rate == 0) { + log_warning("Invalid baud rate code: %02x", req->baud_code); + hr = E_FAIL; + + goto end; + } + + path = p3io_uart_paths[req->port_no]; + + if (path == NULL) { + log_warning("UART #%i: No downstream connection", req->port_no); + hr = E_FAIL; + + goto end; + } + + hr = p3io_uart_open(path, baud_rate, &p3io_uart_fds[req->port_no]); + + if (FAILED(hr)) { + log_warning("p3io_uart_open() failed: %x", (int) hr); + } + + break; + + case P3IO_RS232_CMD_CLOSE: + log_info("Closing remote RS232 port #%d", req->port_no); + + hr = p3io_uart_close(p3io_uart_fds[req->port_no]); + p3io_uart_fds[req->port_no] = NULL; + + break; + + default: + log_warning("Unknown subcommand %02x", req->subcmd); + hr = E_FAIL; + + break; + } + +end: + p3io_resp_init(&resp->hdr, sizeof(resp), &req->hdr); + resp->status = 0; + resp->u8 = FAILED(hr); +} + +void p3io_uart_cmd_read( + const struct p3io_req_rs232_read *req, + struct p3io_resp_rs232_read *resp) +{ + struct iobuf iobuf; + HRESULT hr; + + log_assert(req != NULL); + log_assert(resp != NULL); + + iobuf.bytes = resp->bytes; + iobuf.nbytes = req->nbytes; + iobuf.pos = 0; + + if (req->port_no > 1) { + log_warning("Invalid UART number %i", req->port_no); + hr = E_INVALIDARG; + + goto end; + } + + if (req->nbytes > sizeof(resp->bytes)) { + log_warning("Excessive read %i", req->nbytes); + hr = E_INVALIDARG; + + goto end; + } + + hr = p3io_uart_read(p3io_uart_fds[req->port_no], &iobuf); + +end: + /* Variable-length response, init the header manually */ + + resp->hdr.nbytes = iobuf.pos + 3; + resp->hdr.seq_no = req->hdr.seq_no; + resp->status = FAILED(hr); + resp->nbytes = iobuf.pos; +} + +void p3io_uart_cmd_write( + const struct p3io_req_rs232_write *req, + struct p3io_resp_rs232_write *resp) +{ + struct const_iobuf iobuf; + HRESULT hr; + + iobuf.bytes = req->bytes; + iobuf.nbytes = req->nbytes; + iobuf.pos = 0; + + if (req->port_no > 1) { + log_warning("Invalid UART number %i", req->port_no); + hr = E_INVALIDARG; + + goto end; + } + + hr = p3io_uart_write(p3io_uart_fds[req->port_no], &iobuf); + +end: + p3io_resp_init(&resp->hdr, sizeof(resp), &req->hdr); + resp->status = FAILED(hr); + resp->nbytes = iobuf.pos; +} + +static HRESULT p3io_uart_open( + const wchar_t *path, + uint32_t baud_rate, + HANDLE *fd) +{ + struct irp irp; + uint32_t comm_mask; + uint32_t flags; + SERIAL_QUEUE_SIZE qs; + SERIAL_TIMEOUTS timeouts; + SERIAL_LINE_CONTROL lc; + SERIAL_BAUD_RATE baud; + SERIAL_HANDFLOW handflow; + HRESULT hr; + + if (*fd != NULL) { + log_warning("Port is already open"); + + return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_OPEN; + irp.open_filename = path; + irp.open_access = GENERIC_READ | GENERIC_WRITE; + irp.open_share = 0; + irp.open_sa = NULL; + irp.open_creation = OPEN_EXISTING; + irp.open_flags = 0; + irp.open_tmpl = NULL; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + goto fail; + } + + *fd = irp.fd; + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = *fd; + irp.ioctl = IOCTL_SERIAL_SET_WAIT_MASK; + irp.write.bytes = (const void *) &comm_mask; + irp.write.nbytes = sizeof(comm_mask); + comm_mask = EV_RXCHAR; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + goto fail; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = *fd; + irp.ioctl = IOCTL_SERIAL_SET_QUEUE_SIZE; + irp.write.bytes = (const void *) &qs; + irp.write.nbytes = sizeof(qs); + qs.InSize = 0x4000; + qs.OutSize = 0x4000; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + goto fail; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = *fd; + irp.ioctl = IOCTL_SERIAL_PURGE; + irp.write.bytes = (const void *) &flags; + irp.write.nbytes = sizeof(flags); + flags = PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + goto fail; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = *fd; + irp.ioctl = IOCTL_SERIAL_SET_TIMEOUTS; + irp.write.bytes = (const void *) &timeouts; + irp.write.nbytes = sizeof(timeouts); + timeouts.ReadIntervalTimeout = -1; + timeouts.ReadTotalTimeoutMultiplier = 10; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 100; + timeouts.WriteTotalTimeoutConstant = 0; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + goto fail; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = *fd; + irp.ioctl = IOCTL_SERIAL_SET_LINE_CONTROL; + irp.write.bytes = (const void *) &lc; + irp.write.nbytes = sizeof(lc); + lc.WordLength = 8; + lc.Parity = NO_PARITY; + lc.StopBits = STOP_BIT_1; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + goto fail; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = *fd; + irp.ioctl = IOCTL_SERIAL_SET_BAUD_RATE; + irp.write.bytes = (const void *) &baud; + irp.write.nbytes = sizeof(baud); + baud.BaudRate = baud_rate; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + goto fail; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = *fd; + irp.ioctl = IOCTL_SERIAL_SET_HANDFLOW; + irp.write.bytes = (const void *) &handflow; + irp.write.nbytes = sizeof(handflow); + handflow.ControlHandShake = 0; + handflow.FlowReplace = 0; + handflow.XonLimit = 0; + handflow.XoffLimit = 0; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + goto fail; + } + + return S_OK; + +fail: + if (*fd != NULL) { + irp.op = IRP_OP_CLOSE; + irp.fd = *fd; + + irp_invoke_next(&irp); + + *fd = NULL; + } + + return hr; +} + +static HRESULT p3io_uart_close(HANDLE fd) +{ + struct irp irp; + HRESULT hr; + + if (fd != NULL) { + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_CLOSE; + irp.fd = fd; + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("Error closing port: %x", (int) hr); + } + } else { + log_warning("Port is already closed"); + hr = S_OK; + } + + return hr; +} + +static HRESULT p3io_uart_read(HANDLE fd, struct iobuf *iobuf) +{ + struct irp irp; + SERIAL_STATUS status; + HRESULT hr; + + if (fd == NULL) { + log_warning("Read from unopened port"); + + return E_FAIL; + } + + /* Peek RX buffer */ + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl = IOCTL_SERIAL_GET_COMMSTATUS; + irp.read.bytes = (uint8_t *) &status; + irp.read.nbytes = sizeof(status); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("UART FIFO peek failed: %x", (int) hr); + + return hr; + } + + /* Return immediately if no data available */ + + if (status.AmountInInQueue == 0) { + return S_OK; + } + + /* Issue read */ + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_READ; + irp.fd = fd; + memcpy(&irp.read, iobuf, sizeof(*iobuf)); + + hr = irp_invoke_next(&irp); + + if (FAILED(hr)) { + log_warning("Read error: %x", (int) hr); + + return hr; + } + + memcpy(iobuf, &irp.read, sizeof(*iobuf)); + + return S_OK; +} + +static HRESULT p3io_uart_write(HANDLE fd, struct const_iobuf *iobuf) +{ + struct irp irp; + HRESULT hr; + + if (fd == NULL) { + log_warning("Write to unopened port"); + + return E_FAIL; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_WRITE; + irp.fd = fd; + memcpy(&irp.write, iobuf, sizeof(*iobuf)); + + hr = irp_invoke_next(&irp); + + if (SUCCEEDED(hr)) { + memcpy(iobuf, &irp.write, sizeof(*iobuf)); + } else { + log_warning("Write error: %x", (int) hr); + } + + return hr; +} diff --git a/src/main/p3ioemu/uart.h b/src/main/p3ioemu/uart.h new file mode 100644 index 0000000..a388ba4 --- /dev/null +++ b/src/main/p3ioemu/uart.h @@ -0,0 +1,20 @@ +#ifndef P3IO_UART_H +#define P3IO_UART_H + +#include "p3io/cmd.h" + +void p3io_uart_set_path(size_t uart_no, const wchar_t *path); + +void p3io_uart_cmd_open_close( + const struct p3io_req_rs232_open_close *req, + struct p3io_resp_u8 *resp); + +void p3io_uart_cmd_read( + const struct p3io_req_rs232_read *req, + struct p3io_resp_rs232_read *resp); + +void p3io_uart_cmd_write( + const struct p3io_req_rs232_write *req, + struct p3io_resp_rs232_write *resp); + +#endif diff --git a/src/main/p4ioemu/Module.mk b/src/main/p4ioemu/Module.mk new file mode 100644 index 0000000..bfee093 --- /dev/null +++ b/src/main/p4ioemu/Module.mk @@ -0,0 +1,9 @@ +libs += p4ioemu + +libs_p4ioemu := \ + util \ + +src_p4ioemu := \ + device.c \ + setupapi.c + diff --git a/src/main/p4ioemu/device.c b/src/main/p4ioemu/device.c new file mode 100644 index 0000000..d5912b8 --- /dev/null +++ b/src/main/p4ioemu/device.c @@ -0,0 +1,457 @@ +#define LOG_MODULE "p4ioemu-device" + +#include "p4ioemu/device.h" + +#include +#include +#include + +#include "hook/iohook.h" +#include "util/hex.h" +#include "util/log.h" +#include "util/str.h" + +//#define P4IOEMU_DEBUG_DUMP + +/* can't seem to #include the requisite DDK headers from usermode code, + so we have to redefine these macros here */ + +#define CTL_CODE( DeviceType, Function, Method, Access ) ( \ + ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ +) + +#define METHOD_BUFFERED 0 + +#define FILE_ANY_ACCESS 0x00 + +#define FILE_DEVICE_UNKNOWN 0x22 + +#define P4IO_FUNCTION_READ_JAMMA_2 0x801 +#define P4IO_FUNCTION_GET_DEVICE_NAME 0x803 + +#define IOCTL_P4IO_GET_DEVICE_NAME CTL_CODE(FILE_DEVICE_UNKNOWN, P4IO_FUNCTION_GET_DEVICE_NAME, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_P4IO_READ_JAMMA_2 CTL_CODE(FILE_DEVICE_UNKNOWN, P4IO_FUNCTION_READ_JAMMA_2, METHOD_BUFFERED, FILE_ANY_ACCESS) + +enum p4ioemu_p4io_command { + P4IOEMU_P4IO_CMD_INIT = 0x00, + P4IOEMU_P4IO_CMD_GET_DEVICE_INFO = 0x01, + P4IOEMU_P4IO_CMD_UNKNOWN = 0x12, + P4IOEMU_P4IO_CMD_RESET_WTD = 0x1C, + P4IOEMU_P4IO_CMD_SCI_MNG_OPEN = 0x20, + P4IOEMU_P4IO_CMD_SCI_UPDATE = 0x21, + /* SCI = serial communication interface */ + P4IOEMU_P4IO_CMD_SCI_MNG_BREAK = 0x24, + /* Read round plug id over one-wire */ + P4IOEMU_P4IO_CMD_DALLAS_READ_ID = 0x40, + /* Read round plug mem over one-wire */ + P4IOEMU_P4IO_CMD_DALLAS_READ_MEM = 0x41 +}; + +struct p4ioemu_p4io_cmd_package { + uint8_t header_AA; + uint8_t cmd; + uint8_t seq_num; + uint8_t payload_len; +}; + +struct p4ioemu_p4io_device_info_resp { + uint32_t type; + uint8_t padding; + uint8_t version_major; + uint8_t version_minor; + uint8_t version_revision; + char product_code[4]; + char build_date[16]; + char build_time[16]; +}; + +struct p4ioemu_p4io_read_roundplug_req { + /* 0 = black, 1 = white */ + uint8_t type; +}; + +static const struct p4ioemu_device_msg_hook* p4ioemu_device_msg_hook; + +static HANDLE p4ioemu_p4io_fd; +static uint8_t p4ioemu_p4io_cmd_read_buffer[4096]; +static uint32_t p4ioemu_p4io_cmd_buffer_resp_len; +static uint8_t p4ioemu_p4io_last_cmd; +static uint8_t p4ioemu_p4io_last_seq_num; + +static uint32_t p4ioemu_p4io_command_handle(uint8_t cmd, const void* payload, + uint32_t payload_len, void* resp, uint32_t resp_max_len) +{ + switch (cmd) { + case P4IOEMU_P4IO_CMD_INIT: + { + log_misc("P4IOEMU_P4IO_CMD_INIT"); + + /* no data to send to host */ + memset(resp, 0, resp_max_len); + /* driver expects this buffer size, fails otherwise */ + return 0; + } + + case P4IOEMU_P4IO_CMD_GET_DEVICE_INFO: + { + log_misc("P4IOEMU_P4IO_CMD_GET_DEVICE_INFO"); + + struct p4ioemu_p4io_device_info_resp* info = + (struct p4ioemu_p4io_device_info_resp*) resp; + + info->type = 0x37133713; + info->version_major = 5; + info->version_minor = 7; + info->version_revision = 3; + memcpy(info->product_code, "P4IO", 4); + memcpy(info->build_date, "build_date", 11); + memcpy(info->build_time, "build_time", 11); + + return sizeof(struct p4ioemu_p4io_device_info_resp); + } + + case P4IOEMU_P4IO_CMD_DALLAS_READ_ID: + { + const struct p4ioemu_p4io_read_roundplug_req* req = + (const struct p4ioemu_p4io_read_roundplug_req*) payload; + + log_misc("P4IOEMU_P4IO_CMD_DALLAS_READ_ID: %d", req->type); + + if (p4ioemu_device_msg_hook->roundplug_read_id) { + p4ioemu_device_msg_hook->roundplug_read_id(req->type, resp, 8); + } else { + memset(resp, 0, 8); + } + + return 8; + } + + case P4IOEMU_P4IO_CMD_DALLAS_READ_MEM: + { + const struct p4ioemu_p4io_read_roundplug_req* req = + (const struct p4ioemu_p4io_read_roundplug_req*) payload; + + log_misc("P4IOEMU_P4IO_CMD_DALLAS_READ_MEM: %d", req->type); + + if (p4ioemu_device_msg_hook->roundplug_read_mem) { + p4ioemu_device_msg_hook->roundplug_read_mem(req->type, resp, 32); + } else { + memset(resp, 0, 32); + } + + return 32; + } + + case P4IOEMU_P4IO_CMD_UNKNOWN: + { + log_misc("P4IOEMU_P4IO_CMD_UNKNOWN: %d", payload_len); + + return 0; + } + + case P4IOEMU_P4IO_CMD_SCI_UPDATE: + { + //log_misc("P4IOEMU_P4IO_CMD_SCI_UPDATE"); + + // TODO we need a game which uses it + memset(resp, 0, 61); + + return 0; + } + + case P4IOEMU_P4IO_CMD_RESET_WTD: + { + //log_misc("P4IOEMU_P4IO_CMD_RESET_WTD"); + + return 0; + } + + default: + /* forward to game specific handler */ + return 0xFFFFFFFF; + } +} + +static void p4ioemu_p4io_dump_buffer(const void* buffer, uint32_t len) +{ + char buffer_str[4096]; + hex_encode_uc(buffer, len, buffer_str, sizeof(buffer_str)); + log_warning("Package dump: %s", buffer_str); +} + +static HRESULT p4ioemu_p4io_bulk_read(void* resp, uint32_t nbytes) +{ + struct p4ioemu_p4io_cmd_package* package; + void* payload; + uint32_t max_payload_len; + uint32_t payload_len; + + if (nbytes < sizeof(struct p4ioemu_p4io_cmd_package)) { + log_warning("Buffer for bulk read endpoint to short: %d", nbytes); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + package = (struct p4ioemu_p4io_cmd_package*) resp; + payload = resp + sizeof(struct p4ioemu_p4io_cmd_package); + max_payload_len = nbytes - sizeof(struct p4ioemu_p4io_cmd_package); + + if (max_payload_len < p4ioemu_p4io_cmd_buffer_resp_len) { + log_warning("Too much response data (%d) for buffer (%d), truncated", + p4ioemu_p4io_cmd_buffer_resp_len, max_payload_len); + payload_len = max_payload_len; + } else { + payload_len = p4ioemu_p4io_cmd_buffer_resp_len; + } + + package->header_AA = 0xAA; + package->cmd = p4ioemu_p4io_last_cmd; + package->seq_num = p4ioemu_p4io_last_seq_num; + package->payload_len = payload_len; + + memcpy(payload, p4ioemu_p4io_cmd_read_buffer, payload_len); + + return S_OK; +} + +static HRESULT p4ioemu_p4io_bulk_write(const void* req, uint32_t nbytes) +{ + const struct p4ioemu_p4io_cmd_package* package; + const void* payload; + + if (nbytes < sizeof(struct p4ioemu_p4io_cmd_package)) { + log_warning("Command on bulk write endpoint to short: %d", nbytes); + p4ioemu_p4io_dump_buffer(req, nbytes); + + return E_INVALIDARG; + } + + package = (struct p4ioemu_p4io_cmd_package*) req; + payload = req + sizeof(struct p4ioemu_p4io_cmd_package); + + if (package->header_AA != 0xAA) { + log_warning("Command on bulk endpoint to short: %d", nbytes); + p4ioemu_p4io_dump_buffer(req, nbytes); + + return E_INVALIDARG; + } + + p4ioemu_p4io_last_cmd = package->cmd; + p4ioemu_p4io_last_seq_num = package->seq_num; + + /* handle commands that are common p4io ones first */ + p4ioemu_p4io_cmd_buffer_resp_len = p4ioemu_p4io_command_handle( + package->cmd, payload, package->payload_len, + p4ioemu_p4io_cmd_read_buffer, sizeof(p4ioemu_p4io_cmd_read_buffer)); + + /* forward to game specific handlers */ + if (p4ioemu_p4io_cmd_buffer_resp_len == 0xFFFFFFFF) { + if (p4ioemu_device_msg_hook->command_handle) { + p4ioemu_p4io_cmd_buffer_resp_len = + p4ioemu_device_msg_hook->command_handle(package->cmd, payload, + package->payload_len, p4ioemu_p4io_cmd_read_buffer, + sizeof(p4ioemu_p4io_cmd_read_buffer)); + } + } + + if (p4ioemu_p4io_cmd_buffer_resp_len == 0xFFFFFFFF) { + log_warning("Unhandled cmd 0x%X", package->cmd); + + return E_NOTIMPL; + } + + return S_OK; +} + +/* For debugging */ +#ifdef P4IOEMU_DEBUG_DUMP +static void p4ioemu_device_log_ioctl_msg(const char* prefix, uint32_t ctl_code, + const void* ctl, uint32_t ctl_size, void* header, + uint32_t header_bytes, void* data, uint32_t data_bytes) +{ + char header_str[4096]; + char data_str[4096]; + const char* ctl_code_str; + + switch (ctl_code) { + case IOCTL_P4IO_GET_DEVICE_NAME: + ctl_code_str = "IOCTL_P4IO_GET_DEVICE_NAME"; + break; + + case IOCTL_P4IO_READ_JAMMA_2: + ctl_code_str = "IOCTL_P4IO_READ_JAMMA_2"; + break; + + default: + ctl_code_str = "UNKNOWN"; + break; + } + + hex_encode_uc(header, header_bytes, header_str, sizeof(header_str)); + hex_encode_uc(data, data_bytes, data_str, sizeof(data_str)); + + log_warning("[P4IO IOCTL DUMP %s][%s] ctl_code 0x%X, ctl_size %d, header(%d) %s |||| data(%d) %s", + prefix, ctl_code_str, ctl_code, ctl_size, header_bytes, header_str, data_bytes, data_str); +} + +static void p4ioemu_device_log(const char* prefix, const void* data, + uint32_t data_bytes) +{ + char data_str[4096]; + + hex_encode_uc(data, data_bytes, data_str, sizeof(data_str)); + + log_warning("[P4IO DUMP %s] data(%d) %s", + prefix, data_bytes, data_str); +} +#endif + +static HRESULT p4ioemu_device_open(struct irp *irp) +{ + log_assert(irp != NULL); + + /* FIXME I don't know what kind of path the game expects to be returned + on the setupapi calls, so i assumed it wants \\p4io. However, the + game also adds the node \\p4io to that -> result: \\p4io\\p4io */ + + if (!wstr_eq(irp->open_filename, L"\\p4io\\p4io")) { + return irp_invoke_next(irp); + } + + irp->fd = p4ioemu_p4io_fd; + log_info("P4IO opened"); + + return S_OK; +} + +static HRESULT p4ioemu_device_read(struct irp *irp) +{ + HRESULT hr; + + memset(irp->read.bytes, 0, irp->read.nbytes); + hr = p4ioemu_p4io_bulk_read(irp->read.bytes, irp->read.nbytes); + + if (FAILED(hr)) { + return hr; + } + + /* game expects a read size of 0x40, always */ + irp->read.pos = 0x40; + +#ifdef P4IOEMU_DEBUG_DUMP + p4ioemu_device_log("POST READ", dest->bytes, dest->nbytes); +#endif + + return hr; +} + +static HRESULT p4ioemu_device_write(struct irp *irp) +{ + HRESULT hr; + + /* For debugging */ +#ifdef P4IOEMU_DEBUG_DUMP + p4ioemu_device_log("WRITE", src->bytes, src->nbytes); +#endif + + hr = p4ioemu_p4io_bulk_write(irp->write.bytes, irp->write.nbytes); + + if (FAILED(hr)) { + return hr; + } + + irp->write.pos = irp->write.nbytes; + + return hr; +} + +static HRESULT p4ioemu_device_ioctl(struct irp *irp) +{ + log_assert(irp != NULL); + + /* For debugging */ +#ifdef P4IOEMU_DEBUG_DUMP + p4ioemu_device_log_ioctl_msg("BEFORE", code, in_bytes, in_nbytes, in_bytes, + in_nbytes, out_bytes, out_nbytes); +#endif + + /* Cases are listed in order of first receipt */ + switch (irp->ioctl) { + case IOCTL_P4IO_GET_DEVICE_NAME: + { + const char dev_name[] = "kactools p4ioemu"; + + if (irp->read.nbytes < strlen(dev_name)) { + log_fatal("Device name string does not fit into buffer"); + } + + memset(irp->read.bytes, 0, irp->read.nbytes); + memcpy(irp->read.bytes, dev_name, strlen(dev_name)); + irp->read.pos = strlen(dev_name); + + return S_OK; + } + + case IOCTL_P4IO_READ_JAMMA_2: + { + p4ioemu_device_msg_hook->jamma2_read(irp->read.bytes, irp->read.nbytes); + irp->read.pos = irp->read.nbytes; + + return S_OK; + } + + default: + log_warning("Unknown ioctl %08x", irp->ioctl); + + return E_INVALIDARG; + } + + /* For debugging */ +#ifdef P4IOEMU_DEBUG_DUMP + p4ioemu_device_log_ioctl_msg("AFTER", code, in_bytes, in_nbytes, in_bytes, + in_nbytes, out_bytes, out_nbytes); +#endif +} + +static HRESULT p4ioemu_device_close(struct irp *irp) +{ + log_info("P4IO closed"); + + return S_OK; +} + +void p4ioemu_init(const struct p4ioemu_device_msg_hook* msg_hook) +{ + log_assert(p4ioemu_p4io_fd == NULL); + + p4ioemu_p4io_fd = iohook_open_dummy_fd(); + p4ioemu_device_msg_hook = msg_hook; +} + +void p4ioemu_fini(void) +{ + if (p4ioemu_p4io_fd != NULL) { + CloseHandle(p4ioemu_p4io_fd); + } + + p4ioemu_p4io_fd = NULL; +} + +HRESULT p4ioemu_dispatch_irp(struct irp *irp) +{ + log_assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != p4ioemu_p4io_fd) { + return irp_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return p4ioemu_device_open(irp); + case IRP_OP_CLOSE: return p4ioemu_device_close(irp); + case IRP_OP_READ: return p4ioemu_device_read(irp); + case IRP_OP_WRITE: return p4ioemu_device_write(irp); + case IRP_OP_IOCTL: return p4ioemu_device_ioctl(irp); + default: return E_NOTIMPL; + } +} + diff --git a/src/main/p4ioemu/device.h b/src/main/p4ioemu/device.h new file mode 100644 index 0000000..2e36ead --- /dev/null +++ b/src/main/p4ioemu/device.h @@ -0,0 +1,23 @@ +#ifndef P4IOEMU_DEVICE_H +#define P4IOEMU_DEVICE_H + +#include + +#include +#include + +#include "hook/iohook.h" + +struct p4ioemu_device_msg_hook { + void (*jamma2_read)(void* resp, uint32_t nbytes); + uint32_t (*command_handle)(uint8_t cmd, const void* payload, + uint32_t payload_len, void* resp, uint32_t resp_max_len); + void (*roundplug_read_id)(uint8_t type, void* buffer, uint32_t len); + void (*roundplug_read_mem)(uint8_t type, void* buffer, uint32_t len); +}; + +void p4ioemu_init(const struct p4ioemu_device_msg_hook* msg_hook); +void p4ioemu_fini(void); +HRESULT p4ioemu_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/p4ioemu/setupapi.c b/src/main/p4ioemu/setupapi.c new file mode 100644 index 0000000..3c4d01d --- /dev/null +++ b/src/main/p4ioemu/setupapi.c @@ -0,0 +1,14 @@ +#define LOG_MODULE "p4ioemu-setupapi" + +#include "p4ioemu/setupapi.h" + +const struct hook_setupapi_data p4ioemu_setupapi_data = { + .device_guid = { + 0x8B7250A5, + 0x4F61, + 0x46C9, + { 0x84, 0x3A, 0xE6, 0x68, 0x06, 0x47, 0x6A, 0x20 } + }, + .device_desc = NULL, + .device_path = "\\p4io", +}; \ No newline at end of file diff --git a/src/main/p4ioemu/setupapi.h b/src/main/p4ioemu/setupapi.h new file mode 100644 index 0000000..4e338c1 --- /dev/null +++ b/src/main/p4ioemu/setupapi.h @@ -0,0 +1,8 @@ +#ifndef P4IOEMU_SETUPAPI_H +#define P4IOEMU_SETUPAPI_H + +#include "hooklib/setupapi.h" + +extern const struct hook_setupapi_data p4ioemu_setupapi_data; + +#endif diff --git a/src/main/pcbidgen/Module.mk b/src/main/pcbidgen/Module.mk new file mode 100644 index 0000000..852a4d0 --- /dev/null +++ b/src/main/pcbidgen/Module.mk @@ -0,0 +1,8 @@ +exes += pcbidgen + +libs_pcbidgen := \ + security \ + util \ + +src_pcbidgen := \ + main.c \ diff --git a/src/main/pcbidgen/main.c b/src/main/pcbidgen/main.c new file mode 100644 index 0000000..ba67384 --- /dev/null +++ b/src/main/pcbidgen/main.c @@ -0,0 +1,60 @@ +#include + +#include +#include +#include +#include + +#include "security/id.h" + +#include "util/hex.h" + +static void print_usage(char** argv) +{ + fprintf(stderr, + "Usage: %s ...\n" + " gen: Generate a random pcbid\n" + " make <8 byte hex id>: Create a full pcbid (header + checksum) " + "of the provided id\n", + argv[0]); +} + +int main(int argc, char** argv) +{ + struct security_id id; + char* str; + + if (argc < 2) { + print_usage(argv); + return -1; + } + + if (!strcmp(argv[1], "gen")) { + srand(time(NULL)); + + for (uint8_t i = 0; i < sizeof(id.id); i++) { + id.id[i] = rand(); + } + } else if (!strcmp(argv[1], "make")) { + if (argc < 3) { + print_usage(argv); + return -2; + } + + if (strlen(argv[2]) != 16) { + fprintf(stderr, "Invaild length %d for id, must be 16\n", + strlen(argv[2])); + return -3; + } + + hex_decode(id.id, sizeof(id.id), argv[2], strlen(argv[2])); + } else { + fprintf(stderr, "Invaild command '%s'\n", argv[1]); + return -4; + } + + security_id_prepare(&id); + str = security_id_to_str(&id, false); + printf("%s\n", str); + free(str); +} \ No newline at end of file diff --git a/src/main/sdvxhook/Module.mk b/src/main/sdvxhook/Module.mk new file mode 100644 index 0000000..c1a8c5b --- /dev/null +++ b/src/main/sdvxhook/Module.mk @@ -0,0 +1,20 @@ +avsdlls += sdvxhook + +deplibs_sdvxhook := \ + avs \ + +libs_sdvxhook := \ + acioemu \ + hook \ + hooklib \ + util \ + eamio \ + sdvxio \ + +src_sdvxhook := \ + acio.c \ + dllmain.c \ + gfx.c \ + kfca.c \ + lcd.c \ + diff --git a/src/main/sdvxhook/acio.c b/src/main/sdvxhook/acio.c new file mode 100644 index 0000000..bb9ef1f --- /dev/null +++ b/src/main/sdvxhook/acio.c @@ -0,0 +1,94 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "acioemu/addr.h" +#include "acioemu/emu.h" +#include "acioemu/icca.h" + +#include "hook/iohook.h" + +#include "imports/avs.h" + +#include "sdvxhook/acio.h" +#include "sdvxhook/kfca.h" + +#include "util/defs.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +static struct ac_io_emu ac_io_emu; +static struct ac_io_emu_icca ac_io_emu_icca; + +void ac_io_bus_init(void) +{ + ac_io_emu_init(&ac_io_emu, L"COM2"); + ac_io_emu_icca_init(&ac_io_emu_icca, &ac_io_emu, 0); + kfca_init(&ac_io_emu); +} + +void ac_io_bus_fini(void) +{ + ac_io_emu_fini(&ac_io_emu); +} + +HRESULT ac_io_bus_dispatch_irp(struct irp *irp) +{ + const struct ac_io_message *msg; + HRESULT hr; + + log_assert(irp != NULL); + + if (!ac_io_emu_match_irp(&ac_io_emu, irp)) { + return irp_invoke_next(irp); + } + + for (;;) { + hr = ac_io_emu_dispatch_irp(&ac_io_emu, irp); + + if (hr != S_OK) { + return hr; + } + + msg = ac_io_emu_request_peek(&ac_io_emu); + + switch (msg->addr) { + case 0: + ac_io_emu_cmd_assign_addrs(&ac_io_emu, msg, 2); + + break; + + case 1: + ac_io_emu_icca_dispatch_request(&ac_io_emu_icca, msg); + + break; + + case 2: + kfca_dispatch_request(msg); + + break; + + case AC_IO_BROADCAST: + log_warning("Broadcast(?) message on SDVX ACIO bus?"); + + break; + + default: + log_warning( + "ACIO message on unhandled bus address: %d", + msg->addr); + + break; + } + + ac_io_emu_request_pop(&ac_io_emu); + } +} diff --git a/src/main/sdvxhook/acio.h b/src/main/sdvxhook/acio.h new file mode 100644 index 0000000..d6d6ee2 --- /dev/null +++ b/src/main/sdvxhook/acio.h @@ -0,0 +1,12 @@ +#ifndef IIDXHOOK_AC_IO_H +#define IIDXHOOK_AC_IO_H + +#include + +#include "hook/iohook.h" + +void ac_io_bus_init(void); +void ac_io_bus_fini(void); +HRESULT ac_io_bus_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/sdvxhook/dllmain.c b/src/main/sdvxhook/dllmain.c new file mode 100644 index 0000000..a46d524 --- /dev/null +++ b/src/main/sdvxhook/dllmain.c @@ -0,0 +1,143 @@ +#include + +#include +#include + +#include "bemanitools/eamio.h" +#include "bemanitools/sdvxio.h" + +#include "hook/iohook.h" + +#include "hooklib/app.h" +#include "hooklib/rs232.h" + +#include "sdvxhook/acio.h" +#include "sdvxhook/lcd.h" +#include "sdvxhook/gfx.h" + +#include "util/cmdline.h" +#include "util/defs.h" +#include "util/log.h" + +static bool my_dll_entry_init(char *sidcode, struct property_node *config); +static bool my_dll_entry_main(void); + +static const irp_handler_t sdvxhook_handlers[] = { + ac_io_bus_dispatch_irp, + lcd_dispatch_irp, +}; + +static bool my_dll_entry_init(char *sidcode, struct property_node *config) +{ + bool ok; + + log_info("--- Begin sdvxhook dll_entry_init ---"); + + ac_io_bus_init(); + + log_info("Starting up SDVX IO backend"); + + sdvx_io_set_loggers( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + ok = sdvx_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + + if (!ok) { + goto sdvx_io_fail; + } + + log_info("Starting up card reader backend"); + + eam_io_set_loggers( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + ok = eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + + if (!ok) { + goto eam_io_fail; + } + + log_info("--- End sdvxhook dll_entry_init ---"); + + return app_hook_invoke_init(sidcode, config); + +eam_io_fail: + sdvx_io_fini(); + +sdvx_io_fail: + ac_io_bus_fini(); + + return false; +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + log_info("Shutting down card reader backend"); + eam_io_fini(); + + log_info("Shutting down SDVX IO backend"); + sdvx_io_fini(); + + ac_io_bus_fini(); + + return result; +} + +BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) +{ + int i; + int argc; + char **argv; + + if (reason != DLL_PROCESS_ATTACH) { + return TRUE; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + args_recover(&argc, &argv); + + for (i = 1 ; i < argc ; i++) { + if (argv[i][0] != '-') { + continue; + } + + switch (argv[i][1]) { + case 'c': + gfx_set_confined(); + + break; + + case 'w': + gfx_set_windowed(); + + break; + } + } + + args_free(argc, argv); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + iohook_init(sdvxhook_handlers, lengthof(sdvxhook_handlers)); + rs232_hook_init(); + + gfx_init(); + lcd_init(); + + return TRUE; +} + diff --git a/src/main/sdvxhook/gfx.c b/src/main/sdvxhook/gfx.c new file mode 100644 index 0000000..98fda9c --- /dev/null +++ b/src/main/sdvxhook/gfx.c @@ -0,0 +1,139 @@ +#include +#include + +#include + +#include "hook/com-proxy.h" +#include "hook/pe.h" +#include "hook/table.h" + +#include "sdvxhook/gfx.h" + +#include "util/defs.h" +#include "util/log.h" + +static LRESULT CALLBACK my_WndProc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +static HRESULT STDCALL my_CreateDevice( + IDirect3D9 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, DWORD flags, + D3DPRESENT_PARAMETERS *pp, IDirect3DDevice9 **pdev); +static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver); + +static WNDPROC real_WndProc; +static IDirect3D9 * (STDCALL *real_Direct3DCreate9)(UINT sdk_ver); + +static const struct hook_symbol gfx_hooks[] = { + { + .name = "Direct3DCreate9", + .patch = my_Direct3DCreate9, + .link = (void **) &real_Direct3DCreate9 + }, +}; + +static bool gfx_confined; +static bool gfx_windowed; + +static LRESULT gfx_confine(HWND hwnd) +{ + POINT p; + RECT r; + + log_misc("Confining mouse (ALT-TAB to release)"); + + p.x = 0; + p.y = 0; + + ClientToScreen(hwnd, &p); + + r.left = p.x; + r.top = p.y; + r.right = p.x + 100; + r.bottom = p.y + 100; + + ClipCursor(&r); + + return TRUE; +} + +static LRESULT gfx_unconfine(HWND hwnd) +{ + log_misc("Un-confining mouse"); + + ClipCursor(NULL); + + return TRUE; +} + +static LRESULT CALLBACK my_WndProc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) +{ + switch (msg) { + case WM_SETFOCUS: + return gfx_confine(hwnd); + + case WM_KILLFOCUS: + return gfx_unconfine(hwnd); + + default: + return CallWindowProc(real_WndProc, hwnd, msg, wparam, lparam); + } +} + +static HRESULT STDCALL my_CreateDevice( + IDirect3D9 *self, UINT adapter, D3DDEVTYPE type, HWND hwnd, DWORD flags, + D3DPRESENT_PARAMETERS *pp, IDirect3DDevice9 **pdev) +{ + IDirect3D9 *real = COM_PROXY_UNWRAP(self); + HRESULT hr; + + log_misc("IDirect3D9::CreateDevice hook hit"); + + if (gfx_windowed) { + pp->Windowed = TRUE; + pp->FullScreen_RefreshRateInHz = 0; + } + + hr = IDirect3D9_CreateDevice(real, adapter, type, hwnd, flags, pp, pdev); + + if (SUCCEEDED(hr) && gfx_confined) { + real_WndProc = (void *) GetWindowLongPtr(hwnd, GWLP_WNDPROC); + + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (uintptr_t) my_WndProc); + } + + return hr; +} + +static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver) +{ + IDirect3D9 *api; + IDirect3D9Vtbl *api_vtbl; + struct com_proxy *api_proxy; + + log_info("Direct3DCreate9 hook hit"); + + api = real_Direct3DCreate9(sdk_ver); + api_proxy = com_proxy_wrap(api, sizeof(*api->lpVtbl)); + api_vtbl = api_proxy->vptr; + + api_vtbl->CreateDevice = my_CreateDevice; + + return (IDirect3D9 *) api_proxy; +} + +void gfx_init(void) +{ + hook_table_apply(NULL, "d3d9.dll", gfx_hooks, lengthof(gfx_hooks)); + log_info("Inserted graphics hooks"); +} + +void gfx_set_confined(void) +{ + gfx_confined = true; +} + +void gfx_set_windowed(void) +{ + gfx_windowed = true; +} + diff --git a/src/main/sdvxhook/gfx.h b/src/main/sdvxhook/gfx.h new file mode 100644 index 0000000..2a6bc97 --- /dev/null +++ b/src/main/sdvxhook/gfx.h @@ -0,0 +1,8 @@ +#ifndef SDVXHOOK_GFX_H +#define SDVXHOOK_GFX_H + +void gfx_init(void); +void gfx_set_confined(void); +void gfx_set_windowed(void); + +#endif diff --git a/src/main/sdvxhook/kfca.c b/src/main/sdvxhook/kfca.c new file mode 100644 index 0000000..eec5582 --- /dev/null +++ b/src/main/sdvxhook/kfca.c @@ -0,0 +1,195 @@ +#include + +#include +#include + +#include "acio/acio.h" + +#include "acioemu/emu.h" + +#include "bemanitools/sdvxio.h" + +#include "util/defs.h" +#include "util/log.h" +#include "util/time.h" + +static void kfca_send_version(const struct ac_io_message *req); +static void kfca_report_status(const struct ac_io_message *req, uint8_t status); +static void kfca_report_0128(const struct ac_io_message *req); +static void kfca_poll(const struct ac_io_message *req); +static void kfca_poll_thunk(void *ctx_ptr, struct ac_io_message *resp); + +static struct ac_io_emu *kfca_ac_io_emu; + +void kfca_init(struct ac_io_emu *emu) +{ + log_assert(emu != NULL); + + kfca_ac_io_emu = emu; +} + +void kfca_dispatch_request(const struct ac_io_message *req) +{ + uint16_t cmd_code; + + cmd_code = ac_io_u16(req->cmd.code); + + switch (cmd_code) { + case AC_IO_CMD_GET_VERSION: + log_misc("AC_IO_CMD_GET_VERSION(%d)", req->addr); + kfca_send_version(req); + + break; + + case AC_IO_CMD_START_UP: + log_misc("AC_IO_CMD_START_UP(%d)", req->addr); + kfca_report_status(req, 0x00); + + break; + + case AC_IO_CMD_KFCA_POLL: + kfca_poll(req); + + break; + + case AC_IO_CMD_KFCA_UNK_0120: + log_misc("AC_IO_CMD_KFCA_UNK_%04X(%d)", cmd_code, req->addr); + kfca_report_status(req, 0x00); + + break; + + case AC_IO_CMD_KFCA_UNK_0128: + log_misc("AC_IO_CMD_KFCA_UNK_0128(%d)", req->addr); + kfca_report_0128(req); + + break; + + default: + log_warning("Unknown ACIO message %04x on KFCA mode, addr=%d", + cmd_code, req->addr); + + break; + } +} + +static void kfca_send_version(const struct ac_io_message *req) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.version); + resp.cmd.version.type = ac_io_u32(AC_IO_NODE_TYPE_KFCA); + resp.cmd.version.flag = 0x00; + resp.cmd.version.major = 0x01; + resp.cmd.version.minor = 0x01; + resp.cmd.version.revision = 0x00; + memcpy(resp.cmd.version.product_code, "KFCA", + sizeof(resp.cmd.version.product_code)); + strncpy(resp.cmd.version.date, __DATE__, sizeof(resp.cmd.version.date)); + strncpy(resp.cmd.version.time, __TIME__, sizeof(resp.cmd.version.time)); + + ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0); +} + +static void kfca_report_status(const struct ac_io_message *req, uint8_t status) +{ + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = sizeof(resp.cmd.status); + resp.cmd.status = status; + + ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0); +} + +static void kfca_report_0128(const struct ac_io_message *req) { + struct ac_io_message resp; + + resp.addr = req->addr | AC_IO_RESPONSE_FLAG; + resp.cmd.code = req->cmd.code; + resp.cmd.seq_no = req->cmd.seq_no; + resp.cmd.nbytes = req->cmd.nbytes; + memcpy(resp.cmd.raw, req->cmd.raw, req->cmd.nbytes); + + ac_io_emu_response_push(kfca_ac_io_emu, &resp, 0); +} + +static void kfca_poll(const struct ac_io_message *req) +{ + static uint32_t last_poll = 0; + uint64_t delay; + const struct ac_io_kfca_poll_out *pout; + uintptr_t ctx; + size_t i; + + /* Handle lighting output immediately */ + + pout = &req->cmd.kfca_poll_out; + + sdvx_io_set_gpio_lights(ac_io_u32(pout->gpio)); + + for (i = 0 ; i < lengthof(pout->pwm) ; i++) { + sdvx_io_set_pwm_light(i, pout->pwm[i]); + } + + sdvx_io_write_output(); + + /* SDVX expects only one poll response per frame. + + We use a thunk here to defer polling until right before the response + is due. */ + + delay = time_get_elapsed_ms(last_poll) < 16 ? 16000 : 0; + last_poll = time_get_counter(); + ctx = req->addr | (req->cmd.code << 8) | (req->cmd.seq_no << 24); + + ac_io_emu_response_push_thunk( + kfca_ac_io_emu, + kfca_poll_thunk, + (void *) ctx, + delay); +} + +static void kfca_poll_thunk(void *ctx_ptr, struct ac_io_message *resp) +{ + struct ac_io_kfca_poll_in *pin; + uintptr_t ctx; + uint8_t req_addr; + uint16_t req_code; + uint8_t req_seq_no; + + /* Unpack context "pointer" for relevant response information */ + + ctx = (uintptr_t) ctx_ptr; + req_addr = ctx; + req_code = ctx >> 8; + req_seq_no = ctx >> 24; + + /* Poll input now and construct an immediate response */ + + pin = &resp->cmd.kfca_poll_in; + + resp->addr = req_addr | AC_IO_RESPONSE_FLAG; + resp->cmd.code = req_code; + resp->cmd.seq_no = req_seq_no; + resp->cmd.nbytes = sizeof(*pin); + + sdvx_io_read_input(); + + memset(pin, 0, sizeof(*pin)); + + pin->adc[0] = sdvx_io_get_spinner_pos(0) << 6; + pin->adc[1] = sdvx_io_get_spinner_pos(1) << 6; + + pin->gpio_sys |= sdvx_io_get_input_gpio_sys() & 0x3F; + + pin->adc[0] = ac_io_u16(pin->adc[0]); + pin->adc[1] = ac_io_u16(pin->adc[1]); + pin->gpio[0] = ac_io_u16(sdvx_io_get_input_gpio(0)); + pin->gpio[1] = ac_io_u16(sdvx_io_get_input_gpio(1)); +} + diff --git a/src/main/sdvxhook/kfca.h b/src/main/sdvxhook/kfca.h new file mode 100644 index 0000000..7a3b91a --- /dev/null +++ b/src/main/sdvxhook/kfca.h @@ -0,0 +1,11 @@ +#ifndef SDVXHOOK_KFCA_H +#define SDVXHOOK_KFCA_H + +#include "acio/acio.h" + +#include "acioemu/emu.h" + +void kfca_init(struct ac_io_emu *emu); +void kfca_dispatch_request(const struct ac_io_message *req); + +#endif diff --git a/src/main/sdvxhook/lcd.c b/src/main/sdvxhook/lcd.c new file mode 100644 index 0000000..7296da9 --- /dev/null +++ b/src/main/sdvxhook/lcd.c @@ -0,0 +1,105 @@ +#define LOG_MODULE "lcd" + +#include + +#include +#include +#include + +#include +#include +#include + +#include "hook/iohook.h" + +#include "sdvxhook/lcd.h" + +#include "util/hex.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/str.h" + +static HRESULT lcd_open(struct irp *irp); +static HRESULT lcd_close(struct irp *irp); +static HRESULT lcd_write(struct irp *irp); + +static HANDLE lcd_fd; + +void lcd_init(void) +{ + log_assert(lcd_fd == NULL); + + lcd_fd = iohook_open_dummy_fd(); +} + +void lcd_fini(void) +{ + if (lcd_fd != NULL) { + CloseHandle(lcd_fd); + } + + lcd_fd = NULL; +} + +HRESULT lcd_dispatch_irp(struct irp *irp) +{ + log_assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != lcd_fd) { + return irp_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return lcd_open(irp); + case IRP_OP_CLOSE: return lcd_close(irp); + case IRP_OP_READ: return S_OK; + case IRP_OP_WRITE: return lcd_write(irp); + case IRP_OP_IOCTL: return S_OK; + default: return E_NOTIMPL; + } +} + +static HRESULT lcd_open(struct irp *irp) +{ + log_assert(irp != NULL); + + if (!wstr_eq(irp->open_filename, L"COM1")) { + return irp_invoke_next(irp); + } + + irp->fd = lcd_fd; + log_info("\"LCD\" port opened"); + + return S_OK; +} + +static HRESULT lcd_close(struct irp *irp) +{ + log_info("\"LCD\" port closed"); + + return S_OK; +} + +static HRESULT lcd_write(struct irp *irp) +{ + char str[128]; + size_t nbytes; + + log_assert(irp != NULL); + + if (irp->write.nbytes < sizeof(str)) { + nbytes = irp->write.nbytes; + } else { + nbytes = sizeof(str) - 1; + } + + memcpy(str, irp->write.bytes, nbytes); + str[nbytes] = '\0'; + + log_misc("-> %s", str); + + irp->write.pos = irp->write.nbytes; + + return S_OK; +} + diff --git a/src/main/sdvxhook/lcd.h b/src/main/sdvxhook/lcd.h new file mode 100644 index 0000000..aeaa9ea --- /dev/null +++ b/src/main/sdvxhook/lcd.h @@ -0,0 +1,12 @@ +#ifndef SDVXHOOK_LCD_H +#define SDVXHOOK_LCD_H + +#include + +#include "hook/iohook.h" + +void lcd_init(void); +void lcd_fini(void); +HRESULT lcd_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/sdvxhook/sdvxhook.def b/src/main/sdvxhook/sdvxhook.def new file mode 100644 index 0000000..b692fa7 --- /dev/null +++ b/src/main/sdvxhook/sdvxhook.def @@ -0,0 +1,4 @@ +LIBRARY sdvxhook + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/sdvxio/Module.mk b/src/main/sdvxio/Module.mk new file mode 100644 index 0000000..ffeca72 --- /dev/null +++ b/src/main/sdvxio/Module.mk @@ -0,0 +1,8 @@ +dlls += sdvxio + +libs_sdvxio := \ + geninput \ + +src_sdvxio := \ + sdvxio.c \ + diff --git a/src/main/sdvxio/sdvxio.c b/src/main/sdvxio/sdvxio.c new file mode 100644 index 0000000..e47ddc2 --- /dev/null +++ b/src/main/sdvxio/sdvxio.c @@ -0,0 +1,86 @@ +#include +#include +#include + +#include "bemanitools/glue.h" +#include "bemanitools/input.h" +#include "bemanitools/sdvxio.h" + +static uint16_t sdvx_io_gpio[2]; +static uint8_t sdvx_io_gpio_sys; + +void sdvx_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + input_set_loggers(misc, info, warning, fatal); +} + +bool sdvx_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + input_init(thread_create, thread_join, thread_destroy); + mapper_config_load("sdvx"); + + return true; +} + +void sdvx_io_fini(void) +{ + input_fini(); +} + +void sdvx_io_set_gpio_lights(uint32_t gpio_lights) +{ + size_t i; + + for (i = 0 ; i < 16 ; i++) { + if (gpio_lights & (1 << i)) { + mapper_write_light(i, 255); + } else { + mapper_write_light(i, 0); + } + } +} + +void sdvx_io_set_pwm_light(uint8_t light_no, uint8_t intensity) +{ + mapper_write_light(light_no + 0x10, intensity); +} + +bool sdvx_io_write_output(void) +{ + return true; +} + +bool sdvx_io_read_input(void) +{ + uint32_t pack; + + pack = mapper_update(); + + sdvx_io_gpio_sys = pack & 0xFF; + sdvx_io_gpio[0] = (pack >> 8) & 0x00FF; + sdvx_io_gpio[1] = (pack >> 16) & 0x00FF; + + return 0; +} + +uint8_t sdvx_io_get_input_gpio_sys(void) +{ + return sdvx_io_gpio_sys; +} + +uint16_t sdvx_io_get_input_gpio(uint8_t gpio_bank) +{ + if (gpio_bank > 1) { + return 0; + } + + return sdvx_io_gpio[gpio_bank]; +} + +uint16_t sdvx_io_get_spinner_pos(uint8_t spinner_no) +{ + return mapper_read_analog(spinner_no) * 4; +} + diff --git a/src/main/sdvxio/sdvxio.def b/src/main/sdvxio/sdvxio.def new file mode 100644 index 0000000..e4783a2 --- /dev/null +++ b/src/main/sdvxio/sdvxio.def @@ -0,0 +1,13 @@ +LIBRARY sdvxio + +EXPORTS + sdvx_io_fini + sdvx_io_get_input_gpio + sdvx_io_get_input_gpio_sys + sdvx_io_get_spinner_pos + sdvx_io_init + sdvx_io_read_input + sdvx_io_set_gpio_lights + sdvx_io_set_loggers + sdvx_io_set_pwm_light + sdvx_io_write_output diff --git a/src/main/security/Module.mk b/src/main/security/Module.mk new file mode 100644 index 0000000..0ef0dff --- /dev/null +++ b/src/main/security/Module.mk @@ -0,0 +1,14 @@ +libs += security + +libs_security := \ + util \ + +src_security := \ + id.c \ + mcode.c \ + rp-blowfish.c \ + rp-sign-key.c \ + rp.c \ + rp2.c \ + rp3.c \ + util.c \ diff --git a/src/main/security/id.c b/src/main/security/id.c new file mode 100644 index 0000000..570fc5b --- /dev/null +++ b/src/main/security/id.c @@ -0,0 +1,142 @@ +#include + +#include "security/id.h" + +#include "util/hex.h" +#include "util/log.h" +#include "util/mem.h" + +const struct security_id security_id_default = { + .header = SECURITY_ID_HEADER, + .id = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + .checksum = 0x6F, +}; + +static uint8_t security_id_checksum_calc(uint8_t initseed, + const uint8_t* inbuf, size_t length) +{ + const uint8_t *v3; // edi@1 + int v4; // ebp@2 + uint8_t result; // al@2 + unsigned int v6; // esi@3 + signed int v7; // ebx@3 + int v8; // edx@4 + uint8_t v9; // al@4 + int v10; // edx@6 + uint8_t v11; // al@6 + int v12; // edx@8 + uint8_t v13; // al@8 + int v14; // edx@10 + + v3 = inbuf; + + if (length <= 0) { + result = initseed; + } else { + v4 = length; + result = initseed; + + do { + v6 = *v3; + v7 = 0; + + do { + v8 = (result ^ (uint8_t) (v6 >> v7)) & 1; + v9 = result >> 1; + + if (v8) { + v9 ^= 0x8Cu; + } + + v10 = (v9 ^ (uint8_t) (v6 >> (v7 + 1))) & 1; + v11 = v9 >> 1; + + if (v10) { + v11 ^= 0x8Cu; + } + + v12 = (v11 ^ (uint8_t) (v6 >> (v7 + 2))) & 1; + v13 = v11 >> 1; + + if (v12) { + v13 ^= 0x8Cu; + } + + v14 = (v13 ^ (uint8_t) (v6 >> (v7 + 3))) & 1; + result = v13 >> 1; + + if (v14) { + result ^= 0x8Cu; + } + + v7 += 4; + } while (v7 < 8); + + v3 = (v3 + 1); + --v4; + } while (v4); + } + + return result; +} + +static uint8_t security_id_checksum_buffer(const uint8_t* inbuf) +{ + uint8_t bufcheck[7]; + + bufcheck[0] = inbuf[1]; + bufcheck[1] = inbuf[7]; + bufcheck[2] = inbuf[6]; + bufcheck[3] = inbuf[5]; + bufcheck[4] = inbuf[4]; + bufcheck[5] = inbuf[3]; + bufcheck[6] = inbuf[2]; + + return security_id_checksum_calc(0, bufcheck, sizeof(bufcheck)); +} + +bool security_id_parse(const char* str, struct security_id* id) +{ + log_assert(str); + log_assert(id); + + return hex_decode(id, sizeof(struct security_id), str, strlen(str)); +} + +char* security_id_to_str(const struct security_id* id, bool id_only) +{ + char* str; + size_t len; + + log_assert(id); + + if (id_only) { + len = sizeof(id->id) * 2 + 1; + str = xmalloc(len); + hex_encode_uc(id->id, sizeof(id->id), str, len); + } else { + len = sizeof(struct security_id) * 2 + 1; + str = xmalloc(len); + hex_encode_uc(id, sizeof(*id), str, len); + } + + str[len - 1] = '\0'; + + return str; +} + +void security_id_prepare(struct security_id* id) +{ + log_assert(id); + + id->header = SECURITY_ID_HEADER; + id->checksum = security_id_checksum_buffer(id->id); +} + +bool security_id_verify(const struct security_id* id) +{ + log_assert(id); + + return id->header == SECURITY_ID_HEADER && + id->checksum == security_id_checksum_buffer(id->id); +} diff --git a/src/main/security/id.h b/src/main/security/id.h new file mode 100644 index 0000000..984b4c1 --- /dev/null +++ b/src/main/security/id.h @@ -0,0 +1,58 @@ +#ifndef SECURITY_ID +#define SECURITY_ID + +#include +#include + +#define SECURITY_ID_HEADER 0x01 + +/** + * Structure of a security id, e.g. PCBID or EAMID. + */ +struct security_id { + uint8_t header; + uint8_t id[8]; + uint8_t checksum; +}; + +/** + * "Default" security id with updated header and cheacksum ready for use. + */ +extern const struct security_id security_id_default; + +/** + * Parse a stringified security id, e.g. PCBID, EAMID. + * + * @param str String representation of id. + * @param id Pointer to a security_id struct to write the result to. + * @return True on successf, false on parsing error. + */ +bool security_id_parse(const char* str, struct security_id* id); + +/** + * Stringify a security_id struct. + * + * @param id Id to stringify. + * @param id_only True to stringify id only, false stringify header, id and + * checksum. + * @return Allocated string with stringified data. Caller must manage memory. + */ +char* security_id_to_str(const struct security_id* id, bool id_only); + +/** + * Prepare a security id, i.e. update header and checksum. + * + * @param id Id to prepare. + */ +void security_id_prepare(struct security_id* id); + +/** + * Verify a provided security id, i.e. check header and checksum. + * + * @param id Id to verify. + * @return True if verification is successful, false on header or checksum + * mismatch. + */ +bool security_id_verify(const struct security_id* id); + +#endif \ No newline at end of file diff --git a/src/main/security/mcode.c b/src/main/security/mcode.c new file mode 100644 index 0000000..73227c2 --- /dev/null +++ b/src/main/security/mcode.c @@ -0,0 +1,48 @@ +#include + +#include "security/mcode.h" + +#include "util/log.h" +#include "util/mem.h" + +const struct security_mcode security_mcode_eamuse = { + .header = '@', + .unkn = '@', + .game = "@@@", + .region = '@', + .cabinet = '@', + .revision = '@', +}; + +bool security_mcode_parse(const char* str, struct security_mcode* mcode) +{ + size_t len; + + log_assert(str); + log_assert(mcode); + + memset(mcode, ' ', sizeof(struct security_mcode)); + + len = strlen(str); + + if (len > sizeof(struct security_mcode)) { + return false; + } + + memcpy(mcode, str, len); + + return true; +} + +char* security_mcode_to_str(const struct security_mcode* mcode) +{ + char* str; + + log_assert(mcode); + + str = xmalloc(sizeof(struct security_mcode) + 1); + memcpy(str, mcode, sizeof(struct security_mcode)); + str[sizeof(struct security_mcode)] = '\0'; + + return str; +} \ No newline at end of file diff --git a/src/main/security/mcode.h b/src/main/security/mcode.h new file mode 100644 index 0000000..bad201a --- /dev/null +++ b/src/main/security/mcode.h @@ -0,0 +1,107 @@ +#ifndef SECURITY_MCODE_H +#define SECURITY_MCODE_H + +#include + +/* Used to fill up empty spaces */ +#define SECURITY_MCODE_FIELD_BLANK ' ' +#define SECURITY_MCODE_FIELD_NULL '\0' + +/* Header */ +#define SECURITY_MCODE_HEADER 'G' + +/* Unkn */ +#define SECURITY_MCODE_UNKN_C 'C' +#define SECURITY_MCODE_UNKN_E 'E' +#define SECURITY_MCODE_UNKN_K 'K' +#define SECURITY_MCODE_UNKN_N 'N' +#define SECURITY_MCODE_UNKN_Q 'Q' + +/* Games */ +#define SECURITY_MCODE_GAME_LEN 3 + +/* IIDX */ +#define SECURITY_MCODE_GAME_IIDX_1 "863" +#define SECURITY_MCODE_GAME_IIDX_1_CLUB "896" +#define SECURITY_MCODE_GAME_IIDX_SUB "983" +#define SECURITY_MCODE_GAME_IIDX_CLUB_2 "984" +#define SECURITY_MCODE_GAME_IIDX_2 "985" +#define SECURITY_MCODE_GAME_IIDX_3 "992" +#define SECURITY_MCODE_GAME_IIDX_4 "A03" +#define SECURITY_MCODE_GAME_IIDX_5 "A17" +#define SECURITY_MCODE_GAME_IIDX_6 "B4U" +#define SECURITY_MCODE_GAME_IIDX_7 "B44" +#define SECURITY_MCODE_GAME_IIDX_8 "C44" +#define SECURITY_MCODE_GAME_IIDX_9 "C02" +#define SECURITY_MCODE_GAME_IIDX_10 "D01" +#define SECURITY_MCODE_GAME_IIDX_11 "E11" +#define SECURITY_MCODE_GAME_IIDX_12 "ECO" +#define SECURITY_MCODE_GAME_IIDX_13 "FDD" +#define SECURITY_MCODE_GAME_IIDX_14 "GLD" +#define SECURITY_MCODE_GAME_IIDX_15 "HDD" +#define SECURITY_MCODE_GAME_IIDX_16 "I00" +#define SECURITY_MCODE_GAME_IIDX_17 "JDJ" +#define SECURITY_MCODE_GAME_IIDX_18 "JDZ" +#define SECURITY_MCODE_GAME_IIDX_19 "KDZ" +#define SECURITY_MCODE_GAME_IIDX_20 "LDJ" +#define SECURITY_MCODE_GAME_IIDX_21 "LDJ" +#define SECURITY_MCODE_GAME_IIDX_22 "LDJ" +#define SECURITY_MCODE_GAME_IIDX_23 "LDJ" +#define SECURITY_MCODE_GAME_IIDX_24 "LDJ" + +/* Jubeat */ +#define SECURITY_MCODE_GAME_JB_1 "H44" + +/* Region */ +#define SECURITY_MCODE_REGION_ASIA 'A' +#define SECURITY_MCODE_REGION_JAPAN 'J' + +/* Cabinet */ +#define SECURITY_MCODE_CABINET_A 'A' + +/* Revision */ +#define SECURITY_MCODE_REVISION_A 'A' +#define SECURITY_MCODE_REVISION_B 'B' +#define SECURITY_MCODE_REVISION_C 'C' +#define SECURITY_MCODE_REVISION_D 'D' +#define SECURITY_MCODE_REVISION_E 'E' +#define SECURITY_MCODE_REVISION_F 'F' +#define SECURITY_MCODE_REVISION_G 'G' + +/** + * Structure to represent a Konami mcode which is used to identify games and + * different hardware/software revisions. + */ +struct security_mcode { + char header; + char unkn; + /* Identify the game and version, e.g. IIDX 12 */ + char game[3]; + char region; + char cabinet; + char revision; +}; + +/** + * Default mcode used to identify white eamuse roundplugs. + */ +extern const struct security_mcode security_mcode_eamuse; + +/** + * Parse an mcode from a string representation. + * + * @param str String to parse. + * @param mcode Pointer to a security_mcode struct to write the result to. + * @return True on success, false on parsing error. + */ +bool security_mcode_parse(const char* str, struct security_mcode* mcode); + +/** + * Stringify an mcode. + * + * @param mcode Mcode to stringify. + * @return String containing the stringified mcode. Caller has to manage memory. + */ +char* security_mcode_to_str(const struct security_mcode* mcode); + +#endif \ No newline at end of file diff --git a/src/main/security/rp-blowfish-table.h b/src/main/security/rp-blowfish-table.h new file mode 100644 index 0000000..9db279e --- /dev/null +++ b/src/main/security/rp-blowfish-table.h @@ -0,0 +1,228 @@ +#ifndef SECURITY_RP_BLOWFISH_TABLE_H +#define SECURITY_RP_BLOWFISH_TABLE_H + +#include + +/** + * Custom pbox for blowfish used by the roundplug module. + */ +static const uint32_t security_rp_blowfish_table_custom_pbox[16 + 2] = { + 0x79F4182B, 0x0B4B1751, 0x170170D8, 0x4C0F65E3, + 0x24F05558, 0x1E46FA6, 0x61E35AB, 0x579903BE, + 0x1E6D3235, 0x47D05ED6, 0x722F4F14, 0x53031912, + 0x2C4771B1, 0x56C43044, 0x4F8A5C95, 0x6A490262, + 0x176C1D7F, 0x64274C38 +}; + +/** + * Custom sbox for blowfish used by the roundplug module. + */ +static const uint32_t security_rp_blowfish_table_custom_sbox[4 * 256] = { + 0x4BD821B3, 0x6D5E005A, 0x0C5F6494, 0x45960A5D, 0x56956C75, + 0x1CA85246, 0x4BB809AD, 0x25601443, 0x79930BE9, 0x45826099, + 0x2AA261C7, 0x70A57DE, 0x17756A0, 0x2EB87768, 0x635C62A4, + 0x1BCC0A05, 0x763A412D, 0x550F10EC, 0x35BA414D, 0x671238F4, + 0x63A904A9, 0x77BA3004, 0x57A1208B, 0x32FC6ECB, 0x71E65532, + 0x74E64AB1, 0x358E4F6D, 0x54DF640D, 0x4DE52868, 0x762F4498, + 0x30F17C5, 0x41C24222, 0x61EF0BEE, 0x6D287983, 0x1B481CAA, + 0x72E175D5, 0x5E220BEF, 0x5FD857E0, 0x317048F8, 0x1A2D11D6, + 0x10EF2996, 0x5370B41, 0x51514DCB, 0x26CA4139, 0x0F9B6195, + 0x31B536DA, 0x2FCC3108, 0x198F49F5, 0x2EBF429F, 0x13B44006, + 0x4B526BD4, 0x298A1F66, 0x4AC813EE, 0x400928C2, 0x5C6C191B, + 0x387A04CC, 0x60770BBE, 0x0E7E7A30, 0x1634340A, 0x17533FCA, + 0x756215D0, 0x46533F16, 0x36DA6695, 0x1ABD5AE8, 0x507229E8, + 0x1AB80E5D, 0x682067F3, 0x7F957811, 0x263324F, 0x77552592, + 0x6FDE6B1D, 0x1F6B3315, 0x3E460252, 0x1CC31368, 0x307F1D52, + 0x75021428, 0x2025CC1, 0x0D107234, 0x79823495, 0x70D41261, + 0x0ECF0A70, 0x483E3270, 0x27FA0E2E, 0x1D8B263D, 0x31BA00B9, + 0x58C47538, 0x170E5D25, 0x14886C19, 0x5C2417F9, 0x300C76D0, + 0x607A68CA, 0x426056BB, 0x0C43520F, 0x52F4091C, 0x6D0C5B30, + 0x7B5A71CA, 0x59F30E0, 0x564D1E27, 0x55281FAD, 0x5FF33451, + 0x59961CD5, 0x2B5F629D, 0x1144515C, 0x115A6341, 0x3FD95B5B, + 0x3C636850, 0x7A6D399B, 0x35F403EC, 0x3EED1562, 0x590760B7, + 0x1AC05E8D, 0x4DD95E8A, 0x24AA6DDF, 0x72EE676A, 0x4DF12199, + 0x575510B7, 0x4EBF284B, 0x2A2D5CA7, 0x31C86DE9, 0x43682FF3, + 0x432C5F21, 0x29CF4FD0, 0x669F76EC, 0x3A467C21, 0x18C74A64, + 0x545279EB, 0x1FE506D5, 0x2FD82209, 0x2FB81616, 0x40284821, + 0x49F5D1A, 0x48390DD6, 0x39FD48C2, 0x436763F, 0x5FE11CF5, + 0x0C3A4D98, 0x13E739F3, 0x54593938, 0x21594BE6, 0x6DDF03C3, + 0x6C9A18BB, 0x6DDC79A1, 0x2DC5602F, 0x1CE069AF, 0x3E91022D, + 0x54031E34, 0x7F785F57, 0x2B286216, 0x181827E3, 0x5C83664E, + 0x16D90CA8, 0x1591B99, 0x33D00678, 0x0C075470, 0x3AE327B1, + 0x2346433B, 0x612E2C0F, 0x42AD28C2, 0x9A63AC4, 0x447846E4, + 0x3CFC0ECB, 0x38876B8B, 0x58C57979, 0x2CAA27DF, 0x39D77355, + 0x4A1B07BA, 0x65F62F29, 0x6C4C5D5C, 0x78B06359, 0x14E37561, + 0x57853D74, 0x610346F0, 0x714B3409, 0x63CE7434, 0x0CD12ABC, + 0x0A282F10, 0x6AC31C98, 0x6BBD560E, 0x3ECD40A7, 0x2D455D99, + 0x44035CC0, 0x5807596D, 0x70813AA1, 0x4A9B3A4B, 0x0C4E733E, + 0x26F513F1, 0x2F870458, 0x619F7F49, 0x6BB96450, 0x56466CE1, + 0x248E083E, 0x5787B9B, 0x5BAC103E, 0x5FAC27A6, 0x73DA0E0A, + 0x418365AC, 0x2F6419CE, 0x54D7E4D, 0x6DC12937, 0x4DCF3DCD, + 0x6C745CCD, 0x6E444C09, 0x0F303784, 0x13A140B0, 0x733F748D, + 0x131D47AF, 0x52256F72, 0x1F169ED, 0x77805262, 0x7A5C4E39, + 0x4E327ECA, 0x31EB0681, 0x48C26425, 0x44F73D44, 0x29003914, + 0x5B6F5749, 0x6AE84AB1, 0x0AD85D92, 0x56C95620, 0x1991438C, + 0x7EA72068, 0x75C5D88, 0x2ADD4081, 0x7F1A1B6E, 0x7A064FAF, + 0x15D65BB0, 0x381E740E, 0x7B056A7E, 0x30E5796D, 0x7E0C0139, + 0x1846C5F, 0x0D035A29, 0x2D736B90, 0x10B92C72, 0x7A9A2356, + 0x49F82444, 0x58B72788, 0x140A2B53, 0x57FF1F93, 0x307B1587, + 0x730878CA, 0x2105F4, 0x2FE612FE, 0x4D5A200C, 0x5C951B4F, + 0x0B955913, 0x6A970941, 0x30147DD4, 0x2DDD7213, 0x7F04093, + 0x2EB264C, 0x2F5D6342, 0x20384D96, 0x0E2F51F8, 0x16D374CF, + 0x7D9D419E, 0x464C425E, 0x7B4526EC, 0x47D244C3, 0x0D711994, + 0x14990F72, 0x3C9977BC, 0x6B7F19E2, 0x46295DB2, 0x39EE14E1, + 0x6C8345DB, 0x0C042A2A, 0x84864ED, 0x47F15A39, 0x3ED4165E, + 0x38A16C5E, 0x2076D1D, 0x2B9C2511, 0x3D4B3C7F, 0x262A3685, + 0x53D22D7C, 0x75BA1F80, 0x5420341B, 0x1F846C69, 0x4CA792B, + 0x736E0EA3, 0x2E7D0C34, 0x462C3E19, 0x474171AD, 0x16AC191A, + 0x62FF616F, 0x22B17668, 0x0D41496A, 0x2EBE1D8B, 0x703A6897, + 0x535596B, 0x6D06162D, 0x70C0191E, 0x9330918, 0x35F012FF, + 0x557F33A7, 0x50241E00, 0x31E30785, 0x75AD3541, 0x12D135EA, + 0x272F59E1, 0x2BBB4012, 0x4E4A4714, 0x22D46BEF, 0x2F7A2C75, + 0x321A711B, 0x333C629E, 0x59947210, 0x4A734DF6, 0x6D8159B, + 0x720963CB, 0x55D1548E, 0x19D905A1, 0x4EBF4FB6, 0x72CD26C9, + 0x457F420C, 0x68437035, 0x3E0D632F, 0x48B6568A, 0x7A474E12, + 0x619A5677, 0x14F43FF2, 0x12874B46, 0x4E04438C, 0x1B9A13B0, + 0x6C191C8C, 0x2B1978F6, 0x57470748, 0x6E550C2D, 0x511820AE, + 0x29D56DD9, 0x6DD40A7B, 0x1CB033BE, 0x32FF1FAF, 0x333B60F3, + 0x7F224D3D, 0x4CE42438, 0x1ECF6306, 0x195E5089, 0x32DF7108, + 0x50FF43F4, 0x4BFE04D3, 0x4C6630A7, 0x5F6A5B3D, 0x3F607C4D, + 0x78F75A57, 0x1B0D299B, 0x4E5767A0, 0x5B3E0025, 0x17986E78, + 0x43226677, 0x50EE2AD7, 0x4D222261, 0x49B30C3E, 0x52097392, + 0x24AC44EE, 0x2B683C68, 0x2D8E5959, 0x55392D07, 0x27C043D6, + 0x52C224A9, 0x7A630098, 0x5ABC3D35, 0x5D9015E1, 0x0EB53512, + 0x6CE06C73, 0x41B32B3D, 0x43320C48, 0x121A6199, 0x3A81182, + 0x5ACF7987, 0x73F36DA5, 0x3F9E5EB7, 0x0BD14904, 0x58D9543E, + 0x1DD31879, 0x424257E8, 0x7D643B59, 0x112B4FCF, 0x460735A7, + 0x75DC362C, 0x27E05A88, 0x0C35376E, 0x5D767AF7, 0x1F957689, + 0x2EB76CBA, 0x65FA6B91, 0x253D7D95, 0x76691087, 0x55CF26BD, + 0x7E905073, 0x632B148F, 0x7BAA6CB2, 0x1E025682, 0x7AA34C92, + 0x66454B5B, 0x237C6B1C, 0x319F1DA0, 0x13F25731, 0x5B43644E, + 0x4D5A79D9, 0x20E73BCE, 0x39D016D7, 0x71106B26, 0x0C903B81, + 0x6E91696D, 0x1A40FCE, 0x4D4E5781, 0x40C03DB1, 0x6C4641FC, + 0x676A62A5, 0x50D13F63, 0x7E5D3F89, 0x67253EA1, 0x5E301AAF, + 0x2C1F19B4, 0x47330830, 0x244140A7, 0x309A0C82, 0x43F710C0, + 0x12DD4D4C, 0x2D1E2BFA, 0x315E4478, 0x65C664B5, 0x274D298A, + 0x6C3711AD, 0x3BC80531, 0x4C335E29, 0x4147231C, 0x197B6A75, + 0x438560F, 0x73955091, 0x0CE64236, 0x76CB612B, 0x389F21B9, + 0x3E772CCB, 0x6E163790, 0x5C7E2D4E, 0x71066499, 0x6A1B2395, + 0x191812E8, 0x15DE7D7E, 0x70076B5E, 0x52F45813, 0x7B02097D, + 0x0DA87202, 0x3352217A, 0x92A224E, 0x723C4C94, 0x4A8F2743, + 0x54296FA5, 0x351A61AF, 0x57046FF7, 0x3BBA504F, 0x37E42A58, + 0x4BD35F88, 0x53EB6071, 0x55416157, 0x106E1053, 0x139B418B, + 0x0E556A53, 0x7CB44A32, 0x2CC55918, 0x78680C51, 0x1EFD4FF2, + 0x459540D8, 0x197E536D, 0x306735D4, 0x5A6F7027, 0x7DD91BE7, + 0x113F13DE, 0x307847F5, 0x698C7CCD, 0x0A6A3D21, 0x3D443339, + 0x70B012F8, 0x320A4147, 0x13A80BEB, 0x45DB1D10, 0x0D67003, + 0x0EF178F7, 0x1E37FD0, 0x32E76E3B, 0x6CE613A3, 0x591F35C8, + 0x49E33AFD, 0x1C616353, 0x71917046, 0x3FC456A5, 0x96134C2, + 0x2AD25737, 0x0BB28C2, 0x70E70E00, 0x738B7417, 0x67DA518B, + 0x15FC12CC, 0x11DF784C, 0x0D835A15, 0x5EAC2534, 0x5B204982, + 0x7DB4283, 0x3AE17C78, 0x0C901EDE, 0x1CA74FE2, 0x105B60A1, + 0x1A2C1024, 0x21604376, 0x0E454350, 0x29B55427, 0x3165639D, + 0x100B1EB0, 0x5F631E15, 0x4B910299, 0x6A7D698D, 0x6F152182, + 0x1F9C09DA, 0x6F6C1C00, 0x5FDA5342, 0x291510A7, 0x73414231, + 0x69191168, 0x78D53B2D, 0x7D35031E, 0x17D5070C, 0x0F3B1B63, + 0x563F5F65, 0x4FB670A8, 0x38921F44, 0x33CD5085, 0x0ED76C22, + 0x4A6A504D, 0x12E26B14, 0x6C9B4BE6, 0x3BD65642, 0x4D3846D7, + 0x6AE7069B, 0x2BC663A3, 0x556445C7, 0x2E9B6556, 0x4FF47C5D, + 0x28436F5D, 0x5F201057, 0x4C23779A, 0x311107C3, 0x165C1EB8, + 0x649B43BB, 0x6AF262BC, 0x417B6796, 0x302E5FDD, 0x45DD4060, + 0x68401396, 0x2D224088, 0x0E2955F9, 0x53DF63D9, 0x39D87D46, + 0x1D290FAB, 0x4F935FBC, 0x3D10435B, 0x3E9927AF, 0x0C692CF8, + 0x13062DDB, 0x49D1763F, 0x5EF64DF8, 0x6BEE5DCA, 0x3EED698F, + 0x68F87084, 0x7D770D0B, 0x17787760, 0x1A0B6B13, 0x0C506D4B, + 0x37352217, 0x19FC115F, 0x33F50029, 0x55176959, 0x7462390F, + 0x4221484D, 0x1D924296, 0x107F2D98, 0x48BE4B29, 0x6C31017, + 0x71A27EA1, 0x42E6996, 0x24230D5F, 0x3B73168B, 0x2D342998, + 0x0ABE5E05, 0x52FA6EF1, 0x14F535DF, 0x582E684C, 0x3E1739, + 0x4EC427E1, 0x7DC17728, 0x7DCA2590, 0x7EAF37AD, 0x5E925775, + 0x3A8324E4, 0x751338BE, 0x6A88527E, 0x78D32B9, 0x5E9B6D71, + 0x796C1C31, 0x4F3255EE, 0x4D6F42FB, 0x109D1594, 0x130933CC, + 0x0B8C05E3, 0x4308344E, 0x1ED23AF3, 0x4F742372, 0x4E680666, + 0x6ABA3407, 0x63B5269B, 0x4A09358C, 0x630C7624, 0x66FD5F42, + 0x7A772F7B, 0x47B5186, 0x4DB318F0, 0x7ADC415E, 0x437B2CE6, + 0x0F4D0252, 0x380A123F, 0x5A75627D, 0x38DB7915, 0x2590CE3, + 0x2DB32FAB, 0x4B7D6605, 0x12E103A3, 0x454C16B3, 0x28C44371, + 0x15BA3922, 0x3C8F740F, 0x28285C3E, 0x6E490EF2, 0x43885141, + 0x18185C3E, 0x4BC17785, 0x3ABE3D36, 0x32557A96, 0x13647AF6, + 0x5A41368C, 0x3E976D64, 0x73253698, 0x0C900E64, 0x0FA401DC, + 0x6AB84D4B, 0x7B0E2A80, 0x66B3697, 0x123C5302, 0x0CFC43DD, + 0x23BC1DCD, 0x2CFA6802, 0x1B2F3B0E, 0x3CB22BAE, 0x7BEE08C3, + 0x29F56DFE, 0x6EF47914, 0x171C1D79, 0x67F832E7, 0x49487D46, + 0x25D920AF, 0x4BEF4A9C, 0x123F6184, 0x6D945679, 0x44805079, + 0x79D56797, 0x2BC0162A, 0x54AB789C, 0x245D7894, 0x5AEE2690, + 0x7C8A5D2A, 0x2C176395, 0x2C393125, 0x414A41D2, 0x443F2C1B, + 0x75946AB1, 0x58B220D3, 0x6371164A, 0x6A965058, 0x3B91171F, + 0x14B9734C, 0x78DB4E09, 0x7FDC6D7F, 0x15B31A72, 0x62135FC0, + 0x26781AC5, 0x0D6F0BF3, 0x7D515123, 0x51D93F64, 0x3B2A0058, + 0x3641375B, 0x71F9550D, 0x4CF933F1, 0x3C462F3E, 0x275B2D21, + 0x6DEA1FEF, 0x2CB25E22, 0x42176F1B, 0x58581AF4, 0x2A983DEE, + 0x231B243A, 0x245A1934, 0x1BE56D47, 0x352C1010, 0x739D087B, + 0x76C322F1, 0x482DD0, 0x53011EC2, 0x7B740FB0, 0x55773861, + 0x7FD6720A, 0x36287C53, 0x0A3237A4, 0x18F66B6, 0x17B59B3, + 0x1E43776, 0x0A3C7DA9, 0x4D9C56C4, 0x29361D4E, 0x1F2F5DC4, + 0x794A7512, 0x5A1D1786, 0x789A2CA1, 0x61330E38, 0x30EC6DB9, + 0x220A789A, 0x3F4F5D38, 0x79D21BB2, 0x7F002A7A, 0x574D74C0, + 0x5B8E28E1, 0x3E1E3F74, 0x0E3D27B5, 0x0F442C5F, 0x4AA87C2F, + 0x3AE25BA8, 0x1EAB71D0, 0x3F417000, 0x98014F7, 0x19174DD7, + 0x0E280BB3, 0x90A5AD0, 0x450D70D6, 0x52705173, 0x30E5654B, + 0x57597D23, 0x5AD94BA9, 0x35CF724F, 0x79E76207, 0x4A5C16EC, + 0x29852E13, 0x3BCD0F22, 0x73876F5E, 0x74366245, 0x31420BFA, + 0x2B366C08, 0x7F22B47, 0x657C6DED, 0x459730A, 0x0D8A5500, + 0x19A91ABF, 0x9B907C5, 0x7A44931, 0x737E506F, 0x6C027047, + 0x53EB3752, 0x3131B21, 0x979619B, 0x5BA56263, 0x3B04483F, + 0x2287AD1, 0x7C175B31, 0x6710121D, 0x66A64D8, 0x47B6DFC, + 0x2CA941C0, 0x714017F, 0x6A774090, 0x7E376CA1, 0x35B33E3C, + 0x57561A1D, 0x74FA527B, 0x289B6F82, 0x3F6D207C, 0x0ECE4388, + 0x49B41FD4, 0x227A0EA5, 0x244371BC, 0x484B09DC, 0x2EDB1679, + 0x60BB2FD6, 0x6554171B, 0x1AED5433, 0x699B776A, 0x6ED94522, + 0x6EF7041E, 0x2AA2B2F, 0x6A90514E, 0x7F641B5D, 0x2D317D2B, + 0x18CA0F75, 0x4A4D0AEE, 0x4C7F389D, 0x70406A0F, 0x1C6B602C, + 0x15D87DB8, 0x48604ABD, 0x21143879, 0x4AFE5183, 0x6B2B203D, + 0x1DD425DC, 0x45D04E7D, 0x8C77328, 0x65B540B3, 0x0B852DB, + 0x564E0D12, 0x596146D1, 0x5AD74F74, 0x4E802DE2, 0x60986294, + 0x283866B7, 0x6668397D, 0x7EB414DB, 0x7D706D42, 0x610D3C15, + 0x473152F0, 0x636E25F6, 0x14C92DC1, 0x587645AE, 0x0B715192, + 0x39DD0E24, 0x724B4997, 0x45B6E45, 0x0D5B574E, 0x0AC5679B, + 0x29D25DB1, 0x47A7322, 0x3F930CB0, 0x5AFD2858, 0x4BF060DE, + 0x7AE35A94, 0x49B44B6B, 0x2BF02098, 0x6A671652, 0x347D7A69, + 0x74CD58D3, 0x4C1C095F, 0x6CB01024, 0x217F0E78, 0x67DF3064, + 0x779622F1, 0x347741D0, 0x1DF85F18, 0x34615E39, 0x4884635C, + 0x121C22B6, 0x0A4877A6, 0x32BD6996, 0x77A234EC, 0x9346098, + 0x33A46D03, 0x2ED04F5F, 0x6CBD44B4, 0x65062220, 0x0C963413, + 0x646E0C9E, 0x1F3B610C, 0x1D0E5958, 0x357B083F, 0x4BDB3900, + 0x4691680E, 0x97D2630, 0x550461F3, 0x36533159, 0x5ACB4617, + 0x67B748FC, 0x40B57D21, 0x3A83521C, 0x6508044C, 0x2ED324F3, + 0x75686FBD, 0x710D41E4, 0x1FFB6713, 0x341F52B1, 0x5FCB503F, + 0x1084BEE, 0x686C1C96, 0x1F9A42B5, 0x74DE6C24, 0x7C7E4A9C, + 0x7DB47B44, 0x30786FE4, 0x1A760E6E, 0x0CEC21F3, 0x1245504F, + 0x51A22204, 0x5FBD561F, 0x6FC64A1E, 0x2C203E2F, 0x7E334042, + 0x5B01CC1, 0x42F169E9, 0x555C2B1D, 0x75FB33AE, 0x579F6CE1, + 0x21460147, 0x1CA63F99, 0x420059B9, 0x53B1088F, 0x0F1523CC, + 0x65BB74E5, 0x8F806BD, 0x235D7FE0, 0x36A04334, 0x9F03387, + 0x33D11875, 0x63F2576D, 0x749C09DA, 0x48AA01CB, 0x62291881, + 0x3F0F3ED1, 0x34B44542, 0x7E7423E, 0x2ED66EE4, 0x532C572F, + 0x1E066090, 0x64B30234, 0x445F2485, 0x6C1009A9, 0x0FA46F0E, + 0x13B45844, 0x418135B1, 0x0B586DEA, 0x6D1346AA, 0x119F52C7, + 0x6DAB7498, 0x40F6409D, 0x3ACF2A75, 0x23BF765C, 0x3D827BCB, + 0x53B069F4, 0x0AC60525, 0x28754C67, 0x275E30C4, 0x399D1422, + 0x38D83394, 0x37B3018D, 0x47CB565F, 0x60AB51E1, 0x775E5F3F, + 0x1381143A, 0x56092F59, 0x471C54ED, 0x2BA068C8, 0x6878131B, + 0x688D30D8, 0x64043486, 0x1A5C6275, 0x5E496BA4, 0x97E44E3, + 0x5FCD43F2, 0x64461AAF, 0x7837488B, 0x1B381CE5, 0x1CB378F9, + 0x52833683, 0x4598004B, 0x0CCC142E, 0x0F496267, 0x732D5C24, + 0x625913A2, 0x5E7E2272, 0x66F22E8E, 0x5D36D67, 0x47773C13, + 0x76455E48, 0x66841C2, 0x37F65245, 0x6B860A65, 0x444F0FAC, + 0x4C385CDB, 0x318C535D, 0x332E1523, 0x377B527E, 0x2B4453B6, + 0x7180046F, 0x31A9371A, 0x1DD4900, 0x134053B4, 0x7A303EE2, + 0x39465DDE, 0x2D3D5E5C, 0x29363A39, 0x4DEE2C3F, 0x3BE96844, + 0x7F98191C, 0x50027B2E, 0x3B8060B4, 0x7B8914E9, 0x2F9E7DB0, + 0x0D61780, 0x7AA10593, 0x0AB100AA, 0x693300EC, 0x73B7239F, + 0x187D13D0, 0x1D00352A, 0x21EE0A87, 0x17FE5E00, 0x34381E86, + 0x27A84750, 0x3D9C079D, 0x5CD8559B, 0x0B682D72, 0x51F205C6, + 0x2BE0D2E, 0x49C7206E, 0x56962776, 0x35B9377B, 0x5F060A9D, + 0x671F21F8, 0x75C24D18, 0x6FEB6A22, 0x7BDF0C32 +}; + +#endif \ No newline at end of file diff --git a/src/main/security/rp-blowfish.c b/src/main/security/rp-blowfish.c new file mode 100644 index 0000000..ace086e --- /dev/null +++ b/src/main/security/rp-blowfish.c @@ -0,0 +1,159 @@ +#include + +#include "security/rp-blowfish.h" +#include "security/rp-blowfish-table.h" + +#include "util/log.h" + +static int security_rp_blowfish_enc_sub(int a1) +{ + int result; // eax@1 + + result = a1; + + if ( a1 & 7 ) { + result = a1 - (a1 & 7) + 8; + } + + return result; +} + +void security_rp_blowfish_init(struct blowfish* ctx, const uint8_t* key, + size_t key_length, uint32_t seed) +{ + log_assert(ctx); + log_assert(key); + + memcpy(ctx->S, security_rp_blowfish_table_custom_sbox, sizeof(ctx->S)); + memcpy(ctx->P, security_rp_blowfish_table_custom_pbox, sizeof(ctx->P)); + + blowfish_init(ctx, &key[seed], 14); +} + +int security_rp_blowfish_enc(struct blowfish* ctx, const uint8_t* input, + uint8_t* output, int length) +{ + int v4; // ebx@1 + int v5; // ebp@1 + int v6; // esi@1 + int result; // eax@1 + unsigned __int8 *v9; // edx@1 + int v10; // ecx@4 + int v11; // eax@11 + signed int v12; // edi@11 + void *v13; // edi@14 + int v14; // eax@14 + int v15; // edx@14 + int v16; // ecx@15 + int v17; // eax@18 + unsigned int v18; // ecx@18 + int v19; // eax@21 + bool v20; // cf@21 + int v22; // [sp+14h] [bp-8h]@1 + int v23; // [sp+18h] [bp-4h]@1 + int a2a; // [sp+20h] [bp+4h]@2 + unsigned __int8 *outputa; // [sp+24h] [bp+8h]@1 + + log_assert(ctx); + log_assert(input); + log_assert(output); + + v4 = length; + v5 = (int) output; + v6 = (int) input; + v23 = input == output; + result = security_rp_blowfish_enc_sub(length); + v9 = 0; + v22 = result; + outputa = 0; + + if (result) { + a2a = v5 + 4; + + while (1) { + v10 = v4 - 7; + + if (v23) { + + if ((unsigned int) v9 >= v10) { + + if ( result - v4 > 0 ) { + memset((void *)(v6 + v4), 0, result - v4); + } + + blowfish_encrypt(ctx, (uint32_t*) v6, (uint32_t*) v6 + 4); + v6 += 8; + } else { + blowfish_encrypt(ctx, (uint32_t *)v6, (uint32_t *)v6 + 1); + v6 += 8; + } + + } else { + + if ((unsigned int) v9 >= v10) { + v13 = (void*) v5; + v14 = v4 - (int) v9; + v15 = 0; + if ( v14 <= 0 ) + goto LABEL_24; + v16 = v14; + v15 = v14; + + do { + *(uint8_t *)v13 = *(uint8_t *)v6; + v13 = (char *) v13 + 1; + ++v6; + --v16; + } while ( v16 ); + + if ( v14 < 8 ) { +LABEL_24: + ((uint8_t*)v4)[0] = 8 - v15; + ((uint8_t*)v4)[1] = 8 - v15; + v17 = v4 << 16; + ((uint16_t*)v17)[0] = v4; + v18 = (unsigned int)(8 - v15) >> 2; + // poor man's version of memset32 + //memset32(v13, v17, v18); + for (size_t i = 0; i < v18; i ++) { + *(((uint32_t*)v13) + i) = v17; + } + + memset((char *)v13 + 4 * v18, 8 - v15, + (8 - (uint8_t)v15) & 3); + } + + v4 = length; + } else { + v11 = v5; + v12 = 8; + + do { + *(uint8_t *)v11 = *(uint8_t *)(v6 - v5 + v11); + ++v11; + --v12; + } while ( v12 ); + + } + + blowfish_encrypt(ctx, (uint32_t*) v5, (uint32_t*) a2a); + v6 += 8; + v5 += 8; + a2a += 8; + } + + v19 = (int)(outputa + 8); + outputa = (unsigned __int8 *) v19; + v20 = v19 < (unsigned int) v22; + result = v22; + + if ( !v20 ) { + break; + } + + v9 = outputa; + } + } + + return result; +} \ No newline at end of file diff --git a/src/main/security/rp-blowfish.h b/src/main/security/rp-blowfish.h new file mode 100644 index 0000000..245948c --- /dev/null +++ b/src/main/security/rp-blowfish.h @@ -0,0 +1,32 @@ +#ifndef SECURITY_RP_BLOWFISH_H +#define SECURITY_RP_BLOWFISH_H + +#include +#include + +#include "util/crypto.h" + +/** + * Initializes a blowfish context for use with a modified version of the + * algorithm used by the roundplug module. + * + * @param ctx Pointer to a context to initialize. + * @param key Key to use for encryption. + * @param key_length Length of the key. + * @param seed A seed value. + */ +void security_rp_blowfish_init(struct blowfish* ctx, const uint8_t* key, + size_t key_length, uint32_t seed); + +/** + * Encrypt some data with the modified version of blowfish. + * + * @param ctx Initialized context to use for the operation. + * @param input Buffer with input data to encrypt. + * @param output Pointer to a buffer to write the encrypted output data to. + * @param length Length of the input data (output buffer must have min size). + */ +int security_rp_blowfish_enc(struct blowfish* ctx, const uint8_t* input, + uint8_t* output, int length); + +#endif \ No newline at end of file diff --git a/src/main/security/rp-enc-table.h b/src/main/security/rp-enc-table.h new file mode 100644 index 0000000..cd5e8b8 --- /dev/null +++ b/src/main/security/rp-enc-table.h @@ -0,0 +1,1036 @@ +#ifndef SECURITY_RP_ENC_TABLE_H +#define SECURITY_RP_ENC_TABLE_H + +#include + +/** + * Static table used to generate encryption key for roundplug module. + */ +static const uint32_t security_rp_enc_table_key_base[5120] = { + 0x31594308,0x0AC350F,0x0E92308F,0x1B10E167,0x470CADE7, + 0x0D461503F,0x2E2FC437,0x28746421,0x89E1510C,0x0C7E6153B, + 0x121086D6,0x9494EDDE,0x0F82C6866,0x29218D01,0x5CA95077, + 0x507427BC,0x0D4826C22,0x8B3702CD,0x0ECB13B67,0x8C59F916, + 0x0A0B814AB,0x0BD8DFC1D,0x0E31CA035,0x0BAC88E8A,0x1BE45BB0, + 0x0D2C745AD,0x0A31E17AC,0x8B874DF6,0x475B791D,0x1BCAE57A, + 0x0CC2E7ADA,0x0EE99E075,0x66B0E720,0x27BD26C3,0x3FFFE20C, + 0x17453165,0x0F6BB5E24,0x0C8039002,0x1E87A7CF,0x750E6764, + 0x0BB8ED8D8,0x0F41ECF7,0x0C9FB63EF,0x0BABBEC4D,0x0B5808D28, + 0x4D5E449C,0x0E1D0EF7B,0x0D650693E,0x2425F642,0x147DE131, + 0x48BB63BD,0x0FB12C795,0x8A53CC91,0x33034B30,0x1DB11945, + 0x99862FED,0x0A81E08C4,0x0BD964B59,0x0C4E7AADF,0x62720A25, + 0x7FDCE4C8,0x21BEBA7,0x0DBD1EF97,0x47D90158,0x4F20D8C8, + 0x92B77357,0x452600BB,0x7802FDE5,0x9BC29D33,0x40CE6DE7, + 0x23D937D8,0x67712667,0x22D42CB5,0x1C06A113,0x0D4202DBA, + 0x0C4EBE7BD,0x0E6ADBE3B,0x774319D9,0x0FB70BA6F,0x8175E802, + 0x28E2CDF3,0x0E2AB1D75,0x787EF844,0x0CF541295,0x69471148, + 0x2EA33665,0x6D3F40C6,0x1E0E8D11,0x6AF183EA,0x6DCF2E66, + 0x3AE82AC2,0x1F66C455,0x2B355CC3,0x0EE150E74,0x80EE9144, + 0x0C5635E7C,0x0EEA91502,0x439A1DCC,0x20058B9A,0x3E4946E5, + 0x34226812,0x3EC2E7EC,0x3C247451,0x0FE9DA32B,0x0BEB44DA3, + 0x0EE333391,0x357EE336,0x0B424E02D,0x8DB4FD9F,0x0A6D20AB7, + 0x0AA89B255,0x51E3A407,0x0E2B7F135,0x0F6C3B59B,0x7EFAFAFC, + 0x71FD915,0x3D93E2D1,0x0AE6CBFBB,0x0D1C613B8,0x475EA8F6, + 0x605DCA20,0x0E171EED4,0x5229856,0x0C1647B86,0x4C68E10F, + 0x0DFB84DE2,0x0BA835FE3,0x6735F9E1,0x424BA0CC,0x39662522, + 0x0D19E84AB,0x69180C66,0x0C3DBC0C3,0x0C0DFFE12,0x6B675E3C, + 0x3991DDB1,0x902C3BA1,0x85C9A16F,0x0EA158763,0x8866D59F, + 0x0A9934854,0x0DA091835,0x0EE1BD82C,0x469A9D4C,0x50A66208, + 0x4EC3ABA2,0x593069E6,0x0A5741E17,0x639412A,0x0B35DE432, + 0x4B47941C,0x1577819C,0x8C61DF67,0x1B7C8BA3,0x2596F29B, + 0x486A2FB8,0x0E37473AD,0x0D504AAF1,0x88925B72,0x2F51D088, + 0x4B067827,0x741B895A,0x56EFE9EC,0x0FA714F68,0x47EDA645, + 0x0C826B25A,0x0AFA4F598,0x1A4FD4F7,0x963DF6B1,0x0E1C9EDAF, + 0x0F5E51840,0x41B1C711,0x3A4CA15A,0x12E94355,0x0C7262AF4, + 0x609AD3D3,0x74AD206C,0x0EBADFE8A,0x81E4500,0x0A851E09A, + 0x0C4382D54,0x3D78A6CA,0x0D9C0C2F0,0x88591603,0x0EDFFC0BF, + 0x19F005D3,0x9A423992,0x0B479DFEB,0x0EE59129B,0x0D1F924A1, + 0x0EC1984F2,0x1BB5E56F,0x9DB9C14,0x0F4B94A72,0x0B0FABC5F, + 0x0EF530FEC,0x0BF581897,0x549503C2,0x8DA3964,0x0A0D688FF, + 0x0D1E77ED2,0x331154F,0x54EE96CF,0x0E004B785,0x40DD06B0, + 0x50738B15,0x35AAA9AD,0x9FE3469B,0x4CCE2C60,0x0CE7FAE52, + 0x8DC58E4B,0x0FB71E9E,0x778FA64F,0x4CBC0683,0x7C329C35, + 0x0A105682B,0x82366E28,0x0DDCD5C64,0x64276D39,0x5928D1F, + 0x6E17BEDA,0x0D197BAF2,0x0E41ADA64,0x2D633697,0x7CC80E91, + 0x0B940696B,0x0DFBEFB02,0x49BD4CF0,0x2D1C17BB,0x662DF04B, + 0x76092AA2,0x0CB26F8C7,0x412BD203,0x0E9021B50,0x92F000F, + 0x6361A3F9,0x0BA477958,0x93A5F077,0x39A75657,0x375F6A3, + 0x0D90784E6,0x0F53BBBFC,0x0E72544C7,0x0DCA6DA23,0x1F40AC1B, + 0x0E028025D,0x359D24EB,0x5E797814,0x4D06EAA3,0x66129056, + 0x83468E91,0x3BB33854,0x64AE776B,0x0D6DE6EE4,0x72935DC6, + 0x6359C500,0x0A4CECDA0,0x0C2B0E445,0x0E43AA8D7,0x8B10675, + 0x8F3AA3AF,0x0FCEE7BF6,0x0F930C950,0x9B3E2651,0x0E607F446, + 0x943DFDB3,0x12AA5800,0x0CCC78D6F,0x0AA8BF851,0x0DA81737A, + 0x0D11D2FF2,0x8C50E3ED,0x1C5A29FD,0x61E02487,0x163E6774, + 0x0D1E1228,0x0BC4A3EB8,0x0F9BE9C57,0x2025812,0x0C0B644BE, + 0x4D6E322E,0x0B3C1A1A9,0x0F79D9D9A,0x56DDE08A,0x0CE1E3D4C, + 0x0E0CE3F77,0x95850D1A,0x0BF9990AA,0x81EDD83F,0x1109BC00, + 0x0BD70C1DC,0x2C2D3E7C,0x0E7B0BD75,0x17D8A3C1,0x91511470, + 0x0F20119B,0x0C07CE39A,0x4E0BD76,0x705E9DA1,0x193673E5, + 0x0FA7859B,0x2705121D,0x0FE0C357E,0x3A740D78,0x13C51BA5, + 0x136DEBF2,0x1910F752,0x0A41FC8AF,0x4EAC5E26,0x977DD273, + 0x0E45F3AAA,0x0C8BCCD29,0x817048CA,0x0C6D9905D,0x0C02D9855, + 0x561185C1,0x0AD67107E,0x0EF672CAB,0x4CF1E95E,0x3F219995, + 0x13253170,0x0A54BF29D,0x6B5E4813,0x0B533F203,0x337F630B, + 0x0B8EB68AE,0x3D6B0C01,0x29CBB9A6,0x0D08B9FFE,0x39EC599B, + 0x244BCEF4,0x4BAE565D,0x0E99D1F36,0x7933CE62,0x0C07469FB, + 0x0DE17443,0x0C04A5ADE,0x9E40F3F,0x0F29BF461,0x0A0A9FBB7, + 0x0D5680B65,0x0BA64A6B0,0x0DCB5C9B3,0x748C1354,0x0EB0B2A77, + 0x9D5A5B72,0x0DEF780C0,0x3A4D27F9,0x189F001,0x4A93780, + 0x99D5F393,0x0E5F3DEC2,0x5A76D731,0x7E76841A,0x0EE063B7A, + 0x0A3C2230B,0x75A79673,0x0DD28CABB,0x0FF7DB102,0x0E83F1D73, + 0x14322874,0x3362D320,0x2C71EDF8,0x632F37D5,0x386BC522, + 0x0D009A747,0x1755C968,0x5971850,0x21E8E6A8,0x4443906D, + 0x0A0DF5EA1,0x0BCAA40,0x517C4575,0x6275160E,0x0E804042E, + 0x0C5241741,0x8A3FD935,0x3A4403E8,0x51F955DC,0x493C632, + 0x0CB84DDD6,0x1D945FF7,0x7A332ABA,0x0AF0D612F,0x56E0C984, + 0x9C8A7378,0x47679C13,0x0F7D9D194,0x0AB2857AC,0x888DC9E9, + 0x0D6890375,0x4C7B3F00,0x8D727EFB,0x0F2402A07,0x8AD0F9AD, + 0x5FB9144F,0x0FB12795E,0x2B899DCE,0x3AF56C7,0x2296F99E, + 0x389EBF03,0x0C58C6E7C,0x23DC3512,0x0C659D749,0x0C3EA0C56, + 0x8FAF248D,0x94CED19,0x0BB85D7EF,0x5D0C5605,0x9B9688BE, + 0x193418A9,0x0B3E1626C,0x957D7FB,0x3B28A30E,0x0F1098ED1, + 0x9F7120DD,0x7630967,0x0FF87C0B7,0x748069DC,0x0AF7AFC9F, + 0x0D702A4B5,0x0B71B6539,0x0C28E0259,0x54811E4F,0x3B4BA193, + 0x76875D4E,0x3C67F212,0x414CEEC9,0x349339EC,0x8CB1B1F6, + 0x84821117,0x64DE172C,0x36EE6EF,0x8ABDA96B,0x7A9279B4, + 0x0F17E81D4,0x29B15F08,0x4111CE2D,0x408E8275,0x57AF545E, + 0x66719FE5,0x0C252EAF6,0x36A9C728,0x4738FAA7,0x0BC04DE71, + 0x5E6505C6,0x0FD591FD9,0x0B31D1DCC,0x6DFF96DB,0x0A66E69D8, + 0x7653A4D7,0x0CCA4A42A,0x0F7357E90,0x6CD29EA9,0x0C38CAEAD, + 0x250BA5E,0x1EBE913B,0x0BF376EF7,0x4438D729,0x7E7C645, + 0x0E2EC07CB,0x0F282E1BE,0x9ACEF858,0x0CB6F72FC,0x8356596C, + 0x94D8403C,0x0A7012A87,0x802CA7E0,0x84D0408B,0x789D15EE, + 0x8BC8C542,0x97A38E8E,0x0A470B6D6,0x0AE77298F,0x0A8566357, + 0x0C09990E3,0x0E58AF239,0x864A841F,0x9D21DDD3,0x0ED1356FC, + 0x87B46FE1,0x933D6FD9,0x4DDE4B02,0x4E56C936,0x0DC2E53B, + 0x0A5B2743A,0x0D9820773,0x55E8122E,0x36C94EF5,0x0CD515B00, + 0x0A042AAF1,0x0B28C9AC2,0x51FE1FB,0x5CFD2E28,0x45930D8A, + 0x57490A0D,0x0B958197A,0x0E4DB35EE,0x0A92A488B,0x75634B6D, + 0x0D449AEDF,0x3452FACF,0x0ECFBB580,0x7C5F867F,0x160899D8, + 0x5E054589,0x6F3CEC37,0x1E022320,0x82E91351,0x0B4D91F19, + 0x0D363C8BD,0x4B51CD6D,0x57829677,0x0C5F5D2BB,0x0FA23605F, + 0x35926BC4,0x18A7DAB8,0x64BCE7ED,0x4720DA9,0x4D4828C0, + 0x846DD8C0,0x0A0D3266B,0x52856A6A,0x433A6C3F,0x9D2AC97C, + 0x0CC209832,0x86D14EAD,0x86CDF5E,0x0A3722619,0x78C88781, + 0x800BD3C0,0x0CF236B7B,0x191BA904,0x752F7BD2,0x6127512F, + 0x6E43A31,0x0BE3949EB,0x0D4FC3EE4,0x8B5C62C9,0x6174B35E, + 0x942141BA,0x0E611D8B4,0x9D1DDF99,0x0CFD98020,0x0DC690A9F, + 0x3C96937E,0x7B21E1EC,0x81568BD2,0x17E55B05,0x0A7EEF7C0, + 0x435BC051,0x0E972FB12,0x6AA2F9B,0x38BECD36,0x58011694, + 0x0B8F13ABE,0x0A90EBD4D,0x41ED1FDE,0x5B88BCC1,0x0DED8F1F1, + 0x43A68049,0x519E36F4,0x2BA5CB26,0x8D6C0909,0x534530FB, + 0x3B369AF6,0x0EB4C9B4F,0x3632ABA4,0x9B02C710,0x0E5B14A0, + 0x0FCB4C60B,0x89F0409D,0x1C327C75,0x1DF2AEF0,0x0FD512465, + 0x77A97113,0x1669CA57,0x0F820B023,0x0D1D7D0AB,0x32A42761, + 0x0C7C6924,0x7049A4B4,0x983C206A,0x2B658B26,0x0BD815288, + 0x95135263,0x0B4B6B06E,0x13B1053D,0x2BCCBF70,0x0BA62C42B, + 0x0C5B858C6,0x0D98D40C5,0x97F2270A,0x705C4249,0x0ACF830BD, + 0x0B13E2419,0x82C945C2,0x8666553B,0x8B6B91E0,0x0A4AD9D8, + 0x0AC651145,0x0E24C7BD,0x0C14516FE,0x9674CADD,0x181CC480, + 0x597CA1CD,0x0F2FD9622,0x45C39E47,0x582D8A1,0x0F9932AA8, + 0x0FC472B9B,0x497B4072,0x0F56C0514,0x0BDCDEECA,0x2172EF3, + 0x1221D7FA,0x0A9F04386,0x0B8D0B4F0,0x64A337FA,0x527ACEBE, + 0x2262CDE4,0x39818518,0x0C46120BB,0x0F98BCCD9,0x0A0581C5C, + 0x0D304AA7F,0x2040285,0x3A99BBAA,0x0A6A6E75A,0x56BFAEA1, + 0x3F8235E3,0x832AC5CD,0x0F361298B,0x0D256573B,0x0DC1053A2, + 0x8702511E,0x89DF1ADC,0x9AAFB04B,0x7B2335D1,0x372509BC, + 0x0A9B4337B,0x3EEF020E,0x0FD6FF3BB,0x0C4DAD603,0x0D4B52DD4, + 0x8F7AD500,0x81EBE8EE,0x9EA5E190,0x0CAF80194,0x9FF6F2DF, + 0x6ACAAB37,0x764C933F,0x8ED2E9A9,0x0BD4863A7,0x588315A2, + 0x3ED2952F,0x5FD65D3F,0x7B947095,0x2CC9417E,0x277DD0BC, + 0x0BFDB16C3,0x0AB40A328,0x8928656,0x0A2D36D83,0x6FF60DE2, + 0x5CF1C41A,0x4C137DF8,0x5E98D560,0x6F7B7C8A,0x0E18BDD6B, + 0x9AC10170,0x4FD1B072,0x2FFDAE7,0x0C9343655,0x0D251290D, + 0x0A4C3E915,0x0A758E266,0x0E7505957,0x15B84F5A,0x0CFF3A8E6, + 0x209A8CB4,0x4B860E2E,0x0C42A0B25,0x802655C2,0x6E1A11BF, + 0x46B85ED6,0x811E387A,0x0A8629BC6,0x0D26EEBB3,0x690E938D, + 0x2C47EDA8,0x7AEB624F,0x0CD6AD2FD,0x85EB35D0,0x0E79B833A, + 0x6545D2FA,0x1F24BE4A,0x0ABF50E5A,0x114F95FE,0x1A3755A3, + 0x0CBF0EB88,0x2C10272B,0x4ED9FA01,0x8BC59569,0x1063CFE3, + 0x9962C77D,0x7CEBCB8F,0x0E93579A8,0x705324C8,0x0C3507AD0, + 0x0BE7D6336,0x0A40A2CFD,0x0ACD4E4E5,0x0BC7E06DF,0x74C359C3, + 0x6E0A1545,0x0C43B4726,0x0D3D276A7,0x3F328743,0x383BDE9D, + 0x0FB1EC6B8,0x9B701366,0x17E0603,0x31E17062,0x0D0521A08, + 0x0E3BF6598,0x0DA9E2E8F,0x0CD81F431,0x5EA3DC4,0x0BE6135FB, + 0x2BC599B5,0x0BBE0D3E8,0x9B3D67D7,0x7C3E8D8C,0x96691F82, + 0x0ED03B6A2,0x0D3E01170,0x0AE79B687,0x0FB3FDB43,0x932F86BD, + 0x2DA7F4FC,0x25783869,0x7C3C2484,0x1FEA6CD6,0x6CA70926, + 0x0F2DFDCF0,0x78959413,0x41F7D1CA,0x8F3689DD,0x6694AC19, + 0x92BF03FD,0x0EC5D5EB6,0x0D8E8EE4E,0x12BCF020,0x0AB6E8B8F, + 0x4E60FCF7,0x0C892F0E4,0x0C7BB3182,0x0E2998555,0x0DA85D42A, + 0x204A884E,0x973739FD,0x9A738A1D,0x41914E2D,0x0E064F078, + 0x0D115118E,0x747272F4,0x7389151B,0x4B769F8F,0x8790171, + 0x4F4C5727,0x0A2B31052,0x0E0545000,0x0BC99224,0x4EF49243, + 0x3F926A6F,0x6113FB79,0x0EDAA8E63,0x0D2A0BB14,0x0FAED8B4F, + 0x0CF09D9E6,0x0FE00FE2B,0x7AC5ADA8,0x0C5C81B10,0x6BCA6375, + 0x0CFF02ABA,0x29177E4C,0x0D068040D,0x0B52C5391,0x30E19B8D, + 0x8D8A8C,0x8D5270E4,0x6F43A1E9,0x34761B60,0x5E5A6837, + 0x0AC4BC673,0x9B648669,0x2B95B231,0x0E6F5F258,0x1F59B0D5, + 0x78257B3E,0x0A762A93C,0x7911413D,0x16BF1868,0x885A39D6, + 0x77438AFF,0x36A4AD72,0x40425CA,0x8B19BADD,0x0ADDD1E36, + 0x80E4D0C6,0x97EC41D6,0x84B63640,0x9A146DDF,0x0F8448344, + 0x0C37C15AA,0x0B2C6262C,0x0D00EC036,0x7D79DE3A,0x0B6F98AA4, + 0x971F430F,0x1E30A1B9,0x327A3C34,0x0E4E8C95F,0x0F4D58695, + 0x971ED924,0x777B3301,0x0FC0F3BBA,0x0CA3D29AB,0x8BBE6F75, + 0x0EEF09FAC,0x0EF7189D0,0x5AEDA37F,0x8A34AFE8,0x77909783, + 0x0F0559C00,0x24B9AF7D,0x67E61FFB,0x334E7513,0x6D3F9DE5, + 0x0EAB74652,0x32798A6B,0x8460D228,0x1AF4F45E,0x0AD3B9FEB, + 0x3CD2F933,0x0A3B86D1,0x0E6794C86,0x0AEDA3575,0x1615B493, + 0x0A694AB59,0x2138DBC,0x6E6AC05E,0x91A74506,0x79609A4E, + 0x0E048DF9B,0x0AEFF3952,0x0BF2F7449,0x0E5D4EC7C,0x31DBAD02, + 0x6EEFD93F,0x0EF904A5A,0x8B6579F1,0x0E5D39E0C,0x0F1CE1950, + 0x0B3EF1176,0x4AC75BFB,0x2E749D18,0x0B671B3F4,0x0DDB35119, + 0x45F1EA24,0x7C43D1C1,0x7FEE9FD7,0x7C7ED400,0x0DC16C13C, + 0x8105A5E6,0x4C9916E7,0x0E338A229,0x0B1ACB74F,0x2EBFC0A3, + 0x6008914F,0x0A20405D1,0x0A469E5A9,0x0B3B60C54,0x3A0FC97E, + 0x89488574,0x0D640A6D8,0x8170B398,0x9DC3B820,0x0ABA8ECCF, + 0x0A86691A8,0x4BB11C4B,0x887A9623,0x580B42E0,0x0BC528028, + 0x0FF7DF189,0x3CC8DCB3,0x2193D0E4,0x0F2B88BA3,0x0D11B1BB1, + 0x3C83433D,0x0D7A82057,0x6C8D0CA7,0x300FC35F,0x48BEC56E, + 0x8BEDFBF7,0x8A059C01,0x0CD1F5270,0x60D19934,0x8F4768C0, + 0x0ED9614BD,0x9B4E7100,0x0C64E3ABA,0x72DDB3EF,0x76F68C2A, + 0x0C8E2096E,0x0FB0D606F,0x6126302,0x47185ED3,0x0C8614356, + 0x0C0200400,0x5D8B41B6,0x0C0322386,0x4A8F8895,0x16DE6058, + 0x0C62F5607,0x83B9B44F,0x3F718048,0x3DDBEBA6,0x0D420ED33, + 0x7062297D,0x0D94C16CB,0x0B8ED6056,0x57C68CB6,0x0A821DC9B, + 0x89A572BD,0x4628B716,0x5FC4FE4E,0x8C2D6577,0x0FF41FCFA, + 0x0E9E12AD1,0x3F004CF7,0x0BEF99E1D,0x2D265F9F,0x0E2AE30B6, + 0x879EBEEF,0x1E3CA0DC,0x43988309,0x0B1638431,0x804DDB2, + 0x0CEE9C43F,0x0B31B90D5,0x19161EF0,0x0D3D773FE,0x2D36A214, + 0x2D76F0E0,0x191B37DF,0x39FB88CF,0x0DE981468,0x0A4AD4D47, + 0x0F307482B,0x0CC966663,0x0C2C43387,0x46058E67,0x28B00B41, + 0x5B0D9537,0x0F9AF58FF,0x8604DEDD,0x792A77CE,0x0F804DF06, + 0x0E48E1B9D,0x156DA486,0x0E7CBC7C0,0x0F5614CD0,0x24D5566E, + 0x0E4478A7F,0x0B2277343,0x0E14925D0,0x9B3B21BF,0x23D67925, + 0x5C1335C5,0x8F20F483,0x69B3D71C,0x439F9A15,0x0A7A504F1, + 0x0F8C82A7,0x0EF730F53,0x0F667562C,0x91311BB6,0x0B271D739, + 0x0CFF1A271,0x2D335687,0x5E4CF240,0x9F53C75,0x0E9E0ABC3, + 0x18488383,0x8ECC42FC,0x0E47D3BD8,0x5E3681D5,0x292C06BD, + 0x0E1C50498,0x57AE9C20,0x9028BE75,0x11A24812,0x598D7200, + 0x0B06C6949,0x1F2A3AB0,0x0C0B1F29F,0x38B80361,0x81DCEA91, + 0x0EDF30FD3,0x679DEDC2,0x0FC1B7229,0x9B65A975,0x1976966A, + 0x78EB6216,0x69CCB803,0x0FAA6EB1,0x6EA6843,0x0A460B979, + 0x7F200CDE,0x2F904343,0x55C25D6B,0x0DC019D08,0x7EAAE8E6, + 0x8A3E7067,0x0E5B48F2F,0x4F14F520,0x0E8410689,0x0F3167E9A, + 0x0D9B5571C,0x711CEC5F,0x7EFA5E73,0x7AC0379A,0x92FA4DFE, + 0x0F1E0EAA0,0x41282991,0x6F1FA364,0x0AFF84BE3,0x0C1649701, + 0x7263E110,0x65590C34,0x13646A18,0x11EDDDDF,0x927D3D5E, + 0x2CD2FA88,0x0DF330329,0x5300E6D8,0x618D3826,0x0D530371C, + 0x7299AEE5,0x376219C7,0x0E1E70A58,0x0B158CBEE,0x6F0D4557, + 0x0AF1920CA,0x53212C1E,0x516F003A,0x0B742E1CD,0x0EC68E53F, + 0x380F57E7,0x86D95E7E,0x692EDAD3,0x5ED68FC1,0x51C7855E, + 0x623BAE76,0x0E806CA38,0x0B627882E,0x9E9DEE73,0x347CFA20, + 0x0D43F8A07,0x0E76079A4,0x61260B50,0x8EBF8BB8,0x0F8E328E, + 0x20C64B7E,0x1C389564,0x4269E9BC,0x0B9E81C59,0x0D2DE275B, + 0x8EE7855B,0x6020DDE8,0x3382E137,0x0AF6E7616,0x0B8881522, + 0x38C9713A,0x1FD05A33,0x0A879E1CD,0x0DDB1BDE6,0x5D89EDEE, + 0x5D899F9B,0x0ED4651DE,0x7C363615,0x9FC4DB82,0x0EA00AFC, + 0x0EF59EFE4,0x582D795B,0x0F2003B2,0x944B3422,0x617723BE, + 0x70EABFA5,0x0FE816F7C,0x8FFFF51C,0x32A59887,0x6038221, + 0x0FD0D671D,0x0E0716735,0x9328369E,0x9B4B7A3B,0x0DD297411, + 0x0A896E3FB,0x0F686259E,0x0EFDAA0A0,0x0AF796015,0x49A3013A, + 0x0C82D066,0x5022D3D,0x0C4E33427,0x630D9C01,0x0C523DE12, + 0x1C5A9A40,0x0AE8C3783,0x0D888CB9B,0x53AD3AFE,0x0B6B7A115, + 0x3CD8F5A7,0x0C80BE796,0x26A50BC9,0x942C3A69,0x816D3550, + 0x94C98ABE,0x0F0D0BC4E,0x0B7119E29,0x0CC2F007A,0x0DE388D22, + 0x0A233F4AC,0x5FF84683,0x0B0459F62,0x860D090D,0x6C139D3F, + 0x0FB8ECE3,0x0FD119D86,0x0AD3D570D,0x0C0F6E1A3,0x866587F6, + 0x0C43BC0A3,0x0B4FD0FEE,0x4E9F27B7,0x0C65552AA,0x56A613B5, + 0x3DC20ABF,0x8151997,0x101CC52A,0x4478DFFA,0x284163CB, + 0x1E9CA09F,0x0E59099E5,0x5F15ACEE,0x976E71A0,0x2B9E56E, + 0x5C5C888,0x0B823434E,0x0EA80CE0B,0x653251E0,0x721089FE, + 0x0A087AE18,0x0C2EA5319,0x370A8D12,0x6E075776,0x0A8653589, + 0x621816,0x0A9878260,0x787EEA5C,0x0A223621D,0x0C6DD7790, + 0x2E2F5969,0x528A3C5D,0x9D66FD92,0x74910954,0x75C67B09, + 0x0FC818467,0x0EC1210E4,0x0ACF0A66E,0x6F51905D,0x0B9F73DA6, + 0x1F4DE351,0x4EB2682D,0x4E1583C2,0x8C21F87,0x67B0055, + 0x7BCBA81C,0x83977CD5,0x0ABF623B7,0x0B4432FAE,0x9470FF09, + 0x0BE9CE372,0x0A3E88224,0x79877958,0x3B154500,0x0F12A63B8, + 0x312EB5FA,0x0E66F2891,0x50709450,0x4C85E403,0x0D69C76A6, + 0x0CC5EC5D5,0x0F4774584,0x41318EF0,0x5248BDD5,0x3CFA17E2, + 0x895DF06A,0x81F5CC5C,0x0A886C375,0x87272AB4,0x0AF986F0F, + 0x0FCD54564,0x17EB01B2,0x3E0B4489,0x48DDDBBF,0x0E316E365, + 0x214D31F9,0x330BF1DD,0x74218C08,0x0A93ED0FC,0x86C04BF8, + 0x75CBF76E,0x94981DB7,0x0FC107306,0x499A879B,0x57316139, + 0x49B964D9,0x0D09376A0,0x0AA6F610F,0x6861787A,0x75387DBB, + 0x540BC227,0x29148FC1,0x7BC1C4AE,0x3809BFE9,0x0F9FD8038, + 0x889F0B5F,0x0A0F60E93,0x0F362C431,0x712E19B0,0x0C8640FD5, + 0x27E56025,0x48B865A0,0x0ADA336AC,0x2AF81350,0x0A3B203A2, + 0x17C0BA83,0x0DDA1C089,0x2E31D03D,0x0E4C37F87,0x816F1E66, + 0x73AEE0E4,0x9724404A,0x0FBBC9D90,0x0E8FA2717,0x208DFF9B, + 0x64249A63,0x398367BA,0x0ECD6B1A3,0x0D445C6C9,0x0D7C859B5, + 0x2F3D2543,0x6EB8D357,0x0C01A1ECC,0x71E535AD,0x0AF4E60A8, + 0x9094E7BF,0x57913041,0x0F38F27F9,0x0C85CE2A5,0x0B2A186AA, + 0x476F6405,0x5D1B6885,0x0CD4CB437,0x76528822,0x84B96535, + 0x0F21E727E,0x4D421E9D,0x0BB5D0777,0x3DB51B2F,0x316EFC52, + 0x1BA4A951,0x0A4B75C35,0x0E3E6AE8E,0x0DC2001AC,0x46161F12, + 0x92951F28,0x28138930,0x0F488BA7B,0x217F89DD,0x246C2A5E, + 0x0FD405332,0x0B93B9EEC,0x3C012FEE,0x3DF19624,0x92B2F6F2, + 0x0AE0E676A,0x68049AC3,0x0FE18BC06,0x59EE9C0B,0x92170FA4, + 0x0B62B9418,0x0C61834D3,0x2B9AC5B,0x67ABCE86,0x775C25EB, + 0x3864D896,0x7F19D000,0x6E5D1D3E,0x3CBB9678,0x34B3C09D, + 0x151F255,0x2805B637,0x0D6AE713D,0x0DCF84774,0x0F8E839F8, + 0x56B59020,0x56DA7FF6,0x906904EA,0x16A20FC7,0x0F8C4E6E5, + 0x0E23C8A1,0x0F17BD00A,0x4B841DDD,0x51C630B4,0x8DAF957A, + 0x0E1E3C622,0x0C9D34C98,0x0E09127F8,0x0A6E370FF,0x80963DBF, + 0x14C596,0x6739C560,0x686322EC,0x2FC8CFAD,0x0A20DF1B2, + 0x0E71441D8,0x2615B23F,0x90F055FA,0x0A0C1790B,0x0A0B5168E, + 0x701D6C34,0x83C1E7F3,0x2A7746FC,0x15F4FCEE,0x16E1D649, + 0x2932DF25,0x0B2B38124,0x5E3ECA5,0x2B06BC3A,0x0E476D55D, + 0x0E83A925E,0x72D920A0,0x0FF702405,0x5303A4A7,0x0BE1124C9, + 0x9D6B0F0D,0x234659E7,0x5A8E6351,0x647D1FC0,0x76D7457, + 0x69EAE45B,0x170C6CE8,0x4C03ABE2,0x71F4472D,0x0E0048D21, + 0x0EDB25738,0x2769330A,0x0DB55C17C,0x0DA775D34,0x7CF4FE50, + 0x0E5B35E57,0x87265C6F,0x0E8689ED1,0x0A6897A7B,0x0B8261724, + 0x0F63CCF75,0x0DC3DD876,0x8B642447,0x10468510,0x0E9AB153A, + 0x0C59BD5E0,0x8CBC9480,0x9FDB13F7,0x61C866AC,0x0F6679D0B, + 0x4B04A435,0x56E768F5,0x9F283BF7,0x0FFC57A34,0x0A7F26DB1, + 0x65B06E69,0x23A14E87,0x0B11BF4D9,0x0C47B4782,0x3EBC4FEF, + 0x0AF48960C,0x1C0DD5B7,0x0D4CC75,0x91CE7263,0x44704E70, + 0x938125CA,0x0FE72D596,0x50ED81E4,0x23B0EEE2,0x0A69C2D4F, + 0x9E097B33,0x0AB5D65CE,0x0CDDD30CE,0x26C377C5,0x29016D9, + 0x14A347BE,0x20B0BE1,0x259ACCE6,0x8A3A4055,0x3C849090, + 0x0C58FBA09,0x0F20634B1,0x0D77ED2B4,0x1306E963,0x55FFB571, + 0x1F2EF863,0x0CA0FE442,0x0CB6C3B93,0x342FB589,0x7E75A278, + 0x85E3D08D,0x0D43DAFBD,0x2334B3FB,0x1E80FAAE,0x682F2F62, + 0x0E03DAEBD,0x2364E8B5,0x52350901,0x1568D9CD,0x0DF71E0B2, + 0x7454CEE5,0x0A9B91AAF,0x6B43E81D,0x31E30F8,0x98D71DF7, + 0x0F671B334,0x8EB5B5DD,0x0BDC9C62C,0x5104CE99,0x49019F4E, + 0x0DCF2D9DB,0x0BD370A2B,0x9E301AB6,0x0F34FE7F9,0x0F8712C23, + 0x0F56EA712,0x0C0EE7B7D,0x847FD373,0x0C2E5C804,0x95B1853E, + 0x3A16A85B,0x0CFF7EC38,0x5B440509,0x0F87CC4C,0x0CCBA9CFB, + 0x0E55CFC06,0x25C47E00,0x14B4E718,0x76318F4C,0x15920BD9, + 0x0C0D80AF6,0x943D7DC1,0x7A10FB7A,0x0F1BF63EF,0x0E36C337, + 0x0C06778A3,0x592A97F2,0x4A488CC4,0x2ED20152,0x0BB8025B, + 0x0D4965E62,0x32D15016,0x83E35E12,0x24F382C6,0x0EBA988B5, + 0x0F840B7E2,0x0AEDDB384,0x0FC1FA201,0x0E1F78E1A,0x2FBA0A63, + 0x8F7419F7,0x0C5804B69,0x355529FE,0x0A6A3D319,0x479FE8F6, + 0x0EF9AA69B,0x0A9D9530C,0x70A1DBBF,0x3791BA4F,0x2D351C74, + 0x3ED74333,0x0DD982A52,0x1C0680F,0x6D535612,0x35E5749F, + 0x7EAE0910,0x85E10A83,0x0E33A3DD0,0x0FD69EBE,0x24480276, + 0x0E4E9FA37,0x0FD6FFA4C,0x8CC2BB3B,0x0E00BDD40,0x800DD1F9, + 0x6FB8F663,0x0AEFB0205,0x3DBA46B,0x0F9C268D6,0x2819DA32, + 0x0B618EE48,0x1EDD9F35,0x30BCD71B,0x5AD78F16,0x25EE3772, + 0x278581B,0x47F27854,0x75773EAC,0x0BC91D62E,0x0C14F97D9, + 0x9E97E350,0x2ABE4FD2,0x7C56046B,0x0AA47666C,0x0D622F31A, + 0x6CB16DA3,0x0A5D33B56,0x50860D12,0x0D146BFFF,0x6A96807D, + 0x0B9DC335A,0x82711548,0x0AFF9A08E,0x95F0B0FD,0x7986E526, + 0x53AD48CA,0x0D1703192,0x9F866800,0x0E6278AA6,0x131EAE96, + 0x0D9204919,0x795C4FA4,0x41539B07,0x52EA91E8,0x0A7BD0270, + 0x53B74F44,0x0AE0D2B7,0x0E375793E,0x5C3EB424,0x9F1C927E, + 0x4E22762,0x0D6653054,0x5AD7FA01,0x0D507F30,0x2EAED2F5, + 0x7C9FBE27,0x41FCAA15,0x8F59C075,0x0CADA4F9D,0x66486DF6, + 0x0F263E0AD,0x567A3FAB,0x593A54C8,0x66C4C53B,0x8F02F355, + 0x0D23A2370,0x9BE3DC22,0x8BB88FB6,0x780C7EDC,0x0B55DD69F, + 0x932B1E98,0x270AD561,0x4EF7574D,0x0EFE80156,0x80A79557, + 0x0CCDFE07C,0x0F3779AF6,0x0B2208DF3,0x0E42AFACD,0x499C3881, + 0x87839E64,0x6E8AA311,0x80CB44A6,0x0B1E5A630,0x6B4FFD63, + 0x0D5ECAC8F,0x55DFAECF,0x54A03484,0x414F8F01,0x0D8495188, + 0x0A4FAAF76,0x0C2F22ABA,0x0E93A6F8B,0x0A7E87B5B,0x0EDF10206, + 0x0CE3F1250,0x8403F48E,0x0B44D53A3,0x0F3DA9F2E,0x882CB402, + 0x73E0B7D7,0x0AF3D413F,0x0B509C0DB,0x41A01ACE,0x58479372, + 0x8ABDF04A,0x7513DC38,0x8ABE8CF0,0x14EB9FAD,0x75124823, + 0x0B3CEAEB4,0x34EC93E6,0x0C6C13415,0x0E3C17165,0x324E2EFB, + 0x50CEF96D,0x0D100ED76,0x8290D6EF,0x0F1E996FA,0x304AC781, + 0x0D51CA1DF,0x45831EDC,0x33335DDF,0x6385445F,0x0B4C96A9B, + 0x5DDE348B,0x77013E16,0x0BCE3FC85,0x8EFEA138,0x3B283F97, + 0x7F69304C,0x4A0AB6B0,0x0C4EEDE82,0x8E26AADF,0x4BBF6E6E, + 0x60DE7BDC,0x0F710FB88,0x4ADB1C7F,0x1E9971AA,0x8D869546, + 0x9171195,0x9E8D7FD3,0x77CBF06C,0x0A4648C6C,0x1BF97837, + 0x0FCC2FD7A,0x196AE661,0x0B520290B,0x8CEBCA3B,0x143DFD50, + 0x3CC8B77,0x119D761F,0x0FF61E0AD,0x0D8052971,0x778558D9, + 0x4901BCE5,0x5412D3DB,0x785E6A3C,0x0F56A06F6,0x2EB083DB, + 0x0ADF2FB52,0x66CDE94D,0x40918E7B,0x0D15094BE,0x6935F2DF, + 0x53170F81,0x574E2A53,0x83C6F98A,0x2C52908F,0x2F4285F4, + 0x7C3752AD,0x0D636FA7D,0x0D4FCF0AE,0x2C913405,0x3020BFF5, + 0x99062113,0x842969CA,0x0BA8B4550,0x341F6AD8,0x0DFDB360A, + 0x9C0E94B0,0x8BF421B2,0x89878D44,0x0F799435E,0x0BE204C6C, + 0x8FD17048,0x70F09F65,0x7466904D,0x0C914A84D,0x0F8671D6E, + 0x692C5BA8,0x26A6A753,0x0E0DE02DE,0x373B50C3,0x3652B9BA, + 0x1DFF502E,0x64B5F2ED,0x0FC127123,0x0D8B7F289,0x0AC5694DA, + 0x0F50C5387,0x39F22BAB,0x90EF8041,0x63D0BD95,0x749A3CF8, + 0x1E1E66B8,0x0DFCF1B4F,0x13D154DB,0x25206CE,0x1D1D4AE7, + 0x856CBC61,0x0CDFE206A,0x16A4CD6,0x0E7B34215,0x841C9C62, + 0x0E23E2D3E,0x1157E51D,0x6CE5F55F,0x1D743481,0x0E4B4C195, + 0x14CEEAEF,0x0DEF84D22,0x0D1483D2B,0x0A2C568EB,0x29C4B6DB, + 0x0AD7274FB,0x61ADB10B,0x2E18E8FE,0x0B36EE0AC,0x8B16D1C7, + 0x0CEF8C810,0x0DA904CCA,0x5340426F,0x68EB0EA3,0x58BFFB66, + 0x3552DD8D,0x0E9F1F573,0x763514EC,0x82D6037A,0x12C423C4, + 0x96745143,0x26780C3F,0x0B53DAFA,0x818BE72B,0x0BB00F0AD, + 0x287B5F79,0x0F58AB8D2,0x0D38C33BF,0x0FA08ABBE,0x6D43B6B7, + 0x8011162F,0x98EB50BE,0x33429AC3,0x2813FA56,0x2FBCAD7E, + 0x0A30EC6A4,0x87A31748,0x0C87154F3,0x0C1A0766D,0x4995E2F, + 0x5860BB1B,0x0FE232D6B,0x3913ABE8,0x0D72225C,0x46ED5E49, + 0x0BC2D2EDB,0x0D6A9C31C,0x4DBE5A67,0x32011B1F,0x33D440A0, + 0x1A377976,0x96E18FCE,0x3E904829,0x0D39D8C58,0x0C5D3C5A4, + 0x0F9818C4D,0x0CACE8136,0x4E4D7ADE,0x0DAD1E192,0x0C88357E1, + 0x77319D51,0x94E9B550,0x98C7446D,0x920B3BC8,0x276EB6C7, + 0x0D7B7250B,0x8289A9A3,0x2A7FC17F,0x0FA7B2527,0x873AF2AD, + 0x572C08DF,0x0A083B0C2,0x518A833C,0x56398711,0x18069A11, + 0x44FD148F,0x0CE13A814,0x35B88B55,0x8ABD863,0x0A9123422, + 0x51CAAEFC,0x54FCE8D6,0x0A7F17C43,0x9F2799F8,0x0FBA4ED81, + 0x258AC832,0x0B5EC7E5F,0x37E00DD0,0x2ED5FF6F,0x59279244, + 0x34F117A3,0x0C22398A4,0x8AD4BDD4,0x0DFD9F02A,0x6596C03D, + 0x0D11483B3,0x0F05440FD,0x0EBE3CC42,0x0C9AF3298,0x3519587A, + 0x824BE077,0x0ECCC4D27,0x21516763,0x0D7E0B5,0x0A0EE370E, + 0x9458DDB7,0x6DD2978A,0x81322362,0x0EBB21ED0,0x0D38C2510, + 0x0F0C73B3B,0x4A506ABD,0x454CAD0A,0x0DBAD0E8D,0x29030CE1, + 0x2D953F44,0x0CEB13F49,0x1C40C1CD,0x0DA9D032B,0x3AA86BB4, + 0x0E7166B59,0x480BA7AB,0x4EA5A06,0x0C16CF508,0x32EF0A48, + 0x50116945,0x0E77E899D,0x55062980,0x8EF33663,0x6499F0F9, + 0x62E495F,0x0C6DC8D97,0x1F154837,0x0F6276562,0x201093F8, + 0x2593ED0A,0x438BD991,0x0B680305F,0x3778A055,0x0C30F4FD7, + 0x9DCAC17D,0x96A9FC0B,0x8CFBEAA4,0x3176FE3A,0x0D871A49, + 0x0C7E7B1C5,0x0A06F2951,0x402D83B1,0x0B5B73D82,0x0B9BF7728, + 0x33E85609,0x1D8A6F9,0x0F58FC3F4,0x1DF7B913,0x48BCAAB9, + 0x0C55D700D,0x6E8484AE,0x0E6931EA2,0x1E7F9E8F,0x1EE12B2D, + 0x24A9AFB,0x42D79131,0x3706ECF6,0x0DBC85FD4,0x0D1D85D69, + 0x0A54A395F,0x54678C9E,0x0FB8D6BF,0x3C6168C4,0x0BEB18107, + 0x76F6B771,0x99698EC,0x0E45B921B,0x7E0F1048,0x0DD49E69E, + 0x5C86EF98,0x0AB7BF4B9,0x17AAD07E,0x0C34D291,0x0D8EE633F, + 0x0B1B6E92C,0x0FA08EB45,0x0C2CC75F7,0x8E72BE9E,0x58410841, + 0x0D7ECC87C,0x16707BD,0x0CFF808B4,0x40912B04,0x0A05D113F, + 0x0A990111,0x2CA58ABB,0x4A5B6AC3,0x859FAFEA,0x5E371C5F, + 0x76DECE32,0x9793200B,0x0F837CB1C,0x0BD575254,0x0BE479DD6, + 0x8970E3A9,0x0AAEACEB1,0x2A4EDADE,0x55C5FFA3,0x0C0658FAA, + 0x8BBE61C6,0x0E4AF2F29,0x0CC7F3DD7,0x1E283B5A,0x0CDF66DB7, + 0x6D5109A2,0x0F5D7E1EE,0x0C3AF4147,0x0E0191B23,0x864961F6, + 0x0DF75B3A5,0x102D3A3E,0x76EBD4E6,0x2FEB7113,0x0DD3CBDFB, + 0x0A618FF4C,0x8071401D,0x0ABCCB124,0x7E52472A,0x6724A8BD, + 0x2EF04A25,0x79C2D999,0x951CDAB2,0x73488F1F,0x0F1ED1A9B, + 0x5BE1E41C,0x323E404D,0x2CB84C3F,0x7B2D185B,0x527E0B9C, + 0x0A69E82FB,0x32E8B92A,0x0BDB6EF84,0x9F2FC545,0x8260E8F6, + 0x668FEF2C,0x0E8CB8772,0x0C3C8CD84,0x97EAFBBE,0x0E9A047D3, + 0x6BF308C2,0x7E5F1B06,0x0F6E1850D,0x1D5159EC,0x0FAF2DD51, + 0x0D348E8B9,0x0F0298EE3,0x0A217FF84,0x84CDAD3C,0x118ADC9, + 0x16E8607B,0x59A752E7,0x3BC962E0,0x8AA524A3,0x3A838352, + 0x64F6ABA4,0x8F6C26DA,0x2F0344F5,0x6B9EC126,0x2339A286, + 0x32796007,0x0F08748AA,0x0FB1F22F3,0x38E4119B,0x14F7BA84, + 0x10C7A8F2,0x7C2BEFF7,0x82AE102F,0x67291DAE,0x0E981E37, + 0x0BE26B2AE,0x248AFAD0,0x9C96B222,0x0AC0DA127,0x0D1B634D6, + 0x7FAE664B,0x6302E2BD,0x0EB7D6CB0,0x8BF7D6C,0x338F2DAD, + 0x0AE73569C,0x0C7DF7FE,0x0F367D6AC,0x21E36A43,0x0AD2CF71C, + 0x8FE1FB7F,0x6313C913,0x6A9D7499,0x0D2B3EEDA,0x37BE71DE, + 0x69671E5F,0x70F2E57D,0x0CDCEA4B4,0x36A930A,0x564ADF8D, + 0x0D35995FC,0x9081C2C5,0x3579D92F,0x0BBDE54D9,0x74869E63, + 0x4C11316B,0x4EC0FBBB,0x6A8B07C0,0x756D593F,0x71031A43, + 0x0F55F55A,0x72F4C000,0x36455F5C,0x0B11CE52A,0x788CFDF9, + 0x25FE8996,0x588188C8,0x0FC613D3D,0x0CCFB8EC5,0x15CFAABE, + 0x0BDD7EECF,0x84180ADA,0x8A765F2B,0x12CCACF6,0x853CEA00, + 0x0BAC4749C,0x74126BD9,0x2C9A59FA,0x11E51426,0x4E2CE661, + 0x8B25EABB,0x0B61DB9C7,0x347464F,0x31540746,0x104356FE, + 0x407B169C,0x3B1799C8,0x9581C1A9,0x83426610,0x9B18F8F3, + 0x0DA49661E,0x0EB383E25,0x0A636159D,0x0D89A298B,0x4214451D, + 0x0E43EE598,0x7B749B92,0x48E6B161,0x13EB10D3,0x70996020, + 0x4192711C,0x7C20DAB1,0x2E85E08D,0x0C08C9918,0x796451A9, + 0x45AE2CF9,0x0B5D50DD8,0x469EB71E,0x0E60032E5,0x0B43571F5, + 0x8113180,0x0B3932410,0x88BA4FBD,0x1B99B0A3,0x0C8AE2593, + 0x0F7708909,0x9F27205E,0x900353F,0x0D761015E,0x447ACF67, + 0x0AD1D5E36,0x51CF8747,0x551F1F6E,0x0B361FC8,0x73B002F1, + 0x2AA6F77,0x0A31C188F,0x0FC6AE207,0x0F235467C,0x6F77F7CD, + 0x62CFC5CF,0x819C143,0x6B42A205,0x285A6786,0x74EB3E7B, + 0x618AA5D7,0x5EABCFF7,0x0FDBA491D,0x0FA67DD20,0x7841B762, + 0x8D86C601,0x1396552,0x53763B8B,0x0FD035FBF,0x0F927C010, + 0x84BFC520,0x228E30C8,0x0E2D5480D,0x0E323374F,0x176FB1C4, + 0x4764D625,0x58FC5AA7,0x0CC50E32D,0x8CAFB2BC,0x0E4ED862F, + 0x0CFFDBD2C,0x75C2BD56,0x0F22392C4,0x595ED8B3,0x0F89EDC77, + 0x0DECFFEB8,0x9BB15ADA,0x472BA7BD,0x0C6E35EA7,0x490B1F7E, + 0x187E523B,0x900B0A9B,0x67102E17,0x384BDA17,0x3BEE0363, + 0x53F15ADA,0x4FAC7366,0x69A5272F,0x13A7370F,0x0F4143147, + 0x2D7B9870,0x0E06D3FB4,0x0F38DF63D,0x11E768EE,0x0F2834451, + 0x0DD399BD1,0x6BC68A2A,0x8D20181D,0x0D0076168,0x0E0DDF5F5, + 0x4BB77B52,0x88B49A61,0x388E1951,0x0AB6E44CC,0x0A6059272, + 0x64D88687,0x0DADBD3E8,0x4042C344,0x0CC94D988,0x0C101B69B, + 0x0A8F5394C,0x0DDEDEA89,0x4E8A99D1,0x80E342EB,0x0D32034D9, + 0x6457208,0x0FD4B5AC9,0x0C2758405,0x0CBE1ED2B,0x7B5E556E, + 0x0EA7DE52E,0x0E9EB15B3,0x41FDCD21,0x3C8DCAA8,0x670544FC, + 0x95B9CE05,0x277A7DD7,0x8B674CE4,0x307BF70,0x0A895C746, + 0x0AE9BE6AA,0x0E9C3938E,0x90E9E009,0x42745A04,0x46E73137, + 0x17B39954,0x20506F81,0x0C48B200C,0x0A122C75A,0x168E9521, + 0x22076D7,0x0CF4FF16B,0x0B0515032,0x26E80224,0x0C97F3D48, + 0x4575E86D,0x0A3B9B71B,0x0CA9798C9,0x45D14E50,0x46F25BA0, + 0x0EBDF24B2,0x0C1AF50BE,0x87BC71AD,0x34FAE6D1,0x3B8800D4, + 0x0D8563EE,0x0DF25AF5B,0x0AE04600D,0x81BAF39E,0x0EFAF4B88, + 0x0E0315298,0x98C3E3A0,0x0F1B8E86D,0x0E701BFF8,0x5A44E3E0, + 0x102EC818,0x0FA0906DD,0x0B891BAEC,0x5E0329F1,0x7978A742, + 0x4D71B8CD,0x61B37450,0x3E5530C8,0x73175A2B,0x0DCF5A559, + 0x24FC6650,0x895D3EA0,0x0DFC0FA20,0x0D8DAB7DE,0x82424A5C, + 0x1180DFF9,0x0DF67DFAD,0x0B1A618F9,0x3C99181E,0x0E865D98E, + 0x0D343A4A0,0x191C2F87,0x53570985,0x5DEF375C,0x5DC82006, + 0x43A6A5,0x71096BD,0x34342A1,0x5CAA6C2B,0x985E66B0,0x0DA997833, + 0x0A9C782D9,0x0F3E0E29D,0x52F49845,0x8D08A895,0x6220C0C2, + 0x839D1B2A,0x0DEC9A541,0x21B160CF,0x7E330659,0x0AC57F0DC, + 0x2EE336C2,0x0D729180E,0x8E249DDA,0x4FC37903,0x72853423, + 0x304689BF,0x63590DC2,0x8DD6112A,0x1C35C802,0x0EC1CAD90, + 0x0E761FCF,0x0C8C54E20,0x0DCB95B3C,0x0D01BB6D,0x0E061DDFE, + 0x0A0050CEF,0x0A41596EE,0x0D5892E83,0x6A3F8F8B,0x0F64A62E7, + 0x0A58E6275,0x0C28ABE3F,0x7D74BEF3,0x0EC8BAA94,0x4FA9E86E, + 0x9817644F,0x2BA635F4,0x0E2FB7DC2,0x542B90B7,0x5788599F, + 0x0C3BAFA8B,0x7911B37F,0x0A419056B,0x3F6F175C,0x0D8D458F4, + 0x93806618,0x71B82FE8,0x0D2A356F4,0x395AD8AC,0x503EEF19, + 0x2C8E36CB,0x0D1390E13,0x0F9F04172,0x1180E650,0x855F85F1, + 0x3D847BA6,0x687C9F40,0x78BB25CA,0x6D30C378,0x571C13DA, + 0x16233955,0x65A4C5D4,0x1746DBBB,0x9FD3911D,0x0D64A982E, + 0x0F92F1FFB,0x0F126F458,0x0D8C0EC1A,0x0B8958687,0x9693CB0B, + 0x0AF95782D,0x1375FC0,0x0DE90B5E,0x0DC43A312,0x40991055, + 0x5CCE623C,0x666D70EF,0x0ACF5BF5D,0x0D575A62E,0x6A59AFF9, + 0x95843FB8,0x289F7A79,0x0E0B06156,0x0ECEE3DA6,0x0A9D44175, + 0x0B0746B32,0x120EABA8,0x0E2E14730,0x0F4417E24,0x0E5EA6C96, + 0x5B942D42,0x8EC745C2,0x8F13F01,0x0A9B695F2,0x0ED87D082, + 0x7276F0CB,0x0B4480E8A,0x1BCC40D3,0x396CBD03,0x4C0242F7, + 0x0BEDB47B,0x9F630605,0x0EF085DA4,0x20C07233,0x5CC238D2, + 0x0D3EFC097,0x615980,0x2E46FBAC,0x3AEFE6CB,0x9A2486D4, + 0x9EBB9AF6,0x0F66995D0,0x75D744E4,0x15F8B249,0x3A9B49A1, + 0x3B3C394B,0x1E201EDA,0x0A3A1DDC5,0x85C6CE5F,0x0FC16210A, + 0x86AE7AA8,0x0E792DF53,0x6E41D44E,0x7B8CC43B,0x3EA7A38E, + 0x0C181D241,0x295143CA,0x3971DD4A,0x17702207,0x53630F1C, + 0x257B436F,0x0F6DD64E4,0x22ADBFD0,0x0FD6C2EB1,0x14853F1C, + 0x0B52292F6,0x0B14483E7,0x6013090C,0x0E673DEEC,0x0B5D7E1AE, + 0x535AB889,0x5E0CB97B,0x0CD890B3E,0x78D10878,0x0DB53E632, + 0x164B978B,0x394EEDAD,0x0BF230201,0x58D5D79F,0x0EC063B09, + 0x0DC86EF18,0x8B250736,0x1DC593CE,0x7EB37F01,0x0A637B89B, + 0x1C699045,0x0B94764FC,0x0AF057CC1,0x0CAA43292,0x0F5C6579A, + 0x0FFC1D2A5,0x9FF28AD6,0x0B654879C,0x0D55054E2,0x0FFD7A786, + 0x0B0AF430D,0x1D0A1E95,0x0BE5DBF0A,0x0A6FEB9D,0x82B18072, + 0x0F0CDA199,0x0F1811340,0x0B1AEE727,0x0F6AC5A54,0x60E1F907, + 0x0EF9209F0,0x0C5F825A0,0x2D97293E,0x0DDCB4F7B,0x78A498C7, + 0x57F36EC6,0x89A587FD,0x18500CD2,0x8E0CFBB3,0x0B884CA95, + 0x0AA4D4DA0,0x376D928,0x7360AAE0,0x7ACF824A,0x72429372, + 0x0CF809FDC,0x0A6745EBB,0x6C3A2063,0x17AB301,0x0ECF5878A, + 0x0E95D10F1,0x0A16A6C9D,0x0B82A461A,0x0C99FA0F,0x3C71F970, + 0x684370F4,0x38C724CA,0x2071A088,0x0E2449364,0x54EB74A6, + 0x6306675D,0x58C26451,0x5AAC963A,0x34C7012F,0x59DB6861, + 0x25116E09,0x66BDFB9F,0x1B7BE14D,0x7C7DC0A3,0x3A23278C, + 0x7CCFB81,0x59ED1DFC,0x6B604730,0x85FE37F7,0x7E6F100C, + 0x7F41FD7A,0x9381754,0x3AE589A7,0x4C7AF1AD,0x5EDD0A43, + 0x77FB899D,0x0C85F4439,0x31039911,0x7660E15,0x15DE35D8, + 0x0E030D38A,0x305E4026,0x0CBC011EC,0x8456F512,0x745DDDB7, + 0x88206018,0x3C115D06,0x0A518E298,0x0B52B4C1D,0x0BB1FB054, + 0x29BE7AE0,0x9A1954F6,0x13204A59,0x896F2B88,0x0A46B3235, + 0x0C38EE5FA,0x40FF3E48,0x0F56609A0,0x0FFFE8D05,0x0C1EC6EB1, + 0x2CCFD207,0x3E98C3C9,0x0C99CD58B,0x76E70867,0x9D3EFF3, + 0x0E7DA1270,0x0D5A99244,0x2750BAB,0x4892BEA9,0x0AF42F043, + 0x33CB8DEE,0x0C7CC1148,0x99D0A806,0x9824942D,0x34E6D086, + 0x645FF34B,0x0EE935627,0x0E315795E,0x6820A347,0x0BCE4C903, + 0x771EB169,0x10EB5840,0x0A2DE90B1,0x0F050EDFB,0x0A6F7E269, + 0x0E2BB2882,0x0F2C45F7F,0x60D8F802,0x2BE45204,0x58D8230F, + 0x0A9B81AAE,0x0ADF0C323,0x0F9E9AA57,0x0B4D9C317,0x59DF097C, + 0x0B84F66AB,0x3E4BD3BF,0x7793C004,0x0D19DB666,0x0A3E93B29, + 0x6D8FF6DA,0x621A1385,0x2398E928,0x0CEF2DA68,0x377A7B83, + 0x75C3F586,0x0A1B5ACC0,0x0DAE01E76,0x8C110DD8,0x0F223E034, + 0x0DA18476A,0x0A9691EA0,0x9E999436,0x5B108CFC,0x0A12743A3, + 0x5B803372,0x0DE98383A,0x6F9FF28C,0x5876A29,0x515BF8BA, + 0x0FCD264C2,0x30244BD0,0x591CC4F9,0x2D6E4485,0x0EA4CBFE9, + 0x0DE3410F6,0x2D0C9B4F,0x0CF703120,0x0D84B340D,0x0FCA4FB69, + 0x52BB77A9,0x58551718,0x3854EACC,0x498A04D8,0x0D9CF22C3, + 0x2A519139,0x0BA2C4B01,0x0C73B642F,0x132DA49B,0x0E5DB7691, + 0x50D800CA,0x0B74A949B,0x91F9476D,0x6AA6DF6D,0x2A9EF385, + 0x9A8F4D8A,0x249993B7,0x0DBA8275A,0x0BB024BCD,0x311C89A8, + 0x0DEB25235,0x9816E524,0x0B1C97280,0x7C487FE2,0x0E288CE5, + 0x4265F2D9,0x0F811BC,0x0BEA7AA66,0x46218A03,0x0BA4A69C6, + 0x0D6D411DC,0x760FC39F,0x5802D60D,0x22BAA178,0x0A7E29B7E, + 0x6694C13C,0x536F38BF,0x0DBEA34BB,0x22E71880,0x8F8CDF29, + 0x8A4EBA17,0x844EFA9E,0x38E931FD,0x318A9494,0x8DC3A451, + 0x9D0E6C,0x1C2CCF54,0x0CF669BE9,0x2D3580EB,0x6FCAC7B2, + 0x3C351B20,0x2832EDD5,0x7B4616A1,0x330BBE3F,0x4CC87D40, + 0x3E45C145,0x0C8DE7071,0x0ECD5CE1E,0x3BEDA0CF,0x52349266, + 0x0A520D698,0x7BDD0E9D,0x3AE66C2C,0x0E5D317A6,0x0E371D192, + 0x218D849,0x0BB3909F2,0x0B8374BB1,0x93782B1C,0x0E1BBC4EF, + 0x6FACE200,0x0CBB56775,0x618ED34,0x0B93BAC9B,0x46429E72, + 0x60E3E21D,0x0CE765F16,0x6B4CA99B,0x724228A4,0x0F8967718, + 0x0BAF90A40,0x19E5147C,0x6330A735,0x54E21D12,0x0DD44B86F, + 0x2339850B,0x0CBD18600,0x751C0FF9,0x863C719F,0x2BBED654, + 0x9B2B6C24,0x9CD9C6F8,0x48077F0D,0x112920A5,0x38143FD, + 0x49EFF97E,0x0F30A6A37,0x0F66FBB8B,0x745A3527,0x3E73A338, + 0x98EAFBDA,0x3CC541D3,0x0A179A981,0x7CB8FC11,0x888B3CF2, + 0x82A08B89,0x78E54927,0x44567D38,0x541276BD,0x0ACB5B2FC, + 0x2DE2FE75,0x171FDF19,0x0CDE631B7,0x0D8F80EB7,0x2FF5F10A, + 0x0B52A1B61,0x4AA369D,0x69A03987,0x2FE38BC2,0x21CB6BFE, + 0x4A448F6D,0x0FF200B7B,0x20B275BA,0x9A98471B,0x30D88464, + 0x7C2F9FDE,0x30A5984E,0x0A2656827,0x88CC9FFE,0x0FFC04C3E, + 0x0D3431D1D,0x0FA47C7CF,0x61C4A8F9,0x0EC09AB68,0x0B8506D03, + 0x0A3949EFE,0x0EA7A951,0x0E17F9769,0x0D0D0301D,0x0DDE164E3, + 0x1E98EA44,0x0C1D6278D,0x4D0B51C8,0x2A01D4EA,0x64FCF54E, + 0x0AC05B562,0x0A083F8A1,0x4C0BE6C1,0x0F079922D,0x340D9B5, + 0x79FE8E84,0x4254D453,0x13F4CADD,0x68FB6E93,0x0CA82BB90, + 0x4D6F15CF,0x5E91E79C,0x0BBED8B48,0x0C3526A21,0x0F03464A1, + 0x0A2B772E6,0x1E098962,0x0D6F5C2D4,0x0EBB5B977,0x0F0083279, + 0x0F18503AE,0x0B32F2E86,0x3E494D3D,0x996C3351,0x0D5D6D039, + 0x4C055554,0x2B859920,0x2E06BBAC,0x0ABB10A50,0x0D2BC219B, + 0x333C568D,0x813B5606,0x950B097F,0x0B5D9BCF8,0x17857E31, + 0x0A1B2CA1D,0x0F3196794,0x0A7228D48,0x0D7B549FA,0x0E04D22EE, + 0x664FFD9C,0x999B3EA8,0x0B6603014,0x0D034A7B5,0x0D064E4E3, + 0x0BB88B875,0x335BEDED,0x41CAE1EF,0x514B74F8,0x7E70244C, + 0x10BC7730,0x43B59A5A,0x4B3A47A6,0x9417EB0A,0x4ED709CC, + 0x29DAD9F2,0x5DA736FB,0x0EE82B7CC,0x2E421BED,0x835DEAF5, + 0x6A455943,0x0BCF76EEF,0x31D06600,0x22A354E0,0x930A0A0D, + 0x70F93C10,0x1799E3B3,0x1753E06F,0x372AE224,0x0BB4F890D, + 0x0E2EFCCF2,0x0B350989F,0x0F91CB996,0x8DFDFE01,0x0D36998EA, + 0x87C0C4AE,0x0BA94AAAF,0x1747844A,0x6BDC0209,0x6507ED19, + 0x9A860CFE,0x0CBB44485,0x6B5C02FA,0x5CCBDE9C,0x0FA29784E, + 0x5A09A68F,0x0D33ED2AC,0x0BEF09D34,0x7BF0C8AD,0x0B5495585, + 0x0E618E94C,0x1D9D7520,0x0FF911568,0x0BBA39C5,0x23BE0245, + 0x4835EFDA,0x0A601B80B,0x0D0E27AEA,0x4C451546,0x525DD221, + 0x0D1754A64,0x0B38583D2,0x62035B3B,0x8BFB2AF2,0x2262A381, + 0x0A6A8FB9A,0x0A68C4E51,0x84343E89,0x797DDDAC,0x0D990CDA0, + 0x98B6A5F6,0x0E6D9764,0x0F7BB5075,0x0BEC2207F,0x0FB955BD6, + 0x3A4AFF3F,0x0FE5096A7,0x5035E15,0x0CE7EA8DE,0x59B07E18, + 0x2EB38850,0x0A0583180,0x5307053B,0x0FCC65D2B,0x68973FBD, + 0x0C007801B,0x0B042B5B,0x0F4EF27F5,0x0CDF2137B,0x0D5947584, + 0x0B68A14EA,0x52D0A235,0x0BFF99E56,0x90C47999,0x57EFF8D5, + 0x6850D9E5,0x0DF21B95C,0x0E49C3575,0x2DC85049,0x0C5921445, + 0x0C1F7DDA,0x619276,0x0BAE8D5B4,0x3BFBDDD0,0x69E8425D, + 0x5598B93A,0x0BF6680C2,0x0DC31FD43,0x57B0A2B1,0x9506139C, + 0x3D978B60,0x0F8197C9C,0x75EE75E5,0x0B1B149BB,0x750F6EBB, + 0x20D8A317,0x0A655D544,0x0D9DD44F2,0x0E7A3E241,0x24D2FB32, + 0x0ADC1B5B,0x7E0E2DE0,0x5569E09E,0x17AD4FA9,0x3B6E0F9, + 0x530B6C60,0x0C1B5A1BF,0x494DA57A,0x3159FC29,0x47D9AC91, + 0x6A1C9FD2,0x4BD848E1,0x757A8A39,0x8BBBD5CE,0x0D1749243, + 0x0EEB3C15C,0x0F10EE0B6,0x91370FB6,0x0B9D173BF,0x4482D8A8, + 0x54A9A6D,0x0A13C524,0x21897B33,0x9C2B96C1,0x52A28F6A, + 0x0E6529F37,0x4C3023CD,0x88D345E4,0x0B9CDD9FB,0x573B854A, + 0x0B99B25F9,0x0DADE6D8F,0x5CBED0AD,0x0CD52A1FC,0x2AE07964, + 0x0A1F2D87F,0x9BAC0F4D,0x0FA5C5D29,0x9F5358FB,0x30F48EB0, + 0x180A6DE7,0x0CF5F60ED,0x5B8A3BEC,0x1708D95F,0x0B38E03C9, + 0x7C9E94A7,0x0DD59DBA3,0x2694410A,0x8D2D2C80,0x739F23F3, + 0x0E8D532CD,0x703C906E,0x71C7FD9,0x642874AD,0x7B547858, + 0x45E6CE87,0x0C3D0D6E0,0x413D31F4,0x0D86A266F,0x36B74492, + 0x0A2FE49E4,0x3A234324,0x7FECF581,0x14137C0E,0x0C39A3370, + 0x0C15FD3D9,0x33F0DF3B,0x0F0A03FB5,0x89DB2A52,0x87B64AF2, + 0x0F3CA1B88,0x1C439586,0x94340A96,0x7C2E518B,0x1102497, + 0x2720C7BE,0x0C65BEA8A,0x0D10AD101,0x0DE96B2D4,0x0E39D5FD9, + 0x3E4524BB,0x0F9D1F3F1,0x4B72BEEC,0x625F2595,0x5F2751F6, + 0x0A3462037,0x49F782CD,0x0F34A23EE,0x0CA764A48,0x0C07001F2, + 0x19BD76A3,0x2984A122,0x5FE727FD,0x82937E7E,0x3B9652DB, + 0x0D47228AD,0x3F6F42A3,0x5A35C278,0x71991222,0x4B8824D, + 0x0CB423307,0x0F81935BD,0x0BC1BEC66,0x0C3BB8FE,0x0A5D7D436, + 0x4E467F6A,0x5DB05AD3,0x7C2212FE,0x0ADE13E7C,0x8CFF8BDC, + 0x0D42D16D6,0x25D019C9,0x557CA68,0x24CB80AF,0x0E4A7181D, + 0x18EB971D,0x0C6C10C5,0x0C76EC9C2,0x8C749993,0x0A75794F6, + 0x6696EBA0,0x65ED0D31,0x0E291267,0x613A5A92,0x0F18A7148, + 0x2F8D3659,0x0EB9A3D05,0x18F9716D,0x0D03DFE48,0x98D36ED7, + 0x0E4DC111B,0x0DE38A345,0x63E3296E,0x4E851C88,0x0FA44C995, + 0x2DDF817,0x4BF3CCC7,0x45A7EA89,0x6D68DDA0,0x170CBA1E, + 0x6A20FF9F,0x0AC7DC137,0x0C01C07A9,0x0EC2C71DA,0x0E0611D85, + 0x0F48ECA2F,0x0B2763E5E,0x94E0E606,0x0EE9BD44,0x0CF9E6E4E, + 0x45CABDAB,0x5F0D239F,0x962EB8EA,0x2EB257B2,0x0B9AEFCBD, + 0x0DED970E3,0x58E529C3,0x41106BB6,0x91F6B401,0x0E2A9574F, + 0x7603675E,0x7A3BD6F8,0x8CB7DE25,0x7C21A09F,0x4FBD0C86, + 0x8AFA0055,0x0AD47B415,0x0FD2755D0,0x8688F049,0x615192EA, + 0x333AAEFE,0x0F6E0BE20,0x0FC192DF3,0x30847514,0x0A065854D, + 0x38B0650C,0x0D0641C13,0x6A1AD270,0x0B4DE32B0,0x0D53D1250, + 0x659F5674,0x0BDD811DB,0x72F9EE13,0x1C6CCFE9,0x5C40B324, + 0x1DC1DD73,0x194C33A3,0x9F61E415,0x94FA4D70,0x0B91B1E93, + 0x2BABB8CE,0x3082DA51,0x30C37DE4,0x26CFCD9,0x6A2B783F, + 0x0DF747B59,0x91D0DB4F,0x0AA78E222,0x0D422ABE5,0x0FC1BCF2B, + 0x569642BC,0x0A244778C,0x0AD24CBED,0x1A9D2003,0x62CAC878, + 0x1A15A672,0x75078FC2,0x95EF65F,0x0D660C912,0x84709672, + 0x0ECDFF213,0x0DA041BFB,0x118FDC55,0x901AB16A,0x14012CCC, + 0x0DE7497D4,0x0B6CADB4F,0x3018A175,0x2E03B61B,0x0A6D2B429, + 0x0A6C8DF4D,0x95B04DF1,0x0BBB94A6B,0x484FA74,0x7C40DC, + 0x2F66DF7C,0x7C38DF6B,0x72F3375,0x271C98C8,0x0B0FFC0F1, + 0x71D6B007,0x1B76726,0x0BC1DC01F,0x880996E,0x0DD28356D, + 0x8442DFC7,0x9E34D02E,0x692E5351,0x3F012708,0x5F3525D2, + 0x0F0552282,0x438E1735,0x59798087,0x0A5340107,0x12B74FE8, + 0x48654EF9,0x2BE277D7,0x0A5257D60,0x0A6CE306C,0x69B9A1B8, + 0x0F8D38723,0x0F22CE11D,0x8C4EDD5C,0x0D6CEF9D0,0x402068DA, + 0x5CAFB7B9,0x0E52DB045,0x1287FDF,0x0C3DF13A8,0x0F453C7F1, + 0x13A142F3,0x9A8E9BC0,0x8364C17A,0x0BFD1BC3,0x0B61D6B74, + 0x9305F892,0x0C144EC7E,0x32D1FB6B,0x0AF574515,0x1CD07EB1, + 0x0FE554C1F,0x3A34F067,0x1F442963,0x0A77555B9,0x0FAABDA0D, + 0x34CDC474,0x6818B02B,0x0E5BAE889,0x0B89CCF32,0x727F808E, + 0x2B4AB279,0x0C6A2DF3C,0x77BCA5BC,0x836F6CF2,0x478E499B, + 0x7C742231,0x0B9E01319,0x3904111E,0x0DFD8CD1E,0x77B4DB03, + 0x3F1E14F5,0x0A6E139CF,0x535FD9CF,0x69277491,0x0ACAE03E, + 0x1AEBEC01,0x459644BA,0x43D394F7,0x5A7AF020,0x254874F1, + 0x9533272D,0x35FB2591,0x0B202FE07,0x9A5D591D,0x642BDDB5, + 0x0B12551FE,0x0D17701A3,0x89CF6442,0x13B3FD1F,0x67177F1A, + 0x0B92F34DE,0x43829F5C,0x44406178,0x49D55901,0x0ABBE0FE7, + 0x599B53A8,0x0D786210C,0x87A4C812,0x28F94B2A,0x9C8108A4, + 0x0EE7C9769,0x9309F5E9,0x0F0F6E254,0x1DD7870D,0x0E9555C5C, + 0x1EC94963,0x60A0C53,0x2D82DADF,0x698849F1,0x18EA6D9C, + 0x0A9C84153,0x5EA94958,0x4FC87676,0x0B3AF4AF3,0x5B073CC1, + 0x7FAF5EF4,0x0BD0B3D79,0x5F9F0904,0x0DDDCF64A,0x0A239E171, + 0x138637C4,0x0CE7D13AC,0x2F9BA6DB,0x192EDCCB,0x0F3AE3C67, + 0x0F0501108,0x94D9C7A2,0x74B09740,0x3D3867AD,0x0F75CEB74, + 0x8B6C110B,0x83279C4C,0x12190E24,0x5427CC8F,0x0D6687DC4, + 0x5936B3AC,0x0D385CD99,0x0B7741C35,0x762244B9,0x46C3E55F, + 0x22F37B19,0x12498383,0x0AC30E016,0x0DBF27C9F,0x0DF1632F0, + 0x95ECECD6,0x0F966084B,0x0E92901EC,0x2CE44BAD,0x0B2E17ECB, + 0x1DDAB702,0x810D3B01,0x68925E1B,0x1AEDA43F,0x1BE0252A, + 0x0F48336DB,0x36964547,0x0BD15054F,0x320DCCEF,0x0D3AF39B8, + 0x77A21980,0x0CC9E8B58,0x0E43763BE,0x0F0F6CD14,0x4BAE3351, + 0x0BA0D60F7,0x0F272E545,0x5AFEBBAD,0x13EE2B88,0x3628EB09, + 0x5C138A6F,0x64AC0D7F,0x70D7DB32,0x31F2D6AB,0x65B0CB6B, + 0x9D250CC8,0x441D5B9B,0x0DDB20A3A,0x8C1E61A6,0x0D8CD4005, + 0x0AFB70552,0x0AAECB053,0x937046CB,0x254A73F2,0x12D87222, + 0x4B6532D3,0x7BFDB1C9,0x0D480AC8C,0x0FF67B19,0x0AE2237D7, + 0x855A1DCC,0x78963711,0x86C63983,0x769A9BC,0x315B4242, + 0x0E1EE9BFD,0x993D04EF,0x0C4BDB01F,0x42151AD6,0x2030A10E, + 0x0A7907528,0x99E5B7DD,0x0B7DEDC77,0x863F5341,0x5334673C, + 0x75E46613,0x0D24A0351,0x0A540F9D2,0x7EDDF177,0x8A01A61E, + 0x142E47D2,0x4C9A1D3E,0x47826D68,0x48C0A09C,0x3EA0A1A5, + 0x8DF08744,0x115674DA,0x60E7BC68,0x50F44AB9,0x0BA2740DE, + 0x7DCFDEDB,0x0C579A5A0,0x8DC0BC3B,0x6266924B,0x6AA3C5B8, + 0x0A6BC43A1,0x74D8AF96,0x58100608,0x0FBC582FD,0x7436BE08, + 0x0C7511C79,0x0ABCA64D1,0x916BAD79,0x0E1AB81B3,0x87813CD0, + 0x0AE7AB5A4,0x0CB0A2037,0x0DE1F33BD,0x0F5FC88C8,0x0F14543BD, + 0x8C56F484,0x9BD9BF84,0x9198BACF,0x458F9699,0x0F54682EF, + 0x8A5E4CA6,0x1D69C991,0x0BBFF78F7,0x650E6243,0x5B7144FE, + 0x9BC4F0FB,0x0A477F1D1,0x7F226E8E,0x1851AA,0x4A3FA73A, + 0x911F4A66,0x2438BE1D,0x0AA995A08,0x0ABAEA8BC,0x535A0E33, + 0x7345AD7D,0x0C97889B3,0x8127EA2D,0x0FBCD03F4,0x0CF7FD27A, + 0x0F794789,0x0CCFEA87A,0x0DB5F34A7,0x0D8590723,0x6CA33DA8, + 0x0CCC85AD0,0x8233EA88,0x708864C2,0x123B546B,0x35BB99D, + 0x0C3AEAC15,0x0B70343E7,0x71C4B570,0x32CBBF8D,0x0AD76460A, + 0x0EFE3D5E,0x3B04C995,0x5A6DA38D,0x9370BF65,0x17ED2B33, + 0x610139F5,0x0BCD7E0CB,0x5C16063,0x0B28725B4,0x13FFEEEF, + 0x0D8DD2EB2,0x0D4CDB27F,0x0BF856E,0x0C382111E,0x709586F1, + 0x113A8079,0x63CBE42D,0x21510A61,0x85532275,0x0DECD04C, + 0x7B201AFF,0x1F7084D3,0x58AC7969,0x5909ED35,0x29744131, + 0x0ED2567D3,0x6A76463C,0x0C6F660B2,0x8EB9B250,0x0FEF3DBFC, + 0x7CC5819A,0x6557F17F,0x0C270529,0x0A14B2D,0x8EEF6370, + 0x8712A99A,0x443215C8,0x0E6325F82,0x0E28866EC,0x0BE4CD240, + 0x158EF87B,0x0E1EDFF57,0x0FA65417A,0x0D969F4ED,0x0A2320ACF, + 0x6256554B,0x949CEFC7,0x0EB0FD95E,0x4B51E291,0x1832CB39, + 0x0B481A8C0,0x43238991,0x0B2615ACD,0x0F7860840,0x9AAAE693, + 0x74C94EC3,0x0B7188BD2,0x2E98F6BC,0x0C6EB60AC,0x4E67B070, + 0x7B69CD2A,0x2FEBBF49,0x0FA5911B8,0x0E2A17955,0x5F8ABBA5, + 0x0AA46CAC6,0x374A30A5,0x805DB171,0x5EF2B4F,0x91ADC14F, + 0x0BE52389E,0x0BBC0A1FE,0x4D4F3974,0x14658D40,0x1047E212, + 0x642ED37B,0x599E3CA2,0x0A5F35738,0x0EE3D25AE,0x8A4D10A5, + 0x8910CEA6,0x0F51C8813,0x58893C5D,0x7F026078,0x7E1BC88C, + 0x0F4E5C9EE,0x90C19F4E,0x0D4710C32,0x197542A3,0x0D4940325, + 0x17B907ED,0x5B009E4F,0x7C119676,0x5AD5F57,0x0AE866DE7, + 0x2354DE8C,0x7255DD2,0x39F44660,0x567E0A2A,0x7F51D6E8, + 0x5C266FC6,0x6070615D,0x4F3558E2,0x0FE1DCE9D,0x5BC9E69D, + 0x0AC6598AB,0x1C810F7C,0x731C4F2B,0x25021BE6,0x8E5916E7, + 0x7C7628A5,0x0EEF5234C,0x1B08A76B,0x0B60F43ED,0x7169DC4B, + 0x0C08A5300,0x0DF4E623C,0x168DC7DC,0x3CF0A59A,0x7A032781, + 0x518B66A5,0x0DE198D10,0x5DDF3BFF,0x0EDC3284F,0x92B20C32, + 0x8037BC26,0x974C9628,0x2A702024,0x4F9ECB1,0x0ADACBFFB, + 0x0E98BF100,0x82EE1700,0x47D5E02E,0x557D3DAC,0x9531C5B6, + 0x8663571E,0x3DFB01F8,0x0AAEF2096,0x1C14C9B7,0x73468FB, + 0x660A79A,0x1C879256,0x3F48F8AE,0x17051455,0x3396CE2, + 0x5D0BFAC9,0x0FF238A8A,0x5BC6328,0x0D5FA29DD,0x5D7E020C, + 0x9C39F877,0x647D9FB2,0x5E5AF7D8,0x4E27937C,0x985FFBE2, + 0x4AF506F,0x0BE4C2E5F,0x0A18AD6B6,0x0B4AB8D79,0x0F5F43F1D, + 0x56076840,0x66C3597,0x0F171E628,0x8A3476B9,0x0C8FE7E88, + 0x6ED2573C,0x90467E1C,0x53923D7F,0x0F4E78883,0x0C022602, + 0x120011C2,0x21741EEC,0x0FBB7E0C0,0x4F7FCA86,0x37B395C8, + 0x57EE1BE,0x3FA31F0A,0x0E810AEA6,0x1B5441A,0x509690F2, + 0x601F1E6F,0x0C9B87A7D,0x0B19A98E5,0x93E173C4,0x40E2F53C, + 0x23BE1769,0x0C8384996,0x0A0C314B1,0x0FEDFFEFD,0x66A6AE07, + 0x7A250DB,0x86E23B77,0x0FD4DD180,0x4436A734,0x6F30E7A0, + 0x9723F213,0x0DE9F49D2,0x0AD71AD0D,0x40748512,0x65AA81C1, + 0x808B7D40,0x0D39DA8F0,0x0FC45E59F,0x0BFDD71FF,0x606C656, + 0x2640C078,0x64B8FFF3,0x0B8608C89,0x0D145BFE3,0x581E6083, + 0x79200CFB,0x0A01ADA5C,0x80BD3FF2,0x5A3B2F2E,0x7C1585E4, + 0x82DA5B7,0x0F62360D1,0x5CDE18D7,0x0ED69241B,0x0C0017514, + 0x546C7A0C,0x0D08C4219,0x8F33E14E,0x0DB391432,0x0F9CC2871, + 0x670817D2,0x61C7F569,0x0A9BB8A0E,0x88BA410E,0x10584120, + 0x0A5BBD99A,0x0BCAB21E4,0x0E1E9DE2D,0x0FFC2A75E,0x0DCE3464C, + 0x0E36F6132,0x264F5A5E,0x0A3CA732A,0x0C655362E,0x30AC14B4, + 0x0B7214C6C,0x0AE340E62,0x6665E02C,0x0F4464268,0x35D48D66, + 0x10072121,0x0FBA8BF74,0x0C163348A,0x821D3897,0x0FB8895C5, + 0x7F48C72,0x66687498,0x0BEF1A48C,0x0DF3A93EC,0x4B5D3ED2, + 0x0F6F9CA4E,0x4A856B14,0x6EE38471,0x0C8381084,0x0C1FC40AA, + 0x0C84D653A,0x99891173,0x0BF1B79B2,0x57951EE8,0x1800AD4C, + 0x906F234D,0x0B0DB34CB,0x8B2AEE86,0x5B919AD7,0x0C51CE39E, + 0x5E8B3A7B,0x6D607841,0x0F336C5A7,0x0E854B941,0x0C882C2AF, + 0x4E1CC615,0x7F6409CB,0x0EB1C5055,0x30554795,0x0BF82203F, + 0x0E3D07D8E,0x0FFBA9035,0x17D78099,0x91F70F3D,0x3D6F7B7D, + 0x94B1A180,0x3D206868,0x0D6225DCB,0x0F0769B67,0x5BC8EE11, + 0x0A58238EF,0x0DDE613E7,0x9C5AB757,0x47021F07,0x8E94675C, + 0x386879CE,0x23CFE89B,0x86A61CBD,0x7A2BB01A,0x0B90F18FC, + 0x0A5CC87C0,0x8C380FD2,0x2A5A0BD9,0x6F7DBA2E,0x83842C93, + 0x87D6222,0x9F79B183,0x0AD9A6762,0x5F6DB120,0x0EB7ABCC9, + 0x191A1B47,0x38969D5,0x183C2FB6,0x67750726,0x1B140291, + 0x41B04803,0x0D2E6FEE0,0x0EDF16DD5,0x647B600E,0x7AB3CDA9, + 0x0EBA0B86B,0x2DB555B2,0x0F6A56DAB,0x0FE7902C5,0x6DF3666, + 0x19C569DC,0x1226A296,0x5E243094,0x358911C,0x0E16692AF, + 0x38D4B83B,0x6B1CDF92,0x4FF211B,0x0F71DF9C7,0x2BC3A847, + 0x3604D87D,0x690B8030,0x0CAF0703,0x0EA48ACA8,0x11C12551, + 0x0D3EF8667,0x1021657F,0x0B8F93C8A,0x8B7A0E4B,0x23614E0F, + 0x35B9FC98,0x14AA115A,0x76921DEA,0x7B572FB3,0x0EBFBF5EB, + 0x0BF7024C5,0x0E4AE1AE8,0x3B04C321,0x0E4ABB954,0x0BCA1AFB9, + 0x24B40F44,0x5DDE265,0x11D0F1F2,0x4BD12962,0x0C9C9443B, + 0x0B899A6CD,0x0A2AE8521,0x0ECD64D2D,0x0A3524349,0x782C68E7, + 0x8CBA07E,0x61C413C7,0x0C2F3D42C,0x0A3CBC17E,0x0F5EAAAE6, + 0x0ABF0B51E,0x799200E0,0x0DCEA899F,0x5A114B76,0x0BF0AA5E, + 0x585311A3,0x33FDA99,0x68866E8B,0x298ADF4,0x309C90F1, + 0x39C00CF7,0x8ECD3AC1,0x50FEB68D,0x12124588,0x0E0A0BA87, + 0x80AB1AF7,0x0F776FE17,0x50993965,0x9855BB55,0x2D25B549, + 0x3995FEBE,0x7957B6C3,0x439429B0,0x0C780F514,0x953170EF, + 0x5FB14422,0x525C23,0x0BE4104F5,0x0CC5E4B58,0x134BAC3D, + 0x33C3EE7C,0x0C42E49C9,0x0E172CAE5,0x0E70BF911,0x7A5EB8C8, + 0x0C94A6EAD,0x130165C3,0x6B1671DC,0x0B8D7D354,0x0FFDC5CFE, + 0x0E5E0D65F,0x6FD09F1A,0x0D2297AC,0x0D7683C59,0x16251466, + 0x6DF4C89,0x0DA759E9E,0x1B3789B,0x0A8245AC6,0x80277F26, + 0x0C145C137,0x6FC2B7E2,0x0DC7220AB,0x6ECE842D,0x0A24718C3, + 0x51DBE389,0x32E51F83,0x0A139E21B,0x9E70FDD1,0x1981242E, + 0x6D9250F9,0x2A0D62AE,0x17F5092C,0x777BE5AE,0x8CCCEDFC, + 0x5A2E0EE3,0x0AD4B14E2,0x5DCCCE26,0x0D12A6DB9,0x0C2C12EF6, + 0x41273940,0x0FCBBC8F3,0x0BD818B9B,0x332D4C64,0x0F57DCFD2, + 0x0C4CCFCB2,0x10E53F51,0x0BE1530EB,0x29846F66,0x67C4D240, + 0x0CDABC3C7,0x0B55EE388,0x7EAFF409,0x0D5A9F5BB,0x33688E2A, + 0x0A72FAF77,0x0D6B47508,0x40BE4D7E,0x0C5F25BE7,0x67EA1D35, + 0x51884BF0,0x198A0227,0x425E22AC,0x537F581,0x5060118D, + 0x10D17F93,0x0AC001C65,0x0D1F94055,0x75B19EED,0x16976BE0, + 0x436DC33F,0x5E5648F0,0x0A0300C5D,0x5D23B169,0x8A7ACEA8, + 0x7CB295CF,0x0F2D1B369,0x56FA78CC,0x403D384C,0x3CB3F1AE, + 0x0CDCB2DE0,0x0B2DF2AE7,0x670B3918,0x0EF3E648B,0x0D19153CD, + 0x61D871D5,0x40774A39,0x267837A2,0x0DFDA3E7E,0x942B34F4, + 0x4C59241D,0x0B2C5F571,0x1999467F,0x0BD5C9DE2,0x4CC3C170, + 0x0A5CA64B4,0x0DF04059F,0x8D2F157A,0x400C591F,0x4E6E8F69, + 0x0D29054E9,0x0F4AB45DA,0x6BC86557,0x40D3BFCC,0x0D4F24CAB, + 0x24151660,0x4CCD9E86,0x4A617B5B,0x51F4571,0x8EF0B1A7, + 0x0A52FFD58,0x7FBB8FD2,0x0C64BD90A,0x0E2027F8E,0x7744BCBA, + 0x2EC7022F,0x0B9EDE081,0x11512930,0x29F53DA,0x0EAAA1DAE, + 0x0BEB87424,0x4D2098EB,0x0C9187B23,0x83C86ACD,0x0F6A3EE80, + 0x7F6F55D,0x8BC12D45,0x0AC7B149,0x0C2E1E761,0x0EF97A861, + 0x4AF1A22A,0x0D08BFB1D,0x0C4E837D6,0x0F7085C16,0x473D5400, + 0x663A9289,0x0DD6DF92A,0x508FF9D7,0x379FB35,0x9D39FF08, + 0x2C6583EA,0x6AAFA743,0x3FBA9075,0x862C0A57,0x140571E5, + 0x0F62FD136,0x0FB554CAE,0x73F9BC74,0x35C19A26,0x0E7121FCC, + 0x79E0A80B,0x0F3C55F9C,0x715018FC,0x6B9F7964,0x3A305EFB, + 0x0D8F47B4B,0x0E8A544F1,0x0F65A0999,0x0FF59662A,0x342CDA3E, + 0x0FBF7B6D5,0x3708394A,0x0C9B0F986,0x57518473,0x4BB948B3, + 0x1FAEB093,0x0D9CA903A,0x0D18AC327,0x0BAA113DA,0x0E09258D0, + 0x0AF7DE4B5,0x75391FD6,0x69A46FD5,0x0EB375BA0,0x0DDEEFA3, + 0x5306623D,0x0B7F7F671,0x0F06420DF,0x0F440E9F2,0x0BCD4945A, + 0x470F81BD,0x0E11D51A9,0x0A53B4BCE,0x41C9FE6A,0x0F9A02804, + 0x0EFA7D75D,0x0A1A1CEA4,0x0B3492AE9,0x0EFA146D4,0x8A87DB97, + 0x0A8896C21,0x23FAE194,0x884572FA,0x6382C634,0x0BD475C32, + 0x0E2C12B6A,0x68038582,0x6C9D4150,0x1F711905,0x82C251A5, + 0x6D8E9ABB,0x0D61E354A,0x50DB5B06,0x0D263DDC4,0x0BADC0830, + 0x1189CBBE,0x119A1CE3,0x0E4499784,0x0AF2268AF,0x0CFA26C89, + 0x63049387,0x0A5889E1,0x0EED59544,0x170C1C8,0x86AE3B21, + 0x0D4B6FB19,0x56AAA63E,0x0D737B2D7,0x0FC6BCC1D,0x12C776A8, + 0x0A96F528,0x0C27D6A55,0x87523F29,0x0D731C44C,0x6BCD1AD0, + 0x7505511E,0x26B8CD30,0x73D9EE01,0x19C6F042,0x0DED50F4F, + 0x1A30EF61,0x77E43C05,0x0F5348DCD,0x31338F45,0x0E6925F28, + 0x0B4B637D4,0x200D4DFC,0x0DDA3F89D,0x4AF01533,0x3BF5A92C, + 0x0FC87C89D,0x93EAB534,0x46A14A6E,0x5D859A09,0x2915D6BD, + 0x450D6D27,0x1C3A7706,0x0AB8C54A9,0x896D90A8,0x24510AD3, + 0x4D8E5668,0x0F76F5F89,0x3B8351EC,0x0A03BB6D5,0x9AB4DF41, + 0x50D28564,0x0A28EB056,0x6B91D80A,0x3014A7F,0x99BCE3A, + 0x5F0287F3,0x75561A8F,0x0CB09134E,0x0B1EE8446,0x5293EE10, + 0x0F0D165C1,0x71A1F01B,0x1B312F05,0x9C384039,0x4D86E23D, + 0x0B7DFDB94,0x57099835,0x9E24143C,0x3C3AFBE0,0x9E170EA5, + 0x0B55BC9D2,0x0FACD4326,0x0AFF257C7,0x67DF017B,0x0C84B0A1A, + 0x90E8E844,0x0D6F4E04F,0x930A6D83,0x603FE58A,0x816A5A22, + 0x23C3BE15,0x0E2B04B6B,0x8EDB22DE,0x2F88338E,0x46265CF6, + 0x5623D51F,0x0A305CB13,0x3AB74B95,0x3120620F,0x30FA83CC, + 0x31E22867,0x83ABBC84,0x15F8B9BD,0x0F10A0EE0,0x7D6C357, + 0x2C5BDFE7,0x62328A9E,0x5A676F06,0x3787649F,0x95FA4D8C, + 0x0CA933D8D,0x6B66DF2D,0x13DA153D,0x5FFFDE80,0x3D217DA8, + 0x679DD881,0x2AF31E6D,0x6C1DAC10,0x0ED1F324C,0x0CDE01170, + 0x4F3808AC,0x0DC4A10C9,0x4B148416,0x5F4485A6,0x914D9DB8, + 0x10BBA277,0x7C3DEE4,0x1E1F6C10,0x4319E58E,0x0AAE53D28, + 0x1233E7D1,0x4B0540DB,0x0F5BE2A6A,0x8A7CF729,0x9FA88A3B, + 0x66CCB875,0x78A4F4CA,0x0D1772C06,0x1CA7EEC0,0x3288CF89, + 0x0DD6E0D6A,0x0E10A738E,0x3DF67D3B,0x0AE8EBD07,0x75027D48, + 0x5AA9A6CC,0x0F198E1CA,0x1C74FB19,0x0D1848EA0,0x1D0ADE0E, + 0x69D401CB,0x0FD0E432E,0x0C25AC8F0,0x4C2373E0,0x172D07D9, + 0x11718CF1,0x5929F5F4,0x4727FF11,0x0AC6A5CD2,0x5BF31152, + 0x0E8D51DAA,0x0AF8C5BAC,0x188FA9D4,0x1A294A7B,0x0FE84894E, + 0x66079FFF,0x8DE1D73A,0x0D0E6F0DB,0x6E9BC35E,0x8C8C2696, + 0x7CE60FA2,0x3E41FD1E,0x47BE9396,0x82528640,0x9561BAEA, + 0x658FA831,0x0DCD303F4,0x0EACD9B08,0x0C959812D,0x86646C41, + 0x0BDFE5BB4,0x0A4B17C3C,0x4A114DCA,0x219501BC,0x0BBA52554, + 0x0D1F48369,0x8910475C,0x0F838604E,0x0E66D2990,0x0D2CB495B, + 0x361AD8C2,0x0DA1C3E4,0x913A7166,0x49ACA621,0x4334A817, + 0x9F65A1AC,0x4D334513,0x164AB406,0x0E1A4A2BA,0x2F5AB512, + 0x0E9BA2E13,0x5B9BCC98,0x80EDED46,0x8094F7C3,0x7777F623, + 0x78D387A3,0x0D2D6F49A,0x9169A0AC,0x484DA641,0x0E69BD35, + 0x0C7625FD2,0x0A56D2AFB,0x0EE6186A9,0x0FF11889C,0x91D8164A, + 0x3B754C1C,0x38191FD9,0x6BBF4062,0x0A0BC43A1,0x159701BC, + 0x391A3B8E,0x0B3A98057,0x0A6D350B2,0x332781C8,0x4049E2C4, + 0x78451F87,0x9322E49C,0x0D8BB456E,0x0E0CA5EB8,0x9C46363E, + 0x9AEFEABB,0x8327071A,0x0E90337E8,0x3FA2200A,0x29BD89AB, + 0x0F780BE79,0x6B983611,0x0C38E74B7,0x0F3532B4B,0x331CA578, + 0x0B96F652F,0x0C87E0C54,0x0E9B579B7,0x798F3445,0x63AD0D80, + 0x2F29002C,0x3C285C4B,0x49AE254C,0x3AB7B680,0x1983AACD, + 0x5C31FDA6,0x668F6F3A,0x503129EC,0x0E8C0A304,0x0FA95C5A4, + 0x0CF864DFD,0x0F40070C2,0x3C57C4DD,0x8595D62,0x0C62936C1, + 0x0B84BD33F,0x0F7FB25A7,0x0B7C3AE3C,0x0CC4CE6F3,0x6E75DCDF, + 0x39A220ED,0x7756E1D1,0x0A2055340,0x25235360,0x63814E7D, + 0x0FAD95FFD,0x49A8B796,0x303B42BE,0x160D8863,0x3150D1DD, + 0x1CA8C21,0x1EE2F137,0x34FBE6EE,0x4C001FCE,0x4D408A4E, + 0x0C27DEA47,0x0DD3FC1A9,0x0BC6E746D,0x0EC20AACA,0x2CB0F5AC, + 0x7310B462,0x34603896,0x0E0BF2583,0x0AD611B62,0x96E19625, + 0x0A2D91368,0x6AB175A5,0x0D5B4A6A7,0x27698141,0x3A20F03F, + 0x0ACB519D,0x780F1FF7,0x479BCE41,0x6BB9F6B8,0x8322B418, + 0x0A4174E10,0x58AD15F3,0x0E66990B0,0x0D40CD604,0x0ABB03CEE, + 0x0FA143064,0x9D356544,0x3F1E318D,0x1E0033CF,0x0E8538E0, + 0x0C1625FD1,0x452C7C20,0x0CE6CBB2F,0x0B6F586F5,0x0C275A8F4, + 0x0A74BB26B,0x0CD989DCC,0x5299B270,0x5234A78A,0x66D00B5A, + 0x6A6AE4A3,0x84E5965E,0x61A206B0,0x0C251001A,0x3807D6F0, + 0x2B904A0C,0x2106AEC3,0x3DA24A18,0x6D3FE30,0x6B8D2906, + 0x3E4C960,0x99D9E302,0x0E7742521,0x0A213CA1A,0x0B701BE64, + 0x0D54A02C3,0x2BCE53BF,0x73950655,0x2F6237EC,0x328B228B, + 0x6307D148,0x0BEC9F804,0x9B4C1D55,0x3370FBC4,0x60842939, + 0x0A4A4F7B3,0x6D45993C,0x970E871D,0x33EA234E,0x875B9C30, + 0x5511137E,0x61BBFD81,0x2D1DC984,0x762C288,0x45B43639, + 0x0D00BD820,0x0DF426319,0x5708008,0x6E75EAC8,0x66D3D064, + 0x1AC07B8A,0x0A379303C,0x40D858C9,0x0E329DEFC,0x0F03AD993, + 0x41B16BF2,0x6DA2E819,0x4A613BD4,0x0B0958631,0x808E0937, + 0x0E5D945D5,0x0DA116119,0x0EDF7CCA3,0x42C52458,0x0DAC0575B, + 0x150D0839,0x76C53463,0x0A74C15E2,0x0BDDE4646,0x0BC6A29DF, + 0x5EA48630,0x105674A1,0x3EF67D74,0x0D07EFDFA,0x0F578CB07, + 0x1F581BA3,0x4F0FA302,0x92DDFDB7,0x0CB674D24,0x0B4092335, + 0x206DA34E,0x835DE381,0x0B3D59405,0x0F05DE5E3,0x2395A4F3, + 0x6312A607,0x0BD686A66,0x3588F97C,0x84D10CF,0x34518135, + 0x3A05D745,0x2301390B,0x0C1989100,0x37B0ED38,0x0BCD025DE, + 0x9B7BBEDE,0x7EBD0EE1,0x0ED03A17E,0x1230DCAE,0x0BFECE081, + 0x0B23FB110,0x176098B3,0x4EC7C574,0x0EF8939C3,0x0BE5E36B, + 0x0B51A09C4,0x0C17CEA2A,0x0CCC7A2AF,0x7DB34D0D,0x9C96FDF, + 0x0FD749410,0x385B3393,0x36EED854,0x953F826F,0x0D1144AA1, + 0x533A46FB,0x0AA1CADDE,0x18943C23,0x5000D89A,0x7E9974B6, + 0x88003586,0x931AD4E7,0x0CA1E45F9,0x5AEC96CF,0x0C1A1196B, + 0x4968C5E9,0x0CF93DAFA,0x0C9EAC494,0x884242E8,0x0BA52C795, + 0x32C3211B,0x0EC865892,0x4B6AD99C,0x0A6EED398,0x353E218, + 0x22F6EB95,0x0BEDD4762,0x0F9023DF,0x932A26EF,0x0EAF55AC, + 0x0D0A03459,0x36CD2E97,0x736B37DC,0x9066B022,0x0B2F98BDC, + 0x9C7BAF2E,0x747C9E5D,0x0CA105585,0x0D66B748A,0x1AE9E50, + 0x0A801232F,0x1CE2DCA1,0x0EDBC573E,0x68BC34EB,0x5EE0CF4E, + 0x27512085,0x0E7F2DF17,0x1039EB2A,0x283EECF7,0x0CD11377C, + 0x0F651F370,0x7BFA7A7C,0x0D87F02A8,0x2E197D14,0x8A5EBEE8, + 0x6A15DC8D,0x7D49DB1E,0x0AB9C8919,0x55DEA75B,0x0DEDE494B, + 0x6A808055,0x0E510359A,0x48D658E5,0x17EB3BE1,0x2E4B388A, + 0x0BE2A9AE2,0x938CBBE5,0x981069BE,0x9C0C8E3B,0x56DF107F, + 0x0A783F7F7,0x2062CE8F,0x0C46B4B25,0x0F64323D,0x378276F7, + 0x0B137A13F,0x0F74B7744,0x892ED62B,0x318DE104,0x8C235EFD, + 0x0C6D25BD5,0x0A5F21894,0x0C9E9217A,0x32FBBB35,0x2648157, + 0x84A44F06,0x6E1B5FF5,0x63D5B294,0x0BCA7B284,0x887B154E, + 0x0D1E80454,0x22067D08,0x4580F65B,0x51EA4379,0x0E555B9B3, + 0x0AE2194BC,0x2F139619,0x0C1A7F1D3,0x0D8A8676F,0x89FDB01F, + 0x49C61C3A,0x0F8A879EA,0x1E643725,0x74AFCADE,0x0A63E507C, + 0x5720748B,0x655092B1,0x6F8B1CE4,0x955F3CDB,0x7F85BAC2, + 0x0A1702132,0x0BD261D60,0x0A94F2895,0x528B65DE,0x109CC04, + 0x0DD5688BE,0x0B2729D26,0x0F029CE6A,0x0F7A0BEC4,0x912F55AC, + 0x0C06E654C,0x0BB938E34,0x33FB5F4F,0x0DA06BD15,0x26288903, + 0x523D7D48,0x0A71D57C2,0x0F9713A28,0x74C55189,0x99DEB5F5, + 0x834D917A,0x6F448452,0x7BA9465A,0x0AE698FC6,0x390F3413, + 0x0FC95933E,0x4C793234,0x0F414A389,0x7928AB6C,0x0A3B8A3DB, + 0x381C1D13,0x0B54CB4B,0x359D9DA1,0x0A3422A55,0x0D1B55135, + 0x0D4DE2058,0x9EB3F40F,0x7E09E219,0x0E6A95616,0x71A7FA3E, + 0x24EFDA53,0x0F521C5D4,0x899FF674,0x42E4F4CB,0x7917B64A, + 0x8DF0B74,0x0C7A394B,0x0E909E409,0x8E2E3211,0x0FAD92D25, + 0x0FE5F69D9,0x45CDE648,0x937B3705,0x4AE4E455,0x32B40E9B, + 0x79235113,0x0F982EFC3,0x0BA132AAF,0x0B81EF14D,0x0E641BF38, + 0x7208C128,0x4BBE3722,0x0E1821CEC,0x2C9D09C4,0x0EF125751, + 0x3E7784DA,0x3E04D7B6,0x2CEA4704,0x0A0E59A9C,0x1315CA45, + 0x9F0AAC28,0x71ED384,0x0F707B3A4,0x8DAC020E,0x173864FE, + 0x1F6D4612,0x0A53C0A47,0x0AB926524,0x0F7740635,0x144E79BB, + 0x9A884AA2,0x0AF5B6DB3,0x0CEE3DD0B,0x0C77584CC,0x7325B12, + 0x1ADCD229,0x6EE972FD,0x5AD6C1D1,0x5EBE6634,0x0AD2B8D31, + 0x0E82C8DC7,0x2DA8C899,0x50F1D3E5,0x689CD8BA,0x8E913866, + 0x0E15E742E,0x0CED14D43,0x8EC52AF1,0x0EF3CBB18,0x58AEDCE0, + 0x7A1BCA9,0x9E7A3E41,0x0DE939F5A,0x0B28D5A3B,0x6FE848B5, + 0x5AD00C59,0x6B3AAEE8,0x5030860B,0x0B5695C44,0x0C31DCD25, + 0x0E617F0C0,0x0D40C3A5E,0x0CB2B9F74,0x19F5FAD2,0x0E24FAF1E, + 0x1ED68F81,0x0E172FCA0,0x0DF2E4DCE,0x2F47747F,0x4F82DD00, + 0x69C39A66,0x0D4DCBEC5,0x0DEA306A2,0x0C947BFAA,0x13E4E3A4, + 0x0FB5486A4,0x40497081,0x2C980C8A,0x0D1D88175,0x932C1E98, + 0x0E65DFB5C,0x592CDDEE,0x0D4E65B37,0x1D383F0F,0x0A1452FAD, + 0x72F68D62,0x8C919B88,0x5C8EE3B5,0x83A6D634,0x0D72CB3B5, + 0x0AAA0AA43,0x50824277,0x0DB64F8EB,0x294328F2,0x21173186, + 0x39A9D582,0x3BA5DE12,0x4AF00767,0x203B19AF,0x99D95544, + 0x75CD1223,0x5928A7A1,0x18928B59,0x3126BB69,0x99835CDE, + 0x0BA1FA26C,0x0BBDFF063,0x2E541DE,0x0F7AAC63D,0x0B4CD0D6, + 0x0FA29F1E8,0x4CAC5ED0,0x22679880,0x2F624C2C,0x5B37746, + 0x924FCDAD,0x0EA205C22,0x47586DFB,0x4D04AB1B,0x93E48721, + 0x607ADAE0,0x0B261CC10,0x85E0F642,0x51BFC31A,0x0D5591CB8, + 0x13F34477,0x9C4C00DA,0x0D75FCC3,0x0DAE36AE6,0x4AC3EC7E, + 0x0C38DB43E,0x48DCE98B,0x407D4DE8,0x79C61EB2,0x6C293A0D, + 0x0C309861E,0x18CB8F7B,0x3172DF,0x48E3FA23,0x7E4F0969, + 0x0B3B3399B,0x8078C214,0x47C29F9C,0x0BB45EC9A,0x0A35A9385, + 0x0DB52279C,0x9B0A10D7,0x0FBBFE91D,0x0B72426B7,0x30B2FA05, + 0x0B440786F,0x0FFD3F89F,0x2B7BCED,0x0E3CFD31B,0x44293E43, + 0x0C8D5520D,0x0D3F85D25,0x921C8CED,0x3CCA0C6F,0x975B7095, + 0x0BD0B539C,0x204F3EC6,0x0CC69CC4F,0x0EC390BA3,0x905626CE, + 0x0AD5D4134,0x6589A582,0x89841DE4,0x5D7D9F79,0x9B7B2F04, + 0x0BAF101E4,0x6F92DE46,0x7566C899,0x8A1EE142,0x0BAA58991, + 0x0DFF7C7F4,0x6936EA6B,0x5BFB703C,0x96EA26E8,0x5A89915D, + 0x0A528D6D,0x2F06347F,0x0C0450185,0x0A15C352E,0x695E7A5C, + 0x6876C7D8,0x0E97B8345,0x0B4C5ED52,0x0D73FBB34,0x0A6BE0154, + 0x1915544,0x0D55A31F8,0x0E51C9933,0x0CB94FD3F,0x39CD60E1, + 0x85EFBA8C,0x65579BD5,0x0F7ED1227,0x4B2CDB8,0x859981BB, + 0x66988DD8,0x8BFEDAD9,0x110705A6,0x0D6AEC073,0x3BC27538, + 0x283C2D61,0x51D0B1C7,0x0B8C2EEE0,0x73FB9E32,0x0B05C250E, + 0x0F652B97A,0x0AEABC66F,0x0E1A89145,0x43531B6B,0x7112485B, + 0x777D40CD,0x956D162B,0x4355A846,0x72D6C647,0x158D96E8, + 0x0E03036E4,0x4FD9A4AB,0x0EBA1DB5B,0x0C9713FEC,0x53153BAA, + 0x4A9229E9,0x0CBA72F4,0x13FDF646,0x0BF82AEFD,0x55778F8B, + 0x47A5B2AF,0x0B647B3A4,0x351C5798,0x0D0BB715F,0x4D270669, + 0x0B1A3AFF1,0x6C73F78,0x5CD4A672,0x0E45153F,0x46A6635C, + 0x0C3A9B0DB,0x0DA774810,0x0BB44C98E,0x0F6268952,0x3E27323D, + 0x6C99AFC8,0x0C7A94CF3,0x8237187E,0x86E58E5C,0x726E7565, + 0x0E03A054B,0x0EB2F4BCF,0x0EEC9CF34,0x8D6D73F4,0x0FAFA9EB3, + 0x71A29A71,0x5F83B03,0x9F49C2C4,0x44300088,0x9665BFD1, + 0x9ED65D46,0x0C8F9BA60,0x2F605567,0x1B8EB19D,0x0C809FEB9, + 0x6AB0F596,0x6D4F042D,0x171ABC1,0x0D6752659,0x24E44C73, + 0x0EF02B7F7,0x89A3256A,0x58401E64,0x0D94AD744,0x0E5BE5722, + 0x34FAD805,0x1FCF7056,0x0AAD7F095,0x0C1090D4B,0x0C28ABD40, + 0x3CC6E4ED,0x0F7C03034,0x34A6414F,0x35A90E07,0x0C8426, + 0x617A702E,0x2E9C9E4B,0x0CEEB4287,0x0FCC3983D,0x0C9B9C8D0, + 0x0E02F0C9C,0x0E26142D,0x253AAAF,0x517492A6,0x0BDE4B4E0, + 0x0B66D7DA7,0x215E823B,0x59DE667F,0x7380FEF2,0x0C718B4E1, + 0x0ABCC29DB,0x826E1F66,0x0F60995F0,0x81B93308,0x32C0E9CE, + 0x0AED7D4A8,0x75C26137,0x6627B48A,0x86A04B91,0x0FD0FDED4, + 0x61328A64,0x37772F12,0x0B71117E0,0x0D04BD8B5,0x6C1C7B55, + 0x0F5FCDD8F,0x18F756B9,0x0C9009959,0x8787DB25,0x0E14E382E, + 0x39785258,0x0C8E33B0D,0x0E2B79532,0x7B3FF75A,0x0EBF8933A, + 0x0EEE8165E,0x0EF31D01A,0x86E615C2,0x3F1DE31D,0x9D46C418, + 0x5EBBF6B8,0x198CA52,0x86CC4AFE,0x75701E49,0x2158B12A, + 0x2EE88F38,0x51291219,0x571C3D3E,0x694FE8CD,0x8EAD1DDF, + 0x719AC3EC,0x65407983,0x0A623C53F,0x0E1FC6EF6,0x0F7C42633, + 0x0FF0F6CE7,0x8A9BB15B,0x2A29BA69,0x358B45EC,0x0C6FFEF75, + 0x5BB5540,0x0A8C97A66,0x0BA156953,0x9BC51B7B,0x49C89A49, + 0x0DDB26758,0x59C719E6,0x0A0554886,0x0C648A914,0x8AF87AEE, + 0x21452161,0x36E61062,0x2EFEEB7F,0x0B0F0ED15,0x657588C2, + 0x0FCEC4B1D,0x72F109A5,0x903038F8,0x0B57B9746,0x0D51B1404, + 0x0C264ECE5,0x0AA8D1406,0x0E1BED868,0x0E169C2A4,0x90DEBDDA, + 0x0C11A7AEB,0x0FCE013E8,0x820CEECA,0x8C23E563,0x0D42EA394, + 0x56C951C1,0x58777382,0x0A8380C9B,0x255E092F,0x809CDC30, + 0x4460681A,0x14621DE1,0x347D6427,0x4CBF3DB3,0x68BC261F, + 0x422740D0,0x0C4A3AD2C,0x0C9D63C06,0x24BF485D,0x0E34ADD47, + 0x0D5221D28,0x49C7E227,0x19E0A5E3,0x0E7D29F61,0x0A7912D4A, + 0x5DB77953,0x28D357FC,0x80036D7D,0x0BCC597FD,0x0D70A8A06, + 0x738CB736,0x1E627237,0x0D5D153F1,0x0C666DCFD,0x5A415D5D, + 0x73B4156B,0x0F30D9A10,0x7DAF7B36,0x7F682380,0x70FE0038, + 0x560BE589,0x8C0BADEE,0x0C2B422EA,0x45861DFA,0x81A2EEC8, + 0x0C4DFFDA1,0x441BB229,0x64CF934D,0x33E6AC7A,0x35D0340C, + 0x67D26D0A,0x7DA0CC10,0x6E2D5A8C,0x32C25A2D,0x0CA4E2C99, + 0x82FD715F,0x730B702C,0x4BDBB941,0x51430724,0x0A32A6B96, + 0x0C256A7C6,0x517FD8C4,0x1AA85938,0x52AFE354,0x1E22F907, + 0x56548374,0x88B0B9A2,0x404E4275,0x86C49DDD,0x0A93FC14D, + 0x3BD2006F,0x5D0D3116,0x3DD20E74,0x0DC64DB8D,0x15C149EB, + 0x0DB329993,0x0C2200439,0x0C0295BB0,0x3573EBA4,0x2A40A38B, + 0x0D6C379D9,0x67330973,0x0FF1D7F67,0x0FCFEB8DA,0x7C11A442, + 0x2066EFD5,0x1134E040,0x4170819D,0x0F49E009F,0x7EE85510, + 0x4E6D257E,0x2BDBE631,0x0BE404560,0x511DC4A4,0x0D6BDA8AC, + 0x2BC41233,0x9C0B6C2F,0x0AAAB064B,0x0A580198,0x0F1F16D7F, + 0x8F54AEFB,0x0D9772405,0x8CB50B4F,0x6F659F34,0x0D8B184EF, + 0x71A6660C,0x3C87DD1D,0x0D56793AE,0x0F8F6A87B,0x429C55E0, + 0x3CC4D090,0x957B708A,0x0AF391249,0x5EFEBB3B,0x0ECA17F75, + 0x6065A1A5,0x0FFCEFB48,0x14B3A01D,0x0ED93C1D9,0x262DCD1C, + 0x2A4ADCA4,0x0F6DE4EC1,0x204FAF08,0x1815DE4C,0x0AF836FCA, + 0x0D5E44DA4,0x0A7CAA491,0x0A9A3FED2,0x4F8DAC68,0x0C36C6784, + 0x0E239373F,0x879D948B,0x0DBECB69,0x0F59A45D,0x7211421F, + 0x0A6064A94,0x28AA48FC,0x4CD04E6D,0x3906DE80,0x3525094A, + 0x2326D98E,0x4937ED30,0x570E63ED,0x0A57EFB4A,0x0BD4878CA, + 0x19334A64,0x2E5F6B34,0x0A9D68771,0x0F8645F9F,0x0CA96F06, + 0x5D6CCE62,0x2F3257DC,0x140F0639,0x0B3BDA452,0x0C7EBA7C4, + 0x6BD754E8,0x941F2606,0x0DFD573C5,0x8DD14DE6,0x0CB4BA732, + 0x37A9B8B1,0x0A195A51D,0x0E535A96,0x351B897,0x0F629, + 0x4AE34157,0x0EDE6A6E1,0x2F03235,0x2EBB589A,0x6BE492AF, + 0x0B3E4B14,0x0FA6CFD67,0x43AF9772,0x0D5FC21A2,0x8852A2BB, + 0x5B4A42CB,0x0FFF0AC5D,0x9AD1B6EE,0x0C55544A5,0x0D64F693E, + 0x65D3D048,0x9485B90,0x63BD09DD,0x5E811DDD +}; + +#endif \ No newline at end of file diff --git a/src/main/security/rp-sign-key.c b/src/main/security/rp-sign-key.c new file mode 100644 index 0000000..dfa6c34 --- /dev/null +++ b/src/main/security/rp-sign-key.c @@ -0,0 +1,13 @@ +#include "security/rp-sign-key.h" + +const struct security_rp_sign_key security_rp_sign_key_white_eamuse = { + .data = "E-AMUSE3" +}; + +const struct security_rp_sign_key security_rp_sign_key_black_ps2 = { + .data = "GENTAKAH" +}; + +const struct security_rp_sign_key security_rp_sign_key_black_gfdmv4 = { + .data = "UDONHRKI" +}; \ No newline at end of file diff --git a/src/main/security/rp-sign-key.h b/src/main/security/rp-sign-key.h new file mode 100644 index 0000000..bcc317d --- /dev/null +++ b/src/main/security/rp-sign-key.h @@ -0,0 +1,33 @@ +#ifndef SECURITY_RP_SIGN_KEY_H +#define SECURITY_RP_SIGN_KEY_H + +#include + +/** + * Sign keys used to sign roundplug data. + */ +struct security_rp_sign_key { + char data[8]; +}; + +/** + * Signing key used to create eeprom signitures for all white eamuse dongles. + */ +extern const struct security_rp_sign_key security_rp_sign_key_white_eamuse; + +/** + * Signing key used to create eeprom signitures for all black dongles used + * on PS2 games (as far as we are aware of). + */ +extern const struct security_rp_sign_key security_rp_sign_key_black_ps2; + +/** + * Signing key used to create eeprom signitures for all black dongles used + * on the following games: + * + * - GF & DM V4 to V8 (TODO needs verification) + * - jubeat (1) + */ +extern const struct security_rp_sign_key security_rp_sign_key_black_gfdmv4; + +#endif \ No newline at end of file diff --git a/src/main/security/rp-util.h b/src/main/security/rp-util.h new file mode 100644 index 0000000..ab950ab --- /dev/null +++ b/src/main/security/rp-util.h @@ -0,0 +1,12 @@ +#ifndef SECURITY_RP_UTIL_H +#define SECURITY_RP_UTIL_H + +/** + * Enum for available roundplug types + */ +enum security_rp_util_rp_type { + SECURITY_RP_UTIL_RP_TYPE_BLACK = 0, + SECURITY_RP_UTIL_RP_TYPE_WHITE = 1, +}; + +#endif \ No newline at end of file diff --git a/src/main/security/rp.c b/src/main/security/rp.c new file mode 100644 index 0000000..eb37216 --- /dev/null +++ b/src/main/security/rp.c @@ -0,0 +1,84 @@ +#include "security/rp.h" + +#include "security/rp-blowfish.h" +#include "security/rp-enc-table.h" +#include "security/util.h" + +#include "util/crypto.h" +#include "util/log.h" + +static uint32_t security_rp_get_len_mcode(const struct security_mcode* mcode) +{ + uint32_t len; + + len = 0; + + while (len < sizeof(struct security_mcode) && + ((const char*) mcode)[len] != ' ') { + len++; + } + + return len; +} + +void security_rp_generate_signed_eeprom_data( + const struct security_mcode* boot_version, const uint32_t* boot_seeds, + const struct security_mcode* plug_mcode, + const struct security_id* plug_id, struct security_rp_eeprom* out) +{ + uint8_t encryption_key[sizeof(security_rp_enc_table_key_base)]; + uint32_t boot_version_len; + uint32_t idx; + uint32_t seed; + uint8_t* enc_key_section1; + uint8_t* enc_key_section2; + uint8_t data[32]; + struct blowfish ctx; + + log_assert(boot_version); + log_assert(boot_seeds); + log_assert(plug_mcode); + log_assert(plug_id); + log_assert(out); + + log_assert(boot_seeds[0] <= 16); + log_assert(boot_seeds[1] <= 16); + log_assert(boot_seeds[2] >= boot_seeds[1]); + + memcpy(encryption_key, security_rp_enc_table_key_base, + sizeof(security_rp_enc_table_key_base)); + + boot_version_len = security_rp_get_len_mcode(boot_version); + + idx = 0; + + for (uint32_t i = 0; i < sizeof(encryption_key); i++) { + encryption_key[i] ^= ((const char*) boot_version)[idx]; + idx = (idx + 1) % boot_version_len; + } + + seed = 16 * (boot_seeds[2] + 16 * (boot_seeds[1] + 16 * boot_seeds[0])); + enc_key_section1 = (uint8_t*) encryption_key; + enc_key_section2 = (uint8_t*) (((uint8_t*) encryption_key) + 14); + + memset(data, 0, sizeof(data)); + + data[0] = enc_key_section2[seed]; + data[1] = enc_key_section2[seed + 1]; + data[2] = plug_id->id[7]; + data[3] = plug_id->id[6]; + data[4] = plug_id->id[5]; + data[5] = plug_id->id[4]; + data[6] = plug_id->id[3]; + data[7] = plug_id->id[2]; + + security_rp_blowfish_init(&ctx, enc_key_section1, 14, seed); + security_rp_blowfish_enc(&ctx, data, &data[16], 8); + + for (uint8_t i = 0; i < sizeof(out->signature); i++) { + out->signature[i] = data[i + 16] ^ data[i + 22]; + } + + security_util_8_to_6_encode_reverse((const uint8_t*) plug_mcode, + out->packed_payload); +} \ No newline at end of file diff --git a/src/main/security/rp.h b/src/main/security/rp.h new file mode 100644 index 0000000..5922e2d --- /dev/null +++ b/src/main/security/rp.h @@ -0,0 +1,44 @@ +#ifndef SECURITY_RP_H +#define SECURITY_RP_H + +#include +#include + +#include "security/id.h" +#include "security/mcode.h" +#include "security/rp-util.h" + +/** + * Structure for data which is usually stored in the eeprom section of the + * dongle. This contains a signiture to verify the ROM's contents as well as + * the game this dongle is signed for. + */ +struct security_rp_eeprom { + uint8_t signature[6]; + uint8_t packed_payload[6]; +}; + +/** + * Generate signed eeprom data from non encrypted and unobfuscated data required + * to pass security checks on games using black (game specific) roundplugs. + * + * This implementation (rp (1)) is used by the following games + * - iidx 09 to 13 + * + * @param boot_version The boot version mcode that is used for bootstrapping + * the security backend (of the ezusb.dll). + * @param boot_seeds Boot seeds (three numbers >= 0) set when the game is + * bootstrapping the security backend (of the ezusb.dll). + * @param plug_mcode The mcode of the game to boot. Typically, this code is + * printed onto the housing of the black dongle. + * @param plug_id The id stored of the plug. This data is normally stored in the + * ROM area of the black dongle is is often refered to as the + * "PCBID" or "EAMID" when stored on the white dongle. + * @param out Pointer to the eeprom data struct for the resulting data. + */ +void security_rp_generate_signed_eeprom_data( + const struct security_mcode* boot_version, const uint32_t* boot_seeds, + const struct security_mcode* plug_mcode, + const struct security_id* plug_id, struct security_rp_eeprom* out); + +#endif \ No newline at end of file diff --git a/src/main/security/rp2.c b/src/main/security/rp2.c new file mode 100644 index 0000000..b3f1af5 --- /dev/null +++ b/src/main/security/rp2.c @@ -0,0 +1,113 @@ +#include "security/rp2.h" +#include "security/util.h" + +#include "util/crypto.h" +#include "util/log.h" + +static const uint8_t security_rp2_sign_key_base_black[] = { + 0x32, 0x44, 0x58, 0x47, 0x4C, 0x44, 0x41, 0x43 +}; + +static const uint8_t security_rp2_sign_key_base_white[] = { + 0x45, 0x2D, 0x41, 0x4D, 0x55, 0x53, 0x45, 0x33 +}; + +static uint8_t security_rp2_signature_scramble_table[16] = { + 0x0C, 0x02, 0x0F, 0x01, + 0x07, 0x09, 0x04, 0x0A, + 0x00, 0x0E, 0x03, 0x0D, + 0x0B, 0x05, 0x08, 0x06 +}; + +static void security_rp2_create_signiture(const uint8_t *plug_id_enc, + const uint8_t *sign_key_packed, uint8_t *out) +{ + uint8_t data[14]; + uint8_t md5[16]; + uint8_t buffer[18]; + + memcpy(data, plug_id_enc, 8); + memcpy(data + 8, sign_key_packed, 6); + + crypto_init(); + md5_compute(data, 14, md5, sizeof(md5)); + crypto_fini(); + + for (int i = 0; i < 16; i++) { + buffer[i] = md5[security_rp2_signature_scramble_table[i]]; + } + + buffer[16] = 0xDE; + buffer[17] = 0xAD; + + for (int i = 0; i < 6; i++) { + out[i] = buffer[i + 12] ^ buffer[i + 6] ^ buffer[i]; + } +} + +void security_rp2_generate_signed_eeprom_data( + enum security_rp_util_rp_type type, + const struct security_mcode* boot_version, + const struct security_mcode* plug_mcode, + const struct security_id* plug_id, struct security_rp2_eeprom* out) +{ + uint8_t sign_key[8]; + uint8_t plug_id_enc[8]; + char* boot_version_str; + + log_assert(boot_version); + log_assert(plug_mcode); + log_assert(plug_id); + log_assert(out); + + boot_version_str = (char*) boot_version; + + /* -------------------------------- */ + + switch (type) { + case SECURITY_RP_UTIL_RP_TYPE_BLACK: + memcpy(sign_key, security_rp2_sign_key_base_black, + sizeof(sign_key)); + + sign_key[0] = boot_version_str[0] ^ sign_key[0]; + sign_key[1] ^= boot_version_str[1]; + sign_key[2] = boot_version_str[2] ^ sign_key[2]; + sign_key[3] = boot_version_str[3] ^ sign_key[3]; + sign_key[4] = boot_version_str[4] ^ sign_key[4]; + sign_key[5] ^= boot_version_str[5]; + sign_key[6] = boot_version_str[6] ^ sign_key[6]; + sign_key[7] = boot_version_str[7] ^ sign_key[7]; + + break; + + case SECURITY_RP_UTIL_RP_TYPE_WHITE: + memcpy(sign_key, security_rp2_sign_key_base_white, + sizeof(sign_key)); + + break; + + default: + log_assert(false); + break; + } + + for (uint8_t i = 0; i < sizeof(sign_key); i++) { + sign_key[i] ^= 0x40; + } + + security_util_8_to_6_encode(sign_key, sign_key); + + plug_id_enc[0] = plug_id->checksum; + plug_id_enc[1] = plug_id->id[2]; + plug_id_enc[2] = plug_id->id[3]; + plug_id_enc[3] = plug_id->id[4]; + plug_id_enc[4] = plug_id->id[5]; + plug_id_enc[5] = plug_id->id[6]; + plug_id_enc[6] = plug_id->id[7]; + plug_id_enc[7] = plug_id->id[1]; + + security_rp2_create_signiture(plug_id_enc, sign_key, (uint8_t*) out); + + security_util_8_to_6_encode((const uint8_t*) plug_mcode, + out->packed_payload); +} \ No newline at end of file diff --git a/src/main/security/rp2.h b/src/main/security/rp2.h new file mode 100644 index 0000000..32f7837 --- /dev/null +++ b/src/main/security/rp2.h @@ -0,0 +1,46 @@ +#ifndef SECURITY_RP2_H +#define SECURITY_RP2_H + +#include +#include + +#include "security/id.h" +#include "security/mcode.h" +#include "security/rp-util.h" + +/** + * Structure for data which is usually stored in the eeprom section of the + * dongle. This contains a signiture to verify the ROM's contents as well as + * the game this dongle is signed for. + */ +struct security_rp2_eeprom { + uint8_t signature[6]; + uint8_t packed_payload[6]; +}; + +/** + * Generate signed eeprom data from non encrypted and unobfuscated data required + * to pass security checks on games using black (game specific) and white + * (eamuse) roundplugs. + * + * This implementation (rp 2) is used by the following games + * - iidx 14 to 17 + * + * @param type Type of plug to sign eeprom data for (black or white). + * @param boot_version The boot version mcode that is used for bootstrapping + * the security backend (of the ezusb.dll). + * @param plug_mcode The mcode of the game to boot. Typically, this code is + * printed onto the housing of the black dongle. For white + * dongles, the "mcode" @@@@@@@@ is used. + * @param plug_id The id stored on the plug. This data is normally stored in the + * ROM area of the black dongle is is often refered to as the + * "PCBID" or "EAMID" when stored on the white dongle. + * @param out Pointer to the eeprom data struct for the resulting data. + */ +void security_rp2_generate_signed_eeprom_data( + enum security_rp_util_rp_type type, + const struct security_mcode* boot_version, + const struct security_mcode* plug_mcode, + const struct security_id* plug_id, struct security_rp2_eeprom* out); + +#endif \ No newline at end of file diff --git a/src/main/security/rp3.c b/src/main/security/rp3.c new file mode 100644 index 0000000..b86e390 --- /dev/null +++ b/src/main/security/rp3.c @@ -0,0 +1,78 @@ +#include "security/rp3.h" +#include "security/rp-util.h" +#include "security/util.h" + +#include "util/crc.h" +#include "util/crypto.h" +#include "util/log.h" + +static uint8_t security_rp3_signature_scramble_table[] = { + 0x0C, 0x02, 0x0F, 0x01, + 0x07, 0x09, 0x04, 0x0A, + 0x00, 0x0E, 0x03, 0x0D, + 0x0B, 0x05, 0x08, 0x06 +}; + +static void security_rp3_create_signature(const uint8_t *plug_id, + const uint8_t *sign_key_packed, uint8_t *out) +{ + uint8_t data[14]; + uint8_t md5[16]; + uint8_t buffer[18]; + + memcpy(data, plug_id, 8); + memcpy(data + 8, sign_key_packed, 6); + + crypto_init(); + md5_compute(data, 14, md5, sizeof(md5)); + crypto_fini(); + + for (int i = 0; i < 16; i++) { + buffer[i] = md5[security_rp3_signature_scramble_table[i]]; + } + + buffer[16] = 0xDE; + buffer[17] = 0xAD; + + for (int i = 0; i < 6; i++) { + out[i] = buffer[i + 12] ^ buffer[i + 6] ^ buffer[i]; + } +} + +void security_rp3_generate_signed_eeprom_data( + enum security_rp_util_rp_type type, + const struct security_rp_sign_key* sign_key, + const struct security_mcode* plug_mcode, + const struct security_id* plug_id, struct security_rp3_eeprom* out) +{ + uint8_t sign_key_tmp[8]; + uint8_t sign_key_packed[6]; + uint8_t plug_id_reversed[8]; + + log_assert(sign_key); + log_assert(plug_mcode); + log_assert(plug_id); + log_assert(out); + + memcpy(sign_key_tmp, sign_key, sizeof(sign_key_tmp)); + + if (type == SECURITY_RP_UTIL_RP_TYPE_BLACK) { + for (int i = 0 ; i < sizeof(sign_key_tmp); i++) { + sign_key_tmp[i] ^= ((const uint8_t*) plug_mcode)[i]; + } + } + + security_util_8_to_6_encode(sign_key_tmp, sign_key_packed); + + for (int i = 0; i < sizeof(plug_id_reversed); i++) { + plug_id_reversed[i] = plug_id->id[7 - i]; + } + + security_util_8_to_6_encode((const uint8_t*) plug_mcode, + out->packed_payload); + memset(out->zeros, 0, sizeof(out->zeros)); + + security_rp3_create_signature(plug_id_reversed, sign_key_packed, + out->signature); + out->crc = crc8((uint8_t *) out, sizeof(*out) - 1, 0); +} diff --git a/src/main/security/rp3.h b/src/main/security/rp3.h new file mode 100644 index 0000000..43fc5cc --- /dev/null +++ b/src/main/security/rp3.h @@ -0,0 +1,50 @@ +#ifndef SECURITY_RP3_H +#define SECURITY_RP3_H + +#include +#include + +#include "security/id.h" +#include "security/mcode.h" +#include "security/rp-sign-key.h" +#include "security/rp-util.h" + +/** + * Structure for data which is usually stored in the eeprom section of the + * dongle. This contains a signiture to verify the ROM's contents as well as + * the game this dongle is signed for. + */ +struct security_rp3_eeprom { + uint8_t signature[6]; + uint8_t packed_payload[6]; + uint8_t zeros[19]; + uint8_t crc; +}; + +/** + * Generate signed eeprom data from non encrypted and unobfuscated data required + * to pass security checks on games using black (game specific) and white + * (eamuse) roundplugs. + * + * Used on the following games: + * - jubeat + * + * @param type Type of plug to sign eeprom data for (black or white). + * @param sign_key The key to use for generating the signiture. + * This key can be extracted from the executables of the games and might + * be re-used for multiple games of the same series or generation. + * @param plug_mcode The mcode of the game to boot. Typically, this code is + * printed onto the housing of the black dongle. For white + * dongles, the "mcode" @@@@@@@@ is used. + * @param plug_id The id stored on the plug. This data is normally stored in the + * ROM area of the black dongle is is often refered to as the + * "PCBID" or "EAMID" when stored on the white dongle. + * @param out Pointer to the eeprom data struct for the resulting data. + */ +void security_rp3_generate_signed_eeprom_data( + enum security_rp_util_rp_type type, + const struct security_rp_sign_key* sign_key, + const struct security_mcode* plug_mcode, + const struct security_id* plug_id, struct security_rp3_eeprom* out); + +#endif \ No newline at end of file diff --git a/src/main/security/util.c b/src/main/security/util.c new file mode 100644 index 0000000..e6e5add --- /dev/null +++ b/src/main/security/util.c @@ -0,0 +1,60 @@ +#include "security/util.h" + +void security_util_8_to_6_encode(const uint8_t *in, uint8_t *out) +{ + uint8_t tmp[8]; + int i; + + for (i = 0 ; i < 8 ; i++) { + tmp[i] = (in[i] - 0x20) & 0x3F; + } + + out[0] = (tmp[0] >> 0) | (tmp[1] << 6); + out[1] = (tmp[1] >> 2) | (tmp[2] << 4); + out[2] = (tmp[2] >> 4) | (tmp[3] << 2); + + out[3] = (tmp[4] >> 0) | (tmp[5] << 6); + out[4] = (tmp[5] >> 2) | (tmp[6] << 4); + out[5] = (tmp[6] >> 4) | (tmp[7] << 2); +} + +void security_util_6_to_8_decode(const uint8_t *in, uint8_t *out) +{ + int i; + + out[0] = ((in[0] >> 0) & 0x3F); + out[1] = ((in[0] >> 6) & 0x03) | ((in[1] << 2) & 0x3C); + out[2] = ((in[1] >> 4) & 0x0F) | ((in[2] << 4) & 0x30); + out[3] = ((in[2] >> 2) & 0x3F); + + out[4] = ((in[3] >> 0) & 0x3F); + out[5] = ((in[3] >> 6) & 0x03) | ((in[4] << 2) & 0x3C); + out[6] = ((in[4] >> 4) & 0x0F) | ((in[5] << 4) & 0x30); + out[7] = ((in[5] >> 2) & 0x3F); + + for (i = 0 ; i < 8 ; i++) { + out[i] += 0x20; + } +} + +void security_util_8_to_6_encode_reverse(const uint8_t *in, uint8_t *out) +{ + out[0] = ((in[7] - 0x20) << 2) | (((in[6] - 0x20) >> 4) & 0x03); + out[1] = ((in[6] - 0x20) << 4) | (((in[5] - 0x20) >> 2) & 0x0F); + out[2] = ((in[5] - 0x20) << 6) | ((in[4] - 0x20) & 0x3F); + out[3] = ((in[3] - 0x20) << 2) | (((in[2] - 0x20) >> 4) & 0x03); + out[4] = ((in[2] - 0x20) << 4) | (((in[1] - 0x20) >> 2) & 0x0F); + out[5] = ((in[1] - 0x20) << 6) | ((in[0] - 0x20) & 0x3F); +} + +void security_util_6_to_8_decode_reverse(const uint8_t *in, uint8_t *out) +{ + out[0] = (in[5] & 0x3F) + 0x20; + out[1] = ((in[5] >> 6) | (((in[4] & 0xF) << 2) + 0x20)); + out[2] = ((in[4] >> 4) | (((in[3] & 0x03) << 4) + 0x20)); + out[3] = (in[3] >> 2) + 0x20; + out[4] = (in[2] & 0x3F) + 0x20; + out[5] = (in[2] >> 6) | (((in[1] & 0x0F) << 2) + 0x20); + out[6] = (in[1] >> 4) | (((in[0] & 0x03) << 4) + 0x20); + out[7] = (in[0] >> 2) + 0x20; +} diff --git a/src/main/security/util.h b/src/main/security/util.h new file mode 100644 index 0000000..d5078dc --- /dev/null +++ b/src/main/security/util.h @@ -0,0 +1,40 @@ +#ifndef SECURITY_UTIL_H +#define SECURITY_UTIL_H + +#include + +/** + * Pack 8 bytes of input data into 6 bytes payload output data. This is used to + * pack/encode mcodes from roundplug dongles, e.g. GCH44JAA. + * + * @param in Input data of 8 bytes length, e.g. mcode. + * @param out Target buffer for encoded data with at least 6 bytes capacity. + */ +void security_util_8_to_6_encode(const uint8_t *in, uint8_t *out); + +/** + * Decode/unpack 6 bytes of encoded roundplug data, e.g. encoded mcode, to + * the full 8 bytes width. + * + * @param in Input encoded payload of 6 bytes length. + * @param out Target buffer for decoded data with at least 8 bytes capacity. + */ +void security_util_6_to_8_decode(const uint8_t *in, uint8_t *out); + +/** + * Same as security_util_8_to_6_encode but in reversed byte order. + * + * @param in Input data of 8 bytes length, e.g. mcode. + * @param out Target buffer for encoded data with at least 6 bytes capacity. + */ +void security_util_8_to_6_encode_reverse(const uint8_t *in, uint8_t *out); + +/** + * Same as security_util_6_to_8_decode but in reversed byte order. + * + * @param in Input encoded payload of 6 bytes length. + * @param out Target buffer for decoded data with at least 8 bytes capacity. + */ +void security_util_6_to_8_decode_reverse(const uint8_t *in, uint8_t *out); + +#endif diff --git a/src/main/unicorntail/Module.mk b/src/main/unicorntail/Module.mk new file mode 100644 index 0000000..bd6d360 --- /dev/null +++ b/src/main/unicorntail/Module.mk @@ -0,0 +1,17 @@ +avsdlls += unicorntail + +deplibs_unicorntail := \ + avs \ + +libs_unicorntail := \ + p3io \ + p3ioemu \ + hook \ + hooklib \ + util \ + +src_unicorntail := \ + dllmain.c \ + p3io.c \ + usbmem.c \ + diff --git a/src/main/unicorntail/Sources b/src/main/unicorntail/Sources new file mode 100644 index 0000000..31774f4 --- /dev/null +++ b/src/main/unicorntail/Sources @@ -0,0 +1,14 @@ +!include ../Common.inc + +TARGETNAME = unicorntail +TARGETTYPE = DYNLINK + +SOURCES = \ + unicorntail.c \ + +TARGETLIBS = $(SDK_LIB_PATH)/kernel32.lib \ + ../libhook/$(OBJDIR)/$(O)/libhook.lib \ + ../libp3io/$(OBJDIR)/$(O)/libp3io.lib \ + ../libutil/$(OBJDIR)/$(O)/libutil.lib \ + ../imports/$(O)/libavs.lib \ + diff --git a/src/main/unicorntail/dllmain.c b/src/main/unicorntail/dllmain.c new file mode 100644 index 0000000..5c55ccc --- /dev/null +++ b/src/main/unicorntail/dllmain.c @@ -0,0 +1,69 @@ +#include + +#include + +#include "ddrhook/p3io.h" +#include "ddrhook/usbmem.h" + +#include "hook/iohook.h" + +#include "hooklib/app.h" + +#include "imports/avs.h" + +#include "unicorntail/p3io.h" +#include "unicorntail/usbmem.h" + +#include "util/defs.h" +#include "util/log.h" + +static bool my_dll_entry_init(char *sidcode, struct property_node *param); +static bool my_dll_entry_main(void); + +static const irp_handler_t unicorntail_handlers[] = { + p3io_filter_dispatch_irp, + usbmem_dispatch_irp, +}; + +static bool my_dll_entry_init(char *sidcode, struct property_node *param) +{ + log_info("--- Begin unicorntail dll_entry_init ---"); + + iohook_init(unicorntail_handlers, lengthof(unicorntail_handlers)); + p3io_filter_init(); + usbmem_init(); + + log_info("--- End unicorntail dll_entry_init ---"); + + return app_hook_invoke_init(sidcode, param); +} + +static bool my_dll_entry_main(void) +{ + bool result; + + result = app_hook_invoke_main(); + + usbmem_fini(); + p3io_filter_fini(); + + return result; +} + +BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) +{ + if (reason != DLL_PROCESS_ATTACH) { + goto end; + } + + log_to_external( + log_body_misc, + log_body_info, + log_body_warning, + log_body_fatal); + + app_hook_init(my_dll_entry_init, my_dll_entry_main); + +end: + return TRUE; +} diff --git a/src/main/unicorntail/p3io.c b/src/main/unicorntail/p3io.c new file mode 100644 index 0000000..8652188 --- /dev/null +++ b/src/main/unicorntail/p3io.c @@ -0,0 +1,196 @@ +#include + +#include +#include + +#include "p3io/cmd.h" +#include "p3io/frame.h" + +#include "p3ioemu/uart.h" + +#include "unicorntail/p3io.h" + +#include "util/array.h" +#include "util/log.h" +#include "util/str.h" + +static bool p3io_match_irp_locked(const struct irp *irp); + +static HRESULT p3io_handle_open(struct irp *irp); +static HRESULT p3io_handle_close(struct irp *irp); +static HRESULT p3io_handle_write(struct irp *irp); +static HRESULT p3io_handle_read(struct irp *irp); + +static CRITICAL_SECTION p3io_handle_lock; +static struct array p3io_handles; +static CRITICAL_SECTION p3io_cmd_lock; +static uint8_t p3io_resp_bytes[260]; +static struct iobuf p3io_resp; + +void p3io_filter_init(void) +{ + InitializeCriticalSection(&p3io_handle_lock); + InitializeCriticalSection(&p3io_cmd_lock); + + p3io_uart_set_path(0, L"COM4"); + + p3io_resp.bytes = p3io_resp_bytes; + p3io_resp.nbytes = sizeof(p3io_resp_bytes); + p3io_resp.pos = 0; +} + +void p3io_filter_fini(void) +{ + DeleteCriticalSection(&p3io_cmd_lock); + DeleteCriticalSection(&p3io_handle_lock); +} + +HRESULT p3io_filter_dispatch_irp(struct irp *irp) +{ + bool match; + + EnterCriticalSection(&p3io_handle_lock); + match = p3io_match_irp_locked(irp); + LeaveCriticalSection(&p3io_handle_lock); + + if (!match) { + return irp_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return p3io_handle_open(irp); + case IRP_OP_CLOSE: return p3io_handle_close(irp); + case IRP_OP_WRITE: return p3io_handle_write(irp); + case IRP_OP_READ: return p3io_handle_read(irp); + default: return irp_invoke_next(irp); + } +} + +static bool p3io_match_irp_locked(const struct irp *irp) +{ + size_t i; + + if (irp->op == IRP_OP_OPEN) { + return wstr_ends_with(irp->open_filename, L"\\p3io"); + } else { + for (i = 0 ; i < p3io_handles.nitems ; i++) { + if (irp->fd == *array_item(HANDLE, &p3io_handles, i)) { + return true; + } + } + + return false; + } +} + +static HRESULT p3io_handle_open(struct irp *irp) +{ + HRESULT hr; + + hr = irp_invoke_next(irp); + + if (FAILED(hr)) { + return hr; + } + + log_misc("Got p3io fd %p", irp->fd); + + EnterCriticalSection(&p3io_handle_lock); + *array_append(HANDLE, &p3io_handles) = irp->fd; + LeaveCriticalSection(&p3io_handle_lock); + + return hr; +} + +static HRESULT p3io_handle_close(struct irp *irp) +{ + size_t i; + + log_misc("Releasing p3io fd %p", irp->fd); + + EnterCriticalSection(&p3io_handle_lock); + + for (i = 0 ; i < p3io_handles.nitems ; i++) { + if (irp->fd == array_item(HANDLE, &p3io_handles, i)) { + array_remove(HANDLE, &p3io_handles, i); + + break; + } + } + + LeaveCriticalSection(&p3io_handle_lock); + + return S_OK; +} + +static HRESULT p3io_handle_write(struct irp *irp) +{ + union p3io_req_any req; + union p3io_resp_any resp; + struct iobuf desc; + HRESULT hr; + + desc.bytes = req.raw; + desc.nbytes = sizeof(req); + desc.pos = 0; + + hr = p3io_frame_decode(&desc, &irp->write); + + if (FAILED(hr)) { + return hr; + } + + switch (p3io_req_cmd(&req)) { + case P3IO_CMD_RS232_OPEN_CLOSE: + EnterCriticalSection(&p3io_cmd_lock); + p3io_uart_cmd_open_close(&req.rs232_open_close, &resp.u8); + + break; + + case P3IO_CMD_RS232_WRITE: + EnterCriticalSection(&p3io_cmd_lock); + p3io_uart_cmd_write(&req.rs232_write, &resp.rs232_write); + + break; + + case P3IO_CMD_RS232_READ: + EnterCriticalSection(&p3io_cmd_lock); + p3io_uart_cmd_read(&req.rs232_read, &resp.rs232_read); + + break; + + default: + /* Non-UART command, break out here. */ + irp->write.pos = 0; + + return irp_invoke_next(irp); + } + + /* Frame up and queue a response packet */ + + hr = p3io_frame_encode(&p3io_resp, &resp.hdr, resp.hdr.nbytes + 1); + LeaveCriticalSection(&p3io_cmd_lock); + + return S_OK; +} + +static HRESULT p3io_handle_read(struct irp *irp) +{ + struct const_iobuf tmp; + + EnterCriticalSection(&p3io_cmd_lock); + + if (p3io_resp.pos == 0) { + LeaveCriticalSection(&p3io_cmd_lock); + + return irp_invoke_next(irp); + } else { + iobuf_flip(&tmp, &p3io_resp); + iobuf_move(&irp->read, &tmp); + p3io_resp.pos = 0; + + LeaveCriticalSection(&p3io_cmd_lock); + + return S_OK; + } +} diff --git a/src/main/unicorntail/p3io.h b/src/main/unicorntail/p3io.h new file mode 100644 index 0000000..b07c9bd --- /dev/null +++ b/src/main/unicorntail/p3io.h @@ -0,0 +1,10 @@ +#ifndef UNICORNTAIL_P3IO_H +#define UNICORNTAIL_P3IO_H + +#include "hook/iohook.h" + +void p3io_filter_init(void); +void p3io_filter_fini(void); +HRESULT p3io_filter_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/unicorntail/unicorntail.def b/src/main/unicorntail/unicorntail.def new file mode 100644 index 0000000..e45383d --- /dev/null +++ b/src/main/unicorntail/unicorntail.def @@ -0,0 +1,4 @@ +LIBRARY unicorntail + +EXPORTS + DllMain@12 @1 NONAME diff --git a/src/main/unicorntail/usbmem.c b/src/main/unicorntail/usbmem.c new file mode 100644 index 0000000..c760a94 --- /dev/null +++ b/src/main/unicorntail/usbmem.c @@ -0,0 +1,185 @@ +#include + +#include +#include + +#include "hook/iohook.h" + +#include "util/log.h" +#include "util/str.h" + +static bool usbmem_match_irp(const struct irp *irp); +static HRESULT usbmem_dispatch_irp_locked(struct irp *irp); +static HRESULT usbmem_handle_open(struct irp *irp); +static HRESULT usbmem_handle_close(struct irp *irp); +static HRESULT usbmem_handle_write(struct irp *irp); +static HRESULT usbmem_handle_read(struct irp *irp); + +static CRITICAL_SECTION usbmem_lock; +static HANDLE usbmem_fd; +static bool usbmem_pending; +static char usbmem_response[64]; + +void usbmem_init(void) +{ + InitializeCriticalSection(&usbmem_lock); + usbmem_pending = false; +} + +void usbmem_fini(void) +{ + struct irp irp; + + if (usbmem_fd != NULL && usbmem_fd != INVALID_HANDLE_VALUE) { + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_CLOSE; + irp.fd = usbmem_fd; + + irp_invoke_next(&irp); + } + + DeleteCriticalSection(&usbmem_lock); +} + +HRESULT usbmem_dispatch_irp(struct irp *irp) +{ + HRESULT hr; + + /* usbmem is not performance-critical in any way so we can use coarse + locking. */ + + EnterCriticalSection(&usbmem_lock); + + if (!usbmem_match_irp(irp)) { + LeaveCriticalSection(&usbmem_lock); + + return irp_invoke_next(irp); + } else { + hr = usbmem_dispatch_irp_locked(irp); + LeaveCriticalSection(&usbmem_lock); + + return hr; + } +} + +static bool usbmem_match_irp(const struct irp *irp) +{ + if (irp->op == IRP_OP_OPEN) { + return wstr_eq(irp->open_filename, L"COM3"); + } else { + /* This will not match any IRPs at all unless IRP_OP_OPEN failed and + we set usbmem_fd to a non-NULL HANDLE (i.e. we started emulation). */ + return irp->fd == usbmem_fd; + } +} + +static HRESULT usbmem_dispatch_irp_locked(struct irp *irp) +{ + switch (irp->op) { + case IRP_OP_OPEN: return usbmem_handle_open(irp); + case IRP_OP_CLOSE: return usbmem_handle_close(irp); + case IRP_OP_WRITE: return usbmem_handle_write(irp); + case IRP_OP_READ: return usbmem_handle_read(irp); + default: return E_NOTIMPL; + } +} + +static HRESULT usbmem_handle_open(struct irp *irp) +{ + HRESULT hr; + + hr = irp_invoke_next(irp); + + if (SUCCEEDED(hr)) { + log_info("Opened a real usbmem port"); + + return hr; + } + + log_info("Failed to open real usbmem, will emulate one instead"); + + /* Opening the real COM3 port failed. Open a fake FD, return that, and + start emulating a usbmem unit. */ + + if (usbmem_fd == NULL || usbmem_fd == INVALID_HANDLE_VALUE) { + usbmem_fd = iohook_open_dummy_fd(); + } + + irp->fd = usbmem_fd; + + return S_OK; +} + +static HRESULT usbmem_handle_close(struct irp *irp) +{ + usbmem_fd = NULL; + + return irp_invoke_next(irp); +} + +static HRESULT usbmem_handle_write(struct irp *irp) +{ + char request[64]; + size_t nbytes; + + nbytes = irp->write.nbytes - irp->write.pos; + + if (nbytes > sizeof(request)) { + nbytes = sizeof(request); + } + + memcpy(request, &irp->write.bytes[irp->write.pos], nbytes); + irp->write.pos += nbytes; + + if (nbytes > 0) { + request[nbytes - 1] = '\0'; /* This is always a CR. */ + } else { + request[0] = '\0'; /* Shouldn't ever happen but w/e */ + } + + log_misc(">%s", request); + + if (request[0] != '\0') { + if (str_eq(request, "sver")) { + str_cpy(usbmem_response, + sizeof(usbmem_response), + "done GQHDXJAA UNKNOWN"); + } else if ( + str_eq(request, "on_a") || + str_eq(request, "on_b") || + str_eq(request, "offa") || + str_eq(request, "offb") || + strncmp(request, "lma ", 4) == 0 || + strncmp(request, "lmb ", 4) == 0 ) { + str_cpy(usbmem_response, sizeof(usbmem_response), "done"); + } else { + str_cpy(usbmem_response, sizeof(usbmem_response), "not connected"); + } + } + + usbmem_pending = true; + + return S_OK; +} + +static HRESULT usbmem_handle_read(struct irp *irp) +{ + size_t rlength; + + if (usbmem_pending) { + if (strlen(usbmem_response) > 0) { + log_misc("%s", usbmem_response); + } + + str_cat(usbmem_response, sizeof(usbmem_response), "\r>"); + usbmem_pending = false; + } + + rlength = strlen(usbmem_response); + memcpy(&irp->read.bytes[irp->read.pos], usbmem_response, rlength); + irp->read.pos += rlength; + + memset(usbmem_response, 0, sizeof(usbmem_response)); + + return S_OK; +} diff --git a/src/main/unicorntail/usbmem.h b/src/main/unicorntail/usbmem.h new file mode 100644 index 0000000..5872566 --- /dev/null +++ b/src/main/unicorntail/usbmem.h @@ -0,0 +1,10 @@ +#ifndef DDRHOOK_USBMEM_H +#define DDRHOOK_USBMEM_H + +#include "hook/iohook.h" + +void usbmem_init(void); +void usbmem_fini(void); +HRESULT usbmem_dispatch_irp(struct irp *irp); + +#endif diff --git a/src/main/util/Module.mk b/src/main/util/Module.mk new file mode 100644 index 0000000..0e5a93b --- /dev/null +++ b/src/main/util/Module.mk @@ -0,0 +1,21 @@ +libs += util + +src_util := \ + array.c \ + cmdline.c \ + crc.c \ + crypto.c \ + fs.c \ + hex.c \ + hr.c \ + iobuf.c \ + list.c \ + log.c \ + mem.c \ + msg-thread.c \ + net.c \ + str.c \ + thread.c \ + time.c \ + winres.c \ + diff --git a/src/main/util/array.c b/src/main/util/array.c new file mode 100644 index 0000000..2e07df7 --- /dev/null +++ b/src/main/util/array.c @@ -0,0 +1,58 @@ +#include +#include +#include + +#include "util/array.h" +#include "util/log.h" +#include "util/mem.h" + +void array_init(struct array *array) +{ + memset(array, 0, sizeof(*array)); +} + +void array_remove_(size_t itemsz, struct array *array, size_t i) +{ + log_assert(i < array->nitems); + + array->nitems--; + + memmove(((uint8_t *) array->items) + i * itemsz, + ((uint8_t *) array->items) + (i + 1) * itemsz, + (array->nitems - i) * itemsz); +} + +void *array_reserve_(size_t itemsz, struct array *array, size_t nitems) +{ + size_t new_nalloced; + size_t new_nitems; + void *result; + + new_nitems = array->nitems + nitems; + + if (new_nitems > array->nalloced) { + new_nalloced = array->nalloced; + + if (new_nalloced == 0) { + new_nalloced = 1; + } + + while (new_nalloced < new_nitems) { + new_nalloced *= 2; + } + + array->items = xrealloc(array->items, new_nalloced * itemsz); + array->nalloced = new_nalloced; + } + + result = ((uint8_t *) array->items) + array->nitems * itemsz; + array->nitems = new_nitems; + + return result; +} + +void array_fini(struct array *array) +{ + free(array->items); +} + diff --git a/src/main/util/array.h b/src/main/util/array.h new file mode 100644 index 0000000..a9fe4a6 --- /dev/null +++ b/src/main/util/array.h @@ -0,0 +1,29 @@ +#ifndef UTIL_ARRAY_H +#define UTIL_ARRAY_H + +#include + +struct array { + void *items; + size_t nitems; + size_t nalloced; +}; + +#define array_item(type, array, i) \ + (&(((type *) (array)->items)[i])) + +#define array_reserve(type, array, nitems) \ + ((type *) array_reserve_(sizeof(type), array, nitems)) + +#define array_remove(type, array, i) \ + array_remove_(sizeof(type), array, i) + +#define array_append(type, array) \ + array_reserve(type, array, 1) + +void array_init(struct array *array); +void *array_reserve_(size_t itemsz, struct array *array, size_t nitems); +void array_remove_(size_t itemsz, struct array *array, size_t i); +void array_fini(struct array *array); + +#endif diff --git a/src/main/util/cmdline.c b/src/main/util/cmdline.c new file mode 100644 index 0000000..5eaa029 --- /dev/null +++ b/src/main/util/cmdline.c @@ -0,0 +1,129 @@ +#include "util/cmdline.h" +#include "util/mem.h" + +#include +#include +#include +#include + +#include + +static void push_argv(int *argc, char ***argv, const char *begin, + const char *end) +{ + size_t nchars; + char *str; + + (*argc)++; + *argv = xrealloc(*argv, *argc * sizeof(char **)); + + nchars = end - begin; + str = xmalloc(nchars + 1); + memcpy(str, begin, nchars); + str[nchars] = '\0'; + + (*argv)[*argc - 1] = str; +} + +void args_recover(int *argc_out, char ***argv_out) +{ + int argc; + char **argv; + const char *begin; + const char *pos; + bool quote; + + argc = 0; + argv = NULL; + quote = false; + + for (begin = pos = GetCommandLine() ; *pos ; pos++) { + switch (*pos) { + case '"': + if (!quote) { + quote = true; + begin = pos + 1; + } else { + push_argv(&argc, &argv, begin, pos); + + quote = false; + begin = NULL; + } + + break; + + case ' ': + if (!quote && begin != NULL) { + push_argv(&argc, &argv, begin, pos); + begin = NULL; + } + + break; + + default: + if (begin == NULL) { + begin = pos; + } + + break; + } + } + + if (begin != NULL && !quote) { + push_argv(&argc, &argv, begin, pos); + } + + *argc_out = argc; + *argv_out = argv; +} + +void args_free(int argc, char **argv) +{ + int i; + + for (i = 0 ; i < argc ; i++) { + free(argv[i]); + } + + free(argv); +} + +char *args_join(int argc, char **argv) +{ + char *pos; + char *str; + uint32_t nchars; + uint32_t part_len; + int i; + + nchars = 0; + + for (i = 0 ; i < argc ; i++) { + /* 3 = leading space plus two surrounding quotes. + The first element has no leading space, but this is counterbalanced + by the fact that the string needs a NUL terminator. */ + nchars += 3 + strlen(argv[i]); + } + + str = xmalloc(nchars); + pos = str; + + for (i = 0 ; i < argc ; i++) { + if (i != 0) { + *pos++ = ' '; + } + + *pos++ = '"'; + + part_len = strlen(argv[i]); + memcpy(pos, argv[i], part_len); + pos += part_len; + + *pos++ = '"'; + } + + *pos = '\0'; + + return str; +} + diff --git a/src/main/util/cmdline.h b/src/main/util/cmdline.h new file mode 100644 index 0000000..0582bdb --- /dev/null +++ b/src/main/util/cmdline.h @@ -0,0 +1,8 @@ +#ifndef UTIL_CMDLINE_H +#define UTIL_CMDLINE_H + +void args_recover(int *argc, char ***argv); +void args_free(int argc, char **argv); +char *args_join(int argc, char **argv); + +#endif diff --git a/src/main/util/codepage.h b/src/main/util/codepage.h new file mode 100644 index 0000000..8532752 --- /dev/null +++ b/src/main/util/codepage.h @@ -0,0 +1,6 @@ +#ifndef UTIL_CODEPAGE_H +#define UTIL_CODEPAGE_H + +#define CP_SHIFT_JIS 932 + +#endif diff --git a/src/main/util/crc.c b/src/main/util/crc.c new file mode 100644 index 0000000..8ed0fc5 --- /dev/null +++ b/src/main/util/crc.c @@ -0,0 +1,83 @@ +#include +#include + +uint8_t crc8(const void *ptr, size_t nbytes, uint8_t in) +{ + const uint8_t *bytes; + uint8_t crc; + size_t i; + size_t j; + + //log_assert(ptr != NULL); + + bytes = ptr; + crc = ~in; + + for (i = 0 ; i < nbytes ; i++) { + crc ^= bytes[i]; + + for (j = 0 ; j < 8 ; j++) { + if (crc & 1) { + crc = (crc >> 1) ^ 0x8C; + } else { + crc = crc >> 1; + } + } + } + + return ~crc; +} + +uint16_t crc16(const void *ptr, size_t nbytes, uint16_t in) +{ + const uint8_t *bytes; + uint16_t crc; + size_t i; + size_t j; + + //log_assert(ptr != NULL); + + bytes = ptr; + crc = ~in; + + for (i = 0 ; i < nbytes ; i++) { + crc ^= bytes[i]; + + for (j = 0 ; j < 8 ; j++) { + if (crc & 1) { + crc = (crc >> 1) ^ 0x8408; + } else { + crc = crc >> 1; + } + } + } + + return ~crc; +} + +uint32_t crc32(const void *ptr, size_t nbytes, uint32_t in) +{ + const uint8_t *bytes; + uint32_t crc; + size_t i; + size_t j; + + //log_assert(ptr != NULL); + + bytes = ptr; + crc = ~in; + + for (i = 0 ; i < nbytes ; i++) { + crc ^= bytes[i]; + + for (j = 0 ; j < 8 ; j++) { + if (crc & 1) { + crc = (crc >> 1) ^ 0xEDB88320; + } else { + crc = (crc >> 1); + } + } + } + + return ~crc; +} diff --git a/src/main/util/crc.h b/src/main/util/crc.h new file mode 100644 index 0000000..3685781 --- /dev/null +++ b/src/main/util/crc.h @@ -0,0 +1,11 @@ +#ifndef UTIL_CRC_H +#define UTIL_CRC_H + +#include +#include + +uint8_t crc8(const void *ptr, size_t nbytes, uint8_t in); +uint16_t crc16(const void *ptr, size_t nbytes, uint16_t in); +uint32_t crc32(const void *ptr, size_t nbytes, uint32_t in); + +#endif diff --git a/src/main/util/crypto.c b/src/main/util/crypto.c new file mode 100644 index 0000000..62f412f --- /dev/null +++ b/src/main/util/crypto.c @@ -0,0 +1,262 @@ +#define LOG_MODULE "crypto" + +#include "util/crypto.h" +#include "util/log.h" +#include "util/mem.h" + +#include +#include + +static const char vista_prov[] + = "Microsoft Enhanced RSA and AES Cryptographic Provider"; +static const char winxp_prov[] + = "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"; + +static int init_count; +static HCRYPTPROV prov; + +void crypto_init(void) +{ + const char *prov_name; + OSVERSIONINFOEX osix; + + if (init_count++ != 0) { + return; + } + + log_misc("Initializing win32 crypto wrappers"); + + memset(&osix, 0, sizeof(osix)); + osix.dwOSVersionInfoSize = sizeof(osix); + GetVersionEx((void *) &osix); + + if (osix.dwMajorVersion < 6) { + prov_name = winxp_prov; + } else { + prov_name = vista_prov; + } + + if (!CryptAcquireContext(&prov, NULL, prov_name, PROV_RSA_AES, + CRYPT_VERIFYCONTEXT)) { + log_fatal("CryptAcquireContext failed: %08x", + (unsigned int) GetLastError()); + } +} + +void crypto_gen_random(void *bytes, size_t nbytes) +{ + CryptGenRandom(prov, nbytes, bytes); +} + +void crypto_fini(void) +{ + if (--init_count != 0) { + return; + } + + log_misc("Shutting down win32 crypto wrappers"); + + if (!CryptReleaseContext(prov, 0)) { + log_fatal("CryptReleaseContext failed: %08x", + (unsigned int) GetLastError()); + } +} + +void md5_compute(const void *in_bytes, size_t in_nbytes, uint8_t *out_bytes, + size_t out_nbytes) +{ + struct md5 md5; + + md5_init(&md5); + md5_append(&md5, in_bytes, in_nbytes); + md5_fini(&md5, out_bytes, out_nbytes); +} + +void md5_init(struct md5 *md5) +{ + if (!CryptCreateHash(prov, CALG_MD5, 0, 0, &md5->hash)) { + log_fatal("CryptCreateHash(CALG_MD5) failed: %08x", + (unsigned int) GetLastError()); + } +} + +void md5_append(struct md5 *md5, const void *bytes, size_t nbytes) +{ + if (!CryptHashData(md5->hash, bytes, nbytes, 0)) { + log_fatal("CryptHashData failed: %08x", + (unsigned int) GetLastError()); + } +} + +void md5_fini(struct md5 *md5, uint8_t *output, size_t nbytes) +{ + DWORD tmp; + + log_assert(output != NULL && nbytes == 16); + + tmp = nbytes; + + if (output != NULL) { + if (!CryptGetHashParam(md5->hash, HP_HASHVAL, output, &tmp, 0)) { + log_fatal("Error getting MD5 result: %08x", + (unsigned int) GetLastError()); + } + } + + if (!CryptDestroyHash(md5->hash)) { + log_fatal("CryptDestroyHash failed: %08x", + (unsigned int) GetLastError()); + } +} + +#define SWAP(x, y, tmp) tmp = x; x = y; y = tmp + +void arc4_init(struct arc4 *arc4, const uint8_t *key, size_t key_nbytes) +{ + size_t i; + uint8_t j; + uint8_t tmp; + + arc4->i = 0; + arc4->j = 0; + + for (i = 0 ; i < 256 ; i++) { + arc4->S[i] = (uint8_t) i; + } + + j = 0; + + for (i = 0 ; i < 256 ; i++) { + j = j + arc4->S[i] + key[i % key_nbytes]; + SWAP(arc4->S[i], arc4->S[j], tmp); + } +} + +void arc4_apply(struct arc4 *arc4, uint8_t *bytes, size_t nbytes) +{ + size_t i; + uint8_t tmp; + + for (i = 0 ; i < nbytes ; i++) { + arc4->i += 1; + arc4->j += arc4->S[arc4->i]; + + SWAP(arc4->S[arc4->i], arc4->S[arc4->j], tmp); + + bytes[i] ^= arc4->S[(uint8_t) (arc4->S[arc4->i] + arc4->S[arc4->j])]; + } +} + + +static uint32_t blowfish_F(struct blowfish* blowfish, uint32_t x) +{ + unsigned short a, b, c, d; + uint32_t y; + + d = x & 0x00FF; + x >>= 8; + c = x & 0x00FF; + x >>= 8; + b = x & 0x00FF; + x >>= 8; + a = x & 0x00FF; + + y = blowfish->S[0][a] + blowfish->S[1][b]; + y = y ^ blowfish->S[2][c]; + y = y + blowfish->S[3][d]; + return y; +} + +void blowfish_init(struct blowfish* blowfish, const uint8_t* key, + size_t key_length) +{ + int i, j, k; + uint32_t data, datal, datar; + + j = 0; + for (i = 0; i < 16 + 2; ++i) { + data = 0x00000000; + + for (k = 0; k < 4; ++k) { + data = (data << 8) | key[j]; + j = j + 1; + if (j >= key_length) + j = 0; + } + + blowfish->P[i] = blowfish->P[i] ^ data; + } + + datal = 0x00000000; + datar = 0x00000000; + + for (i = 0; i < 16 + 2; i += 2) { + blowfish_encrypt(blowfish, &datal, &datar); + blowfish->P[i] = datal; + blowfish->P[i + 1] = datar; + } + + for (i = 0; i < 4; ++i) { + for (j = 0; j < 256; j += 2) { + blowfish_encrypt(blowfish, &datal, &datar); + blowfish->S[i][j] = datal; + blowfish->S[i][j + 1] = datar; + } + } +} + +void blowfish_encrypt(struct blowfish* blowfish, uint32_t* xl, uint32_t* xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t temp; + short i; + + Xl = *xl; + Xr = *xr; + + for (i = 0; i < 16; ++i) { + Xl = Xl ^ blowfish->P[i]; + Xr = blowfish_F(blowfish, Xl) ^ Xr; + temp = Xl; + Xl = Xr; + Xr = temp; + } + + temp = Xl; + Xl = Xr; + Xr = temp; + Xr = Xr ^ blowfish->P[16]; + Xl = Xl ^ blowfish->P[16 + 1]; + *xl = Xl; + *xr = Xr; +} + +void blowfish_decrypt(struct blowfish* blowfish, uint32_t* xl, uint32_t* xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t temp; + short i; + + Xl = *xl; + Xr = *xr; + + for (i = 16 + 1; i > 1; --i) { + Xl = Xl ^ blowfish->P[i]; + Xr = blowfish_F(blowfish, Xl) ^ Xr; + /* Exchange Xl and Xr */ + temp = Xl; + Xl = Xr; + Xr = temp; + } + + /* Exchange Xl and Xr */ + temp = Xl; + Xl = Xr; + Xr = temp; + Xr = Xr ^ blowfish->P[1]; + Xl = Xl ^ blowfish->P[0]; + *xl = Xl; + *xr = Xr; +} diff --git a/src/main/util/crypto.h b/src/main/util/crypto.h new file mode 100644 index 0000000..0701d66 --- /dev/null +++ b/src/main/util/crypto.h @@ -0,0 +1,42 @@ +#ifndef UTIL_CRYPTO_H +#define UTIL_CRYPTO_H + +#include +#include + +#include +#include + +struct arc4 { + uint8_t S[256]; + uint8_t i; + uint8_t j; +}; + +struct md5 { + HCRYPTHASH hash; +}; + +struct blowfish { + uint32_t P[16 + 2]; + uint32_t S[4][256]; +}; + +void crypto_init(void); +void crypto_gen_random(void *bytes, size_t nbytes); +void crypto_fini(void); + +void md5_compute(const void *in_bytes, size_t in_nbytes, uint8_t *out_bytes, + size_t out_nbytes); +void md5_init(struct md5 *md5); +void md5_append(struct md5 *md5, const void *bytes, size_t nbytes); +void md5_fini(struct md5 *md5, uint8_t *output, size_t nbytes); + +void arc4_init(struct arc4 *arc4, const uint8_t *key, size_t key_nbytes); +void arc4_apply(struct arc4 *arc4, uint8_t *bytes, size_t nbytes); + +void blowfish_init(struct blowfish* blowfish, const uint8_t* key, size_t key_length); +void blowfish_encrypt(struct blowfish* blowfish, uint32_t* xl, uint32_t* xr); +void blowfish_decrypt(struct blowfish* blowfish, uint32_t* xl, uint32_t* xr); + +#endif diff --git a/src/main/util/defs.h b/src/main/util/defs.h new file mode 100644 index 0000000..f3f0586 --- /dev/null +++ b/src/main/util/defs.h @@ -0,0 +1,18 @@ +#ifndef UTIL_DEFS_H +#define UTIL_DEFS_H + +#include + +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +#define DLLEXPORT __declspec(dllexport) +#define DLLIMPORT __declspec(dllimport) +#define STDCALL __stdcall + +#define lengthof(x) (sizeof(x) / sizeof(x[0])) + +#define containerof(ptr, outer_t, member) \ + ((void *) (((uint8_t *) ptr) - offsetof(outer_t, member))) + +#endif diff --git a/src/main/util/fs.c b/src/main/util/fs.c new file mode 100644 index 0000000..7174b88 --- /dev/null +++ b/src/main/util/fs.c @@ -0,0 +1,267 @@ +#include +#include + +#include +#include +#include +#include + +#include "util/defs.h" +#include "util/fs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +bool file_load(const char *filename, void **out_bytes, size_t *out_nbytes, + bool text_mode) +{ + FILE *f; + void *bytes; + size_t nbytes; + int result; + + log_assert(out_bytes != NULL); + *out_bytes = NULL; + + f = fopen(filename, "rb"); + + if (f == NULL) { + log_warning("%s: Error opening file: %s", filename, strerror(errno)); + + goto open_fail; + } + + fseek(f, 0, SEEK_END); + nbytes = ftell(f); + fseek(f, 0, SEEK_SET); + + /* Add for null terminator */ + if (text_mode) { + nbytes++; + } + + bytes = malloc(nbytes); + + if (bytes == NULL) { + log_warning("%s: malloc(%u) failed", filename, (unsigned int) nbytes); + + goto malloc_fail; + } + + if (text_mode) { + result = fread(bytes, nbytes - 1, 1, f); + } else { + result = fread(bytes, nbytes, 1, f); + } + + if (result != 1) { + log_warning("%s: Error reading file: %s", filename, strerror(errno)); + + goto read_fail; + } + + *out_bytes = bytes; + + if (out_nbytes != NULL) { + *out_nbytes = nbytes; + } + + fclose(f); + + /* Add null terminator */ + if (text_mode) { + ((char*) bytes)[nbytes - 1] = '\0'; + } + + log_misc("File loaded %s, size %lu", filename, + (long unsigned int) *out_nbytes); + + return true; + +read_fail: + free(bytes); + +malloc_fail: + fclose(f); + +open_fail: + return false; +} + +bool file_save(const char *filename, const void *bytes, size_t nbytes) +{ + FILE *f; + int result; + + f = fopen(filename, "wb"); + + if (f == NULL) { + log_warning("%s: Error creating file: %s", filename, strerror(errno)); + + goto open_fail; + } + + result = fwrite(bytes, nbytes, 1, f); + + if (result != 1) { + log_warning("%s: Error writing file: %s", filename, strerror(errno)); + + goto write_fail; + } + + fclose(f); + + return true; + +write_fail: + fclose(f); + +open_fail: + return false; +} + +bool path_exists(const char* path) +{ + DWORD attrib = GetFileAttributes(path); + + return attrib != INVALID_FILE_ATTRIBUTES; +} + +bool path_exists_wstr(const wchar_t* path) +{ + DWORD attrib = GetFileAttributesW(path); + + return attrib != INVALID_FILE_ATTRIBUTES; +} + +FILE *fopen_appdata(const char *vendor, const char *filename, + const char *mode) +{ + char root[MAX_PATH]; + char path[MAX_PATH]; + HRESULT hr; + FILE *f; + + hr = SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, root); + + if (FAILED(hr)) { + log_warning("Failed to get AppData folder: hr=%08x", + (unsigned int) hr); + + return NULL; + } + + str_format(path, sizeof(path), "%s\\%s", root, vendor); + + if (!CreateDirectory(path, NULL) + && GetLastError() != ERROR_ALREADY_EXISTS) { + log_warning("Failed to create %s: %08x", + path, (unsigned int) GetLastError()); + + return NULL; + } + + str_format(path, sizeof(path), "%s\\%s\\%s", root, vendor, filename); + f = fopen(path, mode); + + if (f == NULL) { + log_warning("fopen(\"%s\", \"%s\") failed: %s", + path, mode, strerror(errno)); + } + + return f; +} + +bool path_mkdir(const char *path) +{ + char buf[MAX_PATH]; + char *pos; + + str_cpy(buf, lengthof(buf), path); + + for (pos = path_next_element(buf) + ; pos + ; pos = path_next_element(pos + 1)) { + *pos = '\0'; + + if (strlen(buf) != 2 || buf[1] != ':') { + CreateDirectory(buf, NULL); + + if (GetLastError() != ERROR_ALREADY_EXISTS) { + log_warning("%s: Cannot create directory: %#x", + buf, (unsigned int) GetLastError()); + + return false; + } + } + + *pos = '/'; + } + + CreateDirectory(buf, NULL); + + if (GetLastError() != ERROR_ALREADY_EXISTS) { + log_warning("%s: Cannot create directory: %#x", + buf, (unsigned int) GetLastError()); + + return false; + } + + return true; +} + +char *path_next_element(char *path) +{ + char *c; + + for (c = path ; *c ; c++) { + if (*c == '/' || *c == '\\') { + return c; + } + } + + return NULL; +} + +bool read_str(FILE *f, char **str) +{ + uint32_t len; + char *buf; + + if (fread(&len, sizeof(len), 1, f) != 1) { + goto len_fail; + } + + buf = malloc(len + 1); + + if (buf == NULL) { + goto alloc_fail; + } + + if (len > 0 && fread(buf, len, 1, f) != 1) { + goto body_fail; + } + + buf[len] = '\0'; + *str = buf; + + return true; + +body_fail: + free(buf); + +alloc_fail: +len_fail: + return false; +} + +void write_str(FILE *f, const char *str) +{ + uint32_t len; + + len = strlen(str); + + fwrite(&len, sizeof(len), 1, f); + fwrite(str, len, 1, f); +} + diff --git a/src/main/util/fs.h b/src/main/util/fs.h new file mode 100644 index 0000000..b27ebcd --- /dev/null +++ b/src/main/util/fs.h @@ -0,0 +1,75 @@ +#ifndef UTIL_FS_H +#define UTIL_FS_H + +#include +#include +#include +#include + +/** + * Load a file into memory + * + * @param filename Path of the file to load + * @param bytes Pointer to a pointer to return an allocated location with the + * loaded data to + * @param nbytes Pointer to a variable to return the read size of the file to + * @param text_mode True to read the file in text mode (add terminating \0), + * false for binary mode + * @return True on success, false on error + */ +bool file_load(const char *filename, void **bytes, size_t *nbytes, + bool text_mode); + +/** + * Save a buffer to a file (overwrite any existing files with the same name) + * + * @param filename Path to save the data to + * @param bytes Pointer to buffer with contents to save + * @param nbytes Number of bytes to write to the file + * @return True on success, false on error + */ +bool file_save(const char *filename, const void *bytes, size_t nbytes); + +/** + * Check if a normal file, folder, symlink, ... exists + * + * @param path Path as c-string. + * @return True if exists, false otherwise + */ +bool path_exists(const char* path); + +/** + * Check if a normal file, folder, symlink, ... exists + * + * @param path Path as w-string. + * @return True if exists, false otherwise + */ +bool path_exists_wstr(const wchar_t* path); + +FILE *fopen_appdata(const char *vendor, const char *filename, + const char *mode); + +/** + * Create a new directory + * + * @param path Path to directory + * @return True if successful, false on failure + */ +bool path_mkdir(const char *path); +char *path_next_element(char *path); + +#define read_bin(f, val, size) (fread((val), (size), 1, (f)) == 1) +#define read_u8(f, val) (fread((val), 1, 1, (f)) == 1) +#define read_u16(f, val) (fread((val), 2, 1, (f)) == 1) +#define read_u32(f, val) (fread((val), 4, 1, (f)) == 1) + +bool read_str(FILE *f, char **str); + +#define write_bin(f, val, size) fwrite((val), (size), 1, (f)) +#define write_u8(f, val) fwrite((val), 1, 1, (f)) +#define write_u16(f, val) fwrite((val), 2, 1, (f)) +#define write_u32(f, val) fwrite((val), 4, 1, (f)) + +void write_str(FILE *f, const char *str); + +#endif diff --git a/src/main/util/hex.c b/src/main/util/hex.c new file mode 100644 index 0000000..4261db8 --- /dev/null +++ b/src/main/util/hex.c @@ -0,0 +1,72 @@ +#include +#include +#include + +#include "util/hex.h" +#include "util/log.h" + +static bool hex_decode_nibble(char c, uint8_t *nibble) +{ + if (c >= '0' && c <= '9') { + *nibble = c - '0'; + } else if (c >= 'A' && c <= 'F') { + *nibble = c - 'A' + 10; + } else if (c >= 'a' && c <= 'f') { + *nibble = c - 'a' + 10; + } else { + return false; + } + + return true; +} + +bool hex_decode(void *ptr, size_t nbytes, const char *chars, size_t nchars) +{ + uint8_t *bytes = ptr; + + uint32_t i; + uint8_t hi; + uint8_t lo; + + log_assert(nchars <= 2 * nbytes); + log_assert(nchars % 2 == 0); + + for (i = 0 ; i < nchars / 2; i++) { + if ( !hex_decode_nibble(chars[2 * i + 0], &hi) || + !hex_decode_nibble(chars[2 * i + 1], &lo)) { + return false; + } + + bytes[i] = (hi << 4) | lo; + } + + return true; +} + +static void hex_encode(const void *ptr, size_t nbytes, char *chars, + size_t nchars, const char *digits) +{ + const uint8_t *bytes = ptr; + + size_t i; + + log_assert(nchars >= 2 * nbytes + 1); + + for (i = 0 ; i < nbytes ; i++) { + chars[i * 2 + 0] = digits[bytes[i] >> 4]; + chars[i * 2 + 1] = digits[bytes[i] & 15]; + } + + chars[nbytes * 2] = '\0'; +} + +void hex_encode_lc(const void *bytes, size_t nbytes, char *chars, size_t nchars) +{ + hex_encode(bytes, nbytes, chars, nchars, "0123456789abcdef"); +} + +void hex_encode_uc(const void *bytes, size_t nbytes, char *chars, size_t nchars) +{ + hex_encode(bytes, nbytes, chars, nchars, "0123456789ABCDEF"); +} + diff --git a/src/main/util/hex.h b/src/main/util/hex.h new file mode 100644 index 0000000..bd48b90 --- /dev/null +++ b/src/main/util/hex.h @@ -0,0 +1,14 @@ +#ifndef UTIL_HEX_H +#define UTIL_HEX_H + +#include +#include + +bool hex_decode(void *bytes, size_t nbytes, const char *chars, + size_t nchars); +void hex_encode_uc(const void *bytes, size_t nbytes, char *chars, + size_t nchars); +void hex_encode_lc(const void *bytes, size_t nbytes, char *chars, + size_t nchars); + +#endif diff --git a/src/main/util/hr.c b/src/main/util/hr.c new file mode 100644 index 0000000..70c67c1 --- /dev/null +++ b/src/main/util/hr.c @@ -0,0 +1,39 @@ +#include + +#include + +#include "util/hr.h" + +HRESULT hr_from_win32(void) +{ + return HRESULT_FROM_WIN32(GetLastError()); +} + +void hr_propagate_win32_(HRESULT hr) +{ + uint32_t result; + + if (SUCCEEDED(hr)) { + result = ERROR_SUCCESS; + } else if (HRESULT_FACILITY(hr) == FACILITY_WIN32) { + result = HRESULT_CODE(hr); + } else { + // https://docs.microsoft.com/en-us/windows/desktop/seccrypto/common-hresult-values + switch (hr) { + case E_ABORT: result = ERROR_OPERATION_ABORTED; break; + case E_ACCESSDENIED: result = ERROR_ACCESS_DENIED; break; + case E_FAIL: result = ERROR_GEN_FAILURE; break; + case E_HANDLE: result = ERROR_INVALID_HANDLE; break; + case E_INVALIDARG: result = ERROR_INVALID_PARAMETER; break; + case E_NOINTERFACE: result = ERROR_NOT_SUPPORTED; break; + case E_NOTIMPL: result = ERROR_NOT_SUPPORTED; break; + case E_OUTOFMEMORY: result = ERROR_OUTOFMEMORY; break; + case E_POINTER: result = ERROR_INVALID_ADDRESS; break; + case E_UNEXPECTED: result = ERROR_INTERNAL_ERROR; break; + default: result = ERROR_INTERNAL_ERROR; break; + } + } + + SetLastError(result); +} + diff --git a/src/main/util/hr.h b/src/main/util/hr.h new file mode 100644 index 0000000..9ae04eb --- /dev/null +++ b/src/main/util/hr.h @@ -0,0 +1,11 @@ +#ifndef UTIL_HR_H +#define UTIL_HR_H + +#include + +#define hr_propagate_win32(hr, r) (hr_propagate_win32_(hr), r) + +HRESULT hr_from_win32(void); +void hr_propagate_win32_(HRESULT hr); + +#endif diff --git a/src/main/util/iobuf.c b/src/main/util/iobuf.c new file mode 100644 index 0000000..721043e --- /dev/null +++ b/src/main/util/iobuf.c @@ -0,0 +1,72 @@ +#define LOG_MODULE "iobuf" + +#include + +#include "util/hex.h" +#include "util/iobuf.h" +#include "util/log.h" +#include "util/mem.h" + +size_t iobuf_move(struct iobuf *dest, struct const_iobuf *src) +{ + size_t dest_avail; + size_t src_avail; + size_t chunksz; + + dest_avail = dest->nbytes - dest->pos; + src_avail = src->nbytes - src->pos; + chunksz = dest_avail < src_avail ? dest_avail : src_avail; + + memcpy(&dest->bytes[dest->pos], &src->bytes[src->pos], chunksz); + + dest->pos += chunksz; + src->pos += chunksz; + + return chunksz; +} + +void iobuf_flip(struct const_iobuf *dest, const struct iobuf *src) +{ + log_assert(dest != NULL); + log_assert(src != NULL); + + dest->bytes = src->bytes; + dest->pos = 0; + dest->nbytes = src->pos; +} + +void iobuf_log(struct iobuf* buffer, const char* tag) +{ + char* str; + size_t str_len; + + str_len = buffer->nbytes * 2 + 1; + str = xmalloc(str_len); + + log_misc("[%s] (%d %d)", tag, (uint32_t) buffer->nbytes, + (uint32_t) buffer->pos); + + hex_encode_uc(buffer->bytes, buffer->nbytes, str, str_len); + + log_misc("[%s]: %s", tag, str); + + free(str); +} + +void iobuf_log_const(struct const_iobuf* buffer, const char* tag) +{ + char* str; + size_t str_len; + + str_len = buffer->nbytes * 2 + 1; + str = xmalloc(str_len); + + log_misc("[%s] (%d %d)", tag, (uint32_t) buffer->nbytes, + (uint32_t) buffer->pos); + + hex_encode_uc(buffer->bytes, buffer->nbytes, str, str_len); + + log_misc("[%s]: %s", tag, str); + + free(str); +} diff --git a/src/main/util/iobuf.h b/src/main/util/iobuf.h new file mode 100644 index 0000000..a2f63f4 --- /dev/null +++ b/src/main/util/iobuf.h @@ -0,0 +1,27 @@ +#ifndef UTIL_IOBUF_H +#define UTIL_IOBUF_H + +#include +#include + +struct iobuf { + uint8_t *bytes; + size_t nbytes; + size_t pos; +}; + +struct const_iobuf { + const uint8_t *bytes; + size_t nbytes; + size_t pos; +}; + +size_t iobuf_move(struct iobuf *dest, struct const_iobuf *src); + +void iobuf_flip(struct const_iobuf *dest, const struct iobuf *src); + +void iobuf_log(struct iobuf* buffer, const char* tag); + +void iobuf_log_const(struct const_iobuf* buffer, const char* tag); + +#endif diff --git a/src/main/util/list.c b/src/main/util/list.c new file mode 100644 index 0000000..3ef3dfe --- /dev/null +++ b/src/main/util/list.c @@ -0,0 +1,88 @@ +#include +#include + +#include "util/list.h" + +extern inline struct list_node *list_peek_head(struct list *list); +extern inline const struct list_node *list_peek_head_const( + const struct list *list +); + +void list_init(struct list *list) +{ + memset(list, 0, sizeof(*list)); +} + +void list_append(struct list *list, struct list_node *node) +{ + node->next = NULL; + + if (list->tail != NULL) { + list->tail->next = node; + } else { + list->head = node; + } + + list->tail = node; +} + +bool list_contains(struct list *list, struct list_node *node) +{ + struct list_node *pos; + + for (pos = list->head ; pos != NULL ; pos = pos->next) { + if (pos == node) { + return true; + } + } + + return false; +} + +struct list_node *list_pop_head(struct list *list) +{ + struct list_node *node; + + if (list->head == NULL) { + return NULL; + } + + node = list->head; + list->head = node->next; + + if (node->next == NULL) { + list->tail = NULL; + } + + node->next = NULL; + + return node; +} + +void list_remove(struct list *list, struct list_node *node) +{ + struct list_node *pos; + struct list_node *prev; + + for (prev = NULL, pos = list->head + ; pos != NULL + ; prev = pos, pos = pos->next) { + if (pos == node) { + if (prev != NULL) { + prev->next = node->next; + } else { + list->head = node->next; + } + + if (node->next == NULL) { + list->tail = prev; + } + + node->next = NULL; + + return; + } + } +} + + diff --git a/src/main/util/list.h b/src/main/util/list.h new file mode 100644 index 0000000..466f207 --- /dev/null +++ b/src/main/util/list.h @@ -0,0 +1,34 @@ +#ifndef UTIL_LIST_H +#define UTIL_LIST_H + +#include +#include + +struct list { + struct list_node *head; + struct list_node *tail; +}; + +struct list_node { + struct list_node *next; +}; + +void list_init(struct list *list); +void list_append(struct list *list, struct list_node *node); +bool list_contains(struct list *list, struct list_node *node); +inline struct list_node *list_peek_head(struct list *list); +inline const struct list_node *list_peek_head_const(const struct list *list); +struct list_node *list_pop_head(struct list *list); +void list_remove(struct list *list, struct list_node *node); + +inline struct list_node *list_peek_head(struct list *list) +{ + return list->head; +} + +inline const struct list_node *list_peek_head_const(const struct list *list) +{ + return list->head; +} + +#endif diff --git a/src/main/util/log.c b/src/main/util/log.c new file mode 100644 index 0000000..59aab8b --- /dev/null +++ b/src/main/util/log.c @@ -0,0 +1,131 @@ +#include "util/log.h" +#include "util/str.h" + +#include + +#include +#include +#include +#include + +static log_writer_t log_writer; +static void *log_writer_ctx; +static unsigned int log_level; + +static void log_builtin_fatal(const char *module, const char *fmt, ...); +static void log_builtin_info(const char *module, const char *fmt, ...); +static void log_builtin_misc(const char *module, const char *fmt, ...); +static void log_builtin_warning(const char *module, const char *fmt, ...); +static void log_builtin_format(unsigned int msg_level, const char *module, + const char *fmt, va_list ap); + +#define IMPLEMENT_SINK(name, msg_level) \ + static void name(const char *module, const char *fmt, ...) \ + { \ + va_list ap; \ + \ + va_start(ap, fmt); \ + log_builtin_format(msg_level, module, fmt, ap); \ + va_end(ap); \ + } + +IMPLEMENT_SINK(log_builtin_info, 2) +IMPLEMENT_SINK(log_builtin_misc, 3) +IMPLEMENT_SINK(log_builtin_warning, 1) + +static void log_builtin_fatal(const char *module, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_builtin_format(0, module, fmt, ap); + va_end(ap); + + DebugBreak(); + ExitProcess(EXIT_FAILURE); +} + +static void log_builtin_format(unsigned int msg_level, const char *module, + const char *fmt, va_list ap) +{ + static const char chars[] = "FWIM"; + + /* 64k so we can log data dumps of rs232 without crashing */ + char line[65536]; + char msg[65536]; + int result; + + str_vformat(msg, sizeof(msg), fmt, ap); + result = str_format(line, sizeof(line), "%c:%s: %s\n", chars[msg_level], + module, msg); + + if (msg_level <= log_level) { + log_writer(log_writer_ctx, line, result); + } +} + +void log_assert_body(const char *file, int line, const char *function) +{ + log_impl_fatal("assert", "%s:%d: function `%s'", file, line, function); +} + +void log_to_external(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + log_impl_misc = misc; + log_impl_info = info; + log_impl_warning = warning; + log_impl_fatal = fatal; +} + +void log_to_writer(log_writer_t writer, void *ctx) +{ + log_impl_misc = log_builtin_misc; + log_impl_info = log_builtin_info; + log_impl_warning = log_builtin_warning; + log_impl_fatal = log_builtin_fatal; + + if (writer != NULL) { + log_writer = writer; + log_writer_ctx = ctx; + } else { + log_writer = log_writer_null; + } +} + +void log_set_level(unsigned int new_level) +{ + log_level = new_level; +} + +void log_writer_debug(void *ctx, const char *chars, size_t nchars) +{ + OutputDebugStringA(chars); +} + +void log_writer_stdout(void *ctx, const char *chars, size_t nchars) +{ + printf("%s", chars); +} + +void log_writer_stderr(void *ctx, const char *chars, size_t nchars) +{ + fprintf(stderr, "%s", chars); +} + +void log_writer_file(void *ctx, const char *chars, size_t nchars) +{ + fwrite(chars, 1, nchars, (FILE *) ctx); + fflush((FILE*) ctx); +} + +void log_writer_null(void *ctx, const char *chars, size_t nchars) +{} + +log_formatter_t log_impl_misc = log_builtin_misc; +log_formatter_t log_impl_info = log_builtin_info; +log_formatter_t log_impl_warning = log_builtin_warning; +log_formatter_t log_impl_fatal = log_builtin_fatal; +static log_writer_t log_writer = log_writer_null; +static unsigned int log_level = 4; + diff --git a/src/main/util/log.h b/src/main/util/log.h new file mode 100644 index 0000000..9102226 --- /dev/null +++ b/src/main/util/log.h @@ -0,0 +1,75 @@ +#ifndef UTIL_LOG_H +#define UTIL_LOG_H + +#include +#include + +#include "bemanitools/glue.h" + +#include "util/defs.h" + +/* Dynamically retargetable logging system modeled on (and potentially + integrateable with) the one found in AVS2 */ + +/* BUILD_MODULE is passed in as a command-line #define by the makefile */ + +#ifndef LOG_MODULE +#define LOG_MODULE STRINGIFY(BUILD_MODULE) +#endif + +#ifndef LOG_SUPPRESS + +#define log_misc(...) log_impl_misc(LOG_MODULE, __VA_ARGS__) +#define log_info(...) log_impl_info(LOG_MODULE, __VA_ARGS__) +#define log_warning(...) log_impl_warning(LOG_MODULE, __VA_ARGS__) + +/* This doesn't really belong here, but it's what libavs does so w/e */ + +#define log_assert(x) \ + do { \ + if (!(x)) { \ + log_assert_body(__FILE__, __LINE__, __FUNCTION__); \ + } \ + } while (0) + +#else + +#define log_misc(...) +#define log_info(...) +#define log_warning(...) +#define log_assert(x) do { if (!(x)) { abort(); } } while (0) + +#endif + +#define log_fatal(...) \ + do { \ + log_impl_fatal(LOG_MODULE, __VA_ARGS__); \ + abort(); \ + } while (0) + +typedef void (*log_writer_t)(void *ctx, const char *chars, size_t nchars); + +extern log_formatter_t log_impl_misc; +extern log_formatter_t log_impl_info; +extern log_formatter_t log_impl_warning; +extern log_formatter_t log_impl_fatal; + +void log_assert_body(const char *file, int line, const char *function); +void log_to_external(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal); +void log_to_writer(log_writer_t writer, void *ctx); + +void log_set_level(unsigned int new_level); + +/* I tried to make this API match the function signature of the AVS log writer + callback, but then the signature changed and the explicit line breaks + being passed to that callback went away. So we don't try to track that API + any more. Launcher defines its own custom writer anyway. */ + +void log_writer_debug(void *ctx, const char *chars, size_t nchars); +void log_writer_stdout(void *ctx, const char *chars, size_t nchars); +void log_writer_stderr(void *ctx, const char *chars, size_t nchars); +void log_writer_file(void *ctx, const char *chars, size_t nchars); +void log_writer_null(void *ctx, const char *chars, size_t nchars); + +#endif diff --git a/src/main/util/mem.c b/src/main/util/mem.c new file mode 100644 index 0000000..64b49de --- /dev/null +++ b/src/main/util/mem.c @@ -0,0 +1,89 @@ +#define LOG_MODULE "mem" + +#include + +#include +#include +#include +#include + +#include "util/log.h" + +void *xcalloc(size_t nbytes) +{ + void *mem; + + mem = calloc(nbytes, 1); + + if (mem == NULL) { + log_fatal("xmalloc(%u) failed", (uint32_t) nbytes); + + return NULL; + } + + return mem; +} + +void *xmalloc(size_t nbytes) +{ + void *mem; + + mem = malloc(nbytes); + + if (mem == NULL) { + log_fatal("xcalloc(%u) failed", (uint32_t) nbytes); + + return NULL; + } + + return mem; +} + +void *xrealloc(void *mem, size_t nbytes) +{ + void *newmem; + + newmem = realloc(mem, nbytes); + + if (newmem == NULL) { + log_fatal("xrealloc(%p, %u) failed", mem, (uint32_t) nbytes); + + return NULL; + } + + return newmem; +} + +bool mem_nop(size_t mem_offset, size_t length) +{ + DWORD oldProt; + + if (VirtualProtect((void*) (mem_offset), length, PAGE_EXECUTE_READWRITE, + &oldProt) == 0) { + return false; + } + + for (size_t i = 0; i < length; i++) { + ((uint8_t*) mem_offset)[i] = 0x90; + } + + if (VirtualProtect((void*) (mem_offset), length, oldProt, &oldProt) == 0) { + return false; + } + + return true; +} + +void* mem_find_signiture(const uint8_t* sig, uint32_t sig_len, + int32_t sig_offset, void* start_addr, void* end_addr, int32_t alignment) +{ + size_t pos; + + for (pos = (size_t) start_addr; pos < (size_t) end_addr; pos += alignment) { + if (!memcmp((void*) pos, sig, sig_len)) { + return (void*) (pos + sig_offset); + } + } + + return NULL; +} \ No newline at end of file diff --git a/src/main/util/mem.h b/src/main/util/mem.h new file mode 100644 index 0000000..7ea9508 --- /dev/null +++ b/src/main/util/mem.h @@ -0,0 +1,30 @@ +#ifndef UTIL_MEM_H +#define UTIL_MEM_H + +#include +#include +#include + +void *xcalloc(size_t nbytes); +void *xmalloc(size_t nbytes); +void *xrealloc(void *mem, size_t nbytes); +bool mem_nop(size_t mem_offset, size_t length); + +/** + * Scan a memory area limited by a start and end address for a signiture. + * + * @param sig Buffer with signiture to search for + * @param sig_len Length of signiture + * @param sig_offset Offset that gets added to the address where the signiture + * starts (if found) + * @param start_addr Pointer to the address to start searching at + * @param end_addr Pointer to the address to stop searching at + * @param alignment Aligns the search signiture, i.e. search in steps of + * X bytes + * @return If found, pointer to memory where the signiture was found (first + * occurance) with the signiture offset added, NULL otherwise + */ +void* mem_find_signiture(const uint8_t* sig, uint32_t sig_len, + int32_t sig_offset, void* start_addr, void* end_addr, int32_t alignment); + +#endif diff --git a/src/main/util/msg-thread.c b/src/main/util/msg-thread.c new file mode 100644 index 0000000..d01da52 --- /dev/null +++ b/src/main/util/msg-thread.c @@ -0,0 +1,118 @@ +#define LOG_MODULE "msg-thread" + +#include + +#include +#include + +#include "util/log.h" +#include "util/msg-thread.h" +#include "util/thread.h" + +static bool msg_thread_step(HWND hwnd); + +static const char msg_wndclass_name[] = "msg-thread"; + +static int msg_thread_id; +static HANDLE msg_thread_ready; +static HANDLE msg_thread_stop; +static HWND msg_window; + +static int msg_thread_proc(void *param) +{ + HWND hwnd; + HINSTANCE inst; + MSG msg; + WNDCLASSEX wcx; + + inst = param; + + memset(&wcx, 0, sizeof(wcx)); + + wcx.cbSize = sizeof(wcx); + wcx.lpfnWndProc = msg_window_proc; /* callback */ + wcx.hInstance = inst; + wcx.lpszClassName = msg_wndclass_name; + + RegisterClassEx(&wcx); + + /* Message-only windows (children of HWND_MESSAGE) do not receive + broadcast events. + + Because THAT totally makes sense. */ + + hwnd = CreateWindowEx(0, msg_wndclass_name, NULL, 0, 0, 0, 0, 0, + NULL, NULL, inst, NULL); + + msg_window_setup(hwnd); /* callback */ + + PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); + SetEvent(msg_thread_ready); + + while (msg_thread_step(hwnd)); + + msg_window_teardown(hwnd); /* callback */ + + DestroyWindow(hwnd); + UnregisterClass(msg_wndclass_name, inst); + + return 0; +} + +static bool msg_thread_step(HWND hwnd) +{ + MSG msg; + DWORD result; + + result = MsgWaitForMultipleObjectsEx(1, &msg_thread_stop, INFINITE, + QS_ALLINPUT, MWMO_ALERTABLE); + + switch (result) { + case WAIT_OBJECT_0: + return false; + + case WAIT_OBJECT_0 + 1: + while (PeekMessage(&msg, msg_window, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return true; + + case WAIT_IO_COMPLETION: + return true; + + case WAIT_FAILED: + log_warning("MsgWaitForMultipleObjectsEx failed: %08x", + (unsigned int) GetLastError()); + + return false; + + default: + log_warning("Spurious wakeup: result = %08x", + (unsigned int) result); + + return true; + } +} + +void msg_thread_init(HINSTANCE inst) +{ + msg_thread_ready = CreateEvent(NULL, TRUE, FALSE, NULL); + msg_thread_stop = CreateEvent(NULL, TRUE, FALSE, NULL); + msg_thread_id = thread_create(msg_thread_proc, inst, 0x4000, 0); + + WaitForSingleObject(msg_thread_ready, INFINITE); + CloseHandle(msg_thread_ready); +} + +void msg_thread_fini(void) +{ + SetEvent(msg_thread_stop); + + thread_join(msg_thread_id, NULL); + thread_destroy(msg_thread_id); + + CloseHandle(msg_thread_stop); +} + diff --git a/src/main/util/msg-thread.h b/src/main/util/msg-thread.h new file mode 100644 index 0000000..f14b975 --- /dev/null +++ b/src/main/util/msg-thread.h @@ -0,0 +1,14 @@ +#ifndef UTIL_MSG_THREAD_H +#define UTIL_MSG_THREAD_H + +void msg_thread_init(HINSTANCE inst); +void msg_thread_fini(void); + +/* Define these callbacks */ + +extern void msg_window_setup(HWND hwnd); +extern LRESULT WINAPI msg_window_proc(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam); +extern void msg_window_teardown(HWND hwnd); + +#endif diff --git a/src/main/util/net.c b/src/main/util/net.c new file mode 100644 index 0000000..556970a --- /dev/null +++ b/src/main/util/net.c @@ -0,0 +1,443 @@ +#define LOG_MODULE "util-net" + +#include +#include +#include +#include +#include + +#include "util/log.h" +#include "util/mem.h" +#include "util/net.h" +#include "util/str.h" + +#include + +static bool net_parse_url(const char* str, struct net_addr* addr) +{ + char host[NET_MAX_URL_HOSTNAME_LEN + 1]; + char path[NET_MAX_URL_PATH_LEN + 1]; + uint32_t ipv4[4]; + uint32_t port; + int toks; + + addr->type = NET_ADDR_TYPE_URL; + + memset(host, 0, sizeof(host)); + memset(path, 0, sizeof(path)); + + if (!strncmp(str, "http://", strlen("http://"))) { + addr->url.is_https = false; + str += strlen("http://"); + } else if (!strncmp(str, "https://", strlen("https://"))) { + addr->url.is_https = true; + str += strlen("https://"); + } else { + return false; + } + + /* Try url with IPV4 address */ + + if (sscanf(str, "%d.%d.%d.%d", &ipv4[0], &ipv4[1], &ipv4[2], + &ipv4[3]) == 4) { + addr->url.type = NET_ADDR_TYPE_IPV4; + addr->url.ipv4.addr = (ipv4[0] & 0xFF) | ((ipv4[1] & 0xFF) << 8) | + ((ipv4[2] & 0xFF) << 16) | ((ipv4[3] & 0xFF) << 24); + addr->url.ipv4.port = NET_INVALID_PORT; + memset(addr->url.path, 0, sizeof(addr->url.path)); + + /* Port and trailing path are optional, check with and without */ + + toks = sscanf(str, "%d.%d.%d.%d:%d/%s", &ipv4[0], &ipv4[1], + &ipv4[2], &ipv4[3], &port, path); + + if (toks > 4) { + if (toks >= 5) { + addr->url.ipv4.port = (uint16_t) port; + } + + if (toks == 6) { + strcpy(addr->url.path, path); + } + } else { + /* No port, but may have trailing path */ + toks = sscanf(str, "%d.%d.%d.%d/%s", &ipv4[0], &ipv4[1], + &ipv4[2], &ipv4[3], path); + + if (toks > 4) { + strcpy(addr->url.path, path); + } + } + } else { + /* URL with hostname */ + addr->url.type = NET_ADDR_TYPE_HOSTNAME; + memset(addr->url.hostname.host, 0, sizeof(addr->url.hostname.host)); + addr->url.hostname.port = NET_INVALID_PORT; + memset(addr->url.path, 0, sizeof(addr->url.path)); + + toks = sscanf(str, "%[^:]:%d/%s", host, &port, path); + + if (toks == 0) { + return false; + } + + if (toks >= 2) { + strcpy(addr->url.hostname.host, host); + addr->url.hostname.port = port; + + if (toks == 3) { + strcpy(addr->url.path, path); + } + } else { + /* No port, but may have trailing path */ + toks = sscanf(str, "%[^/]/%s", host, path); + strcpy(addr->url.hostname.host, host); + + + if (toks > 1) { + strcpy(addr->url.path, path); + } + } + } + + return true; +} + +static bool net_parse_ipv4_or_hostname(const char* str, struct net_addr* addr) +{ + char host[NET_MAX_URL_HOSTNAME_LEN + 1]; + uint32_t ipv4[4]; + uint32_t port; + int toks; + + memset(host, 0, sizeof(host)); + + toks = sscanf(str, "%d.%d.%d.%d:%d", &ipv4[0], &ipv4[1], &ipv4[2], &ipv4[3], + &port); + + if (toks >= 4) { + addr->type = NET_ADDR_TYPE_IPV4; + addr->ipv4.addr = (ipv4[0] & 0xFF) | ((ipv4[1] & 0xFF) << 8) | + ((ipv4[2] & 0xFF) << 16) | ((ipv4[3] & 0xFF) << 24); + addr->ipv4.port = NET_INVALID_PORT; + + if (toks == 5) { + addr->ipv4.port = port; + } + } else if (toks == 0) { + addr->type = NET_ADDR_TYPE_HOSTNAME; + addr->hostname.port = NET_INVALID_PORT; + + toks = sscanf(str, "%[^:]:%d", host, &port); + + if (toks == 0) { + return false; + } + + strcpy(addr->hostname.host, host); + + if (toks > 1) { + addr->hostname.port = port; + } + } else { + return false; + } + + return true; +} + +static char* net_ipv4_to_str(const struct net_addr_ipv4* addr) +{ + size_t len; + uint8_t* addr_ipv4; + char* str; + + addr_ipv4 = (uint8_t*) &addr->addr; + + if (addr->port != NET_INVALID_PORT) { + len = snprintf(NULL, 0, "%d.%d.%d.%d:%d", addr_ipv4[0], + addr_ipv4[1], addr_ipv4[2], addr_ipv4[3], addr->port); + str = xmalloc(len + 1); + + sprintf(str, "%d.%d.%d.%d:%d", addr_ipv4[0], + addr_ipv4[1], addr_ipv4[2], addr_ipv4[3], addr->port); + } else { + len = snprintf(NULL, 0, "%d.%d.%d.%d", addr_ipv4[0], addr_ipv4[1], + addr_ipv4[2], addr_ipv4[3]); + str = xmalloc(len + 1); + + sprintf(str, "%d.%d.%d.%d", addr_ipv4[0], addr_ipv4[1], + addr_ipv4[2], addr_ipv4[3]); + } + + return str; +} + +static char* net_hostname_to_str(const struct net_addr_hostname* addr) +{ + size_t len; + char* str; + + if (addr->port != NET_INVALID_PORT) { + len = snprintf(NULL, 0, "%s:%d", addr->host, addr->port); + str = xmalloc(len + 1); + + sprintf(str, "%s:%d", addr->host, addr->port); + } else { + str = str_dup(addr->host); + } + + return str; +} + +static char* net_url_to_str(const struct net_addr_url* addr) +{ + size_t len; + size_t str_len; + char* tmp; + char* str; + + switch (addr->type) { + case NET_ADDR_TYPE_IPV4: + tmp = net_ipv4_to_str(&addr->ipv4); + break; + + case NET_ADDR_TYPE_HOSTNAME: + tmp = net_hostname_to_str(&addr->hostname); + break; + + case NET_ADDR_TYPE_INVALID: + case NET_ADDR_TYPE_URL: + /* URL is not a valid type here */ + default: + log_assert(false); + return NULL; + } + + str_len = strlen(addr->path); + + len = snprintf(NULL, 0, "http%s://%s%s%s", addr->is_https ? "s" : "", + tmp, str_len > 0 ? "/" : "", str_len > 0 ? addr->path : ""); + str = xmalloc(len + 1); + + sprintf(str, "http%s://%s%s%s", addr->is_https ? "s" : "", + tmp, str_len > 0 ? "/" : "", str_len > 0 ? addr->path : ""); + + free(tmp); + + return str; +} + +static bool net_resolve_hostname_str(const char* host, uint32_t* ipv4) +{ + struct WSAData data; + struct addrinfo hints; + struct addrinfo* res; + int err; + + log_assert(host); + log_assert(ipv4); + + /* Resolve and cache */ + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_INET; + + /* Using gethostbyname seems to crash on certain setups...use + getaddrinfo (which is not deprecated) */ + WSAStartup(MAKEWORD(2,0), &data); + + err = getaddrinfo(host, NULL, &hints, &res); + + if (err != 0) { + *ipv4 = NET_INVALID_ADDR; + return false; + } + + *ipv4 = ((struct sockaddr_in *)(res->ai_addr))->sin_addr.S_un.S_addr; + + freeaddrinfo(res); + + WSACleanup(); + + return true; +} + +static bool net_resolve_hostname_ipv4(const struct net_addr_ipv4* addr, + struct net_addr_ipv4* res_addr) +{ + res_addr->addr = addr->addr; + res_addr->port = addr->port; + + return true; +} + +static bool net_resolve_hostname_host(const struct net_addr_hostname* addr, + struct net_addr_ipv4* res_addr) +{ + res_addr->port = addr->port; + return net_resolve_hostname_str(addr->host, &res_addr->addr); +} + +static bool net_resolve_hostname_url(const struct net_addr_url* addr, + struct net_addr_ipv4* res_addr) +{ + switch (addr->type) { + case NET_ADDR_TYPE_IPV4: + return net_resolve_hostname_ipv4(&addr->ipv4, res_addr); + + case NET_ADDR_TYPE_HOSTNAME: + return net_resolve_hostname_host(&addr->hostname, res_addr); + + case NET_ADDR_TYPE_INVALID: + case NET_ADDR_TYPE_URL: + /* Not a valid type here */ + default: + log_assert(false); + return false; + } +} + +bool net_str_parse(const char* str, struct net_addr* addr) +{ + log_assert(str); + log_assert(addr); + + if (!strncmp(str, "http", strlen("http"))) { + return net_parse_url(str, addr); + } else { + return net_parse_ipv4_or_hostname(str, addr); + } +} + +char* net_addr_to_str(const struct net_addr* addr) +{ + log_assert(addr); + + switch (addr->type) { + case NET_ADDR_TYPE_IPV4: + return net_ipv4_to_str(&addr->ipv4); + + case NET_ADDR_TYPE_HOSTNAME: + return net_hostname_to_str(&addr->hostname); + + case NET_ADDR_TYPE_URL: + return net_url_to_str(&addr->url); + + case NET_ADDR_TYPE_INVALID: + default: + log_assert(false); + return NULL; + } +} + +bool net_resolve_hostname(const char* hostname, struct net_addr_ipv4* res_addr) +{ + return net_resolve_hostname_str(hostname, &res_addr->addr); +} + +bool net_resolve_hostname_net_addr(const struct net_addr* addr, + struct net_addr_ipv4* res_addr) +{ + switch (addr->type) { + case NET_ADDR_TYPE_IPV4: + return net_resolve_hostname_ipv4(&addr->ipv4, res_addr); + + case NET_ADDR_TYPE_HOSTNAME: + return net_resolve_hostname_host(&addr->hostname, res_addr); + + case NET_ADDR_TYPE_URL: + return net_resolve_hostname_url(&addr->url, res_addr); + + case NET_ADDR_TYPE_INVALID: + default: + log_assert(false); + return false; + } +} + +bool net_check_remote_connection(const struct net_addr* addr, + uint32_t timeout_ms) +{ + struct net_addr_ipv4 target; + + if (!net_resolve_hostname_net_addr(addr, &target)) { + return false; + } + + return net_check_remote_connection_ipv4(&target, timeout_ms); +} + +bool net_check_remote_connection_ipv4(const struct net_addr_ipv4* addr, + uint32_t timeout_ms) +{ + struct WSAData data; + SOCKET s; + u_long block; + fd_set set_w; + fd_set set_e; + struct timeval timeout; + int err; + bool success; + struct sockaddr_in sockaddr; + + WSAStartup(MAKEWORD(2,0), &data); + + err = 0; + success = true; + + s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (s == INVALID_SOCKET) { + log_assert(false); + return false; + } + + /* Need socket in non-blocking mode to timeout */ + block = 1; + + if (ioctlsocket(s, FIONBIO, &block) == SOCKET_ERROR) { + log_assert(false); + closesocket(s); + return false; + } + + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = addr->addr; + sockaddr.sin_port = htons(addr->port); + + if (connect(s, (struct sockaddr*) &sockaddr, sizeof(sockaddr)) == + SOCKET_ERROR) { + if (WSAGetLastError() != WSAEWOULDBLOCK) { + /* connection failed */; + success = false; + } else { + FD_ZERO(&set_w); + FD_SET(s, &set_w); + FD_ZERO(&set_e); + FD_SET(s, &set_e); + + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms % 1000) * 1000; + + err = select(0, NULL, &set_w, &set_e, &timeout); + + if (err <= 0) { + /* select failed or timeout */ + success = false; + } else { + if (FD_ISSET(s, &set_e)) { + /* Connection failed */ + success = false; + } + } + } + } else { + success = false; + } + + closesocket(s); + WSACleanup(); + + return success; +} \ No newline at end of file diff --git a/src/main/util/net.h b/src/main/util/net.h new file mode 100644 index 0000000..b3dad62 --- /dev/null +++ b/src/main/util/net.h @@ -0,0 +1,137 @@ +#ifndef UTIL_NET_H +#define UTIL_NET_H + +#include +#include + +#define NET_INVALID_ADDR 0xFFFFFFFF +#define NET_INVALID_PORT 0xFFFF + +#define NET_LOCALHOST_ADDR 0x0100007F +#define NET_LOCALHOST_NAME "localhost" + +#define NET_MAX_URL_HOSTNAME_LEN 512 +#define NET_MAX_URL_PATH_LEN 1024 + +/** + * Types of net addresses supported. + */ +enum net_addr_type { + NET_ADDR_TYPE_INVALID = 0, + NET_ADDR_TYPE_IPV4 = 1, + NET_ADDR_TYPE_HOSTNAME = 2, + NET_ADDR_TYPE_URL = 3, +}; + +/** + * Struct storing an IPV4 address + optional port. + */ +struct net_addr_ipv4 { + /* IP address stored in little endian byte order */ + uint32_t addr; + uint16_t port; +}; + +/** + * Struct storing a hostname + optional port. + */ +struct net_addr_hostname { + char host[NET_MAX_URL_HOSTNAME_LEN + 1]; + uint16_t port; +}; + +/** + * Struct storing a full https URL. + */ +struct net_addr_url { + bool is_https; + char path[NET_MAX_URL_PATH_LEN + 1]; + enum net_addr_type type; + union { + struct net_addr_ipv4 ipv4; + struct net_addr_hostname hostname; + }; +}; + +/** + * Structure representing a "network address". + */ +struct net_addr { + enum net_addr_type type; + union { + struct net_addr_ipv4 ipv4; + struct net_addr_hostname hostname; + struct net_addr_url url; + }; +}; + +/** + * Parse a string to a net_addr sturct. The string can have one of the following + * formats: + * - Plain IPV4 address, e.g. 127.0.0.1 + * - Plain IPV4 address with port, e.g. 127.0.0.1:1234 + * - Plain hostname, e.g. localhost + * - Plain hostname with port, e.g. localhost:1234 + * - HTTP(s) URL, e.g. http://www.myremote.com + * - HTTP(s) URL with port, e.g. http://www.myremote.com:1234 + * - HTTP(s) URL with or without port and path: http://www.myremote.com:1234/somewhere + * + * @param str String to parse. + * @param addr Pointer to a net_addr struct to write the result to. + * @return True if parsing is successful, false on parse error. + */ +bool net_str_parse(const char* str, struct net_addr* addr); + +// port not printed if invalid +/** + * net_addr struct to string function. Create a single c-string representation. + * + * @param addr Input net_addr. + * @return c-string representation. Note: Invalid port values are omitted. + */ +char* net_addr_to_str(const struct net_addr* addr); + +/** + * Resolve a hostname to an IPV4 address. + * + * @param hostname Hostname to resolve, e.g. localhost. + * @param res_addr Pointer to a net_addr_ipv4 struct to store the result to. + * @return True if resolving was successful, false on error. + */ +bool net_resolve_hostname(const char* hostname, struct net_addr_ipv4* res_addr); + +/** + * Resolve a net_addr struct to an IPV4 address. If the net_addr struct is + * actually of IPV4 type, the IPV4 address and port are simply copied to the + * result struct. + * + * @param addr net_addr struct to resolve. + * @param res_addr Pointer to a net_addr_ipv4 struct to store the result to. + * @return True if resolving was successful, false on error. + */ +bool net_resolve_hostname_net_addr(const struct net_addr* addr, + struct net_addr_ipv4* res_addr); + +/** + * Check if a remote server is reachable, i.e. it is possible to open a + * connection to it. + * + * @param addr net_addr struct of target server to check. + * @param timeout_ms Timeout for the check in ms. + * @return True if target server is reachable/connectable, false otherwise. + */ +bool net_check_remote_connection(const struct net_addr* addr, + uint32_t timeout_ms); + +/** + * Check if a remote server is reachable, i.e. it is possible to open a + * connection to it. + * + * @param addr net_addr_ipv4 struct of target server to check. + * @param timeout_ms Timeout for the check in ms. + * @return True if target server is reachable/connectable, false otherwise. + */ +bool net_check_remote_connection_ipv4(const struct net_addr_ipv4* addr, + uint32_t timeout_ms); + +#endif \ No newline at end of file diff --git a/src/main/util/str.c b/src/main/util/str.c new file mode 100644 index 0000000..a01a968 --- /dev/null +++ b/src/main/util/str.c @@ -0,0 +1,254 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "util/mem.h" +#include "util/str.h" + +bool str_ends_with(const char *haystack, const char *needle) +{ + size_t haystack_len; + size_t needle_len; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (needle_len > haystack_len) { + return false; + } + + return !memcmp(&haystack[haystack_len - needle_len], needle, needle_len); +} + +bool wstr_ends_with(const wchar_t *haystack, const wchar_t *needle) +{ + size_t haystack_len; + size_t needle_len; + + haystack_len = wcslen(haystack); + needle_len = wcslen(needle); + + if (needle_len > haystack_len) { + return false; + } + + return !memcmp( + &haystack[haystack_len - needle_len], + needle, + needle_len * sizeof(wchar_t)); +} + +size_t str_format(char *buf, size_t nchars, const char *fmt, ...) +{ + va_list ap; + size_t result; + + va_start(ap, fmt); + result = str_vformat(buf, nchars, fmt, ap); + va_end(ap); + + return result; +} + +size_t str_vformat(char *buf, size_t nchars, const char *fmt, va_list ap) +{ + int result; + + result = _vsnprintf(buf, nchars, fmt, ap); + + if (result >= (int) nchars || result < 0) { + abort(); + } + + return (size_t) result; +} + +size_t wstr_format(wchar_t *buf, size_t nchars, const wchar_t *fmt, ...) +{ + va_list ap; + size_t result; + + va_start(ap, fmt); + result = wstr_vformat(buf, nchars, fmt, ap); + va_end(ap); + + return result; +} + +size_t wstr_vformat(wchar_t *buf, size_t nchars, const wchar_t *fmt, + va_list ap) +{ + int result; + + result = _vsnwprintf(buf, nchars, fmt, ap); + + if (result >= (int) nchars || result < 0) { + abort(); + } + + return (size_t) result; +} + +void str_cat(char *dest, size_t dnchars, const char *src) +{ + size_t dlen; + size_t slen; + + dlen = strlen(dest); + slen = strlen(src); + + if (dlen + slen >= dnchars) { + abort(); + } + + memcpy(dest + dlen, src, (slen + 1) * sizeof(char)); +} + +void str_cpy(char *dest, size_t dnchars, const char *src) +{ + size_t slen; + + slen = strlen(src); + + if (slen >= dnchars) { + abort(); + } + + memcpy(dest, src, (slen + 1) * sizeof(char)); +} + +void wstr_cat(wchar_t *dest, size_t dnchars, const wchar_t *src) +{ + size_t dlen; + size_t slen; + + dlen = wcslen(dest); + slen = wcslen(src); + + if (dlen + slen >= dnchars) { + abort(); + } + + memcpy(dest + dlen, src, (slen + 1) * sizeof(wchar_t)); +} + +void wstr_cpy(wchar_t *dest, size_t dnchars, const wchar_t *src) +{ + size_t slen; + + slen = wcslen(src); + + if (slen >= dnchars) { + abort(); + } + + memcpy(dest, src, (slen + 1) * sizeof(wchar_t)); +} + +char *str_dup(const char *str) +{ + char *dest; + size_t nbytes; + + nbytes = strlen(str) + 1; + dest = xmalloc(nbytes); + memcpy(dest, str, nbytes); + + return dest; +} + +void str_trim(char *str) +{ + char *pos; + + for (pos = str + strlen(str) - 1 ; pos > str ; pos--) { + if (!isspace(*pos)) { + return; + } + + *pos = '\0'; + } +} + +wchar_t *str_widen(const char *src) +{ + int nchars; + wchar_t *result; + + nchars = MultiByteToWideChar(CP_ACP, 0, src, -1, NULL, 0); + + if (!nchars) { + abort(); + } + + result = xmalloc(nchars * sizeof(wchar_t)); + + if (!MultiByteToWideChar(CP_ACP, 0, src, -1, result, nchars)) { + abort(); + } + + return result; +} + +wchar_t *wstr_dup(const wchar_t *wstr) +{ + wchar_t *dest; + size_t nchars; + + nchars = wcslen(wstr) + 1; + dest = xmalloc(nchars * sizeof(wchar_t)); + memcpy(dest, wstr, nchars * sizeof(wchar_t)); + + return dest; +} + +bool wstr_narrow(const wchar_t *src, char **dest) +{ + int nbytes; + + nbytes = WideCharToMultiByte(CP_ACP, 0, src, -1, NULL, 0, NULL, NULL); + + if (nbytes <= 0) { + goto size_fail; + } + + *dest = xmalloc(nbytes); + + if (WideCharToMultiByte(CP_ACP, 0, src, -1, *dest, nbytes, NULL, NULL) + != nbytes) { + goto conv_fail; + } + + return true; + +conv_fail: + free(*dest); + *dest = NULL; + +size_fail: + return false; +} + +bool str_eq(const char *lhs, const char *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } else { + return strcmp(lhs, rhs) == 0; + } +} + +bool wstr_eq(const wchar_t *lhs, const wchar_t *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } else { + return wcscmp(lhs, rhs) == 0; + } +} + diff --git a/src/main/util/str.h b/src/main/util/str.h new file mode 100644 index 0000000..cd0ea64 --- /dev/null +++ b/src/main/util/str.h @@ -0,0 +1,29 @@ +#ifndef UTIL_STR_H +#define UTIL_STR_H + +#include +#include +#include +#include + +void str_cat(char *dest, size_t dnchars, const char *src); +void str_cpy(char *dest, size_t dnchars, const char *src); +char *str_dup(const char *src); +bool str_ends_with(const char *haystack, const char *needle); +bool str_eq(const char *lhs, const char *rhs); +size_t str_format(char *buf, size_t nchars, const char *fmt, ...); +void str_trim(char *str); +size_t str_vformat(char *buf, size_t nchars, const char *fmt, va_list ap); +wchar_t *str_widen(const char *src); + +void wstr_cat(wchar_t *dest, size_t dnchars, const wchar_t *src); +void wstr_cpy(wchar_t *dest, size_t dnchars, const wchar_t *src); +wchar_t *wstr_dup(const wchar_t *src); +bool wstr_ends_with(const wchar_t *haystack, const wchar_t *needle); +bool wstr_eq(const wchar_t *lhs, const wchar_t *rhs); +size_t wstr_format(wchar_t *buf, size_t nchars, const wchar_t *fmt, ...); +bool wstr_narrow(const wchar_t *src, char **dest); +size_t wstr_vformat(wchar_t *buf, size_t nchars, const wchar_t *fmt, + va_list ap); + +#endif diff --git a/src/main/util/thread.c b/src/main/util/thread.c new file mode 100644 index 0000000..ad6c6df --- /dev/null +++ b/src/main/util/thread.c @@ -0,0 +1,93 @@ +#include +#include + +#include +#include + +#include "util/defs.h" +#include "util/thread.h" + +struct shim_ctx { + HANDLE barrier; + int (*proc)(void *); + void *ctx; +}; + +thread_create_t thread_impl_create = crt_thread_create; +thread_join_t thread_impl_join = crt_thread_join; +thread_destroy_t thread_impl_destroy = crt_thread_destroy; + +static unsigned int STDCALL crt_thread_shim(void *outer_ctx) +{ + struct shim_ctx *sctx = outer_ctx; + int (*proc)(void *); + void *inner_ctx; + + proc = sctx->proc; + inner_ctx = sctx->ctx; + + SetEvent(sctx->barrier); + + return proc(inner_ctx); +} + +int crt_thread_create(int (*proc)(void *), void *ctx, uint32_t stack_sz, + unsigned int priority) +{ + struct shim_ctx sctx; + uintptr_t thread_id; + + sctx.barrier = CreateEvent(NULL, TRUE, FALSE, NULL); + sctx.proc = proc; + sctx.ctx = ctx; + + thread_id = _beginthreadex(NULL, stack_sz, crt_thread_shim, &sctx, 0, NULL); + + WaitForSingleObject(sctx.barrier, INFINITE); + CloseHandle(sctx.barrier); + + return (int) thread_id; +} + +void crt_thread_destroy(int thread_id) +{ + CloseHandle((HANDLE) (uintptr_t) thread_id); +} + +void crt_thread_join(int thread_id, int *result) +{ + WaitForSingleObject((HANDLE) (uintptr_t) thread_id, INFINITE); + + if (result) { + GetExitCodeThread((HANDLE) (uintptr_t) thread_id, (DWORD *) result); + } +} + +void thread_api_init(thread_create_t create, thread_join_t join, + thread_destroy_t destroy) +{ + if (create == NULL || join == NULL || destroy == NULL) { + abort(); + } + + thread_impl_create = create; + thread_impl_join = join; + thread_impl_destroy = destroy; +} + +int thread_create(int (*proc)(void *), void *ctx, uint32_t stack_sz, + unsigned int priority) +{ + return thread_impl_create(proc, ctx, stack_sz, priority); +} + +void thread_join(int thread_id, int *result) +{ + thread_impl_join(thread_id, result); +} + +void thread_destroy(int thread_id) +{ + thread_impl_destroy(thread_id); +} + diff --git a/src/main/util/thread.h b/src/main/util/thread.h new file mode 100644 index 0000000..06cbc6a --- /dev/null +++ b/src/main/util/thread.h @@ -0,0 +1,20 @@ +#ifndef UTIL_THREAD_H +#define UTIL_THREAD_H + +#include + +#include "bemanitools/glue.h" + +int crt_thread_create(int (*proc)(void *), void *ctx, uint32_t stack_sz, + unsigned int priority); +void crt_thread_join(int thread_id, int *result); +void crt_thread_destroy(int thread_id); + +void thread_api_init(thread_create_t create, thread_join_t join, + thread_destroy_t destroy); +int thread_create(int (*proc)(void *), void *ctx, uint32_t stack_sz, + unsigned int priority); +void thread_join(int thread_id, int *result); +void thread_destroy(int thread_id); + +#endif diff --git a/src/main/util/time.c b/src/main/util/time.c new file mode 100644 index 0000000..8ae95dd --- /dev/null +++ b/src/main/util/time.c @@ -0,0 +1,54 @@ +#include + +#include "util/log.h" +#include "util/time.h" + +static uint64_t counter_freq_ns; +static uint64_t counter_freq_us; +static uint64_t counter_freq_ms; + +static void time_init_freq() +{ + LARGE_INTEGER ticks_per_sec; + QueryPerformanceFrequency(&ticks_per_sec); + + counter_freq_ms = ticks_per_sec.QuadPart / 1000; + counter_freq_us = counter_freq_ms / 1000; + counter_freq_ns = counter_freq_us / 1000; +} + +uint64_t time_get_counter(void) +{ + LARGE_INTEGER time; + + log_assert(QueryPerformanceCounter(&time) == TRUE); + + return (uint64_t) time.QuadPart; +} + +uint64_t time_get_elapsed_ns(uint64_t counter_delta) +{ + if (counter_freq_ns == 0) { + time_init_freq(); + } + + return counter_delta / counter_freq_ns; +} + +uint64_t time_get_elapsed_us(uint64_t counter_delta) +{ + if (counter_freq_ns == 0) { + time_init_freq(); + } + + return counter_delta / counter_freq_us; +} + +uint32_t time_get_elapsed_ms(uint64_t counter_delta) +{ + if (counter_freq_ns == 0) { + time_init_freq(); + } + + return counter_delta / counter_freq_ms; +} \ No newline at end of file diff --git a/src/main/util/time.h b/src/main/util/time.h new file mode 100644 index 0000000..4465509 --- /dev/null +++ b/src/main/util/time.h @@ -0,0 +1,14 @@ +#ifndef UTIL_TIME_H +#define UTIL_TIME_H + +#include + +uint64_t time_get_counter(void); + +uint64_t time_get_elapsed_ns(uint64_t counter_delta); + +uint64_t time_get_elapsed_us(uint64_t counter_delta); + +uint32_t time_get_elapsed_ms(uint64_t counter_delta); + +#endif \ No newline at end of file diff --git a/src/main/util/winres.c b/src/main/util/winres.c new file mode 100644 index 0000000..ae881da --- /dev/null +++ b/src/main/util/winres.c @@ -0,0 +1,86 @@ +#include + +#include "util/defs.h" +#include "util/str.h" +#include "util/winres.h" + +void vrsprintf(char *dest, size_t nchars, HINSTANCE inst, + unsigned int fmt_resource, va_list ap) +{ + char fmt_buffer[1024]; + + LoadStringA(inst, fmt_resource, fmt_buffer, lengthof(fmt_buffer)); + str_vformat(dest, nchars, fmt_buffer, ap); + dest[nchars - 1] = '\0'; +} + +void rsprintf(char *dest, size_t nchars, HINSTANCE inst, + unsigned int fmt_resource, ...) +{ + va_list ap; + + va_start(ap, fmt_resource); + vrsprintf(dest, nchars, inst, fmt_resource, ap); + va_end(ap); +} + +void vrswprintf(wchar_t *dest, size_t nchars, HINSTANCE inst, + unsigned int fmt_resource, va_list ap) +{ + wchar_t fmt_buffer[1024]; + + LoadStringW(inst, fmt_resource, fmt_buffer, lengthof(fmt_buffer)); + wstr_vformat(dest, nchars, fmt_buffer, ap); + dest[nchars - 1] = L'\0'; +} + +void rswprintf(wchar_t *dest, size_t nchars, HINSTANCE inst, + unsigned int fmt_resource, ...) +{ + va_list ap; + + va_start(ap, fmt_resource); + vrswprintf(dest, nchars, inst, fmt_resource, ap); + va_end(ap); +} + +void resource_open(struct resource *res, HINSTANCE inst, const char *type, + unsigned int ord) +{ + HRSRC h1; + HGLOBAL h2; + + h1 = FindResourceA(inst, MAKEINTRESOURCE(ord), type); + h2 = LoadResource(inst, h1); + + res->bytes = LockResource(h2); + res->nbytes = SizeofResource(inst, h1); + res->pos = 0; +} + +size_t resource_fgets(struct resource *res, char *chars, size_t nchars) +{ + size_t remain; + size_t i; + char c; + + c = '\0'; + remain = res->nbytes - res->pos; + + for (i = 0 ; i < remain ; i++) { + if (c == '\n') { + break; + } + + c = res->bytes[res->pos++]; + + if (i < nchars - 1) { + chars[i] = c; + } + } + + chars[min(i, nchars - 1)] = '\0'; + + return i > 0; +} + diff --git a/src/main/util/winres.h b/src/main/util/winres.h new file mode 100644 index 0000000..5eacf80 --- /dev/null +++ b/src/main/util/winres.h @@ -0,0 +1,39 @@ +#ifndef UTIL_WINRES_H +#define UTIL_WINRES_H + +#include + +#include +#include +#include +#include + +#ifdef _UNICODE +#define vrstprintf vrswprintf +#define rstprintf rswprintf +#else +#define vrstprintf vrsprintf +#define rstprintf rsprintf +#endif + +struct resource { + const char *bytes; + size_t nbytes; + size_t pos; +}; + +void vrsprintf(char *dest, size_t nchars, HINSTANCE inst, + unsigned int fmt_resource, va_list ap); +void rsprintf(char *dest, size_t nchars, HINSTANCE inst, + unsigned int fmt_resource, ...); + +void vrswprintf(wchar_t *dest, size_t nchars, HINSTANCE inst, + unsigned int fmt_resource, va_list ap); +void rswprintf(wchar_t *dest, size_t nchars, HINSTANCE inst, + unsigned int fmt_resource, ...); + +void resource_open(struct resource *res, HINSTANCE inst, const char *type, + unsigned int ord); +size_t resource_fgets(struct resource *res, char *chars, size_t nchars); + +#endif diff --git a/src/main/vefxio/Module.mk b/src/main/vefxio/Module.mk new file mode 100644 index 0000000..6f2f168 --- /dev/null +++ b/src/main/vefxio/Module.mk @@ -0,0 +1,10 @@ +dlls += vefxio + +ldflags_vefxio := \ + -lwinmm + +libs_vefxio := \ + geninput + +src_vefxio := \ + vefxio.c \ diff --git a/src/main/vefxio/vefxio.c b/src/main/vefxio/vefxio.c new file mode 100644 index 0000000..76b1529 --- /dev/null +++ b/src/main/vefxio/vefxio.c @@ -0,0 +1,181 @@ +/* This is the source code for the VEFXIO.DLL that ships with Bemanitools 5. + + If you want to add on some minor functionality like a custom 16seg display + or a customer slider board then feel free to extend this code with support + for your custom device. + + This DLL is only used by the generic input variant of IIDXIO.DLL that ships + with Bemanitools by default. If you want to create a custom IO board that + provides all of the inputs for IIDX then you should replace IIDXIO.DLL + instead and not call into this DLL at all. */ + +#include +#include + +#include +#include + +#include "bemanitools/vefxio.h" +#include "bemanitools/input.h" + +#define MSEC_PER_NOTCH 128 + +enum iidx_slider_bits { + VEFX_IO_S1_UP = 0x20, + VEFX_IO_S1_DOWN = 0x21, + + VEFX_IO_S2_UP = 0x22, + VEFX_IO_S2_DOWN = 0x23, + + VEFX_IO_S3_UP = 0x24, + VEFX_IO_S3_DOWN = 0x25, + + VEFX_IO_S4_UP = 0x26, + VEFX_IO_S4_DOWN = 0x27, + + VEFX_IO_S5_UP = 0x28, + VEFX_IO_S5_DOWN = 0x29, +}; + +struct vefx_io_slider_inputs { + uint8_t slider_up; + uint8_t slider_down; +}; + +struct vefx_io_slider { + uint32_t last_notch; + int8_t pos; + int8_t last_dir; +}; + +static const struct vefx_io_slider_inputs vefx_io_slider_inputs[5] = { + { VEFX_IO_S1_UP, VEFX_IO_S1_DOWN }, + { VEFX_IO_S2_UP, VEFX_IO_S2_DOWN }, + { VEFX_IO_S3_UP, VEFX_IO_S3_DOWN }, + { VEFX_IO_S4_UP, VEFX_IO_S4_DOWN }, + { VEFX_IO_S5_UP, VEFX_IO_S5_DOWN }, +}; + +static struct vefx_io_slider vefx_io_slider[5]; + +static void vefx_io_slider_update(uint64_t* ppad); + +/* Uncomment these if you need them. */ + +#if 0 +static log_formatter_t vefx_io_log_misc; +static log_formatter_t vefx_io_log_info; +static log_formatter_t vefx_io_log_warning; +static log_formatter_t vefx_io_log_fatal; +#endif + +void vefx_io_set_loggers(log_formatter_t misc, log_formatter_t info, + log_formatter_t warning, log_formatter_t fatal) +{ + /* Uncomment this block if you have something you'd like to log. + + You should probably return false from the appropriate function instead + of calling the fatal logger yourself though. */ +#if 0 + vefx_io_log_misc = misc; + vefx_io_log_info = info; + vefx_io_log_warning = warning; + vefx_io_log_fatal = fatal; +#endif +} + +bool vefx_io_init(thread_create_t thread_create, thread_join_t thread_join, + thread_destroy_t thread_destroy) +{ + /* geninput should have already been initted be now so we don't do it */ + vefx_io_slider[0].pos = 8; + vefx_io_slider[1].pos = 8; + vefx_io_slider[2].pos = 8; + vefx_io_slider[3].pos = 15; + vefx_io_slider[4].pos = 15; + + /* Initialize your own IO devices here. Log something and then return + false if the initialization fails. */ + + return true; +} + +bool vefx_io_recv(uint64_t* ppad) +{ + vefx_io_slider_update(ppad); + + return true; +} + +void vefx_io_fini(void) +{ + /* This function gets called as IIDX shuts down after an Alt-F4. Close your + connections to your IO devices here. */ +} + +static void vefx_io_slider_update(uint64_t* ppad) +{ + uint32_t delta; + int sno; + int8_t sdir; + uint32_t now; + uint64_t pad; + + pad = *ppad; + now = timeGetTime(); + + for (sno = 0 ; sno < 5 ; sno++) { + if (pad & (1ULL << vefx_io_slider_inputs[sno].slider_up)) { + sdir = +1; + } else if (pad & (1ULL << vefx_io_slider_inputs[sno].slider_down)) { + sdir = -1; + } else { + sdir = 0; + } + + /* Update slider based on current direction */ + + if (sdir != vefx_io_slider[sno].last_dir) { + /* Just started (or stopped). Give the slider a big push to make sure + it begins to register straight from the first frame */ + + vefx_io_slider[sno].pos += sdir; + vefx_io_slider[sno].last_notch = now; + } else if (sdir != 0) { + /* Roll slider forward by an appropriate number of notches, given the + elapsed time. Roll `last_notch' forward by an appropriate number + of msec to ensure partial notches get counted properly */ + + delta = now - vefx_io_slider[sno].last_notch; + if (delta >= MSEC_PER_NOTCH) { + vefx_io_slider[sno].pos += sdir; + vefx_io_slider[sno].last_notch = now; + } + } + vefx_io_slider[sno].last_dir = sdir; + if (vefx_io_slider[sno].pos < 0) { + vefx_io_slider[sno].pos = 0; + } + if (vefx_io_slider[sno].pos > 15) { + vefx_io_slider[sno].pos = 15; + } + } +} + +uint8_t vefx_io_get_slider(uint8_t slider_no) +{ + if (slider_no > 4) { + return 0; + } + + return vefx_io_slider[slider_no].pos; +} + +bool vefx_io_write_16seg(const char *text) +{ + /* Insert code to write to your 16seg display here. + Log something and return false if you encounter an IO error. */ + + return true; +} + diff --git a/src/main/vefxio/vefxio.def b/src/main/vefxio/vefxio.def new file mode 100644 index 0000000..550384e --- /dev/null +++ b/src/main/vefxio/vefxio.def @@ -0,0 +1,9 @@ +LIBRARY vefxio + +EXPORTS + vefx_io_get_slider + vefx_io_recv + vefx_io_write_16seg + vefx_io_fini + vefx_io_init + vefx_io_set_loggers diff --git a/src/test/cconfig/Module.mk b/src/test/cconfig/Module.mk new file mode 100644 index 0000000..a61e8d9 --- /dev/null +++ b/src/test/cconfig/Module.mk @@ -0,0 +1,41 @@ +testexes += cconfig-test + +srcdir_cconfig-test := src/test/cconfig + +libs_cconfig-test := \ + cconfig \ + test \ + util \ + +src_cconfig-test := \ + cconfig-test.c \ + +################################################################################ + +testexes += cconfig-util-test + +srcdir_cconfig-util-test := src/test/cconfig + +libs_cconfig-util-test := \ + cconfig \ + test \ + util \ + +src_cconfig-util-test := \ + cconfig-util-test.c \ + +################################################################################ + +testexes += cconfig-cmd-test + +srcdir_cconfig-cmd-test := src/test/cconfig + +libs_cconfig-cmd-test := \ + cconfig \ + test \ + util \ + +src_cconfig-cmd-test := \ + cconfig-cmd-test.c \ + +################################################################################ \ No newline at end of file diff --git a/src/test/cconfig/cconfig-cmd-test.c b/src/test/cconfig/cconfig-cmd-test.c new file mode 100644 index 0000000..b3a3c13 --- /dev/null +++ b/src/test/cconfig/cconfig-cmd-test.c @@ -0,0 +1,64 @@ +#include "cconfig/cmd.h" +#include "cconfig/cconfig-util.h" + +#include "test/check.h" +#include "test/test.h" + +static void test_cmd() +{ + struct cconfig* config; + int argc = 6; + char* argv[] = {"-P", "test=aaa", "-C", "asdf", "-P", "test2=123"}; + char str[32]; + int32_t val; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + check_bool_true(cconfig_cmd_parse(config, "-P", argc, argv, true)); + + check_int_eq(config->nentries, 2); + + check_bool_true(cconfig_util_get_str(config, "test", str, sizeof(str), "1")); + check_str_eq(str, "aaa"); + + check_bool_true(cconfig_util_get_int(config, "test2", &val, 0)); + check_int_eq(val, 123); + + cconfig_finit(config); +} + +static void test_cmd_absent_entries() +{ + struct cconfig* config; + int argc = 6; + char* argv[] = {"-P", "test=aaa", "-C", "asdf", "-P", "test2=123"}; + char str[32]; + int32_t val; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + cconfig_set2(config, "test", "aaa"); + + check_bool_true(cconfig_cmd_parse(config, "-P", argc, argv, false)); + + check_int_eq(config->nentries, 1); + + check_bool_true(cconfig_util_get_str(config, "test", str, sizeof(str), "1")); + check_str_eq(str, "aaa"); + + check_bool_false(cconfig_util_get_int(config, "test2", &val, 0)); + check_int_eq(val, 0); + + cconfig_finit(config); +} + +TEST_MODULE_BEGIN("cconfig-cmd") +TEST_MODULE_TEST(test_cmd) +TEST_MODULE_TEST(test_cmd_absent_entries) +TEST_MODULE_END() diff --git a/src/test/cconfig/cconfig-test.c b/src/test/cconfig/cconfig-test.c new file mode 100644 index 0000000..e308ab3 --- /dev/null +++ b/src/test/cconfig/cconfig-test.c @@ -0,0 +1,156 @@ +#include "cconfig/cconfig.h" + +#include "test/check.h" +#include "test/test.h" + +static void test_init() +{ + struct cconfig* config; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + cconfig_finit(config); +} + +static void test_get_empty() +{ + struct cconfig* config; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + check_null(cconfig_get(config, "test")); + + cconfig_finit(config); +} + +static void test_set_get_one_elem() +{ + struct cconfig* config; + struct cconfig_entry* entry; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + cconfig_set(config, "test", "123", "desc"); + + check_int_eq(config->nentries, 1); + check_str_eq(config->entries[0].key, "test"); + check_str_eq(config->entries[0].value, "123"); + check_str_eq(config->entries[0].desc, "desc"); + + entry = cconfig_get(config, "test"); + + check_str_eq(entry->key, "test"); + check_str_eq(entry->value, "123"); + check_str_eq(entry->desc, "desc"); + + cconfig_finit(config); +} + +static void test_set_get_one_elem2() +{ + struct cconfig* config; + struct cconfig_entry* entry; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + cconfig_set2(config, "test", "123"); + + check_int_eq(config->nentries, 1); + check_str_eq(config->entries[0].key, "test"); + check_str_eq(config->entries[0].value, "123"); + check_str_eq(config->entries[0].desc, ""); + + entry = cconfig_get(config, "test"); + + check_str_eq(entry->key, "test"); + check_str_eq(entry->value, "123"); + check_str_eq(entry->desc, ""); + + cconfig_finit(config); +} + +static void test_set_get_same_elem() +{ + struct cconfig* config; + struct cconfig_entry* entry; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + cconfig_set(config, "test", "123", "desc"); + cconfig_set(config, "test", "12345", "aaa"); + + check_int_eq(config->nentries, 1); + + check_str_eq(config->entries[0].key, "test"); + check_str_eq(config->entries[0].value, "12345"); + check_str_eq(config->entries[0].desc, "aaa"); + + entry = cconfig_get(config, "test"); + + check_str_eq(entry->key, "test"); + check_str_eq(entry->value, "12345"); + check_str_eq(entry->desc, "aaa"); + + cconfig_finit(config); +} + +static void test_set_get_many_elem() +{ + struct cconfig* config; + struct cconfig_entry* entry; + char key[16]; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + for (int i = 0; i < 1000; i++) { + sprintf(key, "%d", i); + cconfig_set(config, key, "123", "desc"); + } + + check_int_eq(config->nentries, 1000); + + for (int i = 0; i < 1000; i++) { + sprintf(key, "%d", i); + check_str_eq(config->entries[i].key, key); + check_str_eq(config->entries[i].value, "123"); + check_str_eq(config->entries[i].desc, "desc"); + } + + for (int i = 0; i < 1000; i++) { + sprintf(key, "%d", i); + entry = cconfig_get(config, key); + + check_str_eq(entry->key, key); + check_str_eq(entry->value, "123"); + check_str_eq(entry->desc, "desc"); + } + + cconfig_finit(config); +} + +TEST_MODULE_BEGIN("cconfig") +TEST_MODULE_TEST(test_init) +TEST_MODULE_TEST(test_get_empty) +TEST_MODULE_TEST(test_set_get_one_elem) +TEST_MODULE_TEST(test_set_get_one_elem2) +TEST_MODULE_TEST(test_set_get_same_elem) +TEST_MODULE_TEST(test_set_get_many_elem) +TEST_MODULE_END() diff --git a/src/test/cconfig/cconfig-util-test.c b/src/test/cconfig/cconfig-util-test.c new file mode 100644 index 0000000..4d61fde --- /dev/null +++ b/src/test/cconfig/cconfig-util-test.c @@ -0,0 +1,220 @@ +#include "cconfig/cconfig-util.h" +#include "cconfig/cconfig.h" + +#include "test/check.h" +#include "test/test.h" + +static void test_set_get_int() +{ + struct cconfig* config; + int32_t value; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + cconfig_util_set_int(config, "test", 12345, "desc"); + + check_int_eq(config->nentries, 1); + check_str_eq(config->entries[0].key, "test"); + check_str_eq(config->entries[0].value, "12345"); + check_str_eq(config->entries[0].desc, "desc"); + + check_bool_true(cconfig_util_get_int(config, "test", &value, 0)); + check_int_eq(value, 12345); + + cconfig_finit(config); +} + +static void test_get_int_na() +{ + struct cconfig* config; + int32_t value; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + check_bool_false(cconfig_util_get_int(config, "test", &value, 123)); + check_int_eq(value, 123); + + cconfig_finit(config); +} + +static void test_set_get_float() +{ + struct cconfig* config; + float value; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + cconfig_util_set_float(config, "test", 100.5f, "desc"); + + check_int_eq(config->nentries, 1); + check_str_eq(config->entries[0].key, "test"); + check_str_eq(config->entries[0].desc, "desc"); + + check_bool_true(cconfig_util_get_float(config, "test", &value, 0)); + check_float_eq(value, 100.5f, 0.1f); + + cconfig_finit(config); +} + +static void test_get_float_na() +{ + struct cconfig* config; + float value; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + check_bool_false(cconfig_util_get_float(config, "test", &value, 115.9f)); + check_float_eq(value, 115.9f, 0.1f); + + cconfig_finit(config); +} + +static void test_set_get_bool() +{ + struct cconfig* config; + bool value; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + cconfig_util_set_bool(config, "test", true, "desc"); + + check_int_eq(config->nentries, 1); + check_str_eq(config->entries[0].key, "test"); + check_str_eq(config->entries[0].desc, "desc"); + + check_bool_true(cconfig_util_get_bool(config, "test", &value, 0)); + check_bool_true(value); + + cconfig_finit(config); +} + +static void test_get_bool_na() +{ + struct cconfig* config; + bool value; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + check_bool_false(cconfig_util_get_bool(config, "test", &value, true)); + check_bool_true(value); + + cconfig_finit(config); +} + +static void test_set_get_str() +{ + struct cconfig* config; + char value[1024]; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + cconfig_util_set_str(config, "test", "hello world", "desc"); + + check_int_eq(config->nentries, 1); + check_str_eq(config->entries[0].key, "test"); + check_str_eq(config->entries[0].desc, "desc"); + + check_bool_true(cconfig_util_get_str(config, "test", value, sizeof(value), + "world hello")); + check_str_eq(value, "hello world"); + + cconfig_finit(config); +} + +static void test_get_str_na() +{ + struct cconfig* config; + char value[1024]; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + check_bool_false(cconfig_util_get_str(config, "test", value, sizeof(value), + "world hello")); + check_str_eq(value, "world hello"); + + cconfig_finit(config); +} + +static void test_set_get_data() +{ + struct cconfig* config; + uint8_t value[1024]; + uint8_t value2[1024]; + uint8_t value_default[] = {0x00}; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + for (int i = 0; i < sizeof(value); i++) { + value[i] = (uint8_t) i; + } + + cconfig_util_set_data(config, "test", value, sizeof(value), "desc"); + + check_int_eq(config->nentries, 1); + check_str_eq(config->entries[0].key, "test"); + check_str_eq(config->entries[0].desc, "desc"); + + check_bool_true(cconfig_util_get_data(config, "test", value2, sizeof(value2), + value_default)); + check_data_eq(value2, sizeof(value2), value, sizeof(value)); + + cconfig_finit(config); +} + +static void test_get_data_na() +{ + struct cconfig* config; + uint8_t value[1024]; + uint8_t value_default[] = {0x01}; + + config = cconfig_init(); + + check_int_eq(config->nentries, 0); + check_null(config->entries); + + check_bool_false(cconfig_util_get_data(config, "test", value, sizeof(value), + value_default)); + check_data_eq(value, 1, value_default, sizeof(value_default)); + + cconfig_finit(config); +} + +TEST_MODULE_BEGIN("cconfig-util") +TEST_MODULE_TEST(test_set_get_int) +TEST_MODULE_TEST(test_get_int_na) +TEST_MODULE_TEST(test_set_get_float) +TEST_MODULE_TEST(test_get_float_na) +TEST_MODULE_TEST(test_set_get_bool) +TEST_MODULE_TEST(test_get_bool_na) +TEST_MODULE_TEST(test_set_get_str) +TEST_MODULE_TEST(test_get_str_na) +TEST_MODULE_TEST(test_set_get_data) +TEST_MODULE_TEST(test_get_data_na) +TEST_MODULE_END() diff --git a/src/test/iidxhook-util/Module.mk b/src/test/iidxhook-util/Module.mk new file mode 100644 index 0000000..be4883e --- /dev/null +++ b/src/test/iidxhook-util/Module.mk @@ -0,0 +1,66 @@ +testexes += iidxhook-util-config-eamuse-test + +srcdir_iidxhook-util-config-eamuse-test := src/test/iidxhook-util + +ldflags_iidxhook-util-config-eamuse-test := \ + -lws2_32 \ + +libs_iidxhook-util-config-eamuse-test := \ + security \ + iidxhook-util \ + cconfig \ + test \ + util \ + +src_iidxhook-util-config-eamuse-test := \ + iidxhook-util-config-eamuse-test.c \ + +################################################################################ + +testexes += iidxhook-util-config-gfx-test + +srcdir_iidxhook-util-config-gfx-test := src/test/iidxhook-util + +libs_iidxhook-util-config-gfx-test := \ + security \ + iidxhook-util \ + cconfig \ + test \ + util \ + +src_iidxhook-util-config-gfx-test := \ + iidxhook-util-config-gfx-test.c \ + +################################################################################ + +testexes += iidxhook-util-config-misc-test + +srcdir_iidxhook-util-config-misc-test := src/test/iidxhook-util + +libs_iidxhook-util-config-misc-test := \ + security \ + iidxhook-util \ + cconfig \ + test \ + util \ + +src_iidxhook-util-config-misc-test := \ + iidxhook-util-config-misc-test.c \ + +################################################################################ + +testexes += iidxhook-util-config-sec-test + +srcdir_iidxhook-util-config-sec-test := src/test/iidxhook-util + +libs_iidxhook-util-config-sec-test := \ + security \ + iidxhook-util \ + cconfig \ + test \ + util \ + +src_iidxhook-util-config-sec-test := \ + iidxhook-util-config-sec-test.c \ + +################################################################################ \ No newline at end of file diff --git a/src/test/iidxhook-util/iidxhook-util-config-eamuse-test.c b/src/test/iidxhook-util/iidxhook-util-config-eamuse-test.c new file mode 100644 index 0000000..77b87e0 --- /dev/null +++ b/src/test/iidxhook-util/iidxhook-util-config-eamuse-test.c @@ -0,0 +1,86 @@ +#include "iidxhook-util/config-eamuse.h" + +#include "test/check.h" +#include "test/test.h" + +/* We don't care about cleaning up some of the memory to avoid cluttering + the tests */ + +static void test_config_eamuse_defaults() +{ + struct cconfig* config; + struct iidxhook_util_config_eamuse config_eamuse; + + config = cconfig_init(); + + iidxhook_util_config_eamuse_init(config); + iidxhook_util_config_eamuse_get(&config_eamuse, config); + + cconfig_finit(config); + + check_str_eq(config_eamuse.card_type, "C02"); + check_str_eq(net_addr_to_str(&config_eamuse.server), "localhost:80"); + check_str_eq(security_id_to_str(&config_eamuse.pcbid, false), + "0101020304050607086F"); + check_str_eq(security_id_to_str(&config_eamuse.eamid, false), + "0101020304050607086F"); +} + +static void test_config_eamuse() +{ + struct cconfig* config; + struct iidxhook_util_config_eamuse config_eamuse; + + config = cconfig_init(); + + iidxhook_util_config_eamuse_init(config); + + cconfig_set2(config, "eamuse.card_type", "D01"); + cconfig_set2(config, "eamuse.server", "http://myServer.com/legacy"); + cconfig_set2(config, "eamuse.pcbid", "0101020304050607086F"); + cconfig_set2(config, "eamuse.eamid", "0101020304050607086F"); + + iidxhook_util_config_eamuse_get(&config_eamuse, config); + + cconfig_finit(config); + + check_str_eq(config_eamuse.card_type, "D01"); + check_str_eq(net_addr_to_str(&config_eamuse.server), + "http://myServer.com/legacy"); + check_str_eq(security_id_to_str(&config_eamuse.pcbid, false), + "0101020304050607086F"); + check_str_eq(security_id_to_str(&config_eamuse.eamid, false), + "0101020304050607086F"); +} + +static void test_config_eamuse_invalid_values() +{ + struct cconfig* config; + struct iidxhook_util_config_eamuse config_eamuse; + + config = cconfig_init(); + + iidxhook_util_config_eamuse_init(config); + + cconfig_set2(config, "eamuse.card_type", "XXXX"); + cconfig_set2(config, "eamuse.server", "asdf"); + cconfig_set2(config, "eamuse.pcbid", "00000000000000000000"); + cconfig_set2(config, "eamuse.eamid", "00000000000000000000"); + + iidxhook_util_config_eamuse_get(&config_eamuse, config); + + cconfig_finit(config); + + check_str_eq(config_eamuse.card_type, "C02"); + check_str_eq(net_addr_to_str(&config_eamuse.server), "asdf"); + check_str_eq(security_id_to_str(&config_eamuse.pcbid, false), + "00000000000000000000"); + check_str_eq(security_id_to_str(&config_eamuse.eamid, false), + "00000000000000000000"); +} + +TEST_MODULE_BEGIN("iidxhook-config-eamuse") +TEST_MODULE_TEST(test_config_eamuse_defaults) +TEST_MODULE_TEST(test_config_eamuse) +TEST_MODULE_TEST(test_config_eamuse_invalid_values) +TEST_MODULE_END() diff --git a/src/test/iidxhook-util/iidxhook-util-config-gfx-test.c b/src/test/iidxhook-util/iidxhook-util-config-gfx-test.c new file mode 100644 index 0000000..b2a5fb6 --- /dev/null +++ b/src/test/iidxhook-util/iidxhook-util-config-gfx-test.c @@ -0,0 +1,107 @@ +#include "iidxhook-util/config-gfx.h" + +#include "test/check.h" +#include "test/test.h" + +/* We don't care about cleaning up some of the memory to avoid cluttering + the tests */ + +static void test_config_gfx_defaults() +{ + struct cconfig* config; + struct iidxhook_config_gfx config_gfx; + + config = cconfig_init(); + + iidxhook_config_gfx_init(config); + iidxhook_config_gfx_get(&config_gfx, config); + + cconfig_finit(config); + + check_bool_false(config_gfx.bgvideo_uv_fix); + check_bool_false(config_gfx.framed); + check_float_eq(config_gfx.frame_rate_limit, 0.0f, 0.1f); + check_float_eq(config_gfx.monitor_check, -1.0f, 0.1f); + check_int_eq(config_gfx.pci_id_vid, 0x1002); + check_int_eq(config_gfx.pci_id_pid, 0x7146); + check_bool_false(config_gfx.windowed); + check_int_eq(config_gfx.scale_back_buffer_width, 0); + check_int_eq(config_gfx.scale_back_buffer_height, 0); + check_int_eq(config_gfx.scale_back_buffer_width, D3D9_BACK_BUFFER_SCALE_FILTER_NONE); +} + +static void test_config_gfx() +{ + struct cconfig* config; + struct iidxhook_config_gfx config_gfx; + + config = cconfig_init(); + + iidxhook_config_gfx_init(config); + + cconfig_set2(config, "gfx.bgvideo_uv_fix", "true"); + cconfig_set2(config, "gfx.framed", "true"); + cconfig_set2(config, "gfx.frame_rate_limit", "60.04"); + cconfig_set2(config, "gfx.monitor_check", "59.999"); + cconfig_set2(config, "gfx.pci_id", "1111:1234"); + cconfig_set2(config, "gfx.windowed", "true"); + cconfig_set2(config, "gfx.scale_back_buffer_width", "1920"); + cconfig_set2(config, "gfx.scale_back_buffer_height", "1080"); + cconfig_set2(config, "gfx.scale_back_buffer_filter", "linear"); + + iidxhook_config_gfx_get(&config_gfx, config); + + cconfig_finit(config); + + check_bool_true(config_gfx.bgvideo_uv_fix); + check_bool_true(config_gfx.framed); + check_float_eq(config_gfx.frame_rate_limit, 60.04f, 0.1f); + check_float_eq(config_gfx.monitor_check, 59.999f, 0.001f); + check_int_eq(config_gfx.pci_id_vid, 0x1111); + check_int_eq(config_gfx.pci_id_pid, 0x1234); + check_bool_true(config_gfx.windowed); + check_int_eq(config_gfx.scale_back_buffer_width, 1920); + check_int_eq(config_gfx.scale_back_buffer_height, 1080); + check_int_eq(config_gfx.scale_back_buffer_filter, D3D9_BACK_BUFFER_SCALE_FILTER_LINEAR); +} + +static void test_config_gfx_invalid_values() +{ + struct cconfig* config; + struct iidxhook_config_gfx config_gfx; + + config = cconfig_init(); + + iidxhook_config_gfx_init(config); + + cconfig_set2(config, "gfx.bgvideo_uv_fix", "123"); + cconfig_set2(config, "gfx.framed", "aaa"); + cconfig_set2(config, "gfx.frame_rate_limit", "-1.0f"); + cconfig_set2(config, "gfx.monitor_check", "asdf"); + cconfig_set2(config, "gfx.pci_id", "11111234"); + cconfig_set2(config, "gfx.windowed", "-5"); + cconfig_set2(config, "gfx.scale_back_buffer_width", "asdf"); + cconfig_set2(config, "gfx.scale_back_buffer_height", ""); + cconfig_set2(config, "gfx.scale_back_buffer_filter", "ffff"); + + iidxhook_config_gfx_get(&config_gfx, config); + + cconfig_finit(config); + + check_bool_false(config_gfx.bgvideo_uv_fix); + check_bool_false(config_gfx.framed); + check_float_eq(config_gfx.frame_rate_limit, 0.0f, 0.1f); + check_float_eq(config_gfx.monitor_check, -1.0f, 0.1f); + check_int_eq(config_gfx.pci_id_vid, 0x1002); + check_int_eq(config_gfx.pci_id_pid, 0x7146); + check_bool_false(config_gfx.windowed); + check_int_eq(config_gfx.scale_back_buffer_width, 0); + check_int_eq(config_gfx.scale_back_buffer_height, 0); + check_int_eq(config_gfx.scale_back_buffer_width, D3D9_BACK_BUFFER_SCALE_FILTER_NONE); +} + +TEST_MODULE_BEGIN("iidxhook-config-gfx") +TEST_MODULE_TEST(test_config_gfx_defaults) +TEST_MODULE_TEST(test_config_gfx) +TEST_MODULE_TEST(test_config_gfx_invalid_values) +TEST_MODULE_END() diff --git a/src/test/iidxhook-util/iidxhook-util-config-misc-test.c b/src/test/iidxhook-util/iidxhook-util-config-misc-test.c new file mode 100644 index 0000000..02a178a --- /dev/null +++ b/src/test/iidxhook-util/iidxhook-util-config-misc-test.c @@ -0,0 +1,68 @@ +#include "iidxhook-util/config-misc.h" + +#include "test/check.h" +#include "test/test.h" + +/* We don't care about cleaning up some of the memory to avoid cluttering the tests */ + +static void test_config_misc_defaults() +{ + struct cconfig* config; + struct iidxhook_config_misc config_misc; + + config = cconfig_init(); + + iidxhook_config_misc_init(config); + iidxhook_config_misc_get(&config_misc, config); + + cconfig_finit(config); + + check_bool_false(config_misc.disable_clock_set); + check_bool_false(config_misc.rteffect_stub); +} + +static void test_config_misc() +{ + struct cconfig* config; + struct iidxhook_config_misc config_misc; + + config = cconfig_init(); + + iidxhook_config_misc_init(config); + + cconfig_set2(config, "misc.disable_clock_set", "true"); + cconfig_set2(config, "misc.rteffect_stub", "true"); + + iidxhook_config_misc_get(&config_misc, config); + + cconfig_finit(config); + + check_bool_true(config_misc.disable_clock_set); + check_bool_true(config_misc.rteffect_stub); +} + +static void test_config_misc_invalid_values() +{ + struct cconfig* config; + struct iidxhook_config_misc config_misc; + + config = cconfig_init(); + + iidxhook_config_misc_init(config); + + cconfig_set2(config, "misc.disable_clock_set", "asdf"); + cconfig_set2(config, "misc.rteffect_stub", "222"); + + iidxhook_config_misc_get(&config_misc, config); + + cconfig_finit(config); + + check_bool_false(config_misc.disable_clock_set); + check_bool_false(config_misc.rteffect_stub); +} + +TEST_MODULE_BEGIN("iidxhook-config-misc") +TEST_MODULE_TEST(test_config_misc_defaults) +TEST_MODULE_TEST(test_config_misc) +TEST_MODULE_TEST(test_config_misc_invalid_values) +TEST_MODULE_END() diff --git a/src/test/iidxhook-util/iidxhook-util-config-sec-test.c b/src/test/iidxhook-util/iidxhook-util-config-sec-test.c new file mode 100644 index 0000000..32bd92b --- /dev/null +++ b/src/test/iidxhook-util/iidxhook-util-config-sec-test.c @@ -0,0 +1,82 @@ +#include "iidxhook-util/config-sec.h" + +#include "test/check.h" +#include "test/test.h" + +/* We don't care about cleaning up some of the memory to avoid cluttering the tests */ + +static void test_config_sec_defaults() +{ + struct cconfig* config; + struct iidxhook_config_sec config_sec; + const uint32_t boot_seeds[3] = {0, 0, 0}; + + config = cconfig_init(); + + iidxhook_config_sec_init(config); + iidxhook_config_sec_get(&config_sec, config); + + cconfig_finit(config); + + check_str_eq(security_mcode_to_str(&config_sec.boot_version), "GEC02 "); + check_data_eq(config_sec.boot_seeds, sizeof(config_sec.boot_seeds), + (void*) boot_seeds, sizeof(boot_seeds)); + check_str_eq(security_mcode_to_str(&config_sec.black_plug_mcode), + "GQC02JAA"); +} + +static void test_config_sec() +{ + struct cconfig* config; + struct iidxhook_config_sec config_sec; + const uint32_t boot_seeds[3] = {1, 1, 1}; + + config = cconfig_init(); + + iidxhook_config_sec_init(config); + + cconfig_set2(config, "sec.boot_version", "ASDFG "); + cconfig_set2(config, "sec.boot_seeds", "1:1:1"); + cconfig_set2(config, "sec.black_plug_mcode", "GQD01JAB"); + + iidxhook_config_sec_get(&config_sec, config); + + cconfig_finit(config); + + check_str_eq(security_mcode_to_str(&config_sec.boot_version), "ASDFG "); + check_data_eq(config_sec.boot_seeds, sizeof(config_sec.boot_seeds), + (void*) boot_seeds, sizeof(boot_seeds)); + check_str_eq(security_mcode_to_str(&config_sec.black_plug_mcode), + "GQD01JAB"); +} + +static void test_config_sec_invalid_values() +{ + struct cconfig* config; + struct iidxhook_config_sec config_sec; + const uint32_t boot_seeds[3] = {0, 0, 0}; + + config = cconfig_init(); + + iidxhook_config_sec_init(config); + + cconfig_set2(config, "sec.boot_version", "ASDFG1234"); + cconfig_set2(config, "sec.boot_seeds", "1:11"); + cconfig_set2(config, "sec.black_plug_mcode", "GQD01JABA"); + + iidxhook_config_sec_get(&config_sec, config); + + cconfig_finit(config); + + check_str_eq(security_mcode_to_str(&config_sec.boot_version), "GEC02 "); + check_data_eq(config_sec.boot_seeds, sizeof(config_sec.boot_seeds), + (void*) boot_seeds, sizeof(boot_seeds)); + check_str_eq(security_mcode_to_str(&config_sec.black_plug_mcode), + "GQC02JAA"); +} + +TEST_MODULE_BEGIN("iidxhook-config-sec") +TEST_MODULE_TEST(test_config_sec_defaults) +TEST_MODULE_TEST(test_config_sec) +TEST_MODULE_TEST(test_config_sec_invalid_values) +TEST_MODULE_END() diff --git a/src/test/iidxhook/Module.mk b/src/test/iidxhook/Module.mk new file mode 100644 index 0000000..8c08642 --- /dev/null +++ b/src/test/iidxhook/Module.mk @@ -0,0 +1,29 @@ +testexes += iidxhook-config-iidxhook1-test + +srcdir_iidxhook-config-iidxhook1-test := src/test/iidxhook + +libs_iidxhook-config-iidxhook1-test := \ + cconfig \ + iidxhook1 \ + test \ + util \ + +src_iidxhook-config-iidxhook1-test := \ + iidxhook-config-iidxhook1-test.c \ + +################################################################################ + +testexes += iidxhook-config-iidxhook2-test + +srcdir_iidxhook-config-iidxhook2-test := src/test/iidxhook + +libs_iidxhook-config-iidxhook2-test := \ + iidxhook2 \ + cconfig \ + test \ + util \ + +src_iidxhook-config-iidxhook2-test := \ + iidxhook-config-iidxhook2-test.c \ + +################################################################################ diff --git a/src/test/iidxhook/iidxhook-config-iidxhook1-test.c b/src/test/iidxhook/iidxhook-config-iidxhook1-test.c new file mode 100644 index 0000000..2e55834 --- /dev/null +++ b/src/test/iidxhook/iidxhook-config-iidxhook1-test.c @@ -0,0 +1,68 @@ +#include "iidxhook1/config-iidxhook1.h" + +#include "test/check.h" +#include "test/test.h" + +/* We don't care about cleaning up some of the memory to avoid cluttering the tests */ + +static void test_config_iidxhook1_defaults() +{ + struct cconfig* config; + struct iidxhook_config_iidxhook1 config_iidxhook1; + + config = cconfig_init(); + + iidxhook_config_iidxhook1_init(config); + iidxhook_config_iidxhook1_get(&config_iidxhook1, config); + + cconfig_finit(config); + + check_bool_false(config_iidxhook1.happy_sky_ms_bg_fix); + check_bool_false(config_iidxhook1.use_d3d9_hooks); +} + +static void test_config_iidxhook1() +{ + struct cconfig* config; + struct iidxhook_config_iidxhook1 config_iidxhook1; + + config = cconfig_init(); + + iidxhook_config_iidxhook1_init(config); + + cconfig_set2(config, "misc.happy_sky_ms_bg_fix", "true"); + cconfig_set2(config, "misc.use_d3d9_hooks", "true"); + + iidxhook_config_iidxhook1_get(&config_iidxhook1, config); + + cconfig_finit(config); + + check_bool_true(config_iidxhook1.happy_sky_ms_bg_fix); + check_bool_true(config_iidxhook1.use_d3d9_hooks); +} + +static void test_config_iidxhook1_invalid_values() +{ + struct cconfig* config; + struct iidxhook_config_iidxhook1 config_iidxhook1; + + config = cconfig_init(); + + iidxhook_config_iidxhook1_init(config); + + cconfig_set2(config, "misc.happy_sky_ms_bg_fix", "123"); + cconfig_set2(config, "misc.use_d3d9_hooks", "sss"); + + iidxhook_config_iidxhook1_get(&config_iidxhook1, config); + + cconfig_finit(config); + + check_bool_false(config_iidxhook1.happy_sky_ms_bg_fix); + check_bool_false(config_iidxhook1.use_d3d9_hooks); +} + +TEST_MODULE_BEGIN("iidxhook-config-iidxhook1") +TEST_MODULE_TEST(test_config_iidxhook1_defaults) +TEST_MODULE_TEST(test_config_iidxhook1) +TEST_MODULE_TEST(test_config_iidxhook1_invalid_values) +TEST_MODULE_END() diff --git a/src/test/iidxhook/iidxhook-config-iidxhook2-test.c b/src/test/iidxhook/iidxhook-config-iidxhook2-test.c new file mode 100644 index 0000000..f14ebed --- /dev/null +++ b/src/test/iidxhook/iidxhook-config-iidxhook2-test.c @@ -0,0 +1,68 @@ +#include "iidxhook2/config-iidxhook2.h" + +#include "test/check.h" +#include "test/test.h" + +/* We don't care about cleaning up some of the memory to avoid cluttering the tests */ + +static void test_config_iidxhook2_defaults() +{ + struct cconfig* config; + struct iidxhook_config_iidxhook2 config_iidxhook2; + + config = cconfig_init(); + + iidxhook_config_iidxhook2_init(config); + iidxhook_config_iidxhook2_get(&config_iidxhook2, config); + + cconfig_finit(config); + + check_bool_false(config_iidxhook2.distorted_ms_bg_fix); + check_bool_false(config_iidxhook2.use_d3d9_hooks); +} + +static void test_config_iidxhook2() +{ + struct cconfig* config; + struct iidxhook_config_iidxhook2 config_iidxhook2; + + config = cconfig_init(); + + iidxhook_config_iidxhook2_init(config); + + cconfig_set2(config, "misc.distorted_ms_bg_fix", "true"); + cconfig_set2(config, "misc.use_d3d9_hooks", "true"); + + iidxhook_config_iidxhook2_get(&config_iidxhook2, config); + + cconfig_finit(config); + + check_bool_true(config_iidxhook2.distorted_ms_bg_fix); + check_bool_true(config_iidxhook2.use_d3d9_hooks); +} + +static void test_config_iidxhook2_invalid_values() +{ + struct cconfig* config; + struct iidxhook_config_iidxhook2 config_iidxhook2; + + config = cconfig_init(); + + iidxhook_config_iidxhook2_init(config); + + cconfig_set2(config, "misc.distorted_ms_bg_fix", "123"); + cconfig_set2(config, "misc.use_d3d9_hooks", "sss"); + + iidxhook_config_iidxhook2_get(&config_iidxhook2, config); + + cconfig_finit(config); + + check_bool_false(config_iidxhook2.distorted_ms_bg_fix); + check_bool_false(config_iidxhook2.use_d3d9_hooks); +} + +TEST_MODULE_BEGIN("iidxhook-config-iidxhook2") +TEST_MODULE_TEST(test_config_iidxhook2_defaults) +TEST_MODULE_TEST(test_config_iidxhook2) +TEST_MODULE_TEST(test_config_iidxhook2_invalid_values) +TEST_MODULE_END() diff --git a/src/test/iidxhook8/Module.mk b/src/test/iidxhook8/Module.mk new file mode 100644 index 0000000..7759e99 --- /dev/null +++ b/src/test/iidxhook8/Module.mk @@ -0,0 +1,27 @@ +testexes += iidxhook8-config-cam-test + +srcdir_iidxhook8-config-cam-test := src/test/iidxhook8 + +libs_iidxhook8-config-cam-test := \ + cconfig \ + test \ + util \ + +src_iidxhook8-config-cam-test := \ + iidxhook8-config-cam-test.c \ + +################################################################################ + +testexes += iidxhook8-config-io-test + +srcdir_iidxhook8-config-io-test := src/test/iidxhook8 + +libs_iidxhook8-config-io-test := \ + cconfig \ + test \ + util \ + +src_iidxhook8-config-io-test := \ + iidxhook8-config-io-test.c \ + +################################################################################ \ No newline at end of file diff --git a/src/test/iidxhook8/iidxhook8-config-cam-test.c b/src/test/iidxhook8/iidxhook8-config-cam-test.c new file mode 100644 index 0000000..a605187 --- /dev/null +++ b/src/test/iidxhook8/iidxhook8-config-cam-test.c @@ -0,0 +1,73 @@ +#include "iidxhook8/config-cam.h" + +#include "test/check.h" +#include "test/test.h" + +/* We don't care about cleaning up some of the memory to avoid cluttering the tests */ + +static void test_config_cam_defaults() +{ + struct cconfig* config; + struct iidxhook8_config_cam config_cam; + + config = cconfig_init(); + + iidxhook8_config_cam_init(config); + iidxhook8_config_cam_get(&config_cam, config); + + cconfig_finit(config); + + check_bool_false(config_cam.disable_emu); + check_str_eq(config_cam.device_id1, ""); + check_str_eq(config_cam.device_id2, ""); +} + +static void test_config_cam() +{ + struct cconfig* config; + struct iidxhook8_config_cam config_cam; + + config = cconfig_init(); + + iidxhook8_config_cam_init(config); + + cconfig_set2(config, "cam.disable_emu", "true"); + cconfig_set2(config, "cam.device_id1", "asdjkasd"); + cconfig_set2(config, "cam.device_id2", "1234"); + + iidxhook8_config_cam_get(&config_cam, config); + + cconfig_finit(config); + + check_bool_true(config_cam.disable_emu); + check_str_eq(config_cam.device_id1, "asdjkasd"); + check_str_eq(config_cam.device_id2, "1234"); +} + +static void test_config_invalid_values() +{ + struct cconfig* config; + struct iidxhook8_config_cam config_cam; + + config = cconfig_init(); + + iidxhook8_config_cam_init(config); + + cconfig_set2(config, "cam.disable_emu", "123"); + cconfig_set2(config, "cam.device_id1", "asdjkasd"); + cconfig_set2(config, "cam.device_id2", "1234"); + + iidxhook8_config_cam_get(&config_cam, config); + + cconfig_finit(config); + + check_bool_fakse(config_cam.disable_emu); + check_str_eq(config_cam.device_id1, "asdjkasd"); + check_str_eq(config_cam.device_id2, "1234"); +} + +TEST_MODULE_BEGIN("iidxhook8-config-cam") +TEST_MODULE_TEST(test_config_cam_defaults) +TEST_MODULE_TEST(test_config_cam) +TEST_MODULE_TEST(test_config_cam_invalid_values) +TEST_MODULE_END() diff --git a/src/test/iidxhook8/iidxhook8-config-io-test.c b/src/test/iidxhook8/iidxhook8-config-io-test.c new file mode 100644 index 0000000..293e8b9 --- /dev/null +++ b/src/test/iidxhook8/iidxhook8-config-io-test.c @@ -0,0 +1,73 @@ +#include "iidxhook8/config-io.h" + +#include "test/check.h" +#include "test/test.h" + +/* We don't care about cleaning up some of the memory to avoid cluttering the tests */ + +static void test_config_io_defaults() +{ + struct cconfig* config; + struct iidxhook8_config_io config_io; + + config = cconfig_init(); + + iidxhook8_config_io_init(config); + iidxhook8_config_io_get(&config_io, config); + + cconfig_finit(config); + + check_bool_false(config_io.disable_card_reader_emu); + check_bool_false(config_io.disable_bio2_emu); + check_bool_false(config_io.disable_poll_limiter); +} + +static void test_config_io() +{ + struct cconfig* config; + struct iidxhook8_config_io config_io; + + config = cconfig_init(); + + iidxhook8_config_io_init(config); + + cconfig_set2(config, "io.disable_card_reader_emu", "true"); + cconfig_set2(config, "io.disable_bio2_emu", "true"); + cconfig_set2(config, "io.disable_poll_limiter", "true"); + + iidxhook8_config_io_get(&config_io, config); + + cconfig_finit(config); + + check_bool_true(config_io.disable_card_reader_emu); + check_bool_true(config_io.disable_bio2_emu); + check_bool_true(config_io.disable_poll_limiter); +} + +static void test_config_invalid_values() +{ + struct cconfig* config; + struct iidxhook8_config_io config_io; + + config = cconfig_init(); + + iidxhook8_config_io_init(config); + + cconfig_set2(config, "io.disable_card_reader_emu", "asdf"); + cconfig_set2(config, "io.disable_bio2_emu", "123"); + cconfig_set2(config, "io.disable_poll_limiter", ""); + + iidxhook8_config_io_get(&config_io, config); + + cconfig_finit(config); + + check_bool_false(config_io.disable_card_reader_emu); + check_bool_false(config_io.disable_bio2_emu); + check_bool_false(config_io.disable_poll_limiter); +} + +TEST_MODULE_BEGIN("iidxhook8-config-io") +TEST_MODULE_TEST(test_config_io_defaults) +TEST_MODULE_TEST(test_config_io) +TEST_MODULE_TEST(test_config_io_invalid_values) +TEST_MODULE_END() diff --git a/src/test/security/Module.mk b/src/test/security/Module.mk new file mode 100644 index 0000000..3bdef67 --- /dev/null +++ b/src/test/security/Module.mk @@ -0,0 +1,83 @@ +testexes += security-id-test + +srcdir_security-id-test := src/test/security + +libs_security-id-test := \ + security \ + test \ + util \ + +src_security-id-test := \ + security-id-test.c \ + +################################################################################ + +testexes += security-mcode-test + +srcdir_security-mcode-test := src/test/security + +libs_security-mcode-test := \ + security \ + test \ + util \ + +src_security-mcode-test := \ + security-mcode-test.c \ + +################################################################################ + +testexes += security-util-test + +srcdir_security-util-test := src/test/security + +libs_security-util-test := \ + security \ + test \ + util \ + +src_security-util-test := \ + security-util-test.c \ + +################################################################################ + +testexes += security-rp-test + +srcdir_security-rp-test := src/test/security + +libs_security-rp-test := \ + security \ + test \ + util \ + +src_security-rp-test := \ + security-rp-test.c \ + +################################################################################ + +testexes += security-rp2-test + +srcdir_security-rp2-test := src/test/security + +libs_security-rp2-test := \ + security \ + test \ + util \ + +src_security-rp2-test := \ + security-rp2-test.c \ + +################################################################################ + +testexes += security-rp3-test + +srcdir_security-rp3-test := src/test/security + +libs_security-rp3-test := \ + security \ + test \ + util \ + +src_security-rp3-test := \ + security-rp3-test.c \ + +################################################################################ \ No newline at end of file diff --git a/src/test/security/security-id-test.c b/src/test/security/security-id-test.c new file mode 100644 index 0000000..c40b6cd --- /dev/null +++ b/src/test/security/security-id-test.c @@ -0,0 +1,57 @@ +#include "security/id.h" + +#include "test/check.h" +#include "test/test.h" + +static void test_to_str() +{ + check_str_eq(security_id_to_str(&security_id_default, false), + "0101020304050607086F"); + check_str_eq(security_id_to_str(&security_id_default, true), + "0102030405060708"); +} + +static void test_parse_valid() +{ + struct security_id id; + + check_bool_true(security_id_parse("0101020304050607086F", &id)); + check_bool_true(security_id_verify(&id)); +} + +static void test_parse_invalid() +{ + struct security_id id; + + check_bool_true(security_id_parse("0102030405060708", &id)); + check_bool_false(security_id_verify(&id)); + + check_bool_true(security_id_parse("010102030405060708FF", &id)); + check_bool_false(security_id_verify(&id)); + + check_bool_false(security_id_parse("asdf", &id)); +} + +static void test_prepare_and_verify() +{ + struct security_id id; + + check_bool_true(security_id_parse("01AB50E2DC4F832A9A78", &id)); + check_bool_true(security_id_verify(&id)); +} + +static void test_prepare_and_verify_invalid() +{ + struct security_id id; + + check_bool_true(security_id_parse("01AB50E2DC4F832A9A79", &id)); + check_bool_false(security_id_verify(&id)); +} + +TEST_MODULE_BEGIN("security-id") +TEST_MODULE_TEST(test_to_str) +TEST_MODULE_TEST(test_parse_valid) +TEST_MODULE_TEST(test_parse_invalid) +TEST_MODULE_TEST(test_prepare_and_verify) +TEST_MODULE_TEST(test_prepare_and_verify_invalid) +TEST_MODULE_END() diff --git a/src/test/security/security-mcode-test.c b/src/test/security/security-mcode-test.c new file mode 100644 index 0000000..c6cf0fc --- /dev/null +++ b/src/test/security/security-mcode-test.c @@ -0,0 +1,63 @@ +#include "security/mcode.h" + +#include "test/check.h" +#include "test/test.h" + +static void test_parse_valid() +{ + struct security_mcode mcode; + + check_bool_true(security_mcode_parse("GQD01JAA", &mcode)); + check_char_eq(mcode.header, 'G'); + check_char_eq(mcode.unkn, 'Q'); + check_data_eq(mcode.game, sizeof(mcode.game), "D01", 3); + check_char_eq(mcode.region, 'J'); + check_char_eq(mcode.cabinet, 'A'); + check_char_eq(mcode.revision, 'A'); +} + +static void test_parse_valid_2() +{ + struct security_mcode mcode; + + check_bool_true(security_mcode_parse("GQD01", &mcode)); + check_char_eq(mcode.header, 'G'); + check_char_eq(mcode.unkn, 'Q'); + check_data_eq(mcode.game, sizeof(mcode.game), "D01", 3); + check_char_eq(mcode.region, ' '); + check_char_eq(mcode.cabinet, ' '); + check_char_eq(mcode.revision, ' '); +} + +static void test_parse_invalid() +{ + struct security_mcode mcode; + + check_bool_false(security_mcode_parse("GQD01JAAA", &mcode)); + check_char_eq(mcode.header, ' '); + check_char_eq(mcode.unkn, ' '); + check_data_eq(mcode.game, sizeof(mcode.game), " ", 3); + check_char_eq(mcode.region, ' '); + check_char_eq(mcode.cabinet, ' '); + check_char_eq(mcode.revision, ' '); +} + +static void test_to_str() +{ + struct security_mcode mcode; + mcode.header = 'G'; + mcode.unkn = 'Q'; + memcpy(mcode.game, "D01", 3); + mcode.region = 'J'; + mcode.cabinet = 'A'; + mcode.revision = 'A'; + + check_str_eq(security_mcode_to_str(&mcode), "GQD01JAA"); +} + +TEST_MODULE_BEGIN("security-mcode") +TEST_MODULE_TEST(test_parse_valid) +TEST_MODULE_TEST(test_parse_valid_2) +TEST_MODULE_TEST(test_parse_invalid) +TEST_MODULE_TEST(test_to_str) +TEST_MODULE_END() diff --git a/src/test/security/security-rp-test.c b/src/test/security/security-rp-test.c new file mode 100644 index 0000000..a1e0a61 --- /dev/null +++ b/src/test/security/security-rp-test.c @@ -0,0 +1,163 @@ +#include "security/id.h" +#include "security/rp.h" + +#include "test/check.h" +#include "test/test.h" + +static const struct security_mcode boot_version_iidx_09_to_13 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_E, + .game = SECURITY_MCODE_GAME_IIDX_9, + .region = SECURITY_MCODE_FIELD_BLANK, + .cabinet = SECURITY_MCODE_FIELD_BLANK, + .revision = SECURITY_MCODE_FIELD_BLANK, +}; + +static const uint32_t boot_seeds_iidx_09[3] = {0, 0, 0}; +static const uint32_t boot_seeds_iidx_10[3] = {0, 1, 1}; +static const uint32_t boot_seeds_iidx_11[3] = {0, 2, 2}; +static const uint32_t boot_seeds_iidx_12[3] = {0, 3, 3}; +static const uint32_t boot_seeds_iidx_13[3] = {0, 4, 4}; + +static const struct security_mcode black_plug_mcode_iidx_09 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_9, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static const struct security_mcode black_plug_mcode_iidx_10 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_10, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static const struct security_mcode black_plug_mcode_iidx_11 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_11, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static const struct security_mcode black_plug_mcode_iidx_12 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_12, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static const struct security_mcode black_plug_mcode_iidx_13 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_13, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static const struct security_rp_eeprom exp_iidx_09_black_eeprom = { + .signature = {0x07, 0x5D, 0x45, 0xCB, 0xDA, 0x19}, + .packed_payload = {0x86, 0x1A, 0x92, 0x42, 0x3C, 0x67}, +}; + +static const struct security_rp_eeprom exp_iidx_10_black_eeprom = { + .signature = {0x10, 0x45, 0x30, 0x7D, 0x1C, 0x13}, + .packed_payload = {0x86, 0x1A, 0x91, 0x42, 0x4C, 0x67}, +}; + +static const struct security_rp_eeprom exp_iidx_11_black_eeprom = { + .signature = {0xA2, 0x8B, 0x97, 0xA3, 0x3B, 0x23}, + .packed_payload = {0x86, 0x1A, 0x91, 0x46, 0x5C, 0x67}, +}; + +static const struct security_rp_eeprom exp_iidx_12_black_eeprom = { + .signature = {0x1B, 0xC5, 0xF1, 0x02, 0xF0, 0xB8}, + .packed_payload = {0x86, 0x1A, 0xAF, 0x8E, 0x5C, 0x67}, +}; + +static const struct security_rp_eeprom exp_iidx_13_black_eeprom = { + .signature = {0x37, 0xCE, 0x49, 0x54, 0x20, 0x83}, + .packed_payload = {0x86, 0x1A, 0xA4, 0x92, 0x6C, 0x67}, +}; + +static void test_encode_iidx_09_black_dongle() +{ + struct security_rp_eeprom result; + + security_rp_generate_signed_eeprom_data(&boot_version_iidx_09_to_13, + boot_seeds_iidx_09, &black_plug_mcode_iidx_09, &security_id_default, + &result); + + check_data_eq((void*) &exp_iidx_09_black_eeprom, + sizeof(struct security_rp_eeprom), (void*) &result, + sizeof(struct security_rp_eeprom)); +} + +static void test_encode_iidx_10_black_dongle() +{ + struct security_rp_eeprom result; + + security_rp_generate_signed_eeprom_data(&boot_version_iidx_09_to_13, + boot_seeds_iidx_10, &black_plug_mcode_iidx_10, &security_id_default, + &result); + + check_data_eq((void*) &exp_iidx_10_black_eeprom, + sizeof(struct security_rp_eeprom), (void*) &result, + sizeof(struct security_rp_eeprom)); +} + +static void test_encode_iidx_11_black_dongle() +{ + struct security_rp_eeprom result; + + security_rp_generate_signed_eeprom_data(&boot_version_iidx_09_to_13, + boot_seeds_iidx_11, &black_plug_mcode_iidx_11, &security_id_default, + &result); + + check_data_eq((void*) &exp_iidx_11_black_eeprom, + sizeof(struct security_rp_eeprom), (void*) &result, + sizeof(struct security_rp_eeprom)); +} + +static void test_encode_iidx_12_black_dongle() +{ + struct security_rp_eeprom result; + + security_rp_generate_signed_eeprom_data(&boot_version_iidx_09_to_13, + boot_seeds_iidx_12, &black_plug_mcode_iidx_12, &security_id_default, + &result); + + check_data_eq((void*) &exp_iidx_12_black_eeprom, + sizeof(struct security_rp_eeprom), (void*) &result, + sizeof(struct security_rp_eeprom)); +} + +static void test_encode_iidx_13_black_dongle() +{ + struct security_rp_eeprom result; + + security_rp_generate_signed_eeprom_data(&boot_version_iidx_09_to_13, + boot_seeds_iidx_13, &black_plug_mcode_iidx_13, &security_id_default, + &result); + + check_data_eq((void*) &exp_iidx_13_black_eeprom, + sizeof(struct security_rp_eeprom), (void*) &result, + sizeof(struct security_rp_eeprom)); +} + +TEST_MODULE_BEGIN("security-rp") +TEST_MODULE_TEST(test_encode_iidx_09_black_dongle) +TEST_MODULE_TEST(test_encode_iidx_10_black_dongle) +TEST_MODULE_TEST(test_encode_iidx_11_black_dongle) +TEST_MODULE_TEST(test_encode_iidx_12_black_dongle) +TEST_MODULE_TEST(test_encode_iidx_13_black_dongle) +TEST_MODULE_END() diff --git a/src/test/security/security-rp2-test.c b/src/test/security/security-rp2-test.c new file mode 100644 index 0000000..e3495f6 --- /dev/null +++ b/src/test/security/security-rp2-test.c @@ -0,0 +1,224 @@ +#include "security/id.h" +#include "security/rp2.h" + +#include "test/check.h" +#include "test/test.h" + +static const struct security_mcode boot_version_iidx_14 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_14, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static const struct security_mcode boot_version_iidx_15 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_15, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static const struct security_mcode boot_version_iidx_16 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_16, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static const struct security_mcode boot_version_iidx_17 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_C, + .game = SECURITY_MCODE_GAME_IIDX_17, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_A, +}; + +static const struct security_mcode black_plug_mcode_iidx_14 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_14, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_FIELD_NULL, +}; + +static const struct security_mcode black_plug_mcode_iidx_15 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_15, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_FIELD_NULL, +}; + +static const struct security_mcode black_plug_mcode_iidx_16 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_Q, + .game = SECURITY_MCODE_GAME_IIDX_16, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_FIELD_NULL, +}; + +static const struct security_mcode black_plug_mcode_iidx_17 = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_C, + .game = SECURITY_MCODE_GAME_IIDX_17, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_FIELD_NULL, +}; + +static const struct security_rp2_eeprom exp_iidx_14_black_eeprom = { + .signature = {0x17, 0xC6, 0x2B, 0x6A, 0xDA, 0x29}, + .packed_payload = {0x67, 0x7C, 0xB2, 0xA4, 0x1A, 0x82}, +}; + +static const struct security_rp2_eeprom exp_iidx_15_black_eeprom = { + .signature = {0xCE, 0xF8, 0xC7, 0xEF, 0xA6, 0xDF}, + .packed_payload = {0x67, 0x8C, 0x92, 0xA4, 0x1A, 0x82}, +}; + +static const struct security_rp2_eeprom exp_iidx_16_black_eeprom = { + .signature = {0xE0, 0x58, 0xDA, 0xE4, 0x61, 0xF9}, + .packed_payload = {0x67, 0x9C, 0x42, 0x90, 0x1A, 0x82}, +}; + +static const struct security_rp2_eeprom exp_iidx_17_black_eeprom = { + .signature = {0xC3, 0x4B, 0xF8, 0xAA, 0xC1, 0x3E}, + .packed_payload = {0xE7, 0xA8, 0x92, 0xAA, 0x1A, 0x82}, +}; + +static const struct security_rp2_eeprom exp_iidx_14_white_eeprom = { + .signature = {0x45, 0xF7, 0xE3, 0x4C, 0x59, 0xE0}, + .packed_payload = {0x20, 0x08, 0x82, 0x20, 0x08, 0x82}, +}; + +static const struct security_rp2_eeprom exp_iidx_15_white_eeprom = { + .signature = {0x45, 0xF7, 0xE3, 0x4C, 0x59, 0xE0}, + .packed_payload = {0x20, 0x08, 0x82, 0x20, 0x08, 0x82}, +}; + +static const struct security_rp2_eeprom exp_iidx_16_white_eeprom = { + .signature = {0x45, 0xF7, 0xE3, 0x4C, 0x59, 0xE0}, + .packed_payload = {0x20, 0x08, 0x82, 0x20, 0x08, 0x82}, +}; + +static const struct security_rp2_eeprom exp_iidx_17_white_eeprom = { + .signature = {0x45, 0xF7, 0xE3, 0x4C, 0x59, 0xE0}, + .packed_payload = {0x20, 0x08, 0x82, 0x20, 0x08, 0x82}, +}; + +static void test_encode_iidx_14_black_dongle() +{ + struct security_rp2_eeprom result; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_BLACK, + &boot_version_iidx_14, &black_plug_mcode_iidx_14, &security_id_default, + &result); + + check_data_eq((void*) &result, sizeof(struct security_rp2_eeprom), + (void*) &exp_iidx_14_black_eeprom, sizeof(struct security_rp2_eeprom)); +} + +static void test_encode_iidx_14_white_dongle() +{ + struct security_rp2_eeprom result; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_WHITE, + &boot_version_iidx_14, &security_mcode_eamuse, &security_id_default, + &result); + + check_data_eq((void*) &result, sizeof(struct security_rp2_eeprom), + (void*) &exp_iidx_14_white_eeprom, sizeof(struct security_rp2_eeprom)); +} + +static void test_encode_iidx_15_black_dongle() +{ + struct security_rp2_eeprom result; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_BLACK, + &boot_version_iidx_15, &black_plug_mcode_iidx_15, &security_id_default, + &result); + + check_data_eq((void*) &result, sizeof(struct security_rp2_eeprom), + (void*) &exp_iidx_15_black_eeprom, sizeof(struct security_rp2_eeprom)); +} + +static void test_encode_iidx_15_white_dongle() +{ + struct security_rp2_eeprom result; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_WHITE, + &boot_version_iidx_15, &security_mcode_eamuse, &security_id_default, + &result); + + check_data_eq((void*) &result, sizeof(struct security_rp2_eeprom), + (void*) &exp_iidx_15_white_eeprom, sizeof(struct security_rp2_eeprom)); +} + +static void test_encode_iidx_16_black_dongle() +{ + struct security_rp2_eeprom result; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_BLACK, + &boot_version_iidx_16, &black_plug_mcode_iidx_16, &security_id_default, + &result); + + check_data_eq((void*) &result, sizeof(struct security_rp2_eeprom), + (void*) &exp_iidx_16_black_eeprom, sizeof(struct security_rp2_eeprom)); +} + +static void test_encode_iidx_16_white_dongle() +{ + struct security_rp2_eeprom result; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_WHITE, + &boot_version_iidx_16, &security_mcode_eamuse, &security_id_default, + &result); + + check_data_eq((void*) &result, sizeof(struct security_rp2_eeprom), + (void*) &exp_iidx_16_white_eeprom, sizeof(struct security_rp2_eeprom)); +} + +static void test_encode_iidx_17_black_dongle() +{ + struct security_rp2_eeprom result; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_BLACK, + &boot_version_iidx_17, &black_plug_mcode_iidx_17, &security_id_default, + &result); + + check_data_eq((void*) &result, sizeof(struct security_rp2_eeprom), + (void*) &exp_iidx_17_black_eeprom, sizeof(struct security_rp2_eeprom)); +} + +static void test_encode_iidx_17_white_dongle() +{ + struct security_rp2_eeprom result; + + security_rp2_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_WHITE, + &boot_version_iidx_17, &security_mcode_eamuse, &security_id_default, + &result); + + check_data_eq((void*) &result, sizeof(struct security_rp2_eeprom), + (void*) &exp_iidx_17_white_eeprom, sizeof(struct security_rp2_eeprom)); +} + +TEST_MODULE_BEGIN("security-rp2") +TEST_MODULE_TEST(test_encode_iidx_14_black_dongle) +TEST_MODULE_TEST(test_encode_iidx_14_white_dongle) +TEST_MODULE_TEST(test_encode_iidx_15_black_dongle) +TEST_MODULE_TEST(test_encode_iidx_15_white_dongle) +TEST_MODULE_TEST(test_encode_iidx_16_black_dongle) +TEST_MODULE_TEST(test_encode_iidx_16_white_dongle) +TEST_MODULE_TEST(test_encode_iidx_17_black_dongle) +TEST_MODULE_TEST(test_encode_iidx_17_white_dongle) +TEST_MODULE_END() diff --git a/src/test/security/security-rp3-test.c b/src/test/security/security-rp3-test.c new file mode 100644 index 0000000..a56dd91 --- /dev/null +++ b/src/test/security/security-rp3-test.c @@ -0,0 +1,61 @@ +#include "security/rp3.h" +#include "security/rp-sign-key.h" + +#include "test/check.h" +#include "test/test.h" + +static const struct security_mcode black_plug_mcode_jubeat = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_C, + .game = SECURITY_MCODE_GAME_JB_1, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_B, +}; + +static const struct security_rp3_eeprom exp_jubeat_black_eeprom = { + .signature = {0x73, 0x66, 0xBA, 0xBD, 0xDE, 0x36}, + .packed_payload = {0xE7, 0x88, 0x52, 0x94, 0x1A, 0x8A}, + .zeros = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .crc = 0xA3, +}; + +static const struct security_rp3_eeprom exp_jubeat_white_eeprom = { + .signature = {0x43, 0x17, 0xB4, 0x2A, 0x3E, 0x87}, + .packed_payload = {0x20, 0x08, 0x82, 0x20, 0x08, 0x82}, + .zeros = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .crc = 0x94, +}; + +static void test_mcode_encode_black_dongle_jubeat() +{ + struct security_rp3_eeprom result; + + security_rp3_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_BLACK, + &security_rp_sign_key_black_gfdmv4, &black_plug_mcode_jubeat, + &security_id_default, &result); + + check_data_eq((void*) &exp_jubeat_black_eeprom, + sizeof(struct security_rp3_eeprom), (void*) &result, + sizeof(struct security_rp3_eeprom)); +} + +static void test_mcode_encode_white_dongle_jubeat() +{ + struct security_rp3_eeprom result; + + security_rp3_generate_signed_eeprom_data(SECURITY_RP_UTIL_RP_TYPE_WHITE, + &security_rp_sign_key_white_eamuse, &security_mcode_eamuse, + &security_id_default, &result); + + check_data_eq((void*) &exp_jubeat_white_eeprom, + sizeof(struct security_rp3_eeprom), (void*) &result, + sizeof(struct security_rp3_eeprom)); +} + +TEST_MODULE_BEGIN("security-rp3") +TEST_MODULE_TEST(test_mcode_encode_black_dongle_jubeat) +TEST_MODULE_TEST(test_mcode_encode_white_dongle_jubeat) +TEST_MODULE_END() diff --git a/src/test/security/security-util-test.c b/src/test/security/security-util-test.c new file mode 100644 index 0000000..2aef06e --- /dev/null +++ b/src/test/security/security-util-test.c @@ -0,0 +1,72 @@ +#include "security/mcode.h" +#include "security/util.h" + +#include "test/check.h" +#include "test/test.h" + +static const struct security_mcode black_plug_mcode_jubeat = { + .header = SECURITY_MCODE_HEADER, + .unkn = SECURITY_MCODE_UNKN_C, + .game = SECURITY_MCODE_GAME_JB_1, + .region = SECURITY_MCODE_REGION_JAPAN, + .cabinet = SECURITY_MCODE_CABINET_A, + .revision = SECURITY_MCODE_REVISION_B, +}; + +static void test_mcode_encode_decode_eamuse() +{ + uint8_t buffer[8]; + uint8_t result[8]; + + security_util_8_to_6_encode((const uint8_t*) &security_mcode_eamuse, buffer); + security_util_6_to_8_decode(buffer, result); + + check_data_eq(result, sizeof(result), &security_mcode_eamuse, + sizeof(security_mcode_eamuse)); +} + +static void test_mcode_encode_decode_jubeat() +{ + uint8_t buffer[8]; + uint8_t result[8]; + + security_util_8_to_6_encode((const uint8_t*) &black_plug_mcode_jubeat, + buffer); + security_util_6_to_8_decode(buffer, result); + + check_data_eq(result, sizeof(result), &black_plug_mcode_jubeat, + sizeof(black_plug_mcode_jubeat)); +} + +static void test_mcode_encode_decode_reverse_eamuse() +{ + uint8_t buffer[8]; + uint8_t result[8]; + + security_util_8_to_6_encode_reverse((const uint8_t*) &security_mcode_eamuse, + buffer); + security_util_6_to_8_decode_reverse(buffer, result); + + check_data_eq(result, sizeof(result), &security_mcode_eamuse, + sizeof(security_mcode_eamuse)); +} + +static void test_mcode_encode_decode_reverse_jubeat() +{ + uint8_t buffer[8]; + uint8_t result[8]; + + security_util_8_to_6_encode_reverse( + (const uint8_t*) &black_plug_mcode_jubeat, buffer); + security_util_6_to_8_decode_reverse(buffer, result); + + check_data_eq(result, sizeof(result), &black_plug_mcode_jubeat, + sizeof(black_plug_mcode_jubeat)); +} + +TEST_MODULE_BEGIN("security-util") +TEST_MODULE_TEST(test_mcode_encode_decode_eamuse) +TEST_MODULE_TEST(test_mcode_encode_decode_jubeat) +TEST_MODULE_TEST(test_mcode_encode_decode_reverse_eamuse) +TEST_MODULE_TEST(test_mcode_encode_decode_reverse_jubeat) +TEST_MODULE_END() diff --git a/src/test/test/Module.mk b/src/test/test/Module.mk new file mode 100644 index 0000000..3c43553 --- /dev/null +++ b/src/test/test/Module.mk @@ -0,0 +1,6 @@ +libs += test + +srcdir_test := src/test/test + +src_test := \ + check.c \ diff --git a/src/test/test/check.c b/src/test/test/check.c new file mode 100644 index 0000000..9f40063 --- /dev/null +++ b/src/test/test/check.c @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include + +#include "test/check.h" + +#include "util/hex.h" + +static void _Noreturn __attribute__((format (printf, 4, 5))) fail( + const char *file, int line, const char *func, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "%s:%d: In %s:\n", file, line, func); + vfprintf(stderr, fmt, ap); + va_end(ap); + + abort(); +} + +void _Noreturn check_char_eq_failed(const char *file, int line, + const char *func, char *expr, char result, char expected) +{ + fail(file, line, func, + "\tIncorrect result: %s\n" + "\tReturn value was %c\n" + "\tExpected %c\n\n", + expr, result, expected); +} + +void _Noreturn check_char_neq_failed(const char *file, int line, + const char *func, char *expr, char result, char expected) +{ + fail(file, line, func, + "\tIncorrect result: %s\n" + "\tReturn value was %c\n" + "\tNot expected %c\n\n", + expr, result, expected); +} + +void _Noreturn check_int_eq_failed(const char *file, int line, + const char *func, char *expr, int result, int expected) +{ + fail(file, line, func, + "\tIncorrect result: %s\n" + "\tReturn value was %i\n" + "\tExpected %i\n\n", + expr, result, expected); +} + +void _Noreturn check_int_neq_failed(const char *file, int line, + const char *func, char *expr, int result, int expected) +{ + fail(file, line, func, + "\tIncorrect result: %s\n" + "\tReturn value was %i\n" + "\tNot expected %i\n\n", + expr, result, expected); +} + +void _Noreturn check_bool_eq_failed(const char *file, int line, + const char *func, char *expr, bool result, bool expected) +{ + fail(file, line, func, + "\tIncorrect result: %s\n" + "\tReturn value was %s\n" + "\tExpected %s\n\n", + expr, result ? "true" : "false", expected ? "true" : "false"); +} + +void _Noreturn check_bool_neq_failed(const char *file, int line, + const char *func, char *expr, bool result, bool expected) +{ + fail(file, line, func, + "\tIncorrect result: %s\n" + "\tReturn value was %s\n" + "\tNot expected %s\n\n", + expr, result ? "true" : "false", expected ? "true" : "false"); +} + +void _Noreturn check_float_eq_failed(const char *file, int line, + const char *func, char *expr, float result, float expected, float delta) +{ + fail(file, line, func, + "\tIncorrect result: %s\n" + "\tReturn value was %f\n" + "\tDelta value %f\n" + "\tExpected %f\n\n", + expr, result, delta, expected); +} + +void _Noreturn check_str_eq_failed(const char *file, int line, + const char *func, char *expr, const char *result, + const char *expected) +{ + fail(file, line, func, + "\tIncorrect result: %s\n" + "\tReturn value was \"%s\"\n" + "\tExpected \"%s\"\n\n", + expr, result, expected); +} + +void _Noreturn check_data_eq_failed(const char *file, int line, + const char *func, char *expr, const void *result, const void *expected, + size_t result_size, size_t expected_size) +{ + char result_data[result_size * 2 + 1]; + char expected_data[expected_size * 2 + 1]; + + hex_encode_uc(result, result_size, result_data, sizeof(result_data)); + hex_encode_uc(expected, expected_size, expected_data, + sizeof(expected_data)); + + fail(file, line, func, + "\tIncorrect result: %s\n" + "\tReturn size was: %d\n" + "\tExected size: %d\n" + "\tReturn value was \"%s\"\n" + "\tExpected \"%s\"\n\n", + expr, result_size, expected_size, result_data, expected_data); +} + +void _Noreturn check_non_null_failed(const char *file, int line, const char *func, + char *expr) +{ + fail(file, line, func, + "\tValue is not non-null: %s\n" + "\tExpected non-null value\n\n", + expr); +} + +void _Noreturn check_null_failed(const char *file, int line, const char *func, + char *expr) +{ + fail(file, line, func, + "\tValue is not null: %s\n" + "\tExpected null value\n\n", + expr); +} \ No newline at end of file diff --git a/src/test/test/check.h b/src/test/test/check.h new file mode 100644 index 0000000..7bb8b59 --- /dev/null +++ b/src/test/test/check.h @@ -0,0 +1,136 @@ +#ifndef TEST_CHECK_H +#define TEST_CHECK_H + +#include +#include + +#define check_char_eq(expr, expected) \ + { \ + char _result = expr; \ + \ + if (_result != expected) { \ + check_char_eq_failed(__FILE__, __LINE__, __func__, #expr, \ + _result, expected); \ + } \ + } \ + +#define check_char_neq(expr, expected) \ + { \ + char _result = expr; \ + \ + if (_result == expected) { \ + check_char_neq_failed(__FILE__, __LINE__, __func__, #expr, \ + _result, expected); \ + } \ + } \ + +#define check_int_eq(expr, expected) \ + { \ + int _result = expr; \ + \ + if (_result != expected) { \ + check_int_eq_failed(__FILE__, __LINE__, __func__, #expr, \ + _result, expected); \ + } \ + } \ + +#define check_int_neq(expr, expected) \ + { \ + int _result = expr; \ + \ + if (_result == expected) { \ + check_int_neq_failed(__FILE__, __LINE__, __func__, #expr, \ + _result, expected); \ + } \ + } \ + +#define check_float_eq(expr, expected, delta) \ + { \ + float _result = expr; \ + \ + if (_result - expected > delta) { \ + check_float_eq_failed(__FILE__, __LINE__, __func__, #expr, \ + _result, expected, delta); \ + } \ + } \ + +#define check_bool_true(expr) \ + { \ + bool _result = expr; \ + \ + if (!_result) { \ + check_bool_eq_failed(__FILE__, __LINE__, __func__, #expr, \ + _result, true); \ + } \ + } \ + +#define check_bool_false(expr) \ + { \ + bool _result = expr; \ + \ + if (_result) { \ + check_bool_eq_failed(__FILE__, __LINE__, __func__, #expr, \ + _result, false); \ + } \ + } \ + +#define check_str_eq(expr, expected) \ + { \ + const char *_result = expr; \ + \ + if (strcmp(_result, expected) != 0) { \ + check_str_eq_failed(__FILE__, __LINE__, __func__, #expr, \ + _result, expected); \ + } \ + } \ + +#define check_data_eq(expr, result_size, expected, expected_size) \ + { \ + const void *_result = expr; \ + \ + if (result_size != expected_size || memcmp(expr, expected, \ + expected_size)) { \ + check_data_eq_failed(__FILE__, __LINE__, __func__, #expr, \ + _result, expected, result_size, expected_size); \ + } \ + } \ + +#define check_non_null(expr) \ + { \ + if (!expr) { \ + check_non_null_failed(__FILE__, __LINE__, __func__, #expr); \ + } \ + } \ + +#define check_null(expr) \ + { \ + if (expr) { \ + check_null_failed(__FILE__, __LINE__, __func__, #expr); \ + } \ + } \ + +void _Noreturn check_char_eq_failed(const char *file, int line, + const char *func, char *expr, char result, char expected); +void _Noreturn check_char_neq_failed(const char *file, int line, + const char *func, char *expr, char result, char expected); +void _Noreturn check_int_eq_failed(const char *file, int line, + const char *func, char *expr, int result, int expected); +void _Noreturn check_int_neq_failed(const char *file, int line, + const char *func, char *expr, int result, int expected); +void _Noreturn check_bool_eq_failed(const char *file, int line, + const char *func, char *expr, bool result, bool expected); +void _Noreturn check_float_eq_failed(const char *file, int line, + const char *func, char *expr, float result, float expected, + float delta); +void _Noreturn check_str_eq_failed(const char *file, int line, + const char *func, char *expr, const char *result, + const char *expected); +void _Noreturn check_data_eq_failed(const char *file, int line, + const char *func, char *expr, const void *result, const void *expected, + size_t expr_size, size_t expected_size); +void _Noreturn check_non_null_failed(const char *file, int line, const char *func, + char *expr); +void _Noreturn check_null_failed(const char *file, int line, const char *func, + char *expr); + +#endif \ No newline at end of file diff --git a/src/test/test/test.h b/src/test/test/test.h new file mode 100644 index 0000000..d8ce024 --- /dev/null +++ b/src/test/test/test.h @@ -0,0 +1,25 @@ +#ifndef TEST_TEST_H +#define TEST_TEST_H + +#include + +#include "util/log.h" + +#define TEST_MODULE_BEGIN(name) \ + int main(int argc, char** argv) \ + { \ + log_to_writer(log_writer_stderr, NULL); \ + fprintf(stderr, "Executing test module '%s'...\n", #name); \ + +#define TEST_MODULE_TEST(func) \ + { \ + fprintf(stderr, "\tRunning test '%s'...\n", #func); \ + func(); \ + } \ + +#define TEST_MODULE_END() \ + fprintf(stderr, "Finished execution of test module\n"); \ + return 0; \ + } \ + +#endif \ No newline at end of file diff --git a/src/test/util/Module.mk b/src/test/util/Module.mk new file mode 100644 index 0000000..ca13ecc --- /dev/null +++ b/src/test/util/Module.mk @@ -0,0 +1,16 @@ +testexes += util-net-test + +srcdir_util-net-test := src/test/util + +ldflags_util-net-test := \ + -lws2_32 \ + -liphlpapi \ + +libs_util-net-test := \ + test \ + util \ + +src_util-net-test := \ + util-net-test.c \ + +################################################################################ diff --git a/src/test/util/util-net-test.c b/src/test/util/util-net-test.c new file mode 100644 index 0000000..27120fa --- /dev/null +++ b/src/test/util/util-net-test.c @@ -0,0 +1,524 @@ +#include "test/check.h" +#include "test/test.h" + +#include "util/net.h" + +/* Note: Don't care about cleaning up of returned strings to avoid code + * clutter + */ + +static void test_parse_invalid() +{ + struct net_addr addr; + + check_bool_false(net_str_parse("127.0.0", &addr)); +} + +static void test_parse_ipv4() +{ + struct net_addr addr; + + check_bool_true(net_str_parse("127.0.0.1", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.ipv4.port, NET_INVALID_PORT); + + check_bool_true(net_str_parse("127.0.0.1:1234", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.ipv4.port, 1234); +} + +static void test_parse_hostname() +{ + struct net_addr addr; + + check_bool_true(net_str_parse(NET_LOCALHOST_NAME, &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.hostname.host, NET_LOCALHOST_NAME); + check_int_eq(addr.hostname.port, NET_INVALID_PORT); + + check_bool_true(net_str_parse(NET_LOCALHOST_NAME ":1234", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.hostname.host, NET_LOCALHOST_NAME); + check_int_eq(addr.hostname.port, 1234); +} + +static void test_parse_url_http_ipv4() +{ + struct net_addr addr; + + check_bool_true(net_str_parse("http://127.0.0.1", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_false(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.url.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.url.ipv4.port, NET_INVALID_PORT); + check_str_eq(addr.url.path, ""); + + check_bool_true(net_str_parse("http://127.0.0.1:1234", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_false(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.url.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.url.ipv4.port, 1234); + check_str_eq(addr.url.path, ""); + + check_bool_true(net_str_parse("http://127.0.0.1/somewhere", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_false(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.url.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.url.ipv4.port, NET_INVALID_PORT); + check_str_eq(addr.url.path, "somewhere"); + + check_bool_true(net_str_parse("http://127.0.0.1:22/somewhere", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_false(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.url.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.url.ipv4.port, 22); + check_str_eq(addr.url.path, "somewhere"); +} + +static void test_parse_url_https_ipv4() +{ + struct net_addr addr; + + check_bool_true(net_str_parse("https://127.0.0.1", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_true(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.url.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.url.ipv4.port, NET_INVALID_PORT); + check_str_eq(addr.url.path, ""); + + check_bool_true(net_str_parse("https://127.0.0.1:1234", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_true(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.url.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.url.ipv4.port, 1234); + check_str_eq(addr.url.path, ""); + + check_bool_true(net_str_parse("https://127.0.0.1/somewhere", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_true(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.url.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.url.ipv4.port, NET_INVALID_PORT); + check_str_eq(addr.url.path, "somewhere"); + + check_bool_true(net_str_parse("https://127.0.0.1:22/somewhere", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_true(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_IPV4); + check_int_eq(addr.url.ipv4.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.url.ipv4.port, 22); + check_str_eq(addr.url.path, "somewhere"); +} + +static void test_parse_url_http_hostname() +{ + struct net_addr addr; + + check_bool_true(net_str_parse("http://www.google.com", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_false(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.url.hostname.host, "www.google.com"); + check_int_eq(addr.url.hostname.port, NET_INVALID_PORT); + check_str_eq(addr.url.path, ""); + + check_bool_true(net_str_parse("http://www.google.com:1234", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_false(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.url.hostname.host, "www.google.com"); + check_int_eq(addr.url.hostname.port, 1234); + check_str_eq(addr.url.path, ""); + + check_bool_true(net_str_parse("http://www.google.com/somewhere", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_false(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.url.hostname.host, "www.google.com"); + check_int_eq(addr.url.hostname.port, NET_INVALID_PORT); + check_str_eq(addr.url.path, "somewhere"); + + check_bool_true(net_str_parse("http://www.google.com:22/somewhere", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_false(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.url.hostname.host, "www.google.com"); + check_int_eq(addr.url.hostname.port, 22); + check_str_eq(addr.url.path, "somewhere"); +} + +static void test_parse_url_https_hostname() +{ + struct net_addr addr; + + check_bool_true(net_str_parse("https://www.google.com", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_true(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.url.hostname.host, "www.google.com"); + check_int_eq(addr.url.hostname.port, NET_INVALID_PORT); + check_str_eq(addr.url.path, ""); + + check_bool_true(net_str_parse("https://www.google.com:1234", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_true(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.url.hostname.host, "www.google.com"); + check_int_eq(addr.url.hostname.port, 1234); + check_str_eq(addr.url.path, ""); + + check_bool_true(net_str_parse("https://www.google.com/somewhere", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_true(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.url.hostname.host, "www.google.com"); + check_int_eq(addr.url.hostname.port, NET_INVALID_PORT); + check_str_eq(addr.url.path, "somewhere"); + + check_bool_true(net_str_parse("https://www.google.com:22/somewhere", &addr)); + check_int_eq(addr.type, NET_ADDR_TYPE_URL); + check_bool_true(addr.url.is_https); + check_int_eq(addr.url.type, NET_ADDR_TYPE_HOSTNAME); + check_str_eq(addr.url.hostname.host, "www.google.com"); + check_int_eq(addr.url.hostname.port, 22); + check_str_eq(addr.url.path, "somewhere"); +} + +static void test_net_addr_to_str_ipv4() +{ + struct net_addr addr; + + addr.type = NET_ADDR_TYPE_IPV4; + addr.ipv4.addr = NET_LOCALHOST_ADDR; + addr.ipv4.port = NET_INVALID_PORT; + + check_str_eq(net_addr_to_str(&addr), "127.0.0.1"); + + addr.type = NET_ADDR_TYPE_IPV4; + addr.ipv4.addr = NET_LOCALHOST_ADDR; + addr.ipv4.port = 1234; + + check_str_eq(net_addr_to_str(&addr), "127.0.0.1:1234"); +} + +static void test_net_addr_to_str_hostname() +{ + struct net_addr addr; + + addr.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.hostname.host, NET_LOCALHOST_NAME); + addr.hostname.port = NET_INVALID_PORT; + + check_str_eq(net_addr_to_str(&addr), "localhost"); + + addr.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.hostname.host, NET_LOCALHOST_NAME); + addr.hostname.port = 1234; + + check_str_eq(net_addr_to_str(&addr), "localhost:1234"); +} + +static void test_net_addr_to_str_url_http_ipv4() +{ + struct net_addr addr; + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = false; + addr.url.type = NET_ADDR_TYPE_IPV4; + addr.url.ipv4.addr = NET_LOCALHOST_ADDR; + addr.url.ipv4.port = NET_INVALID_PORT; + memset(addr.url.path, 0, sizeof(addr.url.path)); + + check_str_eq(net_addr_to_str(&addr), "http://127.0.0.1"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = false; + addr.url.type = NET_ADDR_TYPE_IPV4; + addr.url.ipv4.addr = NET_LOCALHOST_ADDR; + addr.url.ipv4.port = NET_INVALID_PORT; + strcpy(addr.url.path, "somewhere"); + + check_str_eq(net_addr_to_str(&addr), "http://127.0.0.1/somewhere"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = false; + addr.url.type = NET_ADDR_TYPE_IPV4; + addr.url.ipv4.addr = NET_LOCALHOST_ADDR; + addr.url.ipv4.port = 1234; + memset(addr.url.path, 0, sizeof(addr.url.path)); + + check_str_eq(net_addr_to_str(&addr), "http://127.0.0.1:1234"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = false; + addr.url.type = NET_ADDR_TYPE_IPV4; + addr.url.ipv4.addr = NET_LOCALHOST_ADDR; + addr.url.ipv4.port = 1234; + strcpy(addr.url.path, "somewhere"); + + check_str_eq(net_addr_to_str(&addr), "http://127.0.0.1:1234/somewhere"); +} + +static void test_net_addr_to_str_url_https_ipv4() +{ + struct net_addr addr; + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = true; + addr.url.type = NET_ADDR_TYPE_IPV4; + addr.url.ipv4.addr = NET_LOCALHOST_ADDR; + addr.url.ipv4.port = NET_INVALID_PORT; + memset(addr.url.path, 0, sizeof(addr.url.path)); + + check_str_eq(net_addr_to_str(&addr), "https://127.0.0.1"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = true; + addr.url.type = NET_ADDR_TYPE_IPV4; + addr.url.ipv4.addr = NET_LOCALHOST_ADDR; + addr.url.ipv4.port = NET_INVALID_PORT; + strcpy(addr.url.path, "somewhere"); + + check_str_eq(net_addr_to_str(&addr), "https://127.0.0.1/somewhere"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = true; + addr.url.type = NET_ADDR_TYPE_IPV4; + addr.url.ipv4.addr = NET_LOCALHOST_ADDR; + addr.url.ipv4.port = 1234; + memset(addr.url.path, 0, sizeof(addr.url.path)); + + check_str_eq(net_addr_to_str(&addr), "https://127.0.0.1:1234"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = true; + addr.url.type = NET_ADDR_TYPE_IPV4; + addr.url.ipv4.addr = NET_LOCALHOST_ADDR; + addr.url.ipv4.port = 1234; + strcpy(addr.url.path, "somewhere"); + + check_str_eq(net_addr_to_str(&addr), "https://127.0.0.1:1234/somewhere"); +} + +static void test_net_addr_to_str_url_http_hostname() +{ + struct net_addr addr; + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = false; + addr.url.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.url.hostname.host, "www.google.com"); + addr.url.hostname.port = NET_INVALID_PORT; + memset(addr.url.path, 0, sizeof(addr.url.path)); + + check_str_eq(net_addr_to_str(&addr), "http://www.google.com"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = false; + addr.url.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.url.hostname.host, "www.google.com"); + addr.url.hostname.port = NET_INVALID_PORT; + strcpy(addr.url.path, "somewhere"); + + check_str_eq(net_addr_to_str(&addr), "http://www.google.com/somewhere"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = false; + addr.url.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.url.hostname.host, "www.google.com"); + addr.url.hostname.port = 1234; + memset(addr.url.path, 0, sizeof(addr.url.path)); + + check_str_eq(net_addr_to_str(&addr), "http://www.google.com:1234"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = false; + addr.url.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.url.hostname.host, "www.google.com"); + addr.url.hostname.port = 1234; + strcpy(addr.url.path, "somewhere"); + + check_str_eq(net_addr_to_str(&addr), "http://www.google.com:1234/somewhere"); +} + +static void test_net_addr_to_str_url_https_hostname() +{ + struct net_addr addr; + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = true; + addr.url.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.url.hostname.host, "www.google.com"); + addr.url.hostname.port = NET_INVALID_PORT; + memset(addr.url.path, 0, sizeof(addr.url.path)); + + check_str_eq(net_addr_to_str(&addr), "https://www.google.com"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = true; + addr.url.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.url.hostname.host, "www.google.com"); + addr.url.hostname.port = NET_INVALID_PORT; + strcpy(addr.url.path, "somewhere"); + + check_str_eq(net_addr_to_str(&addr), "https://www.google.com/somewhere"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = true; + addr.url.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.url.hostname.host, "www.google.com"); + addr.url.hostname.port = 1234; + memset(addr.url.path, 0, sizeof(addr.url.path)); + + check_str_eq(net_addr_to_str(&addr), "https://www.google.com:1234"); + + addr.type = NET_ADDR_TYPE_URL; + addr.url.is_https = true; + addr.url.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.url.hostname.host, "www.google.com"); + addr.url.hostname.port = 1234; + strcpy(addr.url.path, "somewhere"); + + check_str_eq(net_addr_to_str(&addr), "https://www.google.com:1234/somewhere"); +} + +static void test_resolve_hostname_str() +{ + struct net_addr_ipv4 addr; + + addr.addr = NET_INVALID_ADDR; + addr.port = NET_INVALID_PORT; + + check_bool_true(net_resolve_hostname("www.google.com", &addr)); + check_int_neq(addr.addr, NET_INVALID_ADDR); + check_int_eq(addr.port, NET_INVALID_PORT); + + addr.addr = NET_INVALID_ADDR; + addr.port = NET_INVALID_PORT; + + check_bool_true(net_resolve_hostname(NET_LOCALHOST_NAME, &addr)); + check_int_eq(addr.addr, NET_LOCALHOST_ADDR); + check_int_eq(addr.port, NET_INVALID_PORT); + + addr.addr = NET_INVALID_ADDR; + addr.port = NET_INVALID_PORT; + + check_bool_false(net_resolve_hostname("opihjblaksdasd", &addr)); + check_int_eq(addr.addr, NET_INVALID_ADDR); + check_int_eq(addr.port, NET_INVALID_PORT); +} + +static void test_resolve_hostname_ipv4_addr() +{ + struct net_addr addr; + struct net_addr_ipv4 res_addr; + + addr.type = NET_ADDR_TYPE_IPV4; + addr.ipv4.addr = NET_LOCALHOST_ADDR; + addr.ipv4.port = 1234; + + check_bool_true(net_resolve_hostname_net_addr(&addr, &res_addr)); + check_int_eq(res_addr.addr, NET_LOCALHOST_ADDR); + check_int_eq(res_addr.port, 1234); +} + +static void test_resolve_hostname_hostname_addr() +{ + struct net_addr addr; + struct net_addr_ipv4 res_addr; + + addr.type = NET_ADDR_TYPE_HOSTNAME; + strcpy(addr.hostname.host, NET_LOCALHOST_NAME); + addr.hostname.port = 1234; + + check_bool_true(net_resolve_hostname_net_addr(&addr, &res_addr)); + check_int_eq(res_addr.addr, NET_LOCALHOST_ADDR); + check_int_eq(res_addr.port, 1234); +} + +static void test_resolve_hostname_url_ipv4_addr() +{ + struct net_addr addr; + struct net_addr_ipv4 res_addr; + + addr.type = NET_ADDR_TYPE_URL; + addr.url.type = NET_ADDR_TYPE_IPV4; + addr.url.is_https = false; + strcpy(addr.url.path, "somewhere"); + addr.url.ipv4.addr = NET_LOCALHOST_ADDR; + addr.url.ipv4.port = 1234; + + check_bool_true(net_resolve_hostname_net_addr(&addr, &res_addr)); + check_int_eq(res_addr.addr, NET_LOCALHOST_ADDR); + check_int_eq(res_addr.port, 1234); +} + +static void test_resolve_hostname_url_hostname_addr() +{ + struct net_addr addr; + struct net_addr_ipv4 res_addr; + + addr.type = NET_ADDR_TYPE_URL; + addr.url.type = NET_ADDR_TYPE_HOSTNAME; + addr.url.is_https = false; + strcpy(addr.url.path, "somewhere"); + strcpy(addr.url.hostname.host, NET_LOCALHOST_NAME); + addr.url.hostname.port = 1234; + + check_bool_true(net_resolve_hostname_net_addr(&addr, &res_addr)); + check_int_eq(res_addr.addr, NET_LOCALHOST_ADDR); + check_int_eq(res_addr.port, 1234); +} + +static void test_check_remote_connection() +{ + struct net_addr addr; + + check_bool_true(net_str_parse("127.0.0.1:1", &addr)); + check_bool_false(net_check_remote_connection(&addr, 2000)); + + check_bool_true(net_str_parse("www.google.com:80", &addr)); + check_bool_true(net_check_remote_connection(&addr, 2000)); + + check_bool_true(net_str_parse("www.google.com:22", &addr)); + check_bool_false(net_check_remote_connection(&addr, 2000)); +} + +static void test_check_remote_connection_ipv4() +{ + struct net_addr addr; + + check_bool_true(net_str_parse("127.0.0.1:1", &addr)); + check_bool_false(net_check_remote_connection_ipv4(&addr.ipv4, 2000)); +} + +TEST_MODULE_BEGIN("util/net") +TEST_MODULE_TEST(test_parse_invalid) +TEST_MODULE_TEST(test_parse_ipv4) +TEST_MODULE_TEST(test_parse_hostname) +TEST_MODULE_TEST(test_parse_url_http_ipv4) +TEST_MODULE_TEST(test_parse_url_https_ipv4) +TEST_MODULE_TEST(test_parse_url_http_hostname) +TEST_MODULE_TEST(test_parse_url_https_hostname) +TEST_MODULE_TEST(test_net_addr_to_str_ipv4) +TEST_MODULE_TEST(test_net_addr_to_str_hostname) +TEST_MODULE_TEST(test_net_addr_to_str_url_http_ipv4) +TEST_MODULE_TEST(test_net_addr_to_str_url_https_ipv4) +TEST_MODULE_TEST(test_net_addr_to_str_url_http_hostname) +TEST_MODULE_TEST(test_net_addr_to_str_url_https_hostname) +TEST_MODULE_TEST(test_resolve_hostname_str) +TEST_MODULE_TEST(test_resolve_hostname_ipv4_addr) +TEST_MODULE_TEST(test_resolve_hostname_hostname_addr) +TEST_MODULE_TEST(test_resolve_hostname_url_ipv4_addr) +TEST_MODULE_TEST(test_resolve_hostname_url_hostname_addr) +TEST_MODULE_TEST(test_check_remote_connection) +TEST_MODULE_TEST(test_check_remote_connection_ipv4) +TEST_MODULE_END()