From e0b4b49617be0858fd251068b46dcd29d97b4945 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Tue, 6 Feb 2024 03:24:58 -0500 Subject: [PATCH] Initial Commit --- .dockerignore | 4 + .editorconfig | 15 + .gitignore | 20 + .vscode/settings.json | 15 + CHANGELOG.md | 37 ++ CONTRIBUTING.md | 92 +++ Dockerfile | 12 + LICENSE | 24 + Makefile | 59 ++ Package.mk | 144 +++++ README.md | 22 + amex/amex.c | 49 ++ amex/amex.h | 21 + amex/config.c | 104 +++ amex/config.h | 21 + amex/ds.c | 214 +++++++ amex/ds.h | 22 + amex/eeprom.c | 194 ++++++ amex/eeprom.h | 20 + amex/gpio.c | 228 +++++++ amex/gpio.h | 22 + amex/guid.c | 8 + amex/jvs.c | 218 +++++++ amex/jvs.h | 22 + amex/meson.build | 28 + amex/nvram.c | 81 +++ amex/nvram.h | 7 + amex/sram.c | 160 +++++ amex/sram.h | 20 + board/aime-dll.c | 112 ++++ board/aime-dll.h | 25 + board/config.c | 41 ++ board/config.h | 10 + board/guid.c | 3 + board/guid.h | 10 + board/io3.c | 643 +++++++++++++++++++ board/io3.h | 37 ++ board/io4.c | 353 +++++++++++ board/io4.h | 32 + board/meson.build | 36 ++ board/sg-cmd.c | 134 ++++ board/sg-cmd.h | 43 ++ board/sg-frame.c | 165 +++++ board/sg-frame.h | 15 + board/sg-led-cmd.h | 39 ++ board/sg-led.c | 178 ++++++ board/sg-led.h | 30 + board/sg-nfc-cmd.h | 115 ++++ board/sg-nfc.c | 434 +++++++++++++ board/sg-nfc.h | 39 ++ board/sg-reader.c | 186 ++++++ board/sg-reader.h | 17 + cross-mingw-32.txt | 10 + cross-mingw-64.txt | 10 + dist/carol/segatools.ini | 48 ++ dist/carol/start.bat | 13 + dist/chuni/segatools.ini | 80 +++ dist/chuni/start.bat | 11 + dist/cxb/resource/segatools.ini | 4 + dist/cxb/segatools.ini | 75 +++ dist/cxb/start.bat | 9 + dist/diva/segatools.ini | 54 ++ dist/diva/start.bat | 9 + dist/idz/segatools.ini | 116 ++++ dist/idz/start.bat | 10 + dist/mai2/segatools.ini | 44 ++ dist/mai2/start.bat | 11 + dist/mercury/segatools.ini | 56 ++ dist/mercury/start.bat | 15 + dist/mu3/segatools.ini | 60 ++ dist/mu3/start.bat | 11 + doc/config/common.md | 470 ++++++++++++++ doc/development.md | 117 ++++ docker-build.bat | 34 + gfxhook/config.c | 17 + gfxhook/config.h | 7 + gfxhook/d3d11.c | 195 ++++++ gfxhook/d3d11.h | 7 + gfxhook/d3d9.c | 251 ++++++++ gfxhook/d3d9.h | 7 + gfxhook/dxgi.c | 364 +++++++++++ gfxhook/dxgi.h | 7 + gfxhook/gfx.c | 48 ++ gfxhook/gfx.h | 12 + gfxhook/meson.build | 28 + gfxhook/util.c | 116 ++++ gfxhook/util.h | 7 + hooklib/config.c | 16 + hooklib/config.h | 7 + hooklib/createprocess.c | 257 ++++++++ hooklib/createprocess.h | 21 + hooklib/cursor.c | 73 +++ hooklib/cursor.h | 3 + hooklib/dll.c | 351 ++++++++++ hooklib/dll.h | 10 + hooklib/dns.c | 462 ++++++++++++++ hooklib/dns.h | 9 + hooklib/dvd.c | 82 +++ hooklib/dvd.h | 14 + hooklib/fdshark.c | 218 +++++++ hooklib/fdshark.h | 16 + hooklib/meson.build | 33 + hooklib/path.c | 908 ++++++++++++++++++++++++++ hooklib/path.h | 20 + hooklib/reg.c | 1054 +++++++++++++++++++++++++++++++ hooklib/reg.h | 37 ++ hooklib/setupapi.c | 340 ++++++++++ hooklib/setupapi.h | 8 + hooklib/spike.c | 249 ++++++++ hooklib/spike.h | 5 + iccard/felica.c | 196 ++++++ iccard/felica.h | 27 + iccard/meson.build | 16 + iccard/mifare.h | 15 + iccard/nesica.c | 44 ++ iccard/nesica.h | 13 + jvs/jvs-bus.c | 30 + jvs/jvs-bus.h | 24 + jvs/jvs-cmd.h | 45 ++ jvs/jvs-frame.c | 152 +++++ jvs/jvs-frame.h | 17 + jvs/jvs-util.c | 109 ++++ jvs/jvs-util.h | 21 + jvs/meson.build | 18 + mercuryhook/config.c | 74 +++ mercuryhook/config.h | 33 + mercuryhook/dllmain.c | 121 ++++ mercuryhook/elisabeth.c | 87 +++ mercuryhook/elisabeth.h | 13 + mercuryhook/io4.c | 91 +++ mercuryhook/io4.h | 7 + mercuryhook/mercury-dll.c | 118 ++++ mercuryhook/mercury-dll.h | 25 + mercuryhook/mercuryhook.def | 21 + mercuryhook/meson.build | 34 + mercuryhook/touch.c | 518 +++++++++++++++ mercuryhook/touch.h | 73 +++ mercuryio/config.c | 44 ++ mercuryio/config.h | 19 + mercuryio/mercuryio.c | 121 ++++ mercuryio/mercuryio.def | 11 + mercuryio/mercuryio.h | 71 +++ mercuryio/meson.build | 13 + meson.build | 61 ++ minihook/dllmain.c | 75 +++ minihook/meson.build | 19 + mu3hook/config.c | 44 ++ mu3hook/config.h | 30 + mu3hook/dllmain.c | 126 ++++ mu3hook/io4.c | 120 ++++ mu3hook/io4.h | 7 + mu3hook/meson.build | 33 + mu3hook/mu3-dll.c | 112 ++++ mu3hook/mu3-dll.h | 22 + mu3hook/mu3hook.def | 25 + mu3hook/unity.c | 95 +++ mu3hook/unity.h | 3 + mu3io/meson.build | 14 + mu3io/mu3io.c | 148 +++++ mu3io/mu3io.h | 84 +++ platform/clock.c | 259 ++++++++ platform/clock.h | 13 + platform/config.c | 278 ++++++++ platform/config.h | 28 + platform/dns.c | 68 ++ platform/dns.h | 18 + platform/meson.build | 30 + platform/misc.c | 162 +++++ platform/misc.h | 9 + platform/netenv.c | 498 +++++++++++++++ platform/netenv.h | 20 + platform/platform.c | 68 ++ platform/platform.h | 27 + platform/syscfg.c | 115 ++++ platform/syscfg.h | 12 + platform/ttxsec.c | 621 ++++++++++++++++++ platform/ttxsec.h | 24 + platform/vfs.c | 209 ++++++ platform/vfs.h | 13 + precompiled.h | 39 ++ subprojects/capnhook.wrap | 4 + util/async.c | 199 ++++++ util/async.h | 24 + util/crc.c | 28 + util/crc.h | 6 + util/dll-bind.c | 47 ++ util/dll-bind.h | 33 + util/dprintf.c | 85 +++ util/dprintf.h | 22 + util/dump.c | 74 +++ util/dump.h | 15 + util/lib.c | 29 + util/lib.h | 5 + util/meson.build | 25 + util/str.c | 11 + util/str.h | 41 ++ 196 files changed, 17504 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 Package.mk create mode 100644 README.md create mode 100644 amex/amex.c create mode 100644 amex/amex.h create mode 100644 amex/config.c create mode 100644 amex/config.h create mode 100644 amex/ds.c create mode 100644 amex/ds.h create mode 100644 amex/eeprom.c create mode 100644 amex/eeprom.h create mode 100644 amex/gpio.c create mode 100644 amex/gpio.h create mode 100644 amex/guid.c create mode 100644 amex/jvs.c create mode 100644 amex/jvs.h create mode 100644 amex/meson.build create mode 100644 amex/nvram.c create mode 100644 amex/nvram.h create mode 100644 amex/sram.c create mode 100644 amex/sram.h create mode 100644 board/aime-dll.c create mode 100644 board/aime-dll.h create mode 100644 board/config.c create mode 100644 board/config.h create mode 100644 board/guid.c create mode 100644 board/guid.h create mode 100644 board/io3.c create mode 100644 board/io3.h create mode 100644 board/io4.c create mode 100644 board/io4.h create mode 100644 board/meson.build create mode 100644 board/sg-cmd.c create mode 100644 board/sg-cmd.h create mode 100644 board/sg-frame.c create mode 100644 board/sg-frame.h create mode 100644 board/sg-led-cmd.h create mode 100644 board/sg-led.c create mode 100644 board/sg-led.h create mode 100644 board/sg-nfc-cmd.h create mode 100644 board/sg-nfc.c create mode 100644 board/sg-nfc.h create mode 100644 board/sg-reader.c create mode 100644 board/sg-reader.h create mode 100644 cross-mingw-32.txt create mode 100644 cross-mingw-64.txt create mode 100644 dist/carol/segatools.ini create mode 100644 dist/carol/start.bat create mode 100644 dist/chuni/segatools.ini create mode 100644 dist/chuni/start.bat create mode 100644 dist/cxb/resource/segatools.ini create mode 100644 dist/cxb/segatools.ini create mode 100644 dist/cxb/start.bat create mode 100644 dist/diva/segatools.ini create mode 100644 dist/diva/start.bat create mode 100644 dist/idz/segatools.ini create mode 100644 dist/idz/start.bat create mode 100644 dist/mai2/segatools.ini create mode 100644 dist/mai2/start.bat create mode 100644 dist/mercury/segatools.ini create mode 100644 dist/mercury/start.bat create mode 100644 dist/mu3/segatools.ini create mode 100644 dist/mu3/start.bat create mode 100644 doc/config/common.md create mode 100644 doc/development.md create mode 100644 docker-build.bat create mode 100644 gfxhook/config.c create mode 100644 gfxhook/config.h create mode 100644 gfxhook/d3d11.c create mode 100644 gfxhook/d3d11.h create mode 100644 gfxhook/d3d9.c create mode 100644 gfxhook/d3d9.h create mode 100644 gfxhook/dxgi.c create mode 100644 gfxhook/dxgi.h create mode 100644 gfxhook/gfx.c create mode 100644 gfxhook/gfx.h create mode 100644 gfxhook/meson.build create mode 100644 gfxhook/util.c create mode 100644 gfxhook/util.h create mode 100644 hooklib/config.c create mode 100644 hooklib/config.h create mode 100644 hooklib/createprocess.c create mode 100644 hooklib/createprocess.h create mode 100644 hooklib/cursor.c create mode 100644 hooklib/cursor.h create mode 100644 hooklib/dll.c create mode 100644 hooklib/dll.h create mode 100644 hooklib/dns.c create mode 100644 hooklib/dns.h create mode 100644 hooklib/dvd.c create mode 100644 hooklib/dvd.h create mode 100644 hooklib/fdshark.c create mode 100644 hooklib/fdshark.h create mode 100644 hooklib/meson.build create mode 100644 hooklib/path.c create mode 100644 hooklib/path.h create mode 100644 hooklib/reg.c create mode 100644 hooklib/reg.h create mode 100644 hooklib/setupapi.c create mode 100644 hooklib/setupapi.h create mode 100644 hooklib/spike.c create mode 100644 hooklib/spike.h create mode 100644 iccard/felica.c create mode 100644 iccard/felica.h create mode 100644 iccard/meson.build create mode 100644 iccard/mifare.h create mode 100644 iccard/nesica.c create mode 100644 iccard/nesica.h create mode 100644 jvs/jvs-bus.c create mode 100644 jvs/jvs-bus.h create mode 100644 jvs/jvs-cmd.h create mode 100644 jvs/jvs-frame.c create mode 100644 jvs/jvs-frame.h create mode 100644 jvs/jvs-util.c create mode 100644 jvs/jvs-util.h create mode 100644 jvs/meson.build create mode 100644 mercuryhook/config.c create mode 100644 mercuryhook/config.h create mode 100644 mercuryhook/dllmain.c create mode 100644 mercuryhook/elisabeth.c create mode 100644 mercuryhook/elisabeth.h create mode 100644 mercuryhook/io4.c create mode 100644 mercuryhook/io4.h create mode 100644 mercuryhook/mercury-dll.c create mode 100644 mercuryhook/mercury-dll.h create mode 100644 mercuryhook/mercuryhook.def create mode 100644 mercuryhook/meson.build create mode 100644 mercuryhook/touch.c create mode 100644 mercuryhook/touch.h create mode 100644 mercuryio/config.c create mode 100644 mercuryio/config.h create mode 100644 mercuryio/mercuryio.c create mode 100644 mercuryio/mercuryio.def create mode 100644 mercuryio/mercuryio.h create mode 100644 mercuryio/meson.build create mode 100644 meson.build create mode 100644 minihook/dllmain.c create mode 100644 minihook/meson.build create mode 100644 mu3hook/config.c create mode 100644 mu3hook/config.h create mode 100644 mu3hook/dllmain.c create mode 100644 mu3hook/io4.c create mode 100644 mu3hook/io4.h create mode 100644 mu3hook/meson.build create mode 100644 mu3hook/mu3-dll.c create mode 100644 mu3hook/mu3-dll.h create mode 100644 mu3hook/mu3hook.def create mode 100644 mu3hook/unity.c create mode 100644 mu3hook/unity.h create mode 100644 mu3io/meson.build create mode 100644 mu3io/mu3io.c create mode 100644 mu3io/mu3io.h create mode 100644 platform/clock.c create mode 100644 platform/clock.h create mode 100644 platform/config.c create mode 100644 platform/config.h create mode 100644 platform/dns.c create mode 100644 platform/dns.h create mode 100644 platform/meson.build create mode 100644 platform/misc.c create mode 100644 platform/misc.h create mode 100644 platform/netenv.c create mode 100644 platform/netenv.h create mode 100644 platform/platform.c create mode 100644 platform/platform.h create mode 100644 platform/syscfg.c create mode 100644 platform/syscfg.h create mode 100644 platform/ttxsec.c create mode 100644 platform/ttxsec.h create mode 100644 platform/vfs.c create mode 100644 platform/vfs.h create mode 100644 precompiled.h create mode 100644 subprojects/capnhook.wrap create mode 100644 util/async.c create mode 100644 util/async.h create mode 100644 util/crc.c create mode 100644 util/crc.h create mode 100644 util/dll-bind.c create mode 100644 util/dll-bind.h create mode 100644 util/dprintf.c create mode 100644 util/dprintf.h create mode 100644 util/dump.c create mode 100644 util/dump.h create mode 100644 util/lib.c create mode 100644 util/lib.h create mode 100644 util/meson.build create mode 100644 util/str.c create mode 100644 util/str.h diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..85be427 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git +.gitignore + +build/ \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..08b6199 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org/ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{c,h}] +indent_style = space +indent_size = 4 +max_line_length = 79 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b84f6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +.*.swp + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# Suggested names for build dirs +build/ + +# External dependencies +subprojects/capnhook diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9f1bd80 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "files.associations": { + "aime.h": "c", + "dns.h": "c", + "epay.h": "c", + "amvideo.h": "c", + "ttxsec.h": "c", + "config.h": "c", + "syscfg.h": "c", + "platform.h": "c", + "stdbool.h": "c", + "dprintf.h": "c", + "slider-frame.h": "c" + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c51a4ea --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# v005 + +* Allow custom IO DLLs to be specified in INI files: + * `[aimeio] path=` for aime reader drivers + * `[chuniio] path=` for Chunithm input drivers + * `[idzio] path=` for Initial D Zero input drivers +* Add INI documentation +* Build system and contribution workflow improvements (contributed by icex2) +* Add hook to hide DVD drives (contributed by BemaniWitch) +* Add option to disable Diva slider emulation (contributed by dogtopus) +* AMEX board accuracy fixes (contributed by seika1, Felix) +* Improve multi-monitor support (contributed by BemaniWitch) +* Various Ongeki fixes (contributed by Felix) +* Various Diva slider fixes (contributed by dogtopus) + +# v004 + +* Add initial support for mounting DLC package dumps (contributed by Shiz) +* Fix configuration loading in aimeio.dll +* Build system fixes (contributed by Shiz) + +# v003 + +* Add countermeasures for DNS-spamming ISPs (contributed by mon) +* Add option for single-stick steering in IDZ (contributed by BemaniWitch) +* Fix MSVC build (contributed by mariodon) +* Make all 32 Chunithm touch slider cells' keyboard bindings configurable (see + INI) + +# v002 + +* Ship correct inject.exe for IDZ +* Fix IDZ main EXE crash in GetIfTable() hook in platform/netenv.c + +# v001 + +* Initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7934dad --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,92 @@ +# Contributing + +This document outlines different types of contributions and how YOU can help us to improve the +project. Read it, as it provides guidelines that are there to help you and the maintainers. + +## Reporting and discussions: Issues section on gitlab + +In order to avoid information management overehad on different communication channels, discussions +we ask everyone to treat the issue section on gitlab as the place to open their relevant discussions +and bug reports regarding the project. + +The maintainers of the project do not have the time nor motivation to micromanage information from +various channels. We ask *EVERYONE* to support the maintainers and developers to allow them to spend +their time on developing tasks. + +## Bug reports + +Follow these steps when reporting bugs to ensure you provide all information we *always* need and +make your report valuable and actionable. + +1. On the taitools repository, go `Issues` on the left-hand sidebar. +1. Use the search function to check if there is an already open issue regarding what you want to +report + 1. If that applies, read the open issue to check what's already covered regarding the bug + 1. Provide additional information or things that are missing. Upload your log files, + screenshots, videos etc. Be careful and remove sensitive information + 1. Give a thumbs up to the issue to show you are interested/affected as well +1. If no existing issue is avilable, create a new one +1. Come up with a descriptive title +1. **USE OUR BUG REPORTING TEMPLATE**: Pick it by selecting `Bug` on the `Description` section +1. Follow the sections and their instructions provided by the template and fill them in. All fields +are mandatory to provide a comprehensive report if not stated otherwise +1. When finished, submit the issue + +## Git history + +The project is aiming for a linear git history approach since we are convinced that it beneifts +maintenance of the project a lot in the long term. + +For further details on that topic, please refer to +[the many external articles](https://dev.to/bladesensei/avoid-messy-git-history-3g26) available +about this topic by using your favorite search engine. + +## Merge requests: bugfixes, new features or other code contributions + +Merge requests are welcome! May it be a merge request to an already known issue or a new feature that +you consider as a valuable contribution, please open a MR. + +**!!! Maintaining documentation by adding new or improving existing documentation is as important as +code !!!** + +If you want to start working on a new feature that was proposed in an issue, yet, it is recommended +to reach out to the maintainers about this, first, to discuss if this contribution is valuable +to the project. Otherwise, you might waste your time on implementing something that won't make it +into master or someone else is already working on. + +Please read our [development guidelines](doc/development.md) as they contain valuable information +that your contribution meets our standards. This is not meant to annoy people but ensures +consistency that the project stays maintainable for everyone. + +Steps for contributing to the repository using a merge request: + +1. If you are new to git, take a bit of time to learn the basics which are very simple, e.g. Google +for "git tutorial for non-programmers" +1. Fork the upstream repository (Fork button on the top right on the main page of the repository) +1. You can start editing files like documentation easily inside gitlab which might be the prefered +option for many non-coders +1. Clone your fork to your local machine and start working on stuff +1. Ensure you push your changes to your fork on gitlab +1. When done, go to the `Merge Requests` section on the left sidebar of the upstream repository +1. Hit the `New merge request` button +1. Select the `master` branch as the source branch +1. Select whatever branch you worked, likely `master` if you didn't change that, as the target +branch +1. Hit `Compare branches and continue` +1. Provide a descriptive title of what your change is about +1. **USE OUR MR TEMPLATES** + 1. If you submit a bugfix, use the `Bugfix` tempalte and fill in the sections + 1. If you submit a new feature, use the `Feature` template and fill in the sections +1. If you submit some minor fixes or documentation improvements, there is no template for that. +Please provide a expressive description what you did and *why* you did that +1. If any of your changes are tied to one or multiple issues, link them in the description +1. When done, hit `Submit merge request` + +The maintainers will take a look at your submission and provide their feedback. The intention of +this process is to ensure the contribution meets the quality standards. Please also see this is +a learning opportunity, especially with your first contribution, if a lot of comments and change +requests are being made. The maintainers are open to discuss their suggestions/feedback if +reasonable feedback is given back to them. + +Once all discussion is resolved and the involved maintainers approved your submission, it will be +merged into master and also included in the next release. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..45b0d2a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM fedora:36 + +LABEL description="Build environment for taitools" + +RUN dnf -y install meson ninja-build make zip clang mingw64-gcc.x86_64 mingw32-gcc.x86_64 git + +RUN mkdir /taitools +WORKDIR /taitools + +VOLUME [ "/taitools" ] + +ENTRYPOINT [ "make", "dist" ] 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/Makefile b/Makefile new file mode 100644 index 0000000..7838b09 --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +V ?= @ + +.DEFAULT_GOAL := help + +BUILD_DIR := build +BUILD_DIR_32 := $(BUILD_DIR)/build32 +BUILD_DIR_64 := $(BUILD_DIR)/build64 +BUILD_DIR_ZIP := $(BUILD_DIR)/zip + +DOC_DIR := doc + +DIST_DIR := dist + +# ----------------------------------------------------------------------------- +# Targets +# ----------------------------------------------------------------------------- + +include Package.mk + +.PHONY: build # Build the project +build: + $(V)meson --cross cross-mingw-32.txt $(BUILD_DIR_32) + $(V)ninja -C $(BUILD_DIR_32) + $(V)meson --cross cross-mingw-64.txt $(BUILD_DIR_64) + $(V)ninja -C $(BUILD_DIR_64) + +.PHONY: dist # Build and create a zip distribution package +dist: build clean-zip zip + +.PHONY: clean-zip # Remove zip files from build dir before packaging +clean-zip: + $(V)rm -Rf $(BUILD_DIR_ZIP)/*.zip + +.PHONY: zip # Create a zip distribution pacakge +zip: $(BUILD_DIR_ZIP)/taitools.zip + +.PHONY: clean # Cleanup build output +clean: + $(V)rm -rf $(BUILD_DIR) subprojects/capnhook + +# ----------------------------------------------------------------------------- +# Utility, combo and alias targets +# ----------------------------------------------------------------------------- + +# Help screen note: +# Variables that need to be displayed in the help screen need to strictly +# follow the pattern "^[A-Z_]+ \?= .* # .*". +# Targets that need to be displayed in the help screen need to add a separate +# phony definition strictly following the pattern "^\.PHONY\: .* # .*". + +.PHONY: help # Print help screen +help: + $(V)echo taitools makefile. + $(V)echo + $(V)echo "Environment variables:" + $(V)grep -E '^[A-Z_]+ \?\= .* #' Makefile | gawk 'match($$0, /([A-Z_]+) \?= [$$\(]*([^\)]*)[\)]{0,1} # (.*)/, a) { printf(" \033[0;35m%-25s \033[0;0m%-45s [%s]\n", a[1], a[3], a[2]) }' + $(V)echo "" + $(V)echo "Targets:" + $(V)grep '^.PHONY: .* #' Makefile | gawk 'match($$0, /\.PHONY: (.*) # (.*)/, a) { printf(" \033[0;32m%-25s \033[0;0m%s\n", a[1], a[2]) }' diff --git a/Package.mk b/Package.mk new file mode 100644 index 0000000..06765bb --- /dev/null +++ b/Package.mk @@ -0,0 +1,144 @@ +$(BUILD_DIR_ZIP)/chuni.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/chuni + $(V)mkdir -p $(BUILD_DIR_ZIP)/chuni/DEVICE + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_32)/chunihook/chunihook.dll \ + $(DIST_DIR)/chuni/taitools.ini \ + $(DIST_DIR)/chuni/start.bat \ + $(BUILD_DIR_ZIP)/chuni + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/chuni/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/chuni/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/chuni ; zip -r ../chuni.zip * + +$(BUILD_DIR_ZIP)/cxb.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/cxb + $(V)mkdir -p $(BUILD_DIR_ZIP)/cxb/DEVICE + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_32)/cxbhook/cxbhook.dll \ + $(DIST_DIR)/cxb/taitools.ini \ + $(DIST_DIR)/cxb/start.bat \ + $(BUILD_DIR_ZIP)/cxb + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/cxb/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/cxb/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/cxb ; zip -r ../cxb.zip * + +$(BUILD_DIR_ZIP)/diva.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/diva + $(V)mkdir -p $(BUILD_DIR_ZIP)/diva/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/divahook/divahook.dll \ + $(DIST_DIR)/diva/taitools.ini \ + $(DIST_DIR)/diva/start.bat \ + $(BUILD_DIR_ZIP)/diva + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/diva/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/diva/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/diva ; zip -r ../diva.zip * + +$(BUILD_DIR_ZIP)/carol.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/carol + $(V)mkdir -p $(BUILD_DIR_ZIP)/carol/DEVICE + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_32)/carolhook/carolhook.dll \ + $(DIST_DIR)/carol/taitools.ini \ + $(DIST_DIR)/carol/start.bat \ + $(BUILD_DIR_ZIP)/carol + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/carol/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/carol/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/carol ; zip -r ../carol.zip * + +$(BUILD_DIR_ZIP)/idz.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/idz + $(V)mkdir -p $(BUILD_DIR_ZIP)/idz/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/idzhook/idzhook.dll \ + $(DIST_DIR)/idz/taitools.ini \ + $(DIST_DIR)/idz/start.bat \ + $(BUILD_DIR_ZIP)/idz + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/idz/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/idz/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/idz ; zip -r ../idz.zip * + +$(BUILD_DIR_ZIP)/mercury.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/mercury + $(V)mkdir -p $(BUILD_DIR_ZIP)/mercury/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/mercuryhook/mercuryhook.dll \ + $(DIST_DIR)/mercury/taitools.ini \ + $(DIST_DIR)/mercury/start.bat \ + $(BUILD_DIR_ZIP)/mercury + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/mercury/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/mercury/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/mercury ; zip -r ../mercury.zip * + + +$(BUILD_DIR_ZIP)/mu3.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/mu3 + $(V)mkdir -p $(BUILD_DIR_ZIP)/mu3/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/mu3hook/mu3hook.dll \ + $(DIST_DIR)/mu3/taitools.ini \ + $(DIST_DIR)/mu3/start.bat \ + $(BUILD_DIR_ZIP)/mu3 + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/mu3/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/mu3/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/mu3 ; zip -r ../mu3.zip * + +$(BUILD_DIR_ZIP)/mai2.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2 + $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/mai2hook/mai2hook.dll \ + $(DIST_DIR)/mai2/taitools.ini \ + $(DIST_DIR)/mai2/start.bat \ + $(BUILD_DIR_ZIP)/mai2 + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/mai2/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/mai2/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/mai2 ; zip -r ../mai2.zip * + +$(BUILD_DIR_ZIP)/doc.zip: \ + $(DOC_DIR)/config \ + $(DOC_DIR)/chunihook.md \ + $(DOC_DIR)/idzhook.md \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -r $@ $^ + +$(BUILD_DIR_ZIP)/taitools.zip: \ + $(BUILD_DIR_ZIP)/chuni.zip \ + $(BUILD_DIR_ZIP)/cxb.zip \ + $(BUILD_DIR_ZIP)/carol.zip \ + $(BUILD_DIR_ZIP)/diva.zip \ + $(BUILD_DIR_ZIP)/doc.zip \ + $(BUILD_DIR_ZIP)/idz.zip \ + $(BUILD_DIR_ZIP)/mercury.zip \ + $(BUILD_DIR_ZIP)/mu3.zip \ + $(BUILD_DIR_ZIP)/mai2.zip \ + CHANGELOG.md \ + README.md \ + + $(V)echo ... $@ + $(V)zip -j $@ $^ diff --git a/README.md b/README.md new file mode 100644 index 0000000..11f186a --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Taitools + +Version: `v000` + +Loaders and hardware emulators for Taito games that run on the Taito TypeX platforms. + +## List of supported games + +## End-users + +For setup and configuration guides, refer to the dedicated documents available for each game, see +[the links in the previous section](#list-of-supported-games). + +## Contributors + +If you are/want to be a contributor of any kind, e.g. new features, bug fixes, documentation improvements, ..., please +read the [contributing documentation](CONTRIBUTING.md), first. + +## Developers + +For development setup and instructions how to build the project, refer to the +[dedicated development documentation](doc/development.md). diff --git a/amex/amex.c b/amex/amex.c new file mode 100644 index 0000000..903665f --- /dev/null +++ b/amex/amex.c @@ -0,0 +1,49 @@ +#include + +#include "amex/amex.h" +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" + +#include + +HRESULT amex_hook_init(const struct amex_config *cfg, jvs_provider_t jvs) +{ + HRESULT hr; + + assert(cfg != NULL); + + hr = ds_hook_init(&cfg->ds); + + if (FAILED(hr)) { + return hr; + } + + hr = eeprom_hook_init(&cfg->eeprom); + + if (FAILED(hr)) { + return hr; + } + + hr = gpio_hook_init(&cfg->gpio); + + if (FAILED(hr)) { + return hr; + } + + hr = jvs_hook_init(&cfg->jvs, jvs); + + if (FAILED(hr)) { + return hr; + } + + hr = sram_hook_init(&cfg->sram); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} diff --git a/amex/amex.h b/amex/amex.h new file mode 100644 index 0000000..f1a0368 --- /dev/null +++ b/amex/amex.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" + +struct amex_config { + struct ds_config ds; + struct eeprom_config eeprom; + struct gpio_config gpio; + struct jvs_config jvs; + struct sram_config sram; +}; + +HRESULT amex_hook_init( + const struct amex_config *cfg, + jvs_provider_t jvs); diff --git a/amex/config.c b/amex/config.c new file mode 100644 index 0000000..e138447 --- /dev/null +++ b/amex/config.c @@ -0,0 +1,104 @@ +#include + +#include +#include +#include +#include +#include + +#include "amex/amex.h" +#include "amex/config.h" +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" + +void ds_config_load(struct ds_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"ds", L"enable", 1, filename); + cfg->region = GetPrivateProfileIntW(L"ds", L"region", 1, filename); + + GetPrivateProfileStringW( + L"ds", + L"serialNo", + L"AAVE-01A99999999", + cfg->serial_no, + _countof(cfg->serial_no), + filename); +} + +void eeprom_config_load(struct eeprom_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"eeprom", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"eeprom", + L"path", + L"DEVICE\\eeprom.bin", + cfg->path, + _countof(cfg->path), + filename); +} + +void gpio_config_load(struct gpio_config *cfg, const wchar_t *filename) +{ + wchar_t name[7]; + size_t i; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"gpio", L"enable", 1, filename); + cfg->vk_sw1 = GetPrivateProfileIntW(L"gpio", L"sw1", VK_F1, filename); + cfg->vk_sw2 = GetPrivateProfileIntW(L"gpio", L"sw2", VK_F2, filename); + + wcscpy_s(name, _countof(name), L"dipsw0"); + + for (i = 0 ; i < 8 ; i++) { + name[5] = L'1' + i; + cfg->dipsw[i] = GetPrivateProfileIntW(L"gpio", name, 0, filename); + } +} + +void jvs_config_load(struct jvs_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"jvs", L"enable", 1, filename); +} + +void sram_config_load(struct sram_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"sram", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"sram", + L"path", + L"DEVICE\\sram.bin", + cfg->path, + _countof(cfg->path), + filename); +} + +void amex_config_load(struct amex_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + ds_config_load(&cfg->ds, filename); + eeprom_config_load(&cfg->eeprom, filename); + gpio_config_load(&cfg->gpio, filename); + jvs_config_load(&cfg->jvs, filename); + sram_config_load(&cfg->sram, filename); +} diff --git a/amex/config.h b/amex/config.h new file mode 100644 index 0000000..d088bf0 --- /dev/null +++ b/amex/config.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include +#include +#include + +#include "amex/amex.h" +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" + +void ds_config_load(struct ds_config *cfg, const wchar_t *filename); +void eeprom_config_load(struct eeprom_config *cfg, const wchar_t *filename); +void gpio_config_load(struct gpio_config *cfg, const wchar_t *filename); +void jvs_config_load(struct jvs_config *cfg, const wchar_t *filename); +void sram_config_load(struct sram_config *cfg, const wchar_t *filename); +void amex_config_load(struct amex_config *cfg, const wchar_t *filename); diff --git a/amex/ds.c b/amex/ds.c new file mode 100644 index 0000000..c0f357f --- /dev/null +++ b/amex/ds.c @@ -0,0 +1,214 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "amex/ds.h" +#include "amex/nvram.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/crc.h" +#include "util/dprintf.h" +#include "util/str.h" + +#pragma pack(push, 1) + +enum { + DS_IOCTL_GET_ABI_VERSION = 0x80006000, + DS_IOCTL_SETUP = 0x80006004, + DS_IOCTL_READ_SECTOR = 0x80006010, +}; + +struct ds_eeprom { + uint32_t crc32; + uint8_t unk_04[4]; + uint8_t region; + char serial_no[17]; + uint8_t unk_1A[6]; +}; + +static_assert(sizeof(struct ds_eeprom) == 0x20, "DS EEPROM size"); + +#pragma pack(pop) + +static HRESULT ds_handle_irp(struct irp *irp); +static HRESULT ds_handle_open(struct irp *irp); +static HRESULT ds_handle_close(struct irp *irp); +static HRESULT ds_handle_ioctl(struct irp *irp); + +static HRESULT ds_ioctl_get_geometry(struct irp *irp); +static HRESULT ds_ioctl_get_abi_version(struct irp *irp); +static HRESULT ds_ioctl_setup(struct irp *irp); +static HRESULT ds_ioctl_read_sector(struct irp *irp); + +static struct ds_eeprom ds_eeprom; +static HANDLE ds_fd; + +HRESULT ds_hook_init(const struct ds_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + memset(&ds_eeprom, 0, sizeof(ds_eeprom)); + + wcstombs_s( + NULL, + ds_eeprom.serial_no, + _countof(ds_eeprom.serial_no), + cfg->serial_no, + _countof(cfg->serial_no) - 1); + + ds_eeprom.region = cfg->region; + ds_eeprom.crc32 = crc32(&ds_eeprom.unk_04, 0x1C, 0); + + hr = iohook_push_handler(ds_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&ds_guid, L"$ds"); + + if (FAILED(hr)) { + return hr; + } + + hr = iohook_open_nul_fd(&ds_fd); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT ds_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != ds_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return ds_handle_open(irp); + case IRP_OP_CLOSE: return ds_handle_close(irp); + case IRP_OP_IOCTL: return ds_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT ds_handle_open(struct irp *irp) +{ + if (!wstr_eq(irp->open_filename, L"$ds")) { + return iohook_invoke_next(irp); + } + + dprintf("DS: Open device\n"); + irp->fd = ds_fd; + + return S_OK; +} + +static HRESULT ds_handle_close(struct irp *irp) +{ + dprintf("DS: Close device\n"); + + return S_OK; +} + +static HRESULT ds_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case IOCTL_DISK_GET_DRIVE_GEOMETRY: + return ds_ioctl_get_geometry(irp); + + case DS_IOCTL_GET_ABI_VERSION: + return ds_ioctl_get_abi_version(irp); + + case DS_IOCTL_SETUP: + return ds_ioctl_setup(irp); + + case DS_IOCTL_READ_SECTOR: + return ds_ioctl_read_sector(irp); + + default: + dprintf("DS: Unknown ioctl %08x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT ds_ioctl_get_geometry(struct irp *irp) +{ + DISK_GEOMETRY out; + HRESULT hr; + + dprintf("DS: Get geometry\n"); + + memset(&out, 0, sizeof(out)); + out.Cylinders.QuadPart = 1; + out.MediaType = 0; + out.TracksPerCylinder = 1; + out.SectorsPerTrack = 2; + out.BytesPerSector = 32; + + hr = iobuf_write(&irp->read, &out, sizeof(out)); + + if (FAILED(hr)) { + dprintf("DS: Get geometry failed: %08x\n", (int) hr); + } + + return hr; +} + +static HRESULT ds_ioctl_get_abi_version(struct irp *irp) +{ + return iobuf_write_le16(&irp->read, 256); +} + +static HRESULT ds_ioctl_setup(struct irp *irp) +{ + dprintf("DS: Setup IOCTL\n"); + + return S_OK; +} + +static HRESULT ds_ioctl_read_sector(struct irp *irp) +{ + struct const_iobuf src; + uint32_t sector_no; + HRESULT hr; + + hr = iobuf_read_le32(&irp->write, §or_no); + + if (FAILED(hr)) { + return hr; + } + + dprintf("DS: Read sector %08x\n", sector_no); + + src.bytes = (const uint8_t *) &ds_eeprom; + src.nbytes = sizeof(ds_eeprom); + src.pos = 0; + + iobuf_move(&irp->read, &src); + + return S_OK; +} diff --git a/amex/ds.h b/amex/ds.h new file mode 100644 index 0000000..0820190 --- /dev/null +++ b/amex/ds.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include +#include +#include + +struct ds_config { + bool enable; + uint8_t region; + wchar_t serial_no[17]; +}; + +DEFINE_GUID( + ds_guid, + 0x279A9F67, + 0x348F, + 0x41C9, + 0xA4, 0xC4, 0xDF, 0xDB, 0x8A, 0xE8, 0xE5, 0xE0); + +HRESULT ds_hook_init(const struct ds_config *cfg); diff --git a/amex/eeprom.c b/amex/eeprom.c new file mode 100644 index 0000000..84e4701 --- /dev/null +++ b/amex/eeprom.c @@ -0,0 +1,194 @@ +#include + +#ifdef __GNUC__ +#include +#else +#include +#endif +#include +#include + +#include + +#include "amex/eeprom.h" +#include "amex/nvram.h" + +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" +#include "util/str.h" + +enum { + EEPROM_IOCTL_GET_ABI_VERSION = 0x80006000, +}; + +static HRESULT eeprom_handle_irp(struct irp *irp); +static HRESULT eeprom_handle_open(struct irp *irp); +static HRESULT eeprom_handle_close(struct irp *irp); +static HRESULT eeprom_handle_ioctl(struct irp *irp); +static HRESULT eeprom_handle_read(struct irp *irp); +static HRESULT eeprom_handle_write(struct irp *irp); + +static HRESULT eeprom_ioctl_get_geometry(struct irp *irp); +static HRESULT eeprom_ioctl_get_abi_version(struct irp *irp); + +static struct eeprom_config eeprom_config; +static HANDLE eeprom_file; + +HRESULT eeprom_hook_init(const struct eeprom_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&eeprom_config, cfg, sizeof(*cfg)); + + hr = iohook_push_handler(eeprom_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&eeprom_guid, L"$eeprom"); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT eeprom_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != eeprom_file) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return eeprom_handle_open(irp); + case IRP_OP_CLOSE: return eeprom_handle_close(irp); + case IRP_OP_IOCTL: return eeprom_handle_ioctl(irp); + case IRP_OP_READ: return eeprom_handle_read(irp); + case IRP_OP_WRITE: return eeprom_handle_write(irp); + default: return iohook_invoke_next(irp); + } +} + +static HRESULT eeprom_handle_open(struct irp *irp) +{ + HRESULT hr; + + if (!wstr_eq(irp->open_filename, L"$eeprom") != 0) { + return iohook_invoke_next(irp); + } + + if (eeprom_file != NULL) { + dprintf("EEPROM: Already open\n"); + + return HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION); + } + + dprintf("EEPROM: Open device\n"); + hr = nvram_open_file(&eeprom_file, eeprom_config.path, 0x2000); + + if (FAILED(hr)) { + return hr; + } + + irp->fd = eeprom_file; + + return S_OK; +} + +static HRESULT eeprom_handle_close(struct irp *irp) +{ + dprintf("EEPROM: Close device\n"); + eeprom_file = NULL; + + return iohook_invoke_next(irp); +} + +static HRESULT eeprom_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case IOCTL_DISK_GET_DRIVE_GEOMETRY: + return eeprom_ioctl_get_geometry(irp); + + case EEPROM_IOCTL_GET_ABI_VERSION: + return eeprom_ioctl_get_abi_version(irp); + + default: + dprintf("EEPROM: Unknown ioctl %x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT eeprom_ioctl_get_geometry(struct irp *irp) +{ + DISK_GEOMETRY out; + HRESULT hr; + + dprintf("EEPROM: Get geometry\n"); + + memset(&out, 0, sizeof(out)); + out.Cylinders.QuadPart = 1; + out.MediaType = FixedMedia; + out.TracksPerCylinder = 224; + out.SectorsPerTrack = 32; + out.BytesPerSector = 1; + + hr = iobuf_write(&irp->read, &out, sizeof(out)); + + if (FAILED(hr)) { + dprintf("EEPROM: Get geometry failed: %08x\n", (int) hr); + } + + return hr; +} + +static HRESULT eeprom_ioctl_get_abi_version(struct irp *irp) +{ + return iobuf_write_le16(&irp->read, 256); +} + +static HRESULT eeprom_handle_read(struct irp *irp) +{ + if (irp->ovl == NULL) { + dprintf("EEPROM: Synchronous read..?\n"); + + return E_UNEXPECTED; + } + + dprintf("EEPROM: Read off %x len %x\n", + (int) irp->ovl->Offset, + (int) irp->read.nbytes); + + return iohook_invoke_next(irp); +} + +static HRESULT eeprom_handle_write(struct irp *irp) +{ + if (irp->ovl == NULL) { + dprintf("EEPROM: Synchronous write..?\n"); + + return E_UNEXPECTED; + } + + dprintf("EEPROM: Write off %x len %x\n", + (int) irp->ovl->Offset, + (int) irp->write.nbytes); + + return iohook_invoke_next(irp); +} diff --git a/amex/eeprom.h b/amex/eeprom.h new file mode 100644 index 0000000..6a73407 --- /dev/null +++ b/amex/eeprom.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +struct eeprom_config { + bool enable; + wchar_t path[MAX_PATH]; +}; + +DEFINE_GUID( + eeprom_guid, + 0xB7970F0C, + 0x31C4, + 0x45FF, + 0x96, 0x18, 0x0A, 0x24, 0x00, 0x94, 0xB2, 0x71); + +HRESULT eeprom_hook_init(const struct eeprom_config *cfg); diff --git a/amex/gpio.c b/amex/gpio.c new file mode 100644 index 0000000..4e18630 --- /dev/null +++ b/amex/gpio.c @@ -0,0 +1,228 @@ +#include +#include + +#include +#include + +#include "amex/gpio.h" + +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" +#include "util/str.h" + +enum { + GPIO_IOCTL_SET_LEDS = 0x8000A004, + GPIO_IOCTL_GET_PSW = 0x80006008, + GPIO_IOCTL_GET_DIPSW = 0x8000600C, + GPIO_IOCTL_DESCRIBE = 0x80006014, +}; + +enum { + GPIO_TYPE_NONE = 0, + GPIO_TYPE_LED = 1, + GPIO_TYPE_DIPSW = 2, + GPIO_TYPE_PSW = 3, +}; + +#pragma pack(push, 1) + +struct gpio_port { + uint8_t unknown; + + /* Number of distinct instances of this thing..? */ + uint8_t count; + + /* Type of GPIO port */ + uint16_t type; +}; + +struct gpio_ports { + uint8_t unknown; /* Maybe a count of valid items in the array idk */ + struct gpio_port ports[32]; +}; + +#pragma pack(pop) + +static HRESULT gpio_handle_irp(struct irp *irp); +static HRESULT gpio_handle_open(struct irp *irp); +static HRESULT gpio_handle_close(struct irp *irp); +static HRESULT gpio_handle_ioctl(struct irp *irp); + +static HRESULT gpio_ioctl_get_psw(struct irp *irp); +static HRESULT gpio_ioctl_get_dipsw(struct irp *irp); +static HRESULT gpio_ioctl_describe(struct irp *irp); +static HRESULT gpio_ioctl_set_leds(struct irp *irp); + +static const struct gpio_ports gpio_ports = { + .ports = { + { + .type = GPIO_TYPE_LED, + .count = 2, + }, { + .type = GPIO_TYPE_DIPSW, + .count = 8, + }, { + .type = GPIO_TYPE_PSW, + .count = 2, + } + }, +}; + +static_assert(sizeof(gpio_ports) == 129, "GPIO port map size"); + +static HANDLE gpio_fd; +static struct gpio_config gpio_config; + +HRESULT gpio_hook_init(const struct gpio_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&gpio_config, cfg, sizeof(*cfg)); + + hr = iohook_open_nul_fd(&gpio_fd); + + if (FAILED(hr)) { + return hr; + } + + hr = iohook_push_handler(gpio_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&gpio_guid, L"$gpio"); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT gpio_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != gpio_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return gpio_handle_open(irp); + case IRP_OP_CLOSE: return gpio_handle_close(irp); + case IRP_OP_IOCTL: return gpio_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT gpio_handle_open(struct irp *irp) +{ + if (!wstr_eq(irp->open_filename, L"$gpio")) { + return iohook_invoke_next(irp); + } + + dprintf("GPIO: Open device\n"); + irp->fd = gpio_fd; + + return S_OK; +} + +static HRESULT gpio_handle_close(struct irp *irp) +{ + dprintf("GPIO: Close device\n"); + + return S_OK; +} + +static HRESULT gpio_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case GPIO_IOCTL_SET_LEDS: + return gpio_ioctl_set_leds(irp); + + case GPIO_IOCTL_GET_PSW: + return gpio_ioctl_get_psw(irp); + + case GPIO_IOCTL_GET_DIPSW: + return gpio_ioctl_get_dipsw(irp); + + case GPIO_IOCTL_DESCRIBE: + return gpio_ioctl_describe(irp); + + default: + dprintf("GPIO: Unknown ioctl %08x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT gpio_ioctl_get_dipsw(struct irp *irp) +{ + uint32_t dipsw; + size_t i; + + dipsw = 0; + + for (i = 0 ; i < 8 ; i++) { + if (gpio_config.dipsw[i]) { + dipsw |= 1 << i; + } + } + + //dprintf("GPIO: Get dipsw %08x\n", dipsw); + + return iobuf_write_le32(&irp->read, dipsw); +} + +static HRESULT gpio_ioctl_get_psw(struct irp *irp) +{ + uint32_t result; + + result = 0; + + /* Bit 0 == SW1 == Alt. Test */ + /* Bit 1 == SW2 == Alt. Service */ + + if (GetAsyncKeyState(gpio_config.vk_sw1) & 0x8000) { + result |= 1 << 0; + } + + if (GetAsyncKeyState(gpio_config.vk_sw2) & 0x8000) { + result |= 1 << 1; + } + + return iobuf_write_le32(&irp->read, result); +} + +static HRESULT gpio_ioctl_describe(struct irp *irp) +{ + HRESULT hr; + + dprintf("GPIO: Describe GPIO ports\n"); + + hr = iobuf_write(&irp->read, &gpio_ports, sizeof(gpio_ports)); + + if (FAILED(hr)) { + dprintf("GPIO: Describe GPIO ports failed: %08x\n", (int) hr); + } + + return hr; +} + +static HRESULT gpio_ioctl_set_leds(struct irp *irp) +{ + return S_OK; +} diff --git a/amex/gpio.h b/amex/gpio.h new file mode 100644 index 0000000..110562d --- /dev/null +++ b/amex/gpio.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include +#include + +struct gpio_config { + bool enable; + uint8_t vk_sw1; + uint8_t vk_sw2; + bool dipsw[8]; +}; + +DEFINE_GUID( + gpio_guid, + 0xE9A26688, + 0xF522, + 0x44FA, + 0xBF, 0xEE, 0x59, 0xDD, 0x16, 0x15, 0x56, 0x6C); + +HRESULT gpio_hook_init(const struct gpio_config *cfg); diff --git a/amex/guid.c b/amex/guid.c new file mode 100644 index 0000000..de51804 --- /dev/null +++ b/amex/guid.c @@ -0,0 +1,8 @@ +#include +#include + +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" diff --git a/amex/jvs.c b/amex/jvs.c new file mode 100644 index 0000000..40d9673 --- /dev/null +++ b/amex/jvs.c @@ -0,0 +1,218 @@ +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include + +#include + +#include +#include + +#include "amex/jvs.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "jvs/jvs-bus.h" + +#include "util/dprintf.h" +#include "util/dump.h" +#include "util/str.h" + +enum { + JVS_IOCTL_HELLO = 0x80006004, + JVS_IOCTL_SENSE = 0x8000600C, + JVS_IOCTL_TRANSACT = 0x8000E008, +}; + +static HRESULT jvs_handle_irp(struct irp *irp); +static HRESULT jvs_handle_open(struct irp *irp); +static HRESULT jvs_handle_close(struct irp *irp); +static HRESULT jvs_handle_ioctl(struct irp *irp); + +static HRESULT jvs_ioctl_hello(struct irp *irp); +static HRESULT jvs_ioctl_sense(struct irp *irp); +static HRESULT jvs_ioctl_transact(struct irp *irp); + +static HANDLE jvs_fd; +static struct jvs_node *jvs_root; +static jvs_provider_t jvs_provider; + +HRESULT jvs_hook_init(const struct jvs_config *cfg, jvs_provider_t provider) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + hr = iohook_push_handler(jvs_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&jvs_guid, L"$jvs"); + + if (FAILED(hr)) { + return hr; + } + + jvs_provider = provider; + + return S_OK; +} + +static HRESULT jvs_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != jvs_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return jvs_handle_open(irp); + case IRP_OP_CLOSE: return jvs_handle_close(irp); + case IRP_OP_IOCTL: return jvs_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT jvs_handle_open(struct irp *irp) +{ + struct jvs_node *root; + HRESULT hr; + + if (!wstr_eq(irp->open_filename, L"$jvs")) { + return iohook_invoke_next(irp); + } + + if (jvs_fd != NULL) { + dprintf("JVS Port: Already open\n"); + + return HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION); + } + + hr = iohook_open_nul_fd(&jvs_fd); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS Port: Open device\n"); + + if (jvs_provider != NULL) { + hr = jvs_provider(&root); + + if (SUCCEEDED(hr)) { + jvs_root = root; + } + } + + irp->fd = jvs_fd; + + return S_OK; +} + +static HRESULT jvs_handle_close(struct irp *irp) +{ + dprintf("JVS Port: Close device\n"); + jvs_fd = NULL; + + return iohook_invoke_next(irp); +} + +static HRESULT jvs_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case JVS_IOCTL_HELLO: + return jvs_ioctl_hello(irp); + + case JVS_IOCTL_SENSE: + return jvs_ioctl_sense(irp); + + case JVS_IOCTL_TRANSACT: + return jvs_ioctl_transact(irp); + + default: + dprintf("JVS Port: Unknown ioctl %#x\n", irp->ioctl); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT jvs_ioctl_hello(struct irp *irp) +{ + HRESULT hr; + + // uuh fucked if i know + + dprintf("JVS Port: Port startup (?)\n"); + + iobuf_write_8(&irp->read, 0); + hr = iobuf_write_8(&irp->read, 0); + + return hr; +} + +static HRESULT jvs_ioctl_sense(struct irp *irp) +{ + uint8_t code; + bool sense; + + if (jvs_root != NULL) { + sense = jvs_root->sense(jvs_root); + + if (sense) { + dprintf("JVS Port: Sense line 2.5 V (address unassigned)\n"); + code = 3; + } else { + dprintf("JVS Port: Sense line 0.0 V (address assigned)\n"); + code = 2; + } + } else { + dprintf("JVS Port: Sense line 5.0 V (no downstream PCB)\n"); + code = 1; + } + + return iobuf_write_8(&irp->read, code); +} + +static HRESULT jvs_ioctl_transact(struct irp *irp) +{ +#if 0 + dprintf("\nJVS Port: Outbound frame:\n"); + dump_const_iobuf(&irp->write); +#endif + + jvs_bus_transact(jvs_root, irp->write.bytes, irp->write.nbytes, &irp->read); + +#if 0 + dprintf("JVS Port: Inbound frame:\n"); + dump_iobuf(&irp->read); + dprintf("\n"); +#endif + + if (irp->read.pos == 0) { + /* The un-acked JVS reset command must return ERROR_NO_DATA_DETECTED, + and this error must always be returned asynchronously. And since + async I/O comes from the NT kernel, we have to return that win32 + error as the equivalent NTSTATUS. */ + + if (irp->ovl == NULL || irp->ovl->hEvent == NULL) { + return HRESULT_FROM_WIN32(ERROR_NO_DATA_DETECTED); + } + + irp->ovl->Internal = STATUS_NO_DATA_DETECTED; + SetEvent(irp->ovl->hEvent); + + return HRESULT_FROM_WIN32(ERROR_IO_PENDING); + } else { + return S_OK; + } +} diff --git a/amex/jvs.h b/amex/jvs.h new file mode 100644 index 0000000..0bedb0f --- /dev/null +++ b/amex/jvs.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include "jvs/jvs-bus.h" + +DEFINE_GUID( + jvs_guid, + 0xDB6BBB45, + 0xCC96, + 0x4288, + 0xAA, 0x00, 0x6C, 0x00, 0xD7, 0x67, 0xBD, 0xBF); + +struct jvs_config { + bool enable; +}; + +typedef HRESULT (*jvs_provider_t)(struct jvs_node **root); + +HRESULT jvs_hook_init(const struct jvs_config *cfg, jvs_provider_t provider); diff --git a/amex/meson.build b/amex/meson.build new file mode 100644 index 0000000..0f4d61e --- /dev/null +++ b/amex/meson.build @@ -0,0 +1,28 @@ +amex_lib = static_library( + 'amex', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'amex.c', + 'amex.h', + 'config.c', + 'config.h', + 'ds.c', + 'ds.h', + 'eeprom.c', + 'eeprom.h', + 'gpio.c', + 'gpio.h', + 'guid.c', + 'jvs.c', + 'jvs.h', + 'nvram.c', + 'nvram.h', + 'sram.c', + 'sram.h', + ], +) diff --git a/amex/nvram.c b/amex/nvram.c new file mode 100644 index 0000000..bbade62 --- /dev/null +++ b/amex/nvram.c @@ -0,0 +1,81 @@ +#include + +#include +#include +#include + +#include "amex/nvram.h" + +#include "util/dprintf.h" + +HRESULT nvram_open_file(HANDLE *out, const wchar_t *path, size_t size) +{ + LARGE_INTEGER cur_size; + LARGE_INTEGER pos; + HANDLE file; + HRESULT hr; + BOOL ok; + + assert(out != NULL); + assert(path != NULL); + + *out = NULL; + + file = CreateFileW( + path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (file == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("%S: Error opening backing store: %x\n", path, (int) hr); + + goto end; + } + + ok = GetFileSizeEx(file, &cur_size); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("%S: GetFileSizeEx failed: %x\n", path, (int) hr); + + goto end; + } + + if (cur_size.QuadPart != (uint64_t) size) { + pos.QuadPart = (uint64_t) size; + ok = SetFilePointerEx(file, pos, NULL, FILE_BEGIN); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("%S: SetFilePointerEx failed: %x\n", path, (int) hr); + + goto end; + } + + ok = SetEndOfFile(file); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("%S: SetEndOfFile failed: %x\n", path, (int) hr); + + goto end; + } + } + + *out = file; + file = INVALID_HANDLE_VALUE; + + hr = S_OK; + +end: + if (file != INVALID_HANDLE_VALUE) { + CloseHandle(file); + } + + return hr; +} diff --git a/amex/nvram.h b/amex/nvram.h new file mode 100644 index 0000000..ae6f532 --- /dev/null +++ b/amex/nvram.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include + +HRESULT nvram_open_file(HANDLE *out, const wchar_t *path, size_t size); diff --git a/amex/sram.c b/amex/sram.c new file mode 100644 index 0000000..c5a195a --- /dev/null +++ b/amex/sram.c @@ -0,0 +1,160 @@ +#include + +#ifdef __GNUC__ +#include +#else +#include +#endif +#include +#include + +#include + +#include "amex/sram.h" +#include "amex/nvram.h" + +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" +#include "util/str.h" + +enum { + SRAM_IOCTL_GET_ABI_VERSION = 0x80006000, +}; + +static HRESULT sram_handle_irp(struct irp *irp); +static HRESULT sram_handle_open(struct irp *irp); +static HRESULT sram_handle_close(struct irp *irp); +static HRESULT sram_handle_ioctl(struct irp *irp); + +static HRESULT sram_ioctl_get_geometry(struct irp *irp); +static HRESULT sram_ioctl_get_abi_version(struct irp *irp); + +static struct sram_config sram_config; +static HANDLE sram_file; + +HRESULT sram_hook_init(const struct sram_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&sram_config, cfg, sizeof(*cfg)); + + hr = iohook_push_handler(sram_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&sram_guid, L"$sram"); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT sram_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != sram_file) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return sram_handle_open(irp); + case IRP_OP_CLOSE: return sram_handle_close(irp); + case IRP_OP_IOCTL: return sram_handle_ioctl(irp); + default: return iohook_invoke_next(irp); + } +} + +static HRESULT sram_handle_open(struct irp *irp) +{ + HRESULT hr; + + if (!wstr_eq(irp->open_filename, L"$sram")) { + return iohook_invoke_next(irp); + } + + if (sram_file != NULL) { + dprintf("SRAM: Already open\n"); + + return HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION); + } + + dprintf("SRAM: Open device\n"); + hr = nvram_open_file(&sram_file, sram_config.path, 0x80000); + + if (FAILED(hr)) { + return hr; + } + + irp->fd = sram_file; + + return S_OK; +} + +static HRESULT sram_handle_close(struct irp *irp) +{ + dprintf("SRAM: Close device\n"); + sram_file = NULL; + + return iohook_invoke_next(irp); +} + +static HRESULT sram_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case IOCTL_DISK_GET_DRIVE_GEOMETRY: + return sram_ioctl_get_geometry(irp); + + case SRAM_IOCTL_GET_ABI_VERSION: + return sram_ioctl_get_abi_version(irp); + + default: + dprintf("SRAM: Unknown ioctl %x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT sram_ioctl_get_geometry(struct irp *irp) +{ + DISK_GEOMETRY out; + HRESULT hr; + + dprintf("SRAM: Get geometry\n"); + + memset(&out, 0, sizeof(out)); + out.Cylinders.QuadPart = 0x20000; + out.MediaType = 0; + out.TracksPerCylinder = 1; + out.SectorsPerTrack = 1; + out.BytesPerSector = 4; + + hr = iobuf_write(&irp->read, &out, sizeof(out)); + + if (FAILED(hr)) { + dprintf("SRAM: Get geometry failed: %08x\n", (int) hr); + } + + return hr; +} + +static HRESULT sram_ioctl_get_abi_version(struct irp *irp) +{ + return iobuf_write_le16(&irp->read, 256); +} diff --git a/amex/sram.h b/amex/sram.h new file mode 100644 index 0000000..561b2ea --- /dev/null +++ b/amex/sram.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +struct sram_config { + bool enable; + wchar_t path[MAX_PATH]; +}; + +DEFINE_GUID( + sram_guid, + 0x741B5FCA, + 0x4635, + 0x4443, + 0xA7, 0xA0, 0x57, 0xCA, 0x7B, 0x50, 0x6A, 0x49); + +HRESULT sram_hook_init(const struct sram_config *cfg); diff --git a/board/aime-dll.c b/board/aime-dll.c new file mode 100644 index 0000000..de8eb1e --- /dev/null +++ b/board/aime-dll.c @@ -0,0 +1,112 @@ +#include + +#include +#include + +#include "board/aime-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym aime_dll_syms[] = { + { + .sym = "aime_io_init", + .off = offsetof(struct aime_dll, init), + }, { + .sym = "aime_io_nfc_poll", + .off = offsetof(struct aime_dll, nfc_poll), + }, { + .sym = "aime_io_nfc_get_aime_id", + .off = offsetof(struct aime_dll, nfc_get_aime_id), + }, { + .sym = "aime_io_nfc_get_felica_id", + .off = offsetof(struct aime_dll, nfc_get_felica_id), + }, { + .sym = "aime_io_led_set_color", + .off = offsetof(struct aime_dll, led_set_color), + } +}; + +struct aime_dll aime_dll; + +// Copypasta DLL binding and diagnostic message boilerplate. +// Not much of this lends itself to being easily factored out. Also there +// will be a lot of API-specific branching code here eventually as new API +// versions get defined, so even though these functions all look the same +// now this won't remain the case forever. + +HRESULT aime_dll_init(const struct aime_dll_config *cfg, HINSTANCE self) +{ + uint16_t (*get_api_version)(void); + const struct dll_bind_sym *sym; + HINSTANCE owned; + HINSTANCE src; + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (cfg->path[0] != L'\0') { + owned = LoadLibraryW(cfg->path); + + if (owned == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("NFC Assembly: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("NFC Assembly: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "aime_io_get_api_version"); + + if (get_api_version != NULL) { + aime_dll.api_version = get_api_version(); + } else { + aime_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose aime_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (aime_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("NFC Assembly: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Taitools.\n", + aime_dll.api_version); + + goto end; + } + + sym = aime_dll_syms; + hr = dll_bind(&aime_dll, src, &sym, _countof(aime_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("NFC Assembly: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + + goto end; + } else { + dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); + } + } + + owned = NULL; + +end: + if (owned != NULL) { + FreeLibrary(owned); + } + + return hr; +} diff --git a/board/aime-dll.h b/board/aime-dll.h new file mode 100644 index 0000000..354516b --- /dev/null +++ b/board/aime-dll.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "aimeio/aimeio.h" + +struct aime_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*nfc_poll)(uint8_t unit_no); + HRESULT (*nfc_get_aime_id)( + uint8_t unit_no, + uint8_t *luid, + size_t luid_size); + HRESULT (*nfc_get_felica_id)(uint8_t unit_no, uint64_t *IDm); + void (*led_set_color)(uint8_t unit_no, uint8_t r, uint8_t g, uint8_t b); +}; + +struct aime_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct aime_dll aime_dll; + +HRESULT aime_dll_init(const struct aime_dll_config *cfg, HINSTANCE self); diff --git a/board/config.c b/board/config.c new file mode 100644 index 0000000..191425a --- /dev/null +++ b/board/config.c @@ -0,0 +1,41 @@ +#include + +#include +#include +#include +#include + +#include "board/aime-dll.h" +#include "board/config.h" +#include "board/sg-reader.h" + +static void aime_dll_config_load(struct aime_dll_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"aimeio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void aime_config_load(struct aime_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + aime_dll_config_load(&cfg->dll, filename); + cfg->enable = GetPrivateProfileIntW(L"aime", L"enable", 1, filename); +} + +void io4_config_load(struct io4_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"io4", L"enable", 1, filename); +} diff --git a/board/config.h b/board/config.h new file mode 100644 index 0000000..f436a82 --- /dev/null +++ b/board/config.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +#include "board/io4.h" +#include "board/sg-reader.h" + +void aime_config_load(struct aime_config *cfg, const wchar_t *filename); +void io4_config_load(struct io4_config *cfg, const wchar_t *filename); diff --git a/board/guid.c b/board/guid.c new file mode 100644 index 0000000..e987000 --- /dev/null +++ b/board/guid.c @@ -0,0 +1,3 @@ +#include + +#include "board/guid.h" diff --git a/board/guid.h b/board/guid.h new file mode 100644 index 0000000..707e85a --- /dev/null +++ b/board/guid.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +DEFINE_GUID( + hid_guid, + 0x4D1E55B2L, + 0xF16F, + 0x11CF, + 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30); diff --git a/board/io3.c b/board/io3.c new file mode 100644 index 0000000..369714f --- /dev/null +++ b/board/io3.c @@ -0,0 +1,643 @@ +/* + Sega "Type 3" JVS I/O emulator + + Credits: + + Protocol docs: + https://github.com/TheOnlyJoey/openjvs/wiki/ (a/o October 2018) + + Capability dumps: + https://wiki.arcadeotaku.com/w/JVS#Sega_837-14572 (a/o October 2018) +*/ + +#include + +#include +#include +#include +#include + +#include "board/io3.h" + +#include "jvs/jvs-bus.h" +#include "jvs/jvs-cmd.h" +#include "jvs/jvs-util.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static void io3_transact( + struct jvs_node *node, + const void *bytes, + size_t nbytes, + struct iobuf *resp); + +static bool io3_sense(struct jvs_node *node); + +static HRESULT io3_cmd( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp); + +static HRESULT io3_cmd_read_id( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_cmd_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_jvs_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_comm_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_features( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_read_switches( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_read_coin( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_read_analogs( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_write_gpio( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_reset(struct io3 *io3, struct const_iobuf *buf); + +static HRESULT io3_cmd_assign_addr( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static const uint8_t io3_ident[] = + "SEGA CORPORATION;I/O BD JVS;837-14572;Ver1.00;2005/10"; + +static uint8_t io3_features[] = { + /* Feature : 0x01 : Players and switches + Param1 : 2 : Number of players + Param2 : 14 : Number of switches per player + Param3 : 0 : N/A */ + + 0x01, 2, 14, 0, + + /* Feature : 0x02 : Coin slots + Param1 : 2 : Number of coin slots + Param2 : 0 : N/A + Param3 : 0 : N/A */ + + 0x02, 2, 0, 0, + + /* Feature : 0x03 : Analog inputs + Param1 : 8 : Number of ADC channels + Param2 : 10 : Effective bits of resolution per ADC + Param3 : 0 : N/A */ + + 0x03, 8, 10, 0, + + /* Feature : 0x12 : GPIO outputs + Param1 : 3 : Number of ports (8 bits per port) + Param2 : 0 : N/A + Param3 : 0 : N/A + + NOTE: This particular port count is what an IO-4 attached over JVS + advertises, an IO-3 only advertises 3. Still, this seems to be backwards + compatible with games that expect an IO-3, and the protocols seem to be + identical otherwise. */ + + 0x12, 20, 0, 0, + + /* Feature : 0x00 : End of capabilities */ + + 0x00, +}; + +void io3_init( + struct io3 *io3, + struct jvs_node *next, + const struct io3_ops *ops, + void *ops_ctx) +{ + assert(io3 != NULL); + assert(ops != NULL); + + io3->jvs.next = next; + io3->jvs.transact = io3_transact; + io3->jvs.sense = io3_sense; + io3->addr = 0xFF; + io3->ops = ops; + io3->ops_ctx = ops_ctx; +} + +struct jvs_node *io3_to_jvs_node(struct io3 *io3) +{ + assert(io3 != NULL); + + return &io3->jvs; +} + +static void io3_transact( + struct jvs_node *node, + const void *bytes, + size_t nbytes, + struct iobuf *resp) +{ + struct io3 *io3; + + assert(node != NULL); + assert(bytes != NULL); + assert(resp != NULL); + + io3 = CONTAINING_RECORD(node, struct io3, jvs); + + jvs_crack_request(bytes, nbytes, resp, io3->addr, io3_cmd, io3); +} + +static bool io3_sense(struct jvs_node *node) +{ + struct io3 *io3; + + assert(node != NULL); + + io3 = CONTAINING_RECORD(node, struct io3, jvs); + + return io3->addr == 0xFF; +} + +static HRESULT io3_cmd( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp) +{ + struct io3 *io3; + + io3 = ctx; + + switch (req->bytes[req->pos]) { + case JVS_CMD_READ_ID: + return io3_cmd_read_id(io3, req, resp); + + case JVS_CMD_GET_CMD_VERSION: + return io3_cmd_get_cmd_version(io3, req, resp); + + case JVS_CMD_GET_JVS_VERSION: + return io3_cmd_get_jvs_version(io3, req, resp); + + case JVS_CMD_GET_COMM_VERSION: + return io3_cmd_get_comm_version(io3, req, resp); + + case JVS_CMD_GET_FEATURES: + return io3_cmd_get_features(io3, req, resp); + + case JVS_CMD_READ_SWITCHES: + return io3_cmd_read_switches(io3, req, resp); + + case JVS_CMD_READ_COIN: + return io3_cmd_read_coin(io3, req, resp); + + case JVS_CMD_READ_ANALOGS: + return io3_cmd_read_analogs(io3, req, resp); + + case JVS_CMD_WRITE_GPIO: + return io3_cmd_write_gpio(io3, req, resp); + + case JVS_CMD_RESET: + return io3_cmd_reset(io3, req); + + case JVS_CMD_ASSIGN_ADDR: + return io3_cmd_assign_addr(io3, req, resp); + + default: + dprintf("JVS I/O: Node %02x: Unhandled command byte %02x\n", + io3->addr, + req->bytes[req->pos]); + + return E_NOTIMPL; + } +} + +static HRESULT io3_cmd_read_id( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Read ID\n"); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write the identification string. The NUL terminator at the end of this C + string is also sent, and it naturally terminates the response chunk. */ + + return iobuf_write(resp_buf, io3_ident, sizeof(io3_ident)); +} + +static HRESULT io3_cmd_get_cmd_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + uint8_t resp[2]; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get command format version\n"); + resp[0] = 0x01; /* Report byte */ + resp[1] = 0x13; /* Command format version BCD */ + + return iobuf_write(resp_buf, resp, sizeof(resp)); +} + +static HRESULT io3_cmd_get_jvs_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + uint8_t resp[2]; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get JVS version\n"); + resp[0] = 0x01; /* Report byte */ + resp[1] = 0x20; /* JVS version BCD */ + + return iobuf_write(resp_buf, resp, sizeof(resp)); +} + +static HRESULT io3_cmd_get_comm_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + uint8_t resp[2]; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get communication version\n"); + resp[0] = 0x01; /* Report byte */ + resp[1] = 0x10; /* "Communication version" BCD */ + + return iobuf_write(resp_buf, resp, sizeof(resp)); +} + +static HRESULT io3_cmd_get_features( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get features\n"); + + hr = iobuf_write_8(resp_buf, 0x01); /* Write report byte */ + + if (FAILED(hr)) { + return hr; + } + + return iobuf_write(resp_buf, io3_features, sizeof(io3_features)); +} + +static HRESULT io3_cmd_read_switches( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_switches req; + struct io3_switch_state state; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + +#if 0 + dprintf("JVS I/O: Read switches, np=%i, bpp=%i\n", + req.num_players, + req.bytes_per_player); +#endif + + if (req.num_players > 2 || req.bytes_per_player != 2) { + dprintf("JVS I/O: Invalid read size " + "num_players=%i " + "bytes_per_player=%i\n", + req.num_players, + req.bytes_per_player); + hr = iobuf_write_8(resp_buf, 0x02); + + if (FAILED(hr)) { + return hr; + } + + return E_FAIL; + } + + /* Build response */ + + hr = iobuf_write_8(resp_buf, 0x01); /* Report byte */ + + if (FAILED(hr)) { + return hr; + } + + memset(&state, 0, sizeof(state)); + + if (io3->ops != NULL) { + io3->ops->read_switches(io3->ops_ctx, &state); + } + + hr = iobuf_write_8(resp_buf, state.system); /* Test, Tilt lines */ + + if (FAILED(hr)) { + return hr; + } + + if (req.num_players > 0) { + hr = iobuf_write_be16(resp_buf, state.p1); + + if (FAILED(hr)) { + return hr; + } + } + + if (req.num_players > 1) { + hr = iobuf_write_be16(resp_buf, state.p2); + + if (FAILED(hr)) { + return hr; + } + } + + return hr; +} + +static HRESULT io3_cmd_read_coin( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_coin req; + uint16_t ncoins; + uint8_t i; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + //dprintf("JVS I/O: Read coin, nslots=%i\n", req.nslots); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write slot detail */ + + for (i = 0 ; i < req.nslots ; i++) { + ncoins = 0; + + if (io3->ops->read_coin_counter != NULL) { + io3->ops->read_coin_counter(io3->ops_ctx, i, &ncoins); + } + + hr = iobuf_write_be16(resp_buf, ncoins); + + if (FAILED(hr)) { + return hr; + } + } + + return hr; +} + +static HRESULT io3_cmd_read_analogs( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_analogs req; + uint16_t analogs[8]; + uint8_t i; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + if (req.nanalogs > _countof(analogs)) { + dprintf("JVS I/O: Invalid analog count %i\n", req.nanalogs); + + return E_FAIL; + } + + //dprintf("JVS I/O: Read analogs, nanalogs=%i\n", req.nanalogs); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write analogs */ + + memset(analogs, 0, sizeof(analogs)); + + if (io3->ops->read_analogs != NULL) { + io3->ops->read_analogs(io3->ops_ctx, analogs, req.nanalogs); + } + + for (i = 0 ; i < req.nanalogs ; i++) { + hr = iobuf_write_be16(resp_buf, analogs[i]); + + if (FAILED(hr)) { + return hr; + } + } + + return hr; + +} + +static HRESULT io3_cmd_write_gpio( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t cmd; + uint8_t nbytes; + uint8_t bytes[3]; + HRESULT hr; + + /* Read request header */ + + hr = iobuf_read_8(req_buf, &cmd); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req_buf, &nbytes); + + if (FAILED(hr)) { + return hr; + } + + if (nbytes > 3) { + dprintf("JVS I/O: Invalid GPIO write size %i\n", nbytes); + hr = iobuf_write_8(resp_buf, 0x02); + + if (FAILED(hr)) { + return hr; + } + + return E_FAIL; + } + + /* Read payload */ + + memset(bytes, 0, sizeof(bytes)); + hr = iobuf_read(req_buf, bytes, nbytes); + + if (FAILED(hr)) { + return hr; + } + + if (io3->ops->write_gpio != NULL) { + io3->ops->write_gpio( + io3->ops_ctx, + bytes[0] | (bytes[1] << 8) | (bytes[2] << 16)); + } + + /* Write report byte */ + + return iobuf_write_8(resp_buf, 0x01); +} + +static HRESULT io3_cmd_reset(struct io3 *io3, struct const_iobuf *req_buf) +{ + struct jvs_req_reset req; + HRESULT hr; + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Reset (param %02x)\n", req.unknown); + io3->addr = 0xFF; + + if (io3->ops->reset != NULL) { + io3->ops->reset(io3->ops_ctx); + } + + /* No ack for this since it really is addressed to everybody */ + + return S_OK; +} + +static HRESULT io3_cmd_assign_addr( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_assign_addr req; + bool sense; + HRESULT hr; + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + sense = jvs_node_sense(io3->jvs.next); + dprintf("JVS I/O: Assign addr %02x sense %i\n", req.addr, sense); + + if (sense) { + /* That address is for somebody else */ + return S_OK; + } + + io3->addr = req.addr; + + return iobuf_write_8(resp_buf, 0x01); +} diff --git a/board/io3.h b/board/io3.h new file mode 100644 index 0000000..a094a2a --- /dev/null +++ b/board/io3.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "jvs/jvs-bus.h" + +struct io3_switch_state { + /* Note: this struct is host-endian. The IO3 emulator handles the conversion + to protocol-endian. */ + + uint8_t system; + uint16_t p1; + uint16_t p2; +}; + +struct io3_ops { + void (*reset)(void *ctx); + void (*write_gpio)(void *ctx, uint32_t state); + void (*read_switches)(void *ctx, struct io3_switch_state *out); + void (*read_analogs)(void *ctx, uint16_t *analogs, uint8_t nanalogs); + void (*read_coin_counter)(void *ctx, uint8_t slot_no, uint16_t *out); +}; + +struct io3 { + struct jvs_node jvs; + uint8_t addr; + const struct io3_ops *ops; + void *ops_ctx; +}; + +void io3_init( + struct io3 *io3, + struct jvs_node *next, + const struct io3_ops *ops, + void *ops_ctx); + +struct jvs_node *io3_to_jvs_node(struct io3 *io3); diff --git a/board/io4.c b/board/io4.c new file mode 100644 index 0000000..e7fe9e3 --- /dev/null +++ b/board/io4.c @@ -0,0 +1,353 @@ +#include + +#include +#include + +#include +#include +#include +#include + +#include "board/config.h" +#include "board/guid.h" +#include "board/io4.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/async.h" +#include "util/dprintf.h" + +#pragma pack(push, 1) + +enum { + IO4_CMD_SET_COMM_TIMEOUT = 0x01, + IO4_CMD_SET_SAMPLING_COUNT = 0x02, + IO4_CMD_CLEAR_BOARD_STATUS = 0x03, + IO4_CMD_SET_GENERAL_OUTPUT = 0x04, + IO4_CMD_SET_PWM_OUTPUT = 0x05, + IO4_CMD_UNIMPLEMENTED = 0x41, + IO4_CMD_UPDATE_FIRMWARE = 0x85, +}; + +struct io4_report_in { + uint8_t report_id; + uint16_t adcs[8]; + uint16_t spinners[4]; + uint16_t chutes[2]; + uint16_t buttons[2]; + uint8_t system_status; + uint8_t usb_status; + uint8_t unknown[29]; +}; + +static_assert(sizeof(struct io4_report_in) == 0x40, "IO4 IN report size"); + +struct io4_report_out { + uint8_t report_id; + uint8_t cmd; + uint8_t payload[62]; +}; + +static_assert(sizeof(struct io4_report_out) == 0x40, "IO4 OUT report size"); + +#pragma pack(pop) + + +static HRESULT io4_handle_irp(struct irp *irp); +static HRESULT io4_handle_open(struct irp *irp); +static HRESULT io4_handle_close(struct irp *irp); +static HRESULT io4_handle_read(struct irp *irp); +static HRESULT io4_handle_write(struct irp *irp); +static HRESULT io4_handle_ioctl(struct irp *irp); + +static HRESULT io4_ioctl_get_manufacturer_string(struct irp *irp); +static HRESULT io4_ioctl_get_product_string(struct irp *irp); + +static HRESULT io4_async_poll(void *ctx, struct irp *irp); + +/* Device node path must contain substring "vid_0ca3" (case-insensitive). */ +static const wchar_t io4_path[] = L"$io4\\vid_0ca3"; + +static const wchar_t io4_manf[] = L"SEGA"; +static const wchar_t io4_prod[] = + /* "Product" (N.B. numbers are in hex) */ + + L"I/O CONTROL BD;" /* Board type */ + L"15257;" /* Board number */ + L"01;" /* "Mode" (prob. USB vs JVS?) */ + L"90;" /* Firmware revision */ + L"1831;" /* Firmware checksum */ + L"6679A;" /* "Custom chip no" */ + L"00;" /* "Config" */ + + /* "Function" (N.B. all values are in hex) */ + + L"GOUT=14_" /* General-purpose output */ + L"ADIN=8,E_" /* ADC inputs */ + L"ROTIN=4_" /* Rotary inputs */ + L"COININ=2_" /* Coin inputs */ + L"SWIN=2,E_" /* Switch inputs */ + L"UQ1=41,6" /* "Unique function 1" */ + ; + +static HANDLE io4_fd; +static struct async io4_async; +static uint8_t io4_system_status; +static const struct io4_ops *io4_ops; +static void *io4_ops_ctx; + +HRESULT io4_hook_init( + const struct io4_config *cfg, + const struct io4_ops *ops, + void *ctx) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(ops != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + async_init(&io4_async, NULL); + + hr = iohook_open_nul_fd(&io4_fd); + + if (FAILED(hr)) { + return hr; + } + + io4_ops = ops; + io4_ops_ctx = ctx; + io4_system_status = 0x02; /* idk */ + iohook_push_handler(io4_handle_irp); + + hr = setupapi_add_phantom_dev(&hid_guid, io4_path); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT io4_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != io4_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return io4_handle_open(irp); + case IRP_OP_CLOSE: return io4_handle_close(irp); + case IRP_OP_READ: return io4_handle_read(irp); + case IRP_OP_WRITE: return io4_handle_write(irp); + case IRP_OP_IOCTL: return io4_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT io4_handle_open(struct irp *irp) +{ + if (wcscmp(irp->open_filename, io4_path) != 0) { + return iohook_invoke_next(irp); + } + + dprintf("USB I/O: Device opened\n"); + irp->fd = io4_fd; + + return S_OK; +} + +static HRESULT io4_handle_close(struct irp *irp) +{ + dprintf("USB I/O: Device closed\n"); + + return S_OK; +} + +static HRESULT io4_handle_read(struct irp *irp) +{ + /* The amdaemon USBIO driver will continuously poll the IO until the IO + call returns an async operation in progress. We have to return and then + signal the OVERLAPPED event object "a little bit later" in order to avoid + an infinite loop. */ + + return async_submit(&io4_async, irp, io4_async_poll); +} + +static HRESULT io4_handle_write(struct irp *irp) +{ + struct io4_report_out out; + HRESULT hr; + + hr = iobuf_read(&irp->write, &out, sizeof(out)); + + if (FAILED(hr)) { + return hr; + } + + if (out.report_id != 0x10) { + dprintf("USB I/O: OUT Report ID is incorrect"); + + return E_FAIL; + } + + switch (out.cmd) { + case IO4_CMD_SET_COMM_TIMEOUT: + dprintf("USB I/O: Set comm timeout\n"); + + // Ongeki Summer expects the system status to be 0x30 at this point + io4_system_status = 0x30; + + return S_OK; + + case IO4_CMD_SET_SAMPLING_COUNT: + dprintf("USB I/O: Set sampling count\n"); + + // Ongeki Summer expects the system status to be 0x30 at this point + io4_system_status = 0x30; + + return S_OK; + + case IO4_CMD_CLEAR_BOARD_STATUS: + dprintf("USB I/O: Clear board status\n"); + io4_system_status = 0x00; + + return S_OK; + + case IO4_CMD_SET_GENERAL_OUTPUT: + dprintf("USB I/O: GPIO Out\n"); + + return S_OK; + + case IO4_CMD_SET_PWM_OUTPUT: + dprintf("USB I/O: PWM Out\n"); + + return S_OK; + + case IO4_CMD_UPDATE_FIRMWARE: + dprintf("USB I/O: Update firmware..?\n"); + + return E_FAIL; + + case IO4_CMD_UNIMPLEMENTED: + //dprintf("USB I/O: Unimplemented cmd 41\n"); + + return S_OK; + + default: + dprintf("USB I/O: Unknown command %02x\n", out.cmd); + + return E_FAIL; + } +} + +static HRESULT io4_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case IOCTL_HID_GET_MANUFACTURER_STRING: + return io4_ioctl_get_manufacturer_string(irp); + + case IOCTL_HID_GET_PRODUCT_STRING: + return io4_ioctl_get_product_string(irp); + + case IOCTL_HID_GET_INPUT_REPORT: + dprintf("USB I/O: Control IN (untested!!)\n"); + + return io4_handle_read(irp); + + case IOCTL_HID_SET_OUTPUT_REPORT: + dprintf("USB I/O: Control OUT (untested!!)\n"); + + return io4_handle_write(irp); + + default: + dprintf("USB I/O: Unknown ioctl %#08x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT io4_ioctl_get_manufacturer_string(struct irp *irp) +{ + dprintf("USB I/O: Get manufacturer string\n"); + + if (irp->read.nbytes < sizeof(io4_manf)) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + memcpy(irp->read.bytes, io4_manf, sizeof(io4_manf)); + irp->read.pos = sizeof(io4_manf); + + return S_OK; +} + +static HRESULT io4_ioctl_get_product_string(struct irp *irp) +{ + dprintf("USB I/O: Get product string\n"); + + if (irp->read.nbytes < sizeof(io4_prod)) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + memcpy(irp->read.bytes, io4_prod, sizeof(io4_prod)); + irp->read.pos = sizeof(io4_prod); + + return S_OK; +} + +static HRESULT io4_async_poll(void *ctx, struct irp *irp) +{ + struct io4_report_in in; + struct io4_state state; + HRESULT hr; + size_t i; + + /* Delay long enough for the instigating thread in amdaemon to be satisfied + that all queued-up reports have been drained. */ + + Sleep(1); + + /* Call into ops to poll the underlying inputs */ + + memset(&state, 0, sizeof(state)); + hr = io4_ops->poll(io4_ops_ctx, &state); + + if (FAILED(hr)) { + return hr; + } + + /* Construct IN report. Values are all little-endian, unlike JVS. */ + + memset(&in, 0, sizeof(in)); + in.report_id = 0x01; + in.system_status = io4_system_status; + + for (i = 0 ; i < 8 ; i++) { + in.adcs[i] = state.adcs[i]; + } + + for (i = 0 ; i < 4 ; i++) { + in.spinners[i] = state.spinners[i]; + } + + for (i = 0 ; i < 2 ; i++) { + in.chutes[i] = state.chutes[i]; + } + + for (i = 0 ; i < 2 ; i++) { + in.buttons[i] = state.buttons[i]; + } + + return iobuf_write(&irp->read, &in, sizeof(in)); +} diff --git a/board/io4.h b/board/io4.h new file mode 100644 index 0000000..1a6cc05 --- /dev/null +++ b/board/io4.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include + +enum { + /* System buttons in button[0] */ + + IO4_BUTTON_TEST = 1 << 9, + IO4_BUTTON_SERVICE = 1 << 6, +}; + +struct io4_config { + bool enable; +}; + +struct io4_state { + uint16_t adcs[8]; + uint16_t spinners[4]; + uint16_t chutes[2]; + uint16_t buttons[2]; +}; + +struct io4_ops { + HRESULT (*poll)(void *ctx, struct io4_state *state); +}; + +HRESULT io4_hook_init( + const struct io4_config *cfg, + const struct io4_ops *ops, + void *ctx); diff --git a/board/meson.build b/board/meson.build new file mode 100644 index 0000000..a710ef6 --- /dev/null +++ b/board/meson.build @@ -0,0 +1,36 @@ +board_lib = static_library( + 'board', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + link_with : [ + iccard_lib, + ], + sources : [ + 'aime-dll.c', + 'aime-dll.h', + 'config.c', + 'config.h', + 'guid.c', + 'guid.h', + 'io3.c', + 'io3.h', + 'io4.c', + 'io4.h', + 'sg-cmd.c', + 'sg-cmd.h', + 'sg-frame.c', + 'sg-frame.h', + 'sg-led.c', + 'sg-led.h', + 'sg-led-cmd.h', + 'sg-nfc.c', + 'sg-nfc.h', + 'sg-nfc-cmd.h', + 'sg-reader.c', + 'sg-reader.h', + ], +) diff --git a/board/sg-cmd.c b/board/sg-cmd.c new file mode 100644 index 0000000..0ed6f25 --- /dev/null +++ b/board/sg-cmd.c @@ -0,0 +1,134 @@ +#include + +#include "board/sg-cmd.h" +#include "board/sg-frame.h" + +#include "hook/iobuf.h" + +#include "util/dprintf.h" + +union sg_req_any { + struct sg_req_header req; + uint8_t bytes[256]; +}; + +union sg_res_any { + struct sg_res_header res; + uint8_t bytes[256]; +}; + +static HRESULT sg_req_validate(const void *ptr, size_t nbytes); + +static void sg_res_error( + struct sg_res_header *res, + const struct sg_req_header *req); + +static HRESULT sg_req_validate(const void *ptr, size_t nbytes) +{ + const struct sg_req_header *req; + size_t payload_len; + + assert(ptr != NULL); + + if (nbytes < sizeof(*req)) { + dprintf("SG Cmd: Request header truncated\n"); + + return E_FAIL; + } + + req = ptr; + + if (req->hdr.frame_len != nbytes) { + dprintf("SG Cmd: Frame length mismatch: got %i exp %i\n", + req->hdr.frame_len, + (int) nbytes); + + return E_FAIL; + } + + payload_len = req->hdr.frame_len - sizeof(*req); + + if (req->payload_len != payload_len) { + dprintf("SG Cmd: Payload length mismatch: got %i exp %i\n", + req->payload_len, + (int) payload_len); + + return E_FAIL; + } + + return S_OK; +} + +void sg_req_transact( + struct iobuf *res_frame, + const uint8_t *req_bytes, + size_t req_nbytes, + sg_dispatch_fn_t dispatch, + void *ctx) +{ + struct iobuf req_span; + union sg_req_any req; + union sg_res_any res; + HRESULT hr; + + assert(res_frame != NULL); + assert(req_bytes != NULL); + assert(dispatch != NULL); + + req_span.bytes = req.bytes; + req_span.nbytes = sizeof(req.bytes); + req_span.pos = 0; + + hr = sg_frame_decode(&req_span, req_bytes, req_nbytes); + + if (FAILED(hr)) { + return; + } + + hr = sg_req_validate(req.bytes, req_span.pos); + + if (FAILED(hr)) { + return; + } + + hr = dispatch(ctx, &req, &res); + + if (hr != S_FALSE) { + if (FAILED(hr)) { + sg_res_error(&res.res, &req.req); + } + + sg_frame_encode(res_frame, res.bytes, res.res.hdr.frame_len); + } +} + +void sg_res_init( + struct sg_res_header *res, + const struct sg_req_header *req, + size_t payload_len) +{ + assert(res != NULL); + assert(req != NULL); + + res->hdr.frame_len = sizeof(*res) + payload_len; + res->hdr.addr = req->hdr.addr; + res->hdr.seq_no = req->hdr.seq_no; + res->hdr.cmd = req->hdr.cmd; + res->status = 0; + res->payload_len = payload_len; +} + +static void sg_res_error( + struct sg_res_header *res, + const struct sg_req_header *req) +{ + assert(res != NULL); + assert(req != NULL); + + res->hdr.frame_len = sizeof(*res); + res->hdr.addr = req->hdr.addr; + res->hdr.seq_no = req->hdr.seq_no; + res->hdr.cmd = req->hdr.cmd; + res->status = 1; + res->payload_len = 0; +} diff --git a/board/sg-cmd.h b/board/sg-cmd.h new file mode 100644 index 0000000..685377f --- /dev/null +++ b/board/sg-cmd.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +struct sg_header { + uint8_t frame_len; + uint8_t addr; + uint8_t seq_no; + uint8_t cmd; +}; + +struct sg_req_header { + struct sg_header hdr; + uint8_t payload_len; +}; + +struct sg_res_header { + struct sg_header hdr; + uint8_t status; + uint8_t payload_len; +}; + +typedef HRESULT (*sg_dispatch_fn_t)( + void *ctx, + const void *req, + void *res); + +void sg_req_transact( + struct iobuf *res_frame, + const uint8_t *req_bytes, + size_t req_nbytes, + sg_dispatch_fn_t dispatch, + void *ctx); + +void sg_res_init( + struct sg_res_header *res, + const struct sg_req_header *req, + size_t payload_len); diff --git a/board/sg-frame.c b/board/sg-frame.c new file mode 100644 index 0000000..d65dbb1 --- /dev/null +++ b/board/sg-frame.c @@ -0,0 +1,165 @@ +#include + +#include +#include +#include +#include + +#include "board/sg-frame.h" + +#include "hook/iobuf.h" + +#include "util/dprintf.h" + +static HRESULT sg_frame_accept(struct iobuf *dest); +static HRESULT sg_frame_encode_byte(struct iobuf *dest, uint8_t byte); + +/* Frame structure: + + [0] Sync byte (0xE0) + [1] Frame size (including self) + [2] Address + [3] Sequence no + ... Body + [n] Checksum: Sum of all non-framing bytes + + Byte stuffing: + + 0xD0 is an escape byte. Un-escape the subsequent byte by adding 1. */ + +static HRESULT sg_frame_accept(struct iobuf *dest) +{ + uint8_t checksum; + size_t i; + + if (dest->pos < 1 || dest->pos != dest->bytes[0] + 1) { + dprintf("SG Frame: Size mismatch\n"); + + return S_FALSE; + } + + checksum = 0; + + for (i = 0 ; i < dest->pos - 1 ; i++) { + checksum += dest->bytes[i]; + } + + if (checksum != dest->bytes[dest->pos - 1]) { + dprintf("SG Frame: Checksum mismatch\n"); + + return HRESULT_FROM_WIN32(ERROR_CRC); + } + + /* Discard checksum */ + dest->pos--; + + return S_OK; +} + +HRESULT sg_frame_decode(struct iobuf *dest, const uint8_t *bytes, size_t nbytes) +{ + uint8_t byte; + size_t i; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(bytes != NULL); + + if (nbytes < 1 || bytes[0] != 0xE0) { + dprintf("SG Frame: Bad sync\n"); + + return E_FAIL; + } + + dest->pos = 0; + i = 1; + + while (i < nbytes) { + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + byte = bytes[i++]; + + if (byte == 0xE0) { + dprintf("SG Frame: Unescaped sync\n"); + + return E_FAIL; + } else if (byte == 0xD0) { + if (i >= nbytes) { + dprintf("SG Frame: Trailing escape\n"); + + return E_FAIL; + } + + byte = bytes[i++]; + dest->bytes[dest->pos++] = byte + 1; + } else { + dest->bytes[dest->pos++] = byte; + } + } + + return sg_frame_accept(dest); +} + +HRESULT sg_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *src; + uint8_t checksum; + uint8_t byte; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(ptr != NULL); + + src = ptr; + + assert(nbytes != 0 && src[0] == nbytes); + + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xE0; + checksum = 0; + + for (i = 0 ; i < nbytes ; i++) { + byte = src[i]; + checksum += byte; + + hr = sg_frame_encode_byte(dest, byte); + + if (FAILED(hr)) { + return hr; + } + } + + return sg_frame_encode_byte(dest, checksum); +} + +static HRESULT sg_frame_encode_byte(struct iobuf *dest, uint8_t byte) +{ + if (byte == 0xD0 || byte == 0xE0) { + if (dest->pos + 2 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xD0; + dest->bytes[dest->pos++] = byte - 1; + } else { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + } + + return S_OK; +} diff --git a/board/sg-frame.h b/board/sg-frame.h new file mode 100644 index 0000000..d74d9b3 --- /dev/null +++ b/board/sg-frame.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +HRESULT sg_frame_decode( + struct iobuf *dest, + const uint8_t *bytes, + size_t nbytes); + +HRESULT sg_frame_encode(struct iobuf *dest, const void *ptr, size_t nbytes); diff --git a/board/sg-led-cmd.h b/board/sg-led-cmd.h new file mode 100644 index 0000000..f74505b --- /dev/null +++ b/board/sg-led-cmd.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "board/sg-cmd.h" + +enum { + SG_RGB_CMD_SET_COLOR = 0x81, + SG_RGB_CMD_RESET = 0xF5, + SG_RGB_CMD_GET_INFO = 0xF0, +}; + +struct sg_led_res_reset { + struct sg_res_header res; + uint8_t payload; +}; + +struct sg_led_res_get_info { + struct sg_res_header res; + uint8_t payload[9]; +}; + +struct sg_led_req_set_color { + struct sg_req_header req; + uint8_t payload[3]; +}; + +union sg_led_req_any { + uint8_t bytes[256]; + struct sg_req_header simple; + struct sg_led_req_set_color set_color; +}; + +union sg_led_res_any { + uint8_t bytes[256]; + struct sg_res_header simple; + struct sg_led_res_reset reset; + struct sg_led_res_get_info get_info; +}; diff --git a/board/sg-led.c b/board/sg-led.c new file mode 100644 index 0000000..855e36b --- /dev/null +++ b/board/sg-led.c @@ -0,0 +1,178 @@ +#include + +#include + +#include "board/sg-cmd.h" +#include "board/sg-led.h" +#include "board/sg-led-cmd.h" + +#include "util/dprintf.h" + +static HRESULT sg_led_dispatch( + void *ctx, + const void *v_req, + void *v_res); + +static HRESULT sg_led_cmd_reset( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_reset *res); + +static HRESULT sg_led_cmd_get_info( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_get_info *res); + +static HRESULT sg_led_cmd_set_color( + const struct sg_led *led, + const struct sg_led_req_set_color *req); + +static const uint8_t sg_led_info[] = { + '1', '5', '0', '8', '4', 0xFF, 0x10, 0x00, 0x12, +}; + +void sg_led_init( + struct sg_led *led, + uint8_t addr, + const struct sg_led_ops *ops, + void *ctx) +{ + assert(led != NULL); + assert(ops != NULL); + + led->ops = ops; + led->ops_ctx = ctx; + led->addr = addr; +} + +void sg_led_transact( + struct sg_led *led, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes) +{ + assert(led != NULL); + assert(res_frame != NULL); + assert(req_bytes != NULL); + + sg_req_transact(res_frame, req_bytes, req_nbytes, sg_led_dispatch, led); +} + +#ifdef NDEBUG +#define sg_led_dprintfv(led, fmt, ap) +#define sg_led_dprintf(led, fmt, ...) +#else +static void sg_led_dprintfv( + const struct sg_led *led, + const char *fmt, + va_list ap) +{ + dprintf("RGB LED %02x: ", led->addr); + dprintfv(fmt, ap); +} + +static void sg_led_dprintf( + const struct sg_led *led, + const char *fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + sg_led_dprintfv(led, fmt, ap); + va_end(ap); +} +#endif + +static HRESULT sg_led_dispatch( + void *ctx, + const void *v_req, + void *v_res) +{ + const struct sg_led *led; + const union sg_led_req_any *req; + union sg_led_res_any *res; + + led = ctx; + req = v_req; + res = v_res; + + if (req->simple.hdr.addr != led->addr) { + /* Not addressed to us, don't send a response */ + return S_FALSE; + } + + switch (req->simple.hdr.cmd) { + case SG_RGB_CMD_RESET: + return sg_led_cmd_reset(led, &req->simple, &res->reset); + + case SG_RGB_CMD_GET_INFO: + return sg_led_cmd_get_info(led, &req->simple, &res->get_info); + + case SG_RGB_CMD_SET_COLOR: + return sg_led_cmd_set_color(led, &req->set_color); + + default: + sg_led_dprintf(led, "Unimpl command %02x\n", req->simple.hdr.cmd); + + return E_NOTIMPL; + } +} + +static HRESULT sg_led_cmd_reset( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_reset *res) +{ + HRESULT hr; + + sg_led_dprintf(led, "Reset\n"); + sg_res_init(&res->res, req, sizeof(res->payload)); + res->payload = 0; + + if (led->ops->reset != NULL) { + hr = led->ops->reset(led->ops_ctx); + } else { + hr = S_OK; + } + + if (FAILED(hr)) { + sg_led_dprintf(led, "led->ops->reset: Error %x\n", hr); + return hr; + } + + return S_OK; +} + +static HRESULT sg_led_cmd_get_info( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_get_info *res) +{ + sg_led_dprintf(led, "Get info\n"); + sg_res_init(&res->res, req, sizeof(res->payload)); + memcpy(res->payload, sg_led_info, sizeof(sg_led_info)); + + return S_OK; +} + +static HRESULT sg_led_cmd_set_color( + const struct sg_led *led, + const struct sg_led_req_set_color *req) +{ + if (req->req.payload_len != sizeof(req->payload)) { + sg_led_dprintf(led, "%s: Payload size is incorrect\n", __func__); + + goto fail; + } + + led->ops->set_color( + led->ops_ctx, + req->payload[0], + req->payload[1], + req->payload[2]); + +fail: + /* No response */ + return S_FALSE; +} diff --git a/board/sg-led.h b/board/sg-led.h new file mode 100644 index 0000000..de3caa6 --- /dev/null +++ b/board/sg-led.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include "hook/iobuf.h" + +struct sg_led_ops { + HRESULT (*reset)(void *ctx); + void (*set_color)(void *ctx, uint8_t r, uint8_t g, uint8_t b); +}; + +struct sg_led { + const struct sg_led_ops *ops; + void *ops_ctx; + uint8_t addr; +}; + +void sg_led_init( + struct sg_led *led, + uint8_t addr, + const struct sg_led_ops *ops, + void *ctx); + +void sg_led_transact( + struct sg_led *led, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes); diff --git a/board/sg-nfc-cmd.h b/board/sg-nfc-cmd.h new file mode 100644 index 0000000..b6754c2 --- /dev/null +++ b/board/sg-nfc-cmd.h @@ -0,0 +1,115 @@ +#pragma once + +#include + +#pragma pack(push, 1) + +enum { + SG_NFC_CMD_GET_FW_VERSION = 0x30, + SG_NFC_CMD_GET_HW_VERSION = 0x32, + SG_NFC_CMD_RADIO_ON = 0x40, + SG_NFC_CMD_RADIO_OFF = 0x41, + SG_NFC_CMD_POLL = 0x42, + SG_NFC_CMD_MIFARE_SELECT_TAG = 0x43, + SG_NFC_CMD_MIFARE_SET_KEY_BANA = 0x50, + SG_NFC_CMD_MIFARE_READ_BLOCK = 0x52, + SG_NFC_CMD_MIFARE_SET_KEY_AIME = 0x54, + SG_NFC_CMD_MIFARE_AUTHENTICATE = 0x55, /* guess based on time sent */ + SG_NFC_CMD_RESET = 0x62, + SG_NFC_CMD_FELICA_ENCAP = 0x71, +}; + +struct sg_nfc_res_get_fw_version { + struct sg_res_header res; + char version[23]; +}; + +struct sg_nfc_res_get_hw_version { + struct sg_res_header res; + char version[23]; +}; + +struct sg_nfc_req_mifare_set_key { + struct sg_req_header req; + uint8_t key_a[6]; +}; + +struct sg_nfc_req_mifare_50 { + struct sg_req_header req; + uint8_t payload[6]; +}; + +struct sg_nfc_req_poll_40 { + struct sg_req_header req; + uint8_t payload; +}; + +struct sg_nfc_poll_mifare { + uint8_t type; + uint8_t id_len; + uint32_t uid; +}; + +struct sg_nfc_poll_felica { + uint8_t type; + uint8_t id_len; + uint64_t IDm; + uint64_t PMm; +}; + +struct sg_nfc_res_poll { + struct sg_res_header res; + uint8_t count; + uint8_t payload[250]; +}; + +struct sg_nfc_req_mifare_select_tag { + struct sg_res_header res; + uint32_t uid; +}; + +struct sg_nfc_req_mifare_read_block { + struct sg_req_header req; + struct { + uint32_t uid; + uint8_t block_no; + } payload; +}; + +struct sg_nfc_res_mifare_read_block { + struct sg_res_header res; + uint8_t block[16]; +}; + +struct sg_nfc_req_felica_encap { + struct sg_req_header req; + uint64_t IDm; + uint8_t payload[243]; +}; + +struct sg_nfc_res_felica_encap { + struct sg_res_header res; + uint8_t payload[250]; +}; + +union sg_nfc_req_any { + uint8_t bytes[256]; + struct sg_req_header simple; + struct sg_nfc_req_mifare_set_key mifare_set_key; + struct sg_nfc_req_mifare_read_block mifare_read_block; + struct sg_nfc_req_mifare_50 mifare_50; + struct sg_nfc_req_poll_40 poll_40; + struct sg_nfc_req_felica_encap felica_encap; +}; + +union sg_nfc_res_any { + uint8_t bytes[256]; + struct sg_res_header simple; + struct sg_nfc_res_get_fw_version get_fw_version; + struct sg_nfc_res_get_hw_version get_hw_version; + struct sg_nfc_res_poll poll; + struct sg_nfc_res_mifare_read_block mifare_read_block; + struct sg_nfc_res_felica_encap felica_encap; +}; + +#pragma pack(pop) diff --git a/board/sg-nfc.c b/board/sg-nfc.c new file mode 100644 index 0000000..ce5d620 --- /dev/null +++ b/board/sg-nfc.c @@ -0,0 +1,434 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "board/sg-cmd.h" +#include "board/sg-nfc.h" +#include "board/sg-nfc-cmd.h" + +#include "iccard/nesica.h" +#include "iccard/felica.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT sg_nfc_dispatch( + void *ctx, + const void *v_req, + void *v_res); + +static HRESULT sg_nfc_cmd_reset( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res); + +static HRESULT sg_nfc_cmd_get_fw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_fw_version *res); + +static HRESULT sg_nfc_cmd_get_hw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_hw_version *res); + +static HRESULT sg_nfc_cmd_poll( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_poll *res); + +static HRESULT sg_nfc_poll_aime( + struct sg_nfc *nfc, + struct sg_nfc_poll_mifare *mifare); + +static HRESULT sg_nfc_poll_felica( + struct sg_nfc *nfc, + struct sg_nfc_poll_felica *felica); + +static HRESULT sg_nfc_cmd_mifare_read_block( + struct sg_nfc *nfc, + const struct sg_nfc_req_mifare_read_block *req, + struct sg_nfc_res_mifare_read_block *res); + +static HRESULT sg_nfc_cmd_felica_encap( + struct sg_nfc *nfc, + const struct sg_nfc_req_felica_encap *req, + struct sg_nfc_res_felica_encap *res); + +static HRESULT sg_nfc_cmd_dummy( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res); + +void sg_nfc_init( + struct sg_nfc *nfc, + uint8_t addr, + const struct sg_nfc_ops *ops, + void *ops_ctx) +{ + assert(nfc != NULL); + assert(ops != NULL); + + nfc->ops = ops; + nfc->ops_ctx = ops_ctx; + nfc->addr = addr; +} + +#ifdef NDEBUG +#define sg_nfc_dprintfv(nfc, fmt, ap) +#define sg_nfc_dprintf(nfc, fmt, ...) +#else +static void sg_nfc_dprintfv( + const struct sg_nfc *nfc, + const char *fmt, + va_list ap) +{ + dprintf("NFC %02x: ", nfc->addr); + dprintfv(fmt, ap); +} + +static void sg_nfc_dprintf( + const struct sg_nfc *nfc, + const char *fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + sg_nfc_dprintfv(nfc, fmt, ap); + va_end(ap); +} +#endif + +void sg_nfc_transact( + struct sg_nfc *nfc, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes) +{ + assert(nfc != NULL); + assert(res_frame != NULL); + assert(req_bytes != NULL); + + sg_req_transact(res_frame, req_bytes, req_nbytes, sg_nfc_dispatch, nfc); +} + +static HRESULT sg_nfc_dispatch( + void *ctx, + const void *v_req, + void *v_res) +{ + struct sg_nfc *nfc; + const union sg_nfc_req_any *req; + union sg_nfc_res_any *res; + + nfc = ctx; + req = v_req; + res = v_res; + + if (req->simple.hdr.addr != nfc->addr) { + /* Not addressed to us, don't send a response */ + return S_FALSE; + } + + switch (req->simple.hdr.cmd) { + case SG_NFC_CMD_RESET: + return sg_nfc_cmd_reset(nfc, &req->simple, &res->simple); + + case SG_NFC_CMD_GET_FW_VERSION: + return sg_nfc_cmd_get_fw_version( + nfc, + &req->simple, + &res->get_fw_version); + + case SG_NFC_CMD_GET_HW_VERSION: + return sg_nfc_cmd_get_hw_version( + nfc, + &req->simple, + &res->get_hw_version); + + case SG_NFC_CMD_POLL: + return sg_nfc_cmd_poll( + nfc, + &req->simple, + &res->poll); + + case SG_NFC_CMD_MIFARE_READ_BLOCK: + return sg_nfc_cmd_mifare_read_block( + nfc, + &req->mifare_read_block, + &res->mifare_read_block); + + case SG_NFC_CMD_FELICA_ENCAP: + return sg_nfc_cmd_felica_encap( + nfc, + &req->felica_encap, + &res->felica_encap); + + case SG_NFC_CMD_MIFARE_AUTHENTICATE: + case SG_NFC_CMD_MIFARE_SELECT_TAG: + case SG_NFC_CMD_MIFARE_SET_KEY_AIME: + case SG_NFC_CMD_MIFARE_SET_KEY_BANA: + case SG_NFC_CMD_RADIO_ON: + case SG_NFC_CMD_RADIO_OFF: + return sg_nfc_cmd_dummy(nfc, &req->simple, &res->simple); + + default: + sg_nfc_dprintf(nfc, "Unimpl command %02x\n", req->simple.hdr.cmd); + + return E_NOTIMPL; + } +} + +static HRESULT sg_nfc_cmd_reset( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res) +{ + sg_nfc_dprintf(nfc, "Reset\n"); + sg_res_init(res, req, 0); + res->status = 3; + + return S_OK; +} + +static HRESULT sg_nfc_cmd_get_fw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_fw_version *res) +{ + /* Dest version is not NUL terminated, this is intentional */ + sg_res_init(&res->res, req, sizeof(res->version)); + memcpy(res->version, "TN32MSEC003S F/W Ver1.2E", sizeof(res->version)); + + return S_OK; +} + +static HRESULT sg_nfc_cmd_get_hw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_hw_version *res) +{ + /* Dest version is not NUL terminated, this is intentional */ + sg_res_init(&res->res, req, sizeof(res->version)); + memcpy(res->version, "TN32MSEC003S H/W Ver3.0J", sizeof(res->version)); + + return S_OK; +} + +static HRESULT sg_nfc_cmd_poll( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_poll *res) +{ + struct sg_nfc_poll_mifare mifare; + struct sg_nfc_poll_felica felica; + HRESULT hr; + + hr = nfc->ops->poll(nfc->ops_ctx); + + if (FAILED(hr)) { + return hr; + } + + hr = sg_nfc_poll_felica(nfc, &felica); + + if (SUCCEEDED(hr) && hr != S_FALSE) { + sg_res_init(&res->res, req, 1 + sizeof(felica)); + memcpy(res->payload, &felica, sizeof(felica)); + res->count = 1; + + return S_OK; + } + + hr = sg_nfc_poll_aime(nfc, &mifare); + + if (SUCCEEDED(hr) && hr != S_FALSE) { + sg_res_init(&res->res, req, 1 + sizeof(mifare)); + memcpy(res->payload, &mifare, sizeof(mifare)); + res->count = 1; + + return S_OK; + } + + sg_res_init(&res->res, req, 1); + res->count = 0; + + return S_OK; +} + +static HRESULT sg_nfc_poll_aime( + struct sg_nfc *nfc, + struct sg_nfc_poll_mifare *mifare) +{ + uint8_t luid[10]; + HRESULT hr; + + /* Call backend */ + + if (nfc->ops->get_aime_id != NULL) { + hr = nfc->ops->get_aime_id(nfc->ops_ctx, luid, sizeof(luid)); + } else { + hr = S_FALSE; + } + + if (FAILED(hr) || hr == S_FALSE) { + return hr; + } + + sg_nfc_dprintf(nfc, "AiMe card is present\n"); + + /* Construct response (use an arbitrary UID) */ + + mifare->type = 0x10; + mifare->id_len = sizeof(mifare->uid); + mifare->uid = _byteswap_ulong(0x01020304); + + /* Initialize MIFARE IC emulator */ + + hr = aime_card_populate(&nfc->mifare, luid, sizeof(luid)); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT sg_nfc_poll_felica( + struct sg_nfc *nfc, + struct sg_nfc_poll_felica *felica) +{ + uint64_t IDm; + HRESULT hr; + + /* Call backend */ + + if (nfc->ops->get_felica_id != NULL) { + hr = nfc->ops->get_felica_id(nfc->ops_ctx, &IDm); + } else { + hr = S_FALSE; + } + + if (FAILED(hr) || hr == S_FALSE) { + return hr; + } + + sg_nfc_dprintf(nfc, "FeliCa card is present\n"); + + /* Construct poll response */ + + felica->type = 0x20; + felica->id_len = sizeof(felica->IDm) + sizeof(felica->PMm); + felica->IDm = _byteswap_uint64(IDm); + felica->PMm = _byteswap_uint64(felica_get_generic_PMm()); + + /* Initialize FeliCa IC emulator */ + + nfc->felica.IDm = IDm; + nfc->felica.PMm = felica_get_generic_PMm(); + nfc->felica.system_code = 0x0000; + + return S_OK; +} + +static HRESULT sg_nfc_cmd_mifare_read_block( + struct sg_nfc *nfc, + const struct sg_nfc_req_mifare_read_block *req, + struct sg_nfc_res_mifare_read_block *res) +{ + uint32_t uid; + + if (req->req.payload_len != sizeof(req->payload)) { + sg_nfc_dprintf(nfc, "%s: Payload size is incorrect\n", __func__); + + return E_FAIL; + } + + uid = _byteswap_ulong(req->payload.uid); + + sg_nfc_dprintf(nfc, "Read uid %08x block %i\n", uid, req->payload.block_no); + + if (req->payload.block_no > 3) { + sg_nfc_dprintf(nfc, "MIFARE block number out of range\n"); + + return E_FAIL; + } + + sg_res_init(&res->res, &req->req, sizeof(res->block)); + + memcpy( res->block, + nfc->mifare.sectors[0].blocks[req->payload.block_no].bytes, + sizeof(res->block)); + + return S_OK; +} + +static HRESULT sg_nfc_cmd_felica_encap( + struct sg_nfc *nfc, + const struct sg_nfc_req_felica_encap *req, + struct sg_nfc_res_felica_encap *res) +{ + struct const_iobuf f_req; + struct iobuf f_res; + HRESULT hr; + + /* First byte of encapsulated request and response is a length byte + (inclusive of itself). The FeliCa emulator expects its caller to handle + that length byte on its behalf (we adopt the convention that the length + byte is part of the FeliCa protocol's framing layer). */ + + if (req->req.payload_len != 8 + req->payload[0]) { + sg_nfc_dprintf( + nfc, + "FeliCa encap payload length mismatch: sg %i != felica %i + 8", + req->req.payload_len, + req->payload[0]); + + return E_FAIL; + } + + f_req.bytes = req->payload; + f_req.nbytes = req->payload[0]; + f_req.pos = 1; + + f_res.bytes = res->payload; + f_res.nbytes = sizeof(res->payload); + f_res.pos = 1; + +#if 0 + dprintf("FELICA OUTBOUND:\n"); + dump_const_iobuf(&f_req); +#endif + + hr = felica_transact(&nfc->felica, &f_req, &f_res); + + if (FAILED(hr)) { + return hr; + } + + sg_res_init(&res->res, &req->req, f_res.pos); + res->payload[0] = f_res.pos; + +#if 0 + dprintf("FELICA INBOUND:\n"); + dump_iobuf(&f_res); +#endif + + return S_OK; +} + +static HRESULT sg_nfc_cmd_dummy( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res) +{ + sg_res_init(res, req, 0); + + return S_OK; +} diff --git a/board/sg-nfc.h b/board/sg-nfc.h new file mode 100644 index 0000000..5562b2b --- /dev/null +++ b/board/sg-nfc.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +#include "iccard/felica.h" +#include "iccard/mifare.h" + +struct sg_nfc_ops { + HRESULT (*poll)(void *ctx); + HRESULT (*get_aime_id)(void *ctx, uint8_t *luid, size_t nbytes); + HRESULT (*get_felica_id)(void *ctx, uint64_t *IDm); + + // TODO Banapass, AmuseIC +}; + +struct sg_nfc { + const struct sg_nfc_ops *ops; + void *ops_ctx; + uint8_t addr; + struct felica felica; + struct mifare mifare; +}; + +void sg_nfc_init( + struct sg_nfc *nfc, + uint8_t addr, + const struct sg_nfc_ops *ops, + void *ops_ctx); + +void sg_nfc_transact( + struct sg_nfc *nfc, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes); diff --git a/board/sg-reader.c b/board/sg-reader.c new file mode 100644 index 0000000..dbf0392 --- /dev/null +++ b/board/sg-reader.c @@ -0,0 +1,186 @@ +#include + +#include +#include +#include + +#include "board/aime-dll.h" +#include "board/sg-led.h" +#include "board/sg-nfc.h" +#include "board/sg-reader.h" + +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT sg_reader_handle_irp(struct irp *irp); +static HRESULT sg_reader_handle_irp_locked(struct irp *irp); +static HRESULT sg_reader_nfc_poll(void *ctx); +static HRESULT sg_reader_nfc_get_aime_id( + void *ctx, + uint8_t *luid, + size_t luid_size); +static HRESULT sg_reader_nfc_get_felica_id(void *ctx, uint64_t *IDm); +static void sg_reader_led_set_color(void *ctx, uint8_t r, uint8_t g, uint8_t b); + +static const struct sg_nfc_ops sg_reader_nfc_ops = { + .poll = sg_reader_nfc_poll, + .get_aime_id = sg_reader_nfc_get_aime_id, + .get_felica_id = sg_reader_nfc_get_felica_id, +}; + +static const struct sg_led_ops sg_reader_led_ops = { + .set_color = sg_reader_led_set_color, +}; + +static CRITICAL_SECTION sg_reader_lock; +static bool sg_reader_started; +static HRESULT sg_reader_start_hr; +static struct uart sg_reader_uart; +static uint8_t sg_reader_written_bytes[520]; +static uint8_t sg_reader_readable_bytes[520]; +static struct sg_nfc sg_reader_nfc; +static struct sg_led sg_reader_led; + +HRESULT sg_reader_hook_init( + const struct aime_config *cfg, + unsigned int port_no, + HINSTANCE self) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + hr = aime_dll_init(&cfg->dll, self); + + if (FAILED(hr)) { + return hr; + } + + sg_nfc_init(&sg_reader_nfc, 0x00, &sg_reader_nfc_ops, NULL); + sg_led_init(&sg_reader_led, 0x08, &sg_reader_led_ops, NULL); + + InitializeCriticalSection(&sg_reader_lock); + + uart_init(&sg_reader_uart, port_no); + sg_reader_uart.written.bytes = sg_reader_written_bytes; + sg_reader_uart.written.nbytes = sizeof(sg_reader_written_bytes); + sg_reader_uart.readable.bytes = sg_reader_readable_bytes; + sg_reader_uart.readable.nbytes = sizeof(sg_reader_readable_bytes); + + return iohook_push_handler(sg_reader_handle_irp); +} + +static HRESULT sg_reader_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&sg_reader_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&sg_reader_lock); + hr = sg_reader_handle_irp_locked(irp); + LeaveCriticalSection(&sg_reader_lock); + + return hr; +} + +static HRESULT sg_reader_handle_irp_locked(struct irp *irp) +{ + HRESULT hr; + +#if 0 + if (irp->op == IRP_OP_WRITE) { + dprintf("WRITE:\n"); + dump_const_iobuf(&irp->write); + } +#endif + +#if 0 + if (irp->op == IRP_OP_READ) { + dprintf("READ:\n"); + dump_iobuf(&sg_reader_uart.readable); + } +#endif + + if (irp->op == IRP_OP_OPEN) { + /* Unfortunately the card reader UART gets opened and closed + repeatedly */ + + if (!sg_reader_started) { + dprintf("NFC Assembly: Starting backend DLL\n"); + hr = aime_dll.init(); + + sg_reader_started = true; + sg_reader_start_hr = hr; + + if (FAILED(hr)) { + dprintf("NFC Assembly: Backend error: %x\n", (int) hr); + + return hr; + } + } else { + hr = sg_reader_start_hr; + + if (FAILED(hr)) { + return hr; + } + } + } + + hr = uart_handle_irp(&sg_reader_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + sg_nfc_transact( + &sg_reader_nfc, + &sg_reader_uart.readable, + sg_reader_uart.written.bytes, + sg_reader_uart.written.pos); + + sg_led_transact( + &sg_reader_led, + &sg_reader_uart.readable, + sg_reader_uart.written.bytes, + sg_reader_uart.written.pos); + + sg_reader_uart.written.pos = 0; + + return hr; +} + +static HRESULT sg_reader_nfc_poll(void *ctx) +{ + return aime_dll.nfc_poll(0); +} + +static HRESULT sg_reader_nfc_get_aime_id( + void *ctx, + uint8_t *luid, + size_t luid_size) +{ + return aime_dll.nfc_get_aime_id(0, luid, luid_size); +} + +static HRESULT sg_reader_nfc_get_felica_id(void *ctx, uint64_t *IDm) +{ + return aime_dll.nfc_get_felica_id(0, IDm); +} + +static void sg_reader_led_set_color(void *ctx, uint8_t r, uint8_t g, uint8_t b) +{ + aime_dll.led_set_color(0, r, g, b); +} diff --git a/board/sg-reader.h b/board/sg-reader.h new file mode 100644 index 0000000..673a8bd --- /dev/null +++ b/board/sg-reader.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +#include "board/aime-dll.h" + +struct aime_config { + struct aime_dll_config dll; + bool enable; +}; + +HRESULT sg_reader_hook_init( + const struct aime_config *cfg, + unsigned int port_no, + HINSTANCE self); diff --git a/cross-mingw-32.txt b/cross-mingw-32.txt new file mode 100644 index 0000000..21ed951 --- /dev/null +++ b/cross-mingw-32.txt @@ -0,0 +1,10 @@ +[binaries] +c = 'i686-w64-mingw32-gcc' +ar = 'i686-w64-mingw32-ar' +strip = 'i686-w64-mingw32-strip' + +[host_machine] +system = 'windows' +cpu_family = 'x86' +cpu = 'i686' +endian = 'little' diff --git a/cross-mingw-64.txt b/cross-mingw-64.txt new file mode 100644 index 0000000..6b15798 --- /dev/null +++ b/cross-mingw-64.txt @@ -0,0 +1,10 @@ +[binaries] +c = 'x86_64-w64-mingw32-gcc' +ar = 'x86_64-w64-mingw32-ar' +strip = 'x86_64-w64-mingw32-strip' + +[host_machine] +system = 'windows' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' diff --git a/dist/carol/segatools.ini b/dist/carol/segatools.ini new file mode 100644 index 0000000..b0c1d0d --- /dev/null +++ b/dist/carol/segatools.ini @@ -0,0 +1,48 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. +enable=1 + +[gpio] +dipsw1=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.126.0 + +[gfx] +; Force the game to run windowed. +windowed=1 +; Add a frame to the game window if running windowed. +framed=1 +; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) +monitor=0 + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in keyboard input. +path= + +[io3] +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 diff --git a/dist/carol/start.bat b/dist/carol/start.bat new file mode 100644 index 0000000..0fab950 --- /dev/null +++ b/dist/carol/start.bat @@ -0,0 +1,13 @@ +@echo off + +pushd %~dp0 + +taskkill /f /im aimeReaderHost.exe > nul 2>&1 + +start /min inject -d -k carolhook.dll aimeReaderHost.exe -p 10 +inject -d -k carolhook.dll carol_nu.exe +taskkill /f /im aimeReaderHost.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/chuni/segatools.ini b/dist/chuni/segatools.ini new file mode 100644 index 0000000..0a9939e --- /dev/null +++ b/dist/chuni/segatools.ini @@ -0,0 +1,80 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; Chunithm is extremely picky about its LAN environment, so leaving this +; setting enabled is strongly recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.139.0 + +[gfx] +; Force the game to run windowed. +windowed=1 +; Add a frame to the game window if running windowed. +framed=1 +; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) +monitor=0 + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in keyboard input. +path= + +[chuniio] +; To use a custom Chunithm IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in keyboard input. +path= + +; ----------------------------------------------------------------------------- +; Input settings +; ----------------------------------------------------------------------------- + +; Keyboard bindings are specified as hexadecimal (prefixed with 0x) or decimal +; (not prefixed with 0x) virtual-key codes, a list of which can be found here: +; +; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +; +; This is, admittedly, not the most user-friendly configuration method in the +; world. An improved solution will be provided later. + +[io3] +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 + +; Key bindings for each of the 32 touch cells. The default key map, depicted +; in left-to-right order, is as follows: +; +; SSSSDDDDFFFFGGGGHHHHJJJJKKKKLLLL +; +; Touch cells are numbered FROM RIGHT TO LEFT! starting from 1. This is in +; order to match the numbering used in the operator menu and service manual. +; +; Uncomment and complete the following sequence of settings to configure a +; custom high-precision touch strip controller if you have one. +[slider] +;cell32=0x53 +;cell31=0x53 +;cell30=0x53 +; ... etc ... diff --git a/dist/chuni/start.bat b/dist/chuni/start.bat new file mode 100644 index 0000000..0c942e9 --- /dev/null +++ b/dist/chuni/start.bat @@ -0,0 +1,11 @@ +@echo off + +pushd %~dp0 + +start /min inject -d -k chunihook.dll aimeReaderHost.exe -p 12 +inject -d -k chunihook.dll chuniApp.exe +taskkill /f /im aimeReaderHost.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/cxb/resource/segatools.ini b/dist/cxb/resource/segatools.ini new file mode 100644 index 0000000..a214976 --- /dev/null +++ b/dist/cxb/resource/segatools.ini @@ -0,0 +1,4 @@ +[aime] +; CXB is stupid, so we have to make the paths go back one +aimePath=../DEVICE/aime.txt +felicaPath=../DEVICE/felica.txt \ No newline at end of file diff --git a/dist/cxb/segatools.ini b/dist/cxb/segatools.ini new file mode 100644 index 0000000..e0491ff --- /dev/null +++ b/dist/cxb/segatools.ini @@ -0,0 +1,75 @@ +[vfs] +; Make sure theses are full paths and not relative or you will have a bad time +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; Crossbeats is extremely picky about its LAN environment, so leaving this +; setting enabled is strongly recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.100.0 +billingCa=../DEVICE/ca.crt +billingPub=../DEVICE/billing.pub +billingType=2 + +[gfx] +; Force the game to run windowed. +windowed=1 +; Add a frame to the game window if running windowed. +framed=1 +; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) +monitor=0 + +[aime] +; Aime reader emulation +; CXB is stupid, so we have to make the paths go back one +enable=1 +aimePath=../DEVICE/aime.txt +felicaPath=../DEVICE/felica.txt + +[eeprom] +; See above +path=../DEVICE/eeprom.bin + +[sram] +; See above +path=../DEVICE/sram.bin + +[led] +; Emulation for the LED board. Currently it's just dummy responses, +; but if somebody wants to make their keyboard or whatever light +; up with the game they can +enable=1 + +[revio] +; Enable emulation of the rev IO board +enabe=1 +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 +; Menu up key. Default is up arrow. +up=0x26 +; Menu down key. Default is down arrow. +down=0x28 +; Menu cancel key. Default is the 4 key. +cancel=0x34 diff --git a/dist/cxb/start.bat b/dist/cxb/start.bat new file mode 100644 index 0000000..af92b2d --- /dev/null +++ b/dist/cxb/start.bat @@ -0,0 +1,9 @@ +@echo off + +pushd %~dp0 + +inject -d -k cxbhook.dll Rev_v11.exe + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/diva/segatools.ini b/dist/diva/segatools.ini new file mode 100644 index 0000000..e2608c0 --- /dev/null +++ b/dist/diva/segatools.ini @@ -0,0 +1,54 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; Chunithm is extremely picky about its LAN environment, so leaving this +; setting enabled is strongly recommended. +enable=1 + +[gpio] +dipsw1=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.150.0 + +[slider] +cell1=0x51 +cell2=0x57 +cell3=0x45 +cell4=0x52 +cell5=0x55 +cell6=0x49 +cell7=0x4F +cell8=0x50 + +[buttons] +key1=0x27 +key2=0x28 +key3=0x25 +key4=0x26 +key5=0x20 + +; Sliders : <- QWER UIOP -> +; Triangle : Up arrow +; Square : Left Arrow +; Cross : Down Arrow +; Circle : Right arrow +; Enter : Space + diff --git a/dist/diva/start.bat b/dist/diva/start.bat new file mode 100644 index 0000000..72934f6 --- /dev/null +++ b/dist/diva/start.bat @@ -0,0 +1,9 @@ +@echo off + +pushd %~dp0 + +inject -d -k divahook.dll diva.exe + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/idz/segatools.ini b/dist/idz/segatools.ini new file mode 100644 index 0000000..4634ab4 --- /dev/null +++ b/dist/idz/segatools.ini @@ -0,0 +1,116 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains OPxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[ds] +; Region code on the emulated AMEX board DS EEPROM. +; 1: Japan +; 4: Export (some UI elements in English) +; +; NOTE: Changing this setting causes a factory reset. +region=1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.100.0 + +[gpio] +; Emulated Nu DIP switch for Distribution Server setting. +; +; If multiple machines are present on the same LAN then set this to 1 on +; exactly one machine and set this to 0 on all others. +dipsw1=1 + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in keyboard input. +path= + +[idzio] +; To use a custom Initial D Zero IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in gamepad/wheel input. +path= + +[io3] +; Input API selection for JVS input emulator. +; Set "xinput" to use a gamepad and "dinput" to use a steering wheel. +mode=xinput +; Automatically reset the simulated shifter to Neutral when XInput Start is +; pressed (e.g. when navigating menus between races). +autoNeutral=1 +; Use the left thumbstick for steering instead of both on XInput Controllers. +; Not recommended as it will not give you the precision needed for this game +singleStickSteering=0 +; Adjust scaling for steering wheel input. +; +; This setting scales the steering wheel input so that the maximum positive +; and minimum negative steering inputs reported in the operator menu's input +; test screen do not exceed the value below. The maximum possible value is 128, +; and the value that matches the input range of a real cabinet is 97. +; +; NOTE: This is not the same thing as DirectInput steering wheel movement +; range! Taitools cannot control the maximum angle of your physical steering +; wheel controller, this setting is vendor-specific and can only be adjusted +; in the Control Panel. +restrict=97 + +[dinput] +; Name of the DirectInput wheel to use (or any text that occurs in its name) +; Example: TMX +; +; If this is left blank then the first DirectInput device will be used. +deviceName= +; Name of the positional shifter to use (or any subset thereof). +; Leave blank if you do not have a positional shifter; a positional shifter +; will be simulated using the configured Shift Down and Shift Up buttons +; in this case. +; +; Can be the same device as the wheel. +; +; Example: T500 +shifterName= +; Pedal mappings. Valid axis names are: +; +; X, Y, Z, RX, RY, RZ, U, V +; +; (U and V are old names for Slider 1 and Slider 2). +; The examples below are valid for a Thrustmaster TMX. +brakeAxis=RZ +accelAxis=Y +; DirectInput button numbers to map to menu inputs. Note that buttons are +; numbered from 1; some software numbers buttons from 0. +start=3 +viewChg=10 +; Button mappings for the simulated six-speed shifter. +shiftDn=1 +shiftUp=2 +; Button mappings for the positional shifter, if present. +gear1=1 +gear2=2 +gear3=3 +gear4=4 +gear5=5 +gear6=6 +; Invert the accelerator and or brake axis +; (Needed when using DirectInput for the Dualshock 4 for example) +reverseAccelAxis=0 +reverseBrakeAxis=0 diff --git a/dist/idz/start.bat b/dist/idz/start.bat new file mode 100644 index 0000000..b8d6b18 --- /dev/null +++ b/dist/idz/start.bat @@ -0,0 +1,10 @@ +@echo off + +pushd %~dp0 + +.\inject.exe -k .\idzhook.dll .\InitialD0_DX11_Nu.exe +.\inject.exe -d -k .\idzhook.dll .\amdaemon.exe -c configDHCP_Final_Common.json configDHCP_Final_JP.json configDHCP_Final_JP_ST1.json configDHCP_Final_JP_ST2.json configDHCP_Final_EX.json configDHCP_Final_EX_ST1.json configDHCP_Final_EX_ST2.json + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/mai2/segatools.ini b/dist/mai2/segatools.ini new file mode 100644 index 0000000..25fdc31 --- /dev/null +++ b/dist/mai2/segatools.ini @@ -0,0 +1,44 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs=amfs +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata=appdata +option=option + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[ds] +; Region code on the emulated AMEX board DS EEPROM. +; 1: Japan +; 4: Export (some UI elements in English) +; +; NOTE: Changing this setting causes a factory reset. +region=1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.172.0 + +[gfx] +enable=1 + +[io4] +; Delete +test=0x2E +; End +service=0x23 +; Insert +coin=0x2D diff --git a/dist/mai2/start.bat b/dist/mai2/start.bat new file mode 100644 index 0000000..2d6bd8f --- /dev/null +++ b/dist/mai2/start.bat @@ -0,0 +1,11 @@ +@echo off +pushd %~dp0 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +start inject -d -k mai2hook.dll amdaemon.exe -f -c config_client.json config_common.json config_server.json +inject.exe -d -k mai2hook.dll Sinmai.exe -screen-fullscreen 0 -screen-width 2160 -screen-height 1920 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo Game processes have terminated \ No newline at end of file diff --git a/dist/mercury/segatools.ini b/dist/mercury/segatools.ini new file mode 100644 index 0000000..f231c66 --- /dev/null +++ b/dist/mercury/segatools.ini @@ -0,0 +1,56 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs=amfs +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata=appdata +option=option + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[ds] +; Region code on the emulated AMEX board DS EEPROM. +; 1: Japan +; 4: Export (some UI elements in English) +; +; NOTE: Changing this setting causes a factory reset. +region=1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.174.0 + +[gfx] +enable=1 + +[io4] +; Input API selection for JVS input emulator. +test=0x2D +service=0x2E +coin=0x24 +volup=0x26 +voldown=0x28 + +; Hooks related to the touch boards +[touch] +enable=1 + +; Hooks related to the LED board (codenamed Elisabeth) +[elisabeth] +enable=1 + +;[mercuryio] +; Use mercuryio.dll +;path=mercuryio.dll diff --git a/dist/mercury/start.bat b/dist/mercury/start.bat new file mode 100644 index 0000000..c00f2ea --- /dev/null +++ b/dist/mercury/start.bat @@ -0,0 +1,15 @@ +@echo off +pushd %~dp0 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +REM USA +REM start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_usa.json + +REM JP +start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_jpn.json +inject -d -k mercuryhook.dll ../WindowsNoEditor/Mercury/Binaries/Win64/Mercury-Win64-Shipping.exe + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo Game processes have terminated \ No newline at end of file diff --git a/dist/mu3/segatools.ini b/dist/mu3/segatools.ini new file mode 100644 index 0000000..98cdb31 --- /dev/null +++ b/dist/mu3/segatools.ini @@ -0,0 +1,60 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs=amfs +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata=appdata +option=option + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[ds] +; Region code on the emulated AMEX board DS EEPROM. +; 1: Japan +; 4: Export (some UI elements in English) +; +; NOTE: Changing this setting causes a factory reset. +region=1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.162.0 + +[gfx] +enable=1 + +[io4] +; Input API selection for JVS input emulator. +; Set "1" to use a xinput gamepad and set "2" to use keyboard. +mode=2 + +test=0x31 +service=0x32 + +[dinput] +LEFT_A=0x53 +LEFT_B=0x44 +LEFT_C=0x46 +LEFT_MENU=0x51 +LEFT_SIDE=0x52 +RIGHT_A=0x4A +RIGHT_B=0x4B +RIGHT_C=0x4C +RIGHT_MENU=0x50 +RIGHT_SIDE=0x55 +SLIDER_LEFT=0x54 +SLIDER_RIGHT=0x59 +;Change move speed of slider when use dinput +SLIDER_SPEED=1000 diff --git a/dist/mu3/start.bat b/dist/mu3/start.bat new file mode 100644 index 0000000..3aceffb --- /dev/null +++ b/dist/mu3/start.bat @@ -0,0 +1,11 @@ +@echo off +pushd %~dp0 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +start inject -d -k mu3hook.dll amdaemon.exe -f -c config_client.json config_common.json config_server.json +inject -d -k mu3hook.dll mu3.exe + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo Game processes have terminated \ No newline at end of file diff --git a/doc/config/common.md b/doc/config/common.md new file mode 100644 index 0000000..e0616a8 --- /dev/null +++ b/doc/config/common.md @@ -0,0 +1,470 @@ +# Taitools common configuration settings + +This file describes configuration settings for Taitools that are common to +all games. + +Keyboard binding settings use +[Virtual-Key Codes](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). + +## `[aimeio]` + +Controls the card reader driver. + +### `path` + +Specify a path for a third-party card reader driver DLL. Default is empty +(use built-in emulation based on text files and keyboard input). + +In previous versions of Taitools this was accomplished by replacing the +AIMEIO.DLL file that came with Taitools. Taitools no longer ships with a +separate AIMEIO.DLL file (its functionality is now built into the various hook +DLLs). + +## `[aime]` + +Controls emulation of the Aime card reader assembly. + +### `enable` + +Default: `1` + +Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +reader (COM port number varies by game). + +### `aimePath` + +Default: `DEVICE\aime.txt` + +Path to a text file containing a classic Nesica IC card ID. **This does not +currently work**. + +### `felicaPath` + +Default: `DEVICE\felica.txt` + +Path to a text file containing a FeliCa e-cash card IDm serial number. + +### `felicaGen` + +Default: `1` + +Whether to generate a random FeliCa ID if the file at `felicaPath` does not +exist. + +### `scan` + +Default: `0x0D` (`VK_RETURN`) + +Virtual-key code. If this button is **held** then the emulated IC card reader +emulates an IC card in its proximity. A variety of different IC cards can be +emulated; the exact choice of card that is emulated depends on the presence or +absence of the configured card ID files. + +## `[amvideo]` + +Controls the `amvideo.dll` stub built into Taitools. This is a DLL that is +normally present on the SEGA operating system image which is responsible for +changing screen resolution and orientation. + +### `enable` + +Default: `1` + +Enable stub `amvideo.dll`. Disable to use a real `amvideo.dll` build. Note that +you must have the correct registry settings installed and you must use the +version of `amvideo.dll` that matches your GPU vendor (since these DLLs make +use of vendor-specific APIs). + +## `[clock]` + +Controls hooks for Windows time-of-day APIs. + +### `timezone` + +Default: `1` + +Make the system time zone appear to be JST. SEGA games malfunction in strange +ways if the system time zone is not JST. There should not be any reason to +disable this hook other than possible implementation bugs, but the option is +provided if you need it. + +### `timewarp` + +Default: `0` + +Experimental time-of-day warping hook that skips over the hardcoded server +maintenance period. Causes an incorrect in-game time-of-day to be reported. +Better solutions for this problem exist and this feature will probably be +removed soon. + +### `writeable` + +Default: `0` + +Allow game to adjust system clock and time zone settings. This should normally +be left at `0`, but the option is provided if you need it. + +## `[dns]` + +Controls redirection of network server hostname lookups + +### `default` + +Default: `localhost` + +Controls hostname of all of the common network services servers, unless +overriden by a specific setting below. Most users will only need to change this +setting. Also, loopback addresses are specifically checked for and rejected by +the games themselves; this needs to be a LAN or WAN IP (or a hostname that +resolves to one). + +### `router` + +Default: Empty string (i.e. use value from `default` setting) + +Overrides the target of the `tenporouter.loc` and `bbrouter.loc` hostname +lookups. + +### `startup` + +Default: Empty string (i.e. use value from `default` setting) + +Overrides the target of the `naominet.jp` host lookup. + +### `billing` + +Default: Empty string (i.e. use value from `default` setting) + +Overrides the target of the `ib.naominet.jp` host lookup. + +### `aimedb` + +Default: Empty string (i.e. use value from `default` setting) + +Overrides the target of the `aime.naominet.jp` host lookup. + +## `[ds]` + +Controls emulation of the "DS (Dallas Semiconductor) EEPROM" chip on the AMEX +PCIe board. This is a small (32 byte) EEPROM that contains serial number and +region code information. It is not normally written to outside of inital +factory provisioning of a Sega Nu. + +### `enable` + +Default: `1` + +Enable DS EEPROM emulation. Disable to use the DS EEPROM chip on a real AMEX. + +### `region` + +Default: `1` + +AMEX Board region code. This appears to be a bit mask? + +- `1`: Japan +- `2`: USA? (Dead code, not used) +- `4`: Export +- `8`: China + +### `serialNo` + +Default `AAVE-01A99999999` + +"MAIN ID" serial number. First three characters are hardware series: + +- `AAV`: Nu-series +- `AAW`: NuSX-series +- `ACA`: ALLS-series + +## `[eeprom]` + +Controls emulation of the bulk EEPROM on the AMEX PCIe board. This chip stores +status and configuration information. + +### `enable` + +Default: `1` + +Enable bulk EEPROM emulation. Disable to use the bulk EEPROM chip on a real +AMEX. + +### `path` + +Default: `DEVICE\eeprom.bin` + +Path to the storage file for EEPROM emulation. This file is automatically +created and initialized with a suitable number of zero bytes if it does not +already exist. + +## `[gpio]` + +Configure emulation of the AMEX PCIe GPIO (General Purpose Input Output) +controller. + +### `enable` + +Default: `1` + +Enable GPIO emulation. Disable to use the GPIO controller on a real AMEX. + +### `sw1` + +Default `0x70` (`VK_F1`) + +Keyboard binding for Nu chassis SW1 button (alternative Test) + +### `sw2` + +Default `0x71` (`VK_F2`) + +Keyboard binding for Nu chassis SW2 button (alternative Service) + +### `dipsw1` .. `dipsw8` + +Defaults: `1`, `0`, `0`, `0`, `0`, `0`, `0`, `0` + +Nu chassis DIP switch settings: + +- Switch 1: Game-specific, but usually controls the "distribution server" + setting. Exactly one arcade machine on a cabinet router must be set to the + Server setting. + - `0`: Client + - `1`: Server +- Switch 2,3: Game-specific. + - Used by Mario&Sonic to configure cabinet ID, possibly other games. +- Switch 4: Screen orientation. Only used by the Nu system startup program. + - `0`: YOKO/Horizontal + - `1`: TATE/Vertical +- Switch 5,6,7: Screen resolution. Only used by the Nu system startup program. + - `000`: No change + - `100`: 640x480 + - `010`: 1024x600 + - `110`: 1024x768 + - `001`: 1280x720 + - `101`: 1280x1024 + - `110`: 1360x768 + - `111`: 1920x1080 +- Switch 8: Game-specific. Not used in any shipping game. + +## `[hwmon]` + +Configure stub implementation of the platform hardware monitor driver. The +real implementation of this driver monitors CPU temperatures by reading from +Intel Model Specific Registers, which is an action that is only permitted from +kernel mode. + +### `enable` + +Default `1` + +Enable hwmon emulation. Disable to use the real hwmon driver. + +## `[jvs]` + +Configure emulation of the AMEX PCIe JVS *controller* (not IO board!) + +### `enable` + +Default `1` + +Enable JVS port emulation. Disable to use the JVS port on a real AMEX. + +## `[keychip]` + +Configure keychip emulation. + +### `enable` + +Enable keychip emulation. Disable to use a real keychip. + +### `id` + +Default: `A69E-01A88888888` + +Keychip serial number. Keychip serials observed in the wild follow this +pattern: `A6xE-01Ayyyyyyyy`. + +### `gameId` + +Default: (Varies depending on game) + +Override the game's four-character model code. Changing this from the game's +expected value will probably just cause a system error. + +### `platformId` + +Default: (Varies depending on game) + +Override the game's four-character platform code (e.g. `AAV2` for Nu 2). This +is actually supposed to be a separate three-character `platformId` and +integer `modelType` setting, but they are combined here for convenience. Valid +values include: + +- `AAV0`: Nu 1 (Project DIVA) +- `AAV1`: Nu 1.1 (Chunithm) +- `AAV2`: Nu 2 (Initial D Zero) +- `AAW0`: NuSX 1 +- `AAW1`: NuSX 1.1 +- `ACA0`: ALLS UX +- `ACA1`: ALLS HX +- `ACA2`: ALLS UX (without dedicated GPU) +- `ACA4`: ALLS MX + +### `region` + +Default: `1` + +Override the keychip's region code. Most games seem to pay attention to the +DS EEPROM region code and not the keychip region code, and this seems to be +a bit mask that controls which Nu PCB region codes this keychip is authorized +for. So it probably only affects the system software and not the game software. +Bit values are: + +- 1: JPN: Japan +- 2: USA (unused) +- 3: EXP: Export (for Asian markets) +- 4: CHS: China (Simplified Chinese?) + +### `billingType` + +Default: `1` + +Set the billing "type" for the keychip. The type determins what kind of revenue share, +if any, the game maker has with SEGA. Some games may be picky and require types other +then 1 (ex. Crossbeats requires billing type 2), so this option is provided if this +is an issue. Billing types are: + +- 0: No billing? +- 1: Billing type A +- 2: Billing type B1 +- 3: Billing type B2 + +### `systemFlag` + +Default: `0x64` + +An 8-bit bitfield of unclear meaning. The least significant bit indicates a +developer dongle, I think? Changing this doesn't seem to have any effect on +anything other than Project DIVA. + +Other values observed in the wild: + +- `0x04`: SDCH, SDCA +- `0x20`: SDCA + +### `subnet` + +Default `192.168.100.0` + +The LAN IP range that the game will expect. The prefix length is hardcoded into +the game program: for some games this is `/24`, for others it is `/20`. + +## `[netenv]` + +Configure network environment virtualization. This module helps bypass various +restrictions placed upon the game's LAN environment. + +### `enable` + +Default `1` + +Enable network environment virtualization. You may need to disable this if +you want to do any head-to-head play on your LAN. + +Note: The virtualized LAN IP range is taken from the emulated keychip's +`subnet` setting. + +### `addrSuffix` + +Default: `11` + +The final octet of the local host's IP address on the virtualized subnet (so, +if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +local host's virtualized LAN IP is `192.168.32.11`). + +### `routerSuffix` + +Default: `1` + +The final octet of the default gateway's IP address on the virtualized subnet. + +### `macAddr` + +Default: `01:02:03:04:05:06` + +The MAC address of the virtualized Ethernet adapter. The exact value shouldn't +ever matter. + +## `[pcbid]` + +Configure Windows host name virtualization. The ALLS-series platform no longer +has an AMEX board, so the MAIN ID serial number is stored in the Windows +hostname. + +### `enable` + +Default: `1` + +Enable Windows host name virtualization. This is only needed for ALLS-platform +games (since the ALLS lacks an AMEX and therefore has no DS EEPROM, so it needs +another way to store the PCB serial), but it does no harm on games that run on +earlier hardware. + +### `serialNo` + +Default: `ACAE01A99999999` + +Set the Windows host name. This should be an ALLS MAIN ID, without the +hyphen (which is not a valid character in a Windows host name). + +## `[sram]` + +Configure emulation of the AMEX PCIe battery-backed SRAM. This stores +bookkeeping state and settings. This file is automatically created and +initialized with a suitable number of zero bytes if it does not already exist. + +### `enable` + +Default `1` + +Enable SRAM emulation. Disable to use the SRAM on a real AMEX. + +### `path` + +Default `DEVICE\sram.bin` + +Path to the storage file for SRAM emulation. + +## `[vfs]` + +Configure Windows path redirection hooks. + +### `enable` + +Default: `1` + +Enable path redirection. + +### `amfs` + +Default: Empty string (causes a startup error) + +Configure the location of the SEGA AMFS volume. Stored on the `E` partition on +real hardware. + +### `appdata` + +Default: Empty string (causes a startup error) + +Configure the location of the SEGA "APPDATA" volume (nothing to do with the +Windows user's `%APPDATA%` directory). Stored on the `Y` partition on real +hardware. + +### `option` + +Default: Empty string + +Configure the location of the "Option" data mount point. This mount point is +optional (hence the name, probably) and contains directories which contain +minor over-the-air content updates. diff --git a/doc/development.md b/doc/development.md new file mode 100644 index 0000000..5a08e72 --- /dev/null +++ b/doc/development.md @@ -0,0 +1,117 @@ +# Development + +This document is intended for developers interested in contributing to taitools. Please read this document before +you start developing/contributing. + +## 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 goals might do as well. + +* Allow running Sega arcade (rhythm) games 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 +* docker (optional) + +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 + +### Further tools for testing and debugging + +* Debugger: Can be part of your reverse engineering IDE of your choice or stand-along like +[OllyDbg](http://www.ollydbg.de/) +* [apitrace](https://apitrace.github.io/): Trace render calls to graphics APIs like D3D and OpenGL. +This tool allows you to record and re-play render calls of an application with frame-by-frame +debugging. Very useful to analyze the render pipeline or debug graphicial glitches + +## Building + +The root `Makefile` contains various targets that allow you to build the project easily. + +### Local build + +For a local build, you need to install Meson and a recent build of MinGW-w64. Then you can start the +build process: + +```shell +make build +``` + +Build output will be located in `build/build32` and `build/build64` folders. + +### Cleanup local build + +```shell +make clean +``` + +### Create distribution package (zip file) + +```shell +make dist +``` + +The output will be located in `build/zip`. + +### Build and create distribution package using docker + +You can also build using docker which avoids having to setup a full development environment if you +are just interested in building binaries of the latest changes. Naturally, this requires you to +have the docker daemon installed. + +```shell +make build-docker +``` + +Once completed successfully, the build output is located in the `build/docker/zip` sub-folder. + +### Building with Docker Desktop on Windows + +* [Install WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10) +* [Install Docker Desktop](https://docs.docker.com/docker-for-windows/install/) +* Run Docker Desktop to start the Docker Engine +* Open a command prompt (`cmd.exe`) and `cd` to your `taitools` folder +* Run `docker-build.bat` +* Once completed successfully, build output is located in the `build/docker/zip` sub-folder. + +### Building with Docker on Windows using WSL2 + +* [Install WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10) +* Regarding Linux distribution, we recommend using Ubuntu 20.04 +* Run the "Ubuntu 20.04 LTS" App which opens a Linux shell +* Install `make` and `docker` by running + * `sudo apt-get update` + * `sudo apt-get install make docker.io` +* Add the current user to the docker group that you don't have to run docker commands with root: +`sudo usermod -a -G docker $USER` +* Run the docker daemon in the background: `sudo dockerd > /dev/null 2>&1 &` +* Navigate to your taitools folder. If it is located on the `C:` drive, WSL automatically provides +a mountpoint for that under `/mnt/c`, e.g. `cd /mnt/c/taitools` (if the folder `taitools` is +located under `C:\taitools` on Windows). +* Build taitools: `make build-docker` +* Once completed successfully, build output is located in the `build/docker/zip` sub-folder \ No newline at end of file diff --git a/docker-build.bat b/docker-build.bat new file mode 100644 index 0000000..c2960d1 --- /dev/null +++ b/docker-build.bat @@ -0,0 +1,34 @@ +@echo off +setlocal enabledelayedexpansion + +:: Static Environment Variables +set IMAGE_NAME=hay1tsme/taitools-build:latest +set CONTAINER_NAME=taitools-build + +:: Main Execution +docker build . -t %IMAGE_NAME% + +if ERRORLEVEL 1 ( + goto failure +) + +docker run -it --rm -v %~dp0:/taitools --name %CONTAINER_NAME% %IMAGE_NAME% + +if ERRORLEVEL 1 ( + goto failure +) + +docker image rm -f %IMAGE_NAME% + +goto success + +:failure +echo taitools Docker build FAILED! +goto finish + +:success +echo taitools Docker build completed successfully. +goto finish + +:finish +pause diff --git a/gfxhook/config.c b/gfxhook/config.c new file mode 100644 index 0000000..98db059 --- /dev/null +++ b/gfxhook/config.c @@ -0,0 +1,17 @@ +#include + +#include +#include + +#include "gfxhook/config.h" + +void gfx_config_load(struct gfx_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"gfx", L"enable", 1, filename); + cfg->windowed = GetPrivateProfileIntW(L"gfx", L"windowed", 0, filename); + cfg->framed = GetPrivateProfileIntW(L"gfx", L"framed", 1, filename); + cfg->monitor = GetPrivateProfileIntW(L"gfx", L"monitor", 0, filename); +} diff --git a/gfxhook/config.h b/gfxhook/config.h new file mode 100644 index 0000000..a6ced69 --- /dev/null +++ b/gfxhook/config.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "gfxhook/gfx.h" + +void gfx_config_load(struct gfx_config *cfg, const wchar_t *filename); diff --git a/gfxhook/d3d11.c b/gfxhook/d3d11.c new file mode 100644 index 0000000..4c94448 --- /dev/null +++ b/gfxhook/d3d11.c @@ -0,0 +1,195 @@ +#include +#include +#include + +#include +#include + +#include "gfxhook/d3d11.h" +#include "gfxhook/gfx.h" +#include "gfxhook/util.h" + +#include "hook/table.h" + +#include "hooklib/dll.h" + +#include "util/dprintf.h" + +typedef HRESULT (WINAPI *D3D11CreateDevice_t)( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *ppFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext); +typedef HRESULT (WINAPI *D3D11CreateDeviceAndSwapChain_t)( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *ppFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, + IDXGISwapChain **ppSwapChain, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext); + +static struct gfx_config gfx_config; +static D3D11CreateDevice_t next_D3D11CreateDevice; +static D3D11CreateDeviceAndSwapChain_t next_D3D11CreateDeviceAndSwapChain; + +static const struct hook_symbol d3d11_hooks[] = { + { + .name = "D3D11CreateDevice", + .patch = D3D11CreateDevice, + .link = (void **) &next_D3D11CreateDevice, + }, { + .name = "D3D11CreateDeviceAndSwapChain", + .patch = D3D11CreateDeviceAndSwapChain, + .link = (void **) &next_D3D11CreateDeviceAndSwapChain, + }, +}; + +void gfx_d3d11_hook_init(const struct gfx_config *cfg, HINSTANCE self) +{ + HMODULE d3d11; + + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "d3d11.dll", d3d11_hooks, _countof(d3d11_hooks)); + + if (next_D3D11CreateDevice == NULL || next_D3D11CreateDeviceAndSwapChain == NULL) { + d3d11 = LoadLibraryW(L"d3d11.dll"); + + if (d3d11 == NULL) { + dprintf("D3D11: d3d11.dll not found or failed initialization\n"); + + goto fail; + } + + if (next_D3D11CreateDevice == NULL) { + next_D3D11CreateDevice = (D3D11CreateDevice_t) GetProcAddress( + d3d11, + "D3D11CreateDevice"); + } + if (next_D3D11CreateDeviceAndSwapChain == NULL) { + next_D3D11CreateDeviceAndSwapChain = (D3D11CreateDeviceAndSwapChain_t) GetProcAddress( + d3d11, + "D3D11CreateDeviceAndSwapChain"); + } + + if (next_D3D11CreateDevice == NULL) { + dprintf("D3D11: D3D11CreateDevice not found in loaded d3d11.dll\n"); + + goto fail; + } + if (next_D3D11CreateDeviceAndSwapChain == NULL) { + dprintf("D3D11: D3D11CreateDeviceAndSwapChain not found in loaded d3d11.dll\n"); + + goto fail; + } + } + + if (self != NULL) { + dll_hook_push(self, L"d3d11.dll"); + } + + return; + +fail: + if (d3d11 != NULL) { + FreeLibrary(d3d11); + } +} + +HRESULT WINAPI D3D11CreateDevice( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *ppFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext) +{ + dprintf("D3D11: D3D11CreateDevice hook hit\n"); + + return next_D3D11CreateDevice( + pAdapter, + DriverType, + Software, + Flags, + ppFeatureLevels, + FeatureLevels, + SDKVersion, + ppDevice, + pFeatureLevel, + ppImmediateContext); +} + +HRESULT WINAPI D3D11CreateDeviceAndSwapChain( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *ppFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, + IDXGISwapChain **ppSwapChain, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext) +{ + DXGI_SWAP_CHAIN_DESC *desc; + HWND hwnd; + UINT width; + UINT height; + + dprintf("D3D11: D3D11CreateDeviceAndSwapChain hook hit\n"); + + desc = (DXGI_SWAP_CHAIN_DESC *) pSwapChainDesc; + + if (desc != NULL) { + desc->Windowed = gfx_config.windowed; + + hwnd = desc->OutputWindow; + width = desc->BufferDesc.Width; + height = desc->BufferDesc.Height; + } else { + hwnd = NULL; + } + + if (hwnd != NULL) { + gfx_util_ensure_win_visible(hwnd); + gfx_util_borderless_fullscreen_windowed(hwnd, width, height); + } + + return next_D3D11CreateDeviceAndSwapChain( + pAdapter, + DriverType, + Software, + Flags, + ppFeatureLevels, + FeatureLevels, + SDKVersion, + pSwapChainDesc, + ppSwapChain, + ppDevice, + pFeatureLevel, + ppImmediateContext); +} + diff --git a/gfxhook/d3d11.h b/gfxhook/d3d11.h new file mode 100644 index 0000000..c7cca9f --- /dev/null +++ b/gfxhook/d3d11.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "gfxhook/gfx.h" + +void gfx_d3d11_hook_init(const struct gfx_config *cfg, HINSTANCE self); diff --git a/gfxhook/d3d9.c b/gfxhook/d3d9.c new file mode 100644 index 0000000..34a165d --- /dev/null +++ b/gfxhook/d3d9.c @@ -0,0 +1,251 @@ +#include +#include + +#include +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "hooklib/dll.h" + +#include "gfxhook/gfx.h" +#include "gfxhook/util.h" + +#include "util/dprintf.h" + +typedef IDirect3D9 * (WINAPI *Direct3DCreate9_t)(UINT sdk_ver); +typedef HRESULT (WINAPI *Direct3DCreate9Ex_t)(UINT sdk_ver, IDirect3D9Ex **d3d9ex); + +static HRESULT STDMETHODCALLTYPE my_IDirect3D9_CreateDevice( + IDirect3D9 *self, + UINT adapter, + D3DDEVTYPE type, + HWND hwnd, + DWORD flags, + D3DPRESENT_PARAMETERS *pp, + IDirect3DDevice9 **pdev); +static HRESULT STDMETHODCALLTYPE my_IDirect3D9Ex_CreateDevice( + IDirect3D9Ex *self, + UINT adapter, + D3DDEVTYPE type, + HWND hwnd, + DWORD flags, + D3DPRESENT_PARAMETERS *pp, + IDirect3DDevice9 **pdev); + +static struct gfx_config gfx_config; +static Direct3DCreate9_t next_Direct3DCreate9; +static Direct3DCreate9Ex_t next_Direct3DCreate9Ex; + +static const struct hook_symbol gfx_hooks[] = { + { + .name = "Direct3DCreate9", + .patch = Direct3DCreate9, + .link = (void **) &next_Direct3DCreate9, + }, { + .name = "Direct3DCreate9Ex", + .patch = Direct3DCreate9Ex, + .link = (void **) &next_Direct3DCreate9Ex, + }, +}; + +void gfx_d3d9_hook_init(const struct gfx_config *cfg, HINSTANCE self) +{ + HMODULE d3d9; + + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "d3d9.dll", gfx_hooks, _countof(gfx_hooks)); + + if (next_Direct3DCreate9 == NULL || next_Direct3DCreate9Ex == NULL) { + d3d9 = LoadLibraryW(L"d3d9.dll"); + + if (d3d9 == NULL) { + dprintf("Gfx: d3d9.dll not found or failed initialization\n"); + + goto fail; + } + + if (next_Direct3DCreate9 == NULL) { + next_Direct3DCreate9 = (Direct3DCreate9_t) GetProcAddress(d3d9, "Direct3DCreate9"); + } + if (next_Direct3DCreate9Ex == NULL) { + next_Direct3DCreate9Ex = (Direct3DCreate9Ex_t) GetProcAddress(d3d9, "Direct3DCreate9Ex"); + } + + if (next_Direct3DCreate9 == NULL) { + dprintf("Gfx: Direct3DCreate9 not found in loaded d3d9.dll\n"); + + goto fail; + } + if (next_Direct3DCreate9Ex == NULL) { + dprintf("Gfx: Direct3DCreate9Ex not found in loaded d3d9.dll\n"); + + goto fail; + } + } + + if (self != NULL) { + dll_hook_push(self, L"d3d9.dll"); + } + + return; + +fail: + if (d3d9 != NULL) { + FreeLibrary(d3d9); + } +} + +IDirect3D9 * WINAPI Direct3DCreate9(UINT sdk_ver) +{ + struct com_proxy *proxy; + IDirect3D9Vtbl *vtbl; + IDirect3D9 *api; + HRESULT hr; + + dprintf("Gfx: Direct3DCreate9 hook hit\n"); + + api = NULL; + + if (next_Direct3DCreate9 == NULL) { + dprintf("Gfx: next_Direct3DCreate9 == NULL\n"); + + goto fail; + } + + api = next_Direct3DCreate9(sdk_ver); + + if (api == NULL) { + dprintf("Gfx: next_Direct3DCreate9 returned NULL\n"); + + goto fail; + } + + hr = com_proxy_wrap(&proxy, api, sizeof(*api->lpVtbl)); + + if (FAILED(hr)) { + dprintf("Gfx: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl = proxy->vptr; + vtbl->CreateDevice = my_IDirect3D9_CreateDevice; + + return (IDirect3D9 *) proxy; + +fail: + if (api != NULL) { + IDirect3D9_Release(api); + } + + return NULL; +} + +HRESULT WINAPI Direct3DCreate9Ex(UINT sdk_ver, IDirect3D9Ex **d3d9ex) +{ + struct com_proxy *proxy; + IDirect3D9ExVtbl *vtbl; + IDirect3D9Ex *api; + HRESULT hr; + + dprintf("Gfx: Direct3DCreate9Ex hook hit\n"); + + api = NULL; + + if (next_Direct3DCreate9Ex == NULL) { + dprintf("Gfx: next_Direct3DCreate9Ex == NULL\n"); + + goto fail; + } + + hr = next_Direct3DCreate9Ex(sdk_ver, d3d9ex); + + if (FAILED(hr)) { + dprintf("Gfx: next_Direct3DCreate9Ex returned %x\n", (int) hr); + + goto fail; + } + + api = *d3d9ex; + hr = com_proxy_wrap(&proxy, api, sizeof(*api->lpVtbl)); + + if (FAILED(hr)) { + dprintf("Gfx: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl = proxy->vptr; + vtbl->CreateDevice = my_IDirect3D9Ex_CreateDevice; + + *d3d9ex = (IDirect3D9Ex *) proxy; + + return S_OK; + +fail: + if (api != NULL) { + IDirect3D9Ex_Release(api); + } + + return hr; +} + +static HRESULT STDMETHODCALLTYPE my_IDirect3D9_CreateDevice( + IDirect3D9 *self, + UINT adapter, + D3DDEVTYPE type, + HWND hwnd, + DWORD flags, + D3DPRESENT_PARAMETERS *pp, + IDirect3DDevice9 **pdev) +{ + struct com_proxy *proxy; + IDirect3D9 *real; + + dprintf("Gfx: IDirect3D9::CreateDevice hook hit\n"); + + proxy = com_proxy_downcast(self); + real = proxy->real; + + if (gfx_config.windowed) { + pp->Windowed = TRUE; + pp->FullScreen_RefreshRateInHz = 0; + } + + if (gfx_config.framed) { + gfx_util_frame_window(hwnd); + } + + dprintf("Gfx: Using adapter %d\n", gfx_config.monitor); + + return IDirect3D9_CreateDevice(real, gfx_config.monitor, type, hwnd, flags, pp, pdev); +} + +static HRESULT STDMETHODCALLTYPE my_IDirect3D9Ex_CreateDevice( + IDirect3D9Ex *self, + UINT adapter, + D3DDEVTYPE type, + HWND hwnd, + DWORD flags, + D3DPRESENT_PARAMETERS *pp, + IDirect3DDevice9 **pdev) +{ + dprintf("Gfx: IDirect3D9Ex::CreateDevice hook forwarding to my_IDirect3D9_CreateDevice\n"); + + return my_IDirect3D9_CreateDevice( + (IDirect3D9 *) self, + adapter, + type, + hwnd, + flags, + pp, + pdev); +} diff --git a/gfxhook/d3d9.h b/gfxhook/d3d9.h new file mode 100644 index 0000000..90acdf4 --- /dev/null +++ b/gfxhook/d3d9.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "gfxhook/gfx.h" + +void gfx_d3d9_hook_init(const struct gfx_config *cfg, HINSTANCE self); diff --git a/gfxhook/dxgi.c b/gfxhook/dxgi.c new file mode 100644 index 0000000..14d3e5f --- /dev/null +++ b/gfxhook/dxgi.c @@ -0,0 +1,364 @@ +#include +#include +#include + +#include +#include + +#include "gfxhook/dxgi.h" +#include "gfxhook/gfx.h" + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "hooklib/dll.h" + +#include "util/dprintf.h" + +typedef HRESULT (WINAPI *CreateDXGIFactory_t)(REFIID riid, void **factory); +typedef HRESULT (WINAPI *CreateDXGIFactory1_t)(REFIID riid, void **factory); +typedef HRESULT (WINAPI *CreateDXGIFactory2_t)( + UINT flags, + REFIID riid, + void **factory); + +static HRESULT hook_factory(REFIID riid, void **factory); + +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory_CreateSwapChain( + IDXGIFactory *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain); +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory1_CreateSwapChain( + IDXGIFactory1 *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain); +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory2_CreateSwapChain( + IDXGIFactory2 *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain); + +static struct gfx_config gfx_config; +static CreateDXGIFactory_t next_CreateDXGIFactory; +static CreateDXGIFactory1_t next_CreateDXGIFactory1; +static CreateDXGIFactory2_t next_CreateDXGIFactory2; + +static const struct hook_symbol dxgi_hooks[] = { + { + .name = "CreateDXGIFactory", + .patch = CreateDXGIFactory, + .link = (void **) &next_CreateDXGIFactory, + }, { + .name = "CreateDXGIFactory1", + .patch = CreateDXGIFactory1, + .link = (void **) &next_CreateDXGIFactory1, + }, { + .name = "CreateDXGIFactory2", + .patch = CreateDXGIFactory2, + .link = (void **) &next_CreateDXGIFactory2, + }, +}; + +void gfx_dxgi_hook_init(const struct gfx_config *cfg, HINSTANCE self) +{ + HMODULE dxgi; + + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "dxgi.dll", dxgi_hooks, _countof(dxgi_hooks)); + + if (next_CreateDXGIFactory == NULL || next_CreateDXGIFactory1 == NULL) { + dxgi = LoadLibraryW(L"dxgi.dll"); + + if (dxgi == NULL) { + dprintf("DXGI: dxgi.dll not found or failed initialization\n"); + + goto fail; + } + + if (next_CreateDXGIFactory == NULL) { + next_CreateDXGIFactory = (CreateDXGIFactory_t) GetProcAddress( + dxgi, + "CreateDXGIFactory"); + } + if (next_CreateDXGIFactory1 == NULL) { + next_CreateDXGIFactory1 = (CreateDXGIFactory1_t) GetProcAddress( + dxgi, + "CreateDXGIFactory1"); + } + if (next_CreateDXGIFactory2 == NULL) { + next_CreateDXGIFactory2 = (CreateDXGIFactory2_t) GetProcAddress( + dxgi, + "CreateDXGIFactory2"); + } + + if (next_CreateDXGIFactory == NULL) { + dprintf("DXGI: CreateDXGIFactory not found in loaded dxgi.dll\n"); + + goto fail; + } + if (next_CreateDXGIFactory1 == NULL) { + dprintf("DXGI: CreateDXGIFactory1 not found in loaded dxgi.dll\n"); + + goto fail; + } + + /* `CreateDXGIFactory2` was introduced in Windows 8.1 and the original + * Nu runs Windows 8, so do not require it to exist */ + } + + if (self != NULL) { + dll_hook_push(self, L"dxgi.dll"); + } + + return; + +fail: + if (dxgi != NULL) { + FreeLibrary(dxgi); + } +} + +HRESULT WINAPI CreateDXGIFactory(REFIID riid, void **factory) +{ + HRESULT hr; + + dprintf("DXGI: CreateDXGIFactory hook hit\n"); + + hr = next_CreateDXGIFactory(riid, factory); + + if (FAILED(hr)) { + dprintf("DXGI: CreateDXGIFactory returned %x\n", (int) hr); + + return hr; + } + + hr = hook_factory(riid, factory); + + if (FAILED(hr)) { + return hr; + } + + return hr; +} + +HRESULT WINAPI CreateDXGIFactory1(REFIID riid, void **factory) +{ + HRESULT hr; + + dprintf("DXGI: CreateDXGIFactory1 hook hit\n"); + + hr = next_CreateDXGIFactory1(riid, factory); + + if (FAILED(hr)) { + dprintf("DXGI: CreateDXGIFactory1 returned %x\n", (int) hr); + + return hr; + } + + hr = hook_factory(riid, factory); + + if (FAILED(hr)) { + return hr; + } + + return hr; +} + +HRESULT WINAPI CreateDXGIFactory2(UINT flags, REFIID riid, void **factory) +{ + HRESULT hr; + + dprintf("DXGI: CreateDXGIFactory2 hook hit\n"); + + if (next_CreateDXGIFactory2 == NULL) { + dprintf("DXGI: CreateDXGIFactory2 not available, forwarding to CreateDXGIFactory1\n"); + + return CreateDXGIFactory1(riid, factory); + } + + hr = next_CreateDXGIFactory2(flags, riid, factory); + + if (FAILED(hr)) { + dprintf("DXGI: CreateDXGIFactory2 returned %x\n", (int) hr); + + return hr; + } + + hr = hook_factory(riid, factory); + + if (FAILED(hr)) { + return hr; + } + + return hr; +} + +static HRESULT hook_factory(REFIID riid, void **factory) +{ + struct com_proxy *proxy; + IDXGIFactory *api_0; + IDXGIFactory1 *api_1; + IDXGIFactory2 *api_2; + IDXGIFactoryVtbl *vtbl_0; + IDXGIFactory1Vtbl *vtbl_1; + IDXGIFactory2Vtbl *vtbl_2; + HRESULT hr; + + api_0 = NULL; + api_1 = NULL; + api_2 = NULL; + + if (memcmp(riid, &IID_IDXGIFactory, sizeof(*riid)) == 0) { + api_0 = *factory; + hr = com_proxy_wrap(&proxy, api_0, sizeof(*api_0->lpVtbl)); + + if (FAILED(hr)) { + dprintf("DXGI: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl_0 = proxy->vptr; + vtbl_0->CreateSwapChain = my_IDXGIFactory_CreateSwapChain; + + *factory = proxy; + } else if (memcmp(riid, &IID_IDXGIFactory1, sizeof(*riid)) == 0) { + api_1 = *factory; + hr = com_proxy_wrap(&proxy, api_1, sizeof(*api_1->lpVtbl)); + + if (FAILED(hr)) { + dprintf("DXGI: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl_1 = proxy->vptr; + vtbl_1->CreateSwapChain = my_IDXGIFactory1_CreateSwapChain; + + *factory = proxy; + } else if (memcmp(riid, &IID_IDXGIFactory2, sizeof(*riid)) == 0) { + api_2 = *factory; + hr = com_proxy_wrap(&proxy, api_2, sizeof(*api_2->lpVtbl)); + + if (FAILED(hr)) { + dprintf("DXGI: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl_2 = proxy->vptr; + vtbl_2->CreateSwapChain = my_IDXGIFactory2_CreateSwapChain; + + *factory = proxy; + } + + return S_OK; + +fail: + if (api_0 != NULL) { + IDXGIFactory_Release(api_0); + } + if (api_1 != NULL) { + IDXGIFactory1_Release(api_1); + } + if (api_2 != NULL) { + IDXGIFactory2_Release(api_2); + } + + return hr; +} + +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory_CreateSwapChain( + IDXGIFactory *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain) +{ + struct com_proxy *proxy; + IDXGIFactory *real; + HWND hwnd; + UINT width; + UINT height; + + dprintf("DXGI: IDXGIFactory::CreateSwapChain hook hit\n"); + + proxy = com_proxy_downcast(self); + real = proxy->real; + + if (desc != NULL) { + desc->Windowed = gfx_config.windowed; + + hwnd = desc->OutputWindow; + width = desc->BufferDesc.Width; + height = desc->BufferDesc.Height; + } else { + hwnd = NULL; + } + + if (hwnd != NULL) { + /* + * Ensure window is maximized to avoid a Windows 10 issue where a + * fullscreen swap chain is not created because the window is minimized + * at the time of creation. + */ + ShowWindow(hwnd, SW_RESTORE); + + if (!gfx_config.framed && width > 0 && height > 0) { + dprintf("DXGI: Resizing window to %ux%u\n", width, height); + + SetWindowLongPtrW(hwnd, GWL_STYLE, WS_POPUP); + SetWindowLongPtrW(hwnd, GWL_EXSTYLE, WS_EX_TOPMOST); + + SetWindowPos( + hwnd, + HWND_TOP, + 0, + 0, + (int) width, + (int) height, + SWP_FRAMECHANGED | SWP_NOSENDCHANGING); + + ShowWindow(hwnd, SW_SHOWMAXIMIZED); + } + } + + return IDXGIFactory_CreateSwapChain(real, device, desc, swapchain); +} + +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory1_CreateSwapChain( + IDXGIFactory1 *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain) +{ + dprintf("DXGI: IDXGIFactory1::CreateSwapChain hook forwarding to my_IDXGIFactory_CreateSwapChain\n"); + + return my_IDXGIFactory_CreateSwapChain( + (IDXGIFactory *) self, + device, + desc, + swapchain); +} + +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory2_CreateSwapChain( + IDXGIFactory2 *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain) +{ + dprintf("DXGI: IDXGIFactory2::CreateSwapChain hook forwarding to my_IDXGIFactory_CreateSwapChain\n"); + + return my_IDXGIFactory_CreateSwapChain( + (IDXGIFactory *) self, + device, + desc, + swapchain); +} diff --git a/gfxhook/dxgi.h b/gfxhook/dxgi.h new file mode 100644 index 0000000..1834300 --- /dev/null +++ b/gfxhook/dxgi.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "gfxhook/gfx.h" + +void gfx_dxgi_hook_init(const struct gfx_config *cfg, HINSTANCE self); diff --git a/gfxhook/gfx.c b/gfxhook/gfx.c new file mode 100644 index 0000000..1af3647 --- /dev/null +++ b/gfxhook/gfx.c @@ -0,0 +1,48 @@ +#include + +#include +#include + +#include "gfxhook/gfx.h" + +#include "hook/table.h" + +#include "util/dprintf.h" + +typedef BOOL (WINAPI *ShowWindow_t)(HWND hWnd, int nCmdShow); + +static BOOL WINAPI hook_ShowWindow(HWND hWnd, int nCmdShow); + +static struct gfx_config gfx_config; +static ShowWindow_t next_ShowWindow; + +static const struct hook_symbol gfx_hooks[] = { + { + .name = "ShowWindow", + .patch = hook_ShowWindow, + .link = (void **) &next_ShowWindow, + }, +}; + +void gfx_hook_init(const struct gfx_config *cfg) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "user32.dll", gfx_hooks, _countof(gfx_hooks)); +} + +static BOOL WINAPI hook_ShowWindow(HWND hWnd, int nCmdShow) +{ + dprintf("Gfx: ShowWindow hook hit\n"); + + if (!gfx_config.framed && nCmdShow == SW_RESTORE) { + nCmdShow = SW_SHOW; + } + + return next_ShowWindow(hWnd, nCmdShow); +} diff --git a/gfxhook/gfx.h b/gfxhook/gfx.h new file mode 100644 index 0000000..9a7e27c --- /dev/null +++ b/gfxhook/gfx.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +struct gfx_config { + bool enable; + bool windowed; + bool framed; + int monitor; +}; + +void gfx_hook_init(const struct gfx_config *cfg); diff --git a/gfxhook/meson.build b/gfxhook/meson.build new file mode 100644 index 0000000..b973ddd --- /dev/null +++ b/gfxhook/meson.build @@ -0,0 +1,28 @@ +gfxhook_lib = static_library( + 'gfxhook', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + dxguid_lib, + ], + link_with : [ + hooklib_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'd3d9.c', + 'd3d9.h', + 'd3d11.c', + 'd3d11.h', + 'dxgi.c', + 'dxgi.h', + 'gfx.c', + 'gfx.h', + 'util.c', + 'util.h', + ], +) diff --git a/gfxhook/util.c b/gfxhook/util.c new file mode 100644 index 0000000..1ee552d --- /dev/null +++ b/gfxhook/util.c @@ -0,0 +1,116 @@ +#include + +#include "gfxhook/util.h" + +#include "util/dprintf.h" + +void gfx_util_ensure_win_visible(HWND hwnd) +{ + /* + * Ensure window is maximized to avoid a Windows 10 issue where a + * fullscreen swap chain is not created because the window is minimized + * at the time of creation. + */ + ShowWindow(hwnd, SW_RESTORE); +} + +void gfx_util_borderless_fullscreen_windowed(HWND hwnd, UINT width, UINT height) +{ + BOOL ok; + HRESULT hr; + + dprintf("Gfx: Resizing window to %ux%u\n", width, height); + + SetWindowLongPtrW(hwnd, GWL_STYLE, WS_POPUP); + SetWindowLongPtrW(hwnd, GWL_EXSTYLE, WS_EX_TOPMOST); + + ok = SetWindowPos( + hwnd, + HWND_TOP, + 0, + 0, + (int) width, + (int) height, + SWP_FRAMECHANGED | SWP_NOSENDCHANGING); + + if (!ok) { + /* come on... */ + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: SetWindowPos failed: %x\n", (int) hr); + + return; + } + + ok = ShowWindow(hwnd, SW_SHOWMAXIMIZED); + + if (!ok) { + /* come on... */ + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: ShowWindow failed: %x\n", (int) hr); + + return; + } +} + +HRESULT gfx_util_frame_window(HWND hwnd) +{ + HRESULT hr; + DWORD error; + LONG style; + RECT rect; + BOOL ok; + + SetLastError(ERROR_SUCCESS); + style = GetWindowLongW(hwnd, GWL_STYLE); + error = GetLastError(); + + if (error != ERROR_SUCCESS) { + hr = HRESULT_FROM_WIN32(error); + dprintf("Gfx: GetWindowLongPtrW(%p, GWL_STYLE) failed: %x\n", + hwnd, + (int) hr); + + return hr; + } + + ok = GetClientRect(hwnd, &rect); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: GetClientRect(%p) failed: %x\n", hwnd, (int) hr); + + return hr; + } + + style |= WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU; + ok = AdjustWindowRect(&rect, style, FALSE); + + if (!ok) { + /* come on... */ + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: AdjustWindowRect failed: %x\n", (int) hr); + + return hr; + } + + /* This... always seems to set an error, even though it works? idk */ + SetWindowLongW(hwnd, GWL_STYLE, style); + + ok = SetWindowPos( + hwnd, + HWND_TOP, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + SWP_FRAMECHANGED | SWP_NOMOVE); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: SetWindowPos(%p) failed: %x\n", hwnd, (int) hr); + + return hr; + } + + return S_OK; +} diff --git a/gfxhook/util.h b/gfxhook/util.h new file mode 100644 index 0000000..d0c2401 --- /dev/null +++ b/gfxhook/util.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +void gfx_util_ensure_win_visible(HWND hwnd); +void gfx_util_borderless_fullscreen_windowed(HWND hwnd, UINT width, UINT height); +HRESULT gfx_util_frame_window(HWND hwnd); diff --git a/hooklib/config.c b/hooklib/config.c new file mode 100644 index 0000000..5fc9383 --- /dev/null +++ b/hooklib/config.c @@ -0,0 +1,16 @@ +#include + +#include +#include +#include + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"dvd", L"enable", 1, filename); +} diff --git a/hooklib/config.h b/hooklib/config.h new file mode 100644 index 0000000..5da045d --- /dev/null +++ b/hooklib/config.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "hooklib/dvd.h" + +void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename); diff --git a/hooklib/createprocess.c b/hooklib/createprocess.c new file mode 100644 index 0000000..e44ebdb --- /dev/null +++ b/hooklib/createprocess.c @@ -0,0 +1,257 @@ +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/createprocess.h" + +#include "util/dprintf.h" + +void createprocess_hook_init(); +static BOOL WINAPI my_CreateProcessA( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); +BOOL my_CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); + +static BOOL (WINAPI *next_CreateProcessA)( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); + +static BOOL (WINAPI *next_CreateProcessW)( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); + +static const struct hook_symbol win32_hooks[] = { + { + .name = "CreateProcessA", + .patch = my_CreateProcessA, + .link = (void **) &next_CreateProcessA + }, + { + .name = "CreateProcessW", + .patch = my_CreateProcessW, + .link = (void **) &next_CreateProcessW + }, +}; + +static bool did_init = false; + +static struct process_hook_sym_w *process_syms_w; +static struct process_hook_sym_a *process_syms_a; + +static size_t process_nsyms_a = 0; +static size_t process_nsyms_w = 0; + +static CRITICAL_SECTION createproc_lock; + +HRESULT createprocess_push_hook_w(const wchar_t *name, const wchar_t *head, const wchar_t *tail, bool replace_all) { + struct process_hook_sym_w *new_mem; + struct process_hook_sym_w *new_proc; + HRESULT hr; + + assert(name != NULL); + assert(head != NULL); + + createprocess_hook_init(); + EnterCriticalSection(&createproc_lock); + + new_mem = realloc( + process_syms_w, + (process_nsyms_w + 1) * sizeof(struct process_hook_sym_w)); + + if (new_mem == NULL) { + + LeaveCriticalSection(&createproc_lock); + return E_OUTOFMEMORY; + } + + new_proc = &new_mem[process_nsyms_w]; + memset(new_proc, 0, sizeof(*new_proc)); + new_proc->name = name; + new_proc->head = head; + new_proc->tail = tail; + new_proc->replace_all = replace_all; + + process_syms_w = new_mem; + process_nsyms_w++; + + LeaveCriticalSection(&createproc_lock); + return S_OK; +} + +HRESULT createprocess_push_hook_a(const char *name, const char *head, const char *tail, bool replace_all) { + struct process_hook_sym_a *new_mem; + struct process_hook_sym_a *new_proc; + + assert(name != NULL); + assert(head != NULL); + + createprocess_hook_init(); + + EnterCriticalSection(&createproc_lock); + + new_mem = realloc( + process_syms_a, + (process_nsyms_a + 1) * sizeof(struct process_hook_sym_a)); + + if (new_mem == NULL) { + + LeaveCriticalSection(&createproc_lock); + return E_OUTOFMEMORY; + } + + new_proc = &new_mem[process_nsyms_a]; + memset(new_proc, 0, sizeof(*new_proc)); + new_proc->name = name; + new_proc->head = head; + new_proc->tail = tail; + new_proc->replace_all = replace_all; + + process_syms_a = new_mem; + process_nsyms_a++; + + LeaveCriticalSection(&createproc_lock); + return S_OK; +} + +void createprocess_hook_init() { + if (did_init) { + return; + } + did_init = true; + + hook_table_apply( + NULL, + "kernel32.dll", + win32_hooks, + _countof(win32_hooks)); + InitializeCriticalSection(&createproc_lock); + dprintf("CreateProcess: Init\n"); +} + + +static BOOL WINAPI my_CreateProcessA( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +) +{ + for (int i = 0; i < process_nsyms_a; i++) { + if (strncmp(process_syms_a[i].name, lpCommandLine, strlen(process_syms_a[i].name))) { + continue; + } + + dprintf("CreateProcess: Hooking child process %s %s\n", lpApplicationName, lpCommandLine); + char new_cmd[MAX_PATH] = {0}; + strcat_s(new_cmd, MAX_PATH, process_syms_a[i].head); + + if (!process_syms_a[i].replace_all) { + strcat_s(new_cmd, MAX_PATH, lpCommandLine); + } + + if (process_syms_a[i].tail != NULL) { + strcat_s(new_cmd, MAX_PATH, process_syms_a[i].tail); + } + + dprintf("CreateProcess: Replaced CreateProcessA %s\n", new_cmd); + return next_CreateProcessA( + lpApplicationName, + new_cmd, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); + } + return next_CreateProcessA( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); +} + +BOOL my_CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) +{ + return next_CreateProcessW( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); +} \ No newline at end of file diff --git a/hooklib/createprocess.h b/hooklib/createprocess.h new file mode 100644 index 0000000..bf226d5 --- /dev/null +++ b/hooklib/createprocess.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +HRESULT createprocess_push_hook_w(const wchar_t *name, const wchar_t *head, const wchar_t *tail, bool replace_all); +HRESULT createprocess_push_hook_a(const char *name, const char *head, const char *tail, bool replace_all); + +struct process_hook_sym_w { + const wchar_t *name; + const wchar_t *head; + const wchar_t *tail; + bool replace_all; +}; + +struct process_hook_sym_a { + const char *name; + const char *head; + const char *tail; + bool replace_all; +}; \ No newline at end of file diff --git a/hooklib/cursor.c b/hooklib/cursor.c new file mode 100644 index 0000000..8c66412 --- /dev/null +++ b/hooklib/cursor.c @@ -0,0 +1,73 @@ +#include + +#include +#include +#include +#include + +#include "hook/table.h" +#include "util/dprintf.h" + +static HCURSOR my_SetCursor(HCURSOR hCursor); +static HCURSOR (*next_SetCursor)(HCURSOR hCursor); +static BOOL my_SetCursorPos(int x, int y); +static BOOL my_SetPhysicalCursorPos(int x, int y); +static int my_ShowCursor(BOOL bShow); + +static const struct hook_symbol cursor_syms[] = { + { + .name = "SetCursor", + .patch = my_SetCursor, + .link = (void **) &next_SetCursor + },/*{ + .name = "SetCursorPos", + .patch = my_SetCursorPos, + },*/ { + .name = "SetPhysicalCursorPos", + .patch = my_SetPhysicalCursorPos + }, /*{ + .name = "ShowCursor", + .patch = my_ShowCursor + }*/ +}; + +void cursor_hook_init() +{ + hook_table_apply( + NULL, + "user32.dll", + cursor_syms, + _countof(cursor_syms)); + + dprintf("Cursor: Init\n"); +} + +static BOOL my_SetCursorPos(int x, int y) +{ + dprintf("my_SetCursorPos Hit! x %d y %d\n", x, y); + return true; +} + +static BOOL my_SetPhysicalCursorPos(int x, int y) +{ + dprintf("my_SetPhysicalCursorPos Hit! x %d y %d\n", x, y); + return true; +} + +static int my_ShowCursor(BOOL bShow) +{ + dprintf("my_ShowCursor Hit!\n"); + return 0; +} + +static HCURSOR my_SetCursor(HCURSOR hCursor) +{ + dprintf("my_SetCursor Hit!\n"); + HCURSOR fake_cursor; + + if ( hCursor ) + return next_SetCursor(hCursor); + fake_cursor = LoadCursorA(0, (LPCSTR)0x7F00); + next_SetCursor(fake_cursor); + return 0; +} diff --git a/hooklib/cursor.h b/hooklib/cursor.h new file mode 100644 index 0000000..de0461a --- /dev/null +++ b/hooklib/cursor.h @@ -0,0 +1,3 @@ +#pragma once + +void cursor_hook_init(); \ No newline at end of file diff --git a/hooklib/dll.c b/hooklib/dll.c new file mode 100644 index 0000000..9400bb9 --- /dev/null +++ b/hooklib/dll.c @@ -0,0 +1,351 @@ +/* This is general enough to break out into capnhook eventually. + Don't introduce util/ dependencies here. */ + +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/dll.h" + +struct dll_hook_reg { + const wchar_t *name; + HMODULE redir_mod; +}; + +/* Helper functions */ + +static void dll_hook_init(void); +static HMODULE dll_hook_search_dll(const wchar_t *name); + +/* Hook functions */ + +static BOOL WINAPI hook_FreeLibrary(HMODULE mod); +static HMODULE WINAPI hook_GetModuleHandleA(const char *name); +static HMODULE WINAPI hook_GetModuleHandleW(const wchar_t *name); +static HMODULE WINAPI hook_LoadLibraryA(const char *name); +static HMODULE WINAPI hook_LoadLibraryW(const wchar_t *name); +static HMODULE WINAPI hook_LoadLibraryExA(const char *name, HANDLE file, DWORD flags); +static HMODULE WINAPI hook_LoadLibraryExW(const wchar_t *name, HANDLE file, DWORD flags); + +/* Link pointers */ + +static BOOL (WINAPI *next_FreeLibrary)(HMODULE mod); +static HMODULE (WINAPI *next_GetModuleHandleA)(const char *name); +static HMODULE (WINAPI *next_GetModuleHandleW)(const wchar_t *name); +static HMODULE (WINAPI *next_LoadLibraryA)(const char *name); +static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name); +static HMODULE (WINAPI *next_LoadLibraryExA)(const char *name, HANDLE file, DWORD flags); +static HMODULE (WINAPI *next_LoadLibraryExW)(const wchar_t *name, HANDLE file, DWORD flags); + +static const struct hook_symbol dll_loader_syms[] = { + { + .name = "FreeLibrary", + .patch = hook_FreeLibrary, + .link = (void **) &next_FreeLibrary, + }, { + .name = "GetModuleHandleA", + .patch = hook_GetModuleHandleA, + .link = (void **) &next_GetModuleHandleA, + }, { + .name = "GetModuleHandleW", + .patch = hook_GetModuleHandleW, + .link = (void **) &next_GetModuleHandleW, + }, { + .name = "LoadLibraryA", + .patch = hook_LoadLibraryA, + .link = (void **) &next_LoadLibraryA, + }, { + .name = "LoadLibraryW", + .patch = hook_LoadLibraryW, + .link = (void **) &next_LoadLibraryW, + }, { + .name = "LoadLibraryExA", + .patch = hook_LoadLibraryExA, + .link = (void **) &next_LoadLibraryExA, + }, { + .name = "LoadLibraryExW", + .patch = hook_LoadLibraryExW, + .link = (void **) &next_LoadLibraryExW, + } +}; + +static bool dll_hook_initted; +static CRITICAL_SECTION dll_hook_lock; +static struct dll_hook_reg *dll_hook_list; +static size_t dll_hook_count; + +HRESULT dll_hook_push( + HMODULE redir_mod, + const wchar_t *name) +{ + struct dll_hook_reg *new_item; + struct dll_hook_reg *new_mem; + HRESULT hr; + + assert(name != NULL); + + dll_hook_init(); + + EnterCriticalSection(&dll_hook_lock); + + new_mem = realloc( + dll_hook_list, + (dll_hook_count + 1) * sizeof(struct dll_hook_reg)); + + if (new_mem == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + new_item = &new_mem[dll_hook_count]; + new_item->name = name; + new_item->redir_mod = redir_mod; + + dll_hook_list = new_mem; + dll_hook_count++; + hr = S_OK; + +end: + LeaveCriticalSection(&dll_hook_lock); + + return hr; +} + +static void dll_hook_init(void) +{ + HMODULE kernel32; + + /* Init is not thread safe, because API hooking is not thread safe. */ + + if (dll_hook_initted) { + return; + } + + dll_hook_initted = true; + InitializeCriticalSection(&dll_hook_lock); + + /* Protect against the (probably impossible) scenario where nothing in the + process imports LoadLibraryW but something imports LoadLibraryA. Also + do the same with LoadLibraryExW. + + We know something imports GetModuleHandleW because we do, right here. + + (we're about to hook these APIs of course, so we have to set this up + before the hooks go in) */ + + kernel32 = GetModuleHandleW(L"kernel32.dll"); + next_LoadLibraryW = (void *) GetProcAddress(kernel32, "LoadLibraryW"); + next_LoadLibraryExW = (void *) GetProcAddress(kernel32, "LoadLibraryExW"); + + /* Now we can apply the hook table */ + + hook_table_apply( + NULL, + "kernel32.dll", + dll_loader_syms, + _countof(dll_loader_syms)); +} + +static HMODULE dll_hook_search_dll(const wchar_t *name) +{ + HMODULE result; + size_t i; + + result = NULL; + + EnterCriticalSection(&dll_hook_lock); + + for (i = 0 ; i < dll_hook_count ; i++) { + if (wcsicmp(name, dll_hook_list[i].name) == 0) { + result = dll_hook_list[i].redir_mod; + + break; + } + } + + LeaveCriticalSection(&dll_hook_lock); + + return result; +} + +static BOOL WINAPI hook_FreeLibrary(HMODULE mod) +{ + bool match; + size_t i; + + match = false; + EnterCriticalSection(&dll_hook_lock); + + for (i = 0 ; i < dll_hook_count ; i++) { + if (mod == dll_hook_list[i].redir_mod) { + match = true; + + break; + } + } + + LeaveCriticalSection(&dll_hook_lock); + + if (match) { + /* Block attempts to unload redirected modules, since this could cause + a hook DLL to unexpectedly vanish and crash the whole application. + + Reference counting might be another solution, although it is + possible that a buggy application might cause a hook DLL unload in + that case. */ + SetLastError(ERROR_SUCCESS); + + return TRUE; + } + + return next_FreeLibrary(mod); +} + +static HMODULE WINAPI hook_GetModuleHandleA(const char *name) +{ + HMODULE result; + wchar_t *name_w; + size_t name_c; + + if (name == NULL) { + return next_GetModuleHandleA(NULL); + } + + mbstowcs_s(&name_c, NULL, 0, name, 0); + name_w = malloc(name_c * sizeof(wchar_t)); + + if (name_w == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + + return NULL; + } + + mbstowcs_s(NULL, name_w, name_c, name, name_c - 1); + result = hook_GetModuleHandleW(name_w); + free(name_w); + + return result; +} + +static HMODULE WINAPI hook_GetModuleHandleW(const wchar_t *name) +{ + HMODULE result; + + if (name == NULL) { + return next_GetModuleHandleW(NULL); + } + + result = dll_hook_search_dll(name); + + if (result != NULL) { + SetLastError(ERROR_SUCCESS); + } else { + result = next_GetModuleHandleW(name); + } + + return result; +} + +static HMODULE WINAPI hook_LoadLibraryA(const char *name) +{ + HMODULE result; + wchar_t *name_w; + size_t name_c; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + mbstowcs_s(&name_c, NULL, 0, name, 0); + name_w = malloc(name_c * sizeof(wchar_t)); + + if (name_w == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + + return NULL; + } + + mbstowcs_s(NULL, name_w, name_c, name, name_c - 1); + result = hook_LoadLibraryW(name_w); + free(name_w); + + return result; +} + +static HMODULE WINAPI hook_LoadLibraryW(const wchar_t *name) +{ + HMODULE result; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + result = dll_hook_search_dll(name); + + if (result != NULL) { + SetLastError(ERROR_SUCCESS); + } else { + result = next_LoadLibraryW(name); + } + + return result; +} + +static HMODULE WINAPI hook_LoadLibraryExA(const char *name, HANDLE file, DWORD flags) +{ + HMODULE result; + wchar_t *name_w; + size_t name_c; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + mbstowcs_s(&name_c, NULL, 0, name, 0); + name_w = malloc(name_c * sizeof(wchar_t)); + + if (name_w == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + + return NULL; + } + + mbstowcs_s(NULL, name_w, name_c, name, name_c - 1); + result = hook_LoadLibraryExW(name_w, file, flags); + free(name_w); + + return result; +} + +static HMODULE WINAPI hook_LoadLibraryExW(const wchar_t *name, HANDLE file, DWORD flags) +{ + HMODULE result; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + result = dll_hook_search_dll(name); + + if (result != NULL) { + SetLastError(ERROR_SUCCESS); + } else { + result = next_LoadLibraryExW(name, file, flags); + } + + return result; +} diff --git a/hooklib/dll.h b/hooklib/dll.h new file mode 100644 index 0000000..b4fc1b7 --- /dev/null +++ b/hooklib/dll.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include +#include + +HRESULT dll_hook_push( + HMODULE redir_mod, + const wchar_t *name); diff --git a/hooklib/dns.c b/hooklib/dns.c new file mode 100644 index 0000000..aac90ce --- /dev/null +++ b/hooklib/dns.c @@ -0,0 +1,462 @@ +/* Might push this to capnhook, don't add any util dependencies. */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "hook/hr.h" +#include "hook/table.h" + +#include "hooklib/dns.h" + +/* Latest w32headers does not include DnsQueryEx, so we'll have to "polyfill" + its associated data types here for the time being. + + Results and cancel handle are passed through, so we'll just use void + pointers for those args. So are most of the fields in this structure, for + that matter. */ + +typedef struct POLYFILL_DNS_QUERY_REQUEST { + ULONG Version; + PCWSTR QueryName; + WORD QueryType; + ULONG64 QueryOptions; + void* pDnsServerList; + ULONG InterfaceIndex; + void* pQueryCompletionCallback; + PVOID pQueryContext; +} POLYFILL_DNS_QUERY_REQUEST; + +struct dns_hook_entry { + wchar_t *from; + wchar_t *to; +}; + +/* Hook funcs */ + +static DNS_STATUS WINAPI hook_DnsQuery_A( + const char *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved); + +static DNS_STATUS WINAPI hook_DnsQuery_W( + const wchar_t *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved); + +static DNS_STATUS WINAPI hook_DnsQueryEx( + POLYFILL_DNS_QUERY_REQUEST *pRequest, + void *pQueryResults, + void *pCancelHandle); + +static int WSAAPI hook_getaddrinfo( + const char *pNodeName, + const char *pServiceName, + const ADDRINFOA *pHints, + ADDRINFOA **ppResult); + +/* Link pointers */ + +static DNS_STATUS (WINAPI *next_DnsQuery_A)( + const char *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved); + +static DNS_STATUS (WINAPI *next_DnsQuery_W)( + const wchar_t *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved); + +static DNS_STATUS (WINAPI *next_DnsQueryEx)( + POLYFILL_DNS_QUERY_REQUEST *pRequest, + void *pQueryResults, + void *pCancelHandle); + +static int (WSAAPI *next_getaddrinfo)( + const char *pNodeName, + const char *pServiceName, + const ADDRINFOA *pHints, + ADDRINFOA **ppResult); + +static const struct hook_symbol dns_hook_syms_dnsapi[] = { + { + .name = "DnsQuery_A", + .patch = hook_DnsQuery_A, + .link = (void **) &next_DnsQuery_A, + }, { + .name = "DnsQuery_W", + .patch = hook_DnsQuery_W, + .link = (void **) &next_DnsQuery_W, + }, { + .name = "DnsQueryEx", + .patch = hook_DnsQueryEx, + .link = (void **) &next_DnsQueryEx, + } +}; + +static const struct hook_symbol dns_hook_syms_ws2[] = { + { + .name = "getaddrinfo", + .ordinal = 176, + .patch = hook_getaddrinfo, + .link = (void **) &next_getaddrinfo, + } +}; + +static bool dns_hook_initted; +static CRITICAL_SECTION dns_hook_lock; +static struct dns_hook_entry *dns_hook_entries; +static size_t dns_hook_nentries; + +static void dns_hook_init(void) +{ + if (dns_hook_initted) { + return; + } + + dns_hook_initted = true; + InitializeCriticalSection(&dns_hook_lock); + + hook_table_apply( + NULL, + "dnsapi.dll", + dns_hook_syms_dnsapi, + _countof(dns_hook_syms_dnsapi)); + + hook_table_apply( + NULL, + "ws2_32.dll", + dns_hook_syms_ws2, + _countof(dns_hook_syms_ws2)); +} + +HRESULT dns_hook_push(const wchar_t *from_src, const wchar_t *to_src) +{ + HRESULT hr; + struct dns_hook_entry *newmem; + struct dns_hook_entry *newitem; + wchar_t *from; + wchar_t *to; + + assert(from_src != NULL); + + to = NULL; + from = NULL; + dns_hook_init(); + + EnterCriticalSection(&dns_hook_lock); + + from = _wcsdup(from_src); + + if (from == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + if(to_src != NULL) { + to = _wcsdup(to_src); + + if (to == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + } + + newmem = realloc( + dns_hook_entries, + (dns_hook_nentries + 1) * sizeof(struct dns_hook_entry)); + + if (newmem == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + dns_hook_entries = newmem; + newitem = &newmem[dns_hook_nentries++]; + newitem->from = from; + newitem->to = to; + + from = NULL; + to = NULL; + hr = S_OK; + +end: + LeaveCriticalSection(&dns_hook_lock); + + free(to); + free(from); + + return hr; +} + +static DNS_STATUS WINAPI hook_DnsQuery_A( + const char *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved) +{ + const struct dns_hook_entry *pos; + size_t i; + size_t wstr_c; + wchar_t *wstr; + size_t str_c; + char *str; + DNS_STATUS code; + HRESULT hr; + + wstr = NULL; + str = NULL; + + if (pszName == NULL) { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + + goto end; + } + + mbstowcs_s(&wstr_c, NULL, 0, pszName, 0); + wstr = malloc(wstr_c * sizeof(wchar_t)); + + if (wstr == NULL) { + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + + goto end; + } + + mbstowcs_s(NULL, wstr, wstr_c, pszName, wstr_c - 1); + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (_wcsicmp(wstr, pos->from) == 0) { + if(pos->to == NULL) { + LeaveCriticalSection(&dns_hook_lock); + hr = HRESULT_FROM_WIN32(DNS_ERROR_RCODE_NAME_ERROR); + + goto end; + } + + wcstombs_s(&str_c, NULL, 0, pos->to, 0); + str = malloc(str_c * sizeof(char)); + + if (str == NULL) { + LeaveCriticalSection(&dns_hook_lock); + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + + goto end; + } + + wcstombs_s(NULL, str, str_c, pos->to, str_c - 1); + pszName = str; + + break; + } + } + + LeaveCriticalSection(&dns_hook_lock); + + code = next_DnsQuery_A( + pszName, + wType, + Options, + pExtra, + ppQueryResults, + pReserved); + + hr = HRESULT_FROM_WIN32(code); + +end: + free(str); + free(wstr); + + return hr_to_win32_error(hr); +} + +static DNS_STATUS WINAPI hook_DnsQuery_W( + const wchar_t *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved) +{ + const struct dns_hook_entry *pos; + size_t i; + + if (pszName == NULL) { + return ERROR_INVALID_PARAMETER; + } + + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (_wcsicmp(pszName, pos->from) == 0) { + if(pos->to == NULL) { + LeaveCriticalSection(&dns_hook_lock); + return HRESULT_FROM_WIN32(DNS_ERROR_RCODE_NAME_ERROR); + } + + pszName = pos->to; + + break; + } + } + + LeaveCriticalSection(&dns_hook_lock); + + return next_DnsQuery_W( + pszName, + wType, + Options, + pExtra, + ppQueryResults, + pReserved); + +} + +static DNS_STATUS WINAPI hook_DnsQueryEx( + POLYFILL_DNS_QUERY_REQUEST *pRequest, + void *pQueryResults, + void *pCancelHandle) +{ + const wchar_t *orig; + const struct dns_hook_entry *pos; + DNS_STATUS code; + size_t i; + + if (pRequest == NULL) { + return ERROR_INVALID_PARAMETER; + } + + orig = pRequest->QueryName; + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (_wcsicmp(pRequest->QueryName, pos->from) == 0) { + if(pos->to == NULL) { + LeaveCriticalSection(&dns_hook_lock); + return HRESULT_FROM_WIN32(DNS_ERROR_RCODE_NAME_ERROR); + } + + pRequest->QueryName = pos->to; + + break; + } + } + + LeaveCriticalSection(&dns_hook_lock); + + code = next_DnsQueryEx(pRequest, pQueryResults, pCancelHandle); + + /* Caller might not appreciate QueryName changing under its feet. It is + strongly implied by MSDN that a copy of *pRequest is taken by WINAPI, + so we can change it back after the call has been issued with no ill + effect... we hope. + + Hopefully the completion callback is issued from an APC or something + (or otherwise happens after this returns) or we're in trouble. */ + + pRequest->QueryName = orig; + + return code; +} + +static int WSAAPI hook_getaddrinfo( + const char *pNodeName, + const char *pServiceName, + const ADDRINFOA *pHints, + ADDRINFOA **ppResult) +{ + const struct dns_hook_entry *pos; + char *str; + size_t str_c; + wchar_t *wstr; + size_t wstr_c; + int result; + size_t i; + + str = NULL; + wstr = NULL; + + if (pNodeName == NULL) { + result = WSA_INVALID_PARAMETER; + + goto end; + } + + mbstowcs_s(&wstr_c, NULL, 0, pNodeName, 0); + wstr = malloc(wstr_c * sizeof(wchar_t)); + + if (wstr == NULL) { + result = WSA_NOT_ENOUGH_MEMORY; + + goto end; + } + + mbstowcs_s(NULL, wstr, wstr_c, pNodeName, wstr_c - 1); + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (_wcsicmp(wstr, pos->from) == 0) { + if(pos->to == NULL) { + LeaveCriticalSection(&dns_hook_lock); + result = EAI_NONAME; + + goto end; + } + + wcstombs_s(&str_c, NULL, 0, pos->to, 0); + str = malloc(str_c * sizeof(char)); + + if (str == NULL) { + LeaveCriticalSection(&dns_hook_lock); + result = WSA_NOT_ENOUGH_MEMORY; + + goto end; + } + + wcstombs_s(NULL, str, str_c, pos->to, str_c - 1); + pNodeName = str; + + break; + } + } + + LeaveCriticalSection(&dns_hook_lock); + + result = next_getaddrinfo(pNodeName, pServiceName, pHints, ppResult); + +end: + free(wstr); + free(str); + + return result; +} diff --git a/hooklib/dns.h b/hooklib/dns.h new file mode 100644 index 0000000..1f93b0f --- /dev/null +++ b/hooklib/dns.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include + +// if to_src is NULL, all lookups for from_src will fail +HRESULT dns_hook_push(const wchar_t *from_src, const wchar_t *to_src); + diff --git a/hooklib/dvd.c b/hooklib/dvd.c new file mode 100644 index 0000000..2c135eb --- /dev/null +++ b/hooklib/dvd.c @@ -0,0 +1,82 @@ +#include + +#include +#include +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "hooklib/config.h" +#include "hooklib/dll.h" +#include "hooklib/dvd.h" + +#include "util/dprintf.h" + +/* API hooks */ + +static DWORD WINAPI hook_QueryDosDeviceW( + const wchar_t *lpDeviceName, + wchar_t *lpTargetPath, + DWORD ucchMax); + +/* Link pointers */ + +static DWORD (WINAPI *next_QueryDosDeviceW)( + const wchar_t *lpDeviceName, + wchar_t *lpTargetPath, + DWORD ucchMax); + +static bool dvd_hook_initted; +static struct dvd_config dvd_config; + +static const struct hook_symbol dvd_hooks[] = { + { + .name = "QueryDosDeviceW", + .patch = hook_QueryDosDeviceW, + .link = (void **) &next_QueryDosDeviceW + }, +}; + +void dvd_hook_init(const struct dvd_config *cfg, HINSTANCE self) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + if (dvd_hook_initted) { + return; + } + + dvd_hook_initted = true; + + memcpy(&dvd_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "kernel32.dll", dvd_hooks, _countof(dvd_hooks)); + dprintf("DVD: hook enabled.\n"); +} + +DWORD WINAPI hook_QueryDosDeviceW( + const wchar_t *lpDeviceName, + wchar_t *lpTargetPath, + DWORD ucchMax) +{ + DWORD ok; + wchar_t *p_dest; + wchar_t *dvd_string = L"CdRom"; + + ok = next_QueryDosDeviceW( + lpDeviceName, + lpTargetPath, + ucchMax); + + p_dest = wcsstr (lpTargetPath, dvd_string); + + if ( p_dest != NULL ) { + dprintf("DVD: Hiding DVD drive.\n"); + return 0; + } + + return ok; +} diff --git a/hooklib/dvd.h b/hooklib/dvd.h new file mode 100644 index 0000000..ba7777a --- /dev/null +++ b/hooklib/dvd.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include + +struct dvd_config { + bool enable; +}; + +/* Init is not thread safe because API hook init is not thread safe blah + blah blah you know the drill by now. */ + +void dvd_hook_init(const struct dvd_config *cfg, HINSTANCE self); diff --git a/hooklib/fdshark.c b/hooklib/fdshark.c new file mode 100644 index 0000000..e33e9e2 --- /dev/null +++ b/hooklib/fdshark.c @@ -0,0 +1,218 @@ +#include + +#include +#include +#include +#include + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/fdshark.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static const wchar_t *fdshark_path; +static HANDLE fdshark_target_fd; +static int fdshark_flags; + +static HRESULT fdshark_handle_irp(struct irp *irp); +static HRESULT fdshark_handle_open(struct irp *irp); +static HRESULT fdshark_handle_close(struct irp *irp); +static HRESULT fdshark_handle_read(struct irp *irp); +static HRESULT fdshark_handle_write(struct irp *irp); +static HRESULT fdshark_handle_ioctl(struct irp *irp); +static bool fdshark_force_sync(struct irp *irp, HRESULT hr); + +HRESULT fdshark_hook_init(const wchar_t *path, int flags) +{ + assert(path != NULL); + assert(!(flags & ~FDSHARK_ALL_FLAGS_)); + + fdshark_path = path; + fdshark_flags = flags; + + return iohook_push_handler(fdshark_handle_irp); +} + +static HRESULT fdshark_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != fdshark_target_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return fdshark_handle_open(irp); + case IRP_OP_CLOSE: return fdshark_handle_close(irp); + case IRP_OP_READ: return fdshark_handle_read(irp); + case IRP_OP_WRITE: return fdshark_handle_write(irp); + case IRP_OP_IOCTL: return fdshark_handle_ioctl(irp); + default: return iohook_invoke_next(irp); + } +} + +static HRESULT fdshark_handle_open(struct irp *irp) +{ + HRESULT hr; + + if (_wcsicmp(irp->open_filename, fdshark_path) != 0) { + return iohook_invoke_next(irp); + } + + hr = iohook_invoke_next(irp); + + if (FAILED(hr)) { + return hr; + } + + dprintf("FdShark: Opened %S\n", fdshark_path); + fdshark_target_fd = irp->fd; + + return hr; +} + +static HRESULT fdshark_handle_close(struct irp *irp) +{ + dprintf("FdShark: Closed %S\n", fdshark_path); + fdshark_target_fd = NULL; + + return iohook_invoke_next(irp); +} + +static HRESULT fdshark_handle_read(struct irp *irp) +{ + HRESULT hr; + + if (!(fdshark_flags & FDSHARK_TRACE_READ)) { + return iohook_invoke_next(irp); + } + + dprintf("FdShark: Read %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + hr = iohook_invoke_next(irp); + + if (FAILED(hr) && !fdshark_force_sync(irp, hr)) { + dprintf("FdShark: FAILED: %x\n", (int) hr); + } else { + dprintf("FdShark: Read %p:%i/%i OK\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + dump_iobuf(&irp->read); + } + + return S_OK; +} + +static HRESULT fdshark_handle_write(struct irp *irp) +{ + HRESULT hr; + + if (!(fdshark_flags & FDSHARK_TRACE_WRITE)) { + return iohook_invoke_next(irp); + } + + dprintf("FdShark: Write %p:%i/%i\n", + irp->write.bytes, + (int) irp->write.pos, + (int) irp->write.nbytes); + dump_const_iobuf(&irp->write); + + hr = iohook_invoke_next(irp); + + if (FAILED(hr) && !fdshark_force_sync(irp, hr)) { + dprintf("FdShark: FAILED: %x\n", (int) hr); + } else { + dprintf("FdShark: Write %p:%i/%i OK\n", + irp->write.bytes, + (int) irp->write.pos, + (int) irp->write.nbytes); + } + + return S_OK; +} + +static HRESULT fdshark_handle_ioctl(struct irp *irp) +{ + HRESULT hr; + + if (!(fdshark_flags & FDSHARK_TRACE_IOCTL)) { + return iohook_invoke_next(irp); + } + + dprintf("FdShark: Ioctl %08x w:%p:%i/%i r:%p:%i/%i\n", + irp->ioctl, + irp->write.bytes, + (int) irp->write.pos, + (int) irp->write.nbytes, + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + dump_const_iobuf(&irp->write); + + hr = iohook_invoke_next(irp); + + if (FAILED(hr) && !fdshark_force_sync(irp, hr)) { + dprintf("FdShark: FAILED: %x\n", (int) hr); + } else { + dprintf("FdShark: Ioctl %08x w:%p:%i/%i r:%p:%i/%i OK\n", + irp->ioctl, + irp->write.bytes, + (int) irp->write.pos, + (int) irp->write.nbytes, + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + dump_iobuf(&irp->read); + } + + return S_OK; +} + +static bool fdshark_force_sync(struct irp *irp, HRESULT hr) +{ + DWORD xferred; + BOOL ok; + + if (!(fdshark_flags & FDSHARK_FORCE_SYNC)) { + return false; + } + + if ( hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING) && + hr != HRESULT_FROM_NT(STATUS_PENDING)) { + return false; + } + + ok = GetOverlappedResult(irp->fd, irp->ovl, &xferred, TRUE); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("FdShark: Synchronous block failed: %x\n", (int) hr); + + return false; + } + + switch (irp->op) { + case IRP_OP_READ: + case IRP_OP_IOCTL: + irp->read.pos += xferred; + + break; + + case IRP_OP_WRITE: + irp->write.pos += xferred; + + break; + + default: + break; + } + + return true; +} diff --git a/hooklib/fdshark.h b/hooklib/fdshark.h new file mode 100644 index 0000000..88dbd5c --- /dev/null +++ b/hooklib/fdshark.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include +#include + +enum { + FDSHARK_FORCE_SYNC = 0x1, + FDSHARK_TRACE_READ = 0x2, + FDSHARK_TRACE_WRITE = 0x4, + FDSHARK_TRACE_IOCTL = 0x8, + FDSHARK_ALL_FLAGS_ = 0xF, +}; + +HRESULT fdshark_hook_init(const wchar_t *filename, int flags); diff --git a/hooklib/meson.build b/hooklib/meson.build new file mode 100644 index 0000000..0a69e9f --- /dev/null +++ b/hooklib/meson.build @@ -0,0 +1,33 @@ +hooklib_lib = static_library( + 'hooklib', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'cursor.c', + 'cursor.h', + 'config.c', + 'config.h', + 'createprocess.c', + 'createprocess.h', + 'dll.c', + 'dll.h', + 'dns.c', + 'dns.h', + 'dvd.c', + 'dvd.h', + 'fdshark.c', + 'fdshark.h', + 'path.c', + 'path.h', + 'reg.c', + 'reg.h', + 'setupapi.c', + 'setupapi.h', + 'spike.c', + 'spike.h', + ], +) diff --git a/hooklib/path.c b/hooklib/path.c new file mode 100644 index 0000000..1d1d4d2 --- /dev/null +++ b/hooklib/path.c @@ -0,0 +1,908 @@ +#include + +#include +#include +#include +#include +#include + +#include "hook/hr.h" +#include "hook/table.h" + +#include "hooklib/path.h" + +/* Helpers */ + +static void path_hook_init(void); +static BOOL path_transform_a(char **out, const char *src); +static BOOL path_transform_w(wchar_t **out, const wchar_t *src); + +/* API hooks */ + +static BOOL WINAPI hook_CreateDirectoryA( + const char *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL WINAPI hook_CreateDirectoryW( + const wchar_t *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL WINAPI hook_CreateDirectoryExA( + const char *lpTemplateDirectory, + const char *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL WINAPI hook_CreateDirectoryExW( + const wchar_t *lpTemplateDirectory, + const wchar_t *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static HANDLE WINAPI hook_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE WINAPI hook_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE WINAPI hook_FindFirstFileA( + const char *lpFileName, + LPWIN32_FIND_DATAA lpFindFileData); + +static HANDLE WINAPI hook_FindFirstFileW( + const wchar_t *lpFileName, + LPWIN32_FIND_DATAW lpFindFileData); + +static HANDLE WINAPI hook_FindFirstFileExA( + const char *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags); + +static HANDLE WINAPI hook_FindFirstFileExW( + const wchar_t *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags); + +static DWORD WINAPI hook_GetFileAttributesA(const char *lpFileName); + +static DWORD WINAPI hook_GetFileAttributesW(const wchar_t *lpFileName); + +static BOOL WINAPI hook_GetFileAttributesExA( + const char *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation); + +static BOOL WINAPI hook_GetFileAttributesExW( + const wchar_t *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation); + +static BOOL WINAPI hook_RemoveDirectoryA(const char *lpFileName); + +static BOOL WINAPI hook_RemoveDirectoryW(const wchar_t *lpFileName); + +static BOOL WINAPI hook_PathFileExistsA(LPCSTR pszPath); + +static BOOL WINAPI hook_PathFileExistsW(LPCWSTR pszPath); + +/* Link pointers */ + +static BOOL (WINAPI *next_CreateDirectoryA)( + const char *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL (WINAPI *next_CreateDirectoryW)( + const wchar_t *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL (WINAPI *next_CreateDirectoryExA)( + const char *lpTemplateDirectory, + const char *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL (WINAPI *next_CreateDirectoryExW)( + const wchar_t *lpTemplateDirectory, + const wchar_t *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static HANDLE (WINAPI *next_CreateFileA)( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE (WINAPI *next_CreateFileW)( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE (WINAPI *next_FindFirstFileA)( + const char *lpFileName, + LPWIN32_FIND_DATAA lpFindFileData); + +static HANDLE (WINAPI *next_FindFirstFileW)( + const wchar_t *lpFileName, + LPWIN32_FIND_DATAW lpFindFileData); + +static HANDLE (WINAPI *next_FindFirstFileExA)( + const char *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags); + +static HANDLE (WINAPI *next_FindFirstFileExW)( + const wchar_t *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags); + +static DWORD (WINAPI *next_GetFileAttributesA)(const char *lpFileName); + +static DWORD (WINAPI *next_GetFileAttributesW)(const wchar_t *lpFileName); + +static BOOL (WINAPI *next_GetFileAttributesExA)( + const char *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation); + +static BOOL (WINAPI *next_GetFileAttributesExW)( + const wchar_t *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation); + +static BOOL (WINAPI *next_RemoveDirectoryA)(const char *lpFileName); + +static BOOL (WINAPI *next_RemoveDirectoryW)(const wchar_t *lpFileName); + +static BOOL (WINAPI *next_PathFileExistsA)(LPCSTR pszPath); + +static BOOL (WINAPI *next_PathFileExistsW)(LPCWSTR pszPath); + +/* Hook table */ + +static const struct hook_symbol path_hook_syms[] = { + { + .name = "CreateDirectoryA", + .patch = hook_CreateDirectoryA, + .link = (void **) &next_CreateDirectoryA, + }, { + .name = "CreateDirectoryW", + .patch = hook_CreateDirectoryW, + .link = (void **) &next_CreateDirectoryW, + }, { + .name = "CreateDirectoryExA", + .patch = hook_CreateDirectoryExA, + .link = (void **) &next_CreateDirectoryExA, + }, { + .name = "CreateDirectoryExW", + .patch = hook_CreateDirectoryExW, + .link = (void **) &next_CreateDirectoryExW, + }, { + .name = "CreateFileA", + .patch = hook_CreateFileA, + .link = (void **) &next_CreateFileA, + }, { + .name = "CreateFileW", + .patch = hook_CreateFileW, + .link = (void **) &next_CreateFileW, + }, { + .name = "FindFirstFileA", + .patch = hook_FindFirstFileA, + .link = (void **) &next_FindFirstFileA, + }, { + .name = "FindFirstFileW", + .patch = hook_FindFirstFileW, + .link = (void **) &next_FindFirstFileW, + }, { + .name = "FindFirstFileExA", + .patch = hook_FindFirstFileExA, + .link = (void **) &next_FindFirstFileExA, + }, { + .name = "FindFirstFileExW", + .patch = hook_FindFirstFileExW, + .link = (void **) &next_FindFirstFileExW, + }, { + .name = "GetFileAttributesA", + .patch = hook_GetFileAttributesA, + .link = (void **) &next_GetFileAttributesA, + }, { + .name = "GetFileAttributesW", + .patch = hook_GetFileAttributesW, + .link = (void **) &next_GetFileAttributesW, + }, { + .name = "GetFileAttributesExA", + .patch = hook_GetFileAttributesExA, + .link = (void **) &next_GetFileAttributesExA, + }, { + .name = "GetFileAttributesExW", + .patch = hook_GetFileAttributesExW, + .link = (void **) &next_GetFileAttributesExW, + }, { + .name = "RemoveDirectoryA", + .patch = hook_RemoveDirectoryA, + .link = (void **) &next_RemoveDirectoryA, + }, { + .name = "RemoveDirectoryW", + .patch = hook_RemoveDirectoryW, + .link = (void **) &next_RemoveDirectoryW, + }, { + .name = "PathFileExistsA", + .patch = hook_PathFileExistsA, + .link = (void **) &next_PathFileExistsA, + }, { + .name = "PathFileExistsW", + .patch = hook_PathFileExistsW, + .link = (void **) &next_PathFileExistsW, + } +}; + +static bool path_hook_initted; +static CRITICAL_SECTION path_hook_lock; +static path_hook_t *path_hook_list; +static size_t path_hook_count; + +HRESULT path_hook_push(path_hook_t hook) +{ + path_hook_t *tmp; + HRESULT hr; + + assert(hook != NULL); + + path_hook_init(); + + EnterCriticalSection(&path_hook_lock); + + tmp = realloc( + path_hook_list, + (path_hook_count + 1) * sizeof(path_hook_t)); + + if (tmp == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + path_hook_list = tmp; + path_hook_list[path_hook_count++] = hook; + + hr = S_OK; + +end: + LeaveCriticalSection(&path_hook_lock); + + return hr; +} + +static void path_hook_init(void) +{ + /* Init is not thread safe because API hook init is not thread safe blah + blah blah you know the drill by now. */ + + if (path_hook_initted) { + return; + } + + path_hook_initted = true; + InitializeCriticalSection(&path_hook_lock); + + path_hook_insert_hooks(NULL); +} + +void path_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "kernel32.dll", + path_hook_syms, + _countof(path_hook_syms)); +} + +static BOOL path_transform_a(char **out, const char *src) +{ + wchar_t *src_w; + size_t src_c; + wchar_t *dest_w; + char *dest_a; + size_t dest_s; + BOOL ok; + + assert(out != NULL); + + src_w = NULL; + dest_w = NULL; + dest_a = NULL; + *out = NULL; + + if (src == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + ok = FALSE; + + goto end; + } + + /* Widen the path */ + + mbstowcs_s(&src_c, NULL, 0, src, 0); + src_w = malloc(src_c * sizeof(wchar_t)); + + if (src_w == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + ok = FALSE; + + goto end; + } + + mbstowcs_s(NULL, src_w, src_c, src, src_c - 1); + + /* Try applying a path transform */ + + ok = path_transform_w(&dest_w, src_w); /* Take ownership! */ + + if (!ok || dest_w == NULL) { + goto end; + } + + /* Narrow the transformed path */ + + wcstombs_s(&dest_s, NULL, 0, dest_w, 0); + dest_a = malloc(dest_s * sizeof(char)); + + if (dest_a == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + ok = FALSE; + + goto end; + } + + wcstombs_s(NULL, dest_a, dest_s, dest_w, dest_s - 1); + + *out = dest_a; /* Relinquish ownership to caller! */ + ok = TRUE; + +end: + free(dest_w); + free(src_w); + + return ok; +} + +static BOOL path_transform_w(wchar_t **out, const wchar_t *src) +{ + BOOL ok; + HRESULT hr; + wchar_t *dest; + size_t dest_c; + size_t i; + + assert(out != NULL); + + dest = NULL; + *out = NULL; + + EnterCriticalSection(&path_hook_lock); + + for (i = 0 ; i < path_hook_count ; i++) { + hr = path_hook_list[i](src, NULL, &dest_c); + + if (FAILED(hr)) { + ok = hr_propagate_win32(hr, FALSE); + + goto end; + } + + if (hr == S_FALSE) { + continue; + } + + dest = malloc(dest_c * sizeof(wchar_t)); + + if (dest == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + ok = FALSE; + + goto end; + } + + hr = path_hook_list[i](src, dest, &dest_c); + + if (FAILED(hr)) { + ok = hr_propagate_win32(hr, FALSE); + + goto end; + } + + break; + } + + *out = dest; + dest = NULL; + ok = TRUE; + +end: + LeaveCriticalSection(&path_hook_lock); + + free(dest); + + return ok; +} + +int path_compare_w(const wchar_t *string1, const wchar_t *string2, size_t count) +{ + size_t i; + wchar_t c1, c2; + + assert(string1 != NULL); + assert(string2 != NULL); + + for (i = 0; i < count && string1[i] && string2[i]; i++) { + c1 = towlower(string1[i]); + + if (c1 == '/') { + c1 = '\\'; + } + + c2 = towlower(string2[i]); + + if (c2 == '/') { + c2 = '\\'; + } + + if (c1 != c2) { + break; + } + } + + return i == count ? 0 : string2[i] - string1[i]; +} + +/* Dumping ground for kernel32 file system ops whose path parameters we have to + hook into and translate. This list will grow over time as we go back and + fix up older games that don't pay attention to the mount point registry. */ + +static BOOL WINAPI hook_CreateDirectoryA( + const char *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_CreateDirectoryA( + trans ? trans : lpFileName, + lpSecurityAttributes); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_CreateDirectoryW( + const wchar_t *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_CreateDirectoryW( + trans ? trans : lpFileName, + lpSecurityAttributes); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_CreateDirectoryExA( + const char *lpTemplateDirectory, + const char *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, lpNewDirectory); + + if (!ok) { + return FALSE; + } + + ok = next_CreateDirectoryExA( + lpTemplateDirectory, + trans ? trans : lpNewDirectory, + lpSecurityAttributes); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_CreateDirectoryExW( + const wchar_t *lpTemplateDirectory, + const wchar_t *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, lpNewDirectory); + + if (!ok) { + return FALSE; + } + + ok = next_CreateDirectoryExW( + lpTemplateDirectory, + trans ? trans : lpNewDirectory, + lpSecurityAttributes); + + free(trans); + + return ok; +} + +/* Don't pull in the entire iohook framework just for CreateFileA/CreateFileW */ + +static HANDLE WINAPI hook_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + char *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_CreateFileA( + trans ? trans : lpFileName, + dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, + dwCreationDisposition, + dwFlagsAndAttributes, + hTemplateFile); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + wchar_t *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_CreateFileW( + trans ? trans : lpFileName, + dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, + dwCreationDisposition, + dwFlagsAndAttributes, + hTemplateFile); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_FindFirstFileA( + const char *lpFileName, + LPWIN32_FIND_DATAA lpFindFileData) +{ + char *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_FindFirstFileA(trans ? trans : lpFileName, lpFindFileData); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_FindFirstFileW( + const wchar_t *lpFileName, + LPWIN32_FIND_DATAW lpFindFileData) +{ + wchar_t *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_FindFirstFileW(trans ? trans : lpFileName, lpFindFileData); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_FindFirstFileExA( + const char *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags) +{ + char *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_FindFirstFileExA( + trans ? trans : lpFileName, + fInfoLevelId, + lpFindFileData, + fSearchOp, + lpSearchFilter, + dwAdditionalFlags); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_FindFirstFileExW( + const wchar_t *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags) +{ + wchar_t *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_FindFirstFileExW( + trans ? trans : lpFileName, + fInfoLevelId, + lpFindFileData, + fSearchOp, + lpSearchFilter, + dwAdditionalFlags); + + free(trans); + + return result; +} + +static DWORD WINAPI hook_GetFileAttributesA(const char *lpFileName) +{ + char *trans; + DWORD result; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_FILE_ATTRIBUTES; + } + + result = next_GetFileAttributesA(trans ? trans : lpFileName); + free(trans); + + return result; +} + +static DWORD WINAPI hook_GetFileAttributesW(const wchar_t *lpFileName) +{ + wchar_t *trans; + DWORD result; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_FILE_ATTRIBUTES; + } + + result = next_GetFileAttributesW(trans ? trans : lpFileName); + + free(trans); + + return result; +} + +static BOOL WINAPI hook_GetFileAttributesExA( + const char *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_FILE_ATTRIBUTES; + } + + ok = next_GetFileAttributesExA( + trans ? trans : lpFileName, + fInfoLevelId, + lpFileInformation); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_GetFileAttributesExW( + const wchar_t *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_FILE_ATTRIBUTES; + } + + ok = next_GetFileAttributesExW( + trans ? trans : lpFileName, + fInfoLevelId, + lpFileInformation); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_RemoveDirectoryA(const char *lpFileName) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_RemoveDirectoryA(trans ? trans : lpFileName); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_RemoveDirectoryW(const wchar_t *lpFileName) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_RemoveDirectoryW(trans ? trans : lpFileName); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_PathFileExistsA(LPCSTR pszPath) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, pszPath); + + if (!ok) { + return FALSE; + } + + ok = next_PathFileExistsA(trans ? trans : pszPath); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_PathFileExistsW(LPCWSTR pszPath) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, pszPath); + + if (!ok) { + return FALSE; + } + + ok = next_PathFileExistsW(trans ? trans : pszPath); + + free(trans); + + return ok; +} diff --git a/hooklib/path.h b/hooklib/path.h new file mode 100644 index 0000000..3a58bb7 --- /dev/null +++ b/hooklib/path.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +typedef HRESULT (*path_hook_t)( + const wchar_t *src, + wchar_t *dest, + size_t *count); + +HRESULT path_hook_push(path_hook_t hook); +void path_hook_insert_hooks(HMODULE target); +int path_compare_w(const wchar_t *string1, const wchar_t *string2, size_t count); + +static inline bool path_is_separator_w(wchar_t c) +{ + return c == L'\\' || c == L'/'; +} diff --git a/hooklib/reg.c b/hooklib/reg.c new file mode 100644 index 0000000..bc18dbb --- /dev/null +++ b/hooklib/reg.c @@ -0,0 +1,1054 @@ +#include + +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/reg.h" +#include "hook/procaddr.h" + +#include "util/dprintf.h" +#include "util/str.h" + +struct reg_hook_key { + HKEY root; + const wchar_t *name; + const struct reg_hook_val *vals; + size_t nvals; + HKEY handle; +}; + +/* Helper functions */ + +static void reg_hook_init(void); + +static LRESULT reg_hook_propagate_hr(HRESULT hr); + +static struct reg_hook_key *reg_hook_match_key_locked(HKEY handle); + +static const struct reg_hook_val *reg_hook_match_val_locked( + struct reg_hook_key *key, + const wchar_t *name); + +static LSTATUS reg_hook_open_locked( + HKEY parent, + const wchar_t *name, + HKEY *out); + +static LSTATUS reg_hook_query_val_locked( + struct reg_hook_key *key, + const wchar_t *name, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +/* API hooks */ + +static LSTATUS WINAPI hook_RegOpenKeyExW( + HKEY parent, + const wchar_t *name, + uint32_t flags, + uint32_t access, + HKEY *out); + +static LSTATUS WINAPI hook_RegCreateKeyExW( + HKEY parent, + const wchar_t *name, + uint32_t reserved, + const wchar_t *class_, + uint32_t options, + uint32_t access, + const SECURITY_ATTRIBUTES *sa, + HKEY *out, + uint32_t *disposition); + +static LSTATUS WINAPI hook_RegCloseKey(HKEY handle); + +static LSTATUS WINAPI hook_RegQueryValueExA( + HKEY handle, + const char *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS WINAPI hook_RegQueryValueExW( + HKEY handle, + const wchar_t *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS WINAPI hook_RegSetValueExW( + HKEY handle, + const wchar_t *name, + uint32_t reserved, + uint32_t type, + const void *bytes, + uint32_t nbytes); + +static LSTATUS WINAPI hook_RegGetValueW( + HKEY hkey, + LPCWSTR lpSubKey, + LPCWSTR lpValue, + uint32_t flags, + uint32_t *type, + void *pData, + uint32_t *numData +); + +static LSTATUS WINAPI hook_RegQueryInfoKeyW( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime); + +static LSTATUS WINAPI hook_RegEnumValueW( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData); +/* Link pointers */ + +static LSTATUS (WINAPI *next_RegOpenKeyExW)( + HKEY parent, + const wchar_t *name, + uint32_t flags, + uint32_t access, + HKEY *out); + +static LSTATUS (WINAPI *next_RegCreateKeyExW)( + HKEY parent, + const wchar_t *name, + uint32_t reserved, + const wchar_t *class_, + uint32_t options, + uint32_t access, + const SECURITY_ATTRIBUTES *sa, + HKEY *out, + uint32_t *disposition); + +static LSTATUS (WINAPI *next_RegCloseKey)(HKEY handle); + +static LSTATUS (WINAPI *next_RegQueryValueExA)( + HKEY handle, + const char *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS (WINAPI *next_RegQueryValueExW)( + HKEY handle, + const wchar_t *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS (WINAPI *next_RegSetValueExW)( + HKEY handle, + const wchar_t *name, + uint32_t reserved, + uint32_t type, + const void *bytes, + uint32_t nbytes); + +static LSTATUS (WINAPI *next_RegGetValueW)( + HKEY hkey, + LPCWSTR lpSubKey, + LPCWSTR lpValue, + uint32_t flags, + uint32_t *type, + void *pData, + uint32_t *numData +); + +static LSTATUS (WINAPI *next_RegQueryInfoKeyW)( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime); + +static LSTATUS (WINAPI *next_RegEnumValueW)( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData); + +static const struct hook_symbol reg_hook_syms[] = { + { + .name = "RegOpenKeyExW", + .patch = hook_RegOpenKeyExW, + .link = (void **) &next_RegOpenKeyExW, + }, { + .name = "RegCreateKeyExW", + .patch = hook_RegCreateKeyExW, + .link = (void **) &next_RegCreateKeyExW, + }, { + .name = "RegCloseKey", + .patch = hook_RegCloseKey, + .link = (void **) &next_RegCloseKey, + }, { + .name = "RegQueryValueExA", + .patch = hook_RegQueryValueExA, + .link = (void **) &next_RegQueryValueExA, + }, { + .name = "RegQueryValueExW", + .patch = hook_RegQueryValueExW, + .link = (void **) &next_RegQueryValueExW, + }, { + .name = "RegSetValueExW", + .patch = hook_RegSetValueExW, + .link = (void **) &next_RegSetValueExW, + }, { + .name = "RegGetValueW", + .patch = hook_RegGetValueW, + .link = (void **) &next_RegGetValueW, + }, { + .name = "RegQueryInfoKeyW", + .patch = hook_RegQueryInfoKeyW, + .link = (void **) &next_RegQueryInfoKeyW, + }, { + .name = "RegEnumValueW", + .patch = hook_RegEnumValueW, + .link = (void **) &next_RegEnumValueW, + } +}; + +static bool reg_hook_initted; +static CRITICAL_SECTION reg_hook_lock; +static struct reg_hook_key *reg_hook_keys; +static size_t reg_hook_nkeys; + +HRESULT reg_hook_push_key( + HKEY root, + const wchar_t *name, + const struct reg_hook_val *vals, + size_t nvals) +{ + struct reg_hook_key *new_mem; + struct reg_hook_key *new_key; + HRESULT hr; + + assert(root != NULL); + assert(name != NULL); + assert(vals != NULL || nvals == 0); + + reg_hook_init(); + + /*dprintf("Pushing reg key %ls:\n", name); + + for (int i = 0; i < nvals; i++) { + dprintf("\t%ls\n", vals[i].name); + } */ + + EnterCriticalSection(®_hook_lock); + + new_mem = realloc( + reg_hook_keys, + (reg_hook_nkeys + 1) * sizeof(struct reg_hook_key)); + + if (new_mem == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + new_key = &new_mem[reg_hook_nkeys]; + memset(new_key, 0, sizeof(*new_key)); + new_key->root = root; + new_key->name = name; /* Expect this to be statically allocated */ + new_key->vals = vals; + new_key->nvals = nvals; + + reg_hook_keys = new_mem; + reg_hook_nkeys++; + + hr = S_OK; + +end: + LeaveCriticalSection(®_hook_lock); + + return hr; +} + +static void reg_hook_init(void) +{ + if (reg_hook_initted) { + return; + } + + reg_hook_initted = true; + InitializeCriticalSection(®_hook_lock); + dprintf("Reg hook init\n"); + + reg_hook_insert_hooks(NULL); + + proc_addr_table_push( + NULL, + "ADVAPI32.dll", + (struct hook_symbol *) reg_hook_syms, + _countof(reg_hook_syms)); + +} + +void reg_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "advapi32.dll", + reg_hook_syms, + _countof(reg_hook_syms)); + +} + +static LRESULT reg_hook_propagate_hr(HRESULT hr) +{ + if (SUCCEEDED(hr)) { + return ERROR_SUCCESS; + } else if (HRESULT_FACILITY(hr) == FACILITY_WIN32) { + return HRESULT_CODE(hr); + } else { + return ERROR_GEN_FAILURE; + } +} + +static struct reg_hook_key *reg_hook_match_key_locked(HKEY handle) +{ + struct reg_hook_key *key; + size_t i; + + if (handle == NULL || handle == INVALID_HANDLE_VALUE) { + return NULL; + } + + for (i = 0 ; i < reg_hook_nkeys ; i++) { + key = ®_hook_keys[i]; + + if (key->handle == handle) { + return key; + } + } + + return NULL; +} + +static const struct reg_hook_val *reg_hook_match_val_locked( + struct reg_hook_key *key, + const wchar_t *name) +{ + const struct reg_hook_val *val; + size_t i; + + /* Watch out for accesses to the key's default value */ + + if (name == NULL) { + name = L""; + } + + for (i = 0 ; i < key->nvals ; i++) { + val = &key->vals[i]; + + if (wstr_ieq(val->name, name)) { + return val; + } + } + + return NULL; +} + +static LSTATUS reg_hook_open_locked( + HKEY parent, + const wchar_t *name, + HKEY *out) +{ + struct reg_hook_key *key; + LSTATUS err; + size_t i; + + *out = NULL; + + for (i = 0 ; i < reg_hook_nkeys ; i++) { + /* Assume reg keys are referenced from a root key and not from some + intermediary key */ + key = ®_hook_keys[i]; + //dprintf("Reg: %ls vs %ls\n", name, key->name); + + if (key->root == parent && wstr_ieq(key->name, name)) { + break; + } + } + + /* (Bail out if we didn't find anything; this causes the open/create call + to be passed onward down the hook chain) */ + + if (i >= reg_hook_nkeys) { + return ERROR_SUCCESS; + } + + /* Assume only one handle will be open at a time */ + + if (key->handle != NULL) { + return ERROR_SHARING_VIOLATION; + } + + /* Open a unique HKEY handle that we can use to identify accesses to + this virtual registry key. We open a read-only handle to an arbitrary + registry key that we can reliably assume exists and isn't one of the + hardcoded root handles. HKLM\SOFTWARE will suffice for this purpose. */ + + err = next_RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE", + 0, + KEY_READ, + out); + + if (err == ERROR_SUCCESS) { + key->handle = *out; + } + + return err; +} + +static LSTATUS WINAPI hook_RegOpenKeyExW( + HKEY parent, + const wchar_t *name, + uint32_t flags, + uint32_t access, + HKEY *out) +{ + LSTATUS err; + + if (out == NULL) { + return ERROR_INVALID_PARAMETER; + } + + EnterCriticalSection(®_hook_lock); + err = reg_hook_open_locked(parent, name, out); + LeaveCriticalSection(®_hook_lock); + + if (err == ERROR_SUCCESS) { + if (*out != NULL) { + //dprintf("Registry: Opened virtual key %S\n", name); + } else { + err = next_RegOpenKeyExW(parent, name, flags, access, out); + } + } + + return err; +} + +static LSTATUS WINAPI hook_RegCreateKeyExW( + HKEY parent, + const wchar_t *name, + uint32_t reserved, + const wchar_t *class_, + uint32_t options, + uint32_t access, + const SECURITY_ATTRIBUTES *sa, + HKEY *out, + uint32_t *disposition) +{ + LSTATUS err; + + if (out == NULL) { + return ERROR_INVALID_PARAMETER; + } + + EnterCriticalSection(®_hook_lock); + err = reg_hook_open_locked(parent, name, out); + LeaveCriticalSection(®_hook_lock); + + if (err == ERROR_SUCCESS) { + if (*out != NULL) { + //dprintf("Registry: Created virtual key %S\n", name); + } else { + err = next_RegCreateKeyExW( + parent, + name, + reserved, + class_, + options, + access, + sa, + out, + disposition); + } + } + + return err; +} + +static LSTATUS WINAPI hook_RegCloseKey(HKEY handle) +{ + struct reg_hook_key *key; + size_t i; + + EnterCriticalSection(®_hook_lock); + + for (i = 0 ; i < reg_hook_nkeys ; i++) { + key = ®_hook_keys[i]; + + if (key->handle == handle) { + //dprintf("Registry: Closed virtual key %S\n", key->name); + key->handle = NULL; + } + } + + LeaveCriticalSection(®_hook_lock); + + return next_RegCloseKey(handle); +} + +static LSTATUS WINAPI hook_RegQueryValueExW( + HKEY handle, + const wchar_t *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes) +{ + struct reg_hook_key *key; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(handle); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegQueryValueExW( + handle, + name, + reserved, + type, + bytes, + nbytes); + } + + /* Call the factored out core of this function because RegQueryValueExA + has to be a blight upon my existence */ + + err = reg_hook_query_val_locked(key, name, type, bytes, nbytes); + + LeaveCriticalSection(®_hook_lock); + + return err; +} + +/* now this right here is a pain in my ass */ + +static LSTATUS WINAPI hook_RegQueryValueExA( + HKEY handle, + const char *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes) +{ + /* _s: sizeof, _c: _countof(), _w: widened */ + + struct reg_hook_key *key; + wchar_t *name_w; + size_t name_c; + wchar_t *content; + uint32_t content_s; + size_t content_c; + uint32_t type_site; + LSTATUS err; + + name_w = NULL; + content = NULL; + + /* Normalize inconvenient inputs */ + + if (name == NULL) { + name = ""; + } + + if (type == NULL) { + type = &type_site; + } + + /* Look up key handle, early exit if no match */ + + EnterCriticalSection(®_hook_lock); + key = reg_hook_match_key_locked(handle); + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegQueryValueExA( + handle, + name, + reserved, + type, + bytes, + nbytes); + } + + /* OK, first off we need to widen the name. This requires a temporary + buffer allocation. */ + + mbstowcs_s(&name_c, NULL, 0, name, 0); + name_w = malloc(name_c * sizeof(wchar_t)); + + if (name_w == NULL) { + err = ERROR_OUTOFMEMORY; + + goto end; + } + + mbstowcs_s(NULL, name_w, name_c, name, name_c - 1); + + /* Next, check to see if the caller even cares about the content. We can + pass through if they don't. */ + + if (bytes == NULL && nbytes == NULL) { + err = reg_hook_query_val_locked(key, name_w, type, NULL, NULL); + + goto end; + } + + /* Next, we need to check the key type to see if it's REG_SZ. */ + + err = reg_hook_query_val_locked(key, name_w, type, NULL, NULL); + + if (err != ERROR_SUCCESS) { + goto end; + } + + /* If it is not REG_SZ then pass the content directly. + (We ignore the REG_MULTI_SZ case here). */ + + assert(*type != REG_MULTI_SZ); + + if (*type != REG_SZ) { + err = reg_hook_query_val_locked(key, name_w, type, bytes, nbytes); + + goto end; + } + + /* Otherwise things get more complicated. First we must measure the wide- + character length of the value (hopefully said value does not change + under our feet, of course). */ + + err = reg_hook_query_val_locked(key, name_w, type, NULL, &content_s); + + if (err != ERROR_SUCCESS) { + goto end; + } + + /* Next, allocate a scratch buffer. Even if the caller doesn't supply an + output buffer we need to know the actual content to be able to size the + narrow version. */ + + content = malloc(content_s); + + if (content == NULL) { + err = ERROR_OUTOFMEMORY; + + goto end; + } + + /* Get the data... */ + + err = reg_hook_query_val_locked(key, name_w, type, content, &content_s); + + if (err != ERROR_SUCCESS) { + goto end; + } + + /* Now size the corresponding narrow form and return it to the caller */ + + wcstombs_s(&content_c, NULL, 0, content, 0); + + if (bytes != NULL) { + if (nbytes == NULL) { + err = ERROR_INVALID_PARAMETER; + + goto end; + } + + if (*nbytes < content_c) { + err = ERROR_MORE_DATA; + + goto end; + } + + wcstombs_s(NULL, bytes, *nbytes, content, content_c - 1); + } + + if (nbytes != NULL) { /* It really should be, based on earlier checks ... */ + *nbytes = content_c; + } + + err = ERROR_SUCCESS; + +end: + LeaveCriticalSection(®_hook_lock); + + free(content); + free(name_w); + + return err; +} + +static LSTATUS reg_hook_query_val_locked( + struct reg_hook_key *key, + const wchar_t *name, + uint32_t *type, + void *bytes, + uint32_t *nbytes) +{ + const struct reg_hook_val *val; + LSTATUS err; + HRESULT hr; + + val = reg_hook_match_val_locked(key, name); + + if (val != NULL) { + if (type != NULL) { + *type = val->type; + } + + if (val->read != NULL) { + hr = val->read(bytes, nbytes); + err = reg_hook_propagate_hr(hr); + } else { + dprintf("Registry: %S: Val %S has no read handler\n", + key->name, + name); + + err = ERROR_ACCESS_DENIED; + } + } else { + dprintf("Registry: Key %S: Val %S not found\n", key->name, name); + err = ERROR_FILE_NOT_FOUND; + } + + return err; +} + +static LSTATUS WINAPI hook_RegSetValueExW( + HKEY handle, + const wchar_t *name, + uint32_t reserved, + uint32_t type, + const void *bytes, + uint32_t nbytes) +{ + struct reg_hook_key *key; + const struct reg_hook_val *val; + LSTATUS err; + HRESULT hr; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(handle); + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegSetValueExW( + handle, + name, + reserved, + type, + bytes, + nbytes); + } + + val = reg_hook_match_val_locked(key, name); + + if (val != NULL) { + if (val->write != NULL) { + if (type != val->type) { + dprintf( "Registry: Key %S: Val %S: Type mismatch " + "(expected %i got %i)\n", + key->name, + name, + val->type, + type); + + err = ERROR_ACCESS_DENIED; + } else { + dprintf("Registry: Write virtual key %S value %S\n", + key->name, + val->name); + + hr = val->write(bytes, nbytes); + err = reg_hook_propagate_hr(hr); + } + } else { + /* No write handler (the common case), black-hole whatever gets + written. */ + + err = ERROR_SUCCESS; + } + } else { + dprintf("Registry: Key %S: Val %S not found\n", key->name, name); + err = ERROR_FILE_NOT_FOUND; + } + + LeaveCriticalSection(®_hook_lock); + + return err; +} + +static LSTATUS WINAPI hook_RegGetValueW( + HKEY handle, + LPCWSTR subkey, + LPCWSTR name, + uint32_t flags, + uint32_t *type, + void *pData, + uint32_t *numData) +{ + struct reg_hook_key *key; + HKEY tmp = NULL; + const struct reg_hook_val *val; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + //dprintf("Registry: RegGetValueW lookup for %ls\\%ls\n", subkey, name); + + if (subkey == NULL) { + key = reg_hook_match_key_locked(handle); + } else { + err = hook_RegOpenKeyExW(handle, subkey, flags, 1, &tmp); + key = reg_hook_match_key_locked(tmp); + } + + //dprintf("Registry: RegGetValueW key is %ls", key->name); + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + dprintf("Registry: RegGetValueW Failed to find %ls\\%ls, passing on\n", subkey, name); + + return next_RegGetValueW( + handle, + subkey, + name, + flags, + type, + pData, + numData); + } + + val = reg_hook_match_val_locked(key, name); + + if (val != NULL) { + //dprintf("Registry: RegGetValueW found %ls\\%ls!\n", subkey, name); + + if (val->read != NULL) { + val->read(pData, numData); + + if (tmp != NULL) { + hook_RegCloseKey(tmp); + } + + LeaveCriticalSection(®_hook_lock); + err = ERROR_SUCCESS; + return err; + } + } + + LeaveCriticalSection(®_hook_lock); + err = ERROR_NOT_FOUND; + return err; +} + +static LSTATUS WINAPI hook_RegQueryInfoKeyW( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime) +{ + struct reg_hook_key *key; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(hKey); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegQueryInfoKeyW( + hKey, + lpClass, + lpcchClass, + lpReserved, + lpcSubKeys, + lpcbMaxSubKeyLen, + lpcbMaxClassLen, + lpcValues, + lpcbMaxValueNameLen, + lpcbMaxValueLen, + lpcbSecurityDescriptor, + lpftLastWriteTime); + } + + // This is the only one I've seen even be changed, so it's all I'm doing + // until I see otherwise. + *lpcValues = key->nvals; + LeaveCriticalSection(®_hook_lock); + return ERROR_SUCCESS; +} + +static LSTATUS WINAPI hook_RegEnumValueW( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData) +{ + struct reg_hook_key *key; + HRESULT hr; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(hkey); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegEnumValueW( + hkey, + dwIndex, + lpValueName, + lpcchValueName, + lpReserved, + lpType, + lpData, + lpcbData); + } + + if (dwIndex >= key->nvals) { + LeaveCriticalSection(®_hook_lock); + return ERROR_NO_MORE_ITEMS; // Pretty sure this is what it actually returns here? + } + + wcscpy_s(lpValueName, *lpcchValueName, key->vals[dwIndex].name); + *lpcchValueName = wcslen(key->vals[dwIndex].name); + LeaveCriticalSection(®_hook_lock); + return ERROR_SUCCESS; +} + +HRESULT reg_hook_read_bin( + void *bytes, + uint32_t *nbytes, + const void *src_bytes, + size_t src_nbytes) +{ + assert(src_bytes != NULL || src_nbytes == 0); + + if (bytes != NULL) { + if (nbytes == NULL || *nbytes < src_nbytes) { + return HRESULT_FROM_WIN32(ERROR_MORE_DATA); + } + + memcpy(bytes, src_bytes, src_nbytes); + } + + if (nbytes != NULL) { + *nbytes = src_nbytes; + } + + return S_OK; +} + +HRESULT reg_hook_read_u32( + void *bytes, + uint32_t *nbytes, + uint32_t src) +{ + if (bytes != NULL) { + if (nbytes == NULL || *nbytes < sizeof(uint32_t)) { + return HRESULT_FROM_WIN32(ERROR_MORE_DATA); + } + + memcpy(bytes, &src, sizeof(uint32_t)); + } + + if (nbytes != NULL) { + *nbytes = sizeof(uint32_t); + } + + return S_OK; +} + +HRESULT reg_hook_read_wstr( + void *bytes, + uint32_t *nbytes, + const wchar_t *src) +{ + size_t src_nbytes; + + assert(src != NULL); + + src_nbytes = (wcslen(src) + 1) * sizeof(wchar_t); + + if (bytes != NULL) { + if (nbytes == NULL || *nbytes < src_nbytes) { + return HRESULT_FROM_WIN32(ERROR_MORE_DATA); + } + + memcpy(bytes, src, src_nbytes); + } + + if (nbytes != NULL) { + *nbytes = src_nbytes; + } + + return S_OK; +} diff --git a/hooklib/reg.h b/hooklib/reg.h new file mode 100644 index 0000000..20e3dda --- /dev/null +++ b/hooklib/reg.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include +#include + +struct reg_hook_val { + const wchar_t *name; + HRESULT (*read)(void *bytes, uint32_t *nbytes); + HRESULT (*write)(const void *bytes, uint32_t nbytes); + uint32_t type; +}; + +void reg_hook_insert_hooks(HMODULE target); + +HRESULT reg_hook_push_key( + HKEY root, + const wchar_t *name, + const struct reg_hook_val *vals, + size_t nvals); + +HRESULT reg_hook_read_bin( + void *bytes, + uint32_t *nbytes, + const void *src_bytes, + size_t src_nbytes); + +HRESULT reg_hook_read_u32( + void *bytes, + uint32_t *nbytes, + uint32_t src); + +HRESULT reg_hook_read_wstr( + void *bytes, + uint32_t *nbytes, + const wchar_t *src); diff --git a/hooklib/setupapi.c b/hooklib/setupapi.c new file mode 100644 index 0000000..edd416a --- /dev/null +++ b/hooklib/setupapi.c @@ -0,0 +1,340 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" + +struct setupapi_class { + const GUID *guid; + const wchar_t *path; + HDEVINFO cur_handle; +}; + +static void setupapi_hook_init(void); + +/* API hooks */ + +static HDEVINFO WINAPI my_SetupDiGetClassDevsW( + const GUID *ClassGuid, + wchar_t *Enumerator, + HWND hwndParent, + DWORD Flags); + +static BOOL WINAPI my_SetupDiEnumDeviceInterfaces( + HDEVINFO DeviceInfoSet, + SP_DEVINFO_DATA *DeviceInfoData, + const GUID *InterfaceClassGuid, + DWORD MemberIndex, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData); + +static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailW( + HDEVINFO DeviceInfoSet, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + DWORD *RequiredSize, + SP_DEVINFO_DATA *DeviceInfoData); + +static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet); + +/* Links */ + +static HDEVINFO (WINAPI *next_SetupDiGetClassDevsW)( + const GUID *ClassGuid, + wchar_t *Enumerator, + HWND hwndParent, + DWORD Flags); + +static BOOL (WINAPI *next_SetupDiEnumDeviceInterfaces)( + HDEVINFO DeviceInfoSet, + SP_DEVINFO_DATA *DeviceInfoData, + const GUID *InterfaceClassGuid, + DWORD MemberIndex, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData); + +static BOOL (WINAPI *next_SetupDiGetDeviceInterfaceDetailW)( + HDEVINFO DeviceInfoSet, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + DWORD *RequiredSize, + SP_DEVINFO_DATA *DeviceInfoData); + +static BOOL (WINAPI *next_SetupDiDestroyDeviceInfoList)(HDEVINFO DeviceInfoSet); + +/* Hook tbl */ + +static const struct hook_symbol setupapi_syms[] = { + { + .name = "SetupDiGetClassDevsW", + .patch = my_SetupDiGetClassDevsW, + .link = (void *) &next_SetupDiGetClassDevsW, + }, { + .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, + } +}; + +static bool setupapi_initted; +static CRITICAL_SECTION setupapi_lock; +static struct setupapi_class *setupapi_classes; +static size_t setupapi_nclasses; + +HRESULT setupapi_add_phantom_dev(const GUID *iface_class, const wchar_t *path) +{ + struct setupapi_class *class_; + struct setupapi_class *new_array; + HRESULT hr; + + assert(iface_class != NULL); + assert(path != NULL); + + setupapi_hook_init(); + + EnterCriticalSection(&setupapi_lock); + + new_array = realloc( + setupapi_classes, + (setupapi_nclasses + 1) * sizeof(struct setupapi_class)); + + if (new_array == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + setupapi_classes = new_array; + + class_ = &setupapi_classes[setupapi_nclasses++]; + class_->guid = iface_class; + class_->path = path; + hr = S_OK; + +end: + LeaveCriticalSection(&setupapi_lock); + + return hr; +} + +static void setupapi_hook_init() +{ + if (setupapi_initted) { + return; + } + + setupapi_hook_insert_hooks(NULL); + + InitializeCriticalSection(&setupapi_lock); + setupapi_initted = true; +} + +void setupapi_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "setupapi.dll", + setupapi_syms, + _countof(setupapi_syms)); +} + +static HDEVINFO WINAPI my_SetupDiGetClassDevsW( + const GUID *ClassGuid, + wchar_t *Enumerator, + HWND hwndParent, + DWORD Flags) +{ + struct setupapi_class *class_; + HDEVINFO result; + size_t i; + + result = next_SetupDiGetClassDevsW( + ClassGuid, + Enumerator, + hwndParent, + Flags); + + if (result == INVALID_HANDLE_VALUE || ClassGuid == NULL) { + return result; + } + + EnterCriticalSection(&setupapi_lock); + + for (i = 0 ; i < setupapi_nclasses ; i++) { + class_ = &setupapi_classes[i]; + if (memcmp(ClassGuid, class_->guid, sizeof(*ClassGuid)) == 0) { + class_->cur_handle = result; + } + } + + LeaveCriticalSection(&setupapi_lock); + + return result; +} + +static BOOL WINAPI my_SetupDiEnumDeviceInterfaces( + HDEVINFO DeviceInfoSet, + SP_DEVINFO_DATA *DeviceInfoData, + const GUID *InterfaceClassGuid, + DWORD MemberIndex, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData) +{ + const struct setupapi_class *class_; + size_t i; + + if ( DeviceInfoSet == INVALID_HANDLE_VALUE || + DeviceInterfaceData == NULL || + DeviceInterfaceData->cbSize != sizeof(*DeviceInterfaceData)) { + goto pass; + } + + if (MemberIndex > 0) { + MemberIndex--; + + goto pass; + } + + EnterCriticalSection(&setupapi_lock); + + for ( i = 0, class_ = NULL ; + i < setupapi_nclasses && class_ == NULL ; + i++) { + if (DeviceInfoSet == setupapi_classes[i].cur_handle) { + class_ = &setupapi_classes[i]; + + dprintf("SetupAPI: Interface {%08lx-...} -> Device node %S\n", + class_->guid->Data1, + class_->path); + + memcpy( &DeviceInterfaceData->InterfaceClassGuid, + class_->guid, + sizeof(GUID)); + DeviceInterfaceData->Flags = SPINT_ACTIVE; + DeviceInterfaceData->Reserved = (ULONG_PTR) class_->path; + } + } + + LeaveCriticalSection(&setupapi_lock); + + if (class_ == NULL) { + goto pass; + } + + SetLastError(ERROR_SUCCESS); + + return TRUE; + +pass: + return next_SetupDiEnumDeviceInterfaces( + DeviceInfoSet, + DeviceInfoData, + InterfaceClassGuid, + MemberIndex, + DeviceInterfaceData); +} + +static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailW( + HDEVINFO DeviceInfoSet, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + DWORD *RequiredSize, + SP_DEVINFO_DATA *DeviceInfoData) +{ + const wchar_t *wstr; + size_t nbytes_wstr; + size_t nbytes_total; + size_t i; + bool match; + + if (DeviceInfoSet == INVALID_HANDLE_VALUE || DeviceInterfaceData == NULL) { + goto pass; + } + + EnterCriticalSection(&setupapi_lock); + + for ( i = 0, match = false ; + i < setupapi_nclasses && !match ; + i++) { + if ( DeviceInfoSet == setupapi_classes[i].cur_handle && + DeviceInterfaceData->Reserved == (ULONG_PTR) setupapi_classes[i].path) { + match = true; + } + } + + LeaveCriticalSection(&setupapi_lock); + + if (!match) { + goto pass; + } + + wstr = (const wchar_t *) DeviceInterfaceData->Reserved; + nbytes_wstr = (wcslen(wstr) + 1) * sizeof(wchar_t); + nbytes_total = offsetof(SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath); + nbytes_total += nbytes_wstr; + + if (RequiredSize != NULL) { + *RequiredSize = (DWORD) nbytes_total; + } + + if ( DeviceInterfaceDetailData == NULL && + DeviceInterfaceDetailDataSize < nbytes_total) { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + + return FALSE; + } + + if (DeviceInterfaceDetailData->cbSize!=sizeof(*DeviceInterfaceDetailData)) { + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + memcpy(DeviceInterfaceDetailData->DevicePath, wstr, nbytes_wstr); + SetLastError(ERROR_SUCCESS); + + return TRUE; + +pass: + return next_SetupDiGetDeviceInterfaceDetailW( + DeviceInfoSet, + DeviceInterfaceData, + DeviceInterfaceDetailData, + DeviceInterfaceDetailDataSize, + RequiredSize, + DeviceInfoData); +} + +static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet) +{ + size_t i; + + EnterCriticalSection(&setupapi_lock); + + for (i = 0 ; i < setupapi_nclasses ; i++) { + if (setupapi_classes[i].cur_handle == DeviceInfoSet) { + setupapi_classes[i].cur_handle = NULL; + } + } + + LeaveCriticalSection(&setupapi_lock); + + return next_SetupDiDestroyDeviceInfoList(DeviceInfoSet); +} diff --git a/hooklib/setupapi.h b/hooklib/setupapi.h new file mode 100644 index 0000000..94eea13 --- /dev/null +++ b/hooklib/setupapi.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include + +HRESULT setupapi_add_phantom_dev(const GUID *iface_class, const wchar_t *path); +void setupapi_hook_insert_hooks(HMODULE target); diff --git a/hooklib/spike.c b/hooklib/spike.c new file mode 100644 index 0000000..f06166c --- /dev/null +++ b/hooklib/spike.c @@ -0,0 +1,249 @@ +#include + +#include +#include +#include +#include +#include + +#include "hook/pe.h" + +#include "hooklib/spike.h" + +#include "util/dprintf.h" + +static void spike_hook_read_config(const wchar_t *spike_file); + +/* Spike functions. Their "style" is named after the libc function they bear + the closest resemblance to. */ + +static void spike_fn_puts(const char *msg) +{ + char line[512]; + + sprintf_s(line, _countof(line), "%s\n", msg); + OutputDebugStringA(line); +} + +static void spike_fn_fputs(const char *msg) +{ + OutputDebugStringA(msg); +} + +static void spike_fn_printf(const char *fmt, ...) +{ + char line[512]; + va_list ap; + + va_start(ap, fmt); + vsprintf_s(line, _countof(line), fmt, ap); + strcat(line, "\n"); + OutputDebugStringA(line); +} + +static void spike_fn_vprintf( + const char *proc, + int line_no, + const char *fmt, + va_list ap) +{ + char msg[512]; + char line[512]; + + vsprintf_s(msg, _countof(msg), fmt, ap); + sprintf_s(line, _countof(line), "%s:%i: %s", proc, line_no, msg); + OutputDebugStringA(line); +} + +static void spike_fn_vwprintf( + const wchar_t *proc, + int line_no, + const wchar_t *fmt, + va_list ap) +{ + wchar_t msg[512]; + wchar_t line[512]; + + vswprintf_s(msg, _countof(msg), fmt, ap); + swprintf_s(line, _countof(line), L"%s:%i: %s", proc, line_no, msg); + OutputDebugStringW(line); +} + +static void spike_fn_perror( + int a1, + int a2, + int error, + const char *file, + int line_no, + const char *msg) +{ + char line[512]; + + sprintf_s( + line, + _countof(line), + "%s:%i:%08x: %s\n", + file, + line_no, + error, + msg); + + OutputDebugStringA(line); +} + +/* Spike inserters */ + +static void spike_insert_jmp(ptrdiff_t rva, void *proc) +{ + uint8_t *base; + uint8_t *target; + uint8_t *func_ptr; + uint32_t delta; + + base = (uint8_t *) GetModuleHandleW(NULL); + + target = base + rva; + func_ptr = proc; + delta = func_ptr - target - 4; /* -4: EIP delta, after end of target insn */ + + pe_patch(target, &delta, sizeof(delta)); +} + +static void spike_insert_ptr(ptrdiff_t rva, void *ptr) +{ + uint8_t *base; + uint8_t *target; + + base = (uint8_t *) GetModuleHandleW(NULL); + target = base + rva; + + pe_patch(target, &ptr, sizeof(ptr)); +} + +static void spike_insert_log_levels(ptrdiff_t rva, size_t count) +{ + uint8_t *base; + uint32_t *levels; + size_t i; + + base = (uint8_t *) GetModuleHandleW(NULL); + levels = (uint32_t *) (base + rva); + + for (i = 0 ; i < count ; i++) { + levels[i] = 255; + } +} + +/* Config reader */ + +void spike_hook_init(const wchar_t *ini_file) +{ + wchar_t module[MAX_PATH]; + wchar_t path[MAX_PATH]; + const wchar_t *basename; + const wchar_t *slash; + + assert(ini_file != NULL); + + /* Get the filename (strip path) of the host EXE */ + + GetModuleFileNameW(NULL, module, _countof(module)); + slash = wcsrchr(module, L'\\'); + + if (slash != NULL) { + basename = slash + 1; + } else { + basename = module; + } + + /* Check our INI file to see if any spikes are configured for this EXE. + Normally we separate out config reading into a separate module... */ + + GetPrivateProfileStringW( + L"spike", + basename, + L"", + path, + _countof(path), + ini_file); + + if (path[0] != L'\0') { + dprintf("Spiking %S using config from %S\n", basename, path); + spike_hook_read_config(path); + } +} + +static void spike_hook_read_config(const wchar_t *spike_file) +{ + int match; + int count; + int rva; + char line[80]; + char *ret; + FILE *f; + + f = _wfopen(spike_file, L"r"); + + if (f == NULL) { + dprintf("Error opening spike file %S\n", spike_file); + + return; + } + + for (;;) { + ret = fgets(line, sizeof(line), f); + + if (ret == NULL) { + break; + } + + if (line[0] == '#' || line[0] == '\r' || line[0] == '\n') { + continue; + } + + match = sscanf(line, "levels %i %i", &rva, &count); + + if (match == 2) { + spike_insert_log_levels((ptrdiff_t) rva, count); + } + + match = sscanf(line, "j_vprintf %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_vprintf); + } + + match = sscanf(line, "j_vwprintf %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_vwprintf); + } + + match = sscanf(line, "j_printf %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_printf); + } + + match = sscanf(line, "j_puts %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_puts); + } + + match = sscanf(line, "j_perror %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_perror); + } + + match = sscanf(line, "c_fputs %i", &rva); /* c == "callback" */ + + if (match == 1) { + spike_insert_ptr((ptrdiff_t) rva, spike_fn_fputs); + } + } + + dprintf("Spike insertion complete\n"); + fclose(f); +} diff --git a/hooklib/spike.h b/hooklib/spike.h new file mode 100644 index 0000000..779d22f --- /dev/null +++ b/hooklib/spike.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void spike_hook_init(const wchar_t *ini_file); diff --git a/iccard/felica.c b/iccard/felica.c new file mode 100644 index 0000000..09e3a77 --- /dev/null +++ b/iccard/felica.c @@ -0,0 +1,196 @@ +#include + +#include +#include +#include + +#include "hook/iobuf.h" + +#include "iccard/felica.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT felica_cmd_poll( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_get_system_code( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_nda_a4( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +uint64_t felica_get_generic_PMm(void) +{ + /* A FeliCa PMm contains low-level protocol timing information for + communicating with a particular IC card. The exact values are not + particularly important for our purposes, so we'll just return a hard- + coded PMm. This current value has been taken from an iPhone, emulating + a Suica pass via Apple Wallet, which seems to be one of the few + universally accepted FeliCa types for these games. Certain older + Suica passes and other payment and transportation cards + do not seem to be supported anymore. */ + + return 0x01168B868FBECBFFULL; +} + +HRESULT felica_transact( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + uint64_t IDm; + uint8_t code; + HRESULT hr; + + assert(f != NULL); + assert(req != NULL); + assert(res != NULL); + + hr = iobuf_read_8(req, &code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_8(res, code + 1); + + if (FAILED(hr)) { + return hr; + } + + if (code != FELICA_CMD_POLL) { + hr = iobuf_read_be64(req, &IDm); + + if (FAILED(hr)) { + return hr; + } + + if (IDm != f->IDm) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = iobuf_write_be64(res, IDm); + + if (FAILED(hr)) { + return hr; + } + } + + switch (code) { + case FELICA_CMD_POLL: + return felica_cmd_poll(f, req, res); + + case FELICA_CMD_GET_SYSTEM_CODE: + return felica_cmd_get_system_code(f, req, res); + + case FELICA_CMD_NDA_A4: + return felica_cmd_nda_a4(f, req, res); + + default: + dprintf("FeliCa: Unimplemented command %02x, payload:\n", code); + dump_const_iobuf(req); + + return E_NOTIMPL; + } +} + +static HRESULT felica_cmd_poll( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + uint16_t system_code; + uint8_t request_code; + uint8_t time_slot; + HRESULT hr; + + /* Request: */ + + hr = iobuf_read_be16(req, &system_code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req, &request_code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req, &time_slot); + + if (FAILED(hr)) { + return hr; + } + + if (system_code != 0xFFFF && system_code != f->system_code) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + // TODO handle other params correctly... + + /* Response: */ + + hr = iobuf_write_be64(res, f->IDm); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be64(res, f->PMm); + + if (FAILED(hr)) { + return hr; + } + + if (request_code == 0x01) { + hr = iobuf_write_be16(res, f->system_code); + + if (FAILED(hr)) { + return hr; + } + } + + return S_OK; +} + +static HRESULT felica_cmd_get_system_code( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + HRESULT hr; + + hr = iobuf_write_8(res, 1); /* Number of system codes */ + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be16(res, f->system_code); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT felica_cmd_nda_a4( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + /* The specification for this command is probably only available under NDA. + Returning what the driver seems to want. */ + + return iobuf_write_8(res, 0); +} diff --git a/iccard/felica.h b/iccard/felica.h new file mode 100644 index 0000000..33be201 --- /dev/null +++ b/iccard/felica.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +enum { + FELICA_CMD_POLL = 0x00, + FELICA_CMD_GET_SYSTEM_CODE = 0x0c, + FELICA_CMD_NDA_A4 = 0xa4, +}; + +struct felica { + uint64_t IDm; + uint64_t PMm; + uint16_t system_code; +}; + +HRESULT felica_transact( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +uint64_t felica_get_generic_PMm(void); diff --git a/iccard/meson.build b/iccard/meson.build new file mode 100644 index 0000000..bca1a1d --- /dev/null +++ b/iccard/meson.build @@ -0,0 +1,16 @@ +iccard_lib = static_library( + 'iccard', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'nesica.c', + 'nesica.h', + 'felica.c', + 'felica.h', + 'mifare.h', + ], +) diff --git a/iccard/mifare.h b/iccard/mifare.h new file mode 100644 index 0000000..10dbc34 --- /dev/null +++ b/iccard/mifare.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +struct mifare_block { + uint8_t bytes[16]; +}; + +struct mifare_sector { + struct mifare_block blocks[4]; +}; + +struct mifare { + struct mifare_sector sectors[16]; +}; diff --git a/iccard/nesica.c b/iccard/nesica.c new file mode 100644 index 0000000..bbef9be --- /dev/null +++ b/iccard/nesica.c @@ -0,0 +1,44 @@ +#include +#include +#include + +#include "iccard/nesica.h" +#include "iccard/mifare.h" + +#include "util/dprintf.h" + +HRESULT aime_card_populate( + struct mifare *mifare, + const uint8_t *card_sn, + size_t nbytes) +{ + uint8_t b; + size_t i; + + assert(mifare != NULL); + assert(card_sn != NULL); + + memset(mifare, 0, sizeof(*mifare)); + + if (nbytes != 16) { + dprintf("Nesica IC: Card Serial must be 16 characters\n"); + + return E_INVALIDARG; + } + + for (int i = 0; i < 16; i++) { + mifare->sectors[0].blocks[0].bytes[i] = i; + } + + memset(mifare->sectors[0].blocks[2].bytes, 0xff, 16); + memset(mifare->sectors[0].blocks[3].bytes, 0xff, 16); + + mifare->sectors[0].blocks[0].bytes[14] = 'T'; + mifare->sectors[0].blocks[0].bytes[15] = 'U'; + + memcpy_s(mifare->sectors[0].blocks[1].bytes, 4, 'T053', 4); + memcpy_s(mifare->sectors[0].blocks[1].bytes[4], 12, card_sn, 12); + memcpy_s(mifare->sectors[0].blocks[2].bytes, 16, card_sn[12], 4); + + return S_OK; +} diff --git a/iccard/nesica.h b/iccard/nesica.h new file mode 100644 index 0000000..34857ef --- /dev/null +++ b/iccard/nesica.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include + +#include "iccard/mifare.h" + +HRESULT aime_card_populate( + struct mifare *mifare, + const uint8_t *luid, + size_t nbytes); diff --git a/jvs/jvs-bus.c b/jvs/jvs-bus.c new file mode 100644 index 0000000..8961b84 --- /dev/null +++ b/jvs/jvs-bus.c @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "jvs/jvs-bus.h" + +void jvs_bus_transact( + struct jvs_node *head, + const void *bytes, + size_t nbytes, + struct iobuf *resp) +{ + struct jvs_node *node; + + assert(bytes != NULL); + assert(resp != NULL); + + for (node = head ; node != NULL ; node = node->next) { + node->transact(node, bytes, nbytes, resp); + } +} + +bool jvs_node_sense(struct jvs_node *node) +{ + if (node != NULL) { + return node->sense(node); + } else { + return false; + } +} diff --git a/jvs/jvs-bus.h b/jvs/jvs-bus.h new file mode 100644 index 0000000..820ba20 --- /dev/null +++ b/jvs/jvs-bus.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "hook/iobuf.h" + +struct jvs_node { + struct jvs_node *next; + void (*transact)( + struct jvs_node *node, + const void *bytes, + size_t nbytes, + struct iobuf *resp); + bool (*sense)(struct jvs_node *node); +}; + +void jvs_bus_transact( + struct jvs_node *head, + const void *bytes, + size_t nbytes, + struct iobuf *resp); + +bool jvs_node_sense(struct jvs_node *node); diff --git a/jvs/jvs-cmd.h b/jvs/jvs-cmd.h new file mode 100644 index 0000000..0197c9f --- /dev/null +++ b/jvs/jvs-cmd.h @@ -0,0 +1,45 @@ +#pragma once + +enum { + JVS_CMD_READ_ID = 0x10, + JVS_CMD_GET_CMD_VERSION = 0x11, + JVS_CMD_GET_JVS_VERSION = 0x12, + JVS_CMD_GET_COMM_VERSION = 0x13, + JVS_CMD_GET_FEATURES = 0x14, + JVS_CMD_READ_SWITCHES = 0x20, + JVS_CMD_READ_COIN = 0x21, + JVS_CMD_READ_ANALOGS = 0x22, + JVS_CMD_WRITE_GPIO = 0x32, + JVS_CMD_RESET = 0xF0, + JVS_CMD_ASSIGN_ADDR = 0xF1, +}; + +#pragma pack(push, 1) + +struct jvs_req_read_switches { + uint8_t cmd; + uint8_t num_players; + uint8_t bytes_per_player; +}; + +struct jvs_req_read_coin { + uint8_t cmd; + uint8_t nslots; +}; + +struct jvs_req_read_analogs { + uint8_t cmd; + uint8_t nanalogs; +}; + +struct jvs_req_reset { + uint8_t cmd; + uint8_t unknown; +}; + +struct jvs_req_assign_addr { + uint8_t cmd; + uint8_t addr; +}; + +#pragma pack(pop) diff --git a/jvs/jvs-frame.c b/jvs/jvs-frame.c new file mode 100644 index 0000000..37fb5e7 --- /dev/null +++ b/jvs/jvs-frame.c @@ -0,0 +1,152 @@ +#include + +#include +#include +#include +#include + +#include "hook/iobuf.h" + +#include "jvs/jvs-frame.h" + +#include "util/dprintf.h" + +static HRESULT jvs_frame_check_sum(const struct iobuf *dest); +static HRESULT jvs_frame_encode_byte(struct iobuf *dest, uint8_t byte); + +/* Deals in whole frames only for simplicity's sake, since that's all we need + to emulate the Nu's kernel driver interface. This could of course be + extended later for other arcade hardware platforms. */ + +HRESULT jvs_frame_decode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *bytes; + bool escape; + size_t i; + + assert(dest != NULL); + assert(ptr != NULL); + + bytes = ptr; + + if (nbytes == 0) { + dprintf("JVS Frame: Empty frame\n"); + + return E_FAIL; + } + + if (bytes[0] != 0xE0) { + dprintf("JVS Frame: Sync byte was expected\n"); + + return E_FAIL; + } + + escape = false; + + for (i = 1 ; i < nbytes ; i++) { + if (bytes[i] == 0xE0) { + dprintf("JVS Frame: Unexpected sync byte\n"); + + return E_FAIL; + } else if (bytes[i] == 0xD0) { + if (escape) { + dprintf("JVS Frame: Escaping fault\n"); + + return E_FAIL; + } + + escape = true; + } else { + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + if (escape) { + escape = false; + dest->bytes[dest->pos++] = bytes[i] + 1; + } else { + dest->bytes[dest->pos++] = bytes[i]; + } + } + } + + return jvs_frame_check_sum(dest); +} + +static HRESULT jvs_frame_check_sum(const struct iobuf *dest) +{ + uint8_t checksum; + size_t i; + + checksum = 0; + + for (i = 0 ; i < dest->pos - 1 ; i++) { + checksum += dest->bytes[i]; + } + + if (checksum != dest->bytes[dest->pos - 1]) { + dprintf("JVS Frame: Checksum failure\n"); + + return HRESULT_FROM_WIN32(ERROR_CRC); + } + + return S_OK; +} + +HRESULT jvs_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *bytes; + uint8_t checksum; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(ptr != NULL); + + bytes = ptr; + checksum = 0; + + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xE0; + + for (i = 0 ; i < nbytes ; i++) { + hr = jvs_frame_encode_byte(dest, bytes[i]); + + if (FAILED(hr)) { + return hr; + } + + checksum += bytes[i]; + } + + return jvs_frame_encode_byte(dest, checksum); +} + +static HRESULT jvs_frame_encode_byte(struct iobuf *dest, uint8_t byte) +{ + if (byte == 0xD0 || byte == 0xE0) { + if (dest->pos + 2 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xD0; + dest->bytes[dest->pos++] = byte - 1; + } else { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + } + + return S_OK; +} diff --git a/jvs/jvs-frame.h b/jvs/jvs-frame.h new file mode 100644 index 0000000..719e528 --- /dev/null +++ b/jvs/jvs-frame.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +#include "hook/iobuf.h" + +HRESULT jvs_frame_decode( + struct iobuf *dest, + const void *bytes, + size_t nbytes); + +HRESULT jvs_frame_encode( + struct iobuf *dest, + const void *bytes, + size_t nbytes); diff --git a/jvs/jvs-util.c b/jvs/jvs-util.c new file mode 100644 index 0000000..1bbe10e --- /dev/null +++ b/jvs/jvs-util.c @@ -0,0 +1,109 @@ +#include + +#include +#include +#include + +#include "hook/iobuf.h" + +#include "jvs/jvs-frame.h" +#include "jvs/jvs-util.h" + +#include "util/dprintf.h" + +typedef HRESULT (*jvs_dispatch_fn_t)( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp); + +void jvs_crack_request( + const void *bytes, + size_t nbytes, + struct iobuf *resp, + uint8_t jvs_addr, + jvs_dispatch_fn_t dispatch_fn, + void *dispatch_ctx) +{ + uint8_t req_bytes[128]; + uint8_t resp_bytes[128]; + struct iobuf decode; + struct iobuf encode; + struct const_iobuf segments; + HRESULT hr; + + assert(bytes != NULL); + assert(resp != NULL); + assert(jvs_addr != 0x00 && (jvs_addr < 0x20 || jvs_addr == 0xFF)); + assert(dispatch_fn != NULL); + + decode.bytes = req_bytes; + decode.nbytes = sizeof(req_bytes); + decode.pos = 0; + + hr = jvs_frame_decode(&decode, bytes, nbytes); + + if (FAILED(hr)) { + return; + } + +#if 0 + dprintf("Decoded request:\n"); + dump_iobuf(&decode); +#endif + + if (req_bytes[0] != jvs_addr && req_bytes[0] != 0xFF) { + return; + } + + iobuf_flip(&segments, &decode); + segments.pos = 2; + + encode.bytes = resp_bytes; + encode.nbytes = sizeof(resp_bytes); + encode.pos = 3; + + /* +1: Don't try to dispatch the trailing checksum byte */ + + hr = S_OK; /* I guess an empty request packet is technically valid? */ + + while (segments.pos + 1 < segments.nbytes) { + hr = dispatch_fn(dispatch_ctx, &segments, &encode); + + if (FAILED(hr)) { + break; + } + } + + if (FAILED(hr)) { + /* Send an error in the overall status byte */ + encode.pos = 3; + + resp_bytes[0] = 0x00; /* Dest addr (master) */ + resp_bytes[1] = 0x02; /* Payload len: Status byte, checksum byte */ + + if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { + resp_bytes[2] = 0x04; /* Status: "Overflow" */ + } else { + resp_bytes[2] = 0x02; /* Status: Encoutered unsupported command */ + } + } else if (encode.pos == 3) { + /* Probably a reset, don't emit a response frame with empty payload */ + return; + } else { + /* Send success response */ + resp_bytes[0] = 0x00; /* Dest addr (master) */ + resp_bytes[1] = encode.pos - 2 + 1; /* -2 header +1 checksum */ + resp_bytes[2] = 0x01; /* Status: Success */ + } + +#if 0 + dprintf("Encoding response:\n"); + dump_iobuf(&encode); +#endif + + hr = jvs_frame_encode(resp, encode.bytes, encode.pos); + + if (FAILED(hr)) { + dprintf("JVS Node: Response encode error: %x\n", (int) hr); + } +} diff --git a/jvs/jvs-util.h b/jvs/jvs-util.h new file mode 100644 index 0000000..e3fc4e5 --- /dev/null +++ b/jvs/jvs-util.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +typedef HRESULT (*jvs_dispatch_fn_t)( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp); + +void jvs_crack_request( + const void *bytes, + size_t nbytes, + struct iobuf *resp, + uint8_t jvs_addr, + jvs_dispatch_fn_t dispatch_fn, + void *dispatch_ctx); diff --git a/jvs/meson.build b/jvs/meson.build new file mode 100644 index 0000000..49737a0 --- /dev/null +++ b/jvs/meson.build @@ -0,0 +1,18 @@ +jvs_lib = static_library( + 'jvs', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'jvs-bus.c', + 'jvs-bus.h', + 'jvs-cmd.h', + 'jvs-frame.c', + 'jvs-frame.h', + 'jvs-util.c', + 'jvs-util.h', + ], +) diff --git a/mercuryhook/config.c b/mercuryhook/config.c new file mode 100644 index 0000000..f087942 --- /dev/null +++ b/mercuryhook/config.c @@ -0,0 +1,74 @@ +#include +#include + +#include "board/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" +#include "gfxhook/config.h" + +#include "mercuryhook/config.h" + +#include "platform/config.h" + +void mercury_dll_config_load( + struct mercury_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"mercuryio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void touch_config_load( + struct touch_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW( + L"touch", + L"enable", + 1, + filename); +} + +void elisabeth_config_load( + struct elisabeth_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW( + L"elisabeth", + L"enable", + 1, + filename); +} + + +void mercury_hook_config_load( + struct mercury_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + aime_config_load(&cfg->aime, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + gfx_config_load(&cfg->gfx, filename); + mercury_dll_config_load(&cfg->dll, filename); + touch_config_load(&cfg->touch, filename); + elisabeth_config_load(&cfg->elisabeth, filename); +} diff --git a/mercuryhook/config.h b/mercuryhook/config.h new file mode 100644 index 0000000..d5bf463 --- /dev/null +++ b/mercuryhook/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "hooklib/dvd.h" +#include "gfxhook/gfx.h" + +#include "mercuryhook/mercury-dll.h" +#include "mercuryhook/touch.h" +#include "mercuryhook/elisabeth.h" + +#include "platform/config.h" + +struct mercury_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct gfx_config gfx; + struct mercury_dll_config dll; + struct touch_config touch; + struct elisabeth_config elisabeth; +}; + +void mercury_dll_config_load( + struct mercury_dll_config *cfg, + const wchar_t *filename); + +void mercury_hook_config_load( + struct mercury_hook_config *cfg, + const wchar_t *filename); diff --git a/mercuryhook/dllmain.c b/mercuryhook/dllmain.c new file mode 100644 index 0000000..14726aa --- /dev/null +++ b/mercuryhook/dllmain.c @@ -0,0 +1,121 @@ +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "hook/process.h" + +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "gfxhook/gfx.h" +#include "gfxhook/d3d11.h" + +#include "mercuryhook/config.h" +#include "mercuryhook/io4.h" +#include "mercuryhook/mercury-dll.h" +#include "mercuryhook/elisabeth.h" +#include "mercuryhook/touch.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" + +static HMODULE mercury_hook_mod; +static process_entry_t mercury_startup; +static struct mercury_hook_config mercury_hook_cfg; + +/* This hook is based on mu3hook, with leaked mercuryhook i/o codes. */ + +static DWORD CALLBACK mercury_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin mercury_pre_startup ---\n"); + + /* Load config */ + + mercury_hook_config_load(&mercury_hook_cfg, L".\\taitools.ini"); + + /* Hook Win32 APIs */ + + dvd_hook_init(&mercury_hook_cfg.dvd, mercury_hook_mod); + serial_hook_init(); + + gfx_hook_init(&mercury_hook_cfg.gfx); + gfx_d3d11_hook_init(&mercury_hook_cfg.gfx, mercury_hook_mod); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &mercury_hook_cfg.platform, + "SDFE", + "ACA1", + mercury_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&mercury_hook_cfg.aime, 1, mercury_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(2); + + if (FAILED(hr)) { + goto fail; + } + + hr = mercury_dll_init(&mercury_hook_cfg.dll, mercury_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = mercury_io4_hook_init(&mercury_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + /* Start elisabeth Hooks for the LED and IO Board DLLs */ + elisabeth_hook_init(&mercury_hook_cfg.elisabeth); + + touch_hook_init(&mercury_hook_cfg.touch); + + /* Initialize debug helpers */ + + spike_hook_init(L".\\taitools.ini"); + + dprintf("--- End mercury_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return mercury_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + mercury_hook_mod = mod; + + hr = process_hijack_startup(mercury_pre_startup, &mercury_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/mercuryhook/elisabeth.c b/mercuryhook/elisabeth.c new file mode 100644 index 0000000..7078a21 --- /dev/null +++ b/mercuryhook/elisabeth.c @@ -0,0 +1,87 @@ +#include +#include + +#include +#include +#include + +#include "mercuryhook/elisabeth.h" +#include "mercuryhook/mercury-dll.h" + +#include "hook/table.h" + +#include "hooklib/uart.h" +#include "hooklib/dll.h" +#include "hooklib/path.h" +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" + +/* Hooks targeted DLLs dynamically loaded by elisabeth. */ + +static void dll_hook_insert_hooks(HMODULE target); +static FARPROC WINAPI my_GetProcAddress(HMODULE hModule, const char *name); +static FARPROC (WINAPI *next_GetProcAddress)(HMODULE hModule, const char *name); +static int my_USBIntLED_Init(); +static int my_USBIntLED_set(); + +static const struct hook_symbol win32_hooks[] = { + { + .name = "GetProcAddress", + .patch = my_GetProcAddress, + .link = (void **) &next_GetProcAddress + } +}; + +HRESULT elisabeth_hook_init(struct elisabeth_config *cfg) +{ + if (!cfg->enable) { + return S_OK; + } + dll_hook_insert_hooks(NULL); + dprintf("Elisabeth: Init\n"); + return S_OK; +} + +static void dll_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "kernel32.dll", + win32_hooks, + _countof(win32_hooks)); +} + +FARPROC WINAPI my_GetProcAddress(HMODULE hModule, const char *name) +{ + uintptr_t ordinal = (uintptr_t) name; + + FARPROC result = next_GetProcAddress(hModule, name); + + if (ordinal > 0xFFFF) { + /* Import by name */ + if (strcmp(name, "USBIntLED_Init") == 0) { + result = (FARPROC) my_USBIntLED_Init; + } + + if (strcmp(name, "USBIntLED_set") == 0) { + result = (FARPROC) my_USBIntLED_set; + } + } + + return result; +} + +/* Intercept the call to initialize the LED board. */ +static int my_USBIntLED_Init() +{ + dprintf("Elisabeth: my_USBIntLED_Init hit!\n"); + return 1; +} + +static int my_USBIntLED_set(int data1, struct led_data data2) +{ + assert(mercury_dll.set_leds != NULL); + mercury_dll.set_leds(data2); + return 1; +} diff --git a/mercuryhook/elisabeth.h b/mercuryhook/elisabeth.h new file mode 100644 index 0000000..5806c99 --- /dev/null +++ b/mercuryhook/elisabeth.h @@ -0,0 +1,13 @@ +#pragma once +#include + +struct led_data { + DWORD unitCount; + uint8_t rgba[480 * 4]; +}; + +struct elisabeth_config { + bool enable; +}; + +HRESULT elisabeth_hook_init(struct elisabeth_config *cfg); diff --git a/mercuryhook/io4.c b/mercuryhook/io4.c new file mode 100644 index 0000000..df22656 --- /dev/null +++ b/mercuryhook/io4.c @@ -0,0 +1,91 @@ +#include + +#include +#include +#include +#include + +#include "board/io4.h" + +#include "mercuryhook/mercury-dll.h" + +#include "util/dprintf.h" + +bool mercury_io_coin = false; +uint16_t mercury_io_coins = 0; + +static HRESULT mercury_io4_poll(void *ctx, struct io4_state *state); + +static const struct io4_ops mercury_io4_ops = { + .poll = mercury_io4_poll, +}; + +HRESULT mercury_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(mercury_dll.init != NULL); + + hr = io4_hook_init(cfg, &mercury_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return mercury_dll.init(); +} + +static HRESULT mercury_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint8_t gamebtn; + HRESULT hr; + + assert(mercury_dll.poll != NULL); + assert(mercury_dll.get_opbtns != NULL); + assert(mercury_dll.get_gamebtns != NULL); + + memset(state, 0, sizeof(*state)); + + hr = mercury_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + gamebtn = 0; + + mercury_dll.get_opbtns(&opbtn); + mercury_dll.get_gamebtns(&gamebtn); + + if (opbtn & MERCURY_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & MERCURY_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & MERCURY_IO_OPBTN_COIN) { + if (!mercury_io_coin) { + mercury_io_coin = true; + mercury_io_coins++; + } + } + else { + mercury_io_coin = false; + } + + state->chutes[0] = 128 + 256 * mercury_io_coins; + + if (gamebtn & MERCURY_IO_GAMEBTN_VOL_UP) { + state->buttons[0] |= 1 << 1; + } + + if (gamebtn & MERCURY_IO_GAMEBTN_VOL_DOWN) { + state->buttons[0] |= 1 << 0; + } + + return S_OK; +} diff --git a/mercuryhook/io4.h b/mercuryhook/io4.h new file mode 100644 index 0000000..87dc6d7 --- /dev/null +++ b/mercuryhook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT mercury_io4_hook_init(const struct io4_config *cfg); diff --git a/mercuryhook/mercury-dll.c b/mercuryhook/mercury-dll.c new file mode 100644 index 0000000..bf21d30 --- /dev/null +++ b/mercuryhook/mercury-dll.c @@ -0,0 +1,118 @@ +#include + +#include +#include + +#include "mercuryhook/mercury-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym mercury_dll_syms[] = { + { + .sym = "mercury_io_init", + .off = offsetof(struct mercury_dll, init), + }, { + .sym = "mercury_io_poll", + .off = offsetof(struct mercury_dll, poll), + }, { + .sym = "mercury_io_get_opbtns", + .off = offsetof(struct mercury_dll, get_opbtns), + }, { + .sym = "mercury_io_get_gamebtns", + .off = offsetof(struct mercury_dll, get_gamebtns), + }, { + .sym = "mercury_io_touch_init", + .off = offsetof(struct mercury_dll, touch_init), + }, { + .sym = "mercury_io_touch_start", + .off = offsetof(struct mercury_dll, touch_start), + }, { + .sym = "mercury_io_touch_set_leds", + .off = offsetof(struct mercury_dll, set_leds), + } +}; + +struct mercury_dll mercury_dll; + +// Copypasta DLL binding and diagnostic message boilerplate. +// Not much of this lends itself to being easily factored out. Also there +// will be a lot of API-specific branching code here eventually as new API +// versions get defined, so even though these functions all look the same +// now this won't remain the case forever. + +HRESULT mercury_dll_init(const struct mercury_dll_config *cfg, HINSTANCE self) +{ + uint16_t (*get_api_version)(void); + const struct dll_bind_sym *sym; + HINSTANCE owned; + HINSTANCE src; + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (cfg->path[0] != L'\0') { + owned = LoadLibraryW(cfg->path); + + if (owned == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Wacca IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Wacca IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "mercury_io_get_api_version"); + + if (get_api_version != NULL) { + mercury_dll.api_version = get_api_version(); + } else { + mercury_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose mercury_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (mercury_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Wacca IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Taitools.\n", + mercury_dll.api_version); + + goto end; + } + + sym = mercury_dll_syms; + hr = dll_bind(&mercury_dll, src, &sym, _countof(mercury_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Wacca IO: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + + goto end; + } else { + dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); + } + } + + owned = NULL; + +end: + if (owned != NULL) { + FreeLibrary(owned); + } + + return hr; +} diff --git a/mercuryhook/mercury-dll.h b/mercuryhook/mercury-dll.h new file mode 100644 index 0000000..fa27edb --- /dev/null +++ b/mercuryhook/mercury-dll.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "mercuryio/mercuryio.h" +#include "mercuryhook/elisabeth.h" + +struct mercury_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint8_t *gamebtn); + HRESULT (*touch_init)(void); + void (*touch_start)(mercury_io_touch_callback_t callback); + void (*set_leds)(struct led_data data); +}; + +struct mercury_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct mercury_dll mercury_dll; + +HRESULT mercury_dll_init(const struct mercury_dll_config *cfg, HINSTANCE self); diff --git a/mercuryhook/mercuryhook.def b/mercuryhook/mercuryhook.def new file mode 100644 index 0000000..32f33c1 --- /dev/null +++ b/mercuryhook/mercuryhook.def @@ -0,0 +1,21 @@ +LIBRARY mercuryhook + +EXPORTS + aime_io_get_api_version + aime_io_init + aime_io_led_set_color + aime_io_nfc_get_aime_id + aime_io_nfc_get_felica_id + aime_io_nfc_poll + amDllVideoClose @2 + amDllVideoGetVBiosVersion @4 + amDllVideoOpen @1 + amDllVideoSetResolution @3 + mercury_io_get_api_version + mercury_io_get_gamebtns + mercury_io_get_opbtns + mercury_io_touch_init + mercury_io_touch_start + mercury_io_touch_set_leds + mercury_io_init + mercury_io_poll diff --git a/mercuryhook/meson.build b/mercuryhook/meson.build new file mode 100644 index 0000000..7a676b2 --- /dev/null +++ b/mercuryhook/meson.build @@ -0,0 +1,34 @@ +shared_library( + 'mercuryhook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'mercuryhook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + ], + link_with : [ + aimeio_lib, + gfxhook_lib, + board_lib, + hooklib_lib, + mercuryio_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'mercury-dll.c', + 'mercury-dll.h', + 'elisabeth.h', + 'elisabeth.c', + 'touch.h', + 'touch.c' + ], +) diff --git a/mercuryhook/touch.c b/mercuryhook/touch.c new file mode 100644 index 0000000..87ba6d5 --- /dev/null +++ b/mercuryhook/touch.c @@ -0,0 +1,518 @@ +#include + +#include +#include +#include +#include +#include + +#include "board/slider-cmd.h" +#include "board/slider-frame.h" + +#include "mercuryhook/mercury-dll.h" +#include "mercuryhook/touch.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/uart.h" +#include "hooklib/fdshark.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +const char SYNC_BOARD_VER[6] = "190523"; +const char UNIT_BOARD_VER[6] = "190514"; + +static HRESULT touch_handle_irp(struct irp *irp); +static HRESULT touch0_handle_irp_locked(struct irp *irp); +static HRESULT touch1_handle_irp_locked(struct irp *irp); + +static HRESULT touch_req_dispatch(const struct touch_req *req); + +static HRESULT touch_frame_decode(struct touch_req *dest, struct iobuf *iobuf, int side); +static uint8_t calc_checksum(const void *ptr, size_t nbytes); + +static HRESULT touch_handle_get_sync_board_ver(const struct touch_req *req); +static HRESULT touch_handle_next_read(const struct touch_req *req); +static HRESULT touch_handle_get_unit_board_ver(const struct touch_req *req); +static HRESULT touch_handle_mystery1(const struct touch_req *req); +static HRESULT touch_handle_mystery2(const struct touch_req *req); +static HRESULT touch_handle_start_auto_scan(const struct touch_req *req); +static void touch_res_auto_scan(const bool *state); + +uint8_t input_frame_count_0 = 0x7b; +uint8_t input_frame_count_1 = 0x7b; +bool touch0_auto = false; +bool touch1_auto = false; + +static CRITICAL_SECTION touch0_lock; +static struct uart touch0_uart; +static uint8_t touch0_written_bytes[520]; +static uint8_t touch0_readable_bytes[520]; + +static CRITICAL_SECTION touch1_lock; +static struct uart touch1_uart; +static uint8_t touch1_written_bytes[520]; +static uint8_t touch1_readable_bytes[520]; + +HRESULT touch_hook_init(const struct touch_config *cfg) +{ + assert(cfg != NULL); + assert(mercury_dll.touch_init != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + InitializeCriticalSection(&touch0_lock); + InitializeCriticalSection(&touch1_lock); + dprintf("Wacca touch: Init\n"); + + uart_init(&touch0_uart, 3); + touch0_uart.written.bytes = touch0_written_bytes; + touch0_uart.written.nbytes = sizeof(touch0_written_bytes); + touch0_uart.readable.bytes = touch0_readable_bytes; + touch0_uart.readable.nbytes = sizeof(touch0_readable_bytes); + + uart_init(&touch1_uart, 4); + touch1_uart.written.bytes = touch1_written_bytes; + touch1_uart.written.nbytes = sizeof(touch1_written_bytes); + touch1_uart.readable.bytes = touch1_readable_bytes; + touch1_uart.readable.nbytes = sizeof(touch1_readable_bytes); + + return iohook_push_handler(touch_handle_irp); +} + +static HRESULT touch_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (uart_match_irp(&touch0_uart, irp)) { + EnterCriticalSection(&touch0_lock); + hr = touch0_handle_irp_locked(irp); + LeaveCriticalSection(&touch0_lock); + } + else if (uart_match_irp(&touch1_uart, irp)) { + EnterCriticalSection(&touch1_lock); + hr = touch1_handle_irp_locked(irp); + LeaveCriticalSection(&touch1_lock); + } + else { + return iohook_invoke_next(irp); + } + + return hr; +} + +static HRESULT touch0_handle_irp_locked(struct irp *irp) +{ + struct touch_req req; + HRESULT hr; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Wacca touch0: Starting backend\n"); + hr = mercury_dll.touch_init(); + + if (FAILED(hr)) { + dprintf("Wacca touch: Backend error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&touch0_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 0 + dprintf("TX0 Buffer:\n"); + dump_iobuf(&touch0_uart.written); +#endif + hr = touch_frame_decode(&req, &touch0_uart.written, 0); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Wacca touch: Deframe error: %x\n", (int) hr); + } + + return hr; + } + + hr = touch_req_dispatch(&req); + + if (FAILED(hr)) { + dprintf("Wacca touch: Processing error: %x\n", (int) hr); + } + + return hr; + } +} + +static HRESULT touch1_handle_irp_locked(struct irp *irp) +{ + struct touch_req req; + HRESULT hr; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Wacca touch1: Starting backend\n"); + hr = mercury_dll.touch_init(); + + if (FAILED(hr)) { + dprintf("Wacca touch: Backend error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&touch1_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 0 + dprintf("TX1 Buffer:\n"); + dump_iobuf(&touch1_uart.written); +#endif + + hr = touch_frame_decode(&req, &touch1_uart.written, 1); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Wacca touch: Deframe error: %x\n", (int) hr); + } + + return hr; + } + + hr = touch_req_dispatch(&req); + + if (FAILED(hr)) { + dprintf("Wacca touch: Processing error: %x\n", (int) hr); + } + + return hr; + } +} + +static HRESULT touch_req_dispatch(const struct touch_req *req) +{ + switch (req->cmd) { + case CMD_GET_SYNC_BOARD_VER: + return touch_handle_get_sync_board_ver(req); + case CMD_NEXT_READ: + return touch_handle_next_read(req); + case CMD_GET_UNIT_BOARD_VER: + return touch_handle_get_unit_board_ver(req); + case CMD_MYSTERY1: + return touch_handle_mystery1(req); + case CMD_MYSTERY2: + return touch_handle_mystery2(req); + case CMD_START_AUTO_SCAN: + return touch_handle_start_auto_scan(req); + case CMD_BEGIN_WRITE: + dprintf("Wacca touch: Begin write for side %d\n", req->side); + return S_OK; + case CMD_NEXT_WRITE: + dprintf("Wacca touch: continue write for side %d\n", req->side); + return S_OK; + default: + dprintf("Wacca touch: Unhandled command %02x\n", req->cmd); + return S_OK; + } +} + +static HRESULT touch_handle_get_sync_board_ver(const struct touch_req *req) +{ + struct touch_resp_get_sync_board_ver resp; + HRESULT hr; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: Get sync board version\n", req->side); + + resp.cmd = 0xa0; + memcpy(resp.version, SYNC_BOARD_VER, sizeof(SYNC_BOARD_VER)); + resp.checksum = 0; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + if (req->side == 0) { + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + + return hr; +} + +static HRESULT touch_handle_next_read(const struct touch_req *req) +{ + struct touch_resp_startup resp; + HRESULT hr; + char *rev; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: Read section %2hx\n", req->side, req->data[2]); + + + switch (req->data[2]) { + // These can be found in the config file + case 0x30: + rev = " 0 0 1 2 3 4 5 15 15 15 15 15 15 11 11 11"; + break; + case 0x31: + rev = " 11 11 11 128 103 103 115 138 127 103 105 111 126 113 95 100"; + break; + case 0x33: + rev = " 101 115 98 86 76 67 68 48 117 0 82 154 0 6 35 4"; + break; + default: + dprintf("Wacca touch: BAD READ REQUEST %2hx\n", req->data[2]); + return 1; + } + + memcpy(resp.data, rev, 80 * sizeof(char)); + resp.checksum = 0; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + if (req->side == 0) { + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + return hr; +} + +static HRESULT touch_handle_get_unit_board_ver(const struct touch_req *req) +{ + struct touch_resp_get_unit_board_ver resp; + HRESULT hr; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: get unit board version\n", req->side); + + memset(resp.version, 0, sizeof(resp.version)); + memcpy(resp.version, SYNC_BOARD_VER, sizeof(SYNC_BOARD_VER)); + + for (int i = 0; i < 6; i++ ) + memcpy(&resp.version[7 + (6 * i)], UNIT_BOARD_VER, sizeof(UNIT_BOARD_VER)); + + resp.cmd = 0xa8; + resp.checksum = 0; + + if (req->side == 0) { + resp.version[6] = 'R'; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + #if 0 + for (int i = 0; i < sizeof(resp.version); i++) { + dprintf("0x%02x ", resp.version[i]); + } + dprintf("\n"); + #endif + + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + resp.version[6] = 'L'; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + #if 0 + for (int i = 0; i < sizeof(resp.version); i++) { + dprintf("0x%02x ", resp.version[i]); + } + dprintf("\n"); + #endif + + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + + return hr; +} + +static HRESULT touch_handle_mystery1(const struct touch_req *req) +{ + struct touch_resp_mystery1 resp; + HRESULT hr; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: Command A2\n", req->side); + + resp.cmd = 0xa2; + resp.data = 0x3f; + resp.checksum = 0; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + if (req->side == 0) { + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + return hr; +} + +static HRESULT touch_handle_mystery2(const struct touch_req *req) +{ + struct touch_resp_mystery2 resp; + HRESULT hr; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: Command 94\n", req->side); + + resp.cmd = 0x94; + resp.data = 0; + resp.checksum = 0; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + if (req->side == 0) { + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + return hr; +} + +static HRESULT touch_handle_start_auto_scan(const struct touch_req *req) +{ + struct touch_resp_start_auto resp; + HRESULT hr; + uint8_t data1[24] = { 0 }; + // Unsure what this does. It seems to change every request on a real board, + // but the game doesn't seem to mind that it's the same + uint8_t data2[9] = { 0x0d, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00 }; + + dprintf("Wacca Touch%d: Start Auto", req->side); + + #if 0 + for (int i = 0; i < req->data_length; i++) + dprintf("0x%02x ", req->data[i]); + #endif + dprintf("\n"); + + resp.cmd = 0x9c; + resp.data = 0; + resp.checksum = 0x49; + + resp.frame.cmd= 0x81; + memcpy(resp.frame.data1, data1, sizeof(data1)); + memcpy(resp.frame.data2, data2, sizeof(data2)); + resp.frame.checksum = 0; + resp.frame.checksum = calc_checksum(&resp.frame, sizeof(resp.frame)); + + if (req->side == 0) { + resp.frame.count = input_frame_count_0++; + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + touch0_auto = true; + } + else { + resp.frame.count = input_frame_count_1++; + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + touch1_auto = true; + } + + mercury_dll.touch_start(touch_res_auto_scan); + return hr; +} + +static void touch_res_auto_scan(const bool *state) +{ + struct touch_input_frame frame0; + struct touch_input_frame frame1; + memset(&frame0, 0, sizeof(frame0)); + memset(&frame1, 0, sizeof(frame1)); + uint8_t dataR[24] = { 0 }; + uint8_t dataL[24] = { 0 }; + // this changes every input on a real board but + // the game doesn't seem to care about it... + uint8_t data2[9] = { 0x0d, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00 }; + uint8_t counter = 0; + + frame0.cmd = 0x81; + frame0.count = input_frame_count_0++; + input_frame_count_0 %= 0x7f; + + frame1.cmd = 0x81; + frame1.count = input_frame_count_1++; + input_frame_count_1 %= 0x7f; + + for (int i = 0; i < 24; i++) { + for (int j = 0; j < 5; j++) { + if (state[counter]) { + dataR[i] |= (1 << j); + } + if (state[counter+120]) { + dataL[i] |= (1 << j); + } + counter++; + } + } + + memcpy(frame0.data1, dataR, sizeof(dataR)); + memcpy(frame0.data2, data2, sizeof(data2)); + + memcpy(frame1.data1, dataL, sizeof(dataL)); + memcpy(frame1.data2, data2, sizeof(data2)); + + frame0.checksum = 0; + frame0.checksum = calc_checksum(&frame0, sizeof(frame0)); + + frame1.checksum = 0; + frame1.checksum = calc_checksum(&frame1, sizeof(frame1)); + + if (touch0_auto) { + //dprintf("Wacca touch: Touch0 auto frame #%2hx sent\n", frame0.count); + EnterCriticalSection(&touch0_lock); + iobuf_write(&touch0_uart.readable, &frame0, sizeof(frame0)); + LeaveCriticalSection(&touch0_lock); + } + + if (touch1_auto) { + //dprintf("Wacca touch: Touch1 auto frame #%2hx sent\n", frame0.count); + EnterCriticalSection(&touch1_lock); + iobuf_write(&touch1_uart.readable, &frame1, sizeof(frame1)); + LeaveCriticalSection(&touch1_lock); + } +} + +/* Decodes the response into a struct that's easier to work with. */ +static HRESULT touch_frame_decode(struct touch_req *dest, struct iobuf *iobuf, int side) +{ + dest->side = side; + dest->cmd = iobuf->bytes[0]; + iobuf->pos--; + dest->data_length = iobuf->pos; + + if (dest->data_length > 0) { + for (int i = 1; i < dest->data_length; i++) { + dest->data[i-1] = iobuf->bytes[i]; + } + } + iobuf->pos -= dest->data_length; + + return S_OK; +} + +/* The last byte of every response is a checksum. + * This checksum is calculated by bitwise XORing + * every byte in the response, then dropping the MSB. + * Thanks the CrazyRedMachine for figuring that out!! + */ +static uint8_t calc_checksum(const void *ptr, size_t nbytes) +{ + const uint8_t *src; + uint8_t checksum = 0; + + src = ptr; + + for (size_t i = 0; i < nbytes; i++) { + //dprintf("Wacca touch: Calculating %2hx\n", src[i]); + checksum = checksum^(src[i]); + } + //dprintf("Wacca touch: Checksum is %2hx\n", checksum&0x7f); + return checksum&0x7f; +} diff --git a/mercuryhook/touch.h b/mercuryhook/touch.h new file mode 100644 index 0000000..cc665c8 --- /dev/null +++ b/mercuryhook/touch.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include + +struct touch_config { + bool enable; +}; + +enum touch_cmd { + CMD_GET_SYNC_BOARD_VER = 0xa0, + CMD_NEXT_READ = 0x72, + CMD_GET_UNIT_BOARD_VER = 0xa8, + CMD_MYSTERY1 = 0xa2, + CMD_MYSTERY2 = 0x94, + CMD_START_AUTO_SCAN = 0xc9, + CMD_BEGIN_WRITE = 0x77, + CMD_NEXT_WRITE = 0x20 +}; + +struct touch_req { + uint8_t side; // COM3 or COM4 + uint8_t cmd; // First byte is the command byte + uint8_t data[256]; // rest of the data goes here + uint8_t data_length; // Size of the data including command byte +}; + +struct touch_input_frame { + uint8_t cmd; + uint8_t data1[24]; + uint8_t data2[9]; + uint8_t count; + uint8_t checksum; +}; + +struct touch_resp_get_sync_board_ver { + uint8_t cmd; + char version[6]; + uint8_t checksum; +}; + +struct touch_resp_startup { + char data[80]; + uint8_t checksum; +}; + +struct touch_resp_get_unit_board_ver { + uint8_t cmd; + uint8_t version[43]; + uint8_t checksum; +}; + +struct touch_resp_mystery1 { + uint8_t cmd; + uint8_t data; + uint8_t checksum; +}; + +struct touch_resp_mystery2 { + uint8_t cmd; + uint8_t data; + uint8_t checksum; +}; + +struct touch_resp_start_auto { + uint8_t cmd; + uint8_t data; + uint8_t checksum; + struct touch_input_frame frame; +}; + +HRESULT touch_hook_init(const struct touch_config *cfg); diff --git a/mercuryio/config.c b/mercuryio/config.c new file mode 100644 index 0000000..f3c8e54 --- /dev/null +++ b/mercuryio/config.c @@ -0,0 +1,44 @@ +#include + +#include +#include +#include + +#include "mercuryio/config.h" + +static const int mercury_io_default_cells[] = { + '1','1','1','2','2','2','3','3','3','4','4','4','5','5','5','6','6','6','7','7','7','8','8','8','9','9','9','0','0','0', + '1','1','1','2','2','2','3','3','3','4','4','4','5','5','5','6','6','6','7','7','7','8','8','8','9','9','9','0','0','0', + 'Q','Q','Q','W','W','W','E','E','E','R','R','R','T','T','T','Y','Y','Y','U','U','U','I','I','I','O','O','O','P','P','P', + 'Q','Q','Q','W','W','W','E','E','E','R','R','R','T','T','T','Y','Y','Y','U','U','U','I','I','I','O','O','O','P','P','P', + 'A','A','A','S','S','S','D','D','D','F','F','F','G','G','G','H','H','H','J','J','J','K','K','K','L','L','L',VK_OEM_1,VK_OEM_1,VK_OEM_1, + 'A','A','A','S','S','S','D','D','D','F','F','F','G','G','G','H','H','H','J','J','J','K','K','K','L','L','L',VK_OEM_1,VK_OEM_1,VK_OEM_1, + 'Z','Z','Z','X','X','X','C','C','C','V','V','V','B','B','B','N','N','N','M','M','M',VK_OEM_COMMA,VK_OEM_COMMA,VK_OEM_COMMA,VK_OEM_PERIOD,VK_OEM_PERIOD,VK_OEM_PERIOD,VK_OEM_2,VK_OEM_2,VK_OEM_2, + 'Z','Z','Z','X','X','X','C','C','C','V','V','V','B','B','B','N','N','N','M','M','M',VK_OEM_COMMA,VK_OEM_COMMA,VK_OEM_COMMA,VK_OEM_PERIOD,VK_OEM_PERIOD,VK_OEM_PERIOD,VK_OEM_2,VK_OEM_2,VK_OEM_2, +}; + +void mercury_io_config_load( + struct mercury_io_config *cfg, + const wchar_t *filename) +{ + wchar_t key[240]; + int i; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", 0x2D, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", 0x2E, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", 0x24, filename); + cfg->vk_vol_up = GetPrivateProfileIntW(L"io4", L"volup", 0x26, filename); + cfg->vk_vol_down = GetPrivateProfileIntW(L"io4", L"voldown", 0x28, filename); + + for (i = 0 ; i < 240 ; i++) { + swprintf_s(key, _countof(key), L"cell%i", i + 1); + cfg->vk_cell[i] = GetPrivateProfileIntW( + L"touch", + key, + mercury_io_default_cells[i], + filename); + } +} diff --git a/mercuryio/config.h b/mercuryio/config.h new file mode 100644 index 0000000..c925764 --- /dev/null +++ b/mercuryio/config.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include + +struct mercury_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + uint8_t vk_vol_up; + uint8_t vk_vol_down; + uint8_t vk_cell[240]; +}; + +void mercury_io_config_load( + struct mercury_io_config *cfg, + const wchar_t *filename); diff --git a/mercuryio/mercuryio.c b/mercuryio/mercuryio.c new file mode 100644 index 0000000..96d4ac0 --- /dev/null +++ b/mercuryio/mercuryio.c @@ -0,0 +1,121 @@ +#include + +#include +#include +#include + +#include "mercuryio/mercuryio.h" +#include "mercuryio/config.h" +#include "mercuryhook/elisabeth.h" + +static unsigned int __stdcall mercury_io_touch_thread_proc(void *ctx); + +static uint8_t mercury_opbtn; +static uint8_t mercury_gamebtn; +static struct mercury_io_config mercury_io_cfg; +static bool mercury_io_touch_stop_flag; +static HANDLE mercury_io_touch_thread; + +uint16_t mercury_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT mercury_io_init(void) +{ + mercury_io_config_load(&mercury_io_cfg, L".\\taitools.ini"); + + return S_OK; +} + +HRESULT mercury_io_poll(void) +{ + mercury_opbtn = 0; + mercury_gamebtn = 0; + + if (GetAsyncKeyState(mercury_io_cfg.vk_test)) { + mercury_opbtn |= MERCURY_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(mercury_io_cfg.vk_service)) { + mercury_opbtn |= MERCURY_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(mercury_io_cfg.vk_coin)) { + mercury_opbtn |= MERCURY_IO_OPBTN_COIN; + } + + if (GetAsyncKeyState(mercury_io_cfg.vk_vol_up)) { + mercury_gamebtn |= MERCURY_IO_GAMEBTN_VOL_UP; + } + + if (GetAsyncKeyState(mercury_io_cfg.vk_vol_down)) { + mercury_gamebtn |= MERCURY_IO_GAMEBTN_VOL_DOWN; + } + + return S_OK; +} + +void mercury_io_get_opbtns(uint8_t *opbtn) +{ + if (opbtn != NULL) { + *opbtn = mercury_opbtn; + } +} + +void mercury_io_get_gamebtns(uint8_t *gamebtn) +{ + if (gamebtn != NULL) { + *gamebtn = mercury_gamebtn; + } +} + +HRESULT mercury_io_touch_init(void) +{ + return S_OK; +} + +void mercury_io_touch_start(mercury_io_touch_callback_t callback) +{ + if (mercury_io_touch_thread != NULL) { + return; + } + + mercury_io_touch_thread = (HANDLE) _beginthreadex( + NULL, + 0, + mercury_io_touch_thread_proc, + callback, + 0, + NULL + ); +} + +void mercury_io_touch_set_leds(struct led_data data) +{ + +} + +static unsigned int __stdcall mercury_io_touch_thread_proc(void *ctx) +{ + mercury_io_touch_callback_t callback; + bool cellPressed[240]; + size_t i; + + callback = ctx; + + while (!mercury_io_touch_stop_flag) { + for (i = 0 ; i < _countof(cellPressed) ; i++) { + if (GetAsyncKeyState(mercury_io_cfg.vk_cell[i])) { + cellPressed[i] = true; + } else { + cellPressed[i] = false; + } + } + + callback(cellPressed); + Sleep(1); + } + + return 0; +} diff --git a/mercuryio/mercuryio.def b/mercuryio/mercuryio.def new file mode 100644 index 0000000..167d1cf --- /dev/null +++ b/mercuryio/mercuryio.def @@ -0,0 +1,11 @@ +LIBRARY mercuryio + +EXPORTS + mercury_io_get_api_version + mercury_io_init + mercury_io_poll + mercury_io_get_opbtns + mercury_io_get_gamebtns + mercury_io_touch_init + mercury_io_touch_start + mercury_io_touch_set_leds \ No newline at end of file diff --git a/mercuryio/mercuryio.h b/mercuryio/mercuryio.h new file mode 100644 index 0000000..4d029ca --- /dev/null +++ b/mercuryio/mercuryio.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include +#include + +#include "mercuryhook/elisabeth.h" + +enum { + MERCURY_IO_OPBTN_TEST = 0x01, + MERCURY_IO_OPBTN_SERVICE = 0x02, + MERCURY_IO_OPBTN_COIN = 0x04, +}; + +enum { + MERCURY_IO_GAMEBTN_VOL_UP = 0x01, + MERCURY_IO_GAMEBTN_VOL_DOWN = 0x02, +}; + +typedef void (*mercury_io_touch_callback_t)(const bool *state); +/* Get the version of the Wacca IO API that this DLL supports. This + function should return a positive 16-bit integer, where the high byte is + the major version and the low byte is the minor version (as defined by the + Semantic Versioning standard). + + The latest API version as of this writing is 0x0100. */ + +uint16_t mercury_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after mercury_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT mercury_io_init(void); + +/* Send any queued outputs (of which there are currently none, though this may + change in subsequent API versions) and retrieve any new inputs. + + Minimum API version: 0x0100 */ + +HRESULT mercury_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + MERCURY_IO_OPBTN enum above: this contains bit mask definitions for button + states returned in *opbtn. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void mercury_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + MERCURY_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into + a left hand side set of inputs and a right hand side set of inputs: the bit + mappings are the same in both cases. + + All buttons are active-high, even though some buttons' electrical signals + on a real cabinet are active-low. + + Minimum API version: 0x0100 */ + +void mercury_io_get_gamebtns(uint8_t *gamebtn); + +HRESULT mercury_io_touch_init(void); + +void mercury_io_touch_start(mercury_io_touch_callback_t callback); + +void mercury_io_touch_set_leds(struct led_data data); diff --git a/mercuryio/meson.build b/mercuryio/meson.build new file mode 100644 index 0000000..2970fa9 --- /dev/null +++ b/mercuryio/meson.build @@ -0,0 +1,13 @@ +mercuryio_lib = static_library( + 'mercuryio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + sources : [ + 'mercuryio.c', + 'mercuryio.h', + 'config.c', + 'config.h', + ], +) \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..daeef33 --- /dev/null +++ b/meson.build @@ -0,0 +1,61 @@ +project( + 'taitools', + 'c', + version: '0.1.0', + default_options: [ + 'werror=true', + ], +) + +add_project_arguments( + '-DCOBJMACROS', + '-DDIRECTINPUT_VERSION=0x0800', + '-DWIN32_LEAN_AND_MEAN', + '-D_WIN32_WINNT=_WIN32_WINNT_WIN7', + '-DMINGW_HAS_SECURE_API=1', + '-Wno-unused', + language: 'c', +) + +# Use get_argument_syntax() instead once Meson 0.49.0 releases +if meson.get_compiler('c').get_id() != 'msvc' + add_project_arguments( + '-ffunction-sections', + '-fdata-sections', + language: 'c', + ) + + add_project_link_arguments( + '-Wl,--enable-stdcall-fixup', + '-Wl,--exclude-all-symbols', + '-Wl,--gc-sections', + '-static-libgcc', + language: 'c', + ) +endif + +cc = meson.get_compiler('c') +shlwapi_lib = cc.find_library('shlwapi') +dinput8_lib = cc.find_library('dinput8') +dxguid_lib = cc.find_library('dxguid') +xinput_lib = cc.find_library('xinput') + +inc = include_directories('.') +capnhook = subproject('capnhook') + +subdir('amex') +subdir('iccard') +subdir('board') +subdir('hooklib') +subdir('jvs') +subdir('platform') +subdir('util') + +subdir('gfxhook') + +subdir('mu3io') +subdir('mercuryio') + +subdir('minihook') +subdir('mu3hook') +subdir('mercuryhook') diff --git a/minihook/dllmain.c b/minihook/dllmain.c new file mode 100644 index 0000000..cc4b402 --- /dev/null +++ b/minihook/dllmain.c @@ -0,0 +1,75 @@ +#include + +#include + +#include "amex/config.h" +#include "amex/ds.h" + +#include "hook/process.h" + +#include "hooklib/spike.h" + +#include "platform/clock.h" +#include "platform/config.h" +#include "platform/ttxsec.h" + +#include "util/dprintf.h" + +static process_entry_t app_startup; + +static DWORD CALLBACK app_pre_startup(void) +{ + struct clock_config clock_cfg; + struct ds_config ds_cfg; + struct ttxsec_config ttxsec_cfg; + HRESULT hr; + + dprintf("--- Begin %s ---\n", __func__); + + clock_config_load(&clock_cfg, L".\\taitools.ini"); + ds_config_load(&ds_cfg, L".\\taitools.ini"); + ttxsec_config_load(&ttxsec_cfg, L".\\taitools.ini"); + spike_hook_init(L".\\taitools.ini"); + + hr = clock_hook_init(&clock_cfg); + + if (FAILED(hr)) { + goto fail; + } + + hr = ttxsec_hook_init(&ttxsec_cfg, 1); + + if (FAILED(hr)) { + goto fail; + } + + hr = ds_hook_init(&ds_cfg); + + if (FAILED(hr)) { + goto fail; + } + + dprintf("--- End %s ---\n", __func__); + + return app_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + hr = process_hijack_startup(app_pre_startup, &app_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/minihook/meson.build b/minihook/meson.build new file mode 100644 index 0000000..8acbf1e --- /dev/null +++ b/minihook/meson.build @@ -0,0 +1,19 @@ +shared_library( + 'minihook', + name_prefix : '', + include_directories: inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + link_with : [ + amex_lib, + hooklib_lib, + platform_lib, + util_lib, + ], + sources : [ + 'dllmain.c', + ], +) diff --git a/mu3hook/config.c b/mu3hook/config.c new file mode 100644 index 0000000..6e3991d --- /dev/null +++ b/mu3hook/config.c @@ -0,0 +1,44 @@ +#include +#include + +#include "board/config.h" + +#include "gfxhook/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "mu3hook/config.h" + +#include "platform/config.h" + +void mu3_dll_config_load( + struct mu3_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"mu3io", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void mu3_hook_config_load( + struct mu3_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + aime_config_load(&cfg->aime, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + gfx_config_load(&cfg->gfx, filename); + mu3_dll_config_load(&cfg->dll, filename); +} diff --git a/mu3hook/config.h b/mu3hook/config.h new file mode 100644 index 0000000..58af239 --- /dev/null +++ b/mu3hook/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "gfxhook/gfx.h" + +#include "hooklib/dvd.h" + +#include "mu3hook/mu3-dll.h" + +#include "platform/config.h" + +struct mu3_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct gfx_config gfx; + struct mu3_dll_config dll; +}; + +void mu3_dll_config_load( + struct mu3_dll_config *cfg, + const wchar_t *filename); + +void mu3_hook_config_load( + struct mu3_hook_config *cfg, + const wchar_t *filename); diff --git a/mu3hook/dllmain.c b/mu3hook/dllmain.c new file mode 100644 index 0000000..23ded9d --- /dev/null +++ b/mu3hook/dllmain.c @@ -0,0 +1,126 @@ +#include + +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "gfxhook/d3d9.h" +#include "gfxhook/d3d11.h" +#include "gfxhook/dxgi.h" +#include "gfxhook/gfx.h" + +#include "hook/process.h" + +#include "hooklib/dvd.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "mu3hook/config.h" +#include "mu3hook/io4.h" +#include "mu3hook/mu3-dll.h" +#include "mu3hook/unity.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" + +static HMODULE mu3_hook_mod; +static process_entry_t mu3_startup; +static struct mu3_hook_config mu3_hook_cfg; + +static DWORD CALLBACK mu3_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin mu3_pre_startup ---\n"); + + /* Load config */ + + mu3_hook_config_load(&mu3_hook_cfg, L".\\taitools.ini"); + + /* Hook Win32 APIs */ + + dvd_hook_init(&mu3_hook_cfg.dvd, mu3_hook_mod); + gfx_hook_init(&mu3_hook_cfg.gfx); + gfx_d3d9_hook_init(&mu3_hook_cfg.gfx, mu3_hook_mod); + gfx_d3d11_hook_init(&mu3_hook_cfg.gfx, mu3_hook_mod); + gfx_dxgi_hook_init(&mu3_hook_cfg.gfx, mu3_hook_mod); + serial_hook_init(); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &mu3_hook_cfg.platform, + "SDDT", + "ACA1", + mu3_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&mu3_hook_cfg.aime, 1, mu3_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(2); + + if (FAILED(hr)) { + goto fail; + } + + hr = mu3_dll_init(&mu3_hook_cfg.dll, mu3_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = mu3_io4_hook_init(&mu3_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize Unity native plugin DLL hooks + + There seems to be an issue with other DLL hooks if `LoadLibraryW` is + hooked earlier in the `mu3hook` initialization. */ + + unity_hook_init(); + + /* Initialize debug helpers */ + + spike_hook_init(L".\\taitools.ini"); + + dprintf("--- End mu3_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return mu3_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + mu3_hook_mod = mod; + + hr = process_hijack_startup(mu3_pre_startup, &mu3_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/mu3hook/io4.c b/mu3hook/io4.c new file mode 100644 index 0000000..7edcb0c --- /dev/null +++ b/mu3hook/io4.c @@ -0,0 +1,120 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "mu3hook/mu3-dll.h" + +#include "util/dprintf.h" + +static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state); + +static const struct io4_ops mu3_io4_ops = { + .poll = mu3_io4_poll, +}; + +HRESULT mu3_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(mu3_dll.init != NULL); + + hr = io4_hook_init(cfg, &mu3_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return mu3_dll.init(); +} + +static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint8_t left; + uint8_t right; + int16_t lever; + HRESULT hr; + + assert(mu3_dll.poll != NULL); + assert(mu3_dll.get_opbtns != NULL); + assert(mu3_dll.get_gamebtns != NULL); + assert(mu3_dll.get_lever != NULL); + + memset(state, 0, sizeof(*state)); + + hr = mu3_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + left = 0; + right = 0; + lever = 0; + + mu3_dll.get_opbtns(&opbtn); + mu3_dll.get_gamebtns(&left, &right); + mu3_dll.get_lever(&lever); + + if (opbtn & MU3_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & MU3_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (left & MU3_IO_GAMEBTN_1) { + state->buttons[0] |= 1 << 0; + } + + if (left & MU3_IO_GAMEBTN_2) { + state->buttons[0] |= 1 << 5; + } + + if (left & MU3_IO_GAMEBTN_3) { + state->buttons[0] |= 1 << 4; + } + + if (right & MU3_IO_GAMEBTN_1) { + state->buttons[0] |= 1 << 1; + } + + if (right & MU3_IO_GAMEBTN_2) { + state->buttons[1] |= 1 << 0; + } + + if (right & MU3_IO_GAMEBTN_3) { + state->buttons[0] |= 1 << 15; + } + + if (left & MU3_IO_GAMEBTN_MENU) { + state->buttons[1] |= 1 << 14; + } + + if (right & MU3_IO_GAMEBTN_MENU) { + state->buttons[0] |= 1 << 13; + } + + if (!(left & MU3_IO_GAMEBTN_SIDE)) { + state->buttons[1] |= 1 << 15; /* L-Side, active-low */ + } + + if (!(right & MU3_IO_GAMEBTN_SIDE)) { + state->buttons[0] |= 1 << 14; /* R-Side, active-low */ + } + + /* Lever increases right-to-left, not left-to-right. + + Use 0x7FFF as the center point instead of 0x8000; the latter would + overflow when the lever pos is INT16_MIN. */ + + state->adcs[0] = 0x7FFF - lever; + + return S_OK; +} diff --git a/mu3hook/io4.h b/mu3hook/io4.h new file mode 100644 index 0000000..acb53b3 --- /dev/null +++ b/mu3hook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT mu3_io4_hook_init(const struct io4_config *cfg); diff --git a/mu3hook/meson.build b/mu3hook/meson.build new file mode 100644 index 0000000..27ba7f7 --- /dev/null +++ b/mu3hook/meson.build @@ -0,0 +1,33 @@ +shared_library( + 'mu3hook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'mu3hook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + gfxhook_lib, + hooklib_lib, + mu3io_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'mu3-dll.c', + 'mu3-dll.h', + 'unity.h', + 'unity.c', + ], +) diff --git a/mu3hook/mu3-dll.c b/mu3hook/mu3-dll.c new file mode 100644 index 0000000..84e785c --- /dev/null +++ b/mu3hook/mu3-dll.c @@ -0,0 +1,112 @@ +#include + +#include +#include + +#include "mu3hook/mu3-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym mu3_dll_syms[] = { + { + .sym = "mu3_io_init", + .off = offsetof(struct mu3_dll, init), + }, { + .sym = "mu3_io_poll", + .off = offsetof(struct mu3_dll, poll), + }, { + .sym = "mu3_io_get_opbtns", + .off = offsetof(struct mu3_dll, get_opbtns), + }, { + .sym = "mu3_io_get_gamebtns", + .off = offsetof(struct mu3_dll, get_gamebtns), + }, { + .sym = "mu3_io_get_lever", + .off = offsetof(struct mu3_dll, get_lever), + } +}; + +struct mu3_dll mu3_dll; + +// Copypasta DLL binding and diagnostic message boilerplate. +// Not much of this lends itself to being easily factored out. Also there +// will be a lot of API-specific branching code here eventually as new API +// versions get defined, so even though these functions all look the same +// now this won't remain the case forever. + +HRESULT mu3_dll_init(const struct mu3_dll_config *cfg, HINSTANCE self) +{ + uint16_t (*get_api_version)(void); + const struct dll_bind_sym *sym; + HINSTANCE owned; + HINSTANCE src; + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (cfg->path[0] != L'\0') { + owned = LoadLibraryW(cfg->path); + + if (owned == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Ongeki IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Ongeki IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "mu3_io_get_api_version"); + + if (get_api_version != NULL) { + mu3_dll.api_version = get_api_version(); + } else { + mu3_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose mu3_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (mu3_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Ongeki IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Taitools.\n", + mu3_dll.api_version); + + goto end; + } + + sym = mu3_dll_syms; + hr = dll_bind(&mu3_dll, src, &sym, _countof(mu3_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Ongeki IO: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + + goto end; + } else { + dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); + } + } + + owned = NULL; + +end: + if (owned != NULL) { + FreeLibrary(owned); + } + + return hr; +} diff --git a/mu3hook/mu3-dll.h b/mu3hook/mu3-dll.h new file mode 100644 index 0000000..41f280f --- /dev/null +++ b/mu3hook/mu3-dll.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "mu3io/mu3io.h" + +struct mu3_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint8_t *left, uint8_t *right); + void (*get_lever)(int16_t *pos); +}; + +struct mu3_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct mu3_dll mu3_dll; + +HRESULT mu3_dll_init(const struct mu3_dll_config *cfg, HINSTANCE self); diff --git a/mu3hook/mu3hook.def b/mu3hook/mu3hook.def new file mode 100644 index 0000000..d90abd5 --- /dev/null +++ b/mu3hook/mu3hook.def @@ -0,0 +1,25 @@ +LIBRARY mu3hook + +EXPORTS + CreateDXGIFactory + CreateDXGIFactory1 + CreateDXGIFactory2 + D3D11CreateDevice + D3D11CreateDeviceAndSwapChain + Direct3DCreate9 + aime_io_get_api_version + aime_io_init + aime_io_led_set_color + aime_io_nfc_get_aime_id + aime_io_nfc_get_felica_id + aime_io_nfc_poll + amDllVideoClose @2 + amDllVideoGetVBiosVersion @4 + amDllVideoOpen @1 + amDllVideoSetResolution @3 + mu3_io_get_api_version + mu3_io_get_gamebtns + mu3_io_get_lever + mu3_io_get_opbtns + mu3_io_init + mu3_io_poll diff --git a/mu3hook/unity.c b/mu3hook/unity.c new file mode 100644 index 0000000..efefc32 --- /dev/null +++ b/mu3hook/unity.c @@ -0,0 +1,95 @@ +#include + +#include + +#include "hook/table.h" + +#include "hooklib/dll.h" +#include "hooklib/path.h" + +#include "util/dprintf.h" + +static void dll_hook_insert_hooks(HMODULE target); + +static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name); +static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name); + +static const struct hook_symbol unity_kernel32_syms[] = { + { + .name = "LoadLibraryW", + .patch = my_LoadLibraryW, + .link = (void **) &next_LoadLibraryW, + }, +}; + +static const wchar_t *target_modules[] = { + L"mono.dll", + L"cri_ware_unity.dll", +}; +static const size_t target_modules_len = _countof(target_modules); + +void unity_hook_init(void) +{ + dll_hook_insert_hooks(NULL); +} + +static void dll_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "kernel32.dll", + unity_kernel32_syms, + _countof(unity_kernel32_syms)); +} + +static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name) +{ + const wchar_t *name_end; + const wchar_t *target_module; + bool already_loaded; + HMODULE result; + size_t name_len; + size_t target_module_len; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + // Check if the module is already loaded + already_loaded = GetModuleHandleW(name) != NULL; + + // Must call the next handler so the DLL reference count is incremented + result = next_LoadLibraryW(name); + + if (!already_loaded && result != NULL) { + name_len = wcslen(name); + + for (size_t i = 0; i < target_modules_len; i++) { + target_module = target_modules[i]; + target_module_len = wcslen(target_module); + + // Check if the newly loaded library is at least the length of + // the name of the target module + if (name_len < target_module_len) { + continue; + } + + name_end = &name[name_len - target_module_len]; + + // Check if the name of the newly loaded library is one of the + // modules the path hooks should be injected into + if (_wcsicmp(name_end, target_module) != 0) { + continue; + } + + dprintf("Unity: Loaded %S\n", target_module); + + dll_hook_insert_hooks(result); + path_hook_insert_hooks(result); + } + } + + return result; +} diff --git a/mu3hook/unity.h b/mu3hook/unity.h new file mode 100644 index 0000000..99c3bd9 --- /dev/null +++ b/mu3hook/unity.h @@ -0,0 +1,3 @@ +#pragma once + +void unity_hook_init(void); diff --git a/mu3io/meson.build b/mu3io/meson.build new file mode 100644 index 0000000..3d6e60e --- /dev/null +++ b/mu3io/meson.build @@ -0,0 +1,14 @@ +mu3io_lib = static_library( + 'mu3io', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + xinput_lib, + ], + sources : [ + 'mu3io.c', + 'mu3io.h', + ], +) diff --git a/mu3io/mu3io.c b/mu3io/mu3io.c new file mode 100644 index 0000000..0bbd37f --- /dev/null +++ b/mu3io/mu3io.c @@ -0,0 +1,148 @@ +#include +#include + +#include +#include + +#include "mu3io/mu3io.h" + +static uint8_t mu3_opbtn; +static uint8_t mu3_left_btn; +static uint8_t mu3_right_btn; +static int16_t mu3_lever_pos; +static int16_t mu3_lever_xpos; + +uint16_t mu3_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT mu3_io_init(void) +{ + return S_OK; +} + +HRESULT mu3_io_poll(void) +{ + int lever; + int xlever; + XINPUT_STATE xi; + WORD xb; + + mu3_opbtn = 0; + mu3_left_btn = 0; + mu3_right_btn = 0; + + if (GetAsyncKeyState('1') & 0x8000) { + mu3_opbtn |= MU3_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState('2') & 0x8000) { + mu3_opbtn |= MU3_IO_OPBTN_SERVICE; + } + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + if (xb & XINPUT_GAMEPAD_DPAD_LEFT) { + mu3_left_btn |= MU3_IO_GAMEBTN_1; + } + + if (xb & XINPUT_GAMEPAD_DPAD_UP) { + mu3_left_btn |= MU3_IO_GAMEBTN_2; + } + + if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) { + mu3_left_btn |= MU3_IO_GAMEBTN_3; + } + + if (xb & XINPUT_GAMEPAD_X) { + mu3_right_btn |= MU3_IO_GAMEBTN_1; + } + + if (xb & XINPUT_GAMEPAD_Y) { + mu3_right_btn |= MU3_IO_GAMEBTN_2; + } + + if (xb & XINPUT_GAMEPAD_B) { + mu3_right_btn |= MU3_IO_GAMEBTN_3; + } + + if (xb & XINPUT_GAMEPAD_BACK) { + mu3_left_btn |= MU3_IO_GAMEBTN_MENU; + } + + if (xb & XINPUT_GAMEPAD_START) { + mu3_right_btn |= MU3_IO_GAMEBTN_MENU; + } + + if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) { + mu3_left_btn |= MU3_IO_GAMEBTN_SIDE; + } + + if (xb & XINPUT_GAMEPAD_RIGHT_SHOULDER) { + mu3_right_btn |= MU3_IO_GAMEBTN_SIDE; + } + + lever = mu3_lever_pos; + + if (abs(xi.Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) { + lever += xi.Gamepad.sThumbLX / 24; + } + + if (abs(xi.Gamepad.sThumbRX) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) { + lever += xi.Gamepad.sThumbRX / 24; + } + + if (lever < INT16_MIN) { + lever = INT16_MIN; + } + + if (lever > INT16_MAX) { + lever = INT16_MAX; + } + + mu3_lever_pos = lever; + + xlever = mu3_lever_pos + - xi.Gamepad.bLeftTrigger * 64 + + xi.Gamepad.bRightTrigger * 64; + + if (xlever < INT16_MIN) { + xlever = INT16_MIN; + } + + if (xlever > INT16_MAX) { + xlever = INT16_MAX; + } + + mu3_lever_xpos = xlever; + + return S_OK; +} + +void mu3_io_get_opbtns(uint8_t *opbtn) +{ + if (opbtn != NULL) { + *opbtn = mu3_opbtn; + } +} + +void mu3_io_get_gamebtns(uint8_t *left, uint8_t *right) +{ + if (left != NULL) { + *left = mu3_left_btn; + } + + if (right != NULL ){ + *right = mu3_right_btn; + } +} + +void mu3_io_get_lever(int16_t *pos) +{ + if (pos != NULL) { + *pos = mu3_lever_xpos; + } +} diff --git a/mu3io/mu3io.h b/mu3io/mu3io.h new file mode 100644 index 0000000..d46a475 --- /dev/null +++ b/mu3io/mu3io.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#include + +enum { + MU3_IO_OPBTN_TEST = 0x01, + MU3_IO_OPBTN_SERVICE = 0x02, +}; + +enum { + MU3_IO_GAMEBTN_1 = 0x01, + MU3_IO_GAMEBTN_2 = 0x02, + MU3_IO_GAMEBTN_3 = 0x04, + MU3_IO_GAMEBTN_SIDE = 0x08, + MU3_IO_GAMEBTN_MENU = 0x10, +}; + +/* Get the version of the Ongeki IO API that this DLL supports. This + function should return a positive 16-bit integer, where the high byte is + the major version and the low byte is the minor version (as defined by the + Semantic Versioning standard). + + The latest API version as of this writing is 0x0100. */ + +uint16_t mu3_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after mu3_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT mu3_io_init(void); + +/* Send any queued outputs (of which there are currently none, though this may + change in subsequent API versions) and retrieve any new inputs. + + Minimum API version: 0x0100 */ + +HRESULT mu3_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + MU3_IO_OPBTN enum above: this contains bit mask definitions for button + states returned in *opbtn. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void mu3_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + MU3_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into + a left hand side set of inputs and a right hand side set of inputs: the bit + mappings are the same in both cases. + + All buttons are active-high, even though some buttons' electrical signals + on a real cabinet are active-low. + + Minimum API version: 0x0100 */ + +void mu3_io_get_gamebtns(uint8_t *left, uint8_t *right); + +/* Get the position of the cabinet lever as of the last poll. The center + position should be equal to or close to zero. + + The operator will be required to calibrate the lever's range of motion on + first power-on, so the lever position reported through this API does not + need to perfectly centered or cover every single position value possible, + but it should be reasonably close in order to make things easier for the + operator. + + The calibration screen displays the leftmost and rightmost position signal + returned from the cabinet's ADC encoder as a pair of raw two's complement + hexadecimal values. On a real cabinet these leftmost and rightmost + positions are somewhere around 0xB000 and 0x5000 respectively (remember + that negative values i.e. left positions have a high most-significant bit), + although these values can easily vary by +/- 0x1000 across different + cabinets. + + Minimum API version: 0x0100 */ + +void mu3_io_get_lever(int16_t *pos); diff --git a/platform/clock.c b/platform/clock.c new file mode 100644 index 0000000..b67b111 --- /dev/null +++ b/platform/clock.c @@ -0,0 +1,259 @@ +#include + +#include +#include + +#include "hook/table.h" + +#include "platform/clock.h" + +#include "util/dprintf.h" + +static void WINAPI my_GetSystemTimeAsFileTime(FILETIME *out); +static BOOL WINAPI my_GetLocalTime(SYSTEMTIME *out); +static BOOL WINAPI my_GetSystemTime(SYSTEMTIME *out); +static DWORD WINAPI my_GetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo); +static BOOL WINAPI my_SetLocalTime(SYSTEMTIME *in); +static BOOL WINAPI my_SetSystemTime(SYSTEMTIME *in); +static BOOL WINAPI my_SetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo); + +static BOOL (WINAPI * next_GetSystemTimeAsFileTime)(FILETIME *out); +static int64_t clock_current_day; +static bool clock_time_warp; + +static const struct hook_symbol clock_base_hook_syms[] = { + { + .name = "GetSystemTimeAsFileTime", + .patch = my_GetSystemTimeAsFileTime, + .link = (void **) &next_GetSystemTimeAsFileTime, + } +}; + +static const struct hook_symbol clock_read_hook_syms[] = { + { + .name = "GetLocalTime", + .patch = my_GetLocalTime, + }, { + .name = "GetSystemTime", + .patch = my_GetSystemTime, + }, { + .name = "GetTimeZoneInformation", + .patch = my_GetTimeZoneInformation, + }, +}; + +static const struct hook_symbol clock_write_hook_syms[] = { + { + .name = "SetLocalTime", + .patch = my_SetLocalTime, + }, { + .name = "SetSystemTime", + .patch = my_SetSystemTime, + }, { + .name = "SetTimeZoneInformation", + .patch = my_SetTimeZoneInformation, + }, +}; + +/* FILETIME is expressed in 100ns i.e. 0.1us i.e. 10^-7 sec units. + No official name for these units is given so let's call them "jiffies". */ + +#define jiffies_per_sec 10000000LL +#define jiffies_per_hour (jiffies_per_sec * 3600LL) +#define jiffies_per_day (jiffies_per_hour * 24LL) + +static void WINAPI my_GetSystemTimeAsFileTime(FILETIME *out) +{ + FILETIME in; + int64_t day; + int64_t real_jiffies; + int64_t real_jiffies_biased; + int64_t real_time; + int64_t fake_time; + int64_t fake_jiffies_biased; + int64_t fake_jiffies; + + if (!clock_time_warp) { + next_GetSystemTimeAsFileTime(out); + + return; + } + + if (out == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return; + } + + /* Get and convert real jiffies */ + + next_GetSystemTimeAsFileTime(&in); + real_jiffies = (((int64_t) in.dwHighDateTime) << 32) | in.dwLowDateTime; + + /* Keepout period is JST [02:00, 07:00), which is equivalent to + UTC [17:00, 22:00). Bias UTC forward by 2 hours, changing this interval + to [19:00, 00:00) to make the math easier. We revert this bias later. */ + + real_jiffies_biased = real_jiffies + 2LL * jiffies_per_hour; + + /* Split date and time */ + + day = real_jiffies_biased / jiffies_per_day; + real_time = real_jiffies_biased % jiffies_per_day; + + /* Debug log */ + + if (clock_current_day != 0 && clock_current_day != day) { + dprintf("\n*** CLOCK JUMP! ***\n\n"); + } + + clock_current_day = day; + + /* We want to skip the final five hours of our UTC+2 biased reference frame, + so scale time-of-day by 19/24. */ + + fake_time = (real_time * 19LL) / 24LL; + + /* Un-split date and time */ + + fake_jiffies_biased = day * jiffies_per_day + fake_time; + + /* Revert bias */ + + fake_jiffies = fake_jiffies_biased - 2LL * jiffies_per_hour; + + /* Return result */ + + out->dwLowDateTime = fake_jiffies; + out->dwHighDateTime = fake_jiffies >> 32; +} + +static BOOL WINAPI my_GetLocalTime(SYSTEMTIME *out) +{ + ULARGE_INTEGER arith; + FILETIME linear; + + /* Force JST */ + + my_GetSystemTimeAsFileTime(&linear); + + arith.LowPart = linear.dwLowDateTime; + arith.HighPart = linear.dwHighDateTime; + arith.QuadPart += 9ULL * jiffies_per_hour; + linear.dwLowDateTime = arith.LowPart; + linear.dwHighDateTime = arith.HighPart; + + return FileTimeToSystemTime(&linear, out); +} + +static BOOL WINAPI my_GetSystemTime(SYSTEMTIME *out) +{ + FILETIME linear; + BOOL ok; + + my_GetSystemTimeAsFileTime(&linear); + ok = FileTimeToSystemTime(&linear, out); + + if (!ok) { + return ok; + } + +#if 0 + static int last_second; + + if (out->wSecond != last_second) { + dprintf("%04i/%02i/%02i %02i:%02i:%02i\n", + out->wYear, + out->wMonth, + out->wDay, + out->wHour, + out->wMinute, + out->wSecond); + } + + last_second = out->wSecond; +#endif + + return TRUE; +} + +static DWORD WINAPI my_GetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo) +{ + dprintf("Clock: Returning JST timezone\n"); + + if (tzinfo == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return TIME_ZONE_ID_INVALID; + } + + /* Force JST (UTC+9), SEGA games malfunction in any other time zone. + Strings and boundary times don't matter, we only set the offset. */ + + memset(tzinfo, 0, sizeof(*tzinfo)); + tzinfo->Bias = -9 * 60; + + SetLastError(ERROR_SUCCESS); + + /* "Unknown" here means that this region does not observe DST */ + + return TIME_ZONE_ID_UNKNOWN; +} + +static BOOL WINAPI my_SetLocalTime(SYSTEMTIME *in) +{ + dprintf("Clock: Blocked local time update\n"); + + return TRUE; +} + +static BOOL WINAPI my_SetSystemTime(SYSTEMTIME *in) +{ + dprintf("Clock: Blocked system time update\n"); + + return TRUE; +} + +static BOOL WINAPI my_SetTimeZoneInformation(TIME_ZONE_INFORMATION *in) +{ + dprintf("Clock: Blocked timezone update\n"); + + return TRUE; +} + +HRESULT clock_hook_init(const struct clock_config *cfg) +{ + assert(cfg != NULL); + + clock_time_warp = cfg->timewarp; + + if (cfg->timezone || cfg->timewarp || !cfg->writeable) { + /* All the clock hooks require the core GSTAFT hook to be installed */ + /* Note the ! up there btw. */ + + hook_table_apply( + NULL, + "kernel32.dll", + clock_base_hook_syms, + _countof(clock_base_hook_syms)); + } + + if (cfg->timezone) { + hook_table_apply( + NULL, + "kernel32.dll", + clock_read_hook_syms, + _countof(clock_read_hook_syms)); + } + + if (!cfg->writeable) { + /* Install hook if this config parameter is FALSE! */ + hook_table_apply( + NULL, + "kernel32.dll", + clock_write_hook_syms, + _countof(clock_write_hook_syms)); + } + + return S_OK; +} diff --git a/platform/clock.h b/platform/clock.h new file mode 100644 index 0000000..4d67754 --- /dev/null +++ b/platform/clock.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include + +struct clock_config { + bool timezone; + bool timewarp; + bool writeable; +}; + +HRESULT clock_hook_init(const struct clock_config *cfg); diff --git a/platform/config.c b/platform/config.c new file mode 100644 index 0000000..86cb9c8 --- /dev/null +++ b/platform/config.c @@ -0,0 +1,278 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "platform/clock.h" +#include "platform/config.h" +#include "platform/dns.h" +#include "platform/misc.h" +#include "platform/netenv.h" +#include "platform/ttxsec.h" +#include "platform/platform.h" +#include "platform/syscfg.h" +#include "platform/vfs.h" + +void platform_config_load(struct platform_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + clock_config_load(&cfg->clock, filename); + dns_config_load(&cfg->dns, filename); + misc_config_load(&cfg->misc, filename); + netenv_config_load(&cfg->netenv, filename); + ttxsec_config_load(&cfg->ttxsec, filename); + vfs_config_load(&cfg->vfs, filename); + syscfg_config_load(&cfg->syscfg, filename); +} + +void clock_config_load(struct clock_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->timezone = GetPrivateProfileIntW(L"clock", L"timezone", 1, filename); + cfg->timewarp = GetPrivateProfileIntW(L"clock", L"timewarp", 0, filename); + cfg->writeable = GetPrivateProfileIntW( + L"clock", + L"writeable", + 0, + filename); +} + +void dns_config_load(struct dns_config *cfg, const wchar_t *filename) +{ + wchar_t default_[128]; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"dns", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"dns", + L"default", + L"localhost", + default_, + _countof(default_), + filename); + + GetPrivateProfileStringW( + L"dns", + L"router", + default_, + cfg->router, + _countof(cfg->router), + filename); + + GetPrivateProfileStringW( + L"dns", + L"cert", + default_, + cfg->cert, + _countof(cfg->cert), + filename); + + GetPrivateProfileStringW( + L"dns", + L"data", + default_, + cfg->data, + _countof(cfg->data), + filename); + + GetPrivateProfileStringW( + L"dns", + L"proxy", + default_, + cfg->proxy, + _countof(cfg->proxy), + filename); + + GetPrivateProfileStringW( + L"dns", + L"nesys", + default_, + cfg->nesys, + _countof(cfg->nesys), + filename); + + GetPrivateProfileStringW( + L"dns", + L"fjm", + default_, + cfg->fjm, + _countof(cfg->fjm), + filename); +} + +void misc_config_load(struct misc_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"misc", L"enable", 1, filename); +} + +void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename) +{ + wchar_t mac_addr[18]; + + assert(cfg != NULL); + assert(filename != NULL); + + memset(cfg, 0, sizeof(*cfg)); + + cfg->enable = GetPrivateProfileIntW(L"netenv", L"enable", 0, filename); + + cfg->addr_suffix = GetPrivateProfileIntW( + L"netenv", + L"addrSuffix", + 11, + filename); + + cfg->router_suffix = GetPrivateProfileIntW( + L"netenv", + L"routerSuffix", + 254, + filename); + + GetPrivateProfileStringW( + L"netenv", + L"macAddr", + L"01:02:03:04:05:06", + mac_addr, + _countof(mac_addr), + filename); + + swscanf(mac_addr, + L"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &cfg->mac_addr[0], + &cfg->mac_addr[1], + &cfg->mac_addr[2], + &cfg->mac_addr[3], + &cfg->mac_addr[4], + &cfg->mac_addr[5], + &cfg->mac_addr[6]); +} + +void ttxsec_config_load(struct ttxsec_config *cfg, const wchar_t *filename) +{ + wchar_t keychip_id[17]; + wchar_t game_id[5]; + wchar_t platform_id[5]; + wchar_t subnet[16]; + unsigned int ip[4]; + size_t i; + + assert(cfg != NULL); + assert(filename != NULL); + + memset(cfg, 0, sizeof(*cfg)); + memset(keychip_id, 0, sizeof(keychip_id)); + memset(game_id, 0, sizeof(game_id)); + memset(platform_id, 0, sizeof(platform_id)); + memset(subnet, 0, sizeof(subnet)); + + cfg->enable = GetPrivateProfileIntW(L"keychip", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"keychip", + L"id", + L"A69E-01A88888888", + keychip_id, + _countof(keychip_id), + filename); + + GetPrivateProfileStringW( + L"keychip", + L"gameId", + L"", + game_id, + _countof(game_id), + filename); + + GetPrivateProfileStringW( + L"keychip", + L"platformId", + L"", + platform_id, + _countof(platform_id), + filename); + + cfg->region = GetPrivateProfileIntW(L"keychip", L"region", 1, filename); + cfg->billing_type = GetPrivateProfileIntW(L"keychip", L"billingType", 1, filename); + cfg->system_flag = GetPrivateProfileIntW( + L"keychip", + L"systemFlag", + 0x64, + filename); + + GetPrivateProfileStringW( + L"keychip", + L"subnet", + L"192.168.100.0", + subnet, + _countof(subnet), + filename); + + for (i = 0 ; i < 16 ; i++) { + cfg->keychip_id[i] = (char) keychip_id[i]; + } + + for (i = 0 ; i < 4 ; i++) { + cfg->game_id[i] = (char) game_id[i]; + } + + for (i = 0 ; i < 4 ; i++) { + cfg->platform_id[i] = (char) platform_id[i]; + } + + swscanf(subnet, L"%u.%u.%u.%u", &ip[0], &ip[1], &ip[2], &ip[3]); + cfg->subnet = (ip[0] << 24) | (ip[1] << 16) | (ip[2] << 8) | 0; + + GetPrivateProfileStringW( + L"keychip", + L"billingCa", + L"DEVICE\\ca.crt", + cfg->billing_ca, + _countof(cfg->billing_ca), + filename); + + GetPrivateProfileStringW( + L"keychip", + L"billingPub", + L"DEVICE\\billing.pub", + cfg->billing_pub, + _countof(cfg->billing_pub), + filename); +} + +void syscfg_config_load(struct syscfg_config *cfg, const wchar_t *filename) +{ + cfg->enable = GetPrivateProfileIntW(L"syscfg", L"enable", 1, filename); + cfg->log_level = GetPrivateProfileIntW(L"syscfg", L"log_level", 1, filename); +} + +void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"vfs", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"vfs", + L"d_drive", + L"d_drive", + cfg->d_drive, + _countof(cfg->d_drive), + filename); +} diff --git a/platform/config.h b/platform/config.h new file mode 100644 index 0000000..c22a552 --- /dev/null +++ b/platform/config.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include +#include + +#include "platform/clock.h" +#include "platform/dns.h" +#include "platform/misc.h" +#include "platform/netenv.h" +#include "platform/ttxsec.h" +#include "platform/platform.h" +#include "platform/syscfg.h" +#include "platform/vfs.h" + +void platform_config_load( + struct platform_config *cfg, + const wchar_t *filename); + +void clock_config_load(struct clock_config *cfg, const wchar_t *filename); +void dns_config_load(struct dns_config *cfg, const wchar_t *filename); +void misc_config_load(struct misc_config *cfg, const wchar_t *filename); +void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename); +void ttxsec_config_load(struct ttxsec_config *cfg, const wchar_t *filename); +void syscfg_config_load(struct syscfg_config *cfg, const wchar_t *filename); +void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename); diff --git a/platform/dns.c b/platform/dns.c new file mode 100644 index 0000000..a57f8da --- /dev/null +++ b/platform/dns.c @@ -0,0 +1,68 @@ +#include + +#include + +#include "hooklib/dns.h" + +#include "platform/dns.h" + +HRESULT dns_platform_hook_init(const struct dns_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + hr = dns_hook_push(L"cert.nesys.jp", cfg->cert); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"cert1.nesys.jp", cfg->cert); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"cert2.nesys.jp", cfg->cert); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"cert3.nesys.jp", cfg->cert); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"data.nesys.jp", cfg->data); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"proxy.nesys.jp", cfg->proxy); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"nesys.taito.co.jp", cfg->nesys); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"fjm170920zero.nesica.net", cfg->fjm); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} diff --git a/platform/dns.h b/platform/dns.h new file mode 100644 index 0000000..a973914 --- /dev/null +++ b/platform/dns.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include +#include + +struct dns_config { + bool enable; + wchar_t router[128]; + wchar_t cert[128]; + wchar_t data[128]; + wchar_t proxy[128]; + wchar_t nesys[128]; + wchar_t fjm[128]; +}; + +HRESULT dns_platform_hook_init(const struct dns_config *cfg); diff --git a/platform/meson.build b/platform/meson.build new file mode 100644 index 0000000..368c5af --- /dev/null +++ b/platform/meson.build @@ -0,0 +1,30 @@ +platform_lib = static_library( + 'platform', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + shlwapi_lib, + ], + sources : [ + 'clock.c', + 'clock.h', + 'config.c', + 'config.h', + 'dns.c', + 'dns.h', + 'misc.c', + 'misc.h', + 'netenv.c', + 'netenv.h', + 'ttxsec.c', + 'ttxsec.h', + 'platform.c', + 'platform.h', + 'vfs.c', + 'vfs.h', + 'syscfg.c', + 'syscfg.h', + ], +) diff --git a/platform/misc.c b/platform/misc.c new file mode 100644 index 0000000..b3fdaf6 --- /dev/null +++ b/platform/misc.c @@ -0,0 +1,162 @@ +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/reg.h" + +#include "platform/misc.h" + +#include "util/dprintf.h" + +static BOOL WINAPI misc_ExitWindowsEx(unsigned int flags, uint32_t reason); + +static HRESULT misc_read_os_version(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_app_loader_count(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_cpu_temp_error(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_cpu_temp_warning(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_platform_id(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes); + +static const struct hook_symbol misc_syms[] = { + { + .name = "ExitWindowsEx", + .patch = misc_ExitWindowsEx, + } +}; + +static const struct reg_hook_val misc_root_keys[] = { + { + .name = L"OSVersion", + .read = misc_read_os_version, + .type = REG_SZ, + } +}; + +static const struct reg_hook_val misc_master_keys[] = { + { + .name = L"AppLoaderCount", + .read = misc_read_app_loader_count, + .type = REG_DWORD, + }, { + /* Black-hole val, list it here so we don't get a warning msg */ + .name = L"NextProcess", + .type = REG_SZ, + }, { + /* ditto */ + .name = L"SystemError", + .type = REG_SZ, + } +}; + +static const struct reg_hook_val misc_static_keys[] = { + { + .name = L"CpuTempError", + .read = misc_read_cpu_temp_error, + .type = REG_DWORD, + }, { + .name = L"CpuTempWarning", + .read = misc_read_cpu_temp_warning, + .type = REG_DWORD, + }, { + .name = L"PlatformId", + .read = misc_read_platform_id, + .type = REG_SZ, + }, { + .name = L"PlatformName", + .read = misc_read_platform_name, + .type = REG_SZ, + } +}; + +static wchar_t misc_platform_id[5]; + +HRESULT misc_hook_init(const struct misc_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + /* Add hardcoded dummy keys */ + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty", + misc_root_keys, + _countof(misc_root_keys)); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty\\static", + misc_static_keys, + _countof(misc_static_keys)); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty\\Master", + misc_master_keys, + _countof(misc_master_keys)); + + if (FAILED(hr)) { + return hr; + } + + /* Apply function hooks */ + + hook_table_apply(NULL, "user32.dll", misc_syms, _countof(misc_syms)); + + return S_OK; +} + +static BOOL WINAPI misc_ExitWindowsEx(unsigned int flags, uint32_t reason) +{ + dprintf("Misc: Blocked system reboot\n"); + + return TRUE; +} + +static HRESULT misc_read_os_version(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"0_0_0"); +} + +static HRESULT misc_read_app_loader_count(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 1); +} + +static HRESULT misc_read_cpu_temp_error(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 100); +} + +static HRESULT misc_read_cpu_temp_warning(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 95); +} + +static HRESULT misc_read_platform_id(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, misc_platform_id); +} + +static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"ALLS MX2.1"); // TODO: Dynamic +} diff --git a/platform/misc.h b/platform/misc.h new file mode 100644 index 0000000..ec4f06f --- /dev/null +++ b/platform/misc.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct misc_config { + bool enable; +}; + +HRESULT misc_hook_init(const struct misc_config *cfg); diff --git a/platform/netenv.c b/platform/netenv.c new file mode 100644 index 0000000..4515b6b --- /dev/null +++ b/platform/netenv.c @@ -0,0 +1,498 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "hook/table.h" + +#include "platform/netenv.h" +#include "platform/ttxsec.h" + +#include "util/dprintf.h" + +struct netenv { + IP_ADAPTER_ADDRESSES head; + char name[64]; + wchar_t dns_suffix[64]; + wchar_t description[64]; + wchar_t friendly_name[64]; + IP_ADAPTER_PREFIX prefix; + IP_ADAPTER_UNICAST_ADDRESS iface; + IP_ADAPTER_GATEWAY_ADDRESS router; + IP_ADAPTER_DNS_SERVER_ADDRESS dns; + struct sockaddr_in prefix_sa; + struct sockaddr_in iface_sa; + struct sockaddr_in router_sa; + struct sockaddr_in dns_sa; +}; + +/* Hook functions */ + +static uint32_t WINAPI hook_GetAdaptersAddresses( + uint32_t Family, + uint32_t Flags, + void *Reserved, + IP_ADAPTER_ADDRESSES *AdapterAddresses, + uint32_t *SizePointer); + +static uint32_t WINAPI hook_GetAdaptersInfo( + IP_ADAPTER_INFO *AdapterInfo, + uint32_t *SizePointer); + +static uint32_t WINAPI hook_GetBestRoute( + uint32_t src_ip, + uint32_t dest_ip, + MIB_IPFORWARDROW *route); + +static uint32_t WINAPI hook_GetIfTable( + MIB_IFTABLE *pIfTable, + uint32_t *pdwSize, + BOOL bOrder); + +static uint32_t WINAPI hook_IcmpSendEcho2( + HANDLE IcmpHandle, + HANDLE Event, + PIO_APC_ROUTINE ApcRoutine, + void *ApcContext, + uint32_t DestinationAddress, + void *RequestData, + uint16_t RequestSize, + IP_OPTION_INFORMATION *RequestOptions, + void *ReplyBuffer, + uint32_t ReplySize, + uint32_t Timeout); + +/* Link pointers */ + +static uint32_t (WINAPI *next_GetAdaptersAddresses)( + uint32_t Family, + uint32_t Flags, + void *Reserved, + IP_ADAPTER_ADDRESSES *AdapterAddresses, + uint32_t *SizePointer); + +static uint32_t (WINAPI *next_GetAdaptersInfo)( + IP_ADAPTER_INFO *AdapterInfo, + uint32_t *SizePointer); + +static uint32_t (WINAPI *next_GetBestRoute)( + uint32_t src_ip, + uint32_t dest_ip, + MIB_IPFORWARDROW *route); + +static uint32_t (WINAPI *next_GetIfTable)( + MIB_IFTABLE *pIfTable, + uint32_t *pdwSize, + BOOL bOrder); + +static uint32_t (WINAPI *next_IcmpSendEcho2)( + HANDLE IcmpHandle, + HANDLE Event, + PIO_APC_ROUTINE ApcRoutine, + void *ApcContext, + uint32_t DestinationAddress, + void *RequestData, + uint16_t RequestSize, + IP_OPTION_INFORMATION *RequestOptions, + void *ReplyBuffer, + uint32_t ReplySize, + uint32_t Timeout); + +static const struct hook_symbol netenv_hook_syms[] = { + { + .name = "GetAdaptersAddresses", + .patch = hook_GetAdaptersAddresses, + .link = (void **) &next_GetAdaptersAddresses, + }, { + .name = "GetAdaptersInfo", + .patch = hook_GetAdaptersInfo, + .link = (void **) &next_GetAdaptersInfo, + }, { + .name = "GetBestRoute", + .patch = hook_GetBestRoute, + .link = (void **) &next_GetBestRoute, + }, { + .name = "GetIfTable", + .patch = hook_GetIfTable, + .link = (void **) &next_GetIfTable, + }, { + .name = "IcmpSendEcho2", + .patch = hook_IcmpSendEcho2, + .link = (void **) &next_IcmpSendEcho2, + } +}; + +static uint32_t netenv_ip_prefix; +static uint32_t netenv_ip_iface; +static uint32_t netenv_ip_router; +static uint8_t netenv_mac_addr[6]; + +HRESULT netenv_hook_init( + const struct netenv_config *cfg, + const struct ttxsec_config *kc_cfg) +{ + assert(cfg != NULL); + assert(kc_cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + if (!kc_cfg->enable) { + dprintf("Netenv: Keychip emu is off? Disabling Netenv emu.\n"); + + return S_FALSE; + } + + netenv_ip_prefix = kc_cfg->subnet; + netenv_ip_iface = kc_cfg->subnet | cfg->addr_suffix; + netenv_ip_router = kc_cfg->subnet | cfg->router_suffix; + memcpy(netenv_mac_addr, cfg->mac_addr, sizeof(netenv_mac_addr)); + + hook_table_apply( + NULL, + "iphlpapi.dll", + netenv_hook_syms, + _countof(netenv_hook_syms)); + + return S_OK; +} + +static uint32_t WINAPI hook_GetAdaptersAddresses( + uint32_t Family, + uint32_t Flags, + void *Reserved, + IP_ADAPTER_ADDRESSES *AdapterAddresses, + uint32_t *SizePointer) +{ + /* This hook errs on the side of caution and returns a lot more + information than the ALLNET lib cares about. MSVC mangles the main + call site for this API quite aggressively, so by the time we decompile + the code in question it's a little difficult to tell which pieces the + ALLNET lib pays attention to. */ + + uint32_t nbytes; + struct netenv *env; + + if (Reserved != NULL || SizePointer == NULL) { + return ERROR_INVALID_PARAMETER; + } + + nbytes = *SizePointer; + *SizePointer = sizeof(*env); + + if (AdapterAddresses == NULL || nbytes < sizeof(*env)) { + return ERROR_BUFFER_OVERFLOW; + } + + env = CONTAINING_RECORD(AdapterAddresses, struct netenv, head); + memset(env, 0, sizeof(*env)); + + env->head.Length = sizeof(env->head); + env->head.IfIndex = 1; + env->head.AdapterName = env->name; + env->head.FirstUnicastAddress = &env->iface; + env->head.FirstDnsServerAddress = &env->dns; + env->head.DnsSuffix = env->dns_suffix; + env->head.Description = env->description; + env->head.FriendlyName = env->friendly_name; + memcpy( env->head.PhysicalAddress, + netenv_mac_addr, + sizeof(netenv_mac_addr)); + env->head.PhysicalAddressLength = sizeof(netenv_mac_addr); + env->head.Flags = IP_ADAPTER_DHCP_ENABLED | IP_ADAPTER_IPV4_ENABLED; + env->head.Mtu = 4200; /* idk what's typical here */ + env->head.IfType = IF_TYPE_ETHERNET_CSMACD; + env->head.OperStatus = IfOperStatusUp; + env->head.FirstPrefix = &env->prefix; + env->head.FirstGatewayAddress = &env->router; + + strcpy_s( + env->name, + _countof(env->name), + "{00000000-0000-0000-0000-000000000000}"); + + wcscpy_s( + env->dns_suffix, + _countof(env->dns_suffix), + L"local"); + + wcscpy_s( + env->description, + _countof(env->description), + L"Interface Description"); + + wcscpy_s( + env->friendly_name, + _countof(env->friendly_name), + L"Fake Ethernet"); + + env->iface.Length = sizeof(env->iface); + env->iface.Flags = 0; + env->iface.Address.lpSockaddr = (struct sockaddr *) &env->iface_sa; + env->iface.Address.iSockaddrLength = sizeof(env->iface_sa); + env->iface.PrefixOrigin = IpPrefixOriginDhcp; + env->iface.SuffixOrigin = IpSuffixOriginDhcp; + env->iface.DadState = IpDadStatePreferred; + env->iface.ValidLifetime = UINT32_MAX; + env->iface.PreferredLifetime = UINT32_MAX; + env->iface.LeaseLifetime = 86400; + env->iface.OnLinkPrefixLength = 24; + + env->prefix.Length = sizeof(env->prefix); + env->prefix.Address.lpSockaddr = (struct sockaddr *) &env->prefix_sa; + env->prefix.Address.iSockaddrLength = sizeof(env->prefix_sa); + env->prefix.PrefixLength = 24; + + env->router.Length = sizeof(env->router); + env->router.Address.lpSockaddr = (struct sockaddr *) &env->router_sa; + env->router.Address.iSockaddrLength = sizeof(env->router_sa); + + env->dns.Length = sizeof(env->dns); + env->dns.Address.lpSockaddr = (struct sockaddr *) &env->dns_sa; + env->dns.Address.iSockaddrLength = sizeof(env->dns_sa); + + env->prefix_sa.sin_family = AF_INET; + env->prefix_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_prefix); + + env->iface_sa.sin_family = AF_INET; + env->iface_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_iface); + + env->router_sa.sin_family = AF_INET; + env->router_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_router); + + env->dns_sa.sin_family = AF_INET; + env->dns_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_router); + + return ERROR_SUCCESS; +} + +static uint32_t WINAPI hook_GetAdaptersInfo( + IP_ADAPTER_INFO *ai, + uint32_t *nbytes_inout) +{ + IP_ADDR_STRING iface; + IP_ADDR_STRING router; + uint32_t nbytes; + + if (nbytes_inout == NULL) { + return ERROR_INVALID_PARAMETER; + } + + nbytes = *nbytes_inout; + *nbytes_inout = sizeof(*ai); + + if (ai == NULL || nbytes < sizeof(*ai)) { + return ERROR_BUFFER_OVERFLOW; + } + + dprintf("Netenv: GetAdaptersInfo: Virtualized LAN configuration:\n"); + dprintf("Netenv: Interface IP : %3i.%3i.%3i.%3i\n", + (uint8_t) (netenv_ip_iface >> 24), + (uint8_t) (netenv_ip_iface >> 16), + (uint8_t) (netenv_ip_iface >> 8), + (uint8_t) (netenv_ip_iface )); + dprintf("Netenv: Router IP : %3i.%3i.%3i.%3i\n", + (uint8_t) (netenv_ip_router >> 24), + (uint8_t) (netenv_ip_router >> 16), + (uint8_t) (netenv_ip_router >> 8), + (uint8_t) (netenv_ip_router )); + dprintf("Netenv: MAC Address : %02x:%02x:%02x:%02x:%02x:%02x\n", + netenv_mac_addr[0], + netenv_mac_addr[1], + netenv_mac_addr[2], + netenv_mac_addr[3], + netenv_mac_addr[4], + netenv_mac_addr[5]); + + memset(&iface, 0, sizeof(iface)); + memset(&router, 0, sizeof(router)); + + sprintf_s( + iface.IpAddress.String, + _countof(iface.IpAddress.String), + "%i.%i.%i.%i", + (uint8_t) (netenv_ip_iface >> 24), + (uint8_t) (netenv_ip_iface >> 16), + (uint8_t) (netenv_ip_iface >> 8), + (uint8_t) (netenv_ip_iface )); + + strcpy_s( + iface.IpMask.String, + _countof(iface.IpMask.String), + "255.255.255.0"); + + sprintf_s( + router.IpAddress.String, + _countof(iface.IpAddress.String), + "%i.%i.%i.%i", + (uint8_t) (netenv_ip_router >> 24), + (uint8_t) (netenv_ip_router >> 16), + (uint8_t) (netenv_ip_router >> 8), + (uint8_t) (netenv_ip_router )); + + strcpy_s( + router.IpMask.String, + _countof(router.IpMask.String), + "255.255.255.0"); + + memset(ai, 0, sizeof(*ai)); + strcpy_s( + ai->AdapterName, + _countof(ai->AdapterName), + "Fake Ethernet"); + strcpy_s(ai->Description, + _countof(ai->Description), + "Adapter Description"); + ai->AddressLength = sizeof(netenv_mac_addr); + memcpy(ai->Address, netenv_mac_addr, sizeof(netenv_mac_addr)); + ai->Index = 1; + ai->Type = MIB_IF_TYPE_ETHERNET; + ai->DhcpEnabled = 1; + memcpy(&ai->IpAddressList, &iface, sizeof(iface)); + memcpy(&ai->GatewayList, &router, sizeof(router)); + memcpy(&ai->DhcpServer, &router, sizeof(router)); + ai->LeaseObtained = time(NULL) - 3600; + ai->LeaseExpires = time(NULL) + 86400; + + return ERROR_SUCCESS; +} + +static uint32_t WINAPI hook_GetBestRoute( + uint32_t src_ip, + uint32_t dest_ip, + MIB_IPFORWARDROW *route) +{ + if (route == NULL) { + return ERROR_INVALID_PARAMETER; + } + + dprintf("Netenv: GetBestRoute ip4 %x -> ip4 %x\n", + (int) _byteswap_ulong(src_ip), + (int) _byteswap_ulong(dest_ip)); + + memset(route, 0, sizeof(*route)); + + /* This doesn't seem to get read? It just needs to succeed. */ + + route->dwForwardDest = 0x00000000; + route->dwForwardMask = 0xFFFFFFFF; + route->dwForwardPolicy = 0; /* idk */ + route->dwForwardNextHop = _byteswap_ulong(netenv_ip_router); + route->dwForwardIfIndex = 1; + route->dwForwardType = MIB_IPROUTE_TYPE_INDIRECT; + route->dwForwardProto = MIB_IPPROTO_NETMGMT; + + return ERROR_SUCCESS; +} + +static uint32_t WINAPI hook_GetIfTable( + MIB_IFTABLE *pIfTable, + uint32_t *pdwSize, + BOOL bOrder) +{ + MIB_IFROW *row; + uint32_t nbytes; + + if (pdwSize == NULL) { + return ERROR_INVALID_PARAMETER; + } + + nbytes = *pdwSize; + *pdwSize = sizeof(*row) + sizeof(DWORD); + + if (pIfTable == NULL || nbytes < sizeof(*row) + sizeof(DWORD)) { + return ERROR_BUFFER_OVERFLOW; + } + + pIfTable->dwNumEntries = 1; + + row = pIfTable->table; + memset(row, 0, sizeof(*row)); + + wcscpy_s(row->wszName, _countof(row->wszName), L"Fake Ethernet"); + row->dwIndex = 1; /* Should match other IF_INDEX fields we return */ + row->dwType = IF_TYPE_ETHERNET_CSMACD; + row->dwMtu = 4200; /* I guess? */ + row->dwSpeed = 1000000000; + row->dwPhysAddrLen = sizeof(netenv_mac_addr); + memcpy(row->bPhysAddr, netenv_mac_addr, sizeof(netenv_mac_addr)); + row->dwAdminStatus = 1; + row->dwOperStatus = IF_OPER_STATUS_OPERATIONAL; + + return ERROR_SUCCESS; +} + +static uint32_t WINAPI hook_IcmpSendEcho2( + HANDLE IcmpHandle, + HANDLE Event, + PIO_APC_ROUTINE ApcRoutine, + void *ApcContext, + uint32_t DestinationAddress, + void *RequestData, + uint16_t RequestSize, + IP_OPTION_INFORMATION *RequestOptions, + void *ReplyBuffer, + uint32_t ReplySize, + uint32_t Timeout) +{ + ICMP_ECHO_REPLY *pong; + BOOL ok; + + if (IcmpHandle == NULL || IcmpHandle == INVALID_HANDLE_VALUE) { + SetLastError(ERROR_INVALID_PARAMETER); + + return 0; + } + + if (ApcRoutine != NULL) { + dprintf("%s: Got APC routine...\n", __func__); + SetLastError(ERROR_NOT_SUPPORTED); + + return 0; + } + + if (ReplyBuffer == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return 0; + } + + if (ReplySize < sizeof(ICMP_ECHO_REPLY)) { + SetLastError(IP_BUF_TOO_SMALL); + + return 0; + } + + dprintf("Netenv: Virtualized ICMP Ping to ip4 %x\n", + (int) _byteswap_ulong(DestinationAddress)); + + pong = (ICMP_ECHO_REPLY *) ReplyBuffer; + memset(pong, 0, sizeof(*pong)); + pong->Address = DestinationAddress; + pong->Status = IP_SUCCESS; + pong->RoundTripTime = 1; + pong->DataSize = 0; + pong->Reserved = 1; /* Number of ICMP_ECHO_REPLY structs in ReplyBuffer */ + pong->Data = NULL; + + if (Event != NULL) { + ok = SetEvent(Event); + + if (ok) { + SetLastError(ERROR_IO_PENDING); + } + + return 0; + } + + dprintf("%s: Unexpected synchronous call...\n", __func__); + SetLastError(ERROR_SUCCESS); + + return 1; +} diff --git a/platform/netenv.h b/platform/netenv.h new file mode 100644 index 0000000..33bf56a --- /dev/null +++ b/platform/netenv.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +#include "platform/ttxsec.h" + +struct netenv_config { + bool enable; + uint8_t addr_suffix; + uint8_t router_suffix; + uint8_t mac_addr[6]; +}; + +HRESULT netenv_hook_init( + const struct netenv_config *cfg, + const struct ttxsec_config *kc_cfg); + diff --git a/platform/platform.c b/platform/platform.c new file mode 100644 index 0000000..bc3d20d --- /dev/null +++ b/platform/platform.c @@ -0,0 +1,68 @@ +#include + +#include + +#include "platform/clock.h" +#include "platform/dns.h" +#include "platform/misc.h" +#include "platform/netenv.h" +#include "platform/ttxsec.h" +#include "platform/platform.h" +#include "platform/vfs.h" +#include "platform/syscfg.h" + +HRESULT platform_hook_init( + const struct platform_config *cfg, + const uint32_t game_id, + HMODULE redir_mod) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(game_id != 0); + assert(redir_mod != NULL); + + hr = clock_hook_init(&cfg->clock); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_platform_hook_init(&cfg->dns); + + if (FAILED(hr)) { + return hr; + } + + hr = misc_hook_init(&cfg->misc); + + if (FAILED(hr)) { + return hr; + } + + hr = netenv_hook_init(&cfg->netenv, &cfg->ttxsec); + + if (FAILED(hr)) { + return hr; + } + + hr = ttxsec_hook_init(&cfg->ttxsec, game_id); + + if (FAILED(hr)) { + return hr; + } + + hr = vfs_hook_init(&cfg->vfs); + + if (FAILED(hr)) { + return hr; + } + + hr = syscfg_hook_init(&cfg->syscfg, game_id); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} diff --git a/platform/platform.h b/platform/platform.h new file mode 100644 index 0000000..65b1898 --- /dev/null +++ b/platform/platform.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "platform/clock.h" +#include "platform/dns.h" +#include "platform/misc.h" +#include "platform/netenv.h" +#include "platform/ttxsec.h" +#include "platform/vfs.h" +#include "platform/syscfg.h" + +struct platform_config { + struct clock_config clock; + struct dns_config dns; + struct misc_config misc; + struct netenv_config netenv; + struct ttxsec_config ttxsec; + struct vfs_config vfs; + struct syscfg_config syscfg; +}; + +HRESULT platform_hook_init( + const struct platform_config *cfg, + const char *game_id, + const char *platform_id, + HMODULE redir_mod); diff --git a/platform/syscfg.c b/platform/syscfg.c new file mode 100644 index 0000000..740c689 --- /dev/null +++ b/platform/syscfg.c @@ -0,0 +1,115 @@ +#include +#include +#include + +#include "hooklib/reg.h" +#include "platform/syscfg.h" +#include "platform/vfs.h" + +#include "util/dprintf.h" + +static struct syscfg_config config; +static uint32_t game_id; + +static HRESULT syscfg_game_kind(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_evt_next_time(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_condition_time(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_traffic_ct(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_log_level(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_news_path(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_event_path(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_log_path(void *bytes, uint32_t *nbytes); + +static const struct reg_hook_val fake_com_keys[] = { + { + .name = L"GameKind", + .read = syscfg_game_kind, + .type = REG_DWORD, + },{ + .name = L"EventNextTime", + .read = syscfg_evt_next_time, + .type = REG_DWORD, + },{ + .name = L"ConditionTime", + .read = syscfg_condition_time, + .type = REG_DWORD, + },{ + .name = L"TrafficCount", + .read = syscfg_traffic_ct, + .type = REG_DWORD, + },{ + .name = L"LogLevel", + .read = syscfg_log_level, + .type = REG_DWORD, + },{ + .name = L"NewsPath", + .read = syscfg_news_path, + .type = REG_SZ, + },{ + .name = L"EventPath", + .read = syscfg_event_path, + .type = REG_SZ, + },{ + .name = L"LogPath", + .read = syscfg_log_path, + .type = REG_SZ, + }, +}; + +HRESULT syscfg_hook_init(const struct syscfg_config *cfg, const uint32_t gid) +{ + HRESULT hr; + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&config, cfg, sizeof(*cfg)); + game_id = gid; + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\taito\\typex", + fake_com_keys, + _countof(fake_com_keys)); +} + +static HRESULT syscfg_game_kind(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, game_id); +} + +static HRESULT syscfg_evt_next_time(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 1800); +} + +static HRESULT syscfg_condition_time(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 1800); +} + +static HRESULT syscfg_traffic_ct(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 0); // unused? +} + +static HRESULT syscfg_log_level(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, config.log_level); +} + +static HRESULT syscfg_news_path(void *bytes, uint32_t *nbytes) +{ + + return reg_hook_read_wstr(bytes, nbytes, L"D:\\news"); +} + +static HRESULT syscfg_event_path(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"D:\\event"); +} + +static HRESULT syscfg_log_path(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"D:\\log"); +} diff --git a/platform/syscfg.h b/platform/syscfg.h new file mode 100644 index 0000000..abb3447 --- /dev/null +++ b/platform/syscfg.h @@ -0,0 +1,12 @@ +#pragma once +#include + +#include +#include + +struct syscfg_config { + bool enable; + uint32_t log_level; +}; + +HRESULT syscfg_hook_init(const struct syscfg_config *cfg, const uint32_t gid); diff --git a/platform/ttxsec.c b/platform/ttxsec.c new file mode 100644 index 0000000..f76bb85 --- /dev/null +++ b/platform/ttxsec.c @@ -0,0 +1,621 @@ +#include +#include +#include +#include + +#include "hook/iohook.h" + +#include "hooklib/reg.h" + +#include "platform/ttxsec.h" + +#include "util/dprintf.h" +#include "util/dump.h" +#include "util/str.h" + +enum { + TTXSEC_IOCTL_PING = 0x22A114, + TTXSEC_IOCTL_ERASE_TRACE_LOG = 0x22E188, + TTXSEC_IOCTL_ADD_PLAY_COUNT = 0x22E154, + TTXSEC_IOCTL_GET_BILLING_CA_CERT = 0x22E1C4, + TTXSEC_IOCTL_GET_BILLING_PUBKEY = 0x22E1C8, + TTXSEC_IOCTL_GET_NEARFULL = 0x22E20C, + TTXSEC_IOCTL_GET_NVRAM_AVAILABLE = 0x22E19C, + TTXSEC_IOCTL_GET_NVRAM_GEOMETRY = 0x22E24C, + TTXSEC_IOCTL_GET_PLAY_COUNT = 0x22E150, + TTXSEC_IOCTL_GET_PLAY_LIMIT = 0x22E204, + TTXSEC_IOCTL_GET_TRACE_LOG_DATA = 0x22E194, + TTXSEC_IOCTL_GET_TRACE_LOG_STATE = 0x22E198, + TTXSEC_IOCTL_PUT_NEARFULL = 0x22E210, + TTXSEC_IOCTL_PUT_PLAY_LIMIT = 0x22E208, + TTXSEC_IOCTL_PUT_TRACE_LOG_DATA = 0x22E190, +}; + +struct ttxsec_log_record { + uint8_t unknown[60]; +}; + +static HRESULT ttxsec_handle_irp(struct irp *irp); +static HRESULT ttxsec_handle_open(struct irp *irp); +static HRESULT ttxsec_handle_close(struct irp *irp); +static HRESULT ttxsec_handle_ioctl(struct irp *irp); + +static HRESULT ttxsec_ioctl_ping(struct irp *irp); +static HRESULT ttxsec_ioctl_erase_trace_log(struct irp *irp); +static HRESULT ttxsec_ioctl_add_play_count(struct irp *irp); +static HRESULT ttxsec_ioctl_get_billing_ca_cert(struct irp *irp); +static HRESULT ttxsec_ioctl_get_billing_pubkey(struct irp *irp); +static HRESULT ttxsec_ioctl_get_nearfull(struct irp *irp); +static HRESULT ttxsec_ioctl_get_nvram_available(struct irp *irp); +static HRESULT ttxsec_ioctl_get_nvram_geometry(struct irp *irp); +static HRESULT ttxsec_ioctl_get_play_count(struct irp *irp); +static HRESULT ttxsec_ioctl_get_play_limit(struct irp *irp); +static HRESULT ttxsec_ioctl_get_trace_log_data(struct irp *irp); +static HRESULT ttxsec_ioctl_get_trace_log_state(struct irp *irp); +static HRESULT ttxsec_ioctl_put_nearfull(struct irp *irp); +static HRESULT ttxsec_ioctl_put_play_limit(struct irp *irp); +static HRESULT ttxsec_ioctl_put_trace_log_data(struct irp *irp); + +static HRESULT ttxsec_reg_read_game_id(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_keychip_id(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_model_type(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_platform_id(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_region(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_server_ip_ipv4(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_server_ip_ipv6(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_system_flag(void *bytes, uint32_t *nbytes); + +static const struct reg_hook_val ttxsec_reg_vals[] = { + { + .name = L"gameId", + .read = ttxsec_reg_read_game_id, + .type = REG_BINARY, + }, { + .name = L"keychipId", + .read = ttxsec_reg_read_keychip_id, + .type = REG_BINARY, + }, { + .name = L"modelType", + .read = ttxsec_reg_read_model_type, + .type = REG_DWORD, + }, { + .name = L"platformId", + .read = ttxsec_reg_read_platform_id, + .type = REG_BINARY, + }, { + .name = L"region", + .read = ttxsec_reg_read_region, + .type = REG_DWORD, + }, { + .name = L"serverIpIpv4", + .read = ttxsec_reg_read_server_ip_ipv4, + .type = REG_BINARY, + }, { + .name = L"serverIpIpv6", + .read = ttxsec_reg_read_server_ip_ipv6, + .type = REG_BINARY, + }, { + .name = L"systemFlag", + .read = ttxsec_reg_read_system_flag, + .type = REG_DWORD, + } +}; + +static HANDLE ttxsec_fd; +static uint32_t ttxsec_nearfull; +static uint32_t ttxsec_play_count; +static uint32_t ttxsec_play_limit; +static struct ttxsec_log_record ttxsec_log[7154]; +static size_t ttxsec_log_head; +static size_t ttxsec_log_tail; +static struct ttxsec_config ttxsec_cfg; + +HRESULT ttxsec_hook_init( + const struct ttxsec_config *cfg, + const uint32_t game_id) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(game_id != 0); + + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&ttxsec_cfg, cfg, sizeof(*cfg)); + + // High 16 bits is billing type, low is actual playlimit + ttxsec_nearfull = (ttxsec_cfg.billing_type << 16) + 512; + ttxsec_play_count = 0; + ttxsec_play_limit = 1024; + + hr = iohook_open_nul_fd(&ttxsec_fd); + + if (FAILED(hr)) { + return hr; + } + + hr = iohook_push_handler(ttxsec_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty\\keychip", + ttxsec_reg_vals, + _countof(ttxsec_reg_vals)); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT ttxsec_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != ttxsec_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return ttxsec_handle_open(irp); + case IRP_OP_CLOSE: return ttxsec_handle_close(irp); + case IRP_OP_IOCTL: return ttxsec_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT ttxsec_handle_open(struct irp *irp) +{ + if (!wstr_ieq(irp->open_filename, L"\\??\\FddDriver")) { + return iohook_invoke_next(irp); + } + + dprintf("Security: Opened handle\n"); + irp->fd = ttxsec_fd; + + return S_OK; +} + +static HRESULT ttxsec_handle_close(struct irp *irp) +{ + dprintf("Security: Closed handle\n"); + + return S_OK; +} + +static HRESULT ttxsec_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case TTXSEC_IOCTL_PING: + return ttxsec_ioctl_ping(irp); + + case TTXSEC_IOCTL_ERASE_TRACE_LOG: + return ttxsec_ioctl_erase_trace_log(irp); + + case TTXSEC_IOCTL_ADD_PLAY_COUNT: + return ttxsec_ioctl_add_play_count(irp); + + case TTXSEC_IOCTL_GET_BILLING_CA_CERT: + return ttxsec_ioctl_get_billing_ca_cert(irp); + + case TTXSEC_IOCTL_GET_BILLING_PUBKEY: + return ttxsec_ioctl_get_billing_pubkey(irp); + + case TTXSEC_IOCTL_GET_NEARFULL: + return ttxsec_ioctl_get_nearfull(irp); + + case TTXSEC_IOCTL_GET_NVRAM_AVAILABLE: + return ttxsec_ioctl_get_nvram_available(irp); + + case TTXSEC_IOCTL_GET_NVRAM_GEOMETRY: + return ttxsec_ioctl_get_nvram_geometry(irp); + + case TTXSEC_IOCTL_GET_PLAY_COUNT: + return ttxsec_ioctl_get_play_count(irp); + + case TTXSEC_IOCTL_GET_PLAY_LIMIT: + return ttxsec_ioctl_get_play_limit(irp); + + case TTXSEC_IOCTL_GET_TRACE_LOG_DATA: + return ttxsec_ioctl_get_trace_log_data(irp); + + case TTXSEC_IOCTL_GET_TRACE_LOG_STATE: + return ttxsec_ioctl_get_trace_log_state(irp); + + case TTXSEC_IOCTL_PUT_NEARFULL: + return ttxsec_ioctl_put_nearfull(irp); + + case TTXSEC_IOCTL_PUT_PLAY_LIMIT: + return ttxsec_ioctl_put_play_limit(irp); + + case TTXSEC_IOCTL_PUT_TRACE_LOG_DATA: + return ttxsec_ioctl_put_trace_log_data(irp); + + default: + dprintf("Security: Unknown ioctl %#08x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT ttxsec_ioctl_ping(struct irp *irp) +{ + return S_OK; +} + +static HRESULT ttxsec_ioctl_erase_trace_log(struct irp *irp) +{ + uint32_t count; + size_t avail; + HRESULT hr; + + hr = iobuf_read_le32(&irp->write, &count); + + if (FAILED(hr)) { + return hr; + } + + dprintf("Security: %s(count=%i)\n", __func__, count); + + avail = ttxsec_log_head - ttxsec_log_tail; + + if (count < avail) { + count = avail; + } + + ttxsec_log_tail += count; + + return S_OK; +} + +static HRESULT ttxsec_ioctl_add_play_count(struct irp *irp) +{ + uint32_t delta; + HRESULT hr; + + hr = iobuf_read_le32(&irp->write, &delta); + + if (FAILED(hr)) { + return hr; + } + + dprintf("Security: Add play count: %i + %i = %i\n", + ttxsec_play_count, + delta, + ttxsec_play_count + delta); + + ttxsec_play_count += delta; + + return iobuf_write_le32(&irp->read, ttxsec_play_count); +} + +static HRESULT ttxsec_ioctl_get_billing_ca_cert(struct irp *irp) +{ + HANDLE fd; + HRESULT hr; + + dprintf("Security: %s\n", __func__); + + fd = CreateFileW( + ttxsec_cfg.billing_ca, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (fd == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Error opening CA cert file: %x\n", (int) hr); + + return hr; + } + + /* Transform this ioctl into a read from that file and pass the IRP on */ + + irp->op = IRP_OP_READ; + irp->fd = fd; + + dprintf(">>> %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + hr = iohook_invoke_next(irp); + + dprintf("<<< %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + if (FAILED(hr)) { + dprintf("ReadFile transformation failed: %x\n", (int) hr); + } + + CloseHandle(fd); + + return hr; +} + +static HRESULT ttxsec_ioctl_get_billing_pubkey(struct irp *irp) +{ + HANDLE fd; + HRESULT hr; + + dprintf("Security: %s\n", __func__); + + fd = CreateFileW( + ttxsec_cfg.billing_pub, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (fd == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Error opening billing pubkey file: %x\n", (int) hr); + + return hr; + } + + irp->op = IRP_OP_READ; + irp->fd = fd; + + dprintf(">>> %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + hr = iohook_invoke_next(irp); + + dprintf("<<< %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + if (FAILED(hr)) { + dprintf("ReadFile transformation failed: %x\n", (int) hr); + } + + CloseHandle(fd); + + return hr; +} + +static HRESULT ttxsec_ioctl_get_nearfull(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + + return iobuf_write_le32(&irp->read, ttxsec_nearfull); +} + +static HRESULT ttxsec_ioctl_get_nvram_available(struct irp *irp) +{ + size_t used; + size_t avail; + + used = ttxsec_log_head - ttxsec_log_tail; + avail = _countof(ttxsec_log) - used; + + dprintf("Security: %s: used=%i avail=%i\n", __func__, + (int) used, + (int) avail); + + return iobuf_write_le32(&irp->read, avail); +} + +static HRESULT ttxsec_ioctl_get_nvram_geometry(struct irp *irp) +{ + HRESULT hr; + + dprintf("Security: %s\n", __func__); + + iobuf_write_le32(&irp->read, 10); /* Num NVRAMs */ + hr = iobuf_write_le32(&irp->read, 4096); /* Num addresses (per NVRAM?) */ + + return hr; +} + +static HRESULT ttxsec_ioctl_get_play_count(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + + return iobuf_write_le32(&irp->read, ttxsec_play_count); +} + +static HRESULT ttxsec_ioctl_get_play_limit(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + + return iobuf_write_le32(&irp->read, ttxsec_play_limit); +} + +static HRESULT ttxsec_ioctl_get_trace_log_data(struct irp *irp) +{ + uint32_t pos; + uint32_t count; + size_t avail; + HRESULT hr; + + dprintf("Security: %s\n", __func__); + + hr = iobuf_read_le32(&irp->write, &pos); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_le32(&irp->write, &count); + + if (FAILED(hr)) { + return hr; + } + + dprintf(" Params: %i %i Buf: %i\n", pos, count, (int) irp->read.nbytes); + + avail = irp->read.nbytes - irp->read.pos; + + if (avail < count * sizeof(struct ttxsec_log_record)) { + dprintf("\tError: Insufficient buffer\n"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + while (count > 0 && pos != ttxsec_log_head) { + memcpy( &irp->read.bytes[irp->read.pos], + &ttxsec_log[pos % _countof(ttxsec_log)], + sizeof(struct ttxsec_log_record)); + + irp->read.pos += sizeof(struct ttxsec_log_record); + pos++; + count--; + } + + return S_OK; +} + +static HRESULT ttxsec_ioctl_get_trace_log_state(struct irp *irp) +{ + HRESULT hr; + + dprintf("Security: %s H: %i T: %i\n", + __func__, + (int) ttxsec_log_head, + (int) ttxsec_log_tail); + + iobuf_write_le32(&irp->read, ttxsec_log_head - ttxsec_log_tail); + hr = iobuf_write_le32(&irp->read, ttxsec_log_tail); + + return hr; +} + +static HRESULT ttxsec_ioctl_put_nearfull(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + dump_const_iobuf(&irp->write); + + return iobuf_read_le32(&irp->write, &ttxsec_nearfull); +} + +static HRESULT ttxsec_ioctl_put_play_limit(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + dump_const_iobuf(&irp->write); + + return iobuf_read_le32(&irp->write, &ttxsec_play_limit); +} + +static HRESULT ttxsec_ioctl_put_trace_log_data(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + dump_const_iobuf(&irp->write); + + if (irp->write.nbytes != sizeof(struct ttxsec_log_record)) { + dprintf(" Log record size is incorrect\n"); + + return E_INVALIDARG; + } + + if (ttxsec_log_head - ttxsec_log_tail >= _countof(ttxsec_log)) { + dprintf(" Log buffer is full!\n"); + + return HRESULT_FROM_WIN32(ERROR_DISK_FULL); + } + + memcpy( &ttxsec_log[ttxsec_log_head % _countof(ttxsec_log)], + irp->write.bytes, + sizeof(struct ttxsec_log_record)); + + ttxsec_log_head++; + + dprintf(" H: %i T: %i\n", (int) ttxsec_log_head, (int) ttxsec_log_tail); + + return S_OK; +} + +static HRESULT ttxsec_reg_read_game_id(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_bin( + bytes, + nbytes, + &ttxsec_cfg.game_id, + sizeof(ttxsec_cfg.game_id)); +} + +static HRESULT ttxsec_reg_read_keychip_id(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_bin( + bytes, + nbytes, + &ttxsec_cfg.keychip_id, + sizeof(ttxsec_cfg.keychip_id)); +} + +static HRESULT ttxsec_reg_read_model_type(void *bytes, uint32_t *nbytes) +{ + uint32_t u32; + char c; + + c = ttxsec_cfg.platform_id[3]; + + if (c >= '0' && c <= '9') { + u32 = c - '0'; + } else { + u32 = 0; + } + + return reg_hook_read_u32(bytes, nbytes, u32); +} + +static HRESULT ttxsec_reg_read_platform_id(void *bytes, uint32_t *nbytes) +{ + /* Fourth byte is a separate registry val (modelType). + We store it in the same config field for ease of configuration. */ + + return reg_hook_read_bin( + bytes, + nbytes, + &ttxsec_cfg.platform_id, + 3); +} + +static HRESULT ttxsec_reg_read_region(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, ttxsec_cfg.region); +} + +static HRESULT ttxsec_reg_read_server_ip_ipv4(void *bytes, uint32_t *nbytes) +{ + uint32_t subnet; + + subnet = _byteswap_ulong(ttxsec_cfg.subnet); + + return reg_hook_read_bin(bytes, nbytes, &subnet, sizeof(subnet)); +} + +static HRESULT ttxsec_reg_read_server_ip_ipv6(void *bytes, uint32_t *nbytes) +{ + uint8_t subnet[16]; + + memset(subnet, 0, sizeof(subnet)); + + return reg_hook_read_bin(bytes, nbytes, subnet, sizeof(subnet)); +} + +static HRESULT ttxsec_reg_read_system_flag(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, ttxsec_cfg.system_flag); +} diff --git a/platform/ttxsec.h b/platform/ttxsec.h new file mode 100644 index 0000000..517eef2 --- /dev/null +++ b/platform/ttxsec.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include +#include +#include + +struct ttxsec_config { + bool enable; + char keychip_id[16]; + char game_id[4]; + char platform_id[4]; + uint8_t region; + uint8_t system_flag; + uint32_t subnet; + uint16_t billing_type; + wchar_t billing_ca[MAX_PATH]; + wchar_t billing_pub[MAX_PATH]; +}; + +HRESULT ttxsec_hook_init( + const struct ttxsec_config *cfg, + const uint32_t game_id); diff --git a/platform/vfs.c b/platform/vfs.c new file mode 100644 index 0000000..98f1f77 --- /dev/null +++ b/platform/vfs.c @@ -0,0 +1,209 @@ +#include +#include + +#include +#include +#include +#include + +#include "hooklib/path.h" +#include "hooklib/reg.h" + +#include "platform/vfs.h" + +#include "util/dprintf.h" + +static void vfs_fixup_path(wchar_t *path, size_t max_count); +static HRESULT vfs_mkdir_rec(const wchar_t *path); +static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count); +static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes); +static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes); + +static struct vfs_config vfs_config; + +HRESULT vfs_hook_init(const struct vfs_config *config) +{ + wchar_t temp[MAX_PATH]; + size_t nthome_len; + DWORD home_ok; + HRESULT hr; + + assert(config != NULL); + + if (!config->enable) { + return S_FALSE; + } + + if (config->d_drive[0] == L'\0') { + dprintf("Vfs: FATAL: AMFS path not specified in INI file\n"); + + return E_FAIL; + } + + memcpy(&vfs_config, config, sizeof(*config)); + + vfs_fixup_path(vfs_config.d_drive, _countof(vfs_config.d_drive)); + + hr = vfs_mkdir_rec(vfs_config.d_drive); + + if (FAILED(hr)) { + dprintf("Vfs: Failed to create D Drive dir %S: %x\n", + config->d_drive, + (int) hr); + } + + /* Need to create the temp subdirectory, not just nthome itself */ + + hr = vfs_mkdir_rec(temp); + + if (FAILED(hr)) { + dprintf("Vfs: Failed to create %S: %x\n", temp, (int) hr); + } + + /* Not auto-creating option directory as it is normally a read-only mount */ + + hr = path_hook_push(vfs_path_hook); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static void vfs_fixup_path(wchar_t *path, size_t max_count) +{ + size_t count; + wchar_t abspath[MAX_PATH]; + + assert(path != NULL); + /* Requirement for PathIsRelativeW */ + assert(max_count <= MAX_PATH); + + if (PathIsRelativeW(path)) { + count = GetFullPathNameW(path, _countof(abspath), abspath, NULL); + + /* GetFullPathName's length return value is tricky, because it includes + the NUL terminator on failure, but doesn't on success. + Check if it fits the temp buf (else it's a failure and includes NUL + anyway), then if it fits the target buf, NUL included. */ + if (count == 0 || count > _countof(abspath) || count >= max_count) { + goto fail; + } + + wcscpy_s(path, max_count, abspath); + } else { + count = wcslen(path); + } + + if (path_is_separator_w(path[count - 1])) { + return; + } + + if (count + 2 > max_count) { + goto fail; + } + + path[count + 0] = L'\\'; + path[count + 1] = L'\0'; + return; + +fail: + dprintf("Vfs: FATAL: Path too long: %S\n", path); + abort(); +} + +static HRESULT vfs_mkdir_rec(const wchar_t *path) +{ + wchar_t *copy; + wchar_t *pos; + wchar_t wc; + HRESULT hr; + DWORD attr; + BOOL ok; + + assert(path != NULL); + + copy = _wcsdup(path); + + if (copy == NULL) { + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + + goto end; + } + + pos = copy; + + do { + wc = *pos; + + if (wc == L'\0' || wc == L'/' || wc == L'\\') { + *pos = L'\0'; + attr = GetFileAttributesW(copy); + + if (attr == INVALID_FILE_ATTRIBUTES) { + ok = CreateDirectoryW(copy, NULL); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + + goto end; + } + } + + *pos = wc; + } + + pos++; + } while (wc != L'\0'); + + hr = S_OK; + +end: + free(copy); + + return hr; +} + +static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count) +{ + const wchar_t *redir; + size_t required; + size_t redir_len; + + assert(src != NULL); + assert(count != NULL); + + if (src[0] == L'\0' || src[1] != L':' || !path_is_separator_w(src[2])) { + return S_FALSE; + } + + switch (src[0]) { + case L'D': + case L'd': + redir = vfs_config.d_drive; + + break; + + default: + return S_FALSE; + } + + /* Cut off \, replace with redir path, count NUL terminator */ + + redir_len = wcslen(redir); + required = wcslen(src) - 3 + redir_len + 1; + + if (dest != NULL) { + if (required > *count) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + wcscpy_s(dest, *count, redir); + wcscpy_s(dest + redir_len, *count - redir_len, src + 3); + } + + *count = required; + + return S_OK; +} diff --git a/platform/vfs.h b/platform/vfs.h new file mode 100644 index 0000000..2836442 --- /dev/null +++ b/platform/vfs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include + +struct vfs_config { + bool enable; + wchar_t d_drive[MAX_PATH]; +}; + +HRESULT vfs_hook_init(const struct vfs_config *config); diff --git a/precompiled.h b/precompiled.h new file mode 100644 index 0000000..7c85da7 --- /dev/null +++ b/precompiled.h @@ -0,0 +1,39 @@ +/* + Making NTSTATUS available is slightly awkward. See: + https://kirkshoop.github.io/2011/09/20/ntstatus.html +*/ + +/* Win32 user-mode API */ +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include +#include +#include +#include +#include +#include +#include +#include + +/* Win32 kernel-mode definitions */ +#ifdef __GNUC__ +/* MinGW needs to include this for PHYSICAL_ADDRESS to be defined. + The MS SDK throws a bunch of duplicate symbol errors instead. */ +#include +#else +#include +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/subprojects/capnhook.wrap b/subprojects/capnhook.wrap new file mode 100644 index 0000000..b2687a2 --- /dev/null +++ b/subprojects/capnhook.wrap @@ -0,0 +1,4 @@ +[wrap-git] +directory = capnhook +url = https://github.com/Hay1tsme/capnhook +revision = dbdcd61b3a3043b08f86f959bd45df4967503a77 diff --git a/util/async.c b/util/async.c new file mode 100644 index 0000000..fd6a1cd --- /dev/null +++ b/util/async.c @@ -0,0 +1,199 @@ +/* NTSTATUS chicanery. See precompiled.h */ +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include +#include + +#include +#include +#include +#include + +#include "hook/iohook.h" + +#include "util/async.h" + +static unsigned int __stdcall async_thread_proc(void *param); + +void async_init(struct async *async, void *ctx) +{ + assert(async != NULL); + + InitializeCriticalSection(&async->lock); + InitializeConditionVariable(&async->pend); + InitializeConditionVariable(&async->avail); + async->thread = NULL; + async->ctx = ctx; + async->stop = false; +} + +void async_fini(struct async *async) +{ + HANDLE thread; + + if (async == NULL) { + return; + } + + EnterCriticalSection(&async->lock); + + async->stop = true; + thread = async->thread; + + WakeConditionVariable(&async->pend); + LeaveCriticalSection(&async->lock); + + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + DeleteCriticalSection(&async->lock); + + /* There is no DeleteConditionVariable function in the Win32 API. */ +} + +HRESULT async_submit(struct async *async, struct irp *irp, async_task_t task) +{ + BOOL ok; + + assert(async != NULL); + assert(irp != NULL); + assert(task != NULL); + + if (irp->ovl == NULL) { + /* If there's no OVERLAPPED struct then just execute synchronously */ + return task(async->ctx, irp); + } + + EnterCriticalSection(&async->lock); + + if (async->thread == NULL) { + /* Ensure our worker thread is running */ + async->thread = (HANDLE) _beginthreadex( + NULL, + 0, + async_thread_proc, + async, + 0, + NULL); + + if (async->thread == NULL) { + return HRESULT_FROM_WIN32(GetLastError()); + } + } + + while (async->task != NULL) { + ok = SleepConditionVariableCS(&async->avail, &async->lock, INFINITE); + + if (!ok) { + abort(); + } + } + + async->task = task; + memcpy(&async->irp, irp, sizeof(*irp)); + async->irp.next_handler = (size_t) -1; + irp->ovl->Internal = STATUS_PENDING; + + WakeConditionVariable(&async->pend); + LeaveCriticalSection(&async->lock); + + return HRESULT_FROM_WIN32(ERROR_IO_PENDING); +} + +static unsigned int __stdcall async_thread_proc(void *param) +{ + struct async *async; + struct irp irp; + async_task_t task; + OVERLAPPED *ovl; + HANDLE event; + HRESULT hr; + BOOL ok; + + async = param; + + for (;;) { + EnterCriticalSection(&async->lock); + + if (async->stop) { + LeaveCriticalSection(&async->lock); + + break; + } else if (async->task == NULL) { + ok = SleepConditionVariableCS(&async->pend, &async->lock, INFINITE); + + if (!ok) { + abort(); + } + + LeaveCriticalSection(&async->lock); + } else { + memcpy(&irp, &async->irp, sizeof(irp)); + task = async->task; + ovl = async->irp.ovl; + async->task = NULL; + + WakeConditionVariable(&async->avail); + LeaveCriticalSection(&async->lock); + + assert(ovl != NULL); + + hr = task(async->ctx, &irp); + + switch (irp.op) { + case IRP_OP_READ: + case IRP_OP_IOCTL: + ovl->InternalHigh = (DWORD) irp.read.pos; + + break; + + case IRP_OP_WRITE: + ovl->InternalHigh = (DWORD) irp.write.pos; + + break; + + default: + break; + } + + /* We have to do a slightly tricky dance with the hooked process' + call to GetOverlappedResult() here. This thread might be blocked + on ovl->hEvent, or it might be just about to read ovl->Internal + to determine whether the IO has completed (and thus determine + whether it needs to block on ovl->hEvent or not). So to avoid + any races and *ovl getting invalidated under our feet we must + wake the initiating thread as follows: + + 1. Take a local copy of ovl->hEvent + + 2. Issue a memory fence to ensure that the previous load does + not get re-ordered after the following store + + https://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/ + + 3. Store the operation's NTSTATUS. At the moment that this store + gets issued the memory pointed to by ovl ceases to be safely + accessible. + + 4. Using our local copy of the event handle (if present), signal + the initiating thread to wake up and retire the IO. */ + + event = ovl->hEvent; + MemoryBarrier(); + + if (SUCCEEDED(hr)) { + ovl->Internal = STATUS_SUCCESS; + } else if (hr & FACILITY_NT_BIT) { + ovl->Internal = hr & ~FACILITY_NT_BIT; + } else { + ovl->Internal = STATUS_UNSUCCESSFUL; + } + + if (event != NULL) { + SetEvent(event); + } + } + } + + return 0; +} diff --git a/util/async.h b/util/async.h new file mode 100644 index 0000000..830b46a --- /dev/null +++ b/util/async.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +#include "hook/iohook.h" + +typedef HRESULT (*async_task_t)(void *ctx, struct irp *irp); + +struct async { + CRITICAL_SECTION lock; + CONDITION_VARIABLE pend; + CONDITION_VARIABLE avail; + HANDLE thread; + struct irp irp; + async_task_t task; + void *ctx; + bool stop; +}; + +void async_init(struct async *async, void *ctx); +void async_fini(struct async *async); +HRESULT async_submit(struct async *async, struct irp *irp, async_task_t task); diff --git a/util/crc.c b/util/crc.c new file mode 100644 index 0000000..5e4defb --- /dev/null +++ b/util/crc.c @@ -0,0 +1,28 @@ +#include +#include + +#include "util/crc.h" + +uint32_t crc32(const void *src, size_t nbytes, uint32_t in) +{ + const uint8_t *bytes; + uint32_t crc; + size_t i; + + bytes = src; + crc = ~in; + + for (i = 0 ; i < nbytes * 8 ; i++) { + if (i % 8 == 0) { + crc ^= *bytes++; + } + + if (crc & 1) { + crc = (crc >> 1) ^ 0xEDB88320; + } else { + crc = (crc >> 1); + } + } + + return ~crc; +} diff --git a/util/crc.h b/util/crc.h new file mode 100644 index 0000000..16ca927 --- /dev/null +++ b/util/crc.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +uint32_t crc32(const void *src, size_t nbytes, uint32_t in); diff --git a/util/dll-bind.c b/util/dll-bind.c new file mode 100644 index 0000000..9568d33 --- /dev/null +++ b/util/dll-bind.c @@ -0,0 +1,47 @@ +#include + +#include +#include + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +HRESULT dll_bind( + void *dest, + HINSTANCE src, + const struct dll_bind_sym **syms_pos, + size_t syms_count) +{ + HRESULT hr; + void *src_result; + void **dest_field; + const struct dll_bind_sym *current_sym; + size_t i; + + assert(dest != NULL); + assert(src != NULL); + assert(syms_pos != NULL); + + hr = S_OK; + current_sym = *syms_pos; + + assert(current_sym != NULL); + + for (i = 0; i < syms_count; i++) { + src_result = GetProcAddress(src, current_sym->sym); + + if (src_result == NULL) { + hr = E_NOTIMPL; + + break; + } + + dest_field = (void **)(((uint8_t *)dest) + current_sym->off); + *dest_field = src_result; + current_sym++; + } + + *syms_pos = current_sym; + + return hr; +} diff --git a/util/dll-bind.h b/util/dll-bind.h new file mode 100644 index 0000000..08e647b --- /dev/null +++ b/util/dll-bind.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include + +struct dll_bind_sym { + /* Symbol name to locate in the source DLL */ + const char *sym; + + /* Offset where the symbol pointer should be written in the target + structure */ + ptrdiff_t off; +}; + +/* + Bind a list of DLL symbols into a structure that contains function + pointers. + + - dest: Pointer to destination structure. + - src: Handle to source DLL. + - syms_pos: Pointer to a (mutable) pointer which points to the start of an + (immutable) table of symbols and structure offsets. This mutable + pointer is advanced until either a symbol fails to bind, in which case + an error is returned, or the end of the table is reached, in which + case success is returned. + - syms_count: Number of entries in the symbol table. +*/ +HRESULT dll_bind( + void *dest, + HINSTANCE src, + const struct dll_bind_sym **syms_pos, + size_t syms_count); diff --git a/util/dprintf.c b/util/dprintf.c new file mode 100644 index 0000000..098988c --- /dev/null +++ b/util/dprintf.c @@ -0,0 +1,85 @@ +#ifndef NDEBUG + +#include + +#include +#include +#include +#include + +#include "util/dprintf.h" + +static long dbg_buf_lock_init; +static CRITICAL_SECTION dbg_buf_lock; +static char dbg_buf[16384]; +static size_t dbg_buf_pos; + +void dprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + dprintfv(fmt, ap); + va_end(ap); +} + +void dprintfv(const char *fmt, va_list ap) +{ + long init; + + /* Static constructors in C are difficult to do in a way that works under + both GCC and MSVC, so we have to use atomic ops to ensure that the + buffer mutex is correctly initialized instead. */ + + do { + init = InterlockedCompareExchange(&dbg_buf_lock_init, 0, 1); + + if (init == 0) { + /* We won the init race, global variable is now set to 1, other + threads will spin until it becomes -1. */ + InitializeCriticalSection(&dbg_buf_lock); + dbg_buf_lock_init = -1; + init = -1; + } + } while (init >= 0); + + EnterCriticalSection(&dbg_buf_lock); + + dbg_buf_pos += vsnprintf_s( + dbg_buf + dbg_buf_pos, + sizeof(dbg_buf) - dbg_buf_pos, + sizeof(dbg_buf) - dbg_buf_pos - 1, + fmt, + ap); + + if (dbg_buf_pos + 1 > sizeof(dbg_buf)) { + abort(); + } + + if (strchr(dbg_buf, '\n') != NULL) { + OutputDebugStringA(dbg_buf); + dbg_buf_pos = 0; + dbg_buf[0] = '\0'; + } + + LeaveCriticalSection(&dbg_buf_lock); +} + +void dwprintf(const wchar_t *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + dwprintfv(fmt, ap); + va_end(ap); +} + +void dwprintfv(const wchar_t *fmt, va_list ap) +{ + wchar_t msg[512]; + + _vsnwprintf_s(msg, _countof(msg), _countof(msg) - 1, fmt, ap); + OutputDebugStringW(msg); +} + +#endif diff --git a/util/dprintf.h b/util/dprintf.h new file mode 100644 index 0000000..af071b1 --- /dev/null +++ b/util/dprintf.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#ifdef __GNUC__ +#define DPRINTF_CHK __attribute__(( format(printf, 1, 2) )) +#else +#define DPRINTF_CHK +#endif + +#ifndef NDEBUG +void dprintf(const char *fmt, ...) DPRINTF_CHK; +void dprintfv(const char *fmt, va_list ap); +void dwprintf(const wchar_t *fmt, ...); +void dwprintfv(const wchar_t *fmt, va_list ap); +#else +#define dprintf(...) +#define dprintfv(fmt, ap) +#define dwprintf(...) +#define dwprintfv(fmt, ap) +#endif diff --git a/util/dump.c b/util/dump.c new file mode 100644 index 0000000..e42448c --- /dev/null +++ b/util/dump.c @@ -0,0 +1,74 @@ +#ifndef NDEBUG + +#include +#include + +#include "hook/iobuf.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +void dump(const void *ptr, size_t nbytes) +{ + const uint8_t *bytes; + uint8_t c; + size_t i; + size_t j; + + assert(ptr != NULL || nbytes == 0); + + if (nbytes == 0) { + dprintf("\t--- Empty ---\n"); + } + + bytes = ptr; + + for (i = 0 ; i < nbytes ; i += 16) { + dprintf(" %08x:", (int) i); + + for (j = 0 ; i + j < nbytes && j < 16 ; j++) { + dprintf(" %02x", bytes[i + j]); + } + + while (j < 16) { + dprintf(" "); + j++; + } + + dprintf(" "); + + for (j = 0 ; i + j < nbytes && j < 16 ; j++) { + c = bytes[i + j]; + + if (c < 0x20 || c >= 0x7F) { + c = '.'; + } + + dprintf("%c", c); + } + + dprintf("\n"); + } + + dprintf("\n"); +} + +void dump_iobuf(const struct iobuf *iobuf) +{ + assert(iobuf != NULL); + assert(iobuf->bytes != NULL || iobuf->nbytes == 0); + assert(iobuf->pos <= iobuf->nbytes); + + dump(iobuf->bytes, iobuf->pos); +} + +void dump_const_iobuf(const struct const_iobuf *iobuf) +{ + assert(iobuf != NULL); + assert(iobuf->bytes != NULL || iobuf->nbytes == 0); + assert(iobuf->pos <= iobuf->nbytes); + + dump(&iobuf->bytes[iobuf->pos], iobuf->nbytes - iobuf->pos); +} + +#endif diff --git a/util/dump.h b/util/dump.h new file mode 100644 index 0000000..bfc3c69 --- /dev/null +++ b/util/dump.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "hook/iobuf.h" + +#ifndef NDEBUG +void dump(const void *ptr, size_t nbytes); +void dump_iobuf(const struct iobuf *iobuf); +void dump_const_iobuf(const struct const_iobuf *iobuf); +#else +#define dump(ptr, nbytes) +#define dump_iobuf(iobuf) +#define dump_const_iobuf(iobuf) +#endif diff --git a/util/lib.c b/util/lib.c new file mode 100644 index 0000000..bd7a5eb --- /dev/null +++ b/util/lib.c @@ -0,0 +1,29 @@ +#include + +#include + +wchar_t *module_file_name(HMODULE module) +{ + size_t buf_len; + DWORD len; + wchar_t *buf; + + buf_len = MAX_PATH; + buf = malloc(buf_len * sizeof(*buf)); + + while (true) { + len = GetModuleFileNameW(module, buf, buf_len); + + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + buf_len = len; + buf = realloc(buf, buf_len * sizeof(*buf)); + + break; + } + + buf_len *= 2; + buf = realloc(buf, buf_len * sizeof(*buf)); + } + + return buf; +} diff --git a/util/lib.h b/util/lib.h new file mode 100644 index 0000000..f3d717e --- /dev/null +++ b/util/lib.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +wchar_t *module_file_name(HMODULE module); diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 0000000..575d123 --- /dev/null +++ b/util/meson.build @@ -0,0 +1,25 @@ +util_lib = static_library( + 'util', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'async.c', + 'async.h', + 'crc.c', + 'crc.h', + 'dll-bind.c', + 'dll-bind.h', + 'dprintf.c', + 'dprintf.h', + 'dump.c', + 'dump.h', + 'lib.c', + 'lib.h', + 'str.c', + 'str.h', + ], +) diff --git a/util/str.c b/util/str.c new file mode 100644 index 0000000..41ea6c2 --- /dev/null +++ b/util/str.c @@ -0,0 +1,11 @@ +#include +#include +#include +#include + +#include "util/str.h" + +extern inline bool str_eq(const char *lhs, const char *rhs); +extern inline bool str_ieq(const char *lhs, const char *rhs); +extern inline bool wstr_eq(const wchar_t *lhs, const wchar_t *rhs); +extern inline bool wstr_ieq(const wchar_t *lhs, const wchar_t *rhs); diff --git a/util/str.h b/util/str.h new file mode 100644 index 0000000..b07948a --- /dev/null +++ b/util/str.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +inline bool str_eq(const char *lhs, const char *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } + + return strcmp(lhs, rhs) == 0; +} + +inline bool str_ieq(const char *lhs, const char *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } + + return _stricmp(lhs, rhs) == 0; +} + +inline bool wstr_eq(const wchar_t *lhs, const wchar_t *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } + + return wcscmp(lhs, rhs) == 0; +} + +inline bool wstr_ieq(const wchar_t *lhs, const wchar_t *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } + + return _wcsicmp(lhs, rhs) == 0; +}