Initial Commit
This commit is contained in:
commit
e0b4b49617
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
build/
|
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@ -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
|
||||||
|
|
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@ -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
|
15
.vscode/settings.json
vendored
Normal file
15
.vscode/settings.json
vendored
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
37
CHANGELOG.md
Normal file
37
CHANGELOG.md
Normal file
@ -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
|
92
CONTRIBUTING.md
Normal file
92
CONTRIBUTING.md
Normal file
@ -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.
|
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -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" ]
|
24
LICENSE
Normal file
24
LICENSE
Normal file
@ -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 <http://unlicense.org/>
|
59
Makefile
Normal file
59
Makefile
Normal file
@ -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]) }'
|
144
Package.mk
Normal file
144
Package.mk
Normal file
@ -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 $@ $^
|
22
README.md
Normal file
22
README.md
Normal file
@ -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).
|
49
amex/amex.c
Normal file
49
amex/amex.c
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#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 <assert.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
21
amex/amex.h
Normal file
21
amex/amex.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#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);
|
104
amex/config.c
Normal file
104
amex/config.c
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
21
amex/config.h
Normal file
21
amex/config.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
214
amex/ds.c
Normal file
214
amex/ds.c
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <devioctl.h>
|
||||||
|
#include <ntdddisk.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
22
amex/ds.h
Normal file
22
amex/ds.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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);
|
194
amex/eeprom.c
Normal file
194
amex/eeprom.c
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#include <ntdef.h>
|
||||||
|
#else
|
||||||
|
#include <winnt.h>
|
||||||
|
#endif
|
||||||
|
#include <devioctl.h>
|
||||||
|
#include <ntdddisk.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
20
amex/eeprom.h
Normal file
20
amex/eeprom.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
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);
|
228
amex/gpio.c
Normal file
228
amex/gpio.c
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <ntstatus.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
22
amex/gpio.h
Normal file
22
amex/gpio.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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);
|
8
amex/guid.c
Normal file
8
amex/guid.c
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <initguid.h>
|
||||||
|
|
||||||
|
#include "amex/ds.h"
|
||||||
|
#include "amex/eeprom.h"
|
||||||
|
#include "amex/gpio.h"
|
||||||
|
#include "amex/jvs.h"
|
||||||
|
#include "amex/sram.h"
|
218
amex/jvs.c
Normal file
218
amex/jvs.c
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
#define WIN32_NO_STATUS
|
||||||
|
#include <windows.h>
|
||||||
|
#undef WIN32_NO_STATUS
|
||||||
|
#include <winternl.h>
|
||||||
|
|
||||||
|
#include <ntstatus.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
22
amex/jvs.h
Normal file
22
amex/jvs.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#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);
|
28
amex/meson.build
Normal file
28
amex/meson.build
Normal file
@ -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',
|
||||||
|
],
|
||||||
|
)
|
81
amex/nvram.c
Normal file
81
amex/nvram.c
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
7
amex/nvram.h
Normal file
7
amex/nvram.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
HRESULT nvram_open_file(HANDLE *out, const wchar_t *path, size_t size);
|
160
amex/sram.c
Normal file
160
amex/sram.c
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#include <ntdef.h>
|
||||||
|
#else
|
||||||
|
#include <winnt.h>
|
||||||
|
#endif
|
||||||
|
#include <devioctl.h>
|
||||||
|
#include <ntdddisk.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
20
amex/sram.h
Normal file
20
amex/sram.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
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);
|
112
board/aime-dll.c
Normal file
112
board/aime-dll.c
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
25
board/aime-dll.h
Normal file
25
board/aime-dll.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#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);
|
41
board/config.c
Normal file
41
board/config.c
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
10
board/config.h
Normal file
10
board/config.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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);
|
3
board/guid.c
Normal file
3
board/guid.c
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#include <initguid.h>
|
||||||
|
|
||||||
|
#include "board/guid.h"
|
10
board/guid.h
Normal file
10
board/guid.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
DEFINE_GUID(
|
||||||
|
hid_guid,
|
||||||
|
0x4D1E55B2L,
|
||||||
|
0xF16F,
|
||||||
|
0x11CF,
|
||||||
|
0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
|
643
board/io3.c
Normal file
643
board/io3.c
Normal file
@ -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 <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
37
board/io3.h
Normal file
37
board/io3.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
353
board/io4.c
Normal file
353
board/io4.c
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <devioctl.h>
|
||||||
|
#include <hidclass.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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));
|
||||||
|
}
|
32
board/io4.h
Normal file
32
board/io4.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
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);
|
36
board/meson.build
Normal file
36
board/meson.build
Normal file
@ -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',
|
||||||
|
],
|
||||||
|
)
|
134
board/sg-cmd.c
Normal file
134
board/sg-cmd.c
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
43
board/sg-cmd.h
Normal file
43
board/sg-cmd.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
165
board/sg-frame.c
Normal file
165
board/sg-frame.c
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
15
board/sg-frame.h
Normal file
15
board/sg-frame.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
39
board/sg-led-cmd.h
Normal file
39
board/sg-led-cmd.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
};
|
178
board/sg-led.c
Normal file
178
board/sg-led.c
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
30
board/sg-led.h
Normal file
30
board/sg-led.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
115
board/sg-nfc-cmd.h
Normal file
115
board/sg-nfc-cmd.h
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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)
|
434
board/sg-nfc.c
Normal file
434
board/sg-nfc.c
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
39
board/sg-nfc.h
Normal file
39
board/sg-nfc.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
186
board/sg-reader.c
Normal file
186
board/sg-reader.c
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
17
board/sg-reader.h
Normal file
17
board/sg-reader.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#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);
|
10
cross-mingw-32.txt
Normal file
10
cross-mingw-32.txt
Normal file
@ -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'
|
10
cross-mingw-64.txt
Normal file
10
cross-mingw-64.txt
Normal file
@ -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'
|
48
dist/carol/segatools.ini
vendored
Normal file
48
dist/carol/segatools.ini
vendored
Normal file
@ -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
|
13
dist/carol/start.bat
vendored
Normal file
13
dist/carol/start.bat
vendored
Normal file
@ -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
|
80
dist/chuni/segatools.ini
vendored
Normal file
80
dist/chuni/segatools.ini
vendored
Normal file
@ -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 ...
|
11
dist/chuni/start.bat
vendored
Normal file
11
dist/chuni/start.bat
vendored
Normal file
@ -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
|
4
dist/cxb/resource/segatools.ini
vendored
Normal file
4
dist/cxb/resource/segatools.ini
vendored
Normal file
@ -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
|
75
dist/cxb/segatools.ini
vendored
Normal file
75
dist/cxb/segatools.ini
vendored
Normal file
@ -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
|
9
dist/cxb/start.bat
vendored
Normal file
9
dist/cxb/start.bat
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
|
inject -d -k cxbhook.dll Rev_v11.exe
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Game processes have terminated
|
||||||
|
pause
|
54
dist/diva/segatools.ini
vendored
Normal file
54
dist/diva/segatools.ini
vendored
Normal file
@ -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
|
||||||
|
|
9
dist/diva/start.bat
vendored
Normal file
9
dist/diva/start.bat
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
|
inject -d -k divahook.dll diva.exe
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Game processes have terminated
|
||||||
|
pause
|
116
dist/idz/segatools.ini
vendored
Normal file
116
dist/idz/segatools.ini
vendored
Normal file
@ -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
|
10
dist/idz/start.bat
vendored
Normal file
10
dist/idz/start.bat
vendored
Normal file
@ -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
|
44
dist/mai2/segatools.ini
vendored
Normal file
44
dist/mai2/segatools.ini
vendored
Normal file
@ -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
|
11
dist/mai2/start.bat
vendored
Normal file
11
dist/mai2/start.bat
vendored
Normal file
@ -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
|
56
dist/mercury/segatools.ini
vendored
Normal file
56
dist/mercury/segatools.ini
vendored
Normal file
@ -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
|
15
dist/mercury/start.bat
vendored
Normal file
15
dist/mercury/start.bat
vendored
Normal file
@ -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
|
60
dist/mu3/segatools.ini
vendored
Normal file
60
dist/mu3/segatools.ini
vendored
Normal file
@ -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
|
11
dist/mu3/start.bat
vendored
Normal file
11
dist/mu3/start.bat
vendored
Normal file
@ -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
|
470
doc/config/common.md
Normal file
470
doc/config/common.md
Normal file
@ -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.
|
117
doc/development.md
Normal file
117
doc/development.md
Normal file
@ -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
|
34
docker-build.bat
Normal file
34
docker-build.bat
Normal file
@ -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
|
17
gfxhook/config.c
Normal file
17
gfxhook/config.c
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
7
gfxhook/config.h
Normal file
7
gfxhook/config.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "gfxhook/gfx.h"
|
||||||
|
|
||||||
|
void gfx_config_load(struct gfx_config *cfg, const wchar_t *filename);
|
195
gfxhook/d3d11.c
Normal file
195
gfxhook/d3d11.c
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <dxgi.h>
|
||||||
|
#include <d3d11.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
7
gfxhook/d3d11.h
Normal file
7
gfxhook/d3d11.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "gfxhook/gfx.h"
|
||||||
|
|
||||||
|
void gfx_d3d11_hook_init(const struct gfx_config *cfg, HINSTANCE self);
|
251
gfxhook/d3d9.c
Normal file
251
gfxhook/d3d9.c
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <d3d9.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
7
gfxhook/d3d9.h
Normal file
7
gfxhook/d3d9.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "gfxhook/gfx.h"
|
||||||
|
|
||||||
|
void gfx_d3d9_hook_init(const struct gfx_config *cfg, HINSTANCE self);
|
364
gfxhook/dxgi.c
Normal file
364
gfxhook/dxgi.c
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <dxgi.h>
|
||||||
|
#include <dxgi1_3.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
7
gfxhook/dxgi.h
Normal file
7
gfxhook/dxgi.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "gfxhook/gfx.h"
|
||||||
|
|
||||||
|
void gfx_dxgi_hook_init(const struct gfx_config *cfg, HINSTANCE self);
|
48
gfxhook/gfx.c
Normal file
48
gfxhook/gfx.c
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
12
gfxhook/gfx.h
Normal file
12
gfxhook/gfx.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
struct gfx_config {
|
||||||
|
bool enable;
|
||||||
|
bool windowed;
|
||||||
|
bool framed;
|
||||||
|
int monitor;
|
||||||
|
};
|
||||||
|
|
||||||
|
void gfx_hook_init(const struct gfx_config *cfg);
|
28
gfxhook/meson.build
Normal file
28
gfxhook/meson.build
Normal file
@ -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',
|
||||||
|
],
|
||||||
|
)
|
116
gfxhook/util.c
Normal file
116
gfxhook/util.c
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
7
gfxhook/util.h
Normal file
7
gfxhook/util.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
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);
|
16
hooklib/config.c
Normal file
16
hooklib/config.c
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
7
hooklib/config.h
Normal file
7
hooklib/config.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "hooklib/dvd.h"
|
||||||
|
|
||||||
|
void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename);
|
257
hooklib/createprocess.c
Normal file
257
hooklib/createprocess.c
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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
|
||||||
|
);
|
||||||
|
}
|
21
hooklib/createprocess.h
Normal file
21
hooklib/createprocess.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
73
hooklib/cursor.c
Normal file
73
hooklib/cursor.c
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
3
hooklib/cursor.h
Normal file
3
hooklib/cursor.h
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void cursor_hook_init();
|
351
hooklib/dll.c
Normal file
351
hooklib/dll.c
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
/* This is general enough to break out into capnhook eventually.
|
||||||
|
Don't introduce util/ dependencies here. */
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
10
hooklib/dll.h
Normal file
10
hooklib/dll.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
HRESULT dll_hook_push(
|
||||||
|
HMODULE redir_mod,
|
||||||
|
const wchar_t *name);
|
462
hooklib/dns.c
Normal file
462
hooklib/dns.c
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
/* Might push this to capnhook, don't add any util dependencies. */
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <windns.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
9
hooklib/dns.h
Normal file
9
hooklib/dns.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
82
hooklib/dvd.c
Normal file
82
hooklib/dvd.c
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
14
hooklib/dvd.h
Normal file
14
hooklib/dvd.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
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);
|
218
hooklib/fdshark.c
Normal file
218
hooklib/fdshark.c
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user