commit e0b4b49617be0858fd251068b46dcd29d97b4945 Author: Kevin Trocolli Date: Tue Feb 6 03:24:58 2024 -0500 Initial Commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..85be427 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git +.gitignore + +build/ \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..08b6199 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org/ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{c,h}] +indent_style = space +indent_size = 4 +max_line_length = 79 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b84f6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +.*.swp + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# Suggested names for build dirs +build/ + +# External dependencies +subprojects/capnhook diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9f1bd80 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "files.associations": { + "aime.h": "c", + "dns.h": "c", + "epay.h": "c", + "amvideo.h": "c", + "ttxsec.h": "c", + "config.h": "c", + "syscfg.h": "c", + "platform.h": "c", + "stdbool.h": "c", + "dprintf.h": "c", + "slider-frame.h": "c" + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c51a4ea --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# v005 + +* Allow custom IO DLLs to be specified in INI files: + * `[aimeio] path=` for aime reader drivers + * `[chuniio] path=` for Chunithm input drivers + * `[idzio] path=` for Initial D Zero input drivers +* Add INI documentation +* Build system and contribution workflow improvements (contributed by icex2) +* Add hook to hide DVD drives (contributed by BemaniWitch) +* Add option to disable Diva slider emulation (contributed by dogtopus) +* AMEX board accuracy fixes (contributed by seika1, Felix) +* Improve multi-monitor support (contributed by BemaniWitch) +* Various Ongeki fixes (contributed by Felix) +* Various Diva slider fixes (contributed by dogtopus) + +# v004 + +* Add initial support for mounting DLC package dumps (contributed by Shiz) +* Fix configuration loading in aimeio.dll +* Build system fixes (contributed by Shiz) + +# v003 + +* Add countermeasures for DNS-spamming ISPs (contributed by mon) +* Add option for single-stick steering in IDZ (contributed by BemaniWitch) +* Fix MSVC build (contributed by mariodon) +* Make all 32 Chunithm touch slider cells' keyboard bindings configurable (see + INI) + +# v002 + +* Ship correct inject.exe for IDZ +* Fix IDZ main EXE crash in GetIfTable() hook in platform/netenv.c + +# v001 + +* Initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7934dad --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,92 @@ +# Contributing + +This document outlines different types of contributions and how YOU can help us to improve the +project. Read it, as it provides guidelines that are there to help you and the maintainers. + +## Reporting and discussions: Issues section on gitlab + +In order to avoid information management overehad on different communication channels, discussions +we ask everyone to treat the issue section on gitlab as the place to open their relevant discussions +and bug reports regarding the project. + +The maintainers of the project do not have the time nor motivation to micromanage information from +various channels. We ask *EVERYONE* to support the maintainers and developers to allow them to spend +their time on developing tasks. + +## Bug reports + +Follow these steps when reporting bugs to ensure you provide all information we *always* need and +make your report valuable and actionable. + +1. On the taitools repository, go `Issues` on the left-hand sidebar. +1. Use the search function to check if there is an already open issue regarding what you want to +report + 1. If that applies, read the open issue to check what's already covered regarding the bug + 1. Provide additional information or things that are missing. Upload your log files, + screenshots, videos etc. Be careful and remove sensitive information + 1. Give a thumbs up to the issue to show you are interested/affected as well +1. If no existing issue is avilable, create a new one +1. Come up with a descriptive title +1. **USE OUR BUG REPORTING TEMPLATE**: Pick it by selecting `Bug` on the `Description` section +1. Follow the sections and their instructions provided by the template and fill them in. All fields +are mandatory to provide a comprehensive report if not stated otherwise +1. When finished, submit the issue + +## Git history + +The project is aiming for a linear git history approach since we are convinced that it beneifts +maintenance of the project a lot in the long term. + +For further details on that topic, please refer to +[the many external articles](https://dev.to/bladesensei/avoid-messy-git-history-3g26) available +about this topic by using your favorite search engine. + +## Merge requests: bugfixes, new features or other code contributions + +Merge requests are welcome! May it be a merge request to an already known issue or a new feature that +you consider as a valuable contribution, please open a MR. + +**!!! Maintaining documentation by adding new or improving existing documentation is as important as +code !!!** + +If you want to start working on a new feature that was proposed in an issue, yet, it is recommended +to reach out to the maintainers about this, first, to discuss if this contribution is valuable +to the project. Otherwise, you might waste your time on implementing something that won't make it +into master or someone else is already working on. + +Please read our [development guidelines](doc/development.md) as they contain valuable information +that your contribution meets our standards. This is not meant to annoy people but ensures +consistency that the project stays maintainable for everyone. + +Steps for contributing to the repository using a merge request: + +1. If you are new to git, take a bit of time to learn the basics which are very simple, e.g. Google +for "git tutorial for non-programmers" +1. Fork the upstream repository (Fork button on the top right on the main page of the repository) +1. You can start editing files like documentation easily inside gitlab which might be the prefered +option for many non-coders +1. Clone your fork to your local machine and start working on stuff +1. Ensure you push your changes to your fork on gitlab +1. When done, go to the `Merge Requests` section on the left sidebar of the upstream repository +1. Hit the `New merge request` button +1. Select the `master` branch as the source branch +1. Select whatever branch you worked, likely `master` if you didn't change that, as the target +branch +1. Hit `Compare branches and continue` +1. Provide a descriptive title of what your change is about +1. **USE OUR MR TEMPLATES** + 1. If you submit a bugfix, use the `Bugfix` tempalte and fill in the sections + 1. If you submit a new feature, use the `Feature` template and fill in the sections +1. If you submit some minor fixes or documentation improvements, there is no template for that. +Please provide a expressive description what you did and *why* you did that +1. If any of your changes are tied to one or multiple issues, link them in the description +1. When done, hit `Submit merge request` + +The maintainers will take a look at your submission and provide their feedback. The intention of +this process is to ensure the contribution meets the quality standards. Please also see this is +a learning opportunity, especially with your first contribution, if a lot of comments and change +requests are being made. The maintainers are open to discuss their suggestions/feedback if +reasonable feedback is given back to them. + +Once all discussion is resolved and the involved maintainers approved your submission, it will be +merged into master and also included in the next release. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..45b0d2a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM fedora:36 + +LABEL description="Build environment for taitools" + +RUN dnf -y install meson ninja-build make zip clang mingw64-gcc.x86_64 mingw32-gcc.x86_64 git + +RUN mkdir /taitools +WORKDIR /taitools + +VOLUME [ "/taitools" ] + +ENTRYPOINT [ "make", "dist" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7838b09 --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +V ?= @ + +.DEFAULT_GOAL := help + +BUILD_DIR := build +BUILD_DIR_32 := $(BUILD_DIR)/build32 +BUILD_DIR_64 := $(BUILD_DIR)/build64 +BUILD_DIR_ZIP := $(BUILD_DIR)/zip + +DOC_DIR := doc + +DIST_DIR := dist + +# ----------------------------------------------------------------------------- +# Targets +# ----------------------------------------------------------------------------- + +include Package.mk + +.PHONY: build # Build the project +build: + $(V)meson --cross cross-mingw-32.txt $(BUILD_DIR_32) + $(V)ninja -C $(BUILD_DIR_32) + $(V)meson --cross cross-mingw-64.txt $(BUILD_DIR_64) + $(V)ninja -C $(BUILD_DIR_64) + +.PHONY: dist # Build and create a zip distribution package +dist: build clean-zip zip + +.PHONY: clean-zip # Remove zip files from build dir before packaging +clean-zip: + $(V)rm -Rf $(BUILD_DIR_ZIP)/*.zip + +.PHONY: zip # Create a zip distribution pacakge +zip: $(BUILD_DIR_ZIP)/taitools.zip + +.PHONY: clean # Cleanup build output +clean: + $(V)rm -rf $(BUILD_DIR) subprojects/capnhook + +# ----------------------------------------------------------------------------- +# Utility, combo and alias targets +# ----------------------------------------------------------------------------- + +# Help screen note: +# Variables that need to be displayed in the help screen need to strictly +# follow the pattern "^[A-Z_]+ \?= .* # .*". +# Targets that need to be displayed in the help screen need to add a separate +# phony definition strictly following the pattern "^\.PHONY\: .* # .*". + +.PHONY: help # Print help screen +help: + $(V)echo taitools makefile. + $(V)echo + $(V)echo "Environment variables:" + $(V)grep -E '^[A-Z_]+ \?\= .* #' Makefile | gawk 'match($$0, /([A-Z_]+) \?= [$$\(]*([^\)]*)[\)]{0,1} # (.*)/, a) { printf(" \033[0;35m%-25s \033[0;0m%-45s [%s]\n", a[1], a[3], a[2]) }' + $(V)echo "" + $(V)echo "Targets:" + $(V)grep '^.PHONY: .* #' Makefile | gawk 'match($$0, /\.PHONY: (.*) # (.*)/, a) { printf(" \033[0;32m%-25s \033[0;0m%s\n", a[1], a[2]) }' diff --git a/Package.mk b/Package.mk new file mode 100644 index 0000000..06765bb --- /dev/null +++ b/Package.mk @@ -0,0 +1,144 @@ +$(BUILD_DIR_ZIP)/chuni.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/chuni + $(V)mkdir -p $(BUILD_DIR_ZIP)/chuni/DEVICE + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_32)/chunihook/chunihook.dll \ + $(DIST_DIR)/chuni/taitools.ini \ + $(DIST_DIR)/chuni/start.bat \ + $(BUILD_DIR_ZIP)/chuni + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/chuni/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/chuni/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/chuni ; zip -r ../chuni.zip * + +$(BUILD_DIR_ZIP)/cxb.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/cxb + $(V)mkdir -p $(BUILD_DIR_ZIP)/cxb/DEVICE + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_32)/cxbhook/cxbhook.dll \ + $(DIST_DIR)/cxb/taitools.ini \ + $(DIST_DIR)/cxb/start.bat \ + $(BUILD_DIR_ZIP)/cxb + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/cxb/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/cxb/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/cxb ; zip -r ../cxb.zip * + +$(BUILD_DIR_ZIP)/diva.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/diva + $(V)mkdir -p $(BUILD_DIR_ZIP)/diva/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/divahook/divahook.dll \ + $(DIST_DIR)/diva/taitools.ini \ + $(DIST_DIR)/diva/start.bat \ + $(BUILD_DIR_ZIP)/diva + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/diva/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/diva/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/diva ; zip -r ../diva.zip * + +$(BUILD_DIR_ZIP)/carol.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/carol + $(V)mkdir -p $(BUILD_DIR_ZIP)/carol/DEVICE + $(V)cp $(BUILD_DIR_32)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_32)/carolhook/carolhook.dll \ + $(DIST_DIR)/carol/taitools.ini \ + $(DIST_DIR)/carol/start.bat \ + $(BUILD_DIR_ZIP)/carol + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/carol/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/carol/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/carol ; zip -r ../carol.zip * + +$(BUILD_DIR_ZIP)/idz.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/idz + $(V)mkdir -p $(BUILD_DIR_ZIP)/idz/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/idzhook/idzhook.dll \ + $(DIST_DIR)/idz/taitools.ini \ + $(DIST_DIR)/idz/start.bat \ + $(BUILD_DIR_ZIP)/idz + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/idz/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/idz/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/idz ; zip -r ../idz.zip * + +$(BUILD_DIR_ZIP)/mercury.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/mercury + $(V)mkdir -p $(BUILD_DIR_ZIP)/mercury/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/mercuryhook/mercuryhook.dll \ + $(DIST_DIR)/mercury/taitools.ini \ + $(DIST_DIR)/mercury/start.bat \ + $(BUILD_DIR_ZIP)/mercury + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/mercury/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/mercury/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/mercury ; zip -r ../mercury.zip * + + +$(BUILD_DIR_ZIP)/mu3.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/mu3 + $(V)mkdir -p $(BUILD_DIR_ZIP)/mu3/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/mu3hook/mu3hook.dll \ + $(DIST_DIR)/mu3/taitools.ini \ + $(DIST_DIR)/mu3/start.bat \ + $(BUILD_DIR_ZIP)/mu3 + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/mu3/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/mu3/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/mu3 ; zip -r ../mu3.zip * + +$(BUILD_DIR_ZIP)/mai2.zip: + $(V)echo ... $@ + $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2 + $(V)mkdir -p $(BUILD_DIR_ZIP)/mai2/DEVICE + $(V)cp $(BUILD_DIR_64)/subprojects/capnhook/inject/inject.exe \ + $(BUILD_DIR_64)/mai2hook/mai2hook.dll \ + $(DIST_DIR)/mai2/taitools.ini \ + $(DIST_DIR)/mai2/start.bat \ + $(BUILD_DIR_ZIP)/mai2 + $(V)cp pki/billing.pub \ + pki/ca.crt \ + $(BUILD_DIR_ZIP)/mai2/DEVICE + $(V)strip $(BUILD_DIR_ZIP)/mai2/*.{exe,dll} + $(V)cd $(BUILD_DIR_ZIP)/mai2 ; zip -r ../mai2.zip * + +$(BUILD_DIR_ZIP)/doc.zip: \ + $(DOC_DIR)/config \ + $(DOC_DIR)/chunihook.md \ + $(DOC_DIR)/idzhook.md \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -r $@ $^ + +$(BUILD_DIR_ZIP)/taitools.zip: \ + $(BUILD_DIR_ZIP)/chuni.zip \ + $(BUILD_DIR_ZIP)/cxb.zip \ + $(BUILD_DIR_ZIP)/carol.zip \ + $(BUILD_DIR_ZIP)/diva.zip \ + $(BUILD_DIR_ZIP)/doc.zip \ + $(BUILD_DIR_ZIP)/idz.zip \ + $(BUILD_DIR_ZIP)/mercury.zip \ + $(BUILD_DIR_ZIP)/mu3.zip \ + $(BUILD_DIR_ZIP)/mai2.zip \ + CHANGELOG.md \ + README.md \ + + $(V)echo ... $@ + $(V)zip -j $@ $^ diff --git a/README.md b/README.md new file mode 100644 index 0000000..11f186a --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Taitools + +Version: `v000` + +Loaders and hardware emulators for Taito games that run on the Taito TypeX platforms. + +## List of supported games + +## End-users + +For setup and configuration guides, refer to the dedicated documents available for each game, see +[the links in the previous section](#list-of-supported-games). + +## Contributors + +If you are/want to be a contributor of any kind, e.g. new features, bug fixes, documentation improvements, ..., please +read the [contributing documentation](CONTRIBUTING.md), first. + +## Developers + +For development setup and instructions how to build the project, refer to the +[dedicated development documentation](doc/development.md). diff --git a/amex/amex.c b/amex/amex.c new file mode 100644 index 0000000..903665f --- /dev/null +++ b/amex/amex.c @@ -0,0 +1,49 @@ +#include + +#include "amex/amex.h" +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" + +#include + +HRESULT amex_hook_init(const struct amex_config *cfg, jvs_provider_t jvs) +{ + HRESULT hr; + + assert(cfg != NULL); + + hr = ds_hook_init(&cfg->ds); + + if (FAILED(hr)) { + return hr; + } + + hr = eeprom_hook_init(&cfg->eeprom); + + if (FAILED(hr)) { + return hr; + } + + hr = gpio_hook_init(&cfg->gpio); + + if (FAILED(hr)) { + return hr; + } + + hr = jvs_hook_init(&cfg->jvs, jvs); + + if (FAILED(hr)) { + return hr; + } + + hr = sram_hook_init(&cfg->sram); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} diff --git a/amex/amex.h b/amex/amex.h new file mode 100644 index 0000000..f1a0368 --- /dev/null +++ b/amex/amex.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" + +struct amex_config { + struct ds_config ds; + struct eeprom_config eeprom; + struct gpio_config gpio; + struct jvs_config jvs; + struct sram_config sram; +}; + +HRESULT amex_hook_init( + const struct amex_config *cfg, + jvs_provider_t jvs); diff --git a/amex/config.c b/amex/config.c new file mode 100644 index 0000000..e138447 --- /dev/null +++ b/amex/config.c @@ -0,0 +1,104 @@ +#include + +#include +#include +#include +#include +#include + +#include "amex/amex.h" +#include "amex/config.h" +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" + +void ds_config_load(struct ds_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"ds", L"enable", 1, filename); + cfg->region = GetPrivateProfileIntW(L"ds", L"region", 1, filename); + + GetPrivateProfileStringW( + L"ds", + L"serialNo", + L"AAVE-01A99999999", + cfg->serial_no, + _countof(cfg->serial_no), + filename); +} + +void eeprom_config_load(struct eeprom_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"eeprom", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"eeprom", + L"path", + L"DEVICE\\eeprom.bin", + cfg->path, + _countof(cfg->path), + filename); +} + +void gpio_config_load(struct gpio_config *cfg, const wchar_t *filename) +{ + wchar_t name[7]; + size_t i; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"gpio", L"enable", 1, filename); + cfg->vk_sw1 = GetPrivateProfileIntW(L"gpio", L"sw1", VK_F1, filename); + cfg->vk_sw2 = GetPrivateProfileIntW(L"gpio", L"sw2", VK_F2, filename); + + wcscpy_s(name, _countof(name), L"dipsw0"); + + for (i = 0 ; i < 8 ; i++) { + name[5] = L'1' + i; + cfg->dipsw[i] = GetPrivateProfileIntW(L"gpio", name, 0, filename); + } +} + +void jvs_config_load(struct jvs_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"jvs", L"enable", 1, filename); +} + +void sram_config_load(struct sram_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"sram", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"sram", + L"path", + L"DEVICE\\sram.bin", + cfg->path, + _countof(cfg->path), + filename); +} + +void amex_config_load(struct amex_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + ds_config_load(&cfg->ds, filename); + eeprom_config_load(&cfg->eeprom, filename); + gpio_config_load(&cfg->gpio, filename); + jvs_config_load(&cfg->jvs, filename); + sram_config_load(&cfg->sram, filename); +} diff --git a/amex/config.h b/amex/config.h new file mode 100644 index 0000000..d088bf0 --- /dev/null +++ b/amex/config.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include +#include +#include + +#include "amex/amex.h" +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" + +void ds_config_load(struct ds_config *cfg, const wchar_t *filename); +void eeprom_config_load(struct eeprom_config *cfg, const wchar_t *filename); +void gpio_config_load(struct gpio_config *cfg, const wchar_t *filename); +void jvs_config_load(struct jvs_config *cfg, const wchar_t *filename); +void sram_config_load(struct sram_config *cfg, const wchar_t *filename); +void amex_config_load(struct amex_config *cfg, const wchar_t *filename); diff --git a/amex/ds.c b/amex/ds.c new file mode 100644 index 0000000..c0f357f --- /dev/null +++ b/amex/ds.c @@ -0,0 +1,214 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "amex/ds.h" +#include "amex/nvram.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/crc.h" +#include "util/dprintf.h" +#include "util/str.h" + +#pragma pack(push, 1) + +enum { + DS_IOCTL_GET_ABI_VERSION = 0x80006000, + DS_IOCTL_SETUP = 0x80006004, + DS_IOCTL_READ_SECTOR = 0x80006010, +}; + +struct ds_eeprom { + uint32_t crc32; + uint8_t unk_04[4]; + uint8_t region; + char serial_no[17]; + uint8_t unk_1A[6]; +}; + +static_assert(sizeof(struct ds_eeprom) == 0x20, "DS EEPROM size"); + +#pragma pack(pop) + +static HRESULT ds_handle_irp(struct irp *irp); +static HRESULT ds_handle_open(struct irp *irp); +static HRESULT ds_handle_close(struct irp *irp); +static HRESULT ds_handle_ioctl(struct irp *irp); + +static HRESULT ds_ioctl_get_geometry(struct irp *irp); +static HRESULT ds_ioctl_get_abi_version(struct irp *irp); +static HRESULT ds_ioctl_setup(struct irp *irp); +static HRESULT ds_ioctl_read_sector(struct irp *irp); + +static struct ds_eeprom ds_eeprom; +static HANDLE ds_fd; + +HRESULT ds_hook_init(const struct ds_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + memset(&ds_eeprom, 0, sizeof(ds_eeprom)); + + wcstombs_s( + NULL, + ds_eeprom.serial_no, + _countof(ds_eeprom.serial_no), + cfg->serial_no, + _countof(cfg->serial_no) - 1); + + ds_eeprom.region = cfg->region; + ds_eeprom.crc32 = crc32(&ds_eeprom.unk_04, 0x1C, 0); + + hr = iohook_push_handler(ds_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&ds_guid, L"$ds"); + + if (FAILED(hr)) { + return hr; + } + + hr = iohook_open_nul_fd(&ds_fd); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT ds_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != ds_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return ds_handle_open(irp); + case IRP_OP_CLOSE: return ds_handle_close(irp); + case IRP_OP_IOCTL: return ds_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT ds_handle_open(struct irp *irp) +{ + if (!wstr_eq(irp->open_filename, L"$ds")) { + return iohook_invoke_next(irp); + } + + dprintf("DS: Open device\n"); + irp->fd = ds_fd; + + return S_OK; +} + +static HRESULT ds_handle_close(struct irp *irp) +{ + dprintf("DS: Close device\n"); + + return S_OK; +} + +static HRESULT ds_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case IOCTL_DISK_GET_DRIVE_GEOMETRY: + return ds_ioctl_get_geometry(irp); + + case DS_IOCTL_GET_ABI_VERSION: + return ds_ioctl_get_abi_version(irp); + + case DS_IOCTL_SETUP: + return ds_ioctl_setup(irp); + + case DS_IOCTL_READ_SECTOR: + return ds_ioctl_read_sector(irp); + + default: + dprintf("DS: Unknown ioctl %08x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT ds_ioctl_get_geometry(struct irp *irp) +{ + DISK_GEOMETRY out; + HRESULT hr; + + dprintf("DS: Get geometry\n"); + + memset(&out, 0, sizeof(out)); + out.Cylinders.QuadPart = 1; + out.MediaType = 0; + out.TracksPerCylinder = 1; + out.SectorsPerTrack = 2; + out.BytesPerSector = 32; + + hr = iobuf_write(&irp->read, &out, sizeof(out)); + + if (FAILED(hr)) { + dprintf("DS: Get geometry failed: %08x\n", (int) hr); + } + + return hr; +} + +static HRESULT ds_ioctl_get_abi_version(struct irp *irp) +{ + return iobuf_write_le16(&irp->read, 256); +} + +static HRESULT ds_ioctl_setup(struct irp *irp) +{ + dprintf("DS: Setup IOCTL\n"); + + return S_OK; +} + +static HRESULT ds_ioctl_read_sector(struct irp *irp) +{ + struct const_iobuf src; + uint32_t sector_no; + HRESULT hr; + + hr = iobuf_read_le32(&irp->write, §or_no); + + if (FAILED(hr)) { + return hr; + } + + dprintf("DS: Read sector %08x\n", sector_no); + + src.bytes = (const uint8_t *) &ds_eeprom; + src.nbytes = sizeof(ds_eeprom); + src.pos = 0; + + iobuf_move(&irp->read, &src); + + return S_OK; +} diff --git a/amex/ds.h b/amex/ds.h new file mode 100644 index 0000000..0820190 --- /dev/null +++ b/amex/ds.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include +#include +#include + +struct ds_config { + bool enable; + uint8_t region; + wchar_t serial_no[17]; +}; + +DEFINE_GUID( + ds_guid, + 0x279A9F67, + 0x348F, + 0x41C9, + 0xA4, 0xC4, 0xDF, 0xDB, 0x8A, 0xE8, 0xE5, 0xE0); + +HRESULT ds_hook_init(const struct ds_config *cfg); diff --git a/amex/eeprom.c b/amex/eeprom.c new file mode 100644 index 0000000..84e4701 --- /dev/null +++ b/amex/eeprom.c @@ -0,0 +1,194 @@ +#include + +#ifdef __GNUC__ +#include +#else +#include +#endif +#include +#include + +#include + +#include "amex/eeprom.h" +#include "amex/nvram.h" + +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" +#include "util/str.h" + +enum { + EEPROM_IOCTL_GET_ABI_VERSION = 0x80006000, +}; + +static HRESULT eeprom_handle_irp(struct irp *irp); +static HRESULT eeprom_handle_open(struct irp *irp); +static HRESULT eeprom_handle_close(struct irp *irp); +static HRESULT eeprom_handle_ioctl(struct irp *irp); +static HRESULT eeprom_handle_read(struct irp *irp); +static HRESULT eeprom_handle_write(struct irp *irp); + +static HRESULT eeprom_ioctl_get_geometry(struct irp *irp); +static HRESULT eeprom_ioctl_get_abi_version(struct irp *irp); + +static struct eeprom_config eeprom_config; +static HANDLE eeprom_file; + +HRESULT eeprom_hook_init(const struct eeprom_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&eeprom_config, cfg, sizeof(*cfg)); + + hr = iohook_push_handler(eeprom_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&eeprom_guid, L"$eeprom"); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT eeprom_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != eeprom_file) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return eeprom_handle_open(irp); + case IRP_OP_CLOSE: return eeprom_handle_close(irp); + case IRP_OP_IOCTL: return eeprom_handle_ioctl(irp); + case IRP_OP_READ: return eeprom_handle_read(irp); + case IRP_OP_WRITE: return eeprom_handle_write(irp); + default: return iohook_invoke_next(irp); + } +} + +static HRESULT eeprom_handle_open(struct irp *irp) +{ + HRESULT hr; + + if (!wstr_eq(irp->open_filename, L"$eeprom") != 0) { + return iohook_invoke_next(irp); + } + + if (eeprom_file != NULL) { + dprintf("EEPROM: Already open\n"); + + return HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION); + } + + dprintf("EEPROM: Open device\n"); + hr = nvram_open_file(&eeprom_file, eeprom_config.path, 0x2000); + + if (FAILED(hr)) { + return hr; + } + + irp->fd = eeprom_file; + + return S_OK; +} + +static HRESULT eeprom_handle_close(struct irp *irp) +{ + dprintf("EEPROM: Close device\n"); + eeprom_file = NULL; + + return iohook_invoke_next(irp); +} + +static HRESULT eeprom_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case IOCTL_DISK_GET_DRIVE_GEOMETRY: + return eeprom_ioctl_get_geometry(irp); + + case EEPROM_IOCTL_GET_ABI_VERSION: + return eeprom_ioctl_get_abi_version(irp); + + default: + dprintf("EEPROM: Unknown ioctl %x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT eeprom_ioctl_get_geometry(struct irp *irp) +{ + DISK_GEOMETRY out; + HRESULT hr; + + dprintf("EEPROM: Get geometry\n"); + + memset(&out, 0, sizeof(out)); + out.Cylinders.QuadPart = 1; + out.MediaType = FixedMedia; + out.TracksPerCylinder = 224; + out.SectorsPerTrack = 32; + out.BytesPerSector = 1; + + hr = iobuf_write(&irp->read, &out, sizeof(out)); + + if (FAILED(hr)) { + dprintf("EEPROM: Get geometry failed: %08x\n", (int) hr); + } + + return hr; +} + +static HRESULT eeprom_ioctl_get_abi_version(struct irp *irp) +{ + return iobuf_write_le16(&irp->read, 256); +} + +static HRESULT eeprom_handle_read(struct irp *irp) +{ + if (irp->ovl == NULL) { + dprintf("EEPROM: Synchronous read..?\n"); + + return E_UNEXPECTED; + } + + dprintf("EEPROM: Read off %x len %x\n", + (int) irp->ovl->Offset, + (int) irp->read.nbytes); + + return iohook_invoke_next(irp); +} + +static HRESULT eeprom_handle_write(struct irp *irp) +{ + if (irp->ovl == NULL) { + dprintf("EEPROM: Synchronous write..?\n"); + + return E_UNEXPECTED; + } + + dprintf("EEPROM: Write off %x len %x\n", + (int) irp->ovl->Offset, + (int) irp->write.nbytes); + + return iohook_invoke_next(irp); +} diff --git a/amex/eeprom.h b/amex/eeprom.h new file mode 100644 index 0000000..6a73407 --- /dev/null +++ b/amex/eeprom.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +struct eeprom_config { + bool enable; + wchar_t path[MAX_PATH]; +}; + +DEFINE_GUID( + eeprom_guid, + 0xB7970F0C, + 0x31C4, + 0x45FF, + 0x96, 0x18, 0x0A, 0x24, 0x00, 0x94, 0xB2, 0x71); + +HRESULT eeprom_hook_init(const struct eeprom_config *cfg); diff --git a/amex/gpio.c b/amex/gpio.c new file mode 100644 index 0000000..4e18630 --- /dev/null +++ b/amex/gpio.c @@ -0,0 +1,228 @@ +#include +#include + +#include +#include + +#include "amex/gpio.h" + +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" +#include "util/str.h" + +enum { + GPIO_IOCTL_SET_LEDS = 0x8000A004, + GPIO_IOCTL_GET_PSW = 0x80006008, + GPIO_IOCTL_GET_DIPSW = 0x8000600C, + GPIO_IOCTL_DESCRIBE = 0x80006014, +}; + +enum { + GPIO_TYPE_NONE = 0, + GPIO_TYPE_LED = 1, + GPIO_TYPE_DIPSW = 2, + GPIO_TYPE_PSW = 3, +}; + +#pragma pack(push, 1) + +struct gpio_port { + uint8_t unknown; + + /* Number of distinct instances of this thing..? */ + uint8_t count; + + /* Type of GPIO port */ + uint16_t type; +}; + +struct gpio_ports { + uint8_t unknown; /* Maybe a count of valid items in the array idk */ + struct gpio_port ports[32]; +}; + +#pragma pack(pop) + +static HRESULT gpio_handle_irp(struct irp *irp); +static HRESULT gpio_handle_open(struct irp *irp); +static HRESULT gpio_handle_close(struct irp *irp); +static HRESULT gpio_handle_ioctl(struct irp *irp); + +static HRESULT gpio_ioctl_get_psw(struct irp *irp); +static HRESULT gpio_ioctl_get_dipsw(struct irp *irp); +static HRESULT gpio_ioctl_describe(struct irp *irp); +static HRESULT gpio_ioctl_set_leds(struct irp *irp); + +static const struct gpio_ports gpio_ports = { + .ports = { + { + .type = GPIO_TYPE_LED, + .count = 2, + }, { + .type = GPIO_TYPE_DIPSW, + .count = 8, + }, { + .type = GPIO_TYPE_PSW, + .count = 2, + } + }, +}; + +static_assert(sizeof(gpio_ports) == 129, "GPIO port map size"); + +static HANDLE gpio_fd; +static struct gpio_config gpio_config; + +HRESULT gpio_hook_init(const struct gpio_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&gpio_config, cfg, sizeof(*cfg)); + + hr = iohook_open_nul_fd(&gpio_fd); + + if (FAILED(hr)) { + return hr; + } + + hr = iohook_push_handler(gpio_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&gpio_guid, L"$gpio"); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT gpio_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != gpio_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return gpio_handle_open(irp); + case IRP_OP_CLOSE: return gpio_handle_close(irp); + case IRP_OP_IOCTL: return gpio_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT gpio_handle_open(struct irp *irp) +{ + if (!wstr_eq(irp->open_filename, L"$gpio")) { + return iohook_invoke_next(irp); + } + + dprintf("GPIO: Open device\n"); + irp->fd = gpio_fd; + + return S_OK; +} + +static HRESULT gpio_handle_close(struct irp *irp) +{ + dprintf("GPIO: Close device\n"); + + return S_OK; +} + +static HRESULT gpio_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case GPIO_IOCTL_SET_LEDS: + return gpio_ioctl_set_leds(irp); + + case GPIO_IOCTL_GET_PSW: + return gpio_ioctl_get_psw(irp); + + case GPIO_IOCTL_GET_DIPSW: + return gpio_ioctl_get_dipsw(irp); + + case GPIO_IOCTL_DESCRIBE: + return gpio_ioctl_describe(irp); + + default: + dprintf("GPIO: Unknown ioctl %08x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT gpio_ioctl_get_dipsw(struct irp *irp) +{ + uint32_t dipsw; + size_t i; + + dipsw = 0; + + for (i = 0 ; i < 8 ; i++) { + if (gpio_config.dipsw[i]) { + dipsw |= 1 << i; + } + } + + //dprintf("GPIO: Get dipsw %08x\n", dipsw); + + return iobuf_write_le32(&irp->read, dipsw); +} + +static HRESULT gpio_ioctl_get_psw(struct irp *irp) +{ + uint32_t result; + + result = 0; + + /* Bit 0 == SW1 == Alt. Test */ + /* Bit 1 == SW2 == Alt. Service */ + + if (GetAsyncKeyState(gpio_config.vk_sw1) & 0x8000) { + result |= 1 << 0; + } + + if (GetAsyncKeyState(gpio_config.vk_sw2) & 0x8000) { + result |= 1 << 1; + } + + return iobuf_write_le32(&irp->read, result); +} + +static HRESULT gpio_ioctl_describe(struct irp *irp) +{ + HRESULT hr; + + dprintf("GPIO: Describe GPIO ports\n"); + + hr = iobuf_write(&irp->read, &gpio_ports, sizeof(gpio_ports)); + + if (FAILED(hr)) { + dprintf("GPIO: Describe GPIO ports failed: %08x\n", (int) hr); + } + + return hr; +} + +static HRESULT gpio_ioctl_set_leds(struct irp *irp) +{ + return S_OK; +} diff --git a/amex/gpio.h b/amex/gpio.h new file mode 100644 index 0000000..110562d --- /dev/null +++ b/amex/gpio.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include +#include + +struct gpio_config { + bool enable; + uint8_t vk_sw1; + uint8_t vk_sw2; + bool dipsw[8]; +}; + +DEFINE_GUID( + gpio_guid, + 0xE9A26688, + 0xF522, + 0x44FA, + 0xBF, 0xEE, 0x59, 0xDD, 0x16, 0x15, 0x56, 0x6C); + +HRESULT gpio_hook_init(const struct gpio_config *cfg); diff --git a/amex/guid.c b/amex/guid.c new file mode 100644 index 0000000..de51804 --- /dev/null +++ b/amex/guid.c @@ -0,0 +1,8 @@ +#include +#include + +#include "amex/ds.h" +#include "amex/eeprom.h" +#include "amex/gpio.h" +#include "amex/jvs.h" +#include "amex/sram.h" diff --git a/amex/jvs.c b/amex/jvs.c new file mode 100644 index 0000000..40d9673 --- /dev/null +++ b/amex/jvs.c @@ -0,0 +1,218 @@ +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include + +#include + +#include +#include + +#include "amex/jvs.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "jvs/jvs-bus.h" + +#include "util/dprintf.h" +#include "util/dump.h" +#include "util/str.h" + +enum { + JVS_IOCTL_HELLO = 0x80006004, + JVS_IOCTL_SENSE = 0x8000600C, + JVS_IOCTL_TRANSACT = 0x8000E008, +}; + +static HRESULT jvs_handle_irp(struct irp *irp); +static HRESULT jvs_handle_open(struct irp *irp); +static HRESULT jvs_handle_close(struct irp *irp); +static HRESULT jvs_handle_ioctl(struct irp *irp); + +static HRESULT jvs_ioctl_hello(struct irp *irp); +static HRESULT jvs_ioctl_sense(struct irp *irp); +static HRESULT jvs_ioctl_transact(struct irp *irp); + +static HANDLE jvs_fd; +static struct jvs_node *jvs_root; +static jvs_provider_t jvs_provider; + +HRESULT jvs_hook_init(const struct jvs_config *cfg, jvs_provider_t provider) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + hr = iohook_push_handler(jvs_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&jvs_guid, L"$jvs"); + + if (FAILED(hr)) { + return hr; + } + + jvs_provider = provider; + + return S_OK; +} + +static HRESULT jvs_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != jvs_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return jvs_handle_open(irp); + case IRP_OP_CLOSE: return jvs_handle_close(irp); + case IRP_OP_IOCTL: return jvs_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT jvs_handle_open(struct irp *irp) +{ + struct jvs_node *root; + HRESULT hr; + + if (!wstr_eq(irp->open_filename, L"$jvs")) { + return iohook_invoke_next(irp); + } + + if (jvs_fd != NULL) { + dprintf("JVS Port: Already open\n"); + + return HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION); + } + + hr = iohook_open_nul_fd(&jvs_fd); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS Port: Open device\n"); + + if (jvs_provider != NULL) { + hr = jvs_provider(&root); + + if (SUCCEEDED(hr)) { + jvs_root = root; + } + } + + irp->fd = jvs_fd; + + return S_OK; +} + +static HRESULT jvs_handle_close(struct irp *irp) +{ + dprintf("JVS Port: Close device\n"); + jvs_fd = NULL; + + return iohook_invoke_next(irp); +} + +static HRESULT jvs_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case JVS_IOCTL_HELLO: + return jvs_ioctl_hello(irp); + + case JVS_IOCTL_SENSE: + return jvs_ioctl_sense(irp); + + case JVS_IOCTL_TRANSACT: + return jvs_ioctl_transact(irp); + + default: + dprintf("JVS Port: Unknown ioctl %#x\n", irp->ioctl); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT jvs_ioctl_hello(struct irp *irp) +{ + HRESULT hr; + + // uuh fucked if i know + + dprintf("JVS Port: Port startup (?)\n"); + + iobuf_write_8(&irp->read, 0); + hr = iobuf_write_8(&irp->read, 0); + + return hr; +} + +static HRESULT jvs_ioctl_sense(struct irp *irp) +{ + uint8_t code; + bool sense; + + if (jvs_root != NULL) { + sense = jvs_root->sense(jvs_root); + + if (sense) { + dprintf("JVS Port: Sense line 2.5 V (address unassigned)\n"); + code = 3; + } else { + dprintf("JVS Port: Sense line 0.0 V (address assigned)\n"); + code = 2; + } + } else { + dprintf("JVS Port: Sense line 5.0 V (no downstream PCB)\n"); + code = 1; + } + + return iobuf_write_8(&irp->read, code); +} + +static HRESULT jvs_ioctl_transact(struct irp *irp) +{ +#if 0 + dprintf("\nJVS Port: Outbound frame:\n"); + dump_const_iobuf(&irp->write); +#endif + + jvs_bus_transact(jvs_root, irp->write.bytes, irp->write.nbytes, &irp->read); + +#if 0 + dprintf("JVS Port: Inbound frame:\n"); + dump_iobuf(&irp->read); + dprintf("\n"); +#endif + + if (irp->read.pos == 0) { + /* The un-acked JVS reset command must return ERROR_NO_DATA_DETECTED, + and this error must always be returned asynchronously. And since + async I/O comes from the NT kernel, we have to return that win32 + error as the equivalent NTSTATUS. */ + + if (irp->ovl == NULL || irp->ovl->hEvent == NULL) { + return HRESULT_FROM_WIN32(ERROR_NO_DATA_DETECTED); + } + + irp->ovl->Internal = STATUS_NO_DATA_DETECTED; + SetEvent(irp->ovl->hEvent); + + return HRESULT_FROM_WIN32(ERROR_IO_PENDING); + } else { + return S_OK; + } +} diff --git a/amex/jvs.h b/amex/jvs.h new file mode 100644 index 0000000..0bedb0f --- /dev/null +++ b/amex/jvs.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include "jvs/jvs-bus.h" + +DEFINE_GUID( + jvs_guid, + 0xDB6BBB45, + 0xCC96, + 0x4288, + 0xAA, 0x00, 0x6C, 0x00, 0xD7, 0x67, 0xBD, 0xBF); + +struct jvs_config { + bool enable; +}; + +typedef HRESULT (*jvs_provider_t)(struct jvs_node **root); + +HRESULT jvs_hook_init(const struct jvs_config *cfg, jvs_provider_t provider); diff --git a/amex/meson.build b/amex/meson.build new file mode 100644 index 0000000..0f4d61e --- /dev/null +++ b/amex/meson.build @@ -0,0 +1,28 @@ +amex_lib = static_library( + 'amex', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'amex.c', + 'amex.h', + 'config.c', + 'config.h', + 'ds.c', + 'ds.h', + 'eeprom.c', + 'eeprom.h', + 'gpio.c', + 'gpio.h', + 'guid.c', + 'jvs.c', + 'jvs.h', + 'nvram.c', + 'nvram.h', + 'sram.c', + 'sram.h', + ], +) diff --git a/amex/nvram.c b/amex/nvram.c new file mode 100644 index 0000000..bbade62 --- /dev/null +++ b/amex/nvram.c @@ -0,0 +1,81 @@ +#include + +#include +#include +#include + +#include "amex/nvram.h" + +#include "util/dprintf.h" + +HRESULT nvram_open_file(HANDLE *out, const wchar_t *path, size_t size) +{ + LARGE_INTEGER cur_size; + LARGE_INTEGER pos; + HANDLE file; + HRESULT hr; + BOOL ok; + + assert(out != NULL); + assert(path != NULL); + + *out = NULL; + + file = CreateFileW( + path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (file == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("%S: Error opening backing store: %x\n", path, (int) hr); + + goto end; + } + + ok = GetFileSizeEx(file, &cur_size); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("%S: GetFileSizeEx failed: %x\n", path, (int) hr); + + goto end; + } + + if (cur_size.QuadPart != (uint64_t) size) { + pos.QuadPart = (uint64_t) size; + ok = SetFilePointerEx(file, pos, NULL, FILE_BEGIN); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("%S: SetFilePointerEx failed: %x\n", path, (int) hr); + + goto end; + } + + ok = SetEndOfFile(file); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("%S: SetEndOfFile failed: %x\n", path, (int) hr); + + goto end; + } + } + + *out = file; + file = INVALID_HANDLE_VALUE; + + hr = S_OK; + +end: + if (file != INVALID_HANDLE_VALUE) { + CloseHandle(file); + } + + return hr; +} diff --git a/amex/nvram.h b/amex/nvram.h new file mode 100644 index 0000000..ae6f532 --- /dev/null +++ b/amex/nvram.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include + +HRESULT nvram_open_file(HANDLE *out, const wchar_t *path, size_t size); diff --git a/amex/sram.c b/amex/sram.c new file mode 100644 index 0000000..c5a195a --- /dev/null +++ b/amex/sram.c @@ -0,0 +1,160 @@ +#include + +#ifdef __GNUC__ +#include +#else +#include +#endif +#include +#include + +#include + +#include "amex/sram.h" +#include "amex/nvram.h" + +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" +#include "util/str.h" + +enum { + SRAM_IOCTL_GET_ABI_VERSION = 0x80006000, +}; + +static HRESULT sram_handle_irp(struct irp *irp); +static HRESULT sram_handle_open(struct irp *irp); +static HRESULT sram_handle_close(struct irp *irp); +static HRESULT sram_handle_ioctl(struct irp *irp); + +static HRESULT sram_ioctl_get_geometry(struct irp *irp); +static HRESULT sram_ioctl_get_abi_version(struct irp *irp); + +static struct sram_config sram_config; +static HANDLE sram_file; + +HRESULT sram_hook_init(const struct sram_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&sram_config, cfg, sizeof(*cfg)); + + hr = iohook_push_handler(sram_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = setupapi_add_phantom_dev(&sram_guid, L"$sram"); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT sram_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != sram_file) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return sram_handle_open(irp); + case IRP_OP_CLOSE: return sram_handle_close(irp); + case IRP_OP_IOCTL: return sram_handle_ioctl(irp); + default: return iohook_invoke_next(irp); + } +} + +static HRESULT sram_handle_open(struct irp *irp) +{ + HRESULT hr; + + if (!wstr_eq(irp->open_filename, L"$sram")) { + return iohook_invoke_next(irp); + } + + if (sram_file != NULL) { + dprintf("SRAM: Already open\n"); + + return HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION); + } + + dprintf("SRAM: Open device\n"); + hr = nvram_open_file(&sram_file, sram_config.path, 0x80000); + + if (FAILED(hr)) { + return hr; + } + + irp->fd = sram_file; + + return S_OK; +} + +static HRESULT sram_handle_close(struct irp *irp) +{ + dprintf("SRAM: Close device\n"); + sram_file = NULL; + + return iohook_invoke_next(irp); +} + +static HRESULT sram_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case IOCTL_DISK_GET_DRIVE_GEOMETRY: + return sram_ioctl_get_geometry(irp); + + case SRAM_IOCTL_GET_ABI_VERSION: + return sram_ioctl_get_abi_version(irp); + + default: + dprintf("SRAM: Unknown ioctl %x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT sram_ioctl_get_geometry(struct irp *irp) +{ + DISK_GEOMETRY out; + HRESULT hr; + + dprintf("SRAM: Get geometry\n"); + + memset(&out, 0, sizeof(out)); + out.Cylinders.QuadPart = 0x20000; + out.MediaType = 0; + out.TracksPerCylinder = 1; + out.SectorsPerTrack = 1; + out.BytesPerSector = 4; + + hr = iobuf_write(&irp->read, &out, sizeof(out)); + + if (FAILED(hr)) { + dprintf("SRAM: Get geometry failed: %08x\n", (int) hr); + } + + return hr; +} + +static HRESULT sram_ioctl_get_abi_version(struct irp *irp) +{ + return iobuf_write_le16(&irp->read, 256); +} diff --git a/amex/sram.h b/amex/sram.h new file mode 100644 index 0000000..561b2ea --- /dev/null +++ b/amex/sram.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +struct sram_config { + bool enable; + wchar_t path[MAX_PATH]; +}; + +DEFINE_GUID( + sram_guid, + 0x741B5FCA, + 0x4635, + 0x4443, + 0xA7, 0xA0, 0x57, 0xCA, 0x7B, 0x50, 0x6A, 0x49); + +HRESULT sram_hook_init(const struct sram_config *cfg); diff --git a/board/aime-dll.c b/board/aime-dll.c new file mode 100644 index 0000000..de8eb1e --- /dev/null +++ b/board/aime-dll.c @@ -0,0 +1,112 @@ +#include + +#include +#include + +#include "board/aime-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym aime_dll_syms[] = { + { + .sym = "aime_io_init", + .off = offsetof(struct aime_dll, init), + }, { + .sym = "aime_io_nfc_poll", + .off = offsetof(struct aime_dll, nfc_poll), + }, { + .sym = "aime_io_nfc_get_aime_id", + .off = offsetof(struct aime_dll, nfc_get_aime_id), + }, { + .sym = "aime_io_nfc_get_felica_id", + .off = offsetof(struct aime_dll, nfc_get_felica_id), + }, { + .sym = "aime_io_led_set_color", + .off = offsetof(struct aime_dll, led_set_color), + } +}; + +struct aime_dll aime_dll; + +// Copypasta DLL binding and diagnostic message boilerplate. +// Not much of this lends itself to being easily factored out. Also there +// will be a lot of API-specific branching code here eventually as new API +// versions get defined, so even though these functions all look the same +// now this won't remain the case forever. + +HRESULT aime_dll_init(const struct aime_dll_config *cfg, HINSTANCE self) +{ + uint16_t (*get_api_version)(void); + const struct dll_bind_sym *sym; + HINSTANCE owned; + HINSTANCE src; + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (cfg->path[0] != L'\0') { + owned = LoadLibraryW(cfg->path); + + if (owned == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("NFC Assembly: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("NFC Assembly: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "aime_io_get_api_version"); + + if (get_api_version != NULL) { + aime_dll.api_version = get_api_version(); + } else { + aime_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose aime_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (aime_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("NFC Assembly: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Taitools.\n", + aime_dll.api_version); + + goto end; + } + + sym = aime_dll_syms; + hr = dll_bind(&aime_dll, src, &sym, _countof(aime_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("NFC Assembly: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + + goto end; + } else { + dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); + } + } + + owned = NULL; + +end: + if (owned != NULL) { + FreeLibrary(owned); + } + + return hr; +} diff --git a/board/aime-dll.h b/board/aime-dll.h new file mode 100644 index 0000000..354516b --- /dev/null +++ b/board/aime-dll.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "aimeio/aimeio.h" + +struct aime_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*nfc_poll)(uint8_t unit_no); + HRESULT (*nfc_get_aime_id)( + uint8_t unit_no, + uint8_t *luid, + size_t luid_size); + HRESULT (*nfc_get_felica_id)(uint8_t unit_no, uint64_t *IDm); + void (*led_set_color)(uint8_t unit_no, uint8_t r, uint8_t g, uint8_t b); +}; + +struct aime_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct aime_dll aime_dll; + +HRESULT aime_dll_init(const struct aime_dll_config *cfg, HINSTANCE self); diff --git a/board/config.c b/board/config.c new file mode 100644 index 0000000..191425a --- /dev/null +++ b/board/config.c @@ -0,0 +1,41 @@ +#include + +#include +#include +#include +#include + +#include "board/aime-dll.h" +#include "board/config.h" +#include "board/sg-reader.h" + +static void aime_dll_config_load(struct aime_dll_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"aimeio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void aime_config_load(struct aime_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + aime_dll_config_load(&cfg->dll, filename); + cfg->enable = GetPrivateProfileIntW(L"aime", L"enable", 1, filename); +} + +void io4_config_load(struct io4_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"io4", L"enable", 1, filename); +} diff --git a/board/config.h b/board/config.h new file mode 100644 index 0000000..f436a82 --- /dev/null +++ b/board/config.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +#include "board/io4.h" +#include "board/sg-reader.h" + +void aime_config_load(struct aime_config *cfg, const wchar_t *filename); +void io4_config_load(struct io4_config *cfg, const wchar_t *filename); diff --git a/board/guid.c b/board/guid.c new file mode 100644 index 0000000..e987000 --- /dev/null +++ b/board/guid.c @@ -0,0 +1,3 @@ +#include + +#include "board/guid.h" diff --git a/board/guid.h b/board/guid.h new file mode 100644 index 0000000..707e85a --- /dev/null +++ b/board/guid.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +DEFINE_GUID( + hid_guid, + 0x4D1E55B2L, + 0xF16F, + 0x11CF, + 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30); diff --git a/board/io3.c b/board/io3.c new file mode 100644 index 0000000..369714f --- /dev/null +++ b/board/io3.c @@ -0,0 +1,643 @@ +/* + Sega "Type 3" JVS I/O emulator + + Credits: + + Protocol docs: + https://github.com/TheOnlyJoey/openjvs/wiki/ (a/o October 2018) + + Capability dumps: + https://wiki.arcadeotaku.com/w/JVS#Sega_837-14572 (a/o October 2018) +*/ + +#include + +#include +#include +#include +#include + +#include "board/io3.h" + +#include "jvs/jvs-bus.h" +#include "jvs/jvs-cmd.h" +#include "jvs/jvs-util.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static void io3_transact( + struct jvs_node *node, + const void *bytes, + size_t nbytes, + struct iobuf *resp); + +static bool io3_sense(struct jvs_node *node); + +static HRESULT io3_cmd( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp); + +static HRESULT io3_cmd_read_id( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_cmd_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_jvs_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_comm_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_get_features( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_read_switches( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_read_coin( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_read_analogs( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_write_gpio( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static HRESULT io3_cmd_reset(struct io3 *io3, struct const_iobuf *buf); + +static HRESULT io3_cmd_assign_addr( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf); + +static const uint8_t io3_ident[] = + "SEGA CORPORATION;I/O BD JVS;837-14572;Ver1.00;2005/10"; + +static uint8_t io3_features[] = { + /* Feature : 0x01 : Players and switches + Param1 : 2 : Number of players + Param2 : 14 : Number of switches per player + Param3 : 0 : N/A */ + + 0x01, 2, 14, 0, + + /* Feature : 0x02 : Coin slots + Param1 : 2 : Number of coin slots + Param2 : 0 : N/A + Param3 : 0 : N/A */ + + 0x02, 2, 0, 0, + + /* Feature : 0x03 : Analog inputs + Param1 : 8 : Number of ADC channels + Param2 : 10 : Effective bits of resolution per ADC + Param3 : 0 : N/A */ + + 0x03, 8, 10, 0, + + /* Feature : 0x12 : GPIO outputs + Param1 : 3 : Number of ports (8 bits per port) + Param2 : 0 : N/A + Param3 : 0 : N/A + + NOTE: This particular port count is what an IO-4 attached over JVS + advertises, an IO-3 only advertises 3. Still, this seems to be backwards + compatible with games that expect an IO-3, and the protocols seem to be + identical otherwise. */ + + 0x12, 20, 0, 0, + + /* Feature : 0x00 : End of capabilities */ + + 0x00, +}; + +void io3_init( + struct io3 *io3, + struct jvs_node *next, + const struct io3_ops *ops, + void *ops_ctx) +{ + assert(io3 != NULL); + assert(ops != NULL); + + io3->jvs.next = next; + io3->jvs.transact = io3_transact; + io3->jvs.sense = io3_sense; + io3->addr = 0xFF; + io3->ops = ops; + io3->ops_ctx = ops_ctx; +} + +struct jvs_node *io3_to_jvs_node(struct io3 *io3) +{ + assert(io3 != NULL); + + return &io3->jvs; +} + +static void io3_transact( + struct jvs_node *node, + const void *bytes, + size_t nbytes, + struct iobuf *resp) +{ + struct io3 *io3; + + assert(node != NULL); + assert(bytes != NULL); + assert(resp != NULL); + + io3 = CONTAINING_RECORD(node, struct io3, jvs); + + jvs_crack_request(bytes, nbytes, resp, io3->addr, io3_cmd, io3); +} + +static bool io3_sense(struct jvs_node *node) +{ + struct io3 *io3; + + assert(node != NULL); + + io3 = CONTAINING_RECORD(node, struct io3, jvs); + + return io3->addr == 0xFF; +} + +static HRESULT io3_cmd( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp) +{ + struct io3 *io3; + + io3 = ctx; + + switch (req->bytes[req->pos]) { + case JVS_CMD_READ_ID: + return io3_cmd_read_id(io3, req, resp); + + case JVS_CMD_GET_CMD_VERSION: + return io3_cmd_get_cmd_version(io3, req, resp); + + case JVS_CMD_GET_JVS_VERSION: + return io3_cmd_get_jvs_version(io3, req, resp); + + case JVS_CMD_GET_COMM_VERSION: + return io3_cmd_get_comm_version(io3, req, resp); + + case JVS_CMD_GET_FEATURES: + return io3_cmd_get_features(io3, req, resp); + + case JVS_CMD_READ_SWITCHES: + return io3_cmd_read_switches(io3, req, resp); + + case JVS_CMD_READ_COIN: + return io3_cmd_read_coin(io3, req, resp); + + case JVS_CMD_READ_ANALOGS: + return io3_cmd_read_analogs(io3, req, resp); + + case JVS_CMD_WRITE_GPIO: + return io3_cmd_write_gpio(io3, req, resp); + + case JVS_CMD_RESET: + return io3_cmd_reset(io3, req); + + case JVS_CMD_ASSIGN_ADDR: + return io3_cmd_assign_addr(io3, req, resp); + + default: + dprintf("JVS I/O: Node %02x: Unhandled command byte %02x\n", + io3->addr, + req->bytes[req->pos]); + + return E_NOTIMPL; + } +} + +static HRESULT io3_cmd_read_id( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Read ID\n"); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write the identification string. The NUL terminator at the end of this C + string is also sent, and it naturally terminates the response chunk. */ + + return iobuf_write(resp_buf, io3_ident, sizeof(io3_ident)); +} + +static HRESULT io3_cmd_get_cmd_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + uint8_t resp[2]; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get command format version\n"); + resp[0] = 0x01; /* Report byte */ + resp[1] = 0x13; /* Command format version BCD */ + + return iobuf_write(resp_buf, resp, sizeof(resp)); +} + +static HRESULT io3_cmd_get_jvs_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + uint8_t resp[2]; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get JVS version\n"); + resp[0] = 0x01; /* Report byte */ + resp[1] = 0x20; /* JVS version BCD */ + + return iobuf_write(resp_buf, resp, sizeof(resp)); +} + +static HRESULT io3_cmd_get_comm_version( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + uint8_t resp[2]; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get communication version\n"); + resp[0] = 0x01; /* Report byte */ + resp[1] = 0x10; /* "Communication version" BCD */ + + return iobuf_write(resp_buf, resp, sizeof(resp)); +} + +static HRESULT io3_cmd_get_features( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t req; + HRESULT hr; + + hr = iobuf_read_8(req_buf, &req); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Get features\n"); + + hr = iobuf_write_8(resp_buf, 0x01); /* Write report byte */ + + if (FAILED(hr)) { + return hr; + } + + return iobuf_write(resp_buf, io3_features, sizeof(io3_features)); +} + +static HRESULT io3_cmd_read_switches( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_switches req; + struct io3_switch_state state; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + +#if 0 + dprintf("JVS I/O: Read switches, np=%i, bpp=%i\n", + req.num_players, + req.bytes_per_player); +#endif + + if (req.num_players > 2 || req.bytes_per_player != 2) { + dprintf("JVS I/O: Invalid read size " + "num_players=%i " + "bytes_per_player=%i\n", + req.num_players, + req.bytes_per_player); + hr = iobuf_write_8(resp_buf, 0x02); + + if (FAILED(hr)) { + return hr; + } + + return E_FAIL; + } + + /* Build response */ + + hr = iobuf_write_8(resp_buf, 0x01); /* Report byte */ + + if (FAILED(hr)) { + return hr; + } + + memset(&state, 0, sizeof(state)); + + if (io3->ops != NULL) { + io3->ops->read_switches(io3->ops_ctx, &state); + } + + hr = iobuf_write_8(resp_buf, state.system); /* Test, Tilt lines */ + + if (FAILED(hr)) { + return hr; + } + + if (req.num_players > 0) { + hr = iobuf_write_be16(resp_buf, state.p1); + + if (FAILED(hr)) { + return hr; + } + } + + if (req.num_players > 1) { + hr = iobuf_write_be16(resp_buf, state.p2); + + if (FAILED(hr)) { + return hr; + } + } + + return hr; +} + +static HRESULT io3_cmd_read_coin( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_coin req; + uint16_t ncoins; + uint8_t i; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + //dprintf("JVS I/O: Read coin, nslots=%i\n", req.nslots); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write slot detail */ + + for (i = 0 ; i < req.nslots ; i++) { + ncoins = 0; + + if (io3->ops->read_coin_counter != NULL) { + io3->ops->read_coin_counter(io3->ops_ctx, i, &ncoins); + } + + hr = iobuf_write_be16(resp_buf, ncoins); + + if (FAILED(hr)) { + return hr; + } + } + + return hr; +} + +static HRESULT io3_cmd_read_analogs( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_read_analogs req; + uint16_t analogs[8]; + uint8_t i; + HRESULT hr; + + /* Read req */ + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + if (req.nanalogs > _countof(analogs)) { + dprintf("JVS I/O: Invalid analog count %i\n", req.nanalogs); + + return E_FAIL; + } + + //dprintf("JVS I/O: Read analogs, nanalogs=%i\n", req.nanalogs); + + /* Write report byte */ + + hr = iobuf_write_8(resp_buf, 0x01); + + if (FAILED(hr)) { + return hr; + } + + /* Write analogs */ + + memset(analogs, 0, sizeof(analogs)); + + if (io3->ops->read_analogs != NULL) { + io3->ops->read_analogs(io3->ops_ctx, analogs, req.nanalogs); + } + + for (i = 0 ; i < req.nanalogs ; i++) { + hr = iobuf_write_be16(resp_buf, analogs[i]); + + if (FAILED(hr)) { + return hr; + } + } + + return hr; + +} + +static HRESULT io3_cmd_write_gpio( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + uint8_t cmd; + uint8_t nbytes; + uint8_t bytes[3]; + HRESULT hr; + + /* Read request header */ + + hr = iobuf_read_8(req_buf, &cmd); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req_buf, &nbytes); + + if (FAILED(hr)) { + return hr; + } + + if (nbytes > 3) { + dprintf("JVS I/O: Invalid GPIO write size %i\n", nbytes); + hr = iobuf_write_8(resp_buf, 0x02); + + if (FAILED(hr)) { + return hr; + } + + return E_FAIL; + } + + /* Read payload */ + + memset(bytes, 0, sizeof(bytes)); + hr = iobuf_read(req_buf, bytes, nbytes); + + if (FAILED(hr)) { + return hr; + } + + if (io3->ops->write_gpio != NULL) { + io3->ops->write_gpio( + io3->ops_ctx, + bytes[0] | (bytes[1] << 8) | (bytes[2] << 16)); + } + + /* Write report byte */ + + return iobuf_write_8(resp_buf, 0x01); +} + +static HRESULT io3_cmd_reset(struct io3 *io3, struct const_iobuf *req_buf) +{ + struct jvs_req_reset req; + HRESULT hr; + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + dprintf("JVS I/O: Reset (param %02x)\n", req.unknown); + io3->addr = 0xFF; + + if (io3->ops->reset != NULL) { + io3->ops->reset(io3->ops_ctx); + } + + /* No ack for this since it really is addressed to everybody */ + + return S_OK; +} + +static HRESULT io3_cmd_assign_addr( + struct io3 *io3, + struct const_iobuf *req_buf, + struct iobuf *resp_buf) +{ + struct jvs_req_assign_addr req; + bool sense; + HRESULT hr; + + hr = iobuf_read(req_buf, &req, sizeof(req)); + + if (FAILED(hr)) { + return hr; + } + + sense = jvs_node_sense(io3->jvs.next); + dprintf("JVS I/O: Assign addr %02x sense %i\n", req.addr, sense); + + if (sense) { + /* That address is for somebody else */ + return S_OK; + } + + io3->addr = req.addr; + + return iobuf_write_8(resp_buf, 0x01); +} diff --git a/board/io3.h b/board/io3.h new file mode 100644 index 0000000..a094a2a --- /dev/null +++ b/board/io3.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "jvs/jvs-bus.h" + +struct io3_switch_state { + /* Note: this struct is host-endian. The IO3 emulator handles the conversion + to protocol-endian. */ + + uint8_t system; + uint16_t p1; + uint16_t p2; +}; + +struct io3_ops { + void (*reset)(void *ctx); + void (*write_gpio)(void *ctx, uint32_t state); + void (*read_switches)(void *ctx, struct io3_switch_state *out); + void (*read_analogs)(void *ctx, uint16_t *analogs, uint8_t nanalogs); + void (*read_coin_counter)(void *ctx, uint8_t slot_no, uint16_t *out); +}; + +struct io3 { + struct jvs_node jvs; + uint8_t addr; + const struct io3_ops *ops; + void *ops_ctx; +}; + +void io3_init( + struct io3 *io3, + struct jvs_node *next, + const struct io3_ops *ops, + void *ops_ctx); + +struct jvs_node *io3_to_jvs_node(struct io3 *io3); diff --git a/board/io4.c b/board/io4.c new file mode 100644 index 0000000..e7fe9e3 --- /dev/null +++ b/board/io4.c @@ -0,0 +1,353 @@ +#include + +#include +#include + +#include +#include +#include +#include + +#include "board/config.h" +#include "board/guid.h" +#include "board/io4.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/setupapi.h" + +#include "util/async.h" +#include "util/dprintf.h" + +#pragma pack(push, 1) + +enum { + IO4_CMD_SET_COMM_TIMEOUT = 0x01, + IO4_CMD_SET_SAMPLING_COUNT = 0x02, + IO4_CMD_CLEAR_BOARD_STATUS = 0x03, + IO4_CMD_SET_GENERAL_OUTPUT = 0x04, + IO4_CMD_SET_PWM_OUTPUT = 0x05, + IO4_CMD_UNIMPLEMENTED = 0x41, + IO4_CMD_UPDATE_FIRMWARE = 0x85, +}; + +struct io4_report_in { + uint8_t report_id; + uint16_t adcs[8]; + uint16_t spinners[4]; + uint16_t chutes[2]; + uint16_t buttons[2]; + uint8_t system_status; + uint8_t usb_status; + uint8_t unknown[29]; +}; + +static_assert(sizeof(struct io4_report_in) == 0x40, "IO4 IN report size"); + +struct io4_report_out { + uint8_t report_id; + uint8_t cmd; + uint8_t payload[62]; +}; + +static_assert(sizeof(struct io4_report_out) == 0x40, "IO4 OUT report size"); + +#pragma pack(pop) + + +static HRESULT io4_handle_irp(struct irp *irp); +static HRESULT io4_handle_open(struct irp *irp); +static HRESULT io4_handle_close(struct irp *irp); +static HRESULT io4_handle_read(struct irp *irp); +static HRESULT io4_handle_write(struct irp *irp); +static HRESULT io4_handle_ioctl(struct irp *irp); + +static HRESULT io4_ioctl_get_manufacturer_string(struct irp *irp); +static HRESULT io4_ioctl_get_product_string(struct irp *irp); + +static HRESULT io4_async_poll(void *ctx, struct irp *irp); + +/* Device node path must contain substring "vid_0ca3" (case-insensitive). */ +static const wchar_t io4_path[] = L"$io4\\vid_0ca3"; + +static const wchar_t io4_manf[] = L"SEGA"; +static const wchar_t io4_prod[] = + /* "Product" (N.B. numbers are in hex) */ + + L"I/O CONTROL BD;" /* Board type */ + L"15257;" /* Board number */ + L"01;" /* "Mode" (prob. USB vs JVS?) */ + L"90;" /* Firmware revision */ + L"1831;" /* Firmware checksum */ + L"6679A;" /* "Custom chip no" */ + L"00;" /* "Config" */ + + /* "Function" (N.B. all values are in hex) */ + + L"GOUT=14_" /* General-purpose output */ + L"ADIN=8,E_" /* ADC inputs */ + L"ROTIN=4_" /* Rotary inputs */ + L"COININ=2_" /* Coin inputs */ + L"SWIN=2,E_" /* Switch inputs */ + L"UQ1=41,6" /* "Unique function 1" */ + ; + +static HANDLE io4_fd; +static struct async io4_async; +static uint8_t io4_system_status; +static const struct io4_ops *io4_ops; +static void *io4_ops_ctx; + +HRESULT io4_hook_init( + const struct io4_config *cfg, + const struct io4_ops *ops, + void *ctx) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(ops != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + async_init(&io4_async, NULL); + + hr = iohook_open_nul_fd(&io4_fd); + + if (FAILED(hr)) { + return hr; + } + + io4_ops = ops; + io4_ops_ctx = ctx; + io4_system_status = 0x02; /* idk */ + iohook_push_handler(io4_handle_irp); + + hr = setupapi_add_phantom_dev(&hid_guid, io4_path); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT io4_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != io4_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return io4_handle_open(irp); + case IRP_OP_CLOSE: return io4_handle_close(irp); + case IRP_OP_READ: return io4_handle_read(irp); + case IRP_OP_WRITE: return io4_handle_write(irp); + case IRP_OP_IOCTL: return io4_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT io4_handle_open(struct irp *irp) +{ + if (wcscmp(irp->open_filename, io4_path) != 0) { + return iohook_invoke_next(irp); + } + + dprintf("USB I/O: Device opened\n"); + irp->fd = io4_fd; + + return S_OK; +} + +static HRESULT io4_handle_close(struct irp *irp) +{ + dprintf("USB I/O: Device closed\n"); + + return S_OK; +} + +static HRESULT io4_handle_read(struct irp *irp) +{ + /* The amdaemon USBIO driver will continuously poll the IO until the IO + call returns an async operation in progress. We have to return and then + signal the OVERLAPPED event object "a little bit later" in order to avoid + an infinite loop. */ + + return async_submit(&io4_async, irp, io4_async_poll); +} + +static HRESULT io4_handle_write(struct irp *irp) +{ + struct io4_report_out out; + HRESULT hr; + + hr = iobuf_read(&irp->write, &out, sizeof(out)); + + if (FAILED(hr)) { + return hr; + } + + if (out.report_id != 0x10) { + dprintf("USB I/O: OUT Report ID is incorrect"); + + return E_FAIL; + } + + switch (out.cmd) { + case IO4_CMD_SET_COMM_TIMEOUT: + dprintf("USB I/O: Set comm timeout\n"); + + // Ongeki Summer expects the system status to be 0x30 at this point + io4_system_status = 0x30; + + return S_OK; + + case IO4_CMD_SET_SAMPLING_COUNT: + dprintf("USB I/O: Set sampling count\n"); + + // Ongeki Summer expects the system status to be 0x30 at this point + io4_system_status = 0x30; + + return S_OK; + + case IO4_CMD_CLEAR_BOARD_STATUS: + dprintf("USB I/O: Clear board status\n"); + io4_system_status = 0x00; + + return S_OK; + + case IO4_CMD_SET_GENERAL_OUTPUT: + dprintf("USB I/O: GPIO Out\n"); + + return S_OK; + + case IO4_CMD_SET_PWM_OUTPUT: + dprintf("USB I/O: PWM Out\n"); + + return S_OK; + + case IO4_CMD_UPDATE_FIRMWARE: + dprintf("USB I/O: Update firmware..?\n"); + + return E_FAIL; + + case IO4_CMD_UNIMPLEMENTED: + //dprintf("USB I/O: Unimplemented cmd 41\n"); + + return S_OK; + + default: + dprintf("USB I/O: Unknown command %02x\n", out.cmd); + + return E_FAIL; + } +} + +static HRESULT io4_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case IOCTL_HID_GET_MANUFACTURER_STRING: + return io4_ioctl_get_manufacturer_string(irp); + + case IOCTL_HID_GET_PRODUCT_STRING: + return io4_ioctl_get_product_string(irp); + + case IOCTL_HID_GET_INPUT_REPORT: + dprintf("USB I/O: Control IN (untested!!)\n"); + + return io4_handle_read(irp); + + case IOCTL_HID_SET_OUTPUT_REPORT: + dprintf("USB I/O: Control OUT (untested!!)\n"); + + return io4_handle_write(irp); + + default: + dprintf("USB I/O: Unknown ioctl %#08x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT io4_ioctl_get_manufacturer_string(struct irp *irp) +{ + dprintf("USB I/O: Get manufacturer string\n"); + + if (irp->read.nbytes < sizeof(io4_manf)) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + memcpy(irp->read.bytes, io4_manf, sizeof(io4_manf)); + irp->read.pos = sizeof(io4_manf); + + return S_OK; +} + +static HRESULT io4_ioctl_get_product_string(struct irp *irp) +{ + dprintf("USB I/O: Get product string\n"); + + if (irp->read.nbytes < sizeof(io4_prod)) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + memcpy(irp->read.bytes, io4_prod, sizeof(io4_prod)); + irp->read.pos = sizeof(io4_prod); + + return S_OK; +} + +static HRESULT io4_async_poll(void *ctx, struct irp *irp) +{ + struct io4_report_in in; + struct io4_state state; + HRESULT hr; + size_t i; + + /* Delay long enough for the instigating thread in amdaemon to be satisfied + that all queued-up reports have been drained. */ + + Sleep(1); + + /* Call into ops to poll the underlying inputs */ + + memset(&state, 0, sizeof(state)); + hr = io4_ops->poll(io4_ops_ctx, &state); + + if (FAILED(hr)) { + return hr; + } + + /* Construct IN report. Values are all little-endian, unlike JVS. */ + + memset(&in, 0, sizeof(in)); + in.report_id = 0x01; + in.system_status = io4_system_status; + + for (i = 0 ; i < 8 ; i++) { + in.adcs[i] = state.adcs[i]; + } + + for (i = 0 ; i < 4 ; i++) { + in.spinners[i] = state.spinners[i]; + } + + for (i = 0 ; i < 2 ; i++) { + in.chutes[i] = state.chutes[i]; + } + + for (i = 0 ; i < 2 ; i++) { + in.buttons[i] = state.buttons[i]; + } + + return iobuf_write(&irp->read, &in, sizeof(in)); +} diff --git a/board/io4.h b/board/io4.h new file mode 100644 index 0000000..1a6cc05 --- /dev/null +++ b/board/io4.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include + +enum { + /* System buttons in button[0] */ + + IO4_BUTTON_TEST = 1 << 9, + IO4_BUTTON_SERVICE = 1 << 6, +}; + +struct io4_config { + bool enable; +}; + +struct io4_state { + uint16_t adcs[8]; + uint16_t spinners[4]; + uint16_t chutes[2]; + uint16_t buttons[2]; +}; + +struct io4_ops { + HRESULT (*poll)(void *ctx, struct io4_state *state); +}; + +HRESULT io4_hook_init( + const struct io4_config *cfg, + const struct io4_ops *ops, + void *ctx); diff --git a/board/meson.build b/board/meson.build new file mode 100644 index 0000000..a710ef6 --- /dev/null +++ b/board/meson.build @@ -0,0 +1,36 @@ +board_lib = static_library( + 'board', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + link_with : [ + iccard_lib, + ], + sources : [ + 'aime-dll.c', + 'aime-dll.h', + 'config.c', + 'config.h', + 'guid.c', + 'guid.h', + 'io3.c', + 'io3.h', + 'io4.c', + 'io4.h', + 'sg-cmd.c', + 'sg-cmd.h', + 'sg-frame.c', + 'sg-frame.h', + 'sg-led.c', + 'sg-led.h', + 'sg-led-cmd.h', + 'sg-nfc.c', + 'sg-nfc.h', + 'sg-nfc-cmd.h', + 'sg-reader.c', + 'sg-reader.h', + ], +) diff --git a/board/sg-cmd.c b/board/sg-cmd.c new file mode 100644 index 0000000..0ed6f25 --- /dev/null +++ b/board/sg-cmd.c @@ -0,0 +1,134 @@ +#include + +#include "board/sg-cmd.h" +#include "board/sg-frame.h" + +#include "hook/iobuf.h" + +#include "util/dprintf.h" + +union sg_req_any { + struct sg_req_header req; + uint8_t bytes[256]; +}; + +union sg_res_any { + struct sg_res_header res; + uint8_t bytes[256]; +}; + +static HRESULT sg_req_validate(const void *ptr, size_t nbytes); + +static void sg_res_error( + struct sg_res_header *res, + const struct sg_req_header *req); + +static HRESULT sg_req_validate(const void *ptr, size_t nbytes) +{ + const struct sg_req_header *req; + size_t payload_len; + + assert(ptr != NULL); + + if (nbytes < sizeof(*req)) { + dprintf("SG Cmd: Request header truncated\n"); + + return E_FAIL; + } + + req = ptr; + + if (req->hdr.frame_len != nbytes) { + dprintf("SG Cmd: Frame length mismatch: got %i exp %i\n", + req->hdr.frame_len, + (int) nbytes); + + return E_FAIL; + } + + payload_len = req->hdr.frame_len - sizeof(*req); + + if (req->payload_len != payload_len) { + dprintf("SG Cmd: Payload length mismatch: got %i exp %i\n", + req->payload_len, + (int) payload_len); + + return E_FAIL; + } + + return S_OK; +} + +void sg_req_transact( + struct iobuf *res_frame, + const uint8_t *req_bytes, + size_t req_nbytes, + sg_dispatch_fn_t dispatch, + void *ctx) +{ + struct iobuf req_span; + union sg_req_any req; + union sg_res_any res; + HRESULT hr; + + assert(res_frame != NULL); + assert(req_bytes != NULL); + assert(dispatch != NULL); + + req_span.bytes = req.bytes; + req_span.nbytes = sizeof(req.bytes); + req_span.pos = 0; + + hr = sg_frame_decode(&req_span, req_bytes, req_nbytes); + + if (FAILED(hr)) { + return; + } + + hr = sg_req_validate(req.bytes, req_span.pos); + + if (FAILED(hr)) { + return; + } + + hr = dispatch(ctx, &req, &res); + + if (hr != S_FALSE) { + if (FAILED(hr)) { + sg_res_error(&res.res, &req.req); + } + + sg_frame_encode(res_frame, res.bytes, res.res.hdr.frame_len); + } +} + +void sg_res_init( + struct sg_res_header *res, + const struct sg_req_header *req, + size_t payload_len) +{ + assert(res != NULL); + assert(req != NULL); + + res->hdr.frame_len = sizeof(*res) + payload_len; + res->hdr.addr = req->hdr.addr; + res->hdr.seq_no = req->hdr.seq_no; + res->hdr.cmd = req->hdr.cmd; + res->status = 0; + res->payload_len = payload_len; +} + +static void sg_res_error( + struct sg_res_header *res, + const struct sg_req_header *req) +{ + assert(res != NULL); + assert(req != NULL); + + res->hdr.frame_len = sizeof(*res); + res->hdr.addr = req->hdr.addr; + res->hdr.seq_no = req->hdr.seq_no; + res->hdr.cmd = req->hdr.cmd; + res->status = 1; + res->payload_len = 0; +} diff --git a/board/sg-cmd.h b/board/sg-cmd.h new file mode 100644 index 0000000..685377f --- /dev/null +++ b/board/sg-cmd.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +struct sg_header { + uint8_t frame_len; + uint8_t addr; + uint8_t seq_no; + uint8_t cmd; +}; + +struct sg_req_header { + struct sg_header hdr; + uint8_t payload_len; +}; + +struct sg_res_header { + struct sg_header hdr; + uint8_t status; + uint8_t payload_len; +}; + +typedef HRESULT (*sg_dispatch_fn_t)( + void *ctx, + const void *req, + void *res); + +void sg_req_transact( + struct iobuf *res_frame, + const uint8_t *req_bytes, + size_t req_nbytes, + sg_dispatch_fn_t dispatch, + void *ctx); + +void sg_res_init( + struct sg_res_header *res, + const struct sg_req_header *req, + size_t payload_len); diff --git a/board/sg-frame.c b/board/sg-frame.c new file mode 100644 index 0000000..d65dbb1 --- /dev/null +++ b/board/sg-frame.c @@ -0,0 +1,165 @@ +#include + +#include +#include +#include +#include + +#include "board/sg-frame.h" + +#include "hook/iobuf.h" + +#include "util/dprintf.h" + +static HRESULT sg_frame_accept(struct iobuf *dest); +static HRESULT sg_frame_encode_byte(struct iobuf *dest, uint8_t byte); + +/* Frame structure: + + [0] Sync byte (0xE0) + [1] Frame size (including self) + [2] Address + [3] Sequence no + ... Body + [n] Checksum: Sum of all non-framing bytes + + Byte stuffing: + + 0xD0 is an escape byte. Un-escape the subsequent byte by adding 1. */ + +static HRESULT sg_frame_accept(struct iobuf *dest) +{ + uint8_t checksum; + size_t i; + + if (dest->pos < 1 || dest->pos != dest->bytes[0] + 1) { + dprintf("SG Frame: Size mismatch\n"); + + return S_FALSE; + } + + checksum = 0; + + for (i = 0 ; i < dest->pos - 1 ; i++) { + checksum += dest->bytes[i]; + } + + if (checksum != dest->bytes[dest->pos - 1]) { + dprintf("SG Frame: Checksum mismatch\n"); + + return HRESULT_FROM_WIN32(ERROR_CRC); + } + + /* Discard checksum */ + dest->pos--; + + return S_OK; +} + +HRESULT sg_frame_decode(struct iobuf *dest, const uint8_t *bytes, size_t nbytes) +{ + uint8_t byte; + size_t i; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(bytes != NULL); + + if (nbytes < 1 || bytes[0] != 0xE0) { + dprintf("SG Frame: Bad sync\n"); + + return E_FAIL; + } + + dest->pos = 0; + i = 1; + + while (i < nbytes) { + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + byte = bytes[i++]; + + if (byte == 0xE0) { + dprintf("SG Frame: Unescaped sync\n"); + + return E_FAIL; + } else if (byte == 0xD0) { + if (i >= nbytes) { + dprintf("SG Frame: Trailing escape\n"); + + return E_FAIL; + } + + byte = bytes[i++]; + dest->bytes[dest->pos++] = byte + 1; + } else { + dest->bytes[dest->pos++] = byte; + } + } + + return sg_frame_accept(dest); +} + +HRESULT sg_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *src; + uint8_t checksum; + uint8_t byte; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + assert(ptr != NULL); + + src = ptr; + + assert(nbytes != 0 && src[0] == nbytes); + + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xE0; + checksum = 0; + + for (i = 0 ; i < nbytes ; i++) { + byte = src[i]; + checksum += byte; + + hr = sg_frame_encode_byte(dest, byte); + + if (FAILED(hr)) { + return hr; + } + } + + return sg_frame_encode_byte(dest, checksum); +} + +static HRESULT sg_frame_encode_byte(struct iobuf *dest, uint8_t byte) +{ + if (byte == 0xD0 || byte == 0xE0) { + if (dest->pos + 2 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xD0; + dest->bytes[dest->pos++] = byte - 1; + } else { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + } + + return S_OK; +} diff --git a/board/sg-frame.h b/board/sg-frame.h new file mode 100644 index 0000000..d74d9b3 --- /dev/null +++ b/board/sg-frame.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +HRESULT sg_frame_decode( + struct iobuf *dest, + const uint8_t *bytes, + size_t nbytes); + +HRESULT sg_frame_encode(struct iobuf *dest, const void *ptr, size_t nbytes); diff --git a/board/sg-led-cmd.h b/board/sg-led-cmd.h new file mode 100644 index 0000000..f74505b --- /dev/null +++ b/board/sg-led-cmd.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "board/sg-cmd.h" + +enum { + SG_RGB_CMD_SET_COLOR = 0x81, + SG_RGB_CMD_RESET = 0xF5, + SG_RGB_CMD_GET_INFO = 0xF0, +}; + +struct sg_led_res_reset { + struct sg_res_header res; + uint8_t payload; +}; + +struct sg_led_res_get_info { + struct sg_res_header res; + uint8_t payload[9]; +}; + +struct sg_led_req_set_color { + struct sg_req_header req; + uint8_t payload[3]; +}; + +union sg_led_req_any { + uint8_t bytes[256]; + struct sg_req_header simple; + struct sg_led_req_set_color set_color; +}; + +union sg_led_res_any { + uint8_t bytes[256]; + struct sg_res_header simple; + struct sg_led_res_reset reset; + struct sg_led_res_get_info get_info; +}; diff --git a/board/sg-led.c b/board/sg-led.c new file mode 100644 index 0000000..855e36b --- /dev/null +++ b/board/sg-led.c @@ -0,0 +1,178 @@ +#include + +#include + +#include "board/sg-cmd.h" +#include "board/sg-led.h" +#include "board/sg-led-cmd.h" + +#include "util/dprintf.h" + +static HRESULT sg_led_dispatch( + void *ctx, + const void *v_req, + void *v_res); + +static HRESULT sg_led_cmd_reset( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_reset *res); + +static HRESULT sg_led_cmd_get_info( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_get_info *res); + +static HRESULT sg_led_cmd_set_color( + const struct sg_led *led, + const struct sg_led_req_set_color *req); + +static const uint8_t sg_led_info[] = { + '1', '5', '0', '8', '4', 0xFF, 0x10, 0x00, 0x12, +}; + +void sg_led_init( + struct sg_led *led, + uint8_t addr, + const struct sg_led_ops *ops, + void *ctx) +{ + assert(led != NULL); + assert(ops != NULL); + + led->ops = ops; + led->ops_ctx = ctx; + led->addr = addr; +} + +void sg_led_transact( + struct sg_led *led, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes) +{ + assert(led != NULL); + assert(res_frame != NULL); + assert(req_bytes != NULL); + + sg_req_transact(res_frame, req_bytes, req_nbytes, sg_led_dispatch, led); +} + +#ifdef NDEBUG +#define sg_led_dprintfv(led, fmt, ap) +#define sg_led_dprintf(led, fmt, ...) +#else +static void sg_led_dprintfv( + const struct sg_led *led, + const char *fmt, + va_list ap) +{ + dprintf("RGB LED %02x: ", led->addr); + dprintfv(fmt, ap); +} + +static void sg_led_dprintf( + const struct sg_led *led, + const char *fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + sg_led_dprintfv(led, fmt, ap); + va_end(ap); +} +#endif + +static HRESULT sg_led_dispatch( + void *ctx, + const void *v_req, + void *v_res) +{ + const struct sg_led *led; + const union sg_led_req_any *req; + union sg_led_res_any *res; + + led = ctx; + req = v_req; + res = v_res; + + if (req->simple.hdr.addr != led->addr) { + /* Not addressed to us, don't send a response */ + return S_FALSE; + } + + switch (req->simple.hdr.cmd) { + case SG_RGB_CMD_RESET: + return sg_led_cmd_reset(led, &req->simple, &res->reset); + + case SG_RGB_CMD_GET_INFO: + return sg_led_cmd_get_info(led, &req->simple, &res->get_info); + + case SG_RGB_CMD_SET_COLOR: + return sg_led_cmd_set_color(led, &req->set_color); + + default: + sg_led_dprintf(led, "Unimpl command %02x\n", req->simple.hdr.cmd); + + return E_NOTIMPL; + } +} + +static HRESULT sg_led_cmd_reset( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_reset *res) +{ + HRESULT hr; + + sg_led_dprintf(led, "Reset\n"); + sg_res_init(&res->res, req, sizeof(res->payload)); + res->payload = 0; + + if (led->ops->reset != NULL) { + hr = led->ops->reset(led->ops_ctx); + } else { + hr = S_OK; + } + + if (FAILED(hr)) { + sg_led_dprintf(led, "led->ops->reset: Error %x\n", hr); + return hr; + } + + return S_OK; +} + +static HRESULT sg_led_cmd_get_info( + const struct sg_led *led, + const struct sg_req_header *req, + struct sg_led_res_get_info *res) +{ + sg_led_dprintf(led, "Get info\n"); + sg_res_init(&res->res, req, sizeof(res->payload)); + memcpy(res->payload, sg_led_info, sizeof(sg_led_info)); + + return S_OK; +} + +static HRESULT sg_led_cmd_set_color( + const struct sg_led *led, + const struct sg_led_req_set_color *req) +{ + if (req->req.payload_len != sizeof(req->payload)) { + sg_led_dprintf(led, "%s: Payload size is incorrect\n", __func__); + + goto fail; + } + + led->ops->set_color( + led->ops_ctx, + req->payload[0], + req->payload[1], + req->payload[2]); + +fail: + /* No response */ + return S_FALSE; +} diff --git a/board/sg-led.h b/board/sg-led.h new file mode 100644 index 0000000..de3caa6 --- /dev/null +++ b/board/sg-led.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include "hook/iobuf.h" + +struct sg_led_ops { + HRESULT (*reset)(void *ctx); + void (*set_color)(void *ctx, uint8_t r, uint8_t g, uint8_t b); +}; + +struct sg_led { + const struct sg_led_ops *ops; + void *ops_ctx; + uint8_t addr; +}; + +void sg_led_init( + struct sg_led *led, + uint8_t addr, + const struct sg_led_ops *ops, + void *ctx); + +void sg_led_transact( + struct sg_led *led, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes); diff --git a/board/sg-nfc-cmd.h b/board/sg-nfc-cmd.h new file mode 100644 index 0000000..b6754c2 --- /dev/null +++ b/board/sg-nfc-cmd.h @@ -0,0 +1,115 @@ +#pragma once + +#include + +#pragma pack(push, 1) + +enum { + SG_NFC_CMD_GET_FW_VERSION = 0x30, + SG_NFC_CMD_GET_HW_VERSION = 0x32, + SG_NFC_CMD_RADIO_ON = 0x40, + SG_NFC_CMD_RADIO_OFF = 0x41, + SG_NFC_CMD_POLL = 0x42, + SG_NFC_CMD_MIFARE_SELECT_TAG = 0x43, + SG_NFC_CMD_MIFARE_SET_KEY_BANA = 0x50, + SG_NFC_CMD_MIFARE_READ_BLOCK = 0x52, + SG_NFC_CMD_MIFARE_SET_KEY_AIME = 0x54, + SG_NFC_CMD_MIFARE_AUTHENTICATE = 0x55, /* guess based on time sent */ + SG_NFC_CMD_RESET = 0x62, + SG_NFC_CMD_FELICA_ENCAP = 0x71, +}; + +struct sg_nfc_res_get_fw_version { + struct sg_res_header res; + char version[23]; +}; + +struct sg_nfc_res_get_hw_version { + struct sg_res_header res; + char version[23]; +}; + +struct sg_nfc_req_mifare_set_key { + struct sg_req_header req; + uint8_t key_a[6]; +}; + +struct sg_nfc_req_mifare_50 { + struct sg_req_header req; + uint8_t payload[6]; +}; + +struct sg_nfc_req_poll_40 { + struct sg_req_header req; + uint8_t payload; +}; + +struct sg_nfc_poll_mifare { + uint8_t type; + uint8_t id_len; + uint32_t uid; +}; + +struct sg_nfc_poll_felica { + uint8_t type; + uint8_t id_len; + uint64_t IDm; + uint64_t PMm; +}; + +struct sg_nfc_res_poll { + struct sg_res_header res; + uint8_t count; + uint8_t payload[250]; +}; + +struct sg_nfc_req_mifare_select_tag { + struct sg_res_header res; + uint32_t uid; +}; + +struct sg_nfc_req_mifare_read_block { + struct sg_req_header req; + struct { + uint32_t uid; + uint8_t block_no; + } payload; +}; + +struct sg_nfc_res_mifare_read_block { + struct sg_res_header res; + uint8_t block[16]; +}; + +struct sg_nfc_req_felica_encap { + struct sg_req_header req; + uint64_t IDm; + uint8_t payload[243]; +}; + +struct sg_nfc_res_felica_encap { + struct sg_res_header res; + uint8_t payload[250]; +}; + +union sg_nfc_req_any { + uint8_t bytes[256]; + struct sg_req_header simple; + struct sg_nfc_req_mifare_set_key mifare_set_key; + struct sg_nfc_req_mifare_read_block mifare_read_block; + struct sg_nfc_req_mifare_50 mifare_50; + struct sg_nfc_req_poll_40 poll_40; + struct sg_nfc_req_felica_encap felica_encap; +}; + +union sg_nfc_res_any { + uint8_t bytes[256]; + struct sg_res_header simple; + struct sg_nfc_res_get_fw_version get_fw_version; + struct sg_nfc_res_get_hw_version get_hw_version; + struct sg_nfc_res_poll poll; + struct sg_nfc_res_mifare_read_block mifare_read_block; + struct sg_nfc_res_felica_encap felica_encap; +}; + +#pragma pack(pop) diff --git a/board/sg-nfc.c b/board/sg-nfc.c new file mode 100644 index 0000000..ce5d620 --- /dev/null +++ b/board/sg-nfc.c @@ -0,0 +1,434 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "board/sg-cmd.h" +#include "board/sg-nfc.h" +#include "board/sg-nfc-cmd.h" + +#include "iccard/nesica.h" +#include "iccard/felica.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT sg_nfc_dispatch( + void *ctx, + const void *v_req, + void *v_res); + +static HRESULT sg_nfc_cmd_reset( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res); + +static HRESULT sg_nfc_cmd_get_fw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_fw_version *res); + +static HRESULT sg_nfc_cmd_get_hw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_hw_version *res); + +static HRESULT sg_nfc_cmd_poll( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_poll *res); + +static HRESULT sg_nfc_poll_aime( + struct sg_nfc *nfc, + struct sg_nfc_poll_mifare *mifare); + +static HRESULT sg_nfc_poll_felica( + struct sg_nfc *nfc, + struct sg_nfc_poll_felica *felica); + +static HRESULT sg_nfc_cmd_mifare_read_block( + struct sg_nfc *nfc, + const struct sg_nfc_req_mifare_read_block *req, + struct sg_nfc_res_mifare_read_block *res); + +static HRESULT sg_nfc_cmd_felica_encap( + struct sg_nfc *nfc, + const struct sg_nfc_req_felica_encap *req, + struct sg_nfc_res_felica_encap *res); + +static HRESULT sg_nfc_cmd_dummy( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res); + +void sg_nfc_init( + struct sg_nfc *nfc, + uint8_t addr, + const struct sg_nfc_ops *ops, + void *ops_ctx) +{ + assert(nfc != NULL); + assert(ops != NULL); + + nfc->ops = ops; + nfc->ops_ctx = ops_ctx; + nfc->addr = addr; +} + +#ifdef NDEBUG +#define sg_nfc_dprintfv(nfc, fmt, ap) +#define sg_nfc_dprintf(nfc, fmt, ...) +#else +static void sg_nfc_dprintfv( + const struct sg_nfc *nfc, + const char *fmt, + va_list ap) +{ + dprintf("NFC %02x: ", nfc->addr); + dprintfv(fmt, ap); +} + +static void sg_nfc_dprintf( + const struct sg_nfc *nfc, + const char *fmt, + ...) +{ + va_list ap; + + va_start(ap, fmt); + sg_nfc_dprintfv(nfc, fmt, ap); + va_end(ap); +} +#endif + +void sg_nfc_transact( + struct sg_nfc *nfc, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes) +{ + assert(nfc != NULL); + assert(res_frame != NULL); + assert(req_bytes != NULL); + + sg_req_transact(res_frame, req_bytes, req_nbytes, sg_nfc_dispatch, nfc); +} + +static HRESULT sg_nfc_dispatch( + void *ctx, + const void *v_req, + void *v_res) +{ + struct sg_nfc *nfc; + const union sg_nfc_req_any *req; + union sg_nfc_res_any *res; + + nfc = ctx; + req = v_req; + res = v_res; + + if (req->simple.hdr.addr != nfc->addr) { + /* Not addressed to us, don't send a response */ + return S_FALSE; + } + + switch (req->simple.hdr.cmd) { + case SG_NFC_CMD_RESET: + return sg_nfc_cmd_reset(nfc, &req->simple, &res->simple); + + case SG_NFC_CMD_GET_FW_VERSION: + return sg_nfc_cmd_get_fw_version( + nfc, + &req->simple, + &res->get_fw_version); + + case SG_NFC_CMD_GET_HW_VERSION: + return sg_nfc_cmd_get_hw_version( + nfc, + &req->simple, + &res->get_hw_version); + + case SG_NFC_CMD_POLL: + return sg_nfc_cmd_poll( + nfc, + &req->simple, + &res->poll); + + case SG_NFC_CMD_MIFARE_READ_BLOCK: + return sg_nfc_cmd_mifare_read_block( + nfc, + &req->mifare_read_block, + &res->mifare_read_block); + + case SG_NFC_CMD_FELICA_ENCAP: + return sg_nfc_cmd_felica_encap( + nfc, + &req->felica_encap, + &res->felica_encap); + + case SG_NFC_CMD_MIFARE_AUTHENTICATE: + case SG_NFC_CMD_MIFARE_SELECT_TAG: + case SG_NFC_CMD_MIFARE_SET_KEY_AIME: + case SG_NFC_CMD_MIFARE_SET_KEY_BANA: + case SG_NFC_CMD_RADIO_ON: + case SG_NFC_CMD_RADIO_OFF: + return sg_nfc_cmd_dummy(nfc, &req->simple, &res->simple); + + default: + sg_nfc_dprintf(nfc, "Unimpl command %02x\n", req->simple.hdr.cmd); + + return E_NOTIMPL; + } +} + +static HRESULT sg_nfc_cmd_reset( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res) +{ + sg_nfc_dprintf(nfc, "Reset\n"); + sg_res_init(res, req, 0); + res->status = 3; + + return S_OK; +} + +static HRESULT sg_nfc_cmd_get_fw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_fw_version *res) +{ + /* Dest version is not NUL terminated, this is intentional */ + sg_res_init(&res->res, req, sizeof(res->version)); + memcpy(res->version, "TN32MSEC003S F/W Ver1.2E", sizeof(res->version)); + + return S_OK; +} + +static HRESULT sg_nfc_cmd_get_hw_version( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_get_hw_version *res) +{ + /* Dest version is not NUL terminated, this is intentional */ + sg_res_init(&res->res, req, sizeof(res->version)); + memcpy(res->version, "TN32MSEC003S H/W Ver3.0J", sizeof(res->version)); + + return S_OK; +} + +static HRESULT sg_nfc_cmd_poll( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_nfc_res_poll *res) +{ + struct sg_nfc_poll_mifare mifare; + struct sg_nfc_poll_felica felica; + HRESULT hr; + + hr = nfc->ops->poll(nfc->ops_ctx); + + if (FAILED(hr)) { + return hr; + } + + hr = sg_nfc_poll_felica(nfc, &felica); + + if (SUCCEEDED(hr) && hr != S_FALSE) { + sg_res_init(&res->res, req, 1 + sizeof(felica)); + memcpy(res->payload, &felica, sizeof(felica)); + res->count = 1; + + return S_OK; + } + + hr = sg_nfc_poll_aime(nfc, &mifare); + + if (SUCCEEDED(hr) && hr != S_FALSE) { + sg_res_init(&res->res, req, 1 + sizeof(mifare)); + memcpy(res->payload, &mifare, sizeof(mifare)); + res->count = 1; + + return S_OK; + } + + sg_res_init(&res->res, req, 1); + res->count = 0; + + return S_OK; +} + +static HRESULT sg_nfc_poll_aime( + struct sg_nfc *nfc, + struct sg_nfc_poll_mifare *mifare) +{ + uint8_t luid[10]; + HRESULT hr; + + /* Call backend */ + + if (nfc->ops->get_aime_id != NULL) { + hr = nfc->ops->get_aime_id(nfc->ops_ctx, luid, sizeof(luid)); + } else { + hr = S_FALSE; + } + + if (FAILED(hr) || hr == S_FALSE) { + return hr; + } + + sg_nfc_dprintf(nfc, "AiMe card is present\n"); + + /* Construct response (use an arbitrary UID) */ + + mifare->type = 0x10; + mifare->id_len = sizeof(mifare->uid); + mifare->uid = _byteswap_ulong(0x01020304); + + /* Initialize MIFARE IC emulator */ + + hr = aime_card_populate(&nfc->mifare, luid, sizeof(luid)); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT sg_nfc_poll_felica( + struct sg_nfc *nfc, + struct sg_nfc_poll_felica *felica) +{ + uint64_t IDm; + HRESULT hr; + + /* Call backend */ + + if (nfc->ops->get_felica_id != NULL) { + hr = nfc->ops->get_felica_id(nfc->ops_ctx, &IDm); + } else { + hr = S_FALSE; + } + + if (FAILED(hr) || hr == S_FALSE) { + return hr; + } + + sg_nfc_dprintf(nfc, "FeliCa card is present\n"); + + /* Construct poll response */ + + felica->type = 0x20; + felica->id_len = sizeof(felica->IDm) + sizeof(felica->PMm); + felica->IDm = _byteswap_uint64(IDm); + felica->PMm = _byteswap_uint64(felica_get_generic_PMm()); + + /* Initialize FeliCa IC emulator */ + + nfc->felica.IDm = IDm; + nfc->felica.PMm = felica_get_generic_PMm(); + nfc->felica.system_code = 0x0000; + + return S_OK; +} + +static HRESULT sg_nfc_cmd_mifare_read_block( + struct sg_nfc *nfc, + const struct sg_nfc_req_mifare_read_block *req, + struct sg_nfc_res_mifare_read_block *res) +{ + uint32_t uid; + + if (req->req.payload_len != sizeof(req->payload)) { + sg_nfc_dprintf(nfc, "%s: Payload size is incorrect\n", __func__); + + return E_FAIL; + } + + uid = _byteswap_ulong(req->payload.uid); + + sg_nfc_dprintf(nfc, "Read uid %08x block %i\n", uid, req->payload.block_no); + + if (req->payload.block_no > 3) { + sg_nfc_dprintf(nfc, "MIFARE block number out of range\n"); + + return E_FAIL; + } + + sg_res_init(&res->res, &req->req, sizeof(res->block)); + + memcpy( res->block, + nfc->mifare.sectors[0].blocks[req->payload.block_no].bytes, + sizeof(res->block)); + + return S_OK; +} + +static HRESULT sg_nfc_cmd_felica_encap( + struct sg_nfc *nfc, + const struct sg_nfc_req_felica_encap *req, + struct sg_nfc_res_felica_encap *res) +{ + struct const_iobuf f_req; + struct iobuf f_res; + HRESULT hr; + + /* First byte of encapsulated request and response is a length byte + (inclusive of itself). The FeliCa emulator expects its caller to handle + that length byte on its behalf (we adopt the convention that the length + byte is part of the FeliCa protocol's framing layer). */ + + if (req->req.payload_len != 8 + req->payload[0]) { + sg_nfc_dprintf( + nfc, + "FeliCa encap payload length mismatch: sg %i != felica %i + 8", + req->req.payload_len, + req->payload[0]); + + return E_FAIL; + } + + f_req.bytes = req->payload; + f_req.nbytes = req->payload[0]; + f_req.pos = 1; + + f_res.bytes = res->payload; + f_res.nbytes = sizeof(res->payload); + f_res.pos = 1; + +#if 0 + dprintf("FELICA OUTBOUND:\n"); + dump_const_iobuf(&f_req); +#endif + + hr = felica_transact(&nfc->felica, &f_req, &f_res); + + if (FAILED(hr)) { + return hr; + } + + sg_res_init(&res->res, &req->req, f_res.pos); + res->payload[0] = f_res.pos; + +#if 0 + dprintf("FELICA INBOUND:\n"); + dump_iobuf(&f_res); +#endif + + return S_OK; +} + +static HRESULT sg_nfc_cmd_dummy( + struct sg_nfc *nfc, + const struct sg_req_header *req, + struct sg_res_header *res) +{ + sg_res_init(res, req, 0); + + return S_OK; +} diff --git a/board/sg-nfc.h b/board/sg-nfc.h new file mode 100644 index 0000000..5562b2b --- /dev/null +++ b/board/sg-nfc.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +#include "iccard/felica.h" +#include "iccard/mifare.h" + +struct sg_nfc_ops { + HRESULT (*poll)(void *ctx); + HRESULT (*get_aime_id)(void *ctx, uint8_t *luid, size_t nbytes); + HRESULT (*get_felica_id)(void *ctx, uint64_t *IDm); + + // TODO Banapass, AmuseIC +}; + +struct sg_nfc { + const struct sg_nfc_ops *ops; + void *ops_ctx; + uint8_t addr; + struct felica felica; + struct mifare mifare; +}; + +void sg_nfc_init( + struct sg_nfc *nfc, + uint8_t addr, + const struct sg_nfc_ops *ops, + void *ops_ctx); + +void sg_nfc_transact( + struct sg_nfc *nfc, + struct iobuf *res_frame, + const void *req_bytes, + size_t req_nbytes); diff --git a/board/sg-reader.c b/board/sg-reader.c new file mode 100644 index 0000000..dbf0392 --- /dev/null +++ b/board/sg-reader.c @@ -0,0 +1,186 @@ +#include + +#include +#include +#include + +#include "board/aime-dll.h" +#include "board/sg-led.h" +#include "board/sg-nfc.h" +#include "board/sg-reader.h" + +#include "hook/iohook.h" + +#include "hooklib/uart.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT sg_reader_handle_irp(struct irp *irp); +static HRESULT sg_reader_handle_irp_locked(struct irp *irp); +static HRESULT sg_reader_nfc_poll(void *ctx); +static HRESULT sg_reader_nfc_get_aime_id( + void *ctx, + uint8_t *luid, + size_t luid_size); +static HRESULT sg_reader_nfc_get_felica_id(void *ctx, uint64_t *IDm); +static void sg_reader_led_set_color(void *ctx, uint8_t r, uint8_t g, uint8_t b); + +static const struct sg_nfc_ops sg_reader_nfc_ops = { + .poll = sg_reader_nfc_poll, + .get_aime_id = sg_reader_nfc_get_aime_id, + .get_felica_id = sg_reader_nfc_get_felica_id, +}; + +static const struct sg_led_ops sg_reader_led_ops = { + .set_color = sg_reader_led_set_color, +}; + +static CRITICAL_SECTION sg_reader_lock; +static bool sg_reader_started; +static HRESULT sg_reader_start_hr; +static struct uart sg_reader_uart; +static uint8_t sg_reader_written_bytes[520]; +static uint8_t sg_reader_readable_bytes[520]; +static struct sg_nfc sg_reader_nfc; +static struct sg_led sg_reader_led; + +HRESULT sg_reader_hook_init( + const struct aime_config *cfg, + unsigned int port_no, + HINSTANCE self) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + hr = aime_dll_init(&cfg->dll, self); + + if (FAILED(hr)) { + return hr; + } + + sg_nfc_init(&sg_reader_nfc, 0x00, &sg_reader_nfc_ops, NULL); + sg_led_init(&sg_reader_led, 0x08, &sg_reader_led_ops, NULL); + + InitializeCriticalSection(&sg_reader_lock); + + uart_init(&sg_reader_uart, port_no); + sg_reader_uart.written.bytes = sg_reader_written_bytes; + sg_reader_uart.written.nbytes = sizeof(sg_reader_written_bytes); + sg_reader_uart.readable.bytes = sg_reader_readable_bytes; + sg_reader_uart.readable.nbytes = sizeof(sg_reader_readable_bytes); + + return iohook_push_handler(sg_reader_handle_irp); +} + +static HRESULT sg_reader_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (!uart_match_irp(&sg_reader_uart, irp)) { + return iohook_invoke_next(irp); + } + + EnterCriticalSection(&sg_reader_lock); + hr = sg_reader_handle_irp_locked(irp); + LeaveCriticalSection(&sg_reader_lock); + + return hr; +} + +static HRESULT sg_reader_handle_irp_locked(struct irp *irp) +{ + HRESULT hr; + +#if 0 + if (irp->op == IRP_OP_WRITE) { + dprintf("WRITE:\n"); + dump_const_iobuf(&irp->write); + } +#endif + +#if 0 + if (irp->op == IRP_OP_READ) { + dprintf("READ:\n"); + dump_iobuf(&sg_reader_uart.readable); + } +#endif + + if (irp->op == IRP_OP_OPEN) { + /* Unfortunately the card reader UART gets opened and closed + repeatedly */ + + if (!sg_reader_started) { + dprintf("NFC Assembly: Starting backend DLL\n"); + hr = aime_dll.init(); + + sg_reader_started = true; + sg_reader_start_hr = hr; + + if (FAILED(hr)) { + dprintf("NFC Assembly: Backend error: %x\n", (int) hr); + + return hr; + } + } else { + hr = sg_reader_start_hr; + + if (FAILED(hr)) { + return hr; + } + } + } + + hr = uart_handle_irp(&sg_reader_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + sg_nfc_transact( + &sg_reader_nfc, + &sg_reader_uart.readable, + sg_reader_uart.written.bytes, + sg_reader_uart.written.pos); + + sg_led_transact( + &sg_reader_led, + &sg_reader_uart.readable, + sg_reader_uart.written.bytes, + sg_reader_uart.written.pos); + + sg_reader_uart.written.pos = 0; + + return hr; +} + +static HRESULT sg_reader_nfc_poll(void *ctx) +{ + return aime_dll.nfc_poll(0); +} + +static HRESULT sg_reader_nfc_get_aime_id( + void *ctx, + uint8_t *luid, + size_t luid_size) +{ + return aime_dll.nfc_get_aime_id(0, luid, luid_size); +} + +static HRESULT sg_reader_nfc_get_felica_id(void *ctx, uint64_t *IDm) +{ + return aime_dll.nfc_get_felica_id(0, IDm); +} + +static void sg_reader_led_set_color(void *ctx, uint8_t r, uint8_t g, uint8_t b) +{ + aime_dll.led_set_color(0, r, g, b); +} diff --git a/board/sg-reader.h b/board/sg-reader.h new file mode 100644 index 0000000..673a8bd --- /dev/null +++ b/board/sg-reader.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +#include "board/aime-dll.h" + +struct aime_config { + struct aime_dll_config dll; + bool enable; +}; + +HRESULT sg_reader_hook_init( + const struct aime_config *cfg, + unsigned int port_no, + HINSTANCE self); diff --git a/cross-mingw-32.txt b/cross-mingw-32.txt new file mode 100644 index 0000000..21ed951 --- /dev/null +++ b/cross-mingw-32.txt @@ -0,0 +1,10 @@ +[binaries] +c = 'i686-w64-mingw32-gcc' +ar = 'i686-w64-mingw32-ar' +strip = 'i686-w64-mingw32-strip' + +[host_machine] +system = 'windows' +cpu_family = 'x86' +cpu = 'i686' +endian = 'little' diff --git a/cross-mingw-64.txt b/cross-mingw-64.txt new file mode 100644 index 0000000..6b15798 --- /dev/null +++ b/cross-mingw-64.txt @@ -0,0 +1,10 @@ +[binaries] +c = 'x86_64-w64-mingw32-gcc' +ar = 'x86_64-w64-mingw32-ar' +strip = 'x86_64-w64-mingw32-strip' + +[host_machine] +system = 'windows' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' diff --git a/dist/carol/segatools.ini b/dist/carol/segatools.ini new file mode 100644 index 0000000..b0c1d0d --- /dev/null +++ b/dist/carol/segatools.ini @@ -0,0 +1,48 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. +enable=1 + +[gpio] +dipsw1=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.126.0 + +[gfx] +; Force the game to run windowed. +windowed=1 +; Add a frame to the game window if running windowed. +framed=1 +; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) +monitor=0 + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in keyboard input. +path= + +[io3] +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 diff --git a/dist/carol/start.bat b/dist/carol/start.bat new file mode 100644 index 0000000..0fab950 --- /dev/null +++ b/dist/carol/start.bat @@ -0,0 +1,13 @@ +@echo off + +pushd %~dp0 + +taskkill /f /im aimeReaderHost.exe > nul 2>&1 + +start /min inject -d -k carolhook.dll aimeReaderHost.exe -p 10 +inject -d -k carolhook.dll carol_nu.exe +taskkill /f /im aimeReaderHost.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/chuni/segatools.ini b/dist/chuni/segatools.ini new file mode 100644 index 0000000..0a9939e --- /dev/null +++ b/dist/chuni/segatools.ini @@ -0,0 +1,80 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; Chunithm is extremely picky about its LAN environment, so leaving this +; setting enabled is strongly recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.139.0 + +[gfx] +; Force the game to run windowed. +windowed=1 +; Add a frame to the game window if running windowed. +framed=1 +; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) +monitor=0 + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in keyboard input. +path= + +[chuniio] +; To use a custom Chunithm IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in keyboard input. +path= + +; ----------------------------------------------------------------------------- +; Input settings +; ----------------------------------------------------------------------------- + +; Keyboard bindings are specified as hexadecimal (prefixed with 0x) or decimal +; (not prefixed with 0x) virtual-key codes, a list of which can be found here: +; +; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +; +; This is, admittedly, not the most user-friendly configuration method in the +; world. An improved solution will be provided later. + +[io3] +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 + +; Key bindings for each of the 32 touch cells. The default key map, depicted +; in left-to-right order, is as follows: +; +; SSSSDDDDFFFFGGGGHHHHJJJJKKKKLLLL +; +; Touch cells are numbered FROM RIGHT TO LEFT! starting from 1. This is in +; order to match the numbering used in the operator menu and service manual. +; +; Uncomment and complete the following sequence of settings to configure a +; custom high-precision touch strip controller if you have one. +[slider] +;cell32=0x53 +;cell31=0x53 +;cell30=0x53 +; ... etc ... diff --git a/dist/chuni/start.bat b/dist/chuni/start.bat new file mode 100644 index 0000000..0c942e9 --- /dev/null +++ b/dist/chuni/start.bat @@ -0,0 +1,11 @@ +@echo off + +pushd %~dp0 + +start /min inject -d -k chunihook.dll aimeReaderHost.exe -p 12 +inject -d -k chunihook.dll chuniApp.exe +taskkill /f /im aimeReaderHost.exe > nul 2>&1 + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/cxb/resource/segatools.ini b/dist/cxb/resource/segatools.ini new file mode 100644 index 0000000..a214976 --- /dev/null +++ b/dist/cxb/resource/segatools.ini @@ -0,0 +1,4 @@ +[aime] +; CXB is stupid, so we have to make the paths go back one +aimePath=../DEVICE/aime.txt +felicaPath=../DEVICE/felica.txt \ No newline at end of file diff --git a/dist/cxb/segatools.ini b/dist/cxb/segatools.ini new file mode 100644 index 0000000..e0491ff --- /dev/null +++ b/dist/cxb/segatools.ini @@ -0,0 +1,75 @@ +[vfs] +; Make sure theses are full paths and not relative or you will have a bad time +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; Crossbeats is extremely picky about its LAN environment, so leaving this +; setting enabled is strongly recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.100.0 +billingCa=../DEVICE/ca.crt +billingPub=../DEVICE/billing.pub +billingType=2 + +[gfx] +; Force the game to run windowed. +windowed=1 +; Add a frame to the game window if running windowed. +framed=1 +; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen) +monitor=0 + +[aime] +; Aime reader emulation +; CXB is stupid, so we have to make the paths go back one +enable=1 +aimePath=../DEVICE/aime.txt +felicaPath=../DEVICE/felica.txt + +[eeprom] +; See above +path=../DEVICE/eeprom.bin + +[sram] +; See above +path=../DEVICE/sram.bin + +[led] +; Emulation for the LED board. Currently it's just dummy responses, +; but if somebody wants to make their keyboard or whatever light +; up with the game they can +enable=1 + +[revio] +; Enable emulation of the rev IO board +enabe=1 +; Test button virtual-key code. Default is the 1 key. +test=0x31 +; Service button virtual-key code. Default is the 2 key. +service=0x32 +; Keyboard button to increment coin counter. Default is the 3 key. +coin=0x33 +; Menu up key. Default is up arrow. +up=0x26 +; Menu down key. Default is down arrow. +down=0x28 +; Menu cancel key. Default is the 4 key. +cancel=0x34 diff --git a/dist/cxb/start.bat b/dist/cxb/start.bat new file mode 100644 index 0000000..af92b2d --- /dev/null +++ b/dist/cxb/start.bat @@ -0,0 +1,9 @@ +@echo off + +pushd %~dp0 + +inject -d -k cxbhook.dll Rev_v11.exe + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/diva/segatools.ini b/dist/diva/segatools.ini new file mode 100644 index 0000000..e2608c0 --- /dev/null +++ b/dist/diva/segatools.ini @@ -0,0 +1,54 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains Axxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; Chunithm is extremely picky about its LAN environment, so leaving this +; setting enabled is strongly recommended. +enable=1 + +[gpio] +dipsw1=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.150.0 + +[slider] +cell1=0x51 +cell2=0x57 +cell3=0x45 +cell4=0x52 +cell5=0x55 +cell6=0x49 +cell7=0x4F +cell8=0x50 + +[buttons] +key1=0x27 +key2=0x28 +key3=0x25 +key4=0x26 +key5=0x20 + +; Sliders : <- QWER UIOP -> +; Triangle : Up arrow +; Square : Left Arrow +; Cross : Down Arrow +; Circle : Right arrow +; Enter : Space + diff --git a/dist/diva/start.bat b/dist/diva/start.bat new file mode 100644 index 0000000..72934f6 --- /dev/null +++ b/dist/diva/start.bat @@ -0,0 +1,9 @@ +@echo off + +pushd %~dp0 + +inject -d -k divahook.dll diva.exe + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/idz/segatools.ini b/dist/idz/segatools.ini new file mode 100644 index 0000000..4634ab4 --- /dev/null +++ b/dist/idz/segatools.ini @@ -0,0 +1,116 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs= +; Insert the path to the game Option directory here (contains OPxx directories) +option= +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata= + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[ds] +; Region code on the emulated AMEX board DS EEPROM. +; 1: Japan +; 4: Export (some UI elements in English) +; +; NOTE: Changing this setting causes a factory reset. +region=1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.100.0 + +[gpio] +; Emulated Nu DIP switch for Distribution Server setting. +; +; If multiple machines are present on the same LAN then set this to 1 on +; exactly one machine and set this to 0 on all others. +dipsw1=1 + +[aimeio] +; To use a custom card reader IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in keyboard input. +path= + +[idzio] +; To use a custom Initial D Zero IO DLL enter its path here. +; Leave empty if you want to use Taitools built-in gamepad/wheel input. +path= + +[io3] +; Input API selection for JVS input emulator. +; Set "xinput" to use a gamepad and "dinput" to use a steering wheel. +mode=xinput +; Automatically reset the simulated shifter to Neutral when XInput Start is +; pressed (e.g. when navigating menus between races). +autoNeutral=1 +; Use the left thumbstick for steering instead of both on XInput Controllers. +; Not recommended as it will not give you the precision needed for this game +singleStickSteering=0 +; Adjust scaling for steering wheel input. +; +; This setting scales the steering wheel input so that the maximum positive +; and minimum negative steering inputs reported in the operator menu's input +; test screen do not exceed the value below. The maximum possible value is 128, +; and the value that matches the input range of a real cabinet is 97. +; +; NOTE: This is not the same thing as DirectInput steering wheel movement +; range! Taitools cannot control the maximum angle of your physical steering +; wheel controller, this setting is vendor-specific and can only be adjusted +; in the Control Panel. +restrict=97 + +[dinput] +; Name of the DirectInput wheel to use (or any text that occurs in its name) +; Example: TMX +; +; If this is left blank then the first DirectInput device will be used. +deviceName= +; Name of the positional shifter to use (or any subset thereof). +; Leave blank if you do not have a positional shifter; a positional shifter +; will be simulated using the configured Shift Down and Shift Up buttons +; in this case. +; +; Can be the same device as the wheel. +; +; Example: T500 +shifterName= +; Pedal mappings. Valid axis names are: +; +; X, Y, Z, RX, RY, RZ, U, V +; +; (U and V are old names for Slider 1 and Slider 2). +; The examples below are valid for a Thrustmaster TMX. +brakeAxis=RZ +accelAxis=Y +; DirectInput button numbers to map to menu inputs. Note that buttons are +; numbered from 1; some software numbers buttons from 0. +start=3 +viewChg=10 +; Button mappings for the simulated six-speed shifter. +shiftDn=1 +shiftUp=2 +; Button mappings for the positional shifter, if present. +gear1=1 +gear2=2 +gear3=3 +gear4=4 +gear5=5 +gear6=6 +; Invert the accelerator and or brake axis +; (Needed when using DirectInput for the Dualshock 4 for example) +reverseAccelAxis=0 +reverseBrakeAxis=0 diff --git a/dist/idz/start.bat b/dist/idz/start.bat new file mode 100644 index 0000000..b8d6b18 --- /dev/null +++ b/dist/idz/start.bat @@ -0,0 +1,10 @@ +@echo off + +pushd %~dp0 + +.\inject.exe -k .\idzhook.dll .\InitialD0_DX11_Nu.exe +.\inject.exe -d -k .\idzhook.dll .\amdaemon.exe -c configDHCP_Final_Common.json configDHCP_Final_JP.json configDHCP_Final_JP_ST1.json configDHCP_Final_JP_ST2.json configDHCP_Final_EX.json configDHCP_Final_EX_ST1.json configDHCP_Final_EX_ST2.json + +echo. +echo Game processes have terminated +pause \ No newline at end of file diff --git a/dist/mai2/segatools.ini b/dist/mai2/segatools.ini new file mode 100644 index 0000000..25fdc31 --- /dev/null +++ b/dist/mai2/segatools.ini @@ -0,0 +1,44 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs=amfs +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata=appdata +option=option + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[ds] +; Region code on the emulated AMEX board DS EEPROM. +; 1: Japan +; 4: Export (some UI elements in English) +; +; NOTE: Changing this setting causes a factory reset. +region=1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.172.0 + +[gfx] +enable=1 + +[io4] +; Delete +test=0x2E +; End +service=0x23 +; Insert +coin=0x2D diff --git a/dist/mai2/start.bat b/dist/mai2/start.bat new file mode 100644 index 0000000..2d6bd8f --- /dev/null +++ b/dist/mai2/start.bat @@ -0,0 +1,11 @@ +@echo off +pushd %~dp0 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +start inject -d -k mai2hook.dll amdaemon.exe -f -c config_client.json config_common.json config_server.json +inject.exe -d -k mai2hook.dll Sinmai.exe -screen-fullscreen 0 -screen-width 2160 -screen-height 1920 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo Game processes have terminated \ No newline at end of file diff --git a/dist/mercury/segatools.ini b/dist/mercury/segatools.ini new file mode 100644 index 0000000..f231c66 --- /dev/null +++ b/dist/mercury/segatools.ini @@ -0,0 +1,56 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs=amfs +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata=appdata +option=option + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[ds] +; Region code on the emulated AMEX board DS EEPROM. +; 1: Japan +; 4: Export (some UI elements in English) +; +; NOTE: Changing this setting causes a factory reset. +region=1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.174.0 + +[gfx] +enable=1 + +[io4] +; Input API selection for JVS input emulator. +test=0x2D +service=0x2E +coin=0x24 +volup=0x26 +voldown=0x28 + +; Hooks related to the touch boards +[touch] +enable=1 + +; Hooks related to the LED board (codenamed Elisabeth) +[elisabeth] +enable=1 + +;[mercuryio] +; Use mercuryio.dll +;path=mercuryio.dll diff --git a/dist/mercury/start.bat b/dist/mercury/start.bat new file mode 100644 index 0000000..c00f2ea --- /dev/null +++ b/dist/mercury/start.bat @@ -0,0 +1,15 @@ +@echo off +pushd %~dp0 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +REM USA +REM start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_usa.json + +REM JP +start inject -d -k mercuryhook.dll amdaemon.exe -f -c config.json config_lan_install_client.json config_lan_install_server.json config_video_clone.json config_video_dual.json config_video_clone_flip.json config_video_dual_flip.json config_region_exp.json config_region_chn.json config_region_jpn.json +inject -d -k mercuryhook.dll ../WindowsNoEditor/Mercury/Binaries/Win64/Mercury-Win64-Shipping.exe + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo Game processes have terminated \ No newline at end of file diff --git a/dist/mu3/segatools.ini b/dist/mu3/segatools.ini new file mode 100644 index 0000000..98cdb31 --- /dev/null +++ b/dist/mu3/segatools.ini @@ -0,0 +1,60 @@ +[vfs] +; Insert the path to the game AMFS directory here (contains ICF1 and ICF2) +amfs=amfs +; Create an empty directory somewhere and insert the path here. +; This directory may be shared between multiple SEGA games. +; NOTE: This has nothing to do with Windows %APPDATA%. +appdata=appdata +option=option + +[dns] +; Insert the hostname or IP address of the server you wish to use here. +; Note that 127.0.0.1, localhost etc are specifically rejected. +default=127.0.0.1 + +[ds] +; Region code on the emulated AMEX board DS EEPROM. +; 1: Japan +; 4: Export (some UI elements in English) +; +; NOTE: Changing this setting causes a factory reset. +region=1 + +[netenv] +; Simulate an ideal LAN environment. This may interfere with head-to-head play. +; SEGA games are somewhat picky about their LAN environment, so leaving this +; setting enabled is recommended. +enable=1 + +[keychip] +; The /24 LAN subnet that the emulated keychip will tell the game to expect. +; If you disable netenv then you must set this to your LAN's IP subnet, and +; that subnet must start with 192.168. +subnet=192.168.162.0 + +[gfx] +enable=1 + +[io4] +; Input API selection for JVS input emulator. +; Set "1" to use a xinput gamepad and set "2" to use keyboard. +mode=2 + +test=0x31 +service=0x32 + +[dinput] +LEFT_A=0x53 +LEFT_B=0x44 +LEFT_C=0x46 +LEFT_MENU=0x51 +LEFT_SIDE=0x52 +RIGHT_A=0x4A +RIGHT_B=0x4B +RIGHT_C=0x4C +RIGHT_MENU=0x50 +RIGHT_SIDE=0x55 +SLIDER_LEFT=0x54 +SLIDER_RIGHT=0x59 +;Change move speed of slider when use dinput +SLIDER_SPEED=1000 diff --git a/dist/mu3/start.bat b/dist/mu3/start.bat new file mode 100644 index 0000000..3aceffb --- /dev/null +++ b/dist/mu3/start.bat @@ -0,0 +1,11 @@ +@echo off +pushd %~dp0 + +taskkill /f /im amdaemon.exe > nul 2>&1 + +start inject -d -k mu3hook.dll amdaemon.exe -f -c config_client.json config_common.json config_server.json +inject -d -k mu3hook.dll mu3.exe + +taskkill /f /im amdaemon.exe > nul 2>&1 + +echo Game processes have terminated \ No newline at end of file diff --git a/doc/config/common.md b/doc/config/common.md new file mode 100644 index 0000000..e0616a8 --- /dev/null +++ b/doc/config/common.md @@ -0,0 +1,470 @@ +# Taitools common configuration settings + +This file describes configuration settings for Taitools that are common to +all games. + +Keyboard binding settings use +[Virtual-Key Codes](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). + +## `[aimeio]` + +Controls the card reader driver. + +### `path` + +Specify a path for a third-party card reader driver DLL. Default is empty +(use built-in emulation based on text files and keyboard input). + +In previous versions of Taitools this was accomplished by replacing the +AIMEIO.DLL file that came with Taitools. Taitools no longer ships with a +separate AIMEIO.DLL file (its functionality is now built into the various hook +DLLs). + +## `[aime]` + +Controls emulation of the Aime card reader assembly. + +### `enable` + +Default: `1` + +Enable Aime card reader assembly emulation. Disable to use a real SEGA Aime +reader (COM port number varies by game). + +### `aimePath` + +Default: `DEVICE\aime.txt` + +Path to a text file containing a classic Nesica IC card ID. **This does not +currently work**. + +### `felicaPath` + +Default: `DEVICE\felica.txt` + +Path to a text file containing a FeliCa e-cash card IDm serial number. + +### `felicaGen` + +Default: `1` + +Whether to generate a random FeliCa ID if the file at `felicaPath` does not +exist. + +### `scan` + +Default: `0x0D` (`VK_RETURN`) + +Virtual-key code. If this button is **held** then the emulated IC card reader +emulates an IC card in its proximity. A variety of different IC cards can be +emulated; the exact choice of card that is emulated depends on the presence or +absence of the configured card ID files. + +## `[amvideo]` + +Controls the `amvideo.dll` stub built into Taitools. This is a DLL that is +normally present on the SEGA operating system image which is responsible for +changing screen resolution and orientation. + +### `enable` + +Default: `1` + +Enable stub `amvideo.dll`. Disable to use a real `amvideo.dll` build. Note that +you must have the correct registry settings installed and you must use the +version of `amvideo.dll` that matches your GPU vendor (since these DLLs make +use of vendor-specific APIs). + +## `[clock]` + +Controls hooks for Windows time-of-day APIs. + +### `timezone` + +Default: `1` + +Make the system time zone appear to be JST. SEGA games malfunction in strange +ways if the system time zone is not JST. There should not be any reason to +disable this hook other than possible implementation bugs, but the option is +provided if you need it. + +### `timewarp` + +Default: `0` + +Experimental time-of-day warping hook that skips over the hardcoded server +maintenance period. Causes an incorrect in-game time-of-day to be reported. +Better solutions for this problem exist and this feature will probably be +removed soon. + +### `writeable` + +Default: `0` + +Allow game to adjust system clock and time zone settings. This should normally +be left at `0`, but the option is provided if you need it. + +## `[dns]` + +Controls redirection of network server hostname lookups + +### `default` + +Default: `localhost` + +Controls hostname of all of the common network services servers, unless +overriden by a specific setting below. Most users will only need to change this +setting. Also, loopback addresses are specifically checked for and rejected by +the games themselves; this needs to be a LAN or WAN IP (or a hostname that +resolves to one). + +### `router` + +Default: Empty string (i.e. use value from `default` setting) + +Overrides the target of the `tenporouter.loc` and `bbrouter.loc` hostname +lookups. + +### `startup` + +Default: Empty string (i.e. use value from `default` setting) + +Overrides the target of the `naominet.jp` host lookup. + +### `billing` + +Default: Empty string (i.e. use value from `default` setting) + +Overrides the target of the `ib.naominet.jp` host lookup. + +### `aimedb` + +Default: Empty string (i.e. use value from `default` setting) + +Overrides the target of the `aime.naominet.jp` host lookup. + +## `[ds]` + +Controls emulation of the "DS (Dallas Semiconductor) EEPROM" chip on the AMEX +PCIe board. This is a small (32 byte) EEPROM that contains serial number and +region code information. It is not normally written to outside of inital +factory provisioning of a Sega Nu. + +### `enable` + +Default: `1` + +Enable DS EEPROM emulation. Disable to use the DS EEPROM chip on a real AMEX. + +### `region` + +Default: `1` + +AMEX Board region code. This appears to be a bit mask? + +- `1`: Japan +- `2`: USA? (Dead code, not used) +- `4`: Export +- `8`: China + +### `serialNo` + +Default `AAVE-01A99999999` + +"MAIN ID" serial number. First three characters are hardware series: + +- `AAV`: Nu-series +- `AAW`: NuSX-series +- `ACA`: ALLS-series + +## `[eeprom]` + +Controls emulation of the bulk EEPROM on the AMEX PCIe board. This chip stores +status and configuration information. + +### `enable` + +Default: `1` + +Enable bulk EEPROM emulation. Disable to use the bulk EEPROM chip on a real +AMEX. + +### `path` + +Default: `DEVICE\eeprom.bin` + +Path to the storage file for EEPROM emulation. This file is automatically +created and initialized with a suitable number of zero bytes if it does not +already exist. + +## `[gpio]` + +Configure emulation of the AMEX PCIe GPIO (General Purpose Input Output) +controller. + +### `enable` + +Default: `1` + +Enable GPIO emulation. Disable to use the GPIO controller on a real AMEX. + +### `sw1` + +Default `0x70` (`VK_F1`) + +Keyboard binding for Nu chassis SW1 button (alternative Test) + +### `sw2` + +Default `0x71` (`VK_F2`) + +Keyboard binding for Nu chassis SW2 button (alternative Service) + +### `dipsw1` .. `dipsw8` + +Defaults: `1`, `0`, `0`, `0`, `0`, `0`, `0`, `0` + +Nu chassis DIP switch settings: + +- Switch 1: Game-specific, but usually controls the "distribution server" + setting. Exactly one arcade machine on a cabinet router must be set to the + Server setting. + - `0`: Client + - `1`: Server +- Switch 2,3: Game-specific. + - Used by Mario&Sonic to configure cabinet ID, possibly other games. +- Switch 4: Screen orientation. Only used by the Nu system startup program. + - `0`: YOKO/Horizontal + - `1`: TATE/Vertical +- Switch 5,6,7: Screen resolution. Only used by the Nu system startup program. + - `000`: No change + - `100`: 640x480 + - `010`: 1024x600 + - `110`: 1024x768 + - `001`: 1280x720 + - `101`: 1280x1024 + - `110`: 1360x768 + - `111`: 1920x1080 +- Switch 8: Game-specific. Not used in any shipping game. + +## `[hwmon]` + +Configure stub implementation of the platform hardware monitor driver. The +real implementation of this driver monitors CPU temperatures by reading from +Intel Model Specific Registers, which is an action that is only permitted from +kernel mode. + +### `enable` + +Default `1` + +Enable hwmon emulation. Disable to use the real hwmon driver. + +## `[jvs]` + +Configure emulation of the AMEX PCIe JVS *controller* (not IO board!) + +### `enable` + +Default `1` + +Enable JVS port emulation. Disable to use the JVS port on a real AMEX. + +## `[keychip]` + +Configure keychip emulation. + +### `enable` + +Enable keychip emulation. Disable to use a real keychip. + +### `id` + +Default: `A69E-01A88888888` + +Keychip serial number. Keychip serials observed in the wild follow this +pattern: `A6xE-01Ayyyyyyyy`. + +### `gameId` + +Default: (Varies depending on game) + +Override the game's four-character model code. Changing this from the game's +expected value will probably just cause a system error. + +### `platformId` + +Default: (Varies depending on game) + +Override the game's four-character platform code (e.g. `AAV2` for Nu 2). This +is actually supposed to be a separate three-character `platformId` and +integer `modelType` setting, but they are combined here for convenience. Valid +values include: + +- `AAV0`: Nu 1 (Project DIVA) +- `AAV1`: Nu 1.1 (Chunithm) +- `AAV2`: Nu 2 (Initial D Zero) +- `AAW0`: NuSX 1 +- `AAW1`: NuSX 1.1 +- `ACA0`: ALLS UX +- `ACA1`: ALLS HX +- `ACA2`: ALLS UX (without dedicated GPU) +- `ACA4`: ALLS MX + +### `region` + +Default: `1` + +Override the keychip's region code. Most games seem to pay attention to the +DS EEPROM region code and not the keychip region code, and this seems to be +a bit mask that controls which Nu PCB region codes this keychip is authorized +for. So it probably only affects the system software and not the game software. +Bit values are: + +- 1: JPN: Japan +- 2: USA (unused) +- 3: EXP: Export (for Asian markets) +- 4: CHS: China (Simplified Chinese?) + +### `billingType` + +Default: `1` + +Set the billing "type" for the keychip. The type determins what kind of revenue share, +if any, the game maker has with SEGA. Some games may be picky and require types other +then 1 (ex. Crossbeats requires billing type 2), so this option is provided if this +is an issue. Billing types are: + +- 0: No billing? +- 1: Billing type A +- 2: Billing type B1 +- 3: Billing type B2 + +### `systemFlag` + +Default: `0x64` + +An 8-bit bitfield of unclear meaning. The least significant bit indicates a +developer dongle, I think? Changing this doesn't seem to have any effect on +anything other than Project DIVA. + +Other values observed in the wild: + +- `0x04`: SDCH, SDCA +- `0x20`: SDCA + +### `subnet` + +Default `192.168.100.0` + +The LAN IP range that the game will expect. The prefix length is hardcoded into +the game program: for some games this is `/24`, for others it is `/20`. + +## `[netenv]` + +Configure network environment virtualization. This module helps bypass various +restrictions placed upon the game's LAN environment. + +### `enable` + +Default `1` + +Enable network environment virtualization. You may need to disable this if +you want to do any head-to-head play on your LAN. + +Note: The virtualized LAN IP range is taken from the emulated keychip's +`subnet` setting. + +### `addrSuffix` + +Default: `11` + +The final octet of the local host's IP address on the virtualized subnet (so, +if the keychip subnet is `192.168.32.0` and this value is set to `11`, then the +local host's virtualized LAN IP is `192.168.32.11`). + +### `routerSuffix` + +Default: `1` + +The final octet of the default gateway's IP address on the virtualized subnet. + +### `macAddr` + +Default: `01:02:03:04:05:06` + +The MAC address of the virtualized Ethernet adapter. The exact value shouldn't +ever matter. + +## `[pcbid]` + +Configure Windows host name virtualization. The ALLS-series platform no longer +has an AMEX board, so the MAIN ID serial number is stored in the Windows +hostname. + +### `enable` + +Default: `1` + +Enable Windows host name virtualization. This is only needed for ALLS-platform +games (since the ALLS lacks an AMEX and therefore has no DS EEPROM, so it needs +another way to store the PCB serial), but it does no harm on games that run on +earlier hardware. + +### `serialNo` + +Default: `ACAE01A99999999` + +Set the Windows host name. This should be an ALLS MAIN ID, without the +hyphen (which is not a valid character in a Windows host name). + +## `[sram]` + +Configure emulation of the AMEX PCIe battery-backed SRAM. This stores +bookkeeping state and settings. This file is automatically created and +initialized with a suitable number of zero bytes if it does not already exist. + +### `enable` + +Default `1` + +Enable SRAM emulation. Disable to use the SRAM on a real AMEX. + +### `path` + +Default `DEVICE\sram.bin` + +Path to the storage file for SRAM emulation. + +## `[vfs]` + +Configure Windows path redirection hooks. + +### `enable` + +Default: `1` + +Enable path redirection. + +### `amfs` + +Default: Empty string (causes a startup error) + +Configure the location of the SEGA AMFS volume. Stored on the `E` partition on +real hardware. + +### `appdata` + +Default: Empty string (causes a startup error) + +Configure the location of the SEGA "APPDATA" volume (nothing to do with the +Windows user's `%APPDATA%` directory). Stored on the `Y` partition on real +hardware. + +### `option` + +Default: Empty string + +Configure the location of the "Option" data mount point. This mount point is +optional (hence the name, probably) and contains directories which contain +minor over-the-air content updates. diff --git a/doc/development.md b/doc/development.md new file mode 100644 index 0000000..5a08e72 --- /dev/null +++ b/doc/development.md @@ -0,0 +1,117 @@ +# Development + +This document is intended for developers interested in contributing to taitools. Please read this document before +you start developing/contributing. + +## Goals + +We want you to understand what this project is about and its goals. The following list serves as a guidance for all +developers to identify valuable contributions for this project. As the project evolves, these goals might do as well. + +* Allow running Sega arcade (rhythm) games on arbitrary hardware + * Emulate required software and hardware features + * Provide means to cope with incompatibility issues resulting from using a different software platform (e.g. version of Windows). +* Provide an API for custom interfaces and configuring fundamental application features + +## Development environment + +The following tooling is required in order to build this project. + +### Tooling + +#### Linux / MacOSX + +* git +* make +* mingw-w64 +* docker (optional) + +On MacOSX, you can use homebrew or macports to install these packages. + +#### Windows + +TODO + +### IDE + +Ultimately, you are free to use whatever you feel comfortable with for development. The following is our preferred +development environment which we run on a Linux distribution of our choice: + +* Visual Studio Code with the following extensions + * C/C++ + * C++ Intellisense + +### Further tools for testing and debugging + +* Debugger: Can be part of your reverse engineering IDE of your choice or stand-along like +[OllyDbg](http://www.ollydbg.de/) +* [apitrace](https://apitrace.github.io/): Trace render calls to graphics APIs like D3D and OpenGL. +This tool allows you to record and re-play render calls of an application with frame-by-frame +debugging. Very useful to analyze the render pipeline or debug graphicial glitches + +## Building + +The root `Makefile` contains various targets that allow you to build the project easily. + +### Local build + +For a local build, you need to install Meson and a recent build of MinGW-w64. Then you can start the +build process: + +```shell +make build +``` + +Build output will be located in `build/build32` and `build/build64` folders. + +### Cleanup local build + +```shell +make clean +``` + +### Create distribution package (zip file) + +```shell +make dist +``` + +The output will be located in `build/zip`. + +### Build and create distribution package using docker + +You can also build using docker which avoids having to setup a full development environment if you +are just interested in building binaries of the latest changes. Naturally, this requires you to +have the docker daemon installed. + +```shell +make build-docker +``` + +Once completed successfully, the build output is located in the `build/docker/zip` sub-folder. + +### Building with Docker Desktop on Windows + +* [Install WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10) +* [Install Docker Desktop](https://docs.docker.com/docker-for-windows/install/) +* Run Docker Desktop to start the Docker Engine +* Open a command prompt (`cmd.exe`) and `cd` to your `taitools` folder +* Run `docker-build.bat` +* Once completed successfully, build output is located in the `build/docker/zip` sub-folder. + +### Building with Docker on Windows using WSL2 + +* [Install WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10) +* Regarding Linux distribution, we recommend using Ubuntu 20.04 +* Run the "Ubuntu 20.04 LTS" App which opens a Linux shell +* Install `make` and `docker` by running + * `sudo apt-get update` + * `sudo apt-get install make docker.io` +* Add the current user to the docker group that you don't have to run docker commands with root: +`sudo usermod -a -G docker $USER` +* Run the docker daemon in the background: `sudo dockerd > /dev/null 2>&1 &` +* Navigate to your taitools folder. If it is located on the `C:` drive, WSL automatically provides +a mountpoint for that under `/mnt/c`, e.g. `cd /mnt/c/taitools` (if the folder `taitools` is +located under `C:\taitools` on Windows). +* Build taitools: `make build-docker` +* Once completed successfully, build output is located in the `build/docker/zip` sub-folder \ No newline at end of file diff --git a/docker-build.bat b/docker-build.bat new file mode 100644 index 0000000..c2960d1 --- /dev/null +++ b/docker-build.bat @@ -0,0 +1,34 @@ +@echo off +setlocal enabledelayedexpansion + +:: Static Environment Variables +set IMAGE_NAME=hay1tsme/taitools-build:latest +set CONTAINER_NAME=taitools-build + +:: Main Execution +docker build . -t %IMAGE_NAME% + +if ERRORLEVEL 1 ( + goto failure +) + +docker run -it --rm -v %~dp0:/taitools --name %CONTAINER_NAME% %IMAGE_NAME% + +if ERRORLEVEL 1 ( + goto failure +) + +docker image rm -f %IMAGE_NAME% + +goto success + +:failure +echo taitools Docker build FAILED! +goto finish + +:success +echo taitools Docker build completed successfully. +goto finish + +:finish +pause diff --git a/gfxhook/config.c b/gfxhook/config.c new file mode 100644 index 0000000..98db059 --- /dev/null +++ b/gfxhook/config.c @@ -0,0 +1,17 @@ +#include + +#include +#include + +#include "gfxhook/config.h" + +void gfx_config_load(struct gfx_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"gfx", L"enable", 1, filename); + cfg->windowed = GetPrivateProfileIntW(L"gfx", L"windowed", 0, filename); + cfg->framed = GetPrivateProfileIntW(L"gfx", L"framed", 1, filename); + cfg->monitor = GetPrivateProfileIntW(L"gfx", L"monitor", 0, filename); +} diff --git a/gfxhook/config.h b/gfxhook/config.h new file mode 100644 index 0000000..a6ced69 --- /dev/null +++ b/gfxhook/config.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "gfxhook/gfx.h" + +void gfx_config_load(struct gfx_config *cfg, const wchar_t *filename); diff --git a/gfxhook/d3d11.c b/gfxhook/d3d11.c new file mode 100644 index 0000000..4c94448 --- /dev/null +++ b/gfxhook/d3d11.c @@ -0,0 +1,195 @@ +#include +#include +#include + +#include +#include + +#include "gfxhook/d3d11.h" +#include "gfxhook/gfx.h" +#include "gfxhook/util.h" + +#include "hook/table.h" + +#include "hooklib/dll.h" + +#include "util/dprintf.h" + +typedef HRESULT (WINAPI *D3D11CreateDevice_t)( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *ppFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext); +typedef HRESULT (WINAPI *D3D11CreateDeviceAndSwapChain_t)( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *ppFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, + IDXGISwapChain **ppSwapChain, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext); + +static struct gfx_config gfx_config; +static D3D11CreateDevice_t next_D3D11CreateDevice; +static D3D11CreateDeviceAndSwapChain_t next_D3D11CreateDeviceAndSwapChain; + +static const struct hook_symbol d3d11_hooks[] = { + { + .name = "D3D11CreateDevice", + .patch = D3D11CreateDevice, + .link = (void **) &next_D3D11CreateDevice, + }, { + .name = "D3D11CreateDeviceAndSwapChain", + .patch = D3D11CreateDeviceAndSwapChain, + .link = (void **) &next_D3D11CreateDeviceAndSwapChain, + }, +}; + +void gfx_d3d11_hook_init(const struct gfx_config *cfg, HINSTANCE self) +{ + HMODULE d3d11; + + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "d3d11.dll", d3d11_hooks, _countof(d3d11_hooks)); + + if (next_D3D11CreateDevice == NULL || next_D3D11CreateDeviceAndSwapChain == NULL) { + d3d11 = LoadLibraryW(L"d3d11.dll"); + + if (d3d11 == NULL) { + dprintf("D3D11: d3d11.dll not found or failed initialization\n"); + + goto fail; + } + + if (next_D3D11CreateDevice == NULL) { + next_D3D11CreateDevice = (D3D11CreateDevice_t) GetProcAddress( + d3d11, + "D3D11CreateDevice"); + } + if (next_D3D11CreateDeviceAndSwapChain == NULL) { + next_D3D11CreateDeviceAndSwapChain = (D3D11CreateDeviceAndSwapChain_t) GetProcAddress( + d3d11, + "D3D11CreateDeviceAndSwapChain"); + } + + if (next_D3D11CreateDevice == NULL) { + dprintf("D3D11: D3D11CreateDevice not found in loaded d3d11.dll\n"); + + goto fail; + } + if (next_D3D11CreateDeviceAndSwapChain == NULL) { + dprintf("D3D11: D3D11CreateDeviceAndSwapChain not found in loaded d3d11.dll\n"); + + goto fail; + } + } + + if (self != NULL) { + dll_hook_push(self, L"d3d11.dll"); + } + + return; + +fail: + if (d3d11 != NULL) { + FreeLibrary(d3d11); + } +} + +HRESULT WINAPI D3D11CreateDevice( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *ppFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext) +{ + dprintf("D3D11: D3D11CreateDevice hook hit\n"); + + return next_D3D11CreateDevice( + pAdapter, + DriverType, + Software, + Flags, + ppFeatureLevels, + FeatureLevels, + SDKVersion, + ppDevice, + pFeatureLevel, + ppImmediateContext); +} + +HRESULT WINAPI D3D11CreateDeviceAndSwapChain( + IDXGIAdapter *pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + const D3D_FEATURE_LEVEL *ppFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, + IDXGISwapChain **ppSwapChain, + ID3D11Device **ppDevice, + D3D_FEATURE_LEVEL *pFeatureLevel, + ID3D11DeviceContext **ppImmediateContext) +{ + DXGI_SWAP_CHAIN_DESC *desc; + HWND hwnd; + UINT width; + UINT height; + + dprintf("D3D11: D3D11CreateDeviceAndSwapChain hook hit\n"); + + desc = (DXGI_SWAP_CHAIN_DESC *) pSwapChainDesc; + + if (desc != NULL) { + desc->Windowed = gfx_config.windowed; + + hwnd = desc->OutputWindow; + width = desc->BufferDesc.Width; + height = desc->BufferDesc.Height; + } else { + hwnd = NULL; + } + + if (hwnd != NULL) { + gfx_util_ensure_win_visible(hwnd); + gfx_util_borderless_fullscreen_windowed(hwnd, width, height); + } + + return next_D3D11CreateDeviceAndSwapChain( + pAdapter, + DriverType, + Software, + Flags, + ppFeatureLevels, + FeatureLevels, + SDKVersion, + pSwapChainDesc, + ppSwapChain, + ppDevice, + pFeatureLevel, + ppImmediateContext); +} + diff --git a/gfxhook/d3d11.h b/gfxhook/d3d11.h new file mode 100644 index 0000000..c7cca9f --- /dev/null +++ b/gfxhook/d3d11.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "gfxhook/gfx.h" + +void gfx_d3d11_hook_init(const struct gfx_config *cfg, HINSTANCE self); diff --git a/gfxhook/d3d9.c b/gfxhook/d3d9.c new file mode 100644 index 0000000..34a165d --- /dev/null +++ b/gfxhook/d3d9.c @@ -0,0 +1,251 @@ +#include +#include + +#include +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "hooklib/dll.h" + +#include "gfxhook/gfx.h" +#include "gfxhook/util.h" + +#include "util/dprintf.h" + +typedef IDirect3D9 * (WINAPI *Direct3DCreate9_t)(UINT sdk_ver); +typedef HRESULT (WINAPI *Direct3DCreate9Ex_t)(UINT sdk_ver, IDirect3D9Ex **d3d9ex); + +static HRESULT STDMETHODCALLTYPE my_IDirect3D9_CreateDevice( + IDirect3D9 *self, + UINT adapter, + D3DDEVTYPE type, + HWND hwnd, + DWORD flags, + D3DPRESENT_PARAMETERS *pp, + IDirect3DDevice9 **pdev); +static HRESULT STDMETHODCALLTYPE my_IDirect3D9Ex_CreateDevice( + IDirect3D9Ex *self, + UINT adapter, + D3DDEVTYPE type, + HWND hwnd, + DWORD flags, + D3DPRESENT_PARAMETERS *pp, + IDirect3DDevice9 **pdev); + +static struct gfx_config gfx_config; +static Direct3DCreate9_t next_Direct3DCreate9; +static Direct3DCreate9Ex_t next_Direct3DCreate9Ex; + +static const struct hook_symbol gfx_hooks[] = { + { + .name = "Direct3DCreate9", + .patch = Direct3DCreate9, + .link = (void **) &next_Direct3DCreate9, + }, { + .name = "Direct3DCreate9Ex", + .patch = Direct3DCreate9Ex, + .link = (void **) &next_Direct3DCreate9Ex, + }, +}; + +void gfx_d3d9_hook_init(const struct gfx_config *cfg, HINSTANCE self) +{ + HMODULE d3d9; + + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "d3d9.dll", gfx_hooks, _countof(gfx_hooks)); + + if (next_Direct3DCreate9 == NULL || next_Direct3DCreate9Ex == NULL) { + d3d9 = LoadLibraryW(L"d3d9.dll"); + + if (d3d9 == NULL) { + dprintf("Gfx: d3d9.dll not found or failed initialization\n"); + + goto fail; + } + + if (next_Direct3DCreate9 == NULL) { + next_Direct3DCreate9 = (Direct3DCreate9_t) GetProcAddress(d3d9, "Direct3DCreate9"); + } + if (next_Direct3DCreate9Ex == NULL) { + next_Direct3DCreate9Ex = (Direct3DCreate9Ex_t) GetProcAddress(d3d9, "Direct3DCreate9Ex"); + } + + if (next_Direct3DCreate9 == NULL) { + dprintf("Gfx: Direct3DCreate9 not found in loaded d3d9.dll\n"); + + goto fail; + } + if (next_Direct3DCreate9Ex == NULL) { + dprintf("Gfx: Direct3DCreate9Ex not found in loaded d3d9.dll\n"); + + goto fail; + } + } + + if (self != NULL) { + dll_hook_push(self, L"d3d9.dll"); + } + + return; + +fail: + if (d3d9 != NULL) { + FreeLibrary(d3d9); + } +} + +IDirect3D9 * WINAPI Direct3DCreate9(UINT sdk_ver) +{ + struct com_proxy *proxy; + IDirect3D9Vtbl *vtbl; + IDirect3D9 *api; + HRESULT hr; + + dprintf("Gfx: Direct3DCreate9 hook hit\n"); + + api = NULL; + + if (next_Direct3DCreate9 == NULL) { + dprintf("Gfx: next_Direct3DCreate9 == NULL\n"); + + goto fail; + } + + api = next_Direct3DCreate9(sdk_ver); + + if (api == NULL) { + dprintf("Gfx: next_Direct3DCreate9 returned NULL\n"); + + goto fail; + } + + hr = com_proxy_wrap(&proxy, api, sizeof(*api->lpVtbl)); + + if (FAILED(hr)) { + dprintf("Gfx: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl = proxy->vptr; + vtbl->CreateDevice = my_IDirect3D9_CreateDevice; + + return (IDirect3D9 *) proxy; + +fail: + if (api != NULL) { + IDirect3D9_Release(api); + } + + return NULL; +} + +HRESULT WINAPI Direct3DCreate9Ex(UINT sdk_ver, IDirect3D9Ex **d3d9ex) +{ + struct com_proxy *proxy; + IDirect3D9ExVtbl *vtbl; + IDirect3D9Ex *api; + HRESULT hr; + + dprintf("Gfx: Direct3DCreate9Ex hook hit\n"); + + api = NULL; + + if (next_Direct3DCreate9Ex == NULL) { + dprintf("Gfx: next_Direct3DCreate9Ex == NULL\n"); + + goto fail; + } + + hr = next_Direct3DCreate9Ex(sdk_ver, d3d9ex); + + if (FAILED(hr)) { + dprintf("Gfx: next_Direct3DCreate9Ex returned %x\n", (int) hr); + + goto fail; + } + + api = *d3d9ex; + hr = com_proxy_wrap(&proxy, api, sizeof(*api->lpVtbl)); + + if (FAILED(hr)) { + dprintf("Gfx: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl = proxy->vptr; + vtbl->CreateDevice = my_IDirect3D9Ex_CreateDevice; + + *d3d9ex = (IDirect3D9Ex *) proxy; + + return S_OK; + +fail: + if (api != NULL) { + IDirect3D9Ex_Release(api); + } + + return hr; +} + +static HRESULT STDMETHODCALLTYPE my_IDirect3D9_CreateDevice( + IDirect3D9 *self, + UINT adapter, + D3DDEVTYPE type, + HWND hwnd, + DWORD flags, + D3DPRESENT_PARAMETERS *pp, + IDirect3DDevice9 **pdev) +{ + struct com_proxy *proxy; + IDirect3D9 *real; + + dprintf("Gfx: IDirect3D9::CreateDevice hook hit\n"); + + proxy = com_proxy_downcast(self); + real = proxy->real; + + if (gfx_config.windowed) { + pp->Windowed = TRUE; + pp->FullScreen_RefreshRateInHz = 0; + } + + if (gfx_config.framed) { + gfx_util_frame_window(hwnd); + } + + dprintf("Gfx: Using adapter %d\n", gfx_config.monitor); + + return IDirect3D9_CreateDevice(real, gfx_config.monitor, type, hwnd, flags, pp, pdev); +} + +static HRESULT STDMETHODCALLTYPE my_IDirect3D9Ex_CreateDevice( + IDirect3D9Ex *self, + UINT adapter, + D3DDEVTYPE type, + HWND hwnd, + DWORD flags, + D3DPRESENT_PARAMETERS *pp, + IDirect3DDevice9 **pdev) +{ + dprintf("Gfx: IDirect3D9Ex::CreateDevice hook forwarding to my_IDirect3D9_CreateDevice\n"); + + return my_IDirect3D9_CreateDevice( + (IDirect3D9 *) self, + adapter, + type, + hwnd, + flags, + pp, + pdev); +} diff --git a/gfxhook/d3d9.h b/gfxhook/d3d9.h new file mode 100644 index 0000000..90acdf4 --- /dev/null +++ b/gfxhook/d3d9.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "gfxhook/gfx.h" + +void gfx_d3d9_hook_init(const struct gfx_config *cfg, HINSTANCE self); diff --git a/gfxhook/dxgi.c b/gfxhook/dxgi.c new file mode 100644 index 0000000..14d3e5f --- /dev/null +++ b/gfxhook/dxgi.c @@ -0,0 +1,364 @@ +#include +#include +#include + +#include +#include + +#include "gfxhook/dxgi.h" +#include "gfxhook/gfx.h" + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "hooklib/dll.h" + +#include "util/dprintf.h" + +typedef HRESULT (WINAPI *CreateDXGIFactory_t)(REFIID riid, void **factory); +typedef HRESULT (WINAPI *CreateDXGIFactory1_t)(REFIID riid, void **factory); +typedef HRESULT (WINAPI *CreateDXGIFactory2_t)( + UINT flags, + REFIID riid, + void **factory); + +static HRESULT hook_factory(REFIID riid, void **factory); + +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory_CreateSwapChain( + IDXGIFactory *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain); +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory1_CreateSwapChain( + IDXGIFactory1 *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain); +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory2_CreateSwapChain( + IDXGIFactory2 *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain); + +static struct gfx_config gfx_config; +static CreateDXGIFactory_t next_CreateDXGIFactory; +static CreateDXGIFactory1_t next_CreateDXGIFactory1; +static CreateDXGIFactory2_t next_CreateDXGIFactory2; + +static const struct hook_symbol dxgi_hooks[] = { + { + .name = "CreateDXGIFactory", + .patch = CreateDXGIFactory, + .link = (void **) &next_CreateDXGIFactory, + }, { + .name = "CreateDXGIFactory1", + .patch = CreateDXGIFactory1, + .link = (void **) &next_CreateDXGIFactory1, + }, { + .name = "CreateDXGIFactory2", + .patch = CreateDXGIFactory2, + .link = (void **) &next_CreateDXGIFactory2, + }, +}; + +void gfx_dxgi_hook_init(const struct gfx_config *cfg, HINSTANCE self) +{ + HMODULE dxgi; + + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "dxgi.dll", dxgi_hooks, _countof(dxgi_hooks)); + + if (next_CreateDXGIFactory == NULL || next_CreateDXGIFactory1 == NULL) { + dxgi = LoadLibraryW(L"dxgi.dll"); + + if (dxgi == NULL) { + dprintf("DXGI: dxgi.dll not found or failed initialization\n"); + + goto fail; + } + + if (next_CreateDXGIFactory == NULL) { + next_CreateDXGIFactory = (CreateDXGIFactory_t) GetProcAddress( + dxgi, + "CreateDXGIFactory"); + } + if (next_CreateDXGIFactory1 == NULL) { + next_CreateDXGIFactory1 = (CreateDXGIFactory1_t) GetProcAddress( + dxgi, + "CreateDXGIFactory1"); + } + if (next_CreateDXGIFactory2 == NULL) { + next_CreateDXGIFactory2 = (CreateDXGIFactory2_t) GetProcAddress( + dxgi, + "CreateDXGIFactory2"); + } + + if (next_CreateDXGIFactory == NULL) { + dprintf("DXGI: CreateDXGIFactory not found in loaded dxgi.dll\n"); + + goto fail; + } + if (next_CreateDXGIFactory1 == NULL) { + dprintf("DXGI: CreateDXGIFactory1 not found in loaded dxgi.dll\n"); + + goto fail; + } + + /* `CreateDXGIFactory2` was introduced in Windows 8.1 and the original + * Nu runs Windows 8, so do not require it to exist */ + } + + if (self != NULL) { + dll_hook_push(self, L"dxgi.dll"); + } + + return; + +fail: + if (dxgi != NULL) { + FreeLibrary(dxgi); + } +} + +HRESULT WINAPI CreateDXGIFactory(REFIID riid, void **factory) +{ + HRESULT hr; + + dprintf("DXGI: CreateDXGIFactory hook hit\n"); + + hr = next_CreateDXGIFactory(riid, factory); + + if (FAILED(hr)) { + dprintf("DXGI: CreateDXGIFactory returned %x\n", (int) hr); + + return hr; + } + + hr = hook_factory(riid, factory); + + if (FAILED(hr)) { + return hr; + } + + return hr; +} + +HRESULT WINAPI CreateDXGIFactory1(REFIID riid, void **factory) +{ + HRESULT hr; + + dprintf("DXGI: CreateDXGIFactory1 hook hit\n"); + + hr = next_CreateDXGIFactory1(riid, factory); + + if (FAILED(hr)) { + dprintf("DXGI: CreateDXGIFactory1 returned %x\n", (int) hr); + + return hr; + } + + hr = hook_factory(riid, factory); + + if (FAILED(hr)) { + return hr; + } + + return hr; +} + +HRESULT WINAPI CreateDXGIFactory2(UINT flags, REFIID riid, void **factory) +{ + HRESULT hr; + + dprintf("DXGI: CreateDXGIFactory2 hook hit\n"); + + if (next_CreateDXGIFactory2 == NULL) { + dprintf("DXGI: CreateDXGIFactory2 not available, forwarding to CreateDXGIFactory1\n"); + + return CreateDXGIFactory1(riid, factory); + } + + hr = next_CreateDXGIFactory2(flags, riid, factory); + + if (FAILED(hr)) { + dprintf("DXGI: CreateDXGIFactory2 returned %x\n", (int) hr); + + return hr; + } + + hr = hook_factory(riid, factory); + + if (FAILED(hr)) { + return hr; + } + + return hr; +} + +static HRESULT hook_factory(REFIID riid, void **factory) +{ + struct com_proxy *proxy; + IDXGIFactory *api_0; + IDXGIFactory1 *api_1; + IDXGIFactory2 *api_2; + IDXGIFactoryVtbl *vtbl_0; + IDXGIFactory1Vtbl *vtbl_1; + IDXGIFactory2Vtbl *vtbl_2; + HRESULT hr; + + api_0 = NULL; + api_1 = NULL; + api_2 = NULL; + + if (memcmp(riid, &IID_IDXGIFactory, sizeof(*riid)) == 0) { + api_0 = *factory; + hr = com_proxy_wrap(&proxy, api_0, sizeof(*api_0->lpVtbl)); + + if (FAILED(hr)) { + dprintf("DXGI: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl_0 = proxy->vptr; + vtbl_0->CreateSwapChain = my_IDXGIFactory_CreateSwapChain; + + *factory = proxy; + } else if (memcmp(riid, &IID_IDXGIFactory1, sizeof(*riid)) == 0) { + api_1 = *factory; + hr = com_proxy_wrap(&proxy, api_1, sizeof(*api_1->lpVtbl)); + + if (FAILED(hr)) { + dprintf("DXGI: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl_1 = proxy->vptr; + vtbl_1->CreateSwapChain = my_IDXGIFactory1_CreateSwapChain; + + *factory = proxy; + } else if (memcmp(riid, &IID_IDXGIFactory2, sizeof(*riid)) == 0) { + api_2 = *factory; + hr = com_proxy_wrap(&proxy, api_2, sizeof(*api_2->lpVtbl)); + + if (FAILED(hr)) { + dprintf("DXGI: com_proxy_wrap returned %x\n", (int) hr); + + goto fail; + } + + vtbl_2 = proxy->vptr; + vtbl_2->CreateSwapChain = my_IDXGIFactory2_CreateSwapChain; + + *factory = proxy; + } + + return S_OK; + +fail: + if (api_0 != NULL) { + IDXGIFactory_Release(api_0); + } + if (api_1 != NULL) { + IDXGIFactory1_Release(api_1); + } + if (api_2 != NULL) { + IDXGIFactory2_Release(api_2); + } + + return hr; +} + +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory_CreateSwapChain( + IDXGIFactory *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain) +{ + struct com_proxy *proxy; + IDXGIFactory *real; + HWND hwnd; + UINT width; + UINT height; + + dprintf("DXGI: IDXGIFactory::CreateSwapChain hook hit\n"); + + proxy = com_proxy_downcast(self); + real = proxy->real; + + if (desc != NULL) { + desc->Windowed = gfx_config.windowed; + + hwnd = desc->OutputWindow; + width = desc->BufferDesc.Width; + height = desc->BufferDesc.Height; + } else { + hwnd = NULL; + } + + if (hwnd != NULL) { + /* + * Ensure window is maximized to avoid a Windows 10 issue where a + * fullscreen swap chain is not created because the window is minimized + * at the time of creation. + */ + ShowWindow(hwnd, SW_RESTORE); + + if (!gfx_config.framed && width > 0 && height > 0) { + dprintf("DXGI: Resizing window to %ux%u\n", width, height); + + SetWindowLongPtrW(hwnd, GWL_STYLE, WS_POPUP); + SetWindowLongPtrW(hwnd, GWL_EXSTYLE, WS_EX_TOPMOST); + + SetWindowPos( + hwnd, + HWND_TOP, + 0, + 0, + (int) width, + (int) height, + SWP_FRAMECHANGED | SWP_NOSENDCHANGING); + + ShowWindow(hwnd, SW_SHOWMAXIMIZED); + } + } + + return IDXGIFactory_CreateSwapChain(real, device, desc, swapchain); +} + +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory1_CreateSwapChain( + IDXGIFactory1 *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain) +{ + dprintf("DXGI: IDXGIFactory1::CreateSwapChain hook forwarding to my_IDXGIFactory_CreateSwapChain\n"); + + return my_IDXGIFactory_CreateSwapChain( + (IDXGIFactory *) self, + device, + desc, + swapchain); +} + +static HRESULT STDMETHODCALLTYPE my_IDXGIFactory2_CreateSwapChain( + IDXGIFactory2 *self, + IUnknown *device, + DXGI_SWAP_CHAIN_DESC *desc, + IDXGISwapChain **swapchain) +{ + dprintf("DXGI: IDXGIFactory2::CreateSwapChain hook forwarding to my_IDXGIFactory_CreateSwapChain\n"); + + return my_IDXGIFactory_CreateSwapChain( + (IDXGIFactory *) self, + device, + desc, + swapchain); +} diff --git a/gfxhook/dxgi.h b/gfxhook/dxgi.h new file mode 100644 index 0000000..1834300 --- /dev/null +++ b/gfxhook/dxgi.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "gfxhook/gfx.h" + +void gfx_dxgi_hook_init(const struct gfx_config *cfg, HINSTANCE self); diff --git a/gfxhook/gfx.c b/gfxhook/gfx.c new file mode 100644 index 0000000..1af3647 --- /dev/null +++ b/gfxhook/gfx.c @@ -0,0 +1,48 @@ +#include + +#include +#include + +#include "gfxhook/gfx.h" + +#include "hook/table.h" + +#include "util/dprintf.h" + +typedef BOOL (WINAPI *ShowWindow_t)(HWND hWnd, int nCmdShow); + +static BOOL WINAPI hook_ShowWindow(HWND hWnd, int nCmdShow); + +static struct gfx_config gfx_config; +static ShowWindow_t next_ShowWindow; + +static const struct hook_symbol gfx_hooks[] = { + { + .name = "ShowWindow", + .patch = hook_ShowWindow, + .link = (void **) &next_ShowWindow, + }, +}; + +void gfx_hook_init(const struct gfx_config *cfg) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + memcpy(&gfx_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "user32.dll", gfx_hooks, _countof(gfx_hooks)); +} + +static BOOL WINAPI hook_ShowWindow(HWND hWnd, int nCmdShow) +{ + dprintf("Gfx: ShowWindow hook hit\n"); + + if (!gfx_config.framed && nCmdShow == SW_RESTORE) { + nCmdShow = SW_SHOW; + } + + return next_ShowWindow(hWnd, nCmdShow); +} diff --git a/gfxhook/gfx.h b/gfxhook/gfx.h new file mode 100644 index 0000000..9a7e27c --- /dev/null +++ b/gfxhook/gfx.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +struct gfx_config { + bool enable; + bool windowed; + bool framed; + int monitor; +}; + +void gfx_hook_init(const struct gfx_config *cfg); diff --git a/gfxhook/meson.build b/gfxhook/meson.build new file mode 100644 index 0000000..b973ddd --- /dev/null +++ b/gfxhook/meson.build @@ -0,0 +1,28 @@ +gfxhook_lib = static_library( + 'gfxhook', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + dxguid_lib, + ], + link_with : [ + hooklib_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'd3d9.c', + 'd3d9.h', + 'd3d11.c', + 'd3d11.h', + 'dxgi.c', + 'dxgi.h', + 'gfx.c', + 'gfx.h', + 'util.c', + 'util.h', + ], +) diff --git a/gfxhook/util.c b/gfxhook/util.c new file mode 100644 index 0000000..1ee552d --- /dev/null +++ b/gfxhook/util.c @@ -0,0 +1,116 @@ +#include + +#include "gfxhook/util.h" + +#include "util/dprintf.h" + +void gfx_util_ensure_win_visible(HWND hwnd) +{ + /* + * Ensure window is maximized to avoid a Windows 10 issue where a + * fullscreen swap chain is not created because the window is minimized + * at the time of creation. + */ + ShowWindow(hwnd, SW_RESTORE); +} + +void gfx_util_borderless_fullscreen_windowed(HWND hwnd, UINT width, UINT height) +{ + BOOL ok; + HRESULT hr; + + dprintf("Gfx: Resizing window to %ux%u\n", width, height); + + SetWindowLongPtrW(hwnd, GWL_STYLE, WS_POPUP); + SetWindowLongPtrW(hwnd, GWL_EXSTYLE, WS_EX_TOPMOST); + + ok = SetWindowPos( + hwnd, + HWND_TOP, + 0, + 0, + (int) width, + (int) height, + SWP_FRAMECHANGED | SWP_NOSENDCHANGING); + + if (!ok) { + /* come on... */ + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: SetWindowPos failed: %x\n", (int) hr); + + return; + } + + ok = ShowWindow(hwnd, SW_SHOWMAXIMIZED); + + if (!ok) { + /* come on... */ + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: ShowWindow failed: %x\n", (int) hr); + + return; + } +} + +HRESULT gfx_util_frame_window(HWND hwnd) +{ + HRESULT hr; + DWORD error; + LONG style; + RECT rect; + BOOL ok; + + SetLastError(ERROR_SUCCESS); + style = GetWindowLongW(hwnd, GWL_STYLE); + error = GetLastError(); + + if (error != ERROR_SUCCESS) { + hr = HRESULT_FROM_WIN32(error); + dprintf("Gfx: GetWindowLongPtrW(%p, GWL_STYLE) failed: %x\n", + hwnd, + (int) hr); + + return hr; + } + + ok = GetClientRect(hwnd, &rect); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: GetClientRect(%p) failed: %x\n", hwnd, (int) hr); + + return hr; + } + + style |= WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU; + ok = AdjustWindowRect(&rect, style, FALSE); + + if (!ok) { + /* come on... */ + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: AdjustWindowRect failed: %x\n", (int) hr); + + return hr; + } + + /* This... always seems to set an error, even though it works? idk */ + SetWindowLongW(hwnd, GWL_STYLE, style); + + ok = SetWindowPos( + hwnd, + HWND_TOP, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + SWP_FRAMECHANGED | SWP_NOMOVE); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Gfx: SetWindowPos(%p) failed: %x\n", hwnd, (int) hr); + + return hr; + } + + return S_OK; +} diff --git a/gfxhook/util.h b/gfxhook/util.h new file mode 100644 index 0000000..d0c2401 --- /dev/null +++ b/gfxhook/util.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +void gfx_util_ensure_win_visible(HWND hwnd); +void gfx_util_borderless_fullscreen_windowed(HWND hwnd, UINT width, UINT height); +HRESULT gfx_util_frame_window(HWND hwnd); diff --git a/hooklib/config.c b/hooklib/config.c new file mode 100644 index 0000000..5fc9383 --- /dev/null +++ b/hooklib/config.c @@ -0,0 +1,16 @@ +#include + +#include +#include +#include + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"dvd", L"enable", 1, filename); +} diff --git a/hooklib/config.h b/hooklib/config.h new file mode 100644 index 0000000..5da045d --- /dev/null +++ b/hooklib/config.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "hooklib/dvd.h" + +void dvd_config_load(struct dvd_config *cfg, const wchar_t *filename); diff --git a/hooklib/createprocess.c b/hooklib/createprocess.c new file mode 100644 index 0000000..e44ebdb --- /dev/null +++ b/hooklib/createprocess.c @@ -0,0 +1,257 @@ +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/createprocess.h" + +#include "util/dprintf.h" + +void createprocess_hook_init(); +static BOOL WINAPI my_CreateProcessA( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); +BOOL my_CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); + +static BOOL (WINAPI *next_CreateProcessA)( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); + +static BOOL (WINAPI *next_CreateProcessW)( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +); + +static const struct hook_symbol win32_hooks[] = { + { + .name = "CreateProcessA", + .patch = my_CreateProcessA, + .link = (void **) &next_CreateProcessA + }, + { + .name = "CreateProcessW", + .patch = my_CreateProcessW, + .link = (void **) &next_CreateProcessW + }, +}; + +static bool did_init = false; + +static struct process_hook_sym_w *process_syms_w; +static struct process_hook_sym_a *process_syms_a; + +static size_t process_nsyms_a = 0; +static size_t process_nsyms_w = 0; + +static CRITICAL_SECTION createproc_lock; + +HRESULT createprocess_push_hook_w(const wchar_t *name, const wchar_t *head, const wchar_t *tail, bool replace_all) { + struct process_hook_sym_w *new_mem; + struct process_hook_sym_w *new_proc; + HRESULT hr; + + assert(name != NULL); + assert(head != NULL); + + createprocess_hook_init(); + EnterCriticalSection(&createproc_lock); + + new_mem = realloc( + process_syms_w, + (process_nsyms_w + 1) * sizeof(struct process_hook_sym_w)); + + if (new_mem == NULL) { + + LeaveCriticalSection(&createproc_lock); + return E_OUTOFMEMORY; + } + + new_proc = &new_mem[process_nsyms_w]; + memset(new_proc, 0, sizeof(*new_proc)); + new_proc->name = name; + new_proc->head = head; + new_proc->tail = tail; + new_proc->replace_all = replace_all; + + process_syms_w = new_mem; + process_nsyms_w++; + + LeaveCriticalSection(&createproc_lock); + return S_OK; +} + +HRESULT createprocess_push_hook_a(const char *name, const char *head, const char *tail, bool replace_all) { + struct process_hook_sym_a *new_mem; + struct process_hook_sym_a *new_proc; + + assert(name != NULL); + assert(head != NULL); + + createprocess_hook_init(); + + EnterCriticalSection(&createproc_lock); + + new_mem = realloc( + process_syms_a, + (process_nsyms_a + 1) * sizeof(struct process_hook_sym_a)); + + if (new_mem == NULL) { + + LeaveCriticalSection(&createproc_lock); + return E_OUTOFMEMORY; + } + + new_proc = &new_mem[process_nsyms_a]; + memset(new_proc, 0, sizeof(*new_proc)); + new_proc->name = name; + new_proc->head = head; + new_proc->tail = tail; + new_proc->replace_all = replace_all; + + process_syms_a = new_mem; + process_nsyms_a++; + + LeaveCriticalSection(&createproc_lock); + return S_OK; +} + +void createprocess_hook_init() { + if (did_init) { + return; + } + did_init = true; + + hook_table_apply( + NULL, + "kernel32.dll", + win32_hooks, + _countof(win32_hooks)); + InitializeCriticalSection(&createproc_lock); + dprintf("CreateProcess: Init\n"); +} + + +static BOOL WINAPI my_CreateProcessA( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation +) +{ + for (int i = 0; i < process_nsyms_a; i++) { + if (strncmp(process_syms_a[i].name, lpCommandLine, strlen(process_syms_a[i].name))) { + continue; + } + + dprintf("CreateProcess: Hooking child process %s %s\n", lpApplicationName, lpCommandLine); + char new_cmd[MAX_PATH] = {0}; + strcat_s(new_cmd, MAX_PATH, process_syms_a[i].head); + + if (!process_syms_a[i].replace_all) { + strcat_s(new_cmd, MAX_PATH, lpCommandLine); + } + + if (process_syms_a[i].tail != NULL) { + strcat_s(new_cmd, MAX_PATH, process_syms_a[i].tail); + } + + dprintf("CreateProcess: Replaced CreateProcessA %s\n", new_cmd); + return next_CreateProcessA( + lpApplicationName, + new_cmd, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); + } + return next_CreateProcessA( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); +} + +BOOL my_CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) +{ + return next_CreateProcessW( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); +} \ No newline at end of file diff --git a/hooklib/createprocess.h b/hooklib/createprocess.h new file mode 100644 index 0000000..bf226d5 --- /dev/null +++ b/hooklib/createprocess.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +HRESULT createprocess_push_hook_w(const wchar_t *name, const wchar_t *head, const wchar_t *tail, bool replace_all); +HRESULT createprocess_push_hook_a(const char *name, const char *head, const char *tail, bool replace_all); + +struct process_hook_sym_w { + const wchar_t *name; + const wchar_t *head; + const wchar_t *tail; + bool replace_all; +}; + +struct process_hook_sym_a { + const char *name; + const char *head; + const char *tail; + bool replace_all; +}; \ No newline at end of file diff --git a/hooklib/cursor.c b/hooklib/cursor.c new file mode 100644 index 0000000..8c66412 --- /dev/null +++ b/hooklib/cursor.c @@ -0,0 +1,73 @@ +#include + +#include +#include +#include +#include + +#include "hook/table.h" +#include "util/dprintf.h" + +static HCURSOR my_SetCursor(HCURSOR hCursor); +static HCURSOR (*next_SetCursor)(HCURSOR hCursor); +static BOOL my_SetCursorPos(int x, int y); +static BOOL my_SetPhysicalCursorPos(int x, int y); +static int my_ShowCursor(BOOL bShow); + +static const struct hook_symbol cursor_syms[] = { + { + .name = "SetCursor", + .patch = my_SetCursor, + .link = (void **) &next_SetCursor + },/*{ + .name = "SetCursorPos", + .patch = my_SetCursorPos, + },*/ { + .name = "SetPhysicalCursorPos", + .patch = my_SetPhysicalCursorPos + }, /*{ + .name = "ShowCursor", + .patch = my_ShowCursor + }*/ +}; + +void cursor_hook_init() +{ + hook_table_apply( + NULL, + "user32.dll", + cursor_syms, + _countof(cursor_syms)); + + dprintf("Cursor: Init\n"); +} + +static BOOL my_SetCursorPos(int x, int y) +{ + dprintf("my_SetCursorPos Hit! x %d y %d\n", x, y); + return true; +} + +static BOOL my_SetPhysicalCursorPos(int x, int y) +{ + dprintf("my_SetPhysicalCursorPos Hit! x %d y %d\n", x, y); + return true; +} + +static int my_ShowCursor(BOOL bShow) +{ + dprintf("my_ShowCursor Hit!\n"); + return 0; +} + +static HCURSOR my_SetCursor(HCURSOR hCursor) +{ + dprintf("my_SetCursor Hit!\n"); + HCURSOR fake_cursor; + + if ( hCursor ) + return next_SetCursor(hCursor); + fake_cursor = LoadCursorA(0, (LPCSTR)0x7F00); + next_SetCursor(fake_cursor); + return 0; +} diff --git a/hooklib/cursor.h b/hooklib/cursor.h new file mode 100644 index 0000000..de0461a --- /dev/null +++ b/hooklib/cursor.h @@ -0,0 +1,3 @@ +#pragma once + +void cursor_hook_init(); \ No newline at end of file diff --git a/hooklib/dll.c b/hooklib/dll.c new file mode 100644 index 0000000..9400bb9 --- /dev/null +++ b/hooklib/dll.c @@ -0,0 +1,351 @@ +/* This is general enough to break out into capnhook eventually. + Don't introduce util/ dependencies here. */ + +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/dll.h" + +struct dll_hook_reg { + const wchar_t *name; + HMODULE redir_mod; +}; + +/* Helper functions */ + +static void dll_hook_init(void); +static HMODULE dll_hook_search_dll(const wchar_t *name); + +/* Hook functions */ + +static BOOL WINAPI hook_FreeLibrary(HMODULE mod); +static HMODULE WINAPI hook_GetModuleHandleA(const char *name); +static HMODULE WINAPI hook_GetModuleHandleW(const wchar_t *name); +static HMODULE WINAPI hook_LoadLibraryA(const char *name); +static HMODULE WINAPI hook_LoadLibraryW(const wchar_t *name); +static HMODULE WINAPI hook_LoadLibraryExA(const char *name, HANDLE file, DWORD flags); +static HMODULE WINAPI hook_LoadLibraryExW(const wchar_t *name, HANDLE file, DWORD flags); + +/* Link pointers */ + +static BOOL (WINAPI *next_FreeLibrary)(HMODULE mod); +static HMODULE (WINAPI *next_GetModuleHandleA)(const char *name); +static HMODULE (WINAPI *next_GetModuleHandleW)(const wchar_t *name); +static HMODULE (WINAPI *next_LoadLibraryA)(const char *name); +static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name); +static HMODULE (WINAPI *next_LoadLibraryExA)(const char *name, HANDLE file, DWORD flags); +static HMODULE (WINAPI *next_LoadLibraryExW)(const wchar_t *name, HANDLE file, DWORD flags); + +static const struct hook_symbol dll_loader_syms[] = { + { + .name = "FreeLibrary", + .patch = hook_FreeLibrary, + .link = (void **) &next_FreeLibrary, + }, { + .name = "GetModuleHandleA", + .patch = hook_GetModuleHandleA, + .link = (void **) &next_GetModuleHandleA, + }, { + .name = "GetModuleHandleW", + .patch = hook_GetModuleHandleW, + .link = (void **) &next_GetModuleHandleW, + }, { + .name = "LoadLibraryA", + .patch = hook_LoadLibraryA, + .link = (void **) &next_LoadLibraryA, + }, { + .name = "LoadLibraryW", + .patch = hook_LoadLibraryW, + .link = (void **) &next_LoadLibraryW, + }, { + .name = "LoadLibraryExA", + .patch = hook_LoadLibraryExA, + .link = (void **) &next_LoadLibraryExA, + }, { + .name = "LoadLibraryExW", + .patch = hook_LoadLibraryExW, + .link = (void **) &next_LoadLibraryExW, + } +}; + +static bool dll_hook_initted; +static CRITICAL_SECTION dll_hook_lock; +static struct dll_hook_reg *dll_hook_list; +static size_t dll_hook_count; + +HRESULT dll_hook_push( + HMODULE redir_mod, + const wchar_t *name) +{ + struct dll_hook_reg *new_item; + struct dll_hook_reg *new_mem; + HRESULT hr; + + assert(name != NULL); + + dll_hook_init(); + + EnterCriticalSection(&dll_hook_lock); + + new_mem = realloc( + dll_hook_list, + (dll_hook_count + 1) * sizeof(struct dll_hook_reg)); + + if (new_mem == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + new_item = &new_mem[dll_hook_count]; + new_item->name = name; + new_item->redir_mod = redir_mod; + + dll_hook_list = new_mem; + dll_hook_count++; + hr = S_OK; + +end: + LeaveCriticalSection(&dll_hook_lock); + + return hr; +} + +static void dll_hook_init(void) +{ + HMODULE kernel32; + + /* Init is not thread safe, because API hooking is not thread safe. */ + + if (dll_hook_initted) { + return; + } + + dll_hook_initted = true; + InitializeCriticalSection(&dll_hook_lock); + + /* Protect against the (probably impossible) scenario where nothing in the + process imports LoadLibraryW but something imports LoadLibraryA. Also + do the same with LoadLibraryExW. + + We know something imports GetModuleHandleW because we do, right here. + + (we're about to hook these APIs of course, so we have to set this up + before the hooks go in) */ + + kernel32 = GetModuleHandleW(L"kernel32.dll"); + next_LoadLibraryW = (void *) GetProcAddress(kernel32, "LoadLibraryW"); + next_LoadLibraryExW = (void *) GetProcAddress(kernel32, "LoadLibraryExW"); + + /* Now we can apply the hook table */ + + hook_table_apply( + NULL, + "kernel32.dll", + dll_loader_syms, + _countof(dll_loader_syms)); +} + +static HMODULE dll_hook_search_dll(const wchar_t *name) +{ + HMODULE result; + size_t i; + + result = NULL; + + EnterCriticalSection(&dll_hook_lock); + + for (i = 0 ; i < dll_hook_count ; i++) { + if (wcsicmp(name, dll_hook_list[i].name) == 0) { + result = dll_hook_list[i].redir_mod; + + break; + } + } + + LeaveCriticalSection(&dll_hook_lock); + + return result; +} + +static BOOL WINAPI hook_FreeLibrary(HMODULE mod) +{ + bool match; + size_t i; + + match = false; + EnterCriticalSection(&dll_hook_lock); + + for (i = 0 ; i < dll_hook_count ; i++) { + if (mod == dll_hook_list[i].redir_mod) { + match = true; + + break; + } + } + + LeaveCriticalSection(&dll_hook_lock); + + if (match) { + /* Block attempts to unload redirected modules, since this could cause + a hook DLL to unexpectedly vanish and crash the whole application. + + Reference counting might be another solution, although it is + possible that a buggy application might cause a hook DLL unload in + that case. */ + SetLastError(ERROR_SUCCESS); + + return TRUE; + } + + return next_FreeLibrary(mod); +} + +static HMODULE WINAPI hook_GetModuleHandleA(const char *name) +{ + HMODULE result; + wchar_t *name_w; + size_t name_c; + + if (name == NULL) { + return next_GetModuleHandleA(NULL); + } + + mbstowcs_s(&name_c, NULL, 0, name, 0); + name_w = malloc(name_c * sizeof(wchar_t)); + + if (name_w == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + + return NULL; + } + + mbstowcs_s(NULL, name_w, name_c, name, name_c - 1); + result = hook_GetModuleHandleW(name_w); + free(name_w); + + return result; +} + +static HMODULE WINAPI hook_GetModuleHandleW(const wchar_t *name) +{ + HMODULE result; + + if (name == NULL) { + return next_GetModuleHandleW(NULL); + } + + result = dll_hook_search_dll(name); + + if (result != NULL) { + SetLastError(ERROR_SUCCESS); + } else { + result = next_GetModuleHandleW(name); + } + + return result; +} + +static HMODULE WINAPI hook_LoadLibraryA(const char *name) +{ + HMODULE result; + wchar_t *name_w; + size_t name_c; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + mbstowcs_s(&name_c, NULL, 0, name, 0); + name_w = malloc(name_c * sizeof(wchar_t)); + + if (name_w == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + + return NULL; + } + + mbstowcs_s(NULL, name_w, name_c, name, name_c - 1); + result = hook_LoadLibraryW(name_w); + free(name_w); + + return result; +} + +static HMODULE WINAPI hook_LoadLibraryW(const wchar_t *name) +{ + HMODULE result; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + result = dll_hook_search_dll(name); + + if (result != NULL) { + SetLastError(ERROR_SUCCESS); + } else { + result = next_LoadLibraryW(name); + } + + return result; +} + +static HMODULE WINAPI hook_LoadLibraryExA(const char *name, HANDLE file, DWORD flags) +{ + HMODULE result; + wchar_t *name_w; + size_t name_c; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + mbstowcs_s(&name_c, NULL, 0, name, 0); + name_w = malloc(name_c * sizeof(wchar_t)); + + if (name_w == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + + return NULL; + } + + mbstowcs_s(NULL, name_w, name_c, name, name_c - 1); + result = hook_LoadLibraryExW(name_w, file, flags); + free(name_w); + + return result; +} + +static HMODULE WINAPI hook_LoadLibraryExW(const wchar_t *name, HANDLE file, DWORD flags) +{ + HMODULE result; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + result = dll_hook_search_dll(name); + + if (result != NULL) { + SetLastError(ERROR_SUCCESS); + } else { + result = next_LoadLibraryExW(name, file, flags); + } + + return result; +} diff --git a/hooklib/dll.h b/hooklib/dll.h new file mode 100644 index 0000000..b4fc1b7 --- /dev/null +++ b/hooklib/dll.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include +#include + +HRESULT dll_hook_push( + HMODULE redir_mod, + const wchar_t *name); diff --git a/hooklib/dns.c b/hooklib/dns.c new file mode 100644 index 0000000..aac90ce --- /dev/null +++ b/hooklib/dns.c @@ -0,0 +1,462 @@ +/* Might push this to capnhook, don't add any util dependencies. */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "hook/hr.h" +#include "hook/table.h" + +#include "hooklib/dns.h" + +/* Latest w32headers does not include DnsQueryEx, so we'll have to "polyfill" + its associated data types here for the time being. + + Results and cancel handle are passed through, so we'll just use void + pointers for those args. So are most of the fields in this structure, for + that matter. */ + +typedef struct POLYFILL_DNS_QUERY_REQUEST { + ULONG Version; + PCWSTR QueryName; + WORD QueryType; + ULONG64 QueryOptions; + void* pDnsServerList; + ULONG InterfaceIndex; + void* pQueryCompletionCallback; + PVOID pQueryContext; +} POLYFILL_DNS_QUERY_REQUEST; + +struct dns_hook_entry { + wchar_t *from; + wchar_t *to; +}; + +/* Hook funcs */ + +static DNS_STATUS WINAPI hook_DnsQuery_A( + const char *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved); + +static DNS_STATUS WINAPI hook_DnsQuery_W( + const wchar_t *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved); + +static DNS_STATUS WINAPI hook_DnsQueryEx( + POLYFILL_DNS_QUERY_REQUEST *pRequest, + void *pQueryResults, + void *pCancelHandle); + +static int WSAAPI hook_getaddrinfo( + const char *pNodeName, + const char *pServiceName, + const ADDRINFOA *pHints, + ADDRINFOA **ppResult); + +/* Link pointers */ + +static DNS_STATUS (WINAPI *next_DnsQuery_A)( + const char *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved); + +static DNS_STATUS (WINAPI *next_DnsQuery_W)( + const wchar_t *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved); + +static DNS_STATUS (WINAPI *next_DnsQueryEx)( + POLYFILL_DNS_QUERY_REQUEST *pRequest, + void *pQueryResults, + void *pCancelHandle); + +static int (WSAAPI *next_getaddrinfo)( + const char *pNodeName, + const char *pServiceName, + const ADDRINFOA *pHints, + ADDRINFOA **ppResult); + +static const struct hook_symbol dns_hook_syms_dnsapi[] = { + { + .name = "DnsQuery_A", + .patch = hook_DnsQuery_A, + .link = (void **) &next_DnsQuery_A, + }, { + .name = "DnsQuery_W", + .patch = hook_DnsQuery_W, + .link = (void **) &next_DnsQuery_W, + }, { + .name = "DnsQueryEx", + .patch = hook_DnsQueryEx, + .link = (void **) &next_DnsQueryEx, + } +}; + +static const struct hook_symbol dns_hook_syms_ws2[] = { + { + .name = "getaddrinfo", + .ordinal = 176, + .patch = hook_getaddrinfo, + .link = (void **) &next_getaddrinfo, + } +}; + +static bool dns_hook_initted; +static CRITICAL_SECTION dns_hook_lock; +static struct dns_hook_entry *dns_hook_entries; +static size_t dns_hook_nentries; + +static void dns_hook_init(void) +{ + if (dns_hook_initted) { + return; + } + + dns_hook_initted = true; + InitializeCriticalSection(&dns_hook_lock); + + hook_table_apply( + NULL, + "dnsapi.dll", + dns_hook_syms_dnsapi, + _countof(dns_hook_syms_dnsapi)); + + hook_table_apply( + NULL, + "ws2_32.dll", + dns_hook_syms_ws2, + _countof(dns_hook_syms_ws2)); +} + +HRESULT dns_hook_push(const wchar_t *from_src, const wchar_t *to_src) +{ + HRESULT hr; + struct dns_hook_entry *newmem; + struct dns_hook_entry *newitem; + wchar_t *from; + wchar_t *to; + + assert(from_src != NULL); + + to = NULL; + from = NULL; + dns_hook_init(); + + EnterCriticalSection(&dns_hook_lock); + + from = _wcsdup(from_src); + + if (from == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + if(to_src != NULL) { + to = _wcsdup(to_src); + + if (to == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + } + + newmem = realloc( + dns_hook_entries, + (dns_hook_nentries + 1) * sizeof(struct dns_hook_entry)); + + if (newmem == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + dns_hook_entries = newmem; + newitem = &newmem[dns_hook_nentries++]; + newitem->from = from; + newitem->to = to; + + from = NULL; + to = NULL; + hr = S_OK; + +end: + LeaveCriticalSection(&dns_hook_lock); + + free(to); + free(from); + + return hr; +} + +static DNS_STATUS WINAPI hook_DnsQuery_A( + const char *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved) +{ + const struct dns_hook_entry *pos; + size_t i; + size_t wstr_c; + wchar_t *wstr; + size_t str_c; + char *str; + DNS_STATUS code; + HRESULT hr; + + wstr = NULL; + str = NULL; + + if (pszName == NULL) { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + + goto end; + } + + mbstowcs_s(&wstr_c, NULL, 0, pszName, 0); + wstr = malloc(wstr_c * sizeof(wchar_t)); + + if (wstr == NULL) { + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + + goto end; + } + + mbstowcs_s(NULL, wstr, wstr_c, pszName, wstr_c - 1); + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (_wcsicmp(wstr, pos->from) == 0) { + if(pos->to == NULL) { + LeaveCriticalSection(&dns_hook_lock); + hr = HRESULT_FROM_WIN32(DNS_ERROR_RCODE_NAME_ERROR); + + goto end; + } + + wcstombs_s(&str_c, NULL, 0, pos->to, 0); + str = malloc(str_c * sizeof(char)); + + if (str == NULL) { + LeaveCriticalSection(&dns_hook_lock); + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + + goto end; + } + + wcstombs_s(NULL, str, str_c, pos->to, str_c - 1); + pszName = str; + + break; + } + } + + LeaveCriticalSection(&dns_hook_lock); + + code = next_DnsQuery_A( + pszName, + wType, + Options, + pExtra, + ppQueryResults, + pReserved); + + hr = HRESULT_FROM_WIN32(code); + +end: + free(str); + free(wstr); + + return hr_to_win32_error(hr); +} + +static DNS_STATUS WINAPI hook_DnsQuery_W( + const wchar_t *pszName, + WORD wType, + DWORD Options, + void *pExtra, + DNS_RECORD **ppQueryResults, + void *pReserved) +{ + const struct dns_hook_entry *pos; + size_t i; + + if (pszName == NULL) { + return ERROR_INVALID_PARAMETER; + } + + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (_wcsicmp(pszName, pos->from) == 0) { + if(pos->to == NULL) { + LeaveCriticalSection(&dns_hook_lock); + return HRESULT_FROM_WIN32(DNS_ERROR_RCODE_NAME_ERROR); + } + + pszName = pos->to; + + break; + } + } + + LeaveCriticalSection(&dns_hook_lock); + + return next_DnsQuery_W( + pszName, + wType, + Options, + pExtra, + ppQueryResults, + pReserved); + +} + +static DNS_STATUS WINAPI hook_DnsQueryEx( + POLYFILL_DNS_QUERY_REQUEST *pRequest, + void *pQueryResults, + void *pCancelHandle) +{ + const wchar_t *orig; + const struct dns_hook_entry *pos; + DNS_STATUS code; + size_t i; + + if (pRequest == NULL) { + return ERROR_INVALID_PARAMETER; + } + + orig = pRequest->QueryName; + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (_wcsicmp(pRequest->QueryName, pos->from) == 0) { + if(pos->to == NULL) { + LeaveCriticalSection(&dns_hook_lock); + return HRESULT_FROM_WIN32(DNS_ERROR_RCODE_NAME_ERROR); + } + + pRequest->QueryName = pos->to; + + break; + } + } + + LeaveCriticalSection(&dns_hook_lock); + + code = next_DnsQueryEx(pRequest, pQueryResults, pCancelHandle); + + /* Caller might not appreciate QueryName changing under its feet. It is + strongly implied by MSDN that a copy of *pRequest is taken by WINAPI, + so we can change it back after the call has been issued with no ill + effect... we hope. + + Hopefully the completion callback is issued from an APC or something + (or otherwise happens after this returns) or we're in trouble. */ + + pRequest->QueryName = orig; + + return code; +} + +static int WSAAPI hook_getaddrinfo( + const char *pNodeName, + const char *pServiceName, + const ADDRINFOA *pHints, + ADDRINFOA **ppResult) +{ + const struct dns_hook_entry *pos; + char *str; + size_t str_c; + wchar_t *wstr; + size_t wstr_c; + int result; + size_t i; + + str = NULL; + wstr = NULL; + + if (pNodeName == NULL) { + result = WSA_INVALID_PARAMETER; + + goto end; + } + + mbstowcs_s(&wstr_c, NULL, 0, pNodeName, 0); + wstr = malloc(wstr_c * sizeof(wchar_t)); + + if (wstr == NULL) { + result = WSA_NOT_ENOUGH_MEMORY; + + goto end; + } + + mbstowcs_s(NULL, wstr, wstr_c, pNodeName, wstr_c - 1); + EnterCriticalSection(&dns_hook_lock); + + for (i = 0 ; i < dns_hook_nentries ; i++) { + pos = &dns_hook_entries[i]; + + if (_wcsicmp(wstr, pos->from) == 0) { + if(pos->to == NULL) { + LeaveCriticalSection(&dns_hook_lock); + result = EAI_NONAME; + + goto end; + } + + wcstombs_s(&str_c, NULL, 0, pos->to, 0); + str = malloc(str_c * sizeof(char)); + + if (str == NULL) { + LeaveCriticalSection(&dns_hook_lock); + result = WSA_NOT_ENOUGH_MEMORY; + + goto end; + } + + wcstombs_s(NULL, str, str_c, pos->to, str_c - 1); + pNodeName = str; + + break; + } + } + + LeaveCriticalSection(&dns_hook_lock); + + result = next_getaddrinfo(pNodeName, pServiceName, pHints, ppResult); + +end: + free(wstr); + free(str); + + return result; +} diff --git a/hooklib/dns.h b/hooklib/dns.h new file mode 100644 index 0000000..1f93b0f --- /dev/null +++ b/hooklib/dns.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include + +// if to_src is NULL, all lookups for from_src will fail +HRESULT dns_hook_push(const wchar_t *from_src, const wchar_t *to_src); + diff --git a/hooklib/dvd.c b/hooklib/dvd.c new file mode 100644 index 0000000..2c135eb --- /dev/null +++ b/hooklib/dvd.c @@ -0,0 +1,82 @@ +#include + +#include +#include +#include + +#include "hook/com-proxy.h" +#include "hook/table.h" + +#include "hooklib/config.h" +#include "hooklib/dll.h" +#include "hooklib/dvd.h" + +#include "util/dprintf.h" + +/* API hooks */ + +static DWORD WINAPI hook_QueryDosDeviceW( + const wchar_t *lpDeviceName, + wchar_t *lpTargetPath, + DWORD ucchMax); + +/* Link pointers */ + +static DWORD (WINAPI *next_QueryDosDeviceW)( + const wchar_t *lpDeviceName, + wchar_t *lpTargetPath, + DWORD ucchMax); + +static bool dvd_hook_initted; +static struct dvd_config dvd_config; + +static const struct hook_symbol dvd_hooks[] = { + { + .name = "QueryDosDeviceW", + .patch = hook_QueryDosDeviceW, + .link = (void **) &next_QueryDosDeviceW + }, +}; + +void dvd_hook_init(const struct dvd_config *cfg, HINSTANCE self) +{ + assert(cfg != NULL); + + if (!cfg->enable) { + return; + } + + if (dvd_hook_initted) { + return; + } + + dvd_hook_initted = true; + + memcpy(&dvd_config, cfg, sizeof(*cfg)); + hook_table_apply(NULL, "kernel32.dll", dvd_hooks, _countof(dvd_hooks)); + dprintf("DVD: hook enabled.\n"); +} + +DWORD WINAPI hook_QueryDosDeviceW( + const wchar_t *lpDeviceName, + wchar_t *lpTargetPath, + DWORD ucchMax) +{ + DWORD ok; + wchar_t *p_dest; + wchar_t *dvd_string = L"CdRom"; + + ok = next_QueryDosDeviceW( + lpDeviceName, + lpTargetPath, + ucchMax); + + p_dest = wcsstr (lpTargetPath, dvd_string); + + if ( p_dest != NULL ) { + dprintf("DVD: Hiding DVD drive.\n"); + return 0; + } + + return ok; +} diff --git a/hooklib/dvd.h b/hooklib/dvd.h new file mode 100644 index 0000000..ba7777a --- /dev/null +++ b/hooklib/dvd.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include + +struct dvd_config { + bool enable; +}; + +/* Init is not thread safe because API hook init is not thread safe blah + blah blah you know the drill by now. */ + +void dvd_hook_init(const struct dvd_config *cfg, HINSTANCE self); diff --git a/hooklib/fdshark.c b/hooklib/fdshark.c new file mode 100644 index 0000000..e33e9e2 --- /dev/null +++ b/hooklib/fdshark.c @@ -0,0 +1,218 @@ +#include + +#include +#include +#include +#include + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/fdshark.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static const wchar_t *fdshark_path; +static HANDLE fdshark_target_fd; +static int fdshark_flags; + +static HRESULT fdshark_handle_irp(struct irp *irp); +static HRESULT fdshark_handle_open(struct irp *irp); +static HRESULT fdshark_handle_close(struct irp *irp); +static HRESULT fdshark_handle_read(struct irp *irp); +static HRESULT fdshark_handle_write(struct irp *irp); +static HRESULT fdshark_handle_ioctl(struct irp *irp); +static bool fdshark_force_sync(struct irp *irp, HRESULT hr); + +HRESULT fdshark_hook_init(const wchar_t *path, int flags) +{ + assert(path != NULL); + assert(!(flags & ~FDSHARK_ALL_FLAGS_)); + + fdshark_path = path; + fdshark_flags = flags; + + return iohook_push_handler(fdshark_handle_irp); +} + +static HRESULT fdshark_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != fdshark_target_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return fdshark_handle_open(irp); + case IRP_OP_CLOSE: return fdshark_handle_close(irp); + case IRP_OP_READ: return fdshark_handle_read(irp); + case IRP_OP_WRITE: return fdshark_handle_write(irp); + case IRP_OP_IOCTL: return fdshark_handle_ioctl(irp); + default: return iohook_invoke_next(irp); + } +} + +static HRESULT fdshark_handle_open(struct irp *irp) +{ + HRESULT hr; + + if (_wcsicmp(irp->open_filename, fdshark_path) != 0) { + return iohook_invoke_next(irp); + } + + hr = iohook_invoke_next(irp); + + if (FAILED(hr)) { + return hr; + } + + dprintf("FdShark: Opened %S\n", fdshark_path); + fdshark_target_fd = irp->fd; + + return hr; +} + +static HRESULT fdshark_handle_close(struct irp *irp) +{ + dprintf("FdShark: Closed %S\n", fdshark_path); + fdshark_target_fd = NULL; + + return iohook_invoke_next(irp); +} + +static HRESULT fdshark_handle_read(struct irp *irp) +{ + HRESULT hr; + + if (!(fdshark_flags & FDSHARK_TRACE_READ)) { + return iohook_invoke_next(irp); + } + + dprintf("FdShark: Read %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + hr = iohook_invoke_next(irp); + + if (FAILED(hr) && !fdshark_force_sync(irp, hr)) { + dprintf("FdShark: FAILED: %x\n", (int) hr); + } else { + dprintf("FdShark: Read %p:%i/%i OK\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + dump_iobuf(&irp->read); + } + + return S_OK; +} + +static HRESULT fdshark_handle_write(struct irp *irp) +{ + HRESULT hr; + + if (!(fdshark_flags & FDSHARK_TRACE_WRITE)) { + return iohook_invoke_next(irp); + } + + dprintf("FdShark: Write %p:%i/%i\n", + irp->write.bytes, + (int) irp->write.pos, + (int) irp->write.nbytes); + dump_const_iobuf(&irp->write); + + hr = iohook_invoke_next(irp); + + if (FAILED(hr) && !fdshark_force_sync(irp, hr)) { + dprintf("FdShark: FAILED: %x\n", (int) hr); + } else { + dprintf("FdShark: Write %p:%i/%i OK\n", + irp->write.bytes, + (int) irp->write.pos, + (int) irp->write.nbytes); + } + + return S_OK; +} + +static HRESULT fdshark_handle_ioctl(struct irp *irp) +{ + HRESULT hr; + + if (!(fdshark_flags & FDSHARK_TRACE_IOCTL)) { + return iohook_invoke_next(irp); + } + + dprintf("FdShark: Ioctl %08x w:%p:%i/%i r:%p:%i/%i\n", + irp->ioctl, + irp->write.bytes, + (int) irp->write.pos, + (int) irp->write.nbytes, + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + dump_const_iobuf(&irp->write); + + hr = iohook_invoke_next(irp); + + if (FAILED(hr) && !fdshark_force_sync(irp, hr)) { + dprintf("FdShark: FAILED: %x\n", (int) hr); + } else { + dprintf("FdShark: Ioctl %08x w:%p:%i/%i r:%p:%i/%i OK\n", + irp->ioctl, + irp->write.bytes, + (int) irp->write.pos, + (int) irp->write.nbytes, + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + dump_iobuf(&irp->read); + } + + return S_OK; +} + +static bool fdshark_force_sync(struct irp *irp, HRESULT hr) +{ + DWORD xferred; + BOOL ok; + + if (!(fdshark_flags & FDSHARK_FORCE_SYNC)) { + return false; + } + + if ( hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING) && + hr != HRESULT_FROM_NT(STATUS_PENDING)) { + return false; + } + + ok = GetOverlappedResult(irp->fd, irp->ovl, &xferred, TRUE); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("FdShark: Synchronous block failed: %x\n", (int) hr); + + return false; + } + + switch (irp->op) { + case IRP_OP_READ: + case IRP_OP_IOCTL: + irp->read.pos += xferred; + + break; + + case IRP_OP_WRITE: + irp->write.pos += xferred; + + break; + + default: + break; + } + + return true; +} diff --git a/hooklib/fdshark.h b/hooklib/fdshark.h new file mode 100644 index 0000000..88dbd5c --- /dev/null +++ b/hooklib/fdshark.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include +#include + +enum { + FDSHARK_FORCE_SYNC = 0x1, + FDSHARK_TRACE_READ = 0x2, + FDSHARK_TRACE_WRITE = 0x4, + FDSHARK_TRACE_IOCTL = 0x8, + FDSHARK_ALL_FLAGS_ = 0xF, +}; + +HRESULT fdshark_hook_init(const wchar_t *filename, int flags); diff --git a/hooklib/meson.build b/hooklib/meson.build new file mode 100644 index 0000000..0a69e9f --- /dev/null +++ b/hooklib/meson.build @@ -0,0 +1,33 @@ +hooklib_lib = static_library( + 'hooklib', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'cursor.c', + 'cursor.h', + 'config.c', + 'config.h', + 'createprocess.c', + 'createprocess.h', + 'dll.c', + 'dll.h', + 'dns.c', + 'dns.h', + 'dvd.c', + 'dvd.h', + 'fdshark.c', + 'fdshark.h', + 'path.c', + 'path.h', + 'reg.c', + 'reg.h', + 'setupapi.c', + 'setupapi.h', + 'spike.c', + 'spike.h', + ], +) diff --git a/hooklib/path.c b/hooklib/path.c new file mode 100644 index 0000000..1d1d4d2 --- /dev/null +++ b/hooklib/path.c @@ -0,0 +1,908 @@ +#include + +#include +#include +#include +#include +#include + +#include "hook/hr.h" +#include "hook/table.h" + +#include "hooklib/path.h" + +/* Helpers */ + +static void path_hook_init(void); +static BOOL path_transform_a(char **out, const char *src); +static BOOL path_transform_w(wchar_t **out, const wchar_t *src); + +/* API hooks */ + +static BOOL WINAPI hook_CreateDirectoryA( + const char *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL WINAPI hook_CreateDirectoryW( + const wchar_t *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL WINAPI hook_CreateDirectoryExA( + const char *lpTemplateDirectory, + const char *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL WINAPI hook_CreateDirectoryExW( + const wchar_t *lpTemplateDirectory, + const wchar_t *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static HANDLE WINAPI hook_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE WINAPI hook_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE WINAPI hook_FindFirstFileA( + const char *lpFileName, + LPWIN32_FIND_DATAA lpFindFileData); + +static HANDLE WINAPI hook_FindFirstFileW( + const wchar_t *lpFileName, + LPWIN32_FIND_DATAW lpFindFileData); + +static HANDLE WINAPI hook_FindFirstFileExA( + const char *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags); + +static HANDLE WINAPI hook_FindFirstFileExW( + const wchar_t *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags); + +static DWORD WINAPI hook_GetFileAttributesA(const char *lpFileName); + +static DWORD WINAPI hook_GetFileAttributesW(const wchar_t *lpFileName); + +static BOOL WINAPI hook_GetFileAttributesExA( + const char *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation); + +static BOOL WINAPI hook_GetFileAttributesExW( + const wchar_t *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation); + +static BOOL WINAPI hook_RemoveDirectoryA(const char *lpFileName); + +static BOOL WINAPI hook_RemoveDirectoryW(const wchar_t *lpFileName); + +static BOOL WINAPI hook_PathFileExistsA(LPCSTR pszPath); + +static BOOL WINAPI hook_PathFileExistsW(LPCWSTR pszPath); + +/* Link pointers */ + +static BOOL (WINAPI *next_CreateDirectoryA)( + const char *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL (WINAPI *next_CreateDirectoryW)( + const wchar_t *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL (WINAPI *next_CreateDirectoryExA)( + const char *lpTemplateDirectory, + const char *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static BOOL (WINAPI *next_CreateDirectoryExW)( + const wchar_t *lpTemplateDirectory, + const wchar_t *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes); + +static HANDLE (WINAPI *next_CreateFileA)( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE (WINAPI *next_CreateFileW)( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); + +static HANDLE (WINAPI *next_FindFirstFileA)( + const char *lpFileName, + LPWIN32_FIND_DATAA lpFindFileData); + +static HANDLE (WINAPI *next_FindFirstFileW)( + const wchar_t *lpFileName, + LPWIN32_FIND_DATAW lpFindFileData); + +static HANDLE (WINAPI *next_FindFirstFileExA)( + const char *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags); + +static HANDLE (WINAPI *next_FindFirstFileExW)( + const wchar_t *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags); + +static DWORD (WINAPI *next_GetFileAttributesA)(const char *lpFileName); + +static DWORD (WINAPI *next_GetFileAttributesW)(const wchar_t *lpFileName); + +static BOOL (WINAPI *next_GetFileAttributesExA)( + const char *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation); + +static BOOL (WINAPI *next_GetFileAttributesExW)( + const wchar_t *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation); + +static BOOL (WINAPI *next_RemoveDirectoryA)(const char *lpFileName); + +static BOOL (WINAPI *next_RemoveDirectoryW)(const wchar_t *lpFileName); + +static BOOL (WINAPI *next_PathFileExistsA)(LPCSTR pszPath); + +static BOOL (WINAPI *next_PathFileExistsW)(LPCWSTR pszPath); + +/* Hook table */ + +static const struct hook_symbol path_hook_syms[] = { + { + .name = "CreateDirectoryA", + .patch = hook_CreateDirectoryA, + .link = (void **) &next_CreateDirectoryA, + }, { + .name = "CreateDirectoryW", + .patch = hook_CreateDirectoryW, + .link = (void **) &next_CreateDirectoryW, + }, { + .name = "CreateDirectoryExA", + .patch = hook_CreateDirectoryExA, + .link = (void **) &next_CreateDirectoryExA, + }, { + .name = "CreateDirectoryExW", + .patch = hook_CreateDirectoryExW, + .link = (void **) &next_CreateDirectoryExW, + }, { + .name = "CreateFileA", + .patch = hook_CreateFileA, + .link = (void **) &next_CreateFileA, + }, { + .name = "CreateFileW", + .patch = hook_CreateFileW, + .link = (void **) &next_CreateFileW, + }, { + .name = "FindFirstFileA", + .patch = hook_FindFirstFileA, + .link = (void **) &next_FindFirstFileA, + }, { + .name = "FindFirstFileW", + .patch = hook_FindFirstFileW, + .link = (void **) &next_FindFirstFileW, + }, { + .name = "FindFirstFileExA", + .patch = hook_FindFirstFileExA, + .link = (void **) &next_FindFirstFileExA, + }, { + .name = "FindFirstFileExW", + .patch = hook_FindFirstFileExW, + .link = (void **) &next_FindFirstFileExW, + }, { + .name = "GetFileAttributesA", + .patch = hook_GetFileAttributesA, + .link = (void **) &next_GetFileAttributesA, + }, { + .name = "GetFileAttributesW", + .patch = hook_GetFileAttributesW, + .link = (void **) &next_GetFileAttributesW, + }, { + .name = "GetFileAttributesExA", + .patch = hook_GetFileAttributesExA, + .link = (void **) &next_GetFileAttributesExA, + }, { + .name = "GetFileAttributesExW", + .patch = hook_GetFileAttributesExW, + .link = (void **) &next_GetFileAttributesExW, + }, { + .name = "RemoveDirectoryA", + .patch = hook_RemoveDirectoryA, + .link = (void **) &next_RemoveDirectoryA, + }, { + .name = "RemoveDirectoryW", + .patch = hook_RemoveDirectoryW, + .link = (void **) &next_RemoveDirectoryW, + }, { + .name = "PathFileExistsA", + .patch = hook_PathFileExistsA, + .link = (void **) &next_PathFileExistsA, + }, { + .name = "PathFileExistsW", + .patch = hook_PathFileExistsW, + .link = (void **) &next_PathFileExistsW, + } +}; + +static bool path_hook_initted; +static CRITICAL_SECTION path_hook_lock; +static path_hook_t *path_hook_list; +static size_t path_hook_count; + +HRESULT path_hook_push(path_hook_t hook) +{ + path_hook_t *tmp; + HRESULT hr; + + assert(hook != NULL); + + path_hook_init(); + + EnterCriticalSection(&path_hook_lock); + + tmp = realloc( + path_hook_list, + (path_hook_count + 1) * sizeof(path_hook_t)); + + if (tmp == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + path_hook_list = tmp; + path_hook_list[path_hook_count++] = hook; + + hr = S_OK; + +end: + LeaveCriticalSection(&path_hook_lock); + + return hr; +} + +static void path_hook_init(void) +{ + /* Init is not thread safe because API hook init is not thread safe blah + blah blah you know the drill by now. */ + + if (path_hook_initted) { + return; + } + + path_hook_initted = true; + InitializeCriticalSection(&path_hook_lock); + + path_hook_insert_hooks(NULL); +} + +void path_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "kernel32.dll", + path_hook_syms, + _countof(path_hook_syms)); +} + +static BOOL path_transform_a(char **out, const char *src) +{ + wchar_t *src_w; + size_t src_c; + wchar_t *dest_w; + char *dest_a; + size_t dest_s; + BOOL ok; + + assert(out != NULL); + + src_w = NULL; + dest_w = NULL; + dest_a = NULL; + *out = NULL; + + if (src == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + ok = FALSE; + + goto end; + } + + /* Widen the path */ + + mbstowcs_s(&src_c, NULL, 0, src, 0); + src_w = malloc(src_c * sizeof(wchar_t)); + + if (src_w == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + ok = FALSE; + + goto end; + } + + mbstowcs_s(NULL, src_w, src_c, src, src_c - 1); + + /* Try applying a path transform */ + + ok = path_transform_w(&dest_w, src_w); /* Take ownership! */ + + if (!ok || dest_w == NULL) { + goto end; + } + + /* Narrow the transformed path */ + + wcstombs_s(&dest_s, NULL, 0, dest_w, 0); + dest_a = malloc(dest_s * sizeof(char)); + + if (dest_a == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + ok = FALSE; + + goto end; + } + + wcstombs_s(NULL, dest_a, dest_s, dest_w, dest_s - 1); + + *out = dest_a; /* Relinquish ownership to caller! */ + ok = TRUE; + +end: + free(dest_w); + free(src_w); + + return ok; +} + +static BOOL path_transform_w(wchar_t **out, const wchar_t *src) +{ + BOOL ok; + HRESULT hr; + wchar_t *dest; + size_t dest_c; + size_t i; + + assert(out != NULL); + + dest = NULL; + *out = NULL; + + EnterCriticalSection(&path_hook_lock); + + for (i = 0 ; i < path_hook_count ; i++) { + hr = path_hook_list[i](src, NULL, &dest_c); + + if (FAILED(hr)) { + ok = hr_propagate_win32(hr, FALSE); + + goto end; + } + + if (hr == S_FALSE) { + continue; + } + + dest = malloc(dest_c * sizeof(wchar_t)); + + if (dest == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + ok = FALSE; + + goto end; + } + + hr = path_hook_list[i](src, dest, &dest_c); + + if (FAILED(hr)) { + ok = hr_propagate_win32(hr, FALSE); + + goto end; + } + + break; + } + + *out = dest; + dest = NULL; + ok = TRUE; + +end: + LeaveCriticalSection(&path_hook_lock); + + free(dest); + + return ok; +} + +int path_compare_w(const wchar_t *string1, const wchar_t *string2, size_t count) +{ + size_t i; + wchar_t c1, c2; + + assert(string1 != NULL); + assert(string2 != NULL); + + for (i = 0; i < count && string1[i] && string2[i]; i++) { + c1 = towlower(string1[i]); + + if (c1 == '/') { + c1 = '\\'; + } + + c2 = towlower(string2[i]); + + if (c2 == '/') { + c2 = '\\'; + } + + if (c1 != c2) { + break; + } + } + + return i == count ? 0 : string2[i] - string1[i]; +} + +/* Dumping ground for kernel32 file system ops whose path parameters we have to + hook into and translate. This list will grow over time as we go back and + fix up older games that don't pay attention to the mount point registry. */ + +static BOOL WINAPI hook_CreateDirectoryA( + const char *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_CreateDirectoryA( + trans ? trans : lpFileName, + lpSecurityAttributes); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_CreateDirectoryW( + const wchar_t *lpFileName, + SECURITY_ATTRIBUTES *lpSecurityAttributes) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_CreateDirectoryW( + trans ? trans : lpFileName, + lpSecurityAttributes); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_CreateDirectoryExA( + const char *lpTemplateDirectory, + const char *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, lpNewDirectory); + + if (!ok) { + return FALSE; + } + + ok = next_CreateDirectoryExA( + lpTemplateDirectory, + trans ? trans : lpNewDirectory, + lpSecurityAttributes); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_CreateDirectoryExW( + const wchar_t *lpTemplateDirectory, + const wchar_t *lpNewDirectory, + SECURITY_ATTRIBUTES *lpSecurityAttributes) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, lpNewDirectory); + + if (!ok) { + return FALSE; + } + + ok = next_CreateDirectoryExW( + lpTemplateDirectory, + trans ? trans : lpNewDirectory, + lpSecurityAttributes); + + free(trans); + + return ok; +} + +/* Don't pull in the entire iohook framework just for CreateFileA/CreateFileW */ + +static HANDLE WINAPI hook_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + char *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_CreateFileA( + trans ? trans : lpFileName, + dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, + dwCreationDisposition, + dwFlagsAndAttributes, + hTemplateFile); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + wchar_t *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_CreateFileW( + trans ? trans : lpFileName, + dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, + dwCreationDisposition, + dwFlagsAndAttributes, + hTemplateFile); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_FindFirstFileA( + const char *lpFileName, + LPWIN32_FIND_DATAA lpFindFileData) +{ + char *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_FindFirstFileA(trans ? trans : lpFileName, lpFindFileData); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_FindFirstFileW( + const wchar_t *lpFileName, + LPWIN32_FIND_DATAW lpFindFileData) +{ + wchar_t *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_FindFirstFileW(trans ? trans : lpFileName, lpFindFileData); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_FindFirstFileExA( + const char *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags) +{ + char *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_FindFirstFileExA( + trans ? trans : lpFileName, + fInfoLevelId, + lpFindFileData, + fSearchOp, + lpSearchFilter, + dwAdditionalFlags); + + free(trans); + + return result; +} + +static HANDLE WINAPI hook_FindFirstFileExW( + const wchar_t *lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + void *lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + void *lpSearchFilter, + DWORD dwAdditionalFlags) +{ + wchar_t *trans; + HANDLE result; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_HANDLE_VALUE; + } + + result = next_FindFirstFileExW( + trans ? trans : lpFileName, + fInfoLevelId, + lpFindFileData, + fSearchOp, + lpSearchFilter, + dwAdditionalFlags); + + free(trans); + + return result; +} + +static DWORD WINAPI hook_GetFileAttributesA(const char *lpFileName) +{ + char *trans; + DWORD result; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_FILE_ATTRIBUTES; + } + + result = next_GetFileAttributesA(trans ? trans : lpFileName); + free(trans); + + return result; +} + +static DWORD WINAPI hook_GetFileAttributesW(const wchar_t *lpFileName) +{ + wchar_t *trans; + DWORD result; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_FILE_ATTRIBUTES; + } + + result = next_GetFileAttributesW(trans ? trans : lpFileName); + + free(trans); + + return result; +} + +static BOOL WINAPI hook_GetFileAttributesExA( + const char *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return INVALID_FILE_ATTRIBUTES; + } + + ok = next_GetFileAttributesExA( + trans ? trans : lpFileName, + fInfoLevelId, + lpFileInformation); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_GetFileAttributesExW( + const wchar_t *lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + void *lpFileInformation) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return INVALID_FILE_ATTRIBUTES; + } + + ok = next_GetFileAttributesExW( + trans ? trans : lpFileName, + fInfoLevelId, + lpFileInformation); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_RemoveDirectoryA(const char *lpFileName) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_RemoveDirectoryA(trans ? trans : lpFileName); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_RemoveDirectoryW(const wchar_t *lpFileName) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, lpFileName); + + if (!ok) { + return FALSE; + } + + ok = next_RemoveDirectoryW(trans ? trans : lpFileName); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_PathFileExistsA(LPCSTR pszPath) +{ + char *trans; + BOOL ok; + + ok = path_transform_a(&trans, pszPath); + + if (!ok) { + return FALSE; + } + + ok = next_PathFileExistsA(trans ? trans : pszPath); + + free(trans); + + return ok; +} + +static BOOL WINAPI hook_PathFileExistsW(LPCWSTR pszPath) +{ + wchar_t *trans; + BOOL ok; + + ok = path_transform_w(&trans, pszPath); + + if (!ok) { + return FALSE; + } + + ok = next_PathFileExistsW(trans ? trans : pszPath); + + free(trans); + + return ok; +} diff --git a/hooklib/path.h b/hooklib/path.h new file mode 100644 index 0000000..3a58bb7 --- /dev/null +++ b/hooklib/path.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +typedef HRESULT (*path_hook_t)( + const wchar_t *src, + wchar_t *dest, + size_t *count); + +HRESULT path_hook_push(path_hook_t hook); +void path_hook_insert_hooks(HMODULE target); +int path_compare_w(const wchar_t *string1, const wchar_t *string2, size_t count); + +static inline bool path_is_separator_w(wchar_t c) +{ + return c == L'\\' || c == L'/'; +} diff --git a/hooklib/reg.c b/hooklib/reg.c new file mode 100644 index 0000000..bc18dbb --- /dev/null +++ b/hooklib/reg.c @@ -0,0 +1,1054 @@ +#include + +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/reg.h" +#include "hook/procaddr.h" + +#include "util/dprintf.h" +#include "util/str.h" + +struct reg_hook_key { + HKEY root; + const wchar_t *name; + const struct reg_hook_val *vals; + size_t nvals; + HKEY handle; +}; + +/* Helper functions */ + +static void reg_hook_init(void); + +static LRESULT reg_hook_propagate_hr(HRESULT hr); + +static struct reg_hook_key *reg_hook_match_key_locked(HKEY handle); + +static const struct reg_hook_val *reg_hook_match_val_locked( + struct reg_hook_key *key, + const wchar_t *name); + +static LSTATUS reg_hook_open_locked( + HKEY parent, + const wchar_t *name, + HKEY *out); + +static LSTATUS reg_hook_query_val_locked( + struct reg_hook_key *key, + const wchar_t *name, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +/* API hooks */ + +static LSTATUS WINAPI hook_RegOpenKeyExW( + HKEY parent, + const wchar_t *name, + uint32_t flags, + uint32_t access, + HKEY *out); + +static LSTATUS WINAPI hook_RegCreateKeyExW( + HKEY parent, + const wchar_t *name, + uint32_t reserved, + const wchar_t *class_, + uint32_t options, + uint32_t access, + const SECURITY_ATTRIBUTES *sa, + HKEY *out, + uint32_t *disposition); + +static LSTATUS WINAPI hook_RegCloseKey(HKEY handle); + +static LSTATUS WINAPI hook_RegQueryValueExA( + HKEY handle, + const char *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS WINAPI hook_RegQueryValueExW( + HKEY handle, + const wchar_t *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS WINAPI hook_RegSetValueExW( + HKEY handle, + const wchar_t *name, + uint32_t reserved, + uint32_t type, + const void *bytes, + uint32_t nbytes); + +static LSTATUS WINAPI hook_RegGetValueW( + HKEY hkey, + LPCWSTR lpSubKey, + LPCWSTR lpValue, + uint32_t flags, + uint32_t *type, + void *pData, + uint32_t *numData +); + +static LSTATUS WINAPI hook_RegQueryInfoKeyW( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime); + +static LSTATUS WINAPI hook_RegEnumValueW( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData); +/* Link pointers */ + +static LSTATUS (WINAPI *next_RegOpenKeyExW)( + HKEY parent, + const wchar_t *name, + uint32_t flags, + uint32_t access, + HKEY *out); + +static LSTATUS (WINAPI *next_RegCreateKeyExW)( + HKEY parent, + const wchar_t *name, + uint32_t reserved, + const wchar_t *class_, + uint32_t options, + uint32_t access, + const SECURITY_ATTRIBUTES *sa, + HKEY *out, + uint32_t *disposition); + +static LSTATUS (WINAPI *next_RegCloseKey)(HKEY handle); + +static LSTATUS (WINAPI *next_RegQueryValueExA)( + HKEY handle, + const char *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS (WINAPI *next_RegQueryValueExW)( + HKEY handle, + const wchar_t *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes); + +static LSTATUS (WINAPI *next_RegSetValueExW)( + HKEY handle, + const wchar_t *name, + uint32_t reserved, + uint32_t type, + const void *bytes, + uint32_t nbytes); + +static LSTATUS (WINAPI *next_RegGetValueW)( + HKEY hkey, + LPCWSTR lpSubKey, + LPCWSTR lpValue, + uint32_t flags, + uint32_t *type, + void *pData, + uint32_t *numData +); + +static LSTATUS (WINAPI *next_RegQueryInfoKeyW)( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime); + +static LSTATUS (WINAPI *next_RegEnumValueW)( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData); + +static const struct hook_symbol reg_hook_syms[] = { + { + .name = "RegOpenKeyExW", + .patch = hook_RegOpenKeyExW, + .link = (void **) &next_RegOpenKeyExW, + }, { + .name = "RegCreateKeyExW", + .patch = hook_RegCreateKeyExW, + .link = (void **) &next_RegCreateKeyExW, + }, { + .name = "RegCloseKey", + .patch = hook_RegCloseKey, + .link = (void **) &next_RegCloseKey, + }, { + .name = "RegQueryValueExA", + .patch = hook_RegQueryValueExA, + .link = (void **) &next_RegQueryValueExA, + }, { + .name = "RegQueryValueExW", + .patch = hook_RegQueryValueExW, + .link = (void **) &next_RegQueryValueExW, + }, { + .name = "RegSetValueExW", + .patch = hook_RegSetValueExW, + .link = (void **) &next_RegSetValueExW, + }, { + .name = "RegGetValueW", + .patch = hook_RegGetValueW, + .link = (void **) &next_RegGetValueW, + }, { + .name = "RegQueryInfoKeyW", + .patch = hook_RegQueryInfoKeyW, + .link = (void **) &next_RegQueryInfoKeyW, + }, { + .name = "RegEnumValueW", + .patch = hook_RegEnumValueW, + .link = (void **) &next_RegEnumValueW, + } +}; + +static bool reg_hook_initted; +static CRITICAL_SECTION reg_hook_lock; +static struct reg_hook_key *reg_hook_keys; +static size_t reg_hook_nkeys; + +HRESULT reg_hook_push_key( + HKEY root, + const wchar_t *name, + const struct reg_hook_val *vals, + size_t nvals) +{ + struct reg_hook_key *new_mem; + struct reg_hook_key *new_key; + HRESULT hr; + + assert(root != NULL); + assert(name != NULL); + assert(vals != NULL || nvals == 0); + + reg_hook_init(); + + /*dprintf("Pushing reg key %ls:\n", name); + + for (int i = 0; i < nvals; i++) { + dprintf("\t%ls\n", vals[i].name); + } */ + + EnterCriticalSection(®_hook_lock); + + new_mem = realloc( + reg_hook_keys, + (reg_hook_nkeys + 1) * sizeof(struct reg_hook_key)); + + if (new_mem == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + new_key = &new_mem[reg_hook_nkeys]; + memset(new_key, 0, sizeof(*new_key)); + new_key->root = root; + new_key->name = name; /* Expect this to be statically allocated */ + new_key->vals = vals; + new_key->nvals = nvals; + + reg_hook_keys = new_mem; + reg_hook_nkeys++; + + hr = S_OK; + +end: + LeaveCriticalSection(®_hook_lock); + + return hr; +} + +static void reg_hook_init(void) +{ + if (reg_hook_initted) { + return; + } + + reg_hook_initted = true; + InitializeCriticalSection(®_hook_lock); + dprintf("Reg hook init\n"); + + reg_hook_insert_hooks(NULL); + + proc_addr_table_push( + NULL, + "ADVAPI32.dll", + (struct hook_symbol *) reg_hook_syms, + _countof(reg_hook_syms)); + +} + +void reg_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "advapi32.dll", + reg_hook_syms, + _countof(reg_hook_syms)); + +} + +static LRESULT reg_hook_propagate_hr(HRESULT hr) +{ + if (SUCCEEDED(hr)) { + return ERROR_SUCCESS; + } else if (HRESULT_FACILITY(hr) == FACILITY_WIN32) { + return HRESULT_CODE(hr); + } else { + return ERROR_GEN_FAILURE; + } +} + +static struct reg_hook_key *reg_hook_match_key_locked(HKEY handle) +{ + struct reg_hook_key *key; + size_t i; + + if (handle == NULL || handle == INVALID_HANDLE_VALUE) { + return NULL; + } + + for (i = 0 ; i < reg_hook_nkeys ; i++) { + key = ®_hook_keys[i]; + + if (key->handle == handle) { + return key; + } + } + + return NULL; +} + +static const struct reg_hook_val *reg_hook_match_val_locked( + struct reg_hook_key *key, + const wchar_t *name) +{ + const struct reg_hook_val *val; + size_t i; + + /* Watch out for accesses to the key's default value */ + + if (name == NULL) { + name = L""; + } + + for (i = 0 ; i < key->nvals ; i++) { + val = &key->vals[i]; + + if (wstr_ieq(val->name, name)) { + return val; + } + } + + return NULL; +} + +static LSTATUS reg_hook_open_locked( + HKEY parent, + const wchar_t *name, + HKEY *out) +{ + struct reg_hook_key *key; + LSTATUS err; + size_t i; + + *out = NULL; + + for (i = 0 ; i < reg_hook_nkeys ; i++) { + /* Assume reg keys are referenced from a root key and not from some + intermediary key */ + key = ®_hook_keys[i]; + //dprintf("Reg: %ls vs %ls\n", name, key->name); + + if (key->root == parent && wstr_ieq(key->name, name)) { + break; + } + } + + /* (Bail out if we didn't find anything; this causes the open/create call + to be passed onward down the hook chain) */ + + if (i >= reg_hook_nkeys) { + return ERROR_SUCCESS; + } + + /* Assume only one handle will be open at a time */ + + if (key->handle != NULL) { + return ERROR_SHARING_VIOLATION; + } + + /* Open a unique HKEY handle that we can use to identify accesses to + this virtual registry key. We open a read-only handle to an arbitrary + registry key that we can reliably assume exists and isn't one of the + hardcoded root handles. HKLM\SOFTWARE will suffice for this purpose. */ + + err = next_RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE", + 0, + KEY_READ, + out); + + if (err == ERROR_SUCCESS) { + key->handle = *out; + } + + return err; +} + +static LSTATUS WINAPI hook_RegOpenKeyExW( + HKEY parent, + const wchar_t *name, + uint32_t flags, + uint32_t access, + HKEY *out) +{ + LSTATUS err; + + if (out == NULL) { + return ERROR_INVALID_PARAMETER; + } + + EnterCriticalSection(®_hook_lock); + err = reg_hook_open_locked(parent, name, out); + LeaveCriticalSection(®_hook_lock); + + if (err == ERROR_SUCCESS) { + if (*out != NULL) { + //dprintf("Registry: Opened virtual key %S\n", name); + } else { + err = next_RegOpenKeyExW(parent, name, flags, access, out); + } + } + + return err; +} + +static LSTATUS WINAPI hook_RegCreateKeyExW( + HKEY parent, + const wchar_t *name, + uint32_t reserved, + const wchar_t *class_, + uint32_t options, + uint32_t access, + const SECURITY_ATTRIBUTES *sa, + HKEY *out, + uint32_t *disposition) +{ + LSTATUS err; + + if (out == NULL) { + return ERROR_INVALID_PARAMETER; + } + + EnterCriticalSection(®_hook_lock); + err = reg_hook_open_locked(parent, name, out); + LeaveCriticalSection(®_hook_lock); + + if (err == ERROR_SUCCESS) { + if (*out != NULL) { + //dprintf("Registry: Created virtual key %S\n", name); + } else { + err = next_RegCreateKeyExW( + parent, + name, + reserved, + class_, + options, + access, + sa, + out, + disposition); + } + } + + return err; +} + +static LSTATUS WINAPI hook_RegCloseKey(HKEY handle) +{ + struct reg_hook_key *key; + size_t i; + + EnterCriticalSection(®_hook_lock); + + for (i = 0 ; i < reg_hook_nkeys ; i++) { + key = ®_hook_keys[i]; + + if (key->handle == handle) { + //dprintf("Registry: Closed virtual key %S\n", key->name); + key->handle = NULL; + } + } + + LeaveCriticalSection(®_hook_lock); + + return next_RegCloseKey(handle); +} + +static LSTATUS WINAPI hook_RegQueryValueExW( + HKEY handle, + const wchar_t *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes) +{ + struct reg_hook_key *key; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(handle); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegQueryValueExW( + handle, + name, + reserved, + type, + bytes, + nbytes); + } + + /* Call the factored out core of this function because RegQueryValueExA + has to be a blight upon my existence */ + + err = reg_hook_query_val_locked(key, name, type, bytes, nbytes); + + LeaveCriticalSection(®_hook_lock); + + return err; +} + +/* now this right here is a pain in my ass */ + +static LSTATUS WINAPI hook_RegQueryValueExA( + HKEY handle, + const char *name, + void *reserved, + uint32_t *type, + void *bytes, + uint32_t *nbytes) +{ + /* _s: sizeof, _c: _countof(), _w: widened */ + + struct reg_hook_key *key; + wchar_t *name_w; + size_t name_c; + wchar_t *content; + uint32_t content_s; + size_t content_c; + uint32_t type_site; + LSTATUS err; + + name_w = NULL; + content = NULL; + + /* Normalize inconvenient inputs */ + + if (name == NULL) { + name = ""; + } + + if (type == NULL) { + type = &type_site; + } + + /* Look up key handle, early exit if no match */ + + EnterCriticalSection(®_hook_lock); + key = reg_hook_match_key_locked(handle); + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegQueryValueExA( + handle, + name, + reserved, + type, + bytes, + nbytes); + } + + /* OK, first off we need to widen the name. This requires a temporary + buffer allocation. */ + + mbstowcs_s(&name_c, NULL, 0, name, 0); + name_w = malloc(name_c * sizeof(wchar_t)); + + if (name_w == NULL) { + err = ERROR_OUTOFMEMORY; + + goto end; + } + + mbstowcs_s(NULL, name_w, name_c, name, name_c - 1); + + /* Next, check to see if the caller even cares about the content. We can + pass through if they don't. */ + + if (bytes == NULL && nbytes == NULL) { + err = reg_hook_query_val_locked(key, name_w, type, NULL, NULL); + + goto end; + } + + /* Next, we need to check the key type to see if it's REG_SZ. */ + + err = reg_hook_query_val_locked(key, name_w, type, NULL, NULL); + + if (err != ERROR_SUCCESS) { + goto end; + } + + /* If it is not REG_SZ then pass the content directly. + (We ignore the REG_MULTI_SZ case here). */ + + assert(*type != REG_MULTI_SZ); + + if (*type != REG_SZ) { + err = reg_hook_query_val_locked(key, name_w, type, bytes, nbytes); + + goto end; + } + + /* Otherwise things get more complicated. First we must measure the wide- + character length of the value (hopefully said value does not change + under our feet, of course). */ + + err = reg_hook_query_val_locked(key, name_w, type, NULL, &content_s); + + if (err != ERROR_SUCCESS) { + goto end; + } + + /* Next, allocate a scratch buffer. Even if the caller doesn't supply an + output buffer we need to know the actual content to be able to size the + narrow version. */ + + content = malloc(content_s); + + if (content == NULL) { + err = ERROR_OUTOFMEMORY; + + goto end; + } + + /* Get the data... */ + + err = reg_hook_query_val_locked(key, name_w, type, content, &content_s); + + if (err != ERROR_SUCCESS) { + goto end; + } + + /* Now size the corresponding narrow form and return it to the caller */ + + wcstombs_s(&content_c, NULL, 0, content, 0); + + if (bytes != NULL) { + if (nbytes == NULL) { + err = ERROR_INVALID_PARAMETER; + + goto end; + } + + if (*nbytes < content_c) { + err = ERROR_MORE_DATA; + + goto end; + } + + wcstombs_s(NULL, bytes, *nbytes, content, content_c - 1); + } + + if (nbytes != NULL) { /* It really should be, based on earlier checks ... */ + *nbytes = content_c; + } + + err = ERROR_SUCCESS; + +end: + LeaveCriticalSection(®_hook_lock); + + free(content); + free(name_w); + + return err; +} + +static LSTATUS reg_hook_query_val_locked( + struct reg_hook_key *key, + const wchar_t *name, + uint32_t *type, + void *bytes, + uint32_t *nbytes) +{ + const struct reg_hook_val *val; + LSTATUS err; + HRESULT hr; + + val = reg_hook_match_val_locked(key, name); + + if (val != NULL) { + if (type != NULL) { + *type = val->type; + } + + if (val->read != NULL) { + hr = val->read(bytes, nbytes); + err = reg_hook_propagate_hr(hr); + } else { + dprintf("Registry: %S: Val %S has no read handler\n", + key->name, + name); + + err = ERROR_ACCESS_DENIED; + } + } else { + dprintf("Registry: Key %S: Val %S not found\n", key->name, name); + err = ERROR_FILE_NOT_FOUND; + } + + return err; +} + +static LSTATUS WINAPI hook_RegSetValueExW( + HKEY handle, + const wchar_t *name, + uint32_t reserved, + uint32_t type, + const void *bytes, + uint32_t nbytes) +{ + struct reg_hook_key *key; + const struct reg_hook_val *val; + LSTATUS err; + HRESULT hr; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(handle); + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegSetValueExW( + handle, + name, + reserved, + type, + bytes, + nbytes); + } + + val = reg_hook_match_val_locked(key, name); + + if (val != NULL) { + if (val->write != NULL) { + if (type != val->type) { + dprintf( "Registry: Key %S: Val %S: Type mismatch " + "(expected %i got %i)\n", + key->name, + name, + val->type, + type); + + err = ERROR_ACCESS_DENIED; + } else { + dprintf("Registry: Write virtual key %S value %S\n", + key->name, + val->name); + + hr = val->write(bytes, nbytes); + err = reg_hook_propagate_hr(hr); + } + } else { + /* No write handler (the common case), black-hole whatever gets + written. */ + + err = ERROR_SUCCESS; + } + } else { + dprintf("Registry: Key %S: Val %S not found\n", key->name, name); + err = ERROR_FILE_NOT_FOUND; + } + + LeaveCriticalSection(®_hook_lock); + + return err; +} + +static LSTATUS WINAPI hook_RegGetValueW( + HKEY handle, + LPCWSTR subkey, + LPCWSTR name, + uint32_t flags, + uint32_t *type, + void *pData, + uint32_t *numData) +{ + struct reg_hook_key *key; + HKEY tmp = NULL; + const struct reg_hook_val *val; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + //dprintf("Registry: RegGetValueW lookup for %ls\\%ls\n", subkey, name); + + if (subkey == NULL) { + key = reg_hook_match_key_locked(handle); + } else { + err = hook_RegOpenKeyExW(handle, subkey, flags, 1, &tmp); + key = reg_hook_match_key_locked(tmp); + } + + //dprintf("Registry: RegGetValueW key is %ls", key->name); + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + dprintf("Registry: RegGetValueW Failed to find %ls\\%ls, passing on\n", subkey, name); + + return next_RegGetValueW( + handle, + subkey, + name, + flags, + type, + pData, + numData); + } + + val = reg_hook_match_val_locked(key, name); + + if (val != NULL) { + //dprintf("Registry: RegGetValueW found %ls\\%ls!\n", subkey, name); + + if (val->read != NULL) { + val->read(pData, numData); + + if (tmp != NULL) { + hook_RegCloseKey(tmp); + } + + LeaveCriticalSection(®_hook_lock); + err = ERROR_SUCCESS; + return err; + } + } + + LeaveCriticalSection(®_hook_lock); + err = ERROR_NOT_FOUND; + return err; +} + +static LSTATUS WINAPI hook_RegQueryInfoKeyW( + HKEY hKey, + LPWSTR lpClass, + LPDWORD lpcchClass, + LPDWORD lpReserved, + LPDWORD lpcSubKeys, + LPDWORD lpcbMaxSubKeyLen, + LPDWORD lpcbMaxClassLen, + LPDWORD lpcValues, + LPDWORD lpcbMaxValueNameLen, + LPDWORD lpcbMaxValueLen, + LPDWORD lpcbSecurityDescriptor, + PFILETIME lpftLastWriteTime) +{ + struct reg_hook_key *key; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(hKey); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegQueryInfoKeyW( + hKey, + lpClass, + lpcchClass, + lpReserved, + lpcSubKeys, + lpcbMaxSubKeyLen, + lpcbMaxClassLen, + lpcValues, + lpcbMaxValueNameLen, + lpcbMaxValueLen, + lpcbSecurityDescriptor, + lpftLastWriteTime); + } + + // This is the only one I've seen even be changed, so it's all I'm doing + // until I see otherwise. + *lpcValues = key->nvals; + LeaveCriticalSection(®_hook_lock); + return ERROR_SUCCESS; +} + +static LSTATUS WINAPI hook_RegEnumValueW( + HKEY hkey, + DWORD dwIndex, + LPWSTR lpValueName, + LPDWORD lpcchValueName, + LPDWORD lpReserved, + LPDWORD lpType, + LPBYTE lpData, + LPDWORD lpcbData) +{ + struct reg_hook_key *key; + HRESULT hr; + LSTATUS err; + + EnterCriticalSection(®_hook_lock); + + key = reg_hook_match_key_locked(hkey); + + /* Check if this is a virtualized registry key */ + + if (key == NULL) { + LeaveCriticalSection(®_hook_lock); + + return next_RegEnumValueW( + hkey, + dwIndex, + lpValueName, + lpcchValueName, + lpReserved, + lpType, + lpData, + lpcbData); + } + + if (dwIndex >= key->nvals) { + LeaveCriticalSection(®_hook_lock); + return ERROR_NO_MORE_ITEMS; // Pretty sure this is what it actually returns here? + } + + wcscpy_s(lpValueName, *lpcchValueName, key->vals[dwIndex].name); + *lpcchValueName = wcslen(key->vals[dwIndex].name); + LeaveCriticalSection(®_hook_lock); + return ERROR_SUCCESS; +} + +HRESULT reg_hook_read_bin( + void *bytes, + uint32_t *nbytes, + const void *src_bytes, + size_t src_nbytes) +{ + assert(src_bytes != NULL || src_nbytes == 0); + + if (bytes != NULL) { + if (nbytes == NULL || *nbytes < src_nbytes) { + return HRESULT_FROM_WIN32(ERROR_MORE_DATA); + } + + memcpy(bytes, src_bytes, src_nbytes); + } + + if (nbytes != NULL) { + *nbytes = src_nbytes; + } + + return S_OK; +} + +HRESULT reg_hook_read_u32( + void *bytes, + uint32_t *nbytes, + uint32_t src) +{ + if (bytes != NULL) { + if (nbytes == NULL || *nbytes < sizeof(uint32_t)) { + return HRESULT_FROM_WIN32(ERROR_MORE_DATA); + } + + memcpy(bytes, &src, sizeof(uint32_t)); + } + + if (nbytes != NULL) { + *nbytes = sizeof(uint32_t); + } + + return S_OK; +} + +HRESULT reg_hook_read_wstr( + void *bytes, + uint32_t *nbytes, + const wchar_t *src) +{ + size_t src_nbytes; + + assert(src != NULL); + + src_nbytes = (wcslen(src) + 1) * sizeof(wchar_t); + + if (bytes != NULL) { + if (nbytes == NULL || *nbytes < src_nbytes) { + return HRESULT_FROM_WIN32(ERROR_MORE_DATA); + } + + memcpy(bytes, src, src_nbytes); + } + + if (nbytes != NULL) { + *nbytes = src_nbytes; + } + + return S_OK; +} diff --git a/hooklib/reg.h b/hooklib/reg.h new file mode 100644 index 0000000..20e3dda --- /dev/null +++ b/hooklib/reg.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include +#include + +struct reg_hook_val { + const wchar_t *name; + HRESULT (*read)(void *bytes, uint32_t *nbytes); + HRESULT (*write)(const void *bytes, uint32_t nbytes); + uint32_t type; +}; + +void reg_hook_insert_hooks(HMODULE target); + +HRESULT reg_hook_push_key( + HKEY root, + const wchar_t *name, + const struct reg_hook_val *vals, + size_t nvals); + +HRESULT reg_hook_read_bin( + void *bytes, + uint32_t *nbytes, + const void *src_bytes, + size_t src_nbytes); + +HRESULT reg_hook_read_u32( + void *bytes, + uint32_t *nbytes, + uint32_t src); + +HRESULT reg_hook_read_wstr( + void *bytes, + uint32_t *nbytes, + const wchar_t *src); diff --git a/hooklib/setupapi.c b/hooklib/setupapi.c new file mode 100644 index 0000000..edd416a --- /dev/null +++ b/hooklib/setupapi.c @@ -0,0 +1,340 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" + +struct setupapi_class { + const GUID *guid; + const wchar_t *path; + HDEVINFO cur_handle; +}; + +static void setupapi_hook_init(void); + +/* API hooks */ + +static HDEVINFO WINAPI my_SetupDiGetClassDevsW( + const GUID *ClassGuid, + wchar_t *Enumerator, + HWND hwndParent, + DWORD Flags); + +static BOOL WINAPI my_SetupDiEnumDeviceInterfaces( + HDEVINFO DeviceInfoSet, + SP_DEVINFO_DATA *DeviceInfoData, + const GUID *InterfaceClassGuid, + DWORD MemberIndex, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData); + +static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailW( + HDEVINFO DeviceInfoSet, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + DWORD *RequiredSize, + SP_DEVINFO_DATA *DeviceInfoData); + +static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet); + +/* Links */ + +static HDEVINFO (WINAPI *next_SetupDiGetClassDevsW)( + const GUID *ClassGuid, + wchar_t *Enumerator, + HWND hwndParent, + DWORD Flags); + +static BOOL (WINAPI *next_SetupDiEnumDeviceInterfaces)( + HDEVINFO DeviceInfoSet, + SP_DEVINFO_DATA *DeviceInfoData, + const GUID *InterfaceClassGuid, + DWORD MemberIndex, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData); + +static BOOL (WINAPI *next_SetupDiGetDeviceInterfaceDetailW)( + HDEVINFO DeviceInfoSet, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + DWORD *RequiredSize, + SP_DEVINFO_DATA *DeviceInfoData); + +static BOOL (WINAPI *next_SetupDiDestroyDeviceInfoList)(HDEVINFO DeviceInfoSet); + +/* Hook tbl */ + +static const struct hook_symbol setupapi_syms[] = { + { + .name = "SetupDiGetClassDevsW", + .patch = my_SetupDiGetClassDevsW, + .link = (void *) &next_SetupDiGetClassDevsW, + }, { + .name = "SetupDiEnumDeviceInterfaces", + .patch = my_SetupDiEnumDeviceInterfaces, + .link = (void *) &next_SetupDiEnumDeviceInterfaces, + }, { + .name = "SetupDiGetDeviceInterfaceDetailW", + .patch = my_SetupDiGetDeviceInterfaceDetailW, + .link = (void *) &next_SetupDiGetDeviceInterfaceDetailW, + }, { + .name = "SetupDiDestroyDeviceInfoList", + .patch = my_SetupDiDestroyDeviceInfoList, + .link = (void *) &next_SetupDiDestroyDeviceInfoList, + } +}; + +static bool setupapi_initted; +static CRITICAL_SECTION setupapi_lock; +static struct setupapi_class *setupapi_classes; +static size_t setupapi_nclasses; + +HRESULT setupapi_add_phantom_dev(const GUID *iface_class, const wchar_t *path) +{ + struct setupapi_class *class_; + struct setupapi_class *new_array; + HRESULT hr; + + assert(iface_class != NULL); + assert(path != NULL); + + setupapi_hook_init(); + + EnterCriticalSection(&setupapi_lock); + + new_array = realloc( + setupapi_classes, + (setupapi_nclasses + 1) * sizeof(struct setupapi_class)); + + if (new_array == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + setupapi_classes = new_array; + + class_ = &setupapi_classes[setupapi_nclasses++]; + class_->guid = iface_class; + class_->path = path; + hr = S_OK; + +end: + LeaveCriticalSection(&setupapi_lock); + + return hr; +} + +static void setupapi_hook_init() +{ + if (setupapi_initted) { + return; + } + + setupapi_hook_insert_hooks(NULL); + + InitializeCriticalSection(&setupapi_lock); + setupapi_initted = true; +} + +void setupapi_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "setupapi.dll", + setupapi_syms, + _countof(setupapi_syms)); +} + +static HDEVINFO WINAPI my_SetupDiGetClassDevsW( + const GUID *ClassGuid, + wchar_t *Enumerator, + HWND hwndParent, + DWORD Flags) +{ + struct setupapi_class *class_; + HDEVINFO result; + size_t i; + + result = next_SetupDiGetClassDevsW( + ClassGuid, + Enumerator, + hwndParent, + Flags); + + if (result == INVALID_HANDLE_VALUE || ClassGuid == NULL) { + return result; + } + + EnterCriticalSection(&setupapi_lock); + + for (i = 0 ; i < setupapi_nclasses ; i++) { + class_ = &setupapi_classes[i]; + if (memcmp(ClassGuid, class_->guid, sizeof(*ClassGuid)) == 0) { + class_->cur_handle = result; + } + } + + LeaveCriticalSection(&setupapi_lock); + + return result; +} + +static BOOL WINAPI my_SetupDiEnumDeviceInterfaces( + HDEVINFO DeviceInfoSet, + SP_DEVINFO_DATA *DeviceInfoData, + const GUID *InterfaceClassGuid, + DWORD MemberIndex, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData) +{ + const struct setupapi_class *class_; + size_t i; + + if ( DeviceInfoSet == INVALID_HANDLE_VALUE || + DeviceInterfaceData == NULL || + DeviceInterfaceData->cbSize != sizeof(*DeviceInterfaceData)) { + goto pass; + } + + if (MemberIndex > 0) { + MemberIndex--; + + goto pass; + } + + EnterCriticalSection(&setupapi_lock); + + for ( i = 0, class_ = NULL ; + i < setupapi_nclasses && class_ == NULL ; + i++) { + if (DeviceInfoSet == setupapi_classes[i].cur_handle) { + class_ = &setupapi_classes[i]; + + dprintf("SetupAPI: Interface {%08lx-...} -> Device node %S\n", + class_->guid->Data1, + class_->path); + + memcpy( &DeviceInterfaceData->InterfaceClassGuid, + class_->guid, + sizeof(GUID)); + DeviceInterfaceData->Flags = SPINT_ACTIVE; + DeviceInterfaceData->Reserved = (ULONG_PTR) class_->path; + } + } + + LeaveCriticalSection(&setupapi_lock); + + if (class_ == NULL) { + goto pass; + } + + SetLastError(ERROR_SUCCESS); + + return TRUE; + +pass: + return next_SetupDiEnumDeviceInterfaces( + DeviceInfoSet, + DeviceInfoData, + InterfaceClassGuid, + MemberIndex, + DeviceInterfaceData); +} + +static BOOL WINAPI my_SetupDiGetDeviceInterfaceDetailW( + HDEVINFO DeviceInfoSet, + SP_DEVICE_INTERFACE_DATA *DeviceInterfaceData, + SP_DEVICE_INTERFACE_DETAIL_DATA_W *DeviceInterfaceDetailData, + DWORD DeviceInterfaceDetailDataSize, + DWORD *RequiredSize, + SP_DEVINFO_DATA *DeviceInfoData) +{ + const wchar_t *wstr; + size_t nbytes_wstr; + size_t nbytes_total; + size_t i; + bool match; + + if (DeviceInfoSet == INVALID_HANDLE_VALUE || DeviceInterfaceData == NULL) { + goto pass; + } + + EnterCriticalSection(&setupapi_lock); + + for ( i = 0, match = false ; + i < setupapi_nclasses && !match ; + i++) { + if ( DeviceInfoSet == setupapi_classes[i].cur_handle && + DeviceInterfaceData->Reserved == (ULONG_PTR) setupapi_classes[i].path) { + match = true; + } + } + + LeaveCriticalSection(&setupapi_lock); + + if (!match) { + goto pass; + } + + wstr = (const wchar_t *) DeviceInterfaceData->Reserved; + nbytes_wstr = (wcslen(wstr) + 1) * sizeof(wchar_t); + nbytes_total = offsetof(SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath); + nbytes_total += nbytes_wstr; + + if (RequiredSize != NULL) { + *RequiredSize = (DWORD) nbytes_total; + } + + if ( DeviceInterfaceDetailData == NULL && + DeviceInterfaceDetailDataSize < nbytes_total) { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + + return FALSE; + } + + if (DeviceInterfaceDetailData->cbSize!=sizeof(*DeviceInterfaceDetailData)) { + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + memcpy(DeviceInterfaceDetailData->DevicePath, wstr, nbytes_wstr); + SetLastError(ERROR_SUCCESS); + + return TRUE; + +pass: + return next_SetupDiGetDeviceInterfaceDetailW( + DeviceInfoSet, + DeviceInterfaceData, + DeviceInterfaceDetailData, + DeviceInterfaceDetailDataSize, + RequiredSize, + DeviceInfoData); +} + +static BOOL WINAPI my_SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet) +{ + size_t i; + + EnterCriticalSection(&setupapi_lock); + + for (i = 0 ; i < setupapi_nclasses ; i++) { + if (setupapi_classes[i].cur_handle == DeviceInfoSet) { + setupapi_classes[i].cur_handle = NULL; + } + } + + LeaveCriticalSection(&setupapi_lock); + + return next_SetupDiDestroyDeviceInfoList(DeviceInfoSet); +} diff --git a/hooklib/setupapi.h b/hooklib/setupapi.h new file mode 100644 index 0000000..94eea13 --- /dev/null +++ b/hooklib/setupapi.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include + +HRESULT setupapi_add_phantom_dev(const GUID *iface_class, const wchar_t *path); +void setupapi_hook_insert_hooks(HMODULE target); diff --git a/hooklib/spike.c b/hooklib/spike.c new file mode 100644 index 0000000..f06166c --- /dev/null +++ b/hooklib/spike.c @@ -0,0 +1,249 @@ +#include + +#include +#include +#include +#include +#include + +#include "hook/pe.h" + +#include "hooklib/spike.h" + +#include "util/dprintf.h" + +static void spike_hook_read_config(const wchar_t *spike_file); + +/* Spike functions. Their "style" is named after the libc function they bear + the closest resemblance to. */ + +static void spike_fn_puts(const char *msg) +{ + char line[512]; + + sprintf_s(line, _countof(line), "%s\n", msg); + OutputDebugStringA(line); +} + +static void spike_fn_fputs(const char *msg) +{ + OutputDebugStringA(msg); +} + +static void spike_fn_printf(const char *fmt, ...) +{ + char line[512]; + va_list ap; + + va_start(ap, fmt); + vsprintf_s(line, _countof(line), fmt, ap); + strcat(line, "\n"); + OutputDebugStringA(line); +} + +static void spike_fn_vprintf( + const char *proc, + int line_no, + const char *fmt, + va_list ap) +{ + char msg[512]; + char line[512]; + + vsprintf_s(msg, _countof(msg), fmt, ap); + sprintf_s(line, _countof(line), "%s:%i: %s", proc, line_no, msg); + OutputDebugStringA(line); +} + +static void spike_fn_vwprintf( + const wchar_t *proc, + int line_no, + const wchar_t *fmt, + va_list ap) +{ + wchar_t msg[512]; + wchar_t line[512]; + + vswprintf_s(msg, _countof(msg), fmt, ap); + swprintf_s(line, _countof(line), L"%s:%i: %s", proc, line_no, msg); + OutputDebugStringW(line); +} + +static void spike_fn_perror( + int a1, + int a2, + int error, + const char *file, + int line_no, + const char *msg) +{ + char line[512]; + + sprintf_s( + line, + _countof(line), + "%s:%i:%08x: %s\n", + file, + line_no, + error, + msg); + + OutputDebugStringA(line); +} + +/* Spike inserters */ + +static void spike_insert_jmp(ptrdiff_t rva, void *proc) +{ + uint8_t *base; + uint8_t *target; + uint8_t *func_ptr; + uint32_t delta; + + base = (uint8_t *) GetModuleHandleW(NULL); + + target = base + rva; + func_ptr = proc; + delta = func_ptr - target - 4; /* -4: EIP delta, after end of target insn */ + + pe_patch(target, &delta, sizeof(delta)); +} + +static void spike_insert_ptr(ptrdiff_t rva, void *ptr) +{ + uint8_t *base; + uint8_t *target; + + base = (uint8_t *) GetModuleHandleW(NULL); + target = base + rva; + + pe_patch(target, &ptr, sizeof(ptr)); +} + +static void spike_insert_log_levels(ptrdiff_t rva, size_t count) +{ + uint8_t *base; + uint32_t *levels; + size_t i; + + base = (uint8_t *) GetModuleHandleW(NULL); + levels = (uint32_t *) (base + rva); + + for (i = 0 ; i < count ; i++) { + levels[i] = 255; + } +} + +/* Config reader */ + +void spike_hook_init(const wchar_t *ini_file) +{ + wchar_t module[MAX_PATH]; + wchar_t path[MAX_PATH]; + const wchar_t *basename; + const wchar_t *slash; + + assert(ini_file != NULL); + + /* Get the filename (strip path) of the host EXE */ + + GetModuleFileNameW(NULL, module, _countof(module)); + slash = wcsrchr(module, L'\\'); + + if (slash != NULL) { + basename = slash + 1; + } else { + basename = module; + } + + /* Check our INI file to see if any spikes are configured for this EXE. + Normally we separate out config reading into a separate module... */ + + GetPrivateProfileStringW( + L"spike", + basename, + L"", + path, + _countof(path), + ini_file); + + if (path[0] != L'\0') { + dprintf("Spiking %S using config from %S\n", basename, path); + spike_hook_read_config(path); + } +} + +static void spike_hook_read_config(const wchar_t *spike_file) +{ + int match; + int count; + int rva; + char line[80]; + char *ret; + FILE *f; + + f = _wfopen(spike_file, L"r"); + + if (f == NULL) { + dprintf("Error opening spike file %S\n", spike_file); + + return; + } + + for (;;) { + ret = fgets(line, sizeof(line), f); + + if (ret == NULL) { + break; + } + + if (line[0] == '#' || line[0] == '\r' || line[0] == '\n') { + continue; + } + + match = sscanf(line, "levels %i %i", &rva, &count); + + if (match == 2) { + spike_insert_log_levels((ptrdiff_t) rva, count); + } + + match = sscanf(line, "j_vprintf %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_vprintf); + } + + match = sscanf(line, "j_vwprintf %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_vwprintf); + } + + match = sscanf(line, "j_printf %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_printf); + } + + match = sscanf(line, "j_puts %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_puts); + } + + match = sscanf(line, "j_perror %i", &rva); + + if (match == 1) { + spike_insert_jmp((ptrdiff_t) rva, spike_fn_perror); + } + + match = sscanf(line, "c_fputs %i", &rva); /* c == "callback" */ + + if (match == 1) { + spike_insert_ptr((ptrdiff_t) rva, spike_fn_fputs); + } + } + + dprintf("Spike insertion complete\n"); + fclose(f); +} diff --git a/hooklib/spike.h b/hooklib/spike.h new file mode 100644 index 0000000..779d22f --- /dev/null +++ b/hooklib/spike.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void spike_hook_init(const wchar_t *ini_file); diff --git a/iccard/felica.c b/iccard/felica.c new file mode 100644 index 0000000..09e3a77 --- /dev/null +++ b/iccard/felica.c @@ -0,0 +1,196 @@ +#include + +#include +#include +#include + +#include "hook/iobuf.h" + +#include "iccard/felica.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +static HRESULT felica_cmd_poll( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_get_system_code( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +static HRESULT felica_cmd_nda_a4( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +uint64_t felica_get_generic_PMm(void) +{ + /* A FeliCa PMm contains low-level protocol timing information for + communicating with a particular IC card. The exact values are not + particularly important for our purposes, so we'll just return a hard- + coded PMm. This current value has been taken from an iPhone, emulating + a Suica pass via Apple Wallet, which seems to be one of the few + universally accepted FeliCa types for these games. Certain older + Suica passes and other payment and transportation cards + do not seem to be supported anymore. */ + + return 0x01168B868FBECBFFULL; +} + +HRESULT felica_transact( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + uint64_t IDm; + uint8_t code; + HRESULT hr; + + assert(f != NULL); + assert(req != NULL); + assert(res != NULL); + + hr = iobuf_read_8(req, &code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_8(res, code + 1); + + if (FAILED(hr)) { + return hr; + } + + if (code != FELICA_CMD_POLL) { + hr = iobuf_read_be64(req, &IDm); + + if (FAILED(hr)) { + return hr; + } + + if (IDm != f->IDm) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + hr = iobuf_write_be64(res, IDm); + + if (FAILED(hr)) { + return hr; + } + } + + switch (code) { + case FELICA_CMD_POLL: + return felica_cmd_poll(f, req, res); + + case FELICA_CMD_GET_SYSTEM_CODE: + return felica_cmd_get_system_code(f, req, res); + + case FELICA_CMD_NDA_A4: + return felica_cmd_nda_a4(f, req, res); + + default: + dprintf("FeliCa: Unimplemented command %02x, payload:\n", code); + dump_const_iobuf(req); + + return E_NOTIMPL; + } +} + +static HRESULT felica_cmd_poll( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + uint16_t system_code; + uint8_t request_code; + uint8_t time_slot; + HRESULT hr; + + /* Request: */ + + hr = iobuf_read_be16(req, &system_code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req, &request_code); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_8(req, &time_slot); + + if (FAILED(hr)) { + return hr; + } + + if (system_code != 0xFFFF && system_code != f->system_code) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + // TODO handle other params correctly... + + /* Response: */ + + hr = iobuf_write_be64(res, f->IDm); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be64(res, f->PMm); + + if (FAILED(hr)) { + return hr; + } + + if (request_code == 0x01) { + hr = iobuf_write_be16(res, f->system_code); + + if (FAILED(hr)) { + return hr; + } + } + + return S_OK; +} + +static HRESULT felica_cmd_get_system_code( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + HRESULT hr; + + hr = iobuf_write_8(res, 1); /* Number of system codes */ + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_write_be16(res, f->system_code); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT felica_cmd_nda_a4( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res) +{ + /* The specification for this command is probably only available under NDA. + Returning what the driver seems to want. */ + + return iobuf_write_8(res, 0); +} diff --git a/iccard/felica.h b/iccard/felica.h new file mode 100644 index 0000000..33be201 --- /dev/null +++ b/iccard/felica.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +enum { + FELICA_CMD_POLL = 0x00, + FELICA_CMD_GET_SYSTEM_CODE = 0x0c, + FELICA_CMD_NDA_A4 = 0xa4, +}; + +struct felica { + uint64_t IDm; + uint64_t PMm; + uint16_t system_code; +}; + +HRESULT felica_transact( + struct felica *f, + struct const_iobuf *req, + struct iobuf *res); + +uint64_t felica_get_generic_PMm(void); diff --git a/iccard/meson.build b/iccard/meson.build new file mode 100644 index 0000000..bca1a1d --- /dev/null +++ b/iccard/meson.build @@ -0,0 +1,16 @@ +iccard_lib = static_library( + 'iccard', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'nesica.c', + 'nesica.h', + 'felica.c', + 'felica.h', + 'mifare.h', + ], +) diff --git a/iccard/mifare.h b/iccard/mifare.h new file mode 100644 index 0000000..10dbc34 --- /dev/null +++ b/iccard/mifare.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +struct mifare_block { + uint8_t bytes[16]; +}; + +struct mifare_sector { + struct mifare_block blocks[4]; +}; + +struct mifare { + struct mifare_sector sectors[16]; +}; diff --git a/iccard/nesica.c b/iccard/nesica.c new file mode 100644 index 0000000..bbef9be --- /dev/null +++ b/iccard/nesica.c @@ -0,0 +1,44 @@ +#include +#include +#include + +#include "iccard/nesica.h" +#include "iccard/mifare.h" + +#include "util/dprintf.h" + +HRESULT aime_card_populate( + struct mifare *mifare, + const uint8_t *card_sn, + size_t nbytes) +{ + uint8_t b; + size_t i; + + assert(mifare != NULL); + assert(card_sn != NULL); + + memset(mifare, 0, sizeof(*mifare)); + + if (nbytes != 16) { + dprintf("Nesica IC: Card Serial must be 16 characters\n"); + + return E_INVALIDARG; + } + + for (int i = 0; i < 16; i++) { + mifare->sectors[0].blocks[0].bytes[i] = i; + } + + memset(mifare->sectors[0].blocks[2].bytes, 0xff, 16); + memset(mifare->sectors[0].blocks[3].bytes, 0xff, 16); + + mifare->sectors[0].blocks[0].bytes[14] = 'T'; + mifare->sectors[0].blocks[0].bytes[15] = 'U'; + + memcpy_s(mifare->sectors[0].blocks[1].bytes, 4, 'T053', 4); + memcpy_s(mifare->sectors[0].blocks[1].bytes[4], 12, card_sn, 12); + memcpy_s(mifare->sectors[0].blocks[2].bytes, 16, card_sn[12], 4); + + return S_OK; +} diff --git a/iccard/nesica.h b/iccard/nesica.h new file mode 100644 index 0000000..34857ef --- /dev/null +++ b/iccard/nesica.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include + +#include "iccard/mifare.h" + +HRESULT aime_card_populate( + struct mifare *mifare, + const uint8_t *luid, + size_t nbytes); diff --git a/jvs/jvs-bus.c b/jvs/jvs-bus.c new file mode 100644 index 0000000..8961b84 --- /dev/null +++ b/jvs/jvs-bus.c @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "jvs/jvs-bus.h" + +void jvs_bus_transact( + struct jvs_node *head, + const void *bytes, + size_t nbytes, + struct iobuf *resp) +{ + struct jvs_node *node; + + assert(bytes != NULL); + assert(resp != NULL); + + for (node = head ; node != NULL ; node = node->next) { + node->transact(node, bytes, nbytes, resp); + } +} + +bool jvs_node_sense(struct jvs_node *node) +{ + if (node != NULL) { + return node->sense(node); + } else { + return false; + } +} diff --git a/jvs/jvs-bus.h b/jvs/jvs-bus.h new file mode 100644 index 0000000..820ba20 --- /dev/null +++ b/jvs/jvs-bus.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "hook/iobuf.h" + +struct jvs_node { + struct jvs_node *next; + void (*transact)( + struct jvs_node *node, + const void *bytes, + size_t nbytes, + struct iobuf *resp); + bool (*sense)(struct jvs_node *node); +}; + +void jvs_bus_transact( + struct jvs_node *head, + const void *bytes, + size_t nbytes, + struct iobuf *resp); + +bool jvs_node_sense(struct jvs_node *node); diff --git a/jvs/jvs-cmd.h b/jvs/jvs-cmd.h new file mode 100644 index 0000000..0197c9f --- /dev/null +++ b/jvs/jvs-cmd.h @@ -0,0 +1,45 @@ +#pragma once + +enum { + JVS_CMD_READ_ID = 0x10, + JVS_CMD_GET_CMD_VERSION = 0x11, + JVS_CMD_GET_JVS_VERSION = 0x12, + JVS_CMD_GET_COMM_VERSION = 0x13, + JVS_CMD_GET_FEATURES = 0x14, + JVS_CMD_READ_SWITCHES = 0x20, + JVS_CMD_READ_COIN = 0x21, + JVS_CMD_READ_ANALOGS = 0x22, + JVS_CMD_WRITE_GPIO = 0x32, + JVS_CMD_RESET = 0xF0, + JVS_CMD_ASSIGN_ADDR = 0xF1, +}; + +#pragma pack(push, 1) + +struct jvs_req_read_switches { + uint8_t cmd; + uint8_t num_players; + uint8_t bytes_per_player; +}; + +struct jvs_req_read_coin { + uint8_t cmd; + uint8_t nslots; +}; + +struct jvs_req_read_analogs { + uint8_t cmd; + uint8_t nanalogs; +}; + +struct jvs_req_reset { + uint8_t cmd; + uint8_t unknown; +}; + +struct jvs_req_assign_addr { + uint8_t cmd; + uint8_t addr; +}; + +#pragma pack(pop) diff --git a/jvs/jvs-frame.c b/jvs/jvs-frame.c new file mode 100644 index 0000000..37fb5e7 --- /dev/null +++ b/jvs/jvs-frame.c @@ -0,0 +1,152 @@ +#include + +#include +#include +#include +#include + +#include "hook/iobuf.h" + +#include "jvs/jvs-frame.h" + +#include "util/dprintf.h" + +static HRESULT jvs_frame_check_sum(const struct iobuf *dest); +static HRESULT jvs_frame_encode_byte(struct iobuf *dest, uint8_t byte); + +/* Deals in whole frames only for simplicity's sake, since that's all we need + to emulate the Nu's kernel driver interface. This could of course be + extended later for other arcade hardware platforms. */ + +HRESULT jvs_frame_decode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *bytes; + bool escape; + size_t i; + + assert(dest != NULL); + assert(ptr != NULL); + + bytes = ptr; + + if (nbytes == 0) { + dprintf("JVS Frame: Empty frame\n"); + + return E_FAIL; + } + + if (bytes[0] != 0xE0) { + dprintf("JVS Frame: Sync byte was expected\n"); + + return E_FAIL; + } + + escape = false; + + for (i = 1 ; i < nbytes ; i++) { + if (bytes[i] == 0xE0) { + dprintf("JVS Frame: Unexpected sync byte\n"); + + return E_FAIL; + } else if (bytes[i] == 0xD0) { + if (escape) { + dprintf("JVS Frame: Escaping fault\n"); + + return E_FAIL; + } + + escape = true; + } else { + if (dest->pos >= dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + if (escape) { + escape = false; + dest->bytes[dest->pos++] = bytes[i] + 1; + } else { + dest->bytes[dest->pos++] = bytes[i]; + } + } + } + + return jvs_frame_check_sum(dest); +} + +static HRESULT jvs_frame_check_sum(const struct iobuf *dest) +{ + uint8_t checksum; + size_t i; + + checksum = 0; + + for (i = 0 ; i < dest->pos - 1 ; i++) { + checksum += dest->bytes[i]; + } + + if (checksum != dest->bytes[dest->pos - 1]) { + dprintf("JVS Frame: Checksum failure\n"); + + return HRESULT_FROM_WIN32(ERROR_CRC); + } + + return S_OK; +} + +HRESULT jvs_frame_encode( + struct iobuf *dest, + const void *ptr, + size_t nbytes) +{ + const uint8_t *bytes; + uint8_t checksum; + size_t i; + HRESULT hr; + + assert(dest != NULL); + assert(ptr != NULL); + + bytes = ptr; + checksum = 0; + + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xE0; + + for (i = 0 ; i < nbytes ; i++) { + hr = jvs_frame_encode_byte(dest, bytes[i]); + + if (FAILED(hr)) { + return hr; + } + + checksum += bytes[i]; + } + + return jvs_frame_encode_byte(dest, checksum); +} + +static HRESULT jvs_frame_encode_byte(struct iobuf *dest, uint8_t byte) +{ + if (byte == 0xD0 || byte == 0xE0) { + if (dest->pos + 2 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = 0xD0; + dest->bytes[dest->pos++] = byte - 1; + } else { + if (dest->pos + 1 > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = byte; + } + + return S_OK; +} diff --git a/jvs/jvs-frame.h b/jvs/jvs-frame.h new file mode 100644 index 0000000..719e528 --- /dev/null +++ b/jvs/jvs-frame.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +#include "hook/iobuf.h" + +HRESULT jvs_frame_decode( + struct iobuf *dest, + const void *bytes, + size_t nbytes); + +HRESULT jvs_frame_encode( + struct iobuf *dest, + const void *bytes, + size_t nbytes); diff --git a/jvs/jvs-util.c b/jvs/jvs-util.c new file mode 100644 index 0000000..1bbe10e --- /dev/null +++ b/jvs/jvs-util.c @@ -0,0 +1,109 @@ +#include + +#include +#include +#include + +#include "hook/iobuf.h" + +#include "jvs/jvs-frame.h" +#include "jvs/jvs-util.h" + +#include "util/dprintf.h" + +typedef HRESULT (*jvs_dispatch_fn_t)( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp); + +void jvs_crack_request( + const void *bytes, + size_t nbytes, + struct iobuf *resp, + uint8_t jvs_addr, + jvs_dispatch_fn_t dispatch_fn, + void *dispatch_ctx) +{ + uint8_t req_bytes[128]; + uint8_t resp_bytes[128]; + struct iobuf decode; + struct iobuf encode; + struct const_iobuf segments; + HRESULT hr; + + assert(bytes != NULL); + assert(resp != NULL); + assert(jvs_addr != 0x00 && (jvs_addr < 0x20 || jvs_addr == 0xFF)); + assert(dispatch_fn != NULL); + + decode.bytes = req_bytes; + decode.nbytes = sizeof(req_bytes); + decode.pos = 0; + + hr = jvs_frame_decode(&decode, bytes, nbytes); + + if (FAILED(hr)) { + return; + } + +#if 0 + dprintf("Decoded request:\n"); + dump_iobuf(&decode); +#endif + + if (req_bytes[0] != jvs_addr && req_bytes[0] != 0xFF) { + return; + } + + iobuf_flip(&segments, &decode); + segments.pos = 2; + + encode.bytes = resp_bytes; + encode.nbytes = sizeof(resp_bytes); + encode.pos = 3; + + /* +1: Don't try to dispatch the trailing checksum byte */ + + hr = S_OK; /* I guess an empty request packet is technically valid? */ + + while (segments.pos + 1 < segments.nbytes) { + hr = dispatch_fn(dispatch_ctx, &segments, &encode); + + if (FAILED(hr)) { + break; + } + } + + if (FAILED(hr)) { + /* Send an error in the overall status byte */ + encode.pos = 3; + + resp_bytes[0] = 0x00; /* Dest addr (master) */ + resp_bytes[1] = 0x02; /* Payload len: Status byte, checksum byte */ + + if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { + resp_bytes[2] = 0x04; /* Status: "Overflow" */ + } else { + resp_bytes[2] = 0x02; /* Status: Encoutered unsupported command */ + } + } else if (encode.pos == 3) { + /* Probably a reset, don't emit a response frame with empty payload */ + return; + } else { + /* Send success response */ + resp_bytes[0] = 0x00; /* Dest addr (master) */ + resp_bytes[1] = encode.pos - 2 + 1; /* -2 header +1 checksum */ + resp_bytes[2] = 0x01; /* Status: Success */ + } + +#if 0 + dprintf("Encoding response:\n"); + dump_iobuf(&encode); +#endif + + hr = jvs_frame_encode(resp, encode.bytes, encode.pos); + + if (FAILED(hr)) { + dprintf("JVS Node: Response encode error: %x\n", (int) hr); + } +} diff --git a/jvs/jvs-util.h b/jvs/jvs-util.h new file mode 100644 index 0000000..e3fc4e5 --- /dev/null +++ b/jvs/jvs-util.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include +#include + +#include "hook/iobuf.h" + +typedef HRESULT (*jvs_dispatch_fn_t)( + void *ctx, + struct const_iobuf *req, + struct iobuf *resp); + +void jvs_crack_request( + const void *bytes, + size_t nbytes, + struct iobuf *resp, + uint8_t jvs_addr, + jvs_dispatch_fn_t dispatch_fn, + void *dispatch_ctx); diff --git a/jvs/meson.build b/jvs/meson.build new file mode 100644 index 0000000..49737a0 --- /dev/null +++ b/jvs/meson.build @@ -0,0 +1,18 @@ +jvs_lib = static_library( + 'jvs', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'jvs-bus.c', + 'jvs-bus.h', + 'jvs-cmd.h', + 'jvs-frame.c', + 'jvs-frame.h', + 'jvs-util.c', + 'jvs-util.h', + ], +) diff --git a/mercuryhook/config.c b/mercuryhook/config.c new file mode 100644 index 0000000..f087942 --- /dev/null +++ b/mercuryhook/config.c @@ -0,0 +1,74 @@ +#include +#include + +#include "board/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" +#include "gfxhook/config.h" + +#include "mercuryhook/config.h" + +#include "platform/config.h" + +void mercury_dll_config_load( + struct mercury_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"mercuryio", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void touch_config_load( + struct touch_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW( + L"touch", + L"enable", + 1, + filename); +} + +void elisabeth_config_load( + struct elisabeth_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW( + L"elisabeth", + L"enable", + 1, + filename); +} + + +void mercury_hook_config_load( + struct mercury_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + aime_config_load(&cfg->aime, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + gfx_config_load(&cfg->gfx, filename); + mercury_dll_config_load(&cfg->dll, filename); + touch_config_load(&cfg->touch, filename); + elisabeth_config_load(&cfg->elisabeth, filename); +} diff --git a/mercuryhook/config.h b/mercuryhook/config.h new file mode 100644 index 0000000..d5bf463 --- /dev/null +++ b/mercuryhook/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "hooklib/dvd.h" +#include "gfxhook/gfx.h" + +#include "mercuryhook/mercury-dll.h" +#include "mercuryhook/touch.h" +#include "mercuryhook/elisabeth.h" + +#include "platform/config.h" + +struct mercury_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct gfx_config gfx; + struct mercury_dll_config dll; + struct touch_config touch; + struct elisabeth_config elisabeth; +}; + +void mercury_dll_config_load( + struct mercury_dll_config *cfg, + const wchar_t *filename); + +void mercury_hook_config_load( + struct mercury_hook_config *cfg, + const wchar_t *filename); diff --git a/mercuryhook/dllmain.c b/mercuryhook/dllmain.c new file mode 100644 index 0000000..14726aa --- /dev/null +++ b/mercuryhook/dllmain.c @@ -0,0 +1,121 @@ +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "hook/process.h" + +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "gfxhook/gfx.h" +#include "gfxhook/d3d11.h" + +#include "mercuryhook/config.h" +#include "mercuryhook/io4.h" +#include "mercuryhook/mercury-dll.h" +#include "mercuryhook/elisabeth.h" +#include "mercuryhook/touch.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" + +static HMODULE mercury_hook_mod; +static process_entry_t mercury_startup; +static struct mercury_hook_config mercury_hook_cfg; + +/* This hook is based on mu3hook, with leaked mercuryhook i/o codes. */ + +static DWORD CALLBACK mercury_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin mercury_pre_startup ---\n"); + + /* Load config */ + + mercury_hook_config_load(&mercury_hook_cfg, L".\\taitools.ini"); + + /* Hook Win32 APIs */ + + dvd_hook_init(&mercury_hook_cfg.dvd, mercury_hook_mod); + serial_hook_init(); + + gfx_hook_init(&mercury_hook_cfg.gfx); + gfx_d3d11_hook_init(&mercury_hook_cfg.gfx, mercury_hook_mod); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &mercury_hook_cfg.platform, + "SDFE", + "ACA1", + mercury_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&mercury_hook_cfg.aime, 1, mercury_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(2); + + if (FAILED(hr)) { + goto fail; + } + + hr = mercury_dll_init(&mercury_hook_cfg.dll, mercury_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = mercury_io4_hook_init(&mercury_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + /* Start elisabeth Hooks for the LED and IO Board DLLs */ + elisabeth_hook_init(&mercury_hook_cfg.elisabeth); + + touch_hook_init(&mercury_hook_cfg.touch); + + /* Initialize debug helpers */ + + spike_hook_init(L".\\taitools.ini"); + + dprintf("--- End mercury_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return mercury_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + mercury_hook_mod = mod; + + hr = process_hijack_startup(mercury_pre_startup, &mercury_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/mercuryhook/elisabeth.c b/mercuryhook/elisabeth.c new file mode 100644 index 0000000..7078a21 --- /dev/null +++ b/mercuryhook/elisabeth.c @@ -0,0 +1,87 @@ +#include +#include + +#include +#include +#include + +#include "mercuryhook/elisabeth.h" +#include "mercuryhook/mercury-dll.h" + +#include "hook/table.h" + +#include "hooklib/uart.h" +#include "hooklib/dll.h" +#include "hooklib/path.h" +#include "hooklib/setupapi.h" + +#include "util/dprintf.h" + +/* Hooks targeted DLLs dynamically loaded by elisabeth. */ + +static void dll_hook_insert_hooks(HMODULE target); +static FARPROC WINAPI my_GetProcAddress(HMODULE hModule, const char *name); +static FARPROC (WINAPI *next_GetProcAddress)(HMODULE hModule, const char *name); +static int my_USBIntLED_Init(); +static int my_USBIntLED_set(); + +static const struct hook_symbol win32_hooks[] = { + { + .name = "GetProcAddress", + .patch = my_GetProcAddress, + .link = (void **) &next_GetProcAddress + } +}; + +HRESULT elisabeth_hook_init(struct elisabeth_config *cfg) +{ + if (!cfg->enable) { + return S_OK; + } + dll_hook_insert_hooks(NULL); + dprintf("Elisabeth: Init\n"); + return S_OK; +} + +static void dll_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "kernel32.dll", + win32_hooks, + _countof(win32_hooks)); +} + +FARPROC WINAPI my_GetProcAddress(HMODULE hModule, const char *name) +{ + uintptr_t ordinal = (uintptr_t) name; + + FARPROC result = next_GetProcAddress(hModule, name); + + if (ordinal > 0xFFFF) { + /* Import by name */ + if (strcmp(name, "USBIntLED_Init") == 0) { + result = (FARPROC) my_USBIntLED_Init; + } + + if (strcmp(name, "USBIntLED_set") == 0) { + result = (FARPROC) my_USBIntLED_set; + } + } + + return result; +} + +/* Intercept the call to initialize the LED board. */ +static int my_USBIntLED_Init() +{ + dprintf("Elisabeth: my_USBIntLED_Init hit!\n"); + return 1; +} + +static int my_USBIntLED_set(int data1, struct led_data data2) +{ + assert(mercury_dll.set_leds != NULL); + mercury_dll.set_leds(data2); + return 1; +} diff --git a/mercuryhook/elisabeth.h b/mercuryhook/elisabeth.h new file mode 100644 index 0000000..5806c99 --- /dev/null +++ b/mercuryhook/elisabeth.h @@ -0,0 +1,13 @@ +#pragma once +#include + +struct led_data { + DWORD unitCount; + uint8_t rgba[480 * 4]; +}; + +struct elisabeth_config { + bool enable; +}; + +HRESULT elisabeth_hook_init(struct elisabeth_config *cfg); diff --git a/mercuryhook/io4.c b/mercuryhook/io4.c new file mode 100644 index 0000000..df22656 --- /dev/null +++ b/mercuryhook/io4.c @@ -0,0 +1,91 @@ +#include + +#include +#include +#include +#include + +#include "board/io4.h" + +#include "mercuryhook/mercury-dll.h" + +#include "util/dprintf.h" + +bool mercury_io_coin = false; +uint16_t mercury_io_coins = 0; + +static HRESULT mercury_io4_poll(void *ctx, struct io4_state *state); + +static const struct io4_ops mercury_io4_ops = { + .poll = mercury_io4_poll, +}; + +HRESULT mercury_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(mercury_dll.init != NULL); + + hr = io4_hook_init(cfg, &mercury_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return mercury_dll.init(); +} + +static HRESULT mercury_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint8_t gamebtn; + HRESULT hr; + + assert(mercury_dll.poll != NULL); + assert(mercury_dll.get_opbtns != NULL); + assert(mercury_dll.get_gamebtns != NULL); + + memset(state, 0, sizeof(*state)); + + hr = mercury_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + gamebtn = 0; + + mercury_dll.get_opbtns(&opbtn); + mercury_dll.get_gamebtns(&gamebtn); + + if (opbtn & MERCURY_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & MERCURY_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (opbtn & MERCURY_IO_OPBTN_COIN) { + if (!mercury_io_coin) { + mercury_io_coin = true; + mercury_io_coins++; + } + } + else { + mercury_io_coin = false; + } + + state->chutes[0] = 128 + 256 * mercury_io_coins; + + if (gamebtn & MERCURY_IO_GAMEBTN_VOL_UP) { + state->buttons[0] |= 1 << 1; + } + + if (gamebtn & MERCURY_IO_GAMEBTN_VOL_DOWN) { + state->buttons[0] |= 1 << 0; + } + + return S_OK; +} diff --git a/mercuryhook/io4.h b/mercuryhook/io4.h new file mode 100644 index 0000000..87dc6d7 --- /dev/null +++ b/mercuryhook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT mercury_io4_hook_init(const struct io4_config *cfg); diff --git a/mercuryhook/mercury-dll.c b/mercuryhook/mercury-dll.c new file mode 100644 index 0000000..bf21d30 --- /dev/null +++ b/mercuryhook/mercury-dll.c @@ -0,0 +1,118 @@ +#include + +#include +#include + +#include "mercuryhook/mercury-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym mercury_dll_syms[] = { + { + .sym = "mercury_io_init", + .off = offsetof(struct mercury_dll, init), + }, { + .sym = "mercury_io_poll", + .off = offsetof(struct mercury_dll, poll), + }, { + .sym = "mercury_io_get_opbtns", + .off = offsetof(struct mercury_dll, get_opbtns), + }, { + .sym = "mercury_io_get_gamebtns", + .off = offsetof(struct mercury_dll, get_gamebtns), + }, { + .sym = "mercury_io_touch_init", + .off = offsetof(struct mercury_dll, touch_init), + }, { + .sym = "mercury_io_touch_start", + .off = offsetof(struct mercury_dll, touch_start), + }, { + .sym = "mercury_io_touch_set_leds", + .off = offsetof(struct mercury_dll, set_leds), + } +}; + +struct mercury_dll mercury_dll; + +// Copypasta DLL binding and diagnostic message boilerplate. +// Not much of this lends itself to being easily factored out. Also there +// will be a lot of API-specific branching code here eventually as new API +// versions get defined, so even though these functions all look the same +// now this won't remain the case forever. + +HRESULT mercury_dll_init(const struct mercury_dll_config *cfg, HINSTANCE self) +{ + uint16_t (*get_api_version)(void); + const struct dll_bind_sym *sym; + HINSTANCE owned; + HINSTANCE src; + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (cfg->path[0] != L'\0') { + owned = LoadLibraryW(cfg->path); + + if (owned == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Wacca IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Wacca IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "mercury_io_get_api_version"); + + if (get_api_version != NULL) { + mercury_dll.api_version = get_api_version(); + } else { + mercury_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose mercury_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (mercury_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Wacca IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Taitools.\n", + mercury_dll.api_version); + + goto end; + } + + sym = mercury_dll_syms; + hr = dll_bind(&mercury_dll, src, &sym, _countof(mercury_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Wacca IO: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + + goto end; + } else { + dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); + } + } + + owned = NULL; + +end: + if (owned != NULL) { + FreeLibrary(owned); + } + + return hr; +} diff --git a/mercuryhook/mercury-dll.h b/mercuryhook/mercury-dll.h new file mode 100644 index 0000000..fa27edb --- /dev/null +++ b/mercuryhook/mercury-dll.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "mercuryio/mercuryio.h" +#include "mercuryhook/elisabeth.h" + +struct mercury_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint8_t *gamebtn); + HRESULT (*touch_init)(void); + void (*touch_start)(mercury_io_touch_callback_t callback); + void (*set_leds)(struct led_data data); +}; + +struct mercury_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct mercury_dll mercury_dll; + +HRESULT mercury_dll_init(const struct mercury_dll_config *cfg, HINSTANCE self); diff --git a/mercuryhook/mercuryhook.def b/mercuryhook/mercuryhook.def new file mode 100644 index 0000000..32f33c1 --- /dev/null +++ b/mercuryhook/mercuryhook.def @@ -0,0 +1,21 @@ +LIBRARY mercuryhook + +EXPORTS + aime_io_get_api_version + aime_io_init + aime_io_led_set_color + aime_io_nfc_get_aime_id + aime_io_nfc_get_felica_id + aime_io_nfc_poll + amDllVideoClose @2 + amDllVideoGetVBiosVersion @4 + amDllVideoOpen @1 + amDllVideoSetResolution @3 + mercury_io_get_api_version + mercury_io_get_gamebtns + mercury_io_get_opbtns + mercury_io_touch_init + mercury_io_touch_start + mercury_io_touch_set_leds + mercury_io_init + mercury_io_poll diff --git a/mercuryhook/meson.build b/mercuryhook/meson.build new file mode 100644 index 0000000..7a676b2 --- /dev/null +++ b/mercuryhook/meson.build @@ -0,0 +1,34 @@ +shared_library( + 'mercuryhook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'mercuryhook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + ], + link_with : [ + aimeio_lib, + gfxhook_lib, + board_lib, + hooklib_lib, + mercuryio_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'mercury-dll.c', + 'mercury-dll.h', + 'elisabeth.h', + 'elisabeth.c', + 'touch.h', + 'touch.c' + ], +) diff --git a/mercuryhook/touch.c b/mercuryhook/touch.c new file mode 100644 index 0000000..87ba6d5 --- /dev/null +++ b/mercuryhook/touch.c @@ -0,0 +1,518 @@ +#include + +#include +#include +#include +#include +#include + +#include "board/slider-cmd.h" +#include "board/slider-frame.h" + +#include "mercuryhook/mercury-dll.h" +#include "mercuryhook/touch.h" + +#include "hook/iobuf.h" +#include "hook/iohook.h" + +#include "hooklib/uart.h" +#include "hooklib/fdshark.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +const char SYNC_BOARD_VER[6] = "190523"; +const char UNIT_BOARD_VER[6] = "190514"; + +static HRESULT touch_handle_irp(struct irp *irp); +static HRESULT touch0_handle_irp_locked(struct irp *irp); +static HRESULT touch1_handle_irp_locked(struct irp *irp); + +static HRESULT touch_req_dispatch(const struct touch_req *req); + +static HRESULT touch_frame_decode(struct touch_req *dest, struct iobuf *iobuf, int side); +static uint8_t calc_checksum(const void *ptr, size_t nbytes); + +static HRESULT touch_handle_get_sync_board_ver(const struct touch_req *req); +static HRESULT touch_handle_next_read(const struct touch_req *req); +static HRESULT touch_handle_get_unit_board_ver(const struct touch_req *req); +static HRESULT touch_handle_mystery1(const struct touch_req *req); +static HRESULT touch_handle_mystery2(const struct touch_req *req); +static HRESULT touch_handle_start_auto_scan(const struct touch_req *req); +static void touch_res_auto_scan(const bool *state); + +uint8_t input_frame_count_0 = 0x7b; +uint8_t input_frame_count_1 = 0x7b; +bool touch0_auto = false; +bool touch1_auto = false; + +static CRITICAL_SECTION touch0_lock; +static struct uart touch0_uart; +static uint8_t touch0_written_bytes[520]; +static uint8_t touch0_readable_bytes[520]; + +static CRITICAL_SECTION touch1_lock; +static struct uart touch1_uart; +static uint8_t touch1_written_bytes[520]; +static uint8_t touch1_readable_bytes[520]; + +HRESULT touch_hook_init(const struct touch_config *cfg) +{ + assert(cfg != NULL); + assert(mercury_dll.touch_init != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + InitializeCriticalSection(&touch0_lock); + InitializeCriticalSection(&touch1_lock); + dprintf("Wacca touch: Init\n"); + + uart_init(&touch0_uart, 3); + touch0_uart.written.bytes = touch0_written_bytes; + touch0_uart.written.nbytes = sizeof(touch0_written_bytes); + touch0_uart.readable.bytes = touch0_readable_bytes; + touch0_uart.readable.nbytes = sizeof(touch0_readable_bytes); + + uart_init(&touch1_uart, 4); + touch1_uart.written.bytes = touch1_written_bytes; + touch1_uart.written.nbytes = sizeof(touch1_written_bytes); + touch1_uart.readable.bytes = touch1_readable_bytes; + touch1_uart.readable.nbytes = sizeof(touch1_readable_bytes); + + return iohook_push_handler(touch_handle_irp); +} + +static HRESULT touch_handle_irp(struct irp *irp) +{ + HRESULT hr; + + assert(irp != NULL); + + if (uart_match_irp(&touch0_uart, irp)) { + EnterCriticalSection(&touch0_lock); + hr = touch0_handle_irp_locked(irp); + LeaveCriticalSection(&touch0_lock); + } + else if (uart_match_irp(&touch1_uart, irp)) { + EnterCriticalSection(&touch1_lock); + hr = touch1_handle_irp_locked(irp); + LeaveCriticalSection(&touch1_lock); + } + else { + return iohook_invoke_next(irp); + } + + return hr; +} + +static HRESULT touch0_handle_irp_locked(struct irp *irp) +{ + struct touch_req req; + HRESULT hr; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Wacca touch0: Starting backend\n"); + hr = mercury_dll.touch_init(); + + if (FAILED(hr)) { + dprintf("Wacca touch: Backend error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&touch0_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 0 + dprintf("TX0 Buffer:\n"); + dump_iobuf(&touch0_uart.written); +#endif + hr = touch_frame_decode(&req, &touch0_uart.written, 0); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Wacca touch: Deframe error: %x\n", (int) hr); + } + + return hr; + } + + hr = touch_req_dispatch(&req); + + if (FAILED(hr)) { + dprintf("Wacca touch: Processing error: %x\n", (int) hr); + } + + return hr; + } +} + +static HRESULT touch1_handle_irp_locked(struct irp *irp) +{ + struct touch_req req; + HRESULT hr; + + if (irp->op == IRP_OP_OPEN) { + dprintf("Wacca touch1: Starting backend\n"); + hr = mercury_dll.touch_init(); + + if (FAILED(hr)) { + dprintf("Wacca touch: Backend error: %x\n", (int) hr); + + return hr; + } + } + + hr = uart_handle_irp(&touch1_uart, irp); + + if (FAILED(hr) || irp->op != IRP_OP_WRITE) { + return hr; + } + + for (;;) { +#if 0 + dprintf("TX1 Buffer:\n"); + dump_iobuf(&touch1_uart.written); +#endif + + hr = touch_frame_decode(&req, &touch1_uart.written, 1); + + if (hr != S_OK) { + if (FAILED(hr)) { + dprintf("Wacca touch: Deframe error: %x\n", (int) hr); + } + + return hr; + } + + hr = touch_req_dispatch(&req); + + if (FAILED(hr)) { + dprintf("Wacca touch: Processing error: %x\n", (int) hr); + } + + return hr; + } +} + +static HRESULT touch_req_dispatch(const struct touch_req *req) +{ + switch (req->cmd) { + case CMD_GET_SYNC_BOARD_VER: + return touch_handle_get_sync_board_ver(req); + case CMD_NEXT_READ: + return touch_handle_next_read(req); + case CMD_GET_UNIT_BOARD_VER: + return touch_handle_get_unit_board_ver(req); + case CMD_MYSTERY1: + return touch_handle_mystery1(req); + case CMD_MYSTERY2: + return touch_handle_mystery2(req); + case CMD_START_AUTO_SCAN: + return touch_handle_start_auto_scan(req); + case CMD_BEGIN_WRITE: + dprintf("Wacca touch: Begin write for side %d\n", req->side); + return S_OK; + case CMD_NEXT_WRITE: + dprintf("Wacca touch: continue write for side %d\n", req->side); + return S_OK; + default: + dprintf("Wacca touch: Unhandled command %02x\n", req->cmd); + return S_OK; + } +} + +static HRESULT touch_handle_get_sync_board_ver(const struct touch_req *req) +{ + struct touch_resp_get_sync_board_ver resp; + HRESULT hr; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: Get sync board version\n", req->side); + + resp.cmd = 0xa0; + memcpy(resp.version, SYNC_BOARD_VER, sizeof(SYNC_BOARD_VER)); + resp.checksum = 0; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + if (req->side == 0) { + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + + return hr; +} + +static HRESULT touch_handle_next_read(const struct touch_req *req) +{ + struct touch_resp_startup resp; + HRESULT hr; + char *rev; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: Read section %2hx\n", req->side, req->data[2]); + + + switch (req->data[2]) { + // These can be found in the config file + case 0x30: + rev = " 0 0 1 2 3 4 5 15 15 15 15 15 15 11 11 11"; + break; + case 0x31: + rev = " 11 11 11 128 103 103 115 138 127 103 105 111 126 113 95 100"; + break; + case 0x33: + rev = " 101 115 98 86 76 67 68 48 117 0 82 154 0 6 35 4"; + break; + default: + dprintf("Wacca touch: BAD READ REQUEST %2hx\n", req->data[2]); + return 1; + } + + memcpy(resp.data, rev, 80 * sizeof(char)); + resp.checksum = 0; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + if (req->side == 0) { + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + return hr; +} + +static HRESULT touch_handle_get_unit_board_ver(const struct touch_req *req) +{ + struct touch_resp_get_unit_board_ver resp; + HRESULT hr; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: get unit board version\n", req->side); + + memset(resp.version, 0, sizeof(resp.version)); + memcpy(resp.version, SYNC_BOARD_VER, sizeof(SYNC_BOARD_VER)); + + for (int i = 0; i < 6; i++ ) + memcpy(&resp.version[7 + (6 * i)], UNIT_BOARD_VER, sizeof(UNIT_BOARD_VER)); + + resp.cmd = 0xa8; + resp.checksum = 0; + + if (req->side == 0) { + resp.version[6] = 'R'; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + #if 0 + for (int i = 0; i < sizeof(resp.version); i++) { + dprintf("0x%02x ", resp.version[i]); + } + dprintf("\n"); + #endif + + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + resp.version[6] = 'L'; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + #if 0 + for (int i = 0; i < sizeof(resp.version); i++) { + dprintf("0x%02x ", resp.version[i]); + } + dprintf("\n"); + #endif + + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + + return hr; +} + +static HRESULT touch_handle_mystery1(const struct touch_req *req) +{ + struct touch_resp_mystery1 resp; + HRESULT hr; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: Command A2\n", req->side); + + resp.cmd = 0xa2; + resp.data = 0x3f; + resp.checksum = 0; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + if (req->side == 0) { + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + return hr; +} + +static HRESULT touch_handle_mystery2(const struct touch_req *req) +{ + struct touch_resp_mystery2 resp; + HRESULT hr; + memset(&resp, 0, sizeof(resp)); + dprintf("Wacca Touch%d: Command 94\n", req->side); + + resp.cmd = 0x94; + resp.data = 0; + resp.checksum = 0; + resp.checksum = calc_checksum(&resp, sizeof(resp)); + + if (req->side == 0) { + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + } + else { + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + } + return hr; +} + +static HRESULT touch_handle_start_auto_scan(const struct touch_req *req) +{ + struct touch_resp_start_auto resp; + HRESULT hr; + uint8_t data1[24] = { 0 }; + // Unsure what this does. It seems to change every request on a real board, + // but the game doesn't seem to mind that it's the same + uint8_t data2[9] = { 0x0d, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00 }; + + dprintf("Wacca Touch%d: Start Auto", req->side); + + #if 0 + for (int i = 0; i < req->data_length; i++) + dprintf("0x%02x ", req->data[i]); + #endif + dprintf("\n"); + + resp.cmd = 0x9c; + resp.data = 0; + resp.checksum = 0x49; + + resp.frame.cmd= 0x81; + memcpy(resp.frame.data1, data1, sizeof(data1)); + memcpy(resp.frame.data2, data2, sizeof(data2)); + resp.frame.checksum = 0; + resp.frame.checksum = calc_checksum(&resp.frame, sizeof(resp.frame)); + + if (req->side == 0) { + resp.frame.count = input_frame_count_0++; + hr = iobuf_write(&touch0_uart.readable, &resp, sizeof(resp)); + touch0_auto = true; + } + else { + resp.frame.count = input_frame_count_1++; + hr = iobuf_write(&touch1_uart.readable, &resp, sizeof(resp)); + touch1_auto = true; + } + + mercury_dll.touch_start(touch_res_auto_scan); + return hr; +} + +static void touch_res_auto_scan(const bool *state) +{ + struct touch_input_frame frame0; + struct touch_input_frame frame1; + memset(&frame0, 0, sizeof(frame0)); + memset(&frame1, 0, sizeof(frame1)); + uint8_t dataR[24] = { 0 }; + uint8_t dataL[24] = { 0 }; + // this changes every input on a real board but + // the game doesn't seem to care about it... + uint8_t data2[9] = { 0x0d, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00 }; + uint8_t counter = 0; + + frame0.cmd = 0x81; + frame0.count = input_frame_count_0++; + input_frame_count_0 %= 0x7f; + + frame1.cmd = 0x81; + frame1.count = input_frame_count_1++; + input_frame_count_1 %= 0x7f; + + for (int i = 0; i < 24; i++) { + for (int j = 0; j < 5; j++) { + if (state[counter]) { + dataR[i] |= (1 << j); + } + if (state[counter+120]) { + dataL[i] |= (1 << j); + } + counter++; + } + } + + memcpy(frame0.data1, dataR, sizeof(dataR)); + memcpy(frame0.data2, data2, sizeof(data2)); + + memcpy(frame1.data1, dataL, sizeof(dataL)); + memcpy(frame1.data2, data2, sizeof(data2)); + + frame0.checksum = 0; + frame0.checksum = calc_checksum(&frame0, sizeof(frame0)); + + frame1.checksum = 0; + frame1.checksum = calc_checksum(&frame1, sizeof(frame1)); + + if (touch0_auto) { + //dprintf("Wacca touch: Touch0 auto frame #%2hx sent\n", frame0.count); + EnterCriticalSection(&touch0_lock); + iobuf_write(&touch0_uart.readable, &frame0, sizeof(frame0)); + LeaveCriticalSection(&touch0_lock); + } + + if (touch1_auto) { + //dprintf("Wacca touch: Touch1 auto frame #%2hx sent\n", frame0.count); + EnterCriticalSection(&touch1_lock); + iobuf_write(&touch1_uart.readable, &frame1, sizeof(frame1)); + LeaveCriticalSection(&touch1_lock); + } +} + +/* Decodes the response into a struct that's easier to work with. */ +static HRESULT touch_frame_decode(struct touch_req *dest, struct iobuf *iobuf, int side) +{ + dest->side = side; + dest->cmd = iobuf->bytes[0]; + iobuf->pos--; + dest->data_length = iobuf->pos; + + if (dest->data_length > 0) { + for (int i = 1; i < dest->data_length; i++) { + dest->data[i-1] = iobuf->bytes[i]; + } + } + iobuf->pos -= dest->data_length; + + return S_OK; +} + +/* The last byte of every response is a checksum. + * This checksum is calculated by bitwise XORing + * every byte in the response, then dropping the MSB. + * Thanks the CrazyRedMachine for figuring that out!! + */ +static uint8_t calc_checksum(const void *ptr, size_t nbytes) +{ + const uint8_t *src; + uint8_t checksum = 0; + + src = ptr; + + for (size_t i = 0; i < nbytes; i++) { + //dprintf("Wacca touch: Calculating %2hx\n", src[i]); + checksum = checksum^(src[i]); + } + //dprintf("Wacca touch: Checksum is %2hx\n", checksum&0x7f); + return checksum&0x7f; +} diff --git a/mercuryhook/touch.h b/mercuryhook/touch.h new file mode 100644 index 0000000..cc665c8 --- /dev/null +++ b/mercuryhook/touch.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include + +struct touch_config { + bool enable; +}; + +enum touch_cmd { + CMD_GET_SYNC_BOARD_VER = 0xa0, + CMD_NEXT_READ = 0x72, + CMD_GET_UNIT_BOARD_VER = 0xa8, + CMD_MYSTERY1 = 0xa2, + CMD_MYSTERY2 = 0x94, + CMD_START_AUTO_SCAN = 0xc9, + CMD_BEGIN_WRITE = 0x77, + CMD_NEXT_WRITE = 0x20 +}; + +struct touch_req { + uint8_t side; // COM3 or COM4 + uint8_t cmd; // First byte is the command byte + uint8_t data[256]; // rest of the data goes here + uint8_t data_length; // Size of the data including command byte +}; + +struct touch_input_frame { + uint8_t cmd; + uint8_t data1[24]; + uint8_t data2[9]; + uint8_t count; + uint8_t checksum; +}; + +struct touch_resp_get_sync_board_ver { + uint8_t cmd; + char version[6]; + uint8_t checksum; +}; + +struct touch_resp_startup { + char data[80]; + uint8_t checksum; +}; + +struct touch_resp_get_unit_board_ver { + uint8_t cmd; + uint8_t version[43]; + uint8_t checksum; +}; + +struct touch_resp_mystery1 { + uint8_t cmd; + uint8_t data; + uint8_t checksum; +}; + +struct touch_resp_mystery2 { + uint8_t cmd; + uint8_t data; + uint8_t checksum; +}; + +struct touch_resp_start_auto { + uint8_t cmd; + uint8_t data; + uint8_t checksum; + struct touch_input_frame frame; +}; + +HRESULT touch_hook_init(const struct touch_config *cfg); diff --git a/mercuryio/config.c b/mercuryio/config.c new file mode 100644 index 0000000..f3c8e54 --- /dev/null +++ b/mercuryio/config.c @@ -0,0 +1,44 @@ +#include + +#include +#include +#include + +#include "mercuryio/config.h" + +static const int mercury_io_default_cells[] = { + '1','1','1','2','2','2','3','3','3','4','4','4','5','5','5','6','6','6','7','7','7','8','8','8','9','9','9','0','0','0', + '1','1','1','2','2','2','3','3','3','4','4','4','5','5','5','6','6','6','7','7','7','8','8','8','9','9','9','0','0','0', + 'Q','Q','Q','W','W','W','E','E','E','R','R','R','T','T','T','Y','Y','Y','U','U','U','I','I','I','O','O','O','P','P','P', + 'Q','Q','Q','W','W','W','E','E','E','R','R','R','T','T','T','Y','Y','Y','U','U','U','I','I','I','O','O','O','P','P','P', + 'A','A','A','S','S','S','D','D','D','F','F','F','G','G','G','H','H','H','J','J','J','K','K','K','L','L','L',VK_OEM_1,VK_OEM_1,VK_OEM_1, + 'A','A','A','S','S','S','D','D','D','F','F','F','G','G','G','H','H','H','J','J','J','K','K','K','L','L','L',VK_OEM_1,VK_OEM_1,VK_OEM_1, + 'Z','Z','Z','X','X','X','C','C','C','V','V','V','B','B','B','N','N','N','M','M','M',VK_OEM_COMMA,VK_OEM_COMMA,VK_OEM_COMMA,VK_OEM_PERIOD,VK_OEM_PERIOD,VK_OEM_PERIOD,VK_OEM_2,VK_OEM_2,VK_OEM_2, + 'Z','Z','Z','X','X','X','C','C','C','V','V','V','B','B','B','N','N','N','M','M','M',VK_OEM_COMMA,VK_OEM_COMMA,VK_OEM_COMMA,VK_OEM_PERIOD,VK_OEM_PERIOD,VK_OEM_PERIOD,VK_OEM_2,VK_OEM_2,VK_OEM_2, +}; + +void mercury_io_config_load( + struct mercury_io_config *cfg, + const wchar_t *filename) +{ + wchar_t key[240]; + int i; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", 0x2D, filename); + cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", 0x2E, filename); + cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", 0x24, filename); + cfg->vk_vol_up = GetPrivateProfileIntW(L"io4", L"volup", 0x26, filename); + cfg->vk_vol_down = GetPrivateProfileIntW(L"io4", L"voldown", 0x28, filename); + + for (i = 0 ; i < 240 ; i++) { + swprintf_s(key, _countof(key), L"cell%i", i + 1); + cfg->vk_cell[i] = GetPrivateProfileIntW( + L"touch", + key, + mercury_io_default_cells[i], + filename); + } +} diff --git a/mercuryio/config.h b/mercuryio/config.h new file mode 100644 index 0000000..c925764 --- /dev/null +++ b/mercuryio/config.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include + +struct mercury_io_config { + uint8_t vk_test; + uint8_t vk_service; + uint8_t vk_coin; + uint8_t vk_vol_up; + uint8_t vk_vol_down; + uint8_t vk_cell[240]; +}; + +void mercury_io_config_load( + struct mercury_io_config *cfg, + const wchar_t *filename); diff --git a/mercuryio/mercuryio.c b/mercuryio/mercuryio.c new file mode 100644 index 0000000..96d4ac0 --- /dev/null +++ b/mercuryio/mercuryio.c @@ -0,0 +1,121 @@ +#include + +#include +#include +#include + +#include "mercuryio/mercuryio.h" +#include "mercuryio/config.h" +#include "mercuryhook/elisabeth.h" + +static unsigned int __stdcall mercury_io_touch_thread_proc(void *ctx); + +static uint8_t mercury_opbtn; +static uint8_t mercury_gamebtn; +static struct mercury_io_config mercury_io_cfg; +static bool mercury_io_touch_stop_flag; +static HANDLE mercury_io_touch_thread; + +uint16_t mercury_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT mercury_io_init(void) +{ + mercury_io_config_load(&mercury_io_cfg, L".\\taitools.ini"); + + return S_OK; +} + +HRESULT mercury_io_poll(void) +{ + mercury_opbtn = 0; + mercury_gamebtn = 0; + + if (GetAsyncKeyState(mercury_io_cfg.vk_test)) { + mercury_opbtn |= MERCURY_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState(mercury_io_cfg.vk_service)) { + mercury_opbtn |= MERCURY_IO_OPBTN_SERVICE; + } + + if (GetAsyncKeyState(mercury_io_cfg.vk_coin)) { + mercury_opbtn |= MERCURY_IO_OPBTN_COIN; + } + + if (GetAsyncKeyState(mercury_io_cfg.vk_vol_up)) { + mercury_gamebtn |= MERCURY_IO_GAMEBTN_VOL_UP; + } + + if (GetAsyncKeyState(mercury_io_cfg.vk_vol_down)) { + mercury_gamebtn |= MERCURY_IO_GAMEBTN_VOL_DOWN; + } + + return S_OK; +} + +void mercury_io_get_opbtns(uint8_t *opbtn) +{ + if (opbtn != NULL) { + *opbtn = mercury_opbtn; + } +} + +void mercury_io_get_gamebtns(uint8_t *gamebtn) +{ + if (gamebtn != NULL) { + *gamebtn = mercury_gamebtn; + } +} + +HRESULT mercury_io_touch_init(void) +{ + return S_OK; +} + +void mercury_io_touch_start(mercury_io_touch_callback_t callback) +{ + if (mercury_io_touch_thread != NULL) { + return; + } + + mercury_io_touch_thread = (HANDLE) _beginthreadex( + NULL, + 0, + mercury_io_touch_thread_proc, + callback, + 0, + NULL + ); +} + +void mercury_io_touch_set_leds(struct led_data data) +{ + +} + +static unsigned int __stdcall mercury_io_touch_thread_proc(void *ctx) +{ + mercury_io_touch_callback_t callback; + bool cellPressed[240]; + size_t i; + + callback = ctx; + + while (!mercury_io_touch_stop_flag) { + for (i = 0 ; i < _countof(cellPressed) ; i++) { + if (GetAsyncKeyState(mercury_io_cfg.vk_cell[i])) { + cellPressed[i] = true; + } else { + cellPressed[i] = false; + } + } + + callback(cellPressed); + Sleep(1); + } + + return 0; +} diff --git a/mercuryio/mercuryio.def b/mercuryio/mercuryio.def new file mode 100644 index 0000000..167d1cf --- /dev/null +++ b/mercuryio/mercuryio.def @@ -0,0 +1,11 @@ +LIBRARY mercuryio + +EXPORTS + mercury_io_get_api_version + mercury_io_init + mercury_io_poll + mercury_io_get_opbtns + mercury_io_get_gamebtns + mercury_io_touch_init + mercury_io_touch_start + mercury_io_touch_set_leds \ No newline at end of file diff --git a/mercuryio/mercuryio.h b/mercuryio/mercuryio.h new file mode 100644 index 0000000..4d029ca --- /dev/null +++ b/mercuryio/mercuryio.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include +#include + +#include "mercuryhook/elisabeth.h" + +enum { + MERCURY_IO_OPBTN_TEST = 0x01, + MERCURY_IO_OPBTN_SERVICE = 0x02, + MERCURY_IO_OPBTN_COIN = 0x04, +}; + +enum { + MERCURY_IO_GAMEBTN_VOL_UP = 0x01, + MERCURY_IO_GAMEBTN_VOL_DOWN = 0x02, +}; + +typedef void (*mercury_io_touch_callback_t)(const bool *state); +/* Get the version of the Wacca IO API that this DLL supports. This + function should return a positive 16-bit integer, where the high byte is + the major version and the low byte is the minor version (as defined by the + Semantic Versioning standard). + + The latest API version as of this writing is 0x0100. */ + +uint16_t mercury_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after mercury_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT mercury_io_init(void); + +/* Send any queued outputs (of which there are currently none, though this may + change in subsequent API versions) and retrieve any new inputs. + + Minimum API version: 0x0100 */ + +HRESULT mercury_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + MERCURY_IO_OPBTN enum above: this contains bit mask definitions for button + states returned in *opbtn. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void mercury_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + MERCURY_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into + a left hand side set of inputs and a right hand side set of inputs: the bit + mappings are the same in both cases. + + All buttons are active-high, even though some buttons' electrical signals + on a real cabinet are active-low. + + Minimum API version: 0x0100 */ + +void mercury_io_get_gamebtns(uint8_t *gamebtn); + +HRESULT mercury_io_touch_init(void); + +void mercury_io_touch_start(mercury_io_touch_callback_t callback); + +void mercury_io_touch_set_leds(struct led_data data); diff --git a/mercuryio/meson.build b/mercuryio/meson.build new file mode 100644 index 0000000..2970fa9 --- /dev/null +++ b/mercuryio/meson.build @@ -0,0 +1,13 @@ +mercuryio_lib = static_library( + 'mercuryio', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + sources : [ + 'mercuryio.c', + 'mercuryio.h', + 'config.c', + 'config.h', + ], +) \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..daeef33 --- /dev/null +++ b/meson.build @@ -0,0 +1,61 @@ +project( + 'taitools', + 'c', + version: '0.1.0', + default_options: [ + 'werror=true', + ], +) + +add_project_arguments( + '-DCOBJMACROS', + '-DDIRECTINPUT_VERSION=0x0800', + '-DWIN32_LEAN_AND_MEAN', + '-D_WIN32_WINNT=_WIN32_WINNT_WIN7', + '-DMINGW_HAS_SECURE_API=1', + '-Wno-unused', + language: 'c', +) + +# Use get_argument_syntax() instead once Meson 0.49.0 releases +if meson.get_compiler('c').get_id() != 'msvc' + add_project_arguments( + '-ffunction-sections', + '-fdata-sections', + language: 'c', + ) + + add_project_link_arguments( + '-Wl,--enable-stdcall-fixup', + '-Wl,--exclude-all-symbols', + '-Wl,--gc-sections', + '-static-libgcc', + language: 'c', + ) +endif + +cc = meson.get_compiler('c') +shlwapi_lib = cc.find_library('shlwapi') +dinput8_lib = cc.find_library('dinput8') +dxguid_lib = cc.find_library('dxguid') +xinput_lib = cc.find_library('xinput') + +inc = include_directories('.') +capnhook = subproject('capnhook') + +subdir('amex') +subdir('iccard') +subdir('board') +subdir('hooklib') +subdir('jvs') +subdir('platform') +subdir('util') + +subdir('gfxhook') + +subdir('mu3io') +subdir('mercuryio') + +subdir('minihook') +subdir('mu3hook') +subdir('mercuryhook') diff --git a/minihook/dllmain.c b/minihook/dllmain.c new file mode 100644 index 0000000..cc4b402 --- /dev/null +++ b/minihook/dllmain.c @@ -0,0 +1,75 @@ +#include + +#include + +#include "amex/config.h" +#include "amex/ds.h" + +#include "hook/process.h" + +#include "hooklib/spike.h" + +#include "platform/clock.h" +#include "platform/config.h" +#include "platform/ttxsec.h" + +#include "util/dprintf.h" + +static process_entry_t app_startup; + +static DWORD CALLBACK app_pre_startup(void) +{ + struct clock_config clock_cfg; + struct ds_config ds_cfg; + struct ttxsec_config ttxsec_cfg; + HRESULT hr; + + dprintf("--- Begin %s ---\n", __func__); + + clock_config_load(&clock_cfg, L".\\taitools.ini"); + ds_config_load(&ds_cfg, L".\\taitools.ini"); + ttxsec_config_load(&ttxsec_cfg, L".\\taitools.ini"); + spike_hook_init(L".\\taitools.ini"); + + hr = clock_hook_init(&clock_cfg); + + if (FAILED(hr)) { + goto fail; + } + + hr = ttxsec_hook_init(&ttxsec_cfg, 1); + + if (FAILED(hr)) { + goto fail; + } + + hr = ds_hook_init(&ds_cfg); + + if (FAILED(hr)) { + goto fail; + } + + dprintf("--- End %s ---\n", __func__); + + return app_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + hr = process_hijack_startup(app_pre_startup, &app_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/minihook/meson.build b/minihook/meson.build new file mode 100644 index 0000000..8acbf1e --- /dev/null +++ b/minihook/meson.build @@ -0,0 +1,19 @@ +shared_library( + 'minihook', + name_prefix : '', + include_directories: inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + link_with : [ + amex_lib, + hooklib_lib, + platform_lib, + util_lib, + ], + sources : [ + 'dllmain.c', + ], +) diff --git a/mu3hook/config.c b/mu3hook/config.c new file mode 100644 index 0000000..6e3991d --- /dev/null +++ b/mu3hook/config.c @@ -0,0 +1,44 @@ +#include +#include + +#include "board/config.h" + +#include "gfxhook/config.h" + +#include "hooklib/config.h" +#include "hooklib/dvd.h" + +#include "mu3hook/config.h" + +#include "platform/config.h" + +void mu3_dll_config_load( + struct mu3_dll_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + GetPrivateProfileStringW( + L"mu3io", + L"path", + L"", + cfg->path, + _countof(cfg->path), + filename); +} + +void mu3_hook_config_load( + struct mu3_hook_config *cfg, + const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + platform_config_load(&cfg->platform, filename); + aime_config_load(&cfg->aime, filename); + dvd_config_load(&cfg->dvd, filename); + io4_config_load(&cfg->io4, filename); + gfx_config_load(&cfg->gfx, filename); + mu3_dll_config_load(&cfg->dll, filename); +} diff --git a/mu3hook/config.h b/mu3hook/config.h new file mode 100644 index 0000000..58af239 --- /dev/null +++ b/mu3hook/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "board/config.h" + +#include "gfxhook/gfx.h" + +#include "hooklib/dvd.h" + +#include "mu3hook/mu3-dll.h" + +#include "platform/config.h" + +struct mu3_hook_config { + struct platform_config platform; + struct aime_config aime; + struct dvd_config dvd; + struct io4_config io4; + struct gfx_config gfx; + struct mu3_dll_config dll; +}; + +void mu3_dll_config_load( + struct mu3_dll_config *cfg, + const wchar_t *filename); + +void mu3_hook_config_load( + struct mu3_hook_config *cfg, + const wchar_t *filename); diff --git a/mu3hook/dllmain.c b/mu3hook/dllmain.c new file mode 100644 index 0000000..23ded9d --- /dev/null +++ b/mu3hook/dllmain.c @@ -0,0 +1,126 @@ +#include + +#include + +#include "board/io4.h" +#include "board/sg-reader.h" +#include "board/vfd.h" + +#include "gfxhook/d3d9.h" +#include "gfxhook/d3d11.h" +#include "gfxhook/dxgi.h" +#include "gfxhook/gfx.h" + +#include "hook/process.h" + +#include "hooklib/dvd.h" +#include "hooklib/serial.h" +#include "hooklib/spike.h" + +#include "mu3hook/config.h" +#include "mu3hook/io4.h" +#include "mu3hook/mu3-dll.h" +#include "mu3hook/unity.h" + +#include "platform/platform.h" + +#include "util/dprintf.h" + +static HMODULE mu3_hook_mod; +static process_entry_t mu3_startup; +static struct mu3_hook_config mu3_hook_cfg; + +static DWORD CALLBACK mu3_pre_startup(void) +{ + HRESULT hr; + + dprintf("--- Begin mu3_pre_startup ---\n"); + + /* Load config */ + + mu3_hook_config_load(&mu3_hook_cfg, L".\\taitools.ini"); + + /* Hook Win32 APIs */ + + dvd_hook_init(&mu3_hook_cfg.dvd, mu3_hook_mod); + gfx_hook_init(&mu3_hook_cfg.gfx); + gfx_d3d9_hook_init(&mu3_hook_cfg.gfx, mu3_hook_mod); + gfx_d3d11_hook_init(&mu3_hook_cfg.gfx, mu3_hook_mod); + gfx_dxgi_hook_init(&mu3_hook_cfg.gfx, mu3_hook_mod); + serial_hook_init(); + + /* Initialize emulation hooks */ + + hr = platform_hook_init( + &mu3_hook_cfg.platform, + "SDDT", + "ACA1", + mu3_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = sg_reader_hook_init(&mu3_hook_cfg.aime, 1, mu3_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = vfd_hook_init(2); + + if (FAILED(hr)) { + goto fail; + } + + hr = mu3_dll_init(&mu3_hook_cfg.dll, mu3_hook_mod); + + if (FAILED(hr)) { + goto fail; + } + + hr = mu3_io4_hook_init(&mu3_hook_cfg.io4); + + if (FAILED(hr)) { + goto fail; + } + + /* Initialize Unity native plugin DLL hooks + + There seems to be an issue with other DLL hooks if `LoadLibraryW` is + hooked earlier in the `mu3hook` initialization. */ + + unity_hook_init(); + + /* Initialize debug helpers */ + + spike_hook_init(L".\\taitools.ini"); + + dprintf("--- End mu3_pre_startup ---\n"); + + /* Jump to EXE start address */ + + return mu3_startup(); + +fail: + ExitProcess(EXIT_FAILURE); +} + +BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) +{ + HRESULT hr; + + if (cause != DLL_PROCESS_ATTACH) { + return TRUE; + } + + mu3_hook_mod = mod; + + hr = process_hijack_startup(mu3_pre_startup, &mu3_startup); + + if (!SUCCEEDED(hr)) { + dprintf("Failed to hijack process startup: %x\n", (int) hr); + } + + return SUCCEEDED(hr); +} diff --git a/mu3hook/io4.c b/mu3hook/io4.c new file mode 100644 index 0000000..7edcb0c --- /dev/null +++ b/mu3hook/io4.c @@ -0,0 +1,120 @@ +#include + +#include +#include +#include + +#include "board/io4.h" + +#include "mu3hook/mu3-dll.h" + +#include "util/dprintf.h" + +static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state); + +static const struct io4_ops mu3_io4_ops = { + .poll = mu3_io4_poll, +}; + +HRESULT mu3_io4_hook_init(const struct io4_config *cfg) +{ + HRESULT hr; + + assert(mu3_dll.init != NULL); + + hr = io4_hook_init(cfg, &mu3_io4_ops, NULL); + + if (FAILED(hr)) { + return hr; + } + + return mu3_dll.init(); +} + +static HRESULT mu3_io4_poll(void *ctx, struct io4_state *state) +{ + uint8_t opbtn; + uint8_t left; + uint8_t right; + int16_t lever; + HRESULT hr; + + assert(mu3_dll.poll != NULL); + assert(mu3_dll.get_opbtns != NULL); + assert(mu3_dll.get_gamebtns != NULL); + assert(mu3_dll.get_lever != NULL); + + memset(state, 0, sizeof(*state)); + + hr = mu3_dll.poll(); + + if (FAILED(hr)) { + return hr; + } + + opbtn = 0; + left = 0; + right = 0; + lever = 0; + + mu3_dll.get_opbtns(&opbtn); + mu3_dll.get_gamebtns(&left, &right); + mu3_dll.get_lever(&lever); + + if (opbtn & MU3_IO_OPBTN_TEST) { + state->buttons[0] |= IO4_BUTTON_TEST; + } + + if (opbtn & MU3_IO_OPBTN_SERVICE) { + state->buttons[0] |= IO4_BUTTON_SERVICE; + } + + if (left & MU3_IO_GAMEBTN_1) { + state->buttons[0] |= 1 << 0; + } + + if (left & MU3_IO_GAMEBTN_2) { + state->buttons[0] |= 1 << 5; + } + + if (left & MU3_IO_GAMEBTN_3) { + state->buttons[0] |= 1 << 4; + } + + if (right & MU3_IO_GAMEBTN_1) { + state->buttons[0] |= 1 << 1; + } + + if (right & MU3_IO_GAMEBTN_2) { + state->buttons[1] |= 1 << 0; + } + + if (right & MU3_IO_GAMEBTN_3) { + state->buttons[0] |= 1 << 15; + } + + if (left & MU3_IO_GAMEBTN_MENU) { + state->buttons[1] |= 1 << 14; + } + + if (right & MU3_IO_GAMEBTN_MENU) { + state->buttons[0] |= 1 << 13; + } + + if (!(left & MU3_IO_GAMEBTN_SIDE)) { + state->buttons[1] |= 1 << 15; /* L-Side, active-low */ + } + + if (!(right & MU3_IO_GAMEBTN_SIDE)) { + state->buttons[0] |= 1 << 14; /* R-Side, active-low */ + } + + /* Lever increases right-to-left, not left-to-right. + + Use 0x7FFF as the center point instead of 0x8000; the latter would + overflow when the lever pos is INT16_MIN. */ + + state->adcs[0] = 0x7FFF - lever; + + return S_OK; +} diff --git a/mu3hook/io4.h b/mu3hook/io4.h new file mode 100644 index 0000000..acb53b3 --- /dev/null +++ b/mu3hook/io4.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#include "board/io4.h" + +HRESULT mu3_io4_hook_init(const struct io4_config *cfg); diff --git a/mu3hook/meson.build b/mu3hook/meson.build new file mode 100644 index 0000000..27ba7f7 --- /dev/null +++ b/mu3hook/meson.build @@ -0,0 +1,33 @@ +shared_library( + 'mu3hook', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + vs_module_defs : 'mu3hook.def', + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + capnhook.get_variable('hooklib_dep'), + xinput_lib, + ], + link_with : [ + aimeio_lib, + board_lib, + gfxhook_lib, + hooklib_lib, + mu3io_lib, + platform_lib, + util_lib, + ], + sources : [ + 'config.c', + 'config.h', + 'dllmain.c', + 'io4.c', + 'io4.h', + 'mu3-dll.c', + 'mu3-dll.h', + 'unity.h', + 'unity.c', + ], +) diff --git a/mu3hook/mu3-dll.c b/mu3hook/mu3-dll.c new file mode 100644 index 0000000..84e785c --- /dev/null +++ b/mu3hook/mu3-dll.c @@ -0,0 +1,112 @@ +#include + +#include +#include + +#include "mu3hook/mu3-dll.h" + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +const struct dll_bind_sym mu3_dll_syms[] = { + { + .sym = "mu3_io_init", + .off = offsetof(struct mu3_dll, init), + }, { + .sym = "mu3_io_poll", + .off = offsetof(struct mu3_dll, poll), + }, { + .sym = "mu3_io_get_opbtns", + .off = offsetof(struct mu3_dll, get_opbtns), + }, { + .sym = "mu3_io_get_gamebtns", + .off = offsetof(struct mu3_dll, get_gamebtns), + }, { + .sym = "mu3_io_get_lever", + .off = offsetof(struct mu3_dll, get_lever), + } +}; + +struct mu3_dll mu3_dll; + +// Copypasta DLL binding and diagnostic message boilerplate. +// Not much of this lends itself to being easily factored out. Also there +// will be a lot of API-specific branching code here eventually as new API +// versions get defined, so even though these functions all look the same +// now this won't remain the case forever. + +HRESULT mu3_dll_init(const struct mu3_dll_config *cfg, HINSTANCE self) +{ + uint16_t (*get_api_version)(void); + const struct dll_bind_sym *sym; + HINSTANCE owned; + HINSTANCE src; + HRESULT hr; + + assert(cfg != NULL); + assert(self != NULL); + + if (cfg->path[0] != L'\0') { + owned = LoadLibraryW(cfg->path); + + if (owned == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Ongeki IO: Failed to load IO DLL: %lx: %S\n", + hr, + cfg->path); + + goto end; + } + + dprintf("Ongeki IO: Using custom IO DLL: %S\n", cfg->path); + src = owned; + } else { + owned = NULL; + src = self; + } + + get_api_version = (void *) GetProcAddress(src, "mu3_io_get_api_version"); + + if (get_api_version != NULL) { + mu3_dll.api_version = get_api_version(); + } else { + mu3_dll.api_version = 0x0100; + dprintf("Custom IO DLL does not expose mu3_io_get_api_version, " + "assuming API version 1.0.\n" + "Please ask the developer to update their DLL.\n"); + } + + if (mu3_dll.api_version >= 0x0200) { + hr = E_NOTIMPL; + dprintf("Ongeki IO: Custom IO DLL implements an unsupported " + "API version (%#04x). Please update Taitools.\n", + mu3_dll.api_version); + + goto end; + } + + sym = mu3_dll_syms; + hr = dll_bind(&mu3_dll, src, &sym, _countof(mu3_dll_syms)); + + if (FAILED(hr)) { + if (src != self) { + dprintf("Ongeki IO: Custom IO DLL does not provide function " + "\"%s\". Please contact your IO DLL's developer for " + "further assistance.\n", + sym->sym); + + goto end; + } else { + dprintf("Internal error: could not reflect \"%s\"\n", sym->sym); + } + } + + owned = NULL; + +end: + if (owned != NULL) { + FreeLibrary(owned); + } + + return hr; +} diff --git a/mu3hook/mu3-dll.h b/mu3hook/mu3-dll.h new file mode 100644 index 0000000..41f280f --- /dev/null +++ b/mu3hook/mu3-dll.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "mu3io/mu3io.h" + +struct mu3_dll { + uint16_t api_version; + HRESULT (*init)(void); + HRESULT (*poll)(void); + void (*get_opbtns)(uint8_t *opbtn); + void (*get_gamebtns)(uint8_t *left, uint8_t *right); + void (*get_lever)(int16_t *pos); +}; + +struct mu3_dll_config { + wchar_t path[MAX_PATH]; +}; + +extern struct mu3_dll mu3_dll; + +HRESULT mu3_dll_init(const struct mu3_dll_config *cfg, HINSTANCE self); diff --git a/mu3hook/mu3hook.def b/mu3hook/mu3hook.def new file mode 100644 index 0000000..d90abd5 --- /dev/null +++ b/mu3hook/mu3hook.def @@ -0,0 +1,25 @@ +LIBRARY mu3hook + +EXPORTS + CreateDXGIFactory + CreateDXGIFactory1 + CreateDXGIFactory2 + D3D11CreateDevice + D3D11CreateDeviceAndSwapChain + Direct3DCreate9 + aime_io_get_api_version + aime_io_init + aime_io_led_set_color + aime_io_nfc_get_aime_id + aime_io_nfc_get_felica_id + aime_io_nfc_poll + amDllVideoClose @2 + amDllVideoGetVBiosVersion @4 + amDllVideoOpen @1 + amDllVideoSetResolution @3 + mu3_io_get_api_version + mu3_io_get_gamebtns + mu3_io_get_lever + mu3_io_get_opbtns + mu3_io_init + mu3_io_poll diff --git a/mu3hook/unity.c b/mu3hook/unity.c new file mode 100644 index 0000000..efefc32 --- /dev/null +++ b/mu3hook/unity.c @@ -0,0 +1,95 @@ +#include + +#include + +#include "hook/table.h" + +#include "hooklib/dll.h" +#include "hooklib/path.h" + +#include "util/dprintf.h" + +static void dll_hook_insert_hooks(HMODULE target); + +static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name); +static HMODULE (WINAPI *next_LoadLibraryW)(const wchar_t *name); + +static const struct hook_symbol unity_kernel32_syms[] = { + { + .name = "LoadLibraryW", + .patch = my_LoadLibraryW, + .link = (void **) &next_LoadLibraryW, + }, +}; + +static const wchar_t *target_modules[] = { + L"mono.dll", + L"cri_ware_unity.dll", +}; +static const size_t target_modules_len = _countof(target_modules); + +void unity_hook_init(void) +{ + dll_hook_insert_hooks(NULL); +} + +static void dll_hook_insert_hooks(HMODULE target) +{ + hook_table_apply( + target, + "kernel32.dll", + unity_kernel32_syms, + _countof(unity_kernel32_syms)); +} + +static HMODULE WINAPI my_LoadLibraryW(const wchar_t *name) +{ + const wchar_t *name_end; + const wchar_t *target_module; + bool already_loaded; + HMODULE result; + size_t name_len; + size_t target_module_len; + + if (name == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return NULL; + } + + // Check if the module is already loaded + already_loaded = GetModuleHandleW(name) != NULL; + + // Must call the next handler so the DLL reference count is incremented + result = next_LoadLibraryW(name); + + if (!already_loaded && result != NULL) { + name_len = wcslen(name); + + for (size_t i = 0; i < target_modules_len; i++) { + target_module = target_modules[i]; + target_module_len = wcslen(target_module); + + // Check if the newly loaded library is at least the length of + // the name of the target module + if (name_len < target_module_len) { + continue; + } + + name_end = &name[name_len - target_module_len]; + + // Check if the name of the newly loaded library is one of the + // modules the path hooks should be injected into + if (_wcsicmp(name_end, target_module) != 0) { + continue; + } + + dprintf("Unity: Loaded %S\n", target_module); + + dll_hook_insert_hooks(result); + path_hook_insert_hooks(result); + } + } + + return result; +} diff --git a/mu3hook/unity.h b/mu3hook/unity.h new file mode 100644 index 0000000..99c3bd9 --- /dev/null +++ b/mu3hook/unity.h @@ -0,0 +1,3 @@ +#pragma once + +void unity_hook_init(void); diff --git a/mu3io/meson.build b/mu3io/meson.build new file mode 100644 index 0000000..3d6e60e --- /dev/null +++ b/mu3io/meson.build @@ -0,0 +1,14 @@ +mu3io_lib = static_library( + 'mu3io', + name_prefix : '', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + xinput_lib, + ], + sources : [ + 'mu3io.c', + 'mu3io.h', + ], +) diff --git a/mu3io/mu3io.c b/mu3io/mu3io.c new file mode 100644 index 0000000..0bbd37f --- /dev/null +++ b/mu3io/mu3io.c @@ -0,0 +1,148 @@ +#include +#include + +#include +#include + +#include "mu3io/mu3io.h" + +static uint8_t mu3_opbtn; +static uint8_t mu3_left_btn; +static uint8_t mu3_right_btn; +static int16_t mu3_lever_pos; +static int16_t mu3_lever_xpos; + +uint16_t mu3_io_get_api_version(void) +{ + return 0x0100; +} + +HRESULT mu3_io_init(void) +{ + return S_OK; +} + +HRESULT mu3_io_poll(void) +{ + int lever; + int xlever; + XINPUT_STATE xi; + WORD xb; + + mu3_opbtn = 0; + mu3_left_btn = 0; + mu3_right_btn = 0; + + if (GetAsyncKeyState('1') & 0x8000) { + mu3_opbtn |= MU3_IO_OPBTN_TEST; + } + + if (GetAsyncKeyState('2') & 0x8000) { + mu3_opbtn |= MU3_IO_OPBTN_SERVICE; + } + + memset(&xi, 0, sizeof(xi)); + XInputGetState(0, &xi); + xb = xi.Gamepad.wButtons; + + if (xb & XINPUT_GAMEPAD_DPAD_LEFT) { + mu3_left_btn |= MU3_IO_GAMEBTN_1; + } + + if (xb & XINPUT_GAMEPAD_DPAD_UP) { + mu3_left_btn |= MU3_IO_GAMEBTN_2; + } + + if (xb & XINPUT_GAMEPAD_DPAD_RIGHT) { + mu3_left_btn |= MU3_IO_GAMEBTN_3; + } + + if (xb & XINPUT_GAMEPAD_X) { + mu3_right_btn |= MU3_IO_GAMEBTN_1; + } + + if (xb & XINPUT_GAMEPAD_Y) { + mu3_right_btn |= MU3_IO_GAMEBTN_2; + } + + if (xb & XINPUT_GAMEPAD_B) { + mu3_right_btn |= MU3_IO_GAMEBTN_3; + } + + if (xb & XINPUT_GAMEPAD_BACK) { + mu3_left_btn |= MU3_IO_GAMEBTN_MENU; + } + + if (xb & XINPUT_GAMEPAD_START) { + mu3_right_btn |= MU3_IO_GAMEBTN_MENU; + } + + if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) { + mu3_left_btn |= MU3_IO_GAMEBTN_SIDE; + } + + if (xb & XINPUT_GAMEPAD_RIGHT_SHOULDER) { + mu3_right_btn |= MU3_IO_GAMEBTN_SIDE; + } + + lever = mu3_lever_pos; + + if (abs(xi.Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) { + lever += xi.Gamepad.sThumbLX / 24; + } + + if (abs(xi.Gamepad.sThumbRX) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) { + lever += xi.Gamepad.sThumbRX / 24; + } + + if (lever < INT16_MIN) { + lever = INT16_MIN; + } + + if (lever > INT16_MAX) { + lever = INT16_MAX; + } + + mu3_lever_pos = lever; + + xlever = mu3_lever_pos + - xi.Gamepad.bLeftTrigger * 64 + + xi.Gamepad.bRightTrigger * 64; + + if (xlever < INT16_MIN) { + xlever = INT16_MIN; + } + + if (xlever > INT16_MAX) { + xlever = INT16_MAX; + } + + mu3_lever_xpos = xlever; + + return S_OK; +} + +void mu3_io_get_opbtns(uint8_t *opbtn) +{ + if (opbtn != NULL) { + *opbtn = mu3_opbtn; + } +} + +void mu3_io_get_gamebtns(uint8_t *left, uint8_t *right) +{ + if (left != NULL) { + *left = mu3_left_btn; + } + + if (right != NULL ){ + *right = mu3_right_btn; + } +} + +void mu3_io_get_lever(int16_t *pos) +{ + if (pos != NULL) { + *pos = mu3_lever_xpos; + } +} diff --git a/mu3io/mu3io.h b/mu3io/mu3io.h new file mode 100644 index 0000000..d46a475 --- /dev/null +++ b/mu3io/mu3io.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#include + +enum { + MU3_IO_OPBTN_TEST = 0x01, + MU3_IO_OPBTN_SERVICE = 0x02, +}; + +enum { + MU3_IO_GAMEBTN_1 = 0x01, + MU3_IO_GAMEBTN_2 = 0x02, + MU3_IO_GAMEBTN_3 = 0x04, + MU3_IO_GAMEBTN_SIDE = 0x08, + MU3_IO_GAMEBTN_MENU = 0x10, +}; + +/* Get the version of the Ongeki IO API that this DLL supports. This + function should return a positive 16-bit integer, where the high byte is + the major version and the low byte is the minor version (as defined by the + Semantic Versioning standard). + + The latest API version as of this writing is 0x0100. */ + +uint16_t mu3_io_get_api_version(void); + +/* Initialize the IO DLL. This is the second function that will be called on + your DLL, after mu3_io_get_api_version. + + All subsequent calls to this API may originate from arbitrary threads. + + Minimum API version: 0x0100 */ + +HRESULT mu3_io_init(void); + +/* Send any queued outputs (of which there are currently none, though this may + change in subsequent API versions) and retrieve any new inputs. + + Minimum API version: 0x0100 */ + +HRESULT mu3_io_poll(void); + +/* Get the state of the cabinet's operator buttons as of the last poll. See + MU3_IO_OPBTN enum above: this contains bit mask definitions for button + states returned in *opbtn. All buttons are active-high. + + Minimum API version: 0x0100 */ + +void mu3_io_get_opbtns(uint8_t *opbtn); + +/* Get the state of the cabinet's gameplay buttons as of the last poll. See + MU3_IO_GAMEBTN enum above for bit mask definitions. Inputs are split into + a left hand side set of inputs and a right hand side set of inputs: the bit + mappings are the same in both cases. + + All buttons are active-high, even though some buttons' electrical signals + on a real cabinet are active-low. + + Minimum API version: 0x0100 */ + +void mu3_io_get_gamebtns(uint8_t *left, uint8_t *right); + +/* Get the position of the cabinet lever as of the last poll. The center + position should be equal to or close to zero. + + The operator will be required to calibrate the lever's range of motion on + first power-on, so the lever position reported through this API does not + need to perfectly centered or cover every single position value possible, + but it should be reasonably close in order to make things easier for the + operator. + + The calibration screen displays the leftmost and rightmost position signal + returned from the cabinet's ADC encoder as a pair of raw two's complement + hexadecimal values. On a real cabinet these leftmost and rightmost + positions are somewhere around 0xB000 and 0x5000 respectively (remember + that negative values i.e. left positions have a high most-significant bit), + although these values can easily vary by +/- 0x1000 across different + cabinets. + + Minimum API version: 0x0100 */ + +void mu3_io_get_lever(int16_t *pos); diff --git a/platform/clock.c b/platform/clock.c new file mode 100644 index 0000000..b67b111 --- /dev/null +++ b/platform/clock.c @@ -0,0 +1,259 @@ +#include + +#include +#include + +#include "hook/table.h" + +#include "platform/clock.h" + +#include "util/dprintf.h" + +static void WINAPI my_GetSystemTimeAsFileTime(FILETIME *out); +static BOOL WINAPI my_GetLocalTime(SYSTEMTIME *out); +static BOOL WINAPI my_GetSystemTime(SYSTEMTIME *out); +static DWORD WINAPI my_GetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo); +static BOOL WINAPI my_SetLocalTime(SYSTEMTIME *in); +static BOOL WINAPI my_SetSystemTime(SYSTEMTIME *in); +static BOOL WINAPI my_SetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo); + +static BOOL (WINAPI * next_GetSystemTimeAsFileTime)(FILETIME *out); +static int64_t clock_current_day; +static bool clock_time_warp; + +static const struct hook_symbol clock_base_hook_syms[] = { + { + .name = "GetSystemTimeAsFileTime", + .patch = my_GetSystemTimeAsFileTime, + .link = (void **) &next_GetSystemTimeAsFileTime, + } +}; + +static const struct hook_symbol clock_read_hook_syms[] = { + { + .name = "GetLocalTime", + .patch = my_GetLocalTime, + }, { + .name = "GetSystemTime", + .patch = my_GetSystemTime, + }, { + .name = "GetTimeZoneInformation", + .patch = my_GetTimeZoneInformation, + }, +}; + +static const struct hook_symbol clock_write_hook_syms[] = { + { + .name = "SetLocalTime", + .patch = my_SetLocalTime, + }, { + .name = "SetSystemTime", + .patch = my_SetSystemTime, + }, { + .name = "SetTimeZoneInformation", + .patch = my_SetTimeZoneInformation, + }, +}; + +/* FILETIME is expressed in 100ns i.e. 0.1us i.e. 10^-7 sec units. + No official name for these units is given so let's call them "jiffies". */ + +#define jiffies_per_sec 10000000LL +#define jiffies_per_hour (jiffies_per_sec * 3600LL) +#define jiffies_per_day (jiffies_per_hour * 24LL) + +static void WINAPI my_GetSystemTimeAsFileTime(FILETIME *out) +{ + FILETIME in; + int64_t day; + int64_t real_jiffies; + int64_t real_jiffies_biased; + int64_t real_time; + int64_t fake_time; + int64_t fake_jiffies_biased; + int64_t fake_jiffies; + + if (!clock_time_warp) { + next_GetSystemTimeAsFileTime(out); + + return; + } + + if (out == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return; + } + + /* Get and convert real jiffies */ + + next_GetSystemTimeAsFileTime(&in); + real_jiffies = (((int64_t) in.dwHighDateTime) << 32) | in.dwLowDateTime; + + /* Keepout period is JST [02:00, 07:00), which is equivalent to + UTC [17:00, 22:00). Bias UTC forward by 2 hours, changing this interval + to [19:00, 00:00) to make the math easier. We revert this bias later. */ + + real_jiffies_biased = real_jiffies + 2LL * jiffies_per_hour; + + /* Split date and time */ + + day = real_jiffies_biased / jiffies_per_day; + real_time = real_jiffies_biased % jiffies_per_day; + + /* Debug log */ + + if (clock_current_day != 0 && clock_current_day != day) { + dprintf("\n*** CLOCK JUMP! ***\n\n"); + } + + clock_current_day = day; + + /* We want to skip the final five hours of our UTC+2 biased reference frame, + so scale time-of-day by 19/24. */ + + fake_time = (real_time * 19LL) / 24LL; + + /* Un-split date and time */ + + fake_jiffies_biased = day * jiffies_per_day + fake_time; + + /* Revert bias */ + + fake_jiffies = fake_jiffies_biased - 2LL * jiffies_per_hour; + + /* Return result */ + + out->dwLowDateTime = fake_jiffies; + out->dwHighDateTime = fake_jiffies >> 32; +} + +static BOOL WINAPI my_GetLocalTime(SYSTEMTIME *out) +{ + ULARGE_INTEGER arith; + FILETIME linear; + + /* Force JST */ + + my_GetSystemTimeAsFileTime(&linear); + + arith.LowPart = linear.dwLowDateTime; + arith.HighPart = linear.dwHighDateTime; + arith.QuadPart += 9ULL * jiffies_per_hour; + linear.dwLowDateTime = arith.LowPart; + linear.dwHighDateTime = arith.HighPart; + + return FileTimeToSystemTime(&linear, out); +} + +static BOOL WINAPI my_GetSystemTime(SYSTEMTIME *out) +{ + FILETIME linear; + BOOL ok; + + my_GetSystemTimeAsFileTime(&linear); + ok = FileTimeToSystemTime(&linear, out); + + if (!ok) { + return ok; + } + +#if 0 + static int last_second; + + if (out->wSecond != last_second) { + dprintf("%04i/%02i/%02i %02i:%02i:%02i\n", + out->wYear, + out->wMonth, + out->wDay, + out->wHour, + out->wMinute, + out->wSecond); + } + + last_second = out->wSecond; +#endif + + return TRUE; +} + +static DWORD WINAPI my_GetTimeZoneInformation(TIME_ZONE_INFORMATION *tzinfo) +{ + dprintf("Clock: Returning JST timezone\n"); + + if (tzinfo == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return TIME_ZONE_ID_INVALID; + } + + /* Force JST (UTC+9), SEGA games malfunction in any other time zone. + Strings and boundary times don't matter, we only set the offset. */ + + memset(tzinfo, 0, sizeof(*tzinfo)); + tzinfo->Bias = -9 * 60; + + SetLastError(ERROR_SUCCESS); + + /* "Unknown" here means that this region does not observe DST */ + + return TIME_ZONE_ID_UNKNOWN; +} + +static BOOL WINAPI my_SetLocalTime(SYSTEMTIME *in) +{ + dprintf("Clock: Blocked local time update\n"); + + return TRUE; +} + +static BOOL WINAPI my_SetSystemTime(SYSTEMTIME *in) +{ + dprintf("Clock: Blocked system time update\n"); + + return TRUE; +} + +static BOOL WINAPI my_SetTimeZoneInformation(TIME_ZONE_INFORMATION *in) +{ + dprintf("Clock: Blocked timezone update\n"); + + return TRUE; +} + +HRESULT clock_hook_init(const struct clock_config *cfg) +{ + assert(cfg != NULL); + + clock_time_warp = cfg->timewarp; + + if (cfg->timezone || cfg->timewarp || !cfg->writeable) { + /* All the clock hooks require the core GSTAFT hook to be installed */ + /* Note the ! up there btw. */ + + hook_table_apply( + NULL, + "kernel32.dll", + clock_base_hook_syms, + _countof(clock_base_hook_syms)); + } + + if (cfg->timezone) { + hook_table_apply( + NULL, + "kernel32.dll", + clock_read_hook_syms, + _countof(clock_read_hook_syms)); + } + + if (!cfg->writeable) { + /* Install hook if this config parameter is FALSE! */ + hook_table_apply( + NULL, + "kernel32.dll", + clock_write_hook_syms, + _countof(clock_write_hook_syms)); + } + + return S_OK; +} diff --git a/platform/clock.h b/platform/clock.h new file mode 100644 index 0000000..4d67754 --- /dev/null +++ b/platform/clock.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include + +struct clock_config { + bool timezone; + bool timewarp; + bool writeable; +}; + +HRESULT clock_hook_init(const struct clock_config *cfg); diff --git a/platform/config.c b/platform/config.c new file mode 100644 index 0000000..86cb9c8 --- /dev/null +++ b/platform/config.c @@ -0,0 +1,278 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "platform/clock.h" +#include "platform/config.h" +#include "platform/dns.h" +#include "platform/misc.h" +#include "platform/netenv.h" +#include "platform/ttxsec.h" +#include "platform/platform.h" +#include "platform/syscfg.h" +#include "platform/vfs.h" + +void platform_config_load(struct platform_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + clock_config_load(&cfg->clock, filename); + dns_config_load(&cfg->dns, filename); + misc_config_load(&cfg->misc, filename); + netenv_config_load(&cfg->netenv, filename); + ttxsec_config_load(&cfg->ttxsec, filename); + vfs_config_load(&cfg->vfs, filename); + syscfg_config_load(&cfg->syscfg, filename); +} + +void clock_config_load(struct clock_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->timezone = GetPrivateProfileIntW(L"clock", L"timezone", 1, filename); + cfg->timewarp = GetPrivateProfileIntW(L"clock", L"timewarp", 0, filename); + cfg->writeable = GetPrivateProfileIntW( + L"clock", + L"writeable", + 0, + filename); +} + +void dns_config_load(struct dns_config *cfg, const wchar_t *filename) +{ + wchar_t default_[128]; + + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"dns", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"dns", + L"default", + L"localhost", + default_, + _countof(default_), + filename); + + GetPrivateProfileStringW( + L"dns", + L"router", + default_, + cfg->router, + _countof(cfg->router), + filename); + + GetPrivateProfileStringW( + L"dns", + L"cert", + default_, + cfg->cert, + _countof(cfg->cert), + filename); + + GetPrivateProfileStringW( + L"dns", + L"data", + default_, + cfg->data, + _countof(cfg->data), + filename); + + GetPrivateProfileStringW( + L"dns", + L"proxy", + default_, + cfg->proxy, + _countof(cfg->proxy), + filename); + + GetPrivateProfileStringW( + L"dns", + L"nesys", + default_, + cfg->nesys, + _countof(cfg->nesys), + filename); + + GetPrivateProfileStringW( + L"dns", + L"fjm", + default_, + cfg->fjm, + _countof(cfg->fjm), + filename); +} + +void misc_config_load(struct misc_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"misc", L"enable", 1, filename); +} + +void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename) +{ + wchar_t mac_addr[18]; + + assert(cfg != NULL); + assert(filename != NULL); + + memset(cfg, 0, sizeof(*cfg)); + + cfg->enable = GetPrivateProfileIntW(L"netenv", L"enable", 0, filename); + + cfg->addr_suffix = GetPrivateProfileIntW( + L"netenv", + L"addrSuffix", + 11, + filename); + + cfg->router_suffix = GetPrivateProfileIntW( + L"netenv", + L"routerSuffix", + 254, + filename); + + GetPrivateProfileStringW( + L"netenv", + L"macAddr", + L"01:02:03:04:05:06", + mac_addr, + _countof(mac_addr), + filename); + + swscanf(mac_addr, + L"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &cfg->mac_addr[0], + &cfg->mac_addr[1], + &cfg->mac_addr[2], + &cfg->mac_addr[3], + &cfg->mac_addr[4], + &cfg->mac_addr[5], + &cfg->mac_addr[6]); +} + +void ttxsec_config_load(struct ttxsec_config *cfg, const wchar_t *filename) +{ + wchar_t keychip_id[17]; + wchar_t game_id[5]; + wchar_t platform_id[5]; + wchar_t subnet[16]; + unsigned int ip[4]; + size_t i; + + assert(cfg != NULL); + assert(filename != NULL); + + memset(cfg, 0, sizeof(*cfg)); + memset(keychip_id, 0, sizeof(keychip_id)); + memset(game_id, 0, sizeof(game_id)); + memset(platform_id, 0, sizeof(platform_id)); + memset(subnet, 0, sizeof(subnet)); + + cfg->enable = GetPrivateProfileIntW(L"keychip", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"keychip", + L"id", + L"A69E-01A88888888", + keychip_id, + _countof(keychip_id), + filename); + + GetPrivateProfileStringW( + L"keychip", + L"gameId", + L"", + game_id, + _countof(game_id), + filename); + + GetPrivateProfileStringW( + L"keychip", + L"platformId", + L"", + platform_id, + _countof(platform_id), + filename); + + cfg->region = GetPrivateProfileIntW(L"keychip", L"region", 1, filename); + cfg->billing_type = GetPrivateProfileIntW(L"keychip", L"billingType", 1, filename); + cfg->system_flag = GetPrivateProfileIntW( + L"keychip", + L"systemFlag", + 0x64, + filename); + + GetPrivateProfileStringW( + L"keychip", + L"subnet", + L"192.168.100.0", + subnet, + _countof(subnet), + filename); + + for (i = 0 ; i < 16 ; i++) { + cfg->keychip_id[i] = (char) keychip_id[i]; + } + + for (i = 0 ; i < 4 ; i++) { + cfg->game_id[i] = (char) game_id[i]; + } + + for (i = 0 ; i < 4 ; i++) { + cfg->platform_id[i] = (char) platform_id[i]; + } + + swscanf(subnet, L"%u.%u.%u.%u", &ip[0], &ip[1], &ip[2], &ip[3]); + cfg->subnet = (ip[0] << 24) | (ip[1] << 16) | (ip[2] << 8) | 0; + + GetPrivateProfileStringW( + L"keychip", + L"billingCa", + L"DEVICE\\ca.crt", + cfg->billing_ca, + _countof(cfg->billing_ca), + filename); + + GetPrivateProfileStringW( + L"keychip", + L"billingPub", + L"DEVICE\\billing.pub", + cfg->billing_pub, + _countof(cfg->billing_pub), + filename); +} + +void syscfg_config_load(struct syscfg_config *cfg, const wchar_t *filename) +{ + cfg->enable = GetPrivateProfileIntW(L"syscfg", L"enable", 1, filename); + cfg->log_level = GetPrivateProfileIntW(L"syscfg", L"log_level", 1, filename); +} + +void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename) +{ + assert(cfg != NULL); + assert(filename != NULL); + + cfg->enable = GetPrivateProfileIntW(L"vfs", L"enable", 1, filename); + + GetPrivateProfileStringW( + L"vfs", + L"d_drive", + L"d_drive", + cfg->d_drive, + _countof(cfg->d_drive), + filename); +} diff --git a/platform/config.h b/platform/config.h new file mode 100644 index 0000000..c22a552 --- /dev/null +++ b/platform/config.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include +#include + +#include "platform/clock.h" +#include "platform/dns.h" +#include "platform/misc.h" +#include "platform/netenv.h" +#include "platform/ttxsec.h" +#include "platform/platform.h" +#include "platform/syscfg.h" +#include "platform/vfs.h" + +void platform_config_load( + struct platform_config *cfg, + const wchar_t *filename); + +void clock_config_load(struct clock_config *cfg, const wchar_t *filename); +void dns_config_load(struct dns_config *cfg, const wchar_t *filename); +void misc_config_load(struct misc_config *cfg, const wchar_t *filename); +void netenv_config_load(struct netenv_config *cfg, const wchar_t *filename); +void ttxsec_config_load(struct ttxsec_config *cfg, const wchar_t *filename); +void syscfg_config_load(struct syscfg_config *cfg, const wchar_t *filename); +void vfs_config_load(struct vfs_config *cfg, const wchar_t *filename); diff --git a/platform/dns.c b/platform/dns.c new file mode 100644 index 0000000..a57f8da --- /dev/null +++ b/platform/dns.c @@ -0,0 +1,68 @@ +#include + +#include + +#include "hooklib/dns.h" + +#include "platform/dns.h" + +HRESULT dns_platform_hook_init(const struct dns_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + hr = dns_hook_push(L"cert.nesys.jp", cfg->cert); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"cert1.nesys.jp", cfg->cert); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"cert2.nesys.jp", cfg->cert); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"cert3.nesys.jp", cfg->cert); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"data.nesys.jp", cfg->data); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"proxy.nesys.jp", cfg->proxy); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"nesys.taito.co.jp", cfg->nesys); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_hook_push(L"fjm170920zero.nesica.net", cfg->fjm); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} diff --git a/platform/dns.h b/platform/dns.h new file mode 100644 index 0000000..a973914 --- /dev/null +++ b/platform/dns.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include +#include + +struct dns_config { + bool enable; + wchar_t router[128]; + wchar_t cert[128]; + wchar_t data[128]; + wchar_t proxy[128]; + wchar_t nesys[128]; + wchar_t fjm[128]; +}; + +HRESULT dns_platform_hook_init(const struct dns_config *cfg); diff --git a/platform/meson.build b/platform/meson.build new file mode 100644 index 0000000..368c5af --- /dev/null +++ b/platform/meson.build @@ -0,0 +1,30 @@ +platform_lib = static_library( + 'platform', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + shlwapi_lib, + ], + sources : [ + 'clock.c', + 'clock.h', + 'config.c', + 'config.h', + 'dns.c', + 'dns.h', + 'misc.c', + 'misc.h', + 'netenv.c', + 'netenv.h', + 'ttxsec.c', + 'ttxsec.h', + 'platform.c', + 'platform.h', + 'vfs.c', + 'vfs.h', + 'syscfg.c', + 'syscfg.h', + ], +) diff --git a/platform/misc.c b/platform/misc.c new file mode 100644 index 0000000..b3fdaf6 --- /dev/null +++ b/platform/misc.c @@ -0,0 +1,162 @@ +#include + +#include +#include +#include +#include + +#include "hook/table.h" + +#include "hooklib/reg.h" + +#include "platform/misc.h" + +#include "util/dprintf.h" + +static BOOL WINAPI misc_ExitWindowsEx(unsigned int flags, uint32_t reason); + +static HRESULT misc_read_os_version(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_app_loader_count(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_cpu_temp_error(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_cpu_temp_warning(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_platform_id(void *bytes, uint32_t *nbytes); +static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes); + +static const struct hook_symbol misc_syms[] = { + { + .name = "ExitWindowsEx", + .patch = misc_ExitWindowsEx, + } +}; + +static const struct reg_hook_val misc_root_keys[] = { + { + .name = L"OSVersion", + .read = misc_read_os_version, + .type = REG_SZ, + } +}; + +static const struct reg_hook_val misc_master_keys[] = { + { + .name = L"AppLoaderCount", + .read = misc_read_app_loader_count, + .type = REG_DWORD, + }, { + /* Black-hole val, list it here so we don't get a warning msg */ + .name = L"NextProcess", + .type = REG_SZ, + }, { + /* ditto */ + .name = L"SystemError", + .type = REG_SZ, + } +}; + +static const struct reg_hook_val misc_static_keys[] = { + { + .name = L"CpuTempError", + .read = misc_read_cpu_temp_error, + .type = REG_DWORD, + }, { + .name = L"CpuTempWarning", + .read = misc_read_cpu_temp_warning, + .type = REG_DWORD, + }, { + .name = L"PlatformId", + .read = misc_read_platform_id, + .type = REG_SZ, + }, { + .name = L"PlatformName", + .read = misc_read_platform_name, + .type = REG_SZ, + } +}; + +static wchar_t misc_platform_id[5]; + +HRESULT misc_hook_init(const struct misc_config *cfg) +{ + HRESULT hr; + + assert(cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + /* Add hardcoded dummy keys */ + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty", + misc_root_keys, + _countof(misc_root_keys)); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty\\static", + misc_static_keys, + _countof(misc_static_keys)); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty\\Master", + misc_master_keys, + _countof(misc_master_keys)); + + if (FAILED(hr)) { + return hr; + } + + /* Apply function hooks */ + + hook_table_apply(NULL, "user32.dll", misc_syms, _countof(misc_syms)); + + return S_OK; +} + +static BOOL WINAPI misc_ExitWindowsEx(unsigned int flags, uint32_t reason) +{ + dprintf("Misc: Blocked system reboot\n"); + + return TRUE; +} + +static HRESULT misc_read_os_version(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"0_0_0"); +} + +static HRESULT misc_read_app_loader_count(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 1); +} + +static HRESULT misc_read_cpu_temp_error(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 100); +} + +static HRESULT misc_read_cpu_temp_warning(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 95); +} + +static HRESULT misc_read_platform_id(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, misc_platform_id); +} + +static HRESULT misc_read_platform_name(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"ALLS MX2.1"); // TODO: Dynamic +} diff --git a/platform/misc.h b/platform/misc.h new file mode 100644 index 0000000..ec4f06f --- /dev/null +++ b/platform/misc.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct misc_config { + bool enable; +}; + +HRESULT misc_hook_init(const struct misc_config *cfg); diff --git a/platform/netenv.c b/platform/netenv.c new file mode 100644 index 0000000..4515b6b --- /dev/null +++ b/platform/netenv.c @@ -0,0 +1,498 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "hook/table.h" + +#include "platform/netenv.h" +#include "platform/ttxsec.h" + +#include "util/dprintf.h" + +struct netenv { + IP_ADAPTER_ADDRESSES head; + char name[64]; + wchar_t dns_suffix[64]; + wchar_t description[64]; + wchar_t friendly_name[64]; + IP_ADAPTER_PREFIX prefix; + IP_ADAPTER_UNICAST_ADDRESS iface; + IP_ADAPTER_GATEWAY_ADDRESS router; + IP_ADAPTER_DNS_SERVER_ADDRESS dns; + struct sockaddr_in prefix_sa; + struct sockaddr_in iface_sa; + struct sockaddr_in router_sa; + struct sockaddr_in dns_sa; +}; + +/* Hook functions */ + +static uint32_t WINAPI hook_GetAdaptersAddresses( + uint32_t Family, + uint32_t Flags, + void *Reserved, + IP_ADAPTER_ADDRESSES *AdapterAddresses, + uint32_t *SizePointer); + +static uint32_t WINAPI hook_GetAdaptersInfo( + IP_ADAPTER_INFO *AdapterInfo, + uint32_t *SizePointer); + +static uint32_t WINAPI hook_GetBestRoute( + uint32_t src_ip, + uint32_t dest_ip, + MIB_IPFORWARDROW *route); + +static uint32_t WINAPI hook_GetIfTable( + MIB_IFTABLE *pIfTable, + uint32_t *pdwSize, + BOOL bOrder); + +static uint32_t WINAPI hook_IcmpSendEcho2( + HANDLE IcmpHandle, + HANDLE Event, + PIO_APC_ROUTINE ApcRoutine, + void *ApcContext, + uint32_t DestinationAddress, + void *RequestData, + uint16_t RequestSize, + IP_OPTION_INFORMATION *RequestOptions, + void *ReplyBuffer, + uint32_t ReplySize, + uint32_t Timeout); + +/* Link pointers */ + +static uint32_t (WINAPI *next_GetAdaptersAddresses)( + uint32_t Family, + uint32_t Flags, + void *Reserved, + IP_ADAPTER_ADDRESSES *AdapterAddresses, + uint32_t *SizePointer); + +static uint32_t (WINAPI *next_GetAdaptersInfo)( + IP_ADAPTER_INFO *AdapterInfo, + uint32_t *SizePointer); + +static uint32_t (WINAPI *next_GetBestRoute)( + uint32_t src_ip, + uint32_t dest_ip, + MIB_IPFORWARDROW *route); + +static uint32_t (WINAPI *next_GetIfTable)( + MIB_IFTABLE *pIfTable, + uint32_t *pdwSize, + BOOL bOrder); + +static uint32_t (WINAPI *next_IcmpSendEcho2)( + HANDLE IcmpHandle, + HANDLE Event, + PIO_APC_ROUTINE ApcRoutine, + void *ApcContext, + uint32_t DestinationAddress, + void *RequestData, + uint16_t RequestSize, + IP_OPTION_INFORMATION *RequestOptions, + void *ReplyBuffer, + uint32_t ReplySize, + uint32_t Timeout); + +static const struct hook_symbol netenv_hook_syms[] = { + { + .name = "GetAdaptersAddresses", + .patch = hook_GetAdaptersAddresses, + .link = (void **) &next_GetAdaptersAddresses, + }, { + .name = "GetAdaptersInfo", + .patch = hook_GetAdaptersInfo, + .link = (void **) &next_GetAdaptersInfo, + }, { + .name = "GetBestRoute", + .patch = hook_GetBestRoute, + .link = (void **) &next_GetBestRoute, + }, { + .name = "GetIfTable", + .patch = hook_GetIfTable, + .link = (void **) &next_GetIfTable, + }, { + .name = "IcmpSendEcho2", + .patch = hook_IcmpSendEcho2, + .link = (void **) &next_IcmpSendEcho2, + } +}; + +static uint32_t netenv_ip_prefix; +static uint32_t netenv_ip_iface; +static uint32_t netenv_ip_router; +static uint8_t netenv_mac_addr[6]; + +HRESULT netenv_hook_init( + const struct netenv_config *cfg, + const struct ttxsec_config *kc_cfg) +{ + assert(cfg != NULL); + assert(kc_cfg != NULL); + + if (!cfg->enable) { + return S_FALSE; + } + + if (!kc_cfg->enable) { + dprintf("Netenv: Keychip emu is off? Disabling Netenv emu.\n"); + + return S_FALSE; + } + + netenv_ip_prefix = kc_cfg->subnet; + netenv_ip_iface = kc_cfg->subnet | cfg->addr_suffix; + netenv_ip_router = kc_cfg->subnet | cfg->router_suffix; + memcpy(netenv_mac_addr, cfg->mac_addr, sizeof(netenv_mac_addr)); + + hook_table_apply( + NULL, + "iphlpapi.dll", + netenv_hook_syms, + _countof(netenv_hook_syms)); + + return S_OK; +} + +static uint32_t WINAPI hook_GetAdaptersAddresses( + uint32_t Family, + uint32_t Flags, + void *Reserved, + IP_ADAPTER_ADDRESSES *AdapterAddresses, + uint32_t *SizePointer) +{ + /* This hook errs on the side of caution and returns a lot more + information than the ALLNET lib cares about. MSVC mangles the main + call site for this API quite aggressively, so by the time we decompile + the code in question it's a little difficult to tell which pieces the + ALLNET lib pays attention to. */ + + uint32_t nbytes; + struct netenv *env; + + if (Reserved != NULL || SizePointer == NULL) { + return ERROR_INVALID_PARAMETER; + } + + nbytes = *SizePointer; + *SizePointer = sizeof(*env); + + if (AdapterAddresses == NULL || nbytes < sizeof(*env)) { + return ERROR_BUFFER_OVERFLOW; + } + + env = CONTAINING_RECORD(AdapterAddresses, struct netenv, head); + memset(env, 0, sizeof(*env)); + + env->head.Length = sizeof(env->head); + env->head.IfIndex = 1; + env->head.AdapterName = env->name; + env->head.FirstUnicastAddress = &env->iface; + env->head.FirstDnsServerAddress = &env->dns; + env->head.DnsSuffix = env->dns_suffix; + env->head.Description = env->description; + env->head.FriendlyName = env->friendly_name; + memcpy( env->head.PhysicalAddress, + netenv_mac_addr, + sizeof(netenv_mac_addr)); + env->head.PhysicalAddressLength = sizeof(netenv_mac_addr); + env->head.Flags = IP_ADAPTER_DHCP_ENABLED | IP_ADAPTER_IPV4_ENABLED; + env->head.Mtu = 4200; /* idk what's typical here */ + env->head.IfType = IF_TYPE_ETHERNET_CSMACD; + env->head.OperStatus = IfOperStatusUp; + env->head.FirstPrefix = &env->prefix; + env->head.FirstGatewayAddress = &env->router; + + strcpy_s( + env->name, + _countof(env->name), + "{00000000-0000-0000-0000-000000000000}"); + + wcscpy_s( + env->dns_suffix, + _countof(env->dns_suffix), + L"local"); + + wcscpy_s( + env->description, + _countof(env->description), + L"Interface Description"); + + wcscpy_s( + env->friendly_name, + _countof(env->friendly_name), + L"Fake Ethernet"); + + env->iface.Length = sizeof(env->iface); + env->iface.Flags = 0; + env->iface.Address.lpSockaddr = (struct sockaddr *) &env->iface_sa; + env->iface.Address.iSockaddrLength = sizeof(env->iface_sa); + env->iface.PrefixOrigin = IpPrefixOriginDhcp; + env->iface.SuffixOrigin = IpSuffixOriginDhcp; + env->iface.DadState = IpDadStatePreferred; + env->iface.ValidLifetime = UINT32_MAX; + env->iface.PreferredLifetime = UINT32_MAX; + env->iface.LeaseLifetime = 86400; + env->iface.OnLinkPrefixLength = 24; + + env->prefix.Length = sizeof(env->prefix); + env->prefix.Address.lpSockaddr = (struct sockaddr *) &env->prefix_sa; + env->prefix.Address.iSockaddrLength = sizeof(env->prefix_sa); + env->prefix.PrefixLength = 24; + + env->router.Length = sizeof(env->router); + env->router.Address.lpSockaddr = (struct sockaddr *) &env->router_sa; + env->router.Address.iSockaddrLength = sizeof(env->router_sa); + + env->dns.Length = sizeof(env->dns); + env->dns.Address.lpSockaddr = (struct sockaddr *) &env->dns_sa; + env->dns.Address.iSockaddrLength = sizeof(env->dns_sa); + + env->prefix_sa.sin_family = AF_INET; + env->prefix_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_prefix); + + env->iface_sa.sin_family = AF_INET; + env->iface_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_iface); + + env->router_sa.sin_family = AF_INET; + env->router_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_router); + + env->dns_sa.sin_family = AF_INET; + env->dns_sa.sin_addr.s_addr = _byteswap_ulong(netenv_ip_router); + + return ERROR_SUCCESS; +} + +static uint32_t WINAPI hook_GetAdaptersInfo( + IP_ADAPTER_INFO *ai, + uint32_t *nbytes_inout) +{ + IP_ADDR_STRING iface; + IP_ADDR_STRING router; + uint32_t nbytes; + + if (nbytes_inout == NULL) { + return ERROR_INVALID_PARAMETER; + } + + nbytes = *nbytes_inout; + *nbytes_inout = sizeof(*ai); + + if (ai == NULL || nbytes < sizeof(*ai)) { + return ERROR_BUFFER_OVERFLOW; + } + + dprintf("Netenv: GetAdaptersInfo: Virtualized LAN configuration:\n"); + dprintf("Netenv: Interface IP : %3i.%3i.%3i.%3i\n", + (uint8_t) (netenv_ip_iface >> 24), + (uint8_t) (netenv_ip_iface >> 16), + (uint8_t) (netenv_ip_iface >> 8), + (uint8_t) (netenv_ip_iface )); + dprintf("Netenv: Router IP : %3i.%3i.%3i.%3i\n", + (uint8_t) (netenv_ip_router >> 24), + (uint8_t) (netenv_ip_router >> 16), + (uint8_t) (netenv_ip_router >> 8), + (uint8_t) (netenv_ip_router )); + dprintf("Netenv: MAC Address : %02x:%02x:%02x:%02x:%02x:%02x\n", + netenv_mac_addr[0], + netenv_mac_addr[1], + netenv_mac_addr[2], + netenv_mac_addr[3], + netenv_mac_addr[4], + netenv_mac_addr[5]); + + memset(&iface, 0, sizeof(iface)); + memset(&router, 0, sizeof(router)); + + sprintf_s( + iface.IpAddress.String, + _countof(iface.IpAddress.String), + "%i.%i.%i.%i", + (uint8_t) (netenv_ip_iface >> 24), + (uint8_t) (netenv_ip_iface >> 16), + (uint8_t) (netenv_ip_iface >> 8), + (uint8_t) (netenv_ip_iface )); + + strcpy_s( + iface.IpMask.String, + _countof(iface.IpMask.String), + "255.255.255.0"); + + sprintf_s( + router.IpAddress.String, + _countof(iface.IpAddress.String), + "%i.%i.%i.%i", + (uint8_t) (netenv_ip_router >> 24), + (uint8_t) (netenv_ip_router >> 16), + (uint8_t) (netenv_ip_router >> 8), + (uint8_t) (netenv_ip_router )); + + strcpy_s( + router.IpMask.String, + _countof(router.IpMask.String), + "255.255.255.0"); + + memset(ai, 0, sizeof(*ai)); + strcpy_s( + ai->AdapterName, + _countof(ai->AdapterName), + "Fake Ethernet"); + strcpy_s(ai->Description, + _countof(ai->Description), + "Adapter Description"); + ai->AddressLength = sizeof(netenv_mac_addr); + memcpy(ai->Address, netenv_mac_addr, sizeof(netenv_mac_addr)); + ai->Index = 1; + ai->Type = MIB_IF_TYPE_ETHERNET; + ai->DhcpEnabled = 1; + memcpy(&ai->IpAddressList, &iface, sizeof(iface)); + memcpy(&ai->GatewayList, &router, sizeof(router)); + memcpy(&ai->DhcpServer, &router, sizeof(router)); + ai->LeaseObtained = time(NULL) - 3600; + ai->LeaseExpires = time(NULL) + 86400; + + return ERROR_SUCCESS; +} + +static uint32_t WINAPI hook_GetBestRoute( + uint32_t src_ip, + uint32_t dest_ip, + MIB_IPFORWARDROW *route) +{ + if (route == NULL) { + return ERROR_INVALID_PARAMETER; + } + + dprintf("Netenv: GetBestRoute ip4 %x -> ip4 %x\n", + (int) _byteswap_ulong(src_ip), + (int) _byteswap_ulong(dest_ip)); + + memset(route, 0, sizeof(*route)); + + /* This doesn't seem to get read? It just needs to succeed. */ + + route->dwForwardDest = 0x00000000; + route->dwForwardMask = 0xFFFFFFFF; + route->dwForwardPolicy = 0; /* idk */ + route->dwForwardNextHop = _byteswap_ulong(netenv_ip_router); + route->dwForwardIfIndex = 1; + route->dwForwardType = MIB_IPROUTE_TYPE_INDIRECT; + route->dwForwardProto = MIB_IPPROTO_NETMGMT; + + return ERROR_SUCCESS; +} + +static uint32_t WINAPI hook_GetIfTable( + MIB_IFTABLE *pIfTable, + uint32_t *pdwSize, + BOOL bOrder) +{ + MIB_IFROW *row; + uint32_t nbytes; + + if (pdwSize == NULL) { + return ERROR_INVALID_PARAMETER; + } + + nbytes = *pdwSize; + *pdwSize = sizeof(*row) + sizeof(DWORD); + + if (pIfTable == NULL || nbytes < sizeof(*row) + sizeof(DWORD)) { + return ERROR_BUFFER_OVERFLOW; + } + + pIfTable->dwNumEntries = 1; + + row = pIfTable->table; + memset(row, 0, sizeof(*row)); + + wcscpy_s(row->wszName, _countof(row->wszName), L"Fake Ethernet"); + row->dwIndex = 1; /* Should match other IF_INDEX fields we return */ + row->dwType = IF_TYPE_ETHERNET_CSMACD; + row->dwMtu = 4200; /* I guess? */ + row->dwSpeed = 1000000000; + row->dwPhysAddrLen = sizeof(netenv_mac_addr); + memcpy(row->bPhysAddr, netenv_mac_addr, sizeof(netenv_mac_addr)); + row->dwAdminStatus = 1; + row->dwOperStatus = IF_OPER_STATUS_OPERATIONAL; + + return ERROR_SUCCESS; +} + +static uint32_t WINAPI hook_IcmpSendEcho2( + HANDLE IcmpHandle, + HANDLE Event, + PIO_APC_ROUTINE ApcRoutine, + void *ApcContext, + uint32_t DestinationAddress, + void *RequestData, + uint16_t RequestSize, + IP_OPTION_INFORMATION *RequestOptions, + void *ReplyBuffer, + uint32_t ReplySize, + uint32_t Timeout) +{ + ICMP_ECHO_REPLY *pong; + BOOL ok; + + if (IcmpHandle == NULL || IcmpHandle == INVALID_HANDLE_VALUE) { + SetLastError(ERROR_INVALID_PARAMETER); + + return 0; + } + + if (ApcRoutine != NULL) { + dprintf("%s: Got APC routine...\n", __func__); + SetLastError(ERROR_NOT_SUPPORTED); + + return 0; + } + + if (ReplyBuffer == NULL) { + SetLastError(ERROR_INVALID_PARAMETER); + + return 0; + } + + if (ReplySize < sizeof(ICMP_ECHO_REPLY)) { + SetLastError(IP_BUF_TOO_SMALL); + + return 0; + } + + dprintf("Netenv: Virtualized ICMP Ping to ip4 %x\n", + (int) _byteswap_ulong(DestinationAddress)); + + pong = (ICMP_ECHO_REPLY *) ReplyBuffer; + memset(pong, 0, sizeof(*pong)); + pong->Address = DestinationAddress; + pong->Status = IP_SUCCESS; + pong->RoundTripTime = 1; + pong->DataSize = 0; + pong->Reserved = 1; /* Number of ICMP_ECHO_REPLY structs in ReplyBuffer */ + pong->Data = NULL; + + if (Event != NULL) { + ok = SetEvent(Event); + + if (ok) { + SetLastError(ERROR_IO_PENDING); + } + + return 0; + } + + dprintf("%s: Unexpected synchronous call...\n", __func__); + SetLastError(ERROR_SUCCESS); + + return 1; +} diff --git a/platform/netenv.h b/platform/netenv.h new file mode 100644 index 0000000..33bf56a --- /dev/null +++ b/platform/netenv.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include +#include + +#include "platform/ttxsec.h" + +struct netenv_config { + bool enable; + uint8_t addr_suffix; + uint8_t router_suffix; + uint8_t mac_addr[6]; +}; + +HRESULT netenv_hook_init( + const struct netenv_config *cfg, + const struct ttxsec_config *kc_cfg); + diff --git a/platform/platform.c b/platform/platform.c new file mode 100644 index 0000000..bc3d20d --- /dev/null +++ b/platform/platform.c @@ -0,0 +1,68 @@ +#include + +#include + +#include "platform/clock.h" +#include "platform/dns.h" +#include "platform/misc.h" +#include "platform/netenv.h" +#include "platform/ttxsec.h" +#include "platform/platform.h" +#include "platform/vfs.h" +#include "platform/syscfg.h" + +HRESULT platform_hook_init( + const struct platform_config *cfg, + const uint32_t game_id, + HMODULE redir_mod) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(game_id != 0); + assert(redir_mod != NULL); + + hr = clock_hook_init(&cfg->clock); + + if (FAILED(hr)) { + return hr; + } + + hr = dns_platform_hook_init(&cfg->dns); + + if (FAILED(hr)) { + return hr; + } + + hr = misc_hook_init(&cfg->misc); + + if (FAILED(hr)) { + return hr; + } + + hr = netenv_hook_init(&cfg->netenv, &cfg->ttxsec); + + if (FAILED(hr)) { + return hr; + } + + hr = ttxsec_hook_init(&cfg->ttxsec, game_id); + + if (FAILED(hr)) { + return hr; + } + + hr = vfs_hook_init(&cfg->vfs); + + if (FAILED(hr)) { + return hr; + } + + hr = syscfg_hook_init(&cfg->syscfg, game_id); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} diff --git a/platform/platform.h b/platform/platform.h new file mode 100644 index 0000000..65b1898 --- /dev/null +++ b/platform/platform.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "platform/clock.h" +#include "platform/dns.h" +#include "platform/misc.h" +#include "platform/netenv.h" +#include "platform/ttxsec.h" +#include "platform/vfs.h" +#include "platform/syscfg.h" + +struct platform_config { + struct clock_config clock; + struct dns_config dns; + struct misc_config misc; + struct netenv_config netenv; + struct ttxsec_config ttxsec; + struct vfs_config vfs; + struct syscfg_config syscfg; +}; + +HRESULT platform_hook_init( + const struct platform_config *cfg, + const char *game_id, + const char *platform_id, + HMODULE redir_mod); diff --git a/platform/syscfg.c b/platform/syscfg.c new file mode 100644 index 0000000..740c689 --- /dev/null +++ b/platform/syscfg.c @@ -0,0 +1,115 @@ +#include +#include +#include + +#include "hooklib/reg.h" +#include "platform/syscfg.h" +#include "platform/vfs.h" + +#include "util/dprintf.h" + +static struct syscfg_config config; +static uint32_t game_id; + +static HRESULT syscfg_game_kind(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_evt_next_time(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_condition_time(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_traffic_ct(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_log_level(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_news_path(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_event_path(void *bytes, uint32_t *nbytes); +static HRESULT syscfg_log_path(void *bytes, uint32_t *nbytes); + +static const struct reg_hook_val fake_com_keys[] = { + { + .name = L"GameKind", + .read = syscfg_game_kind, + .type = REG_DWORD, + },{ + .name = L"EventNextTime", + .read = syscfg_evt_next_time, + .type = REG_DWORD, + },{ + .name = L"ConditionTime", + .read = syscfg_condition_time, + .type = REG_DWORD, + },{ + .name = L"TrafficCount", + .read = syscfg_traffic_ct, + .type = REG_DWORD, + },{ + .name = L"LogLevel", + .read = syscfg_log_level, + .type = REG_DWORD, + },{ + .name = L"NewsPath", + .read = syscfg_news_path, + .type = REG_SZ, + },{ + .name = L"EventPath", + .read = syscfg_event_path, + .type = REG_SZ, + },{ + .name = L"LogPath", + .read = syscfg_log_path, + .type = REG_SZ, + }, +}; + +HRESULT syscfg_hook_init(const struct syscfg_config *cfg, const uint32_t gid) +{ + HRESULT hr; + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&config, cfg, sizeof(*cfg)); + game_id = gid; + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\taito\\typex", + fake_com_keys, + _countof(fake_com_keys)); +} + +static HRESULT syscfg_game_kind(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, game_id); +} + +static HRESULT syscfg_evt_next_time(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 1800); +} + +static HRESULT syscfg_condition_time(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 1800); +} + +static HRESULT syscfg_traffic_ct(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, 0); // unused? +} + +static HRESULT syscfg_log_level(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, config.log_level); +} + +static HRESULT syscfg_news_path(void *bytes, uint32_t *nbytes) +{ + + return reg_hook_read_wstr(bytes, nbytes, L"D:\\news"); +} + +static HRESULT syscfg_event_path(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"D:\\event"); +} + +static HRESULT syscfg_log_path(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_wstr(bytes, nbytes, L"D:\\log"); +} diff --git a/platform/syscfg.h b/platform/syscfg.h new file mode 100644 index 0000000..abb3447 --- /dev/null +++ b/platform/syscfg.h @@ -0,0 +1,12 @@ +#pragma once +#include + +#include +#include + +struct syscfg_config { + bool enable; + uint32_t log_level; +}; + +HRESULT syscfg_hook_init(const struct syscfg_config *cfg, const uint32_t gid); diff --git a/platform/ttxsec.c b/platform/ttxsec.c new file mode 100644 index 0000000..f76bb85 --- /dev/null +++ b/platform/ttxsec.c @@ -0,0 +1,621 @@ +#include +#include +#include +#include + +#include "hook/iohook.h" + +#include "hooklib/reg.h" + +#include "platform/ttxsec.h" + +#include "util/dprintf.h" +#include "util/dump.h" +#include "util/str.h" + +enum { + TTXSEC_IOCTL_PING = 0x22A114, + TTXSEC_IOCTL_ERASE_TRACE_LOG = 0x22E188, + TTXSEC_IOCTL_ADD_PLAY_COUNT = 0x22E154, + TTXSEC_IOCTL_GET_BILLING_CA_CERT = 0x22E1C4, + TTXSEC_IOCTL_GET_BILLING_PUBKEY = 0x22E1C8, + TTXSEC_IOCTL_GET_NEARFULL = 0x22E20C, + TTXSEC_IOCTL_GET_NVRAM_AVAILABLE = 0x22E19C, + TTXSEC_IOCTL_GET_NVRAM_GEOMETRY = 0x22E24C, + TTXSEC_IOCTL_GET_PLAY_COUNT = 0x22E150, + TTXSEC_IOCTL_GET_PLAY_LIMIT = 0x22E204, + TTXSEC_IOCTL_GET_TRACE_LOG_DATA = 0x22E194, + TTXSEC_IOCTL_GET_TRACE_LOG_STATE = 0x22E198, + TTXSEC_IOCTL_PUT_NEARFULL = 0x22E210, + TTXSEC_IOCTL_PUT_PLAY_LIMIT = 0x22E208, + TTXSEC_IOCTL_PUT_TRACE_LOG_DATA = 0x22E190, +}; + +struct ttxsec_log_record { + uint8_t unknown[60]; +}; + +static HRESULT ttxsec_handle_irp(struct irp *irp); +static HRESULT ttxsec_handle_open(struct irp *irp); +static HRESULT ttxsec_handle_close(struct irp *irp); +static HRESULT ttxsec_handle_ioctl(struct irp *irp); + +static HRESULT ttxsec_ioctl_ping(struct irp *irp); +static HRESULT ttxsec_ioctl_erase_trace_log(struct irp *irp); +static HRESULT ttxsec_ioctl_add_play_count(struct irp *irp); +static HRESULT ttxsec_ioctl_get_billing_ca_cert(struct irp *irp); +static HRESULT ttxsec_ioctl_get_billing_pubkey(struct irp *irp); +static HRESULT ttxsec_ioctl_get_nearfull(struct irp *irp); +static HRESULT ttxsec_ioctl_get_nvram_available(struct irp *irp); +static HRESULT ttxsec_ioctl_get_nvram_geometry(struct irp *irp); +static HRESULT ttxsec_ioctl_get_play_count(struct irp *irp); +static HRESULT ttxsec_ioctl_get_play_limit(struct irp *irp); +static HRESULT ttxsec_ioctl_get_trace_log_data(struct irp *irp); +static HRESULT ttxsec_ioctl_get_trace_log_state(struct irp *irp); +static HRESULT ttxsec_ioctl_put_nearfull(struct irp *irp); +static HRESULT ttxsec_ioctl_put_play_limit(struct irp *irp); +static HRESULT ttxsec_ioctl_put_trace_log_data(struct irp *irp); + +static HRESULT ttxsec_reg_read_game_id(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_keychip_id(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_model_type(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_platform_id(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_region(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_server_ip_ipv4(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_server_ip_ipv6(void *bytes, uint32_t *nbytes); +static HRESULT ttxsec_reg_read_system_flag(void *bytes, uint32_t *nbytes); + +static const struct reg_hook_val ttxsec_reg_vals[] = { + { + .name = L"gameId", + .read = ttxsec_reg_read_game_id, + .type = REG_BINARY, + }, { + .name = L"keychipId", + .read = ttxsec_reg_read_keychip_id, + .type = REG_BINARY, + }, { + .name = L"modelType", + .read = ttxsec_reg_read_model_type, + .type = REG_DWORD, + }, { + .name = L"platformId", + .read = ttxsec_reg_read_platform_id, + .type = REG_BINARY, + }, { + .name = L"region", + .read = ttxsec_reg_read_region, + .type = REG_DWORD, + }, { + .name = L"serverIpIpv4", + .read = ttxsec_reg_read_server_ip_ipv4, + .type = REG_BINARY, + }, { + .name = L"serverIpIpv6", + .read = ttxsec_reg_read_server_ip_ipv6, + .type = REG_BINARY, + }, { + .name = L"systemFlag", + .read = ttxsec_reg_read_system_flag, + .type = REG_DWORD, + } +}; + +static HANDLE ttxsec_fd; +static uint32_t ttxsec_nearfull; +static uint32_t ttxsec_play_count; +static uint32_t ttxsec_play_limit; +static struct ttxsec_log_record ttxsec_log[7154]; +static size_t ttxsec_log_head; +static size_t ttxsec_log_tail; +static struct ttxsec_config ttxsec_cfg; + +HRESULT ttxsec_hook_init( + const struct ttxsec_config *cfg, + const uint32_t game_id) +{ + HRESULT hr; + + assert(cfg != NULL); + assert(game_id != 0); + + if (!cfg->enable) { + return S_FALSE; + } + + memcpy(&ttxsec_cfg, cfg, sizeof(*cfg)); + + // High 16 bits is billing type, low is actual playlimit + ttxsec_nearfull = (ttxsec_cfg.billing_type << 16) + 512; + ttxsec_play_count = 0; + ttxsec_play_limit = 1024; + + hr = iohook_open_nul_fd(&ttxsec_fd); + + if (FAILED(hr)) { + return hr; + } + + hr = iohook_push_handler(ttxsec_handle_irp); + + if (FAILED(hr)) { + return hr; + } + + hr = reg_hook_push_key( + HKEY_LOCAL_MACHINE, + L"SYSTEM\\SEGA\\SystemProperty\\keychip", + ttxsec_reg_vals, + _countof(ttxsec_reg_vals)); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static HRESULT ttxsec_handle_irp(struct irp *irp) +{ + assert(irp != NULL); + + if (irp->op != IRP_OP_OPEN && irp->fd != ttxsec_fd) { + return iohook_invoke_next(irp); + } + + switch (irp->op) { + case IRP_OP_OPEN: return ttxsec_handle_open(irp); + case IRP_OP_CLOSE: return ttxsec_handle_close(irp); + case IRP_OP_IOCTL: return ttxsec_handle_ioctl(irp); + default: return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT ttxsec_handle_open(struct irp *irp) +{ + if (!wstr_ieq(irp->open_filename, L"\\??\\FddDriver")) { + return iohook_invoke_next(irp); + } + + dprintf("Security: Opened handle\n"); + irp->fd = ttxsec_fd; + + return S_OK; +} + +static HRESULT ttxsec_handle_close(struct irp *irp) +{ + dprintf("Security: Closed handle\n"); + + return S_OK; +} + +static HRESULT ttxsec_handle_ioctl(struct irp *irp) +{ + switch (irp->ioctl) { + case TTXSEC_IOCTL_PING: + return ttxsec_ioctl_ping(irp); + + case TTXSEC_IOCTL_ERASE_TRACE_LOG: + return ttxsec_ioctl_erase_trace_log(irp); + + case TTXSEC_IOCTL_ADD_PLAY_COUNT: + return ttxsec_ioctl_add_play_count(irp); + + case TTXSEC_IOCTL_GET_BILLING_CA_CERT: + return ttxsec_ioctl_get_billing_ca_cert(irp); + + case TTXSEC_IOCTL_GET_BILLING_PUBKEY: + return ttxsec_ioctl_get_billing_pubkey(irp); + + case TTXSEC_IOCTL_GET_NEARFULL: + return ttxsec_ioctl_get_nearfull(irp); + + case TTXSEC_IOCTL_GET_NVRAM_AVAILABLE: + return ttxsec_ioctl_get_nvram_available(irp); + + case TTXSEC_IOCTL_GET_NVRAM_GEOMETRY: + return ttxsec_ioctl_get_nvram_geometry(irp); + + case TTXSEC_IOCTL_GET_PLAY_COUNT: + return ttxsec_ioctl_get_play_count(irp); + + case TTXSEC_IOCTL_GET_PLAY_LIMIT: + return ttxsec_ioctl_get_play_limit(irp); + + case TTXSEC_IOCTL_GET_TRACE_LOG_DATA: + return ttxsec_ioctl_get_trace_log_data(irp); + + case TTXSEC_IOCTL_GET_TRACE_LOG_STATE: + return ttxsec_ioctl_get_trace_log_state(irp); + + case TTXSEC_IOCTL_PUT_NEARFULL: + return ttxsec_ioctl_put_nearfull(irp); + + case TTXSEC_IOCTL_PUT_PLAY_LIMIT: + return ttxsec_ioctl_put_play_limit(irp); + + case TTXSEC_IOCTL_PUT_TRACE_LOG_DATA: + return ttxsec_ioctl_put_trace_log_data(irp); + + default: + dprintf("Security: Unknown ioctl %#08x, write %i read %i\n", + irp->ioctl, + (int) irp->write.nbytes, + (int) irp->read.nbytes); + + return HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION); + } +} + +static HRESULT ttxsec_ioctl_ping(struct irp *irp) +{ + return S_OK; +} + +static HRESULT ttxsec_ioctl_erase_trace_log(struct irp *irp) +{ + uint32_t count; + size_t avail; + HRESULT hr; + + hr = iobuf_read_le32(&irp->write, &count); + + if (FAILED(hr)) { + return hr; + } + + dprintf("Security: %s(count=%i)\n", __func__, count); + + avail = ttxsec_log_head - ttxsec_log_tail; + + if (count < avail) { + count = avail; + } + + ttxsec_log_tail += count; + + return S_OK; +} + +static HRESULT ttxsec_ioctl_add_play_count(struct irp *irp) +{ + uint32_t delta; + HRESULT hr; + + hr = iobuf_read_le32(&irp->write, &delta); + + if (FAILED(hr)) { + return hr; + } + + dprintf("Security: Add play count: %i + %i = %i\n", + ttxsec_play_count, + delta, + ttxsec_play_count + delta); + + ttxsec_play_count += delta; + + return iobuf_write_le32(&irp->read, ttxsec_play_count); +} + +static HRESULT ttxsec_ioctl_get_billing_ca_cert(struct irp *irp) +{ + HANDLE fd; + HRESULT hr; + + dprintf("Security: %s\n", __func__); + + fd = CreateFileW( + ttxsec_cfg.billing_ca, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (fd == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Error opening CA cert file: %x\n", (int) hr); + + return hr; + } + + /* Transform this ioctl into a read from that file and pass the IRP on */ + + irp->op = IRP_OP_READ; + irp->fd = fd; + + dprintf(">>> %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + hr = iohook_invoke_next(irp); + + dprintf("<<< %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + if (FAILED(hr)) { + dprintf("ReadFile transformation failed: %x\n", (int) hr); + } + + CloseHandle(fd); + + return hr; +} + +static HRESULT ttxsec_ioctl_get_billing_pubkey(struct irp *irp) +{ + HANDLE fd; + HRESULT hr; + + dprintf("Security: %s\n", __func__); + + fd = CreateFileW( + ttxsec_cfg.billing_pub, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (fd == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + dprintf("Error opening billing pubkey file: %x\n", (int) hr); + + return hr; + } + + irp->op = IRP_OP_READ; + irp->fd = fd; + + dprintf(">>> %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + hr = iohook_invoke_next(irp); + + dprintf("<<< %p:%i/%i\n", + irp->read.bytes, + (int) irp->read.pos, + (int) irp->read.nbytes); + + if (FAILED(hr)) { + dprintf("ReadFile transformation failed: %x\n", (int) hr); + } + + CloseHandle(fd); + + return hr; +} + +static HRESULT ttxsec_ioctl_get_nearfull(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + + return iobuf_write_le32(&irp->read, ttxsec_nearfull); +} + +static HRESULT ttxsec_ioctl_get_nvram_available(struct irp *irp) +{ + size_t used; + size_t avail; + + used = ttxsec_log_head - ttxsec_log_tail; + avail = _countof(ttxsec_log) - used; + + dprintf("Security: %s: used=%i avail=%i\n", __func__, + (int) used, + (int) avail); + + return iobuf_write_le32(&irp->read, avail); +} + +static HRESULT ttxsec_ioctl_get_nvram_geometry(struct irp *irp) +{ + HRESULT hr; + + dprintf("Security: %s\n", __func__); + + iobuf_write_le32(&irp->read, 10); /* Num NVRAMs */ + hr = iobuf_write_le32(&irp->read, 4096); /* Num addresses (per NVRAM?) */ + + return hr; +} + +static HRESULT ttxsec_ioctl_get_play_count(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + + return iobuf_write_le32(&irp->read, ttxsec_play_count); +} + +static HRESULT ttxsec_ioctl_get_play_limit(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + + return iobuf_write_le32(&irp->read, ttxsec_play_limit); +} + +static HRESULT ttxsec_ioctl_get_trace_log_data(struct irp *irp) +{ + uint32_t pos; + uint32_t count; + size_t avail; + HRESULT hr; + + dprintf("Security: %s\n", __func__); + + hr = iobuf_read_le32(&irp->write, &pos); + + if (FAILED(hr)) { + return hr; + } + + hr = iobuf_read_le32(&irp->write, &count); + + if (FAILED(hr)) { + return hr; + } + + dprintf(" Params: %i %i Buf: %i\n", pos, count, (int) irp->read.nbytes); + + avail = irp->read.nbytes - irp->read.pos; + + if (avail < count * sizeof(struct ttxsec_log_record)) { + dprintf("\tError: Insufficient buffer\n"); + + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + while (count > 0 && pos != ttxsec_log_head) { + memcpy( &irp->read.bytes[irp->read.pos], + &ttxsec_log[pos % _countof(ttxsec_log)], + sizeof(struct ttxsec_log_record)); + + irp->read.pos += sizeof(struct ttxsec_log_record); + pos++; + count--; + } + + return S_OK; +} + +static HRESULT ttxsec_ioctl_get_trace_log_state(struct irp *irp) +{ + HRESULT hr; + + dprintf("Security: %s H: %i T: %i\n", + __func__, + (int) ttxsec_log_head, + (int) ttxsec_log_tail); + + iobuf_write_le32(&irp->read, ttxsec_log_head - ttxsec_log_tail); + hr = iobuf_write_le32(&irp->read, ttxsec_log_tail); + + return hr; +} + +static HRESULT ttxsec_ioctl_put_nearfull(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + dump_const_iobuf(&irp->write); + + return iobuf_read_le32(&irp->write, &ttxsec_nearfull); +} + +static HRESULT ttxsec_ioctl_put_play_limit(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + dump_const_iobuf(&irp->write); + + return iobuf_read_le32(&irp->write, &ttxsec_play_limit); +} + +static HRESULT ttxsec_ioctl_put_trace_log_data(struct irp *irp) +{ + dprintf("Security: %s\n", __func__); + dump_const_iobuf(&irp->write); + + if (irp->write.nbytes != sizeof(struct ttxsec_log_record)) { + dprintf(" Log record size is incorrect\n"); + + return E_INVALIDARG; + } + + if (ttxsec_log_head - ttxsec_log_tail >= _countof(ttxsec_log)) { + dprintf(" Log buffer is full!\n"); + + return HRESULT_FROM_WIN32(ERROR_DISK_FULL); + } + + memcpy( &ttxsec_log[ttxsec_log_head % _countof(ttxsec_log)], + irp->write.bytes, + sizeof(struct ttxsec_log_record)); + + ttxsec_log_head++; + + dprintf(" H: %i T: %i\n", (int) ttxsec_log_head, (int) ttxsec_log_tail); + + return S_OK; +} + +static HRESULT ttxsec_reg_read_game_id(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_bin( + bytes, + nbytes, + &ttxsec_cfg.game_id, + sizeof(ttxsec_cfg.game_id)); +} + +static HRESULT ttxsec_reg_read_keychip_id(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_bin( + bytes, + nbytes, + &ttxsec_cfg.keychip_id, + sizeof(ttxsec_cfg.keychip_id)); +} + +static HRESULT ttxsec_reg_read_model_type(void *bytes, uint32_t *nbytes) +{ + uint32_t u32; + char c; + + c = ttxsec_cfg.platform_id[3]; + + if (c >= '0' && c <= '9') { + u32 = c - '0'; + } else { + u32 = 0; + } + + return reg_hook_read_u32(bytes, nbytes, u32); +} + +static HRESULT ttxsec_reg_read_platform_id(void *bytes, uint32_t *nbytes) +{ + /* Fourth byte is a separate registry val (modelType). + We store it in the same config field for ease of configuration. */ + + return reg_hook_read_bin( + bytes, + nbytes, + &ttxsec_cfg.platform_id, + 3); +} + +static HRESULT ttxsec_reg_read_region(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, ttxsec_cfg.region); +} + +static HRESULT ttxsec_reg_read_server_ip_ipv4(void *bytes, uint32_t *nbytes) +{ + uint32_t subnet; + + subnet = _byteswap_ulong(ttxsec_cfg.subnet); + + return reg_hook_read_bin(bytes, nbytes, &subnet, sizeof(subnet)); +} + +static HRESULT ttxsec_reg_read_server_ip_ipv6(void *bytes, uint32_t *nbytes) +{ + uint8_t subnet[16]; + + memset(subnet, 0, sizeof(subnet)); + + return reg_hook_read_bin(bytes, nbytes, subnet, sizeof(subnet)); +} + +static HRESULT ttxsec_reg_read_system_flag(void *bytes, uint32_t *nbytes) +{ + return reg_hook_read_u32(bytes, nbytes, ttxsec_cfg.system_flag); +} diff --git a/platform/ttxsec.h b/platform/ttxsec.h new file mode 100644 index 0000000..517eef2 --- /dev/null +++ b/platform/ttxsec.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include +#include +#include + +struct ttxsec_config { + bool enable; + char keychip_id[16]; + char game_id[4]; + char platform_id[4]; + uint8_t region; + uint8_t system_flag; + uint32_t subnet; + uint16_t billing_type; + wchar_t billing_ca[MAX_PATH]; + wchar_t billing_pub[MAX_PATH]; +}; + +HRESULT ttxsec_hook_init( + const struct ttxsec_config *cfg, + const uint32_t game_id); diff --git a/platform/vfs.c b/platform/vfs.c new file mode 100644 index 0000000..98f1f77 --- /dev/null +++ b/platform/vfs.c @@ -0,0 +1,209 @@ +#include +#include + +#include +#include +#include +#include + +#include "hooklib/path.h" +#include "hooklib/reg.h" + +#include "platform/vfs.h" + +#include "util/dprintf.h" + +static void vfs_fixup_path(wchar_t *path, size_t max_count); +static HRESULT vfs_mkdir_rec(const wchar_t *path); +static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count); +static HRESULT vfs_reg_read_amfs(void *bytes, uint32_t *nbytes); +static HRESULT vfs_reg_read_appdata(void *bytes, uint32_t *nbytes); + +static struct vfs_config vfs_config; + +HRESULT vfs_hook_init(const struct vfs_config *config) +{ + wchar_t temp[MAX_PATH]; + size_t nthome_len; + DWORD home_ok; + HRESULT hr; + + assert(config != NULL); + + if (!config->enable) { + return S_FALSE; + } + + if (config->d_drive[0] == L'\0') { + dprintf("Vfs: FATAL: AMFS path not specified in INI file\n"); + + return E_FAIL; + } + + memcpy(&vfs_config, config, sizeof(*config)); + + vfs_fixup_path(vfs_config.d_drive, _countof(vfs_config.d_drive)); + + hr = vfs_mkdir_rec(vfs_config.d_drive); + + if (FAILED(hr)) { + dprintf("Vfs: Failed to create D Drive dir %S: %x\n", + config->d_drive, + (int) hr); + } + + /* Need to create the temp subdirectory, not just nthome itself */ + + hr = vfs_mkdir_rec(temp); + + if (FAILED(hr)) { + dprintf("Vfs: Failed to create %S: %x\n", temp, (int) hr); + } + + /* Not auto-creating option directory as it is normally a read-only mount */ + + hr = path_hook_push(vfs_path_hook); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +static void vfs_fixup_path(wchar_t *path, size_t max_count) +{ + size_t count; + wchar_t abspath[MAX_PATH]; + + assert(path != NULL); + /* Requirement for PathIsRelativeW */ + assert(max_count <= MAX_PATH); + + if (PathIsRelativeW(path)) { + count = GetFullPathNameW(path, _countof(abspath), abspath, NULL); + + /* GetFullPathName's length return value is tricky, because it includes + the NUL terminator on failure, but doesn't on success. + Check if it fits the temp buf (else it's a failure and includes NUL + anyway), then if it fits the target buf, NUL included. */ + if (count == 0 || count > _countof(abspath) || count >= max_count) { + goto fail; + } + + wcscpy_s(path, max_count, abspath); + } else { + count = wcslen(path); + } + + if (path_is_separator_w(path[count - 1])) { + return; + } + + if (count + 2 > max_count) { + goto fail; + } + + path[count + 0] = L'\\'; + path[count + 1] = L'\0'; + return; + +fail: + dprintf("Vfs: FATAL: Path too long: %S\n", path); + abort(); +} + +static HRESULT vfs_mkdir_rec(const wchar_t *path) +{ + wchar_t *copy; + wchar_t *pos; + wchar_t wc; + HRESULT hr; + DWORD attr; + BOOL ok; + + assert(path != NULL); + + copy = _wcsdup(path); + + if (copy == NULL) { + hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY); + + goto end; + } + + pos = copy; + + do { + wc = *pos; + + if (wc == L'\0' || wc == L'/' || wc == L'\\') { + *pos = L'\0'; + attr = GetFileAttributesW(copy); + + if (attr == INVALID_FILE_ATTRIBUTES) { + ok = CreateDirectoryW(copy, NULL); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + + goto end; + } + } + + *pos = wc; + } + + pos++; + } while (wc != L'\0'); + + hr = S_OK; + +end: + free(copy); + + return hr; +} + +static HRESULT vfs_path_hook(const wchar_t *src, wchar_t *dest, size_t *count) +{ + const wchar_t *redir; + size_t required; + size_t redir_len; + + assert(src != NULL); + assert(count != NULL); + + if (src[0] == L'\0' || src[1] != L':' || !path_is_separator_w(src[2])) { + return S_FALSE; + } + + switch (src[0]) { + case L'D': + case L'd': + redir = vfs_config.d_drive; + + break; + + default: + return S_FALSE; + } + + /* Cut off \, replace with redir path, count NUL terminator */ + + redir_len = wcslen(redir); + required = wcslen(src) - 3 + redir_len + 1; + + if (dest != NULL) { + if (required > *count) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + wcscpy_s(dest, *count, redir); + wcscpy_s(dest + redir_len, *count - redir_len, src + 3); + } + + *count = required; + + return S_OK; +} diff --git a/platform/vfs.h b/platform/vfs.h new file mode 100644 index 0000000..2836442 --- /dev/null +++ b/platform/vfs.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include + +struct vfs_config { + bool enable; + wchar_t d_drive[MAX_PATH]; +}; + +HRESULT vfs_hook_init(const struct vfs_config *config); diff --git a/precompiled.h b/precompiled.h new file mode 100644 index 0000000..7c85da7 --- /dev/null +++ b/precompiled.h @@ -0,0 +1,39 @@ +/* + Making NTSTATUS available is slightly awkward. See: + https://kirkshoop.github.io/2011/09/20/ntstatus.html +*/ + +/* Win32 user-mode API */ +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include +#include +#include +#include +#include +#include +#include +#include + +/* Win32 kernel-mode definitions */ +#ifdef __GNUC__ +/* MinGW needs to include this for PHYSICAL_ADDRESS to be defined. + The MS SDK throws a bunch of duplicate symbol errors instead. */ +#include +#else +#include +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/subprojects/capnhook.wrap b/subprojects/capnhook.wrap new file mode 100644 index 0000000..b2687a2 --- /dev/null +++ b/subprojects/capnhook.wrap @@ -0,0 +1,4 @@ +[wrap-git] +directory = capnhook +url = https://github.com/Hay1tsme/capnhook +revision = dbdcd61b3a3043b08f86f959bd45df4967503a77 diff --git a/util/async.c b/util/async.c new file mode 100644 index 0000000..fd6a1cd --- /dev/null +++ b/util/async.c @@ -0,0 +1,199 @@ +/* NTSTATUS chicanery. See precompiled.h */ +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include +#include + +#include +#include +#include +#include + +#include "hook/iohook.h" + +#include "util/async.h" + +static unsigned int __stdcall async_thread_proc(void *param); + +void async_init(struct async *async, void *ctx) +{ + assert(async != NULL); + + InitializeCriticalSection(&async->lock); + InitializeConditionVariable(&async->pend); + InitializeConditionVariable(&async->avail); + async->thread = NULL; + async->ctx = ctx; + async->stop = false; +} + +void async_fini(struct async *async) +{ + HANDLE thread; + + if (async == NULL) { + return; + } + + EnterCriticalSection(&async->lock); + + async->stop = true; + thread = async->thread; + + WakeConditionVariable(&async->pend); + LeaveCriticalSection(&async->lock); + + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + DeleteCriticalSection(&async->lock); + + /* There is no DeleteConditionVariable function in the Win32 API. */ +} + +HRESULT async_submit(struct async *async, struct irp *irp, async_task_t task) +{ + BOOL ok; + + assert(async != NULL); + assert(irp != NULL); + assert(task != NULL); + + if (irp->ovl == NULL) { + /* If there's no OVERLAPPED struct then just execute synchronously */ + return task(async->ctx, irp); + } + + EnterCriticalSection(&async->lock); + + if (async->thread == NULL) { + /* Ensure our worker thread is running */ + async->thread = (HANDLE) _beginthreadex( + NULL, + 0, + async_thread_proc, + async, + 0, + NULL); + + if (async->thread == NULL) { + return HRESULT_FROM_WIN32(GetLastError()); + } + } + + while (async->task != NULL) { + ok = SleepConditionVariableCS(&async->avail, &async->lock, INFINITE); + + if (!ok) { + abort(); + } + } + + async->task = task; + memcpy(&async->irp, irp, sizeof(*irp)); + async->irp.next_handler = (size_t) -1; + irp->ovl->Internal = STATUS_PENDING; + + WakeConditionVariable(&async->pend); + LeaveCriticalSection(&async->lock); + + return HRESULT_FROM_WIN32(ERROR_IO_PENDING); +} + +static unsigned int __stdcall async_thread_proc(void *param) +{ + struct async *async; + struct irp irp; + async_task_t task; + OVERLAPPED *ovl; + HANDLE event; + HRESULT hr; + BOOL ok; + + async = param; + + for (;;) { + EnterCriticalSection(&async->lock); + + if (async->stop) { + LeaveCriticalSection(&async->lock); + + break; + } else if (async->task == NULL) { + ok = SleepConditionVariableCS(&async->pend, &async->lock, INFINITE); + + if (!ok) { + abort(); + } + + LeaveCriticalSection(&async->lock); + } else { + memcpy(&irp, &async->irp, sizeof(irp)); + task = async->task; + ovl = async->irp.ovl; + async->task = NULL; + + WakeConditionVariable(&async->avail); + LeaveCriticalSection(&async->lock); + + assert(ovl != NULL); + + hr = task(async->ctx, &irp); + + switch (irp.op) { + case IRP_OP_READ: + case IRP_OP_IOCTL: + ovl->InternalHigh = (DWORD) irp.read.pos; + + break; + + case IRP_OP_WRITE: + ovl->InternalHigh = (DWORD) irp.write.pos; + + break; + + default: + break; + } + + /* We have to do a slightly tricky dance with the hooked process' + call to GetOverlappedResult() here. This thread might be blocked + on ovl->hEvent, or it might be just about to read ovl->Internal + to determine whether the IO has completed (and thus determine + whether it needs to block on ovl->hEvent or not). So to avoid + any races and *ovl getting invalidated under our feet we must + wake the initiating thread as follows: + + 1. Take a local copy of ovl->hEvent + + 2. Issue a memory fence to ensure that the previous load does + not get re-ordered after the following store + + https://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/ + + 3. Store the operation's NTSTATUS. At the moment that this store + gets issued the memory pointed to by ovl ceases to be safely + accessible. + + 4. Using our local copy of the event handle (if present), signal + the initiating thread to wake up and retire the IO. */ + + event = ovl->hEvent; + MemoryBarrier(); + + if (SUCCEEDED(hr)) { + ovl->Internal = STATUS_SUCCESS; + } else if (hr & FACILITY_NT_BIT) { + ovl->Internal = hr & ~FACILITY_NT_BIT; + } else { + ovl->Internal = STATUS_UNSUCCESSFUL; + } + + if (event != NULL) { + SetEvent(event); + } + } + } + + return 0; +} diff --git a/util/async.h b/util/async.h new file mode 100644 index 0000000..830b46a --- /dev/null +++ b/util/async.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +#include "hook/iohook.h" + +typedef HRESULT (*async_task_t)(void *ctx, struct irp *irp); + +struct async { + CRITICAL_SECTION lock; + CONDITION_VARIABLE pend; + CONDITION_VARIABLE avail; + HANDLE thread; + struct irp irp; + async_task_t task; + void *ctx; + bool stop; +}; + +void async_init(struct async *async, void *ctx); +void async_fini(struct async *async); +HRESULT async_submit(struct async *async, struct irp *irp, async_task_t task); diff --git a/util/crc.c b/util/crc.c new file mode 100644 index 0000000..5e4defb --- /dev/null +++ b/util/crc.c @@ -0,0 +1,28 @@ +#include +#include + +#include "util/crc.h" + +uint32_t crc32(const void *src, size_t nbytes, uint32_t in) +{ + const uint8_t *bytes; + uint32_t crc; + size_t i; + + bytes = src; + crc = ~in; + + for (i = 0 ; i < nbytes * 8 ; i++) { + if (i % 8 == 0) { + crc ^= *bytes++; + } + + if (crc & 1) { + crc = (crc >> 1) ^ 0xEDB88320; + } else { + crc = (crc >> 1); + } + } + + return ~crc; +} diff --git a/util/crc.h b/util/crc.h new file mode 100644 index 0000000..16ca927 --- /dev/null +++ b/util/crc.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +uint32_t crc32(const void *src, size_t nbytes, uint32_t in); diff --git a/util/dll-bind.c b/util/dll-bind.c new file mode 100644 index 0000000..9568d33 --- /dev/null +++ b/util/dll-bind.c @@ -0,0 +1,47 @@ +#include + +#include +#include + +#include "util/dll-bind.h" +#include "util/dprintf.h" + +HRESULT dll_bind( + void *dest, + HINSTANCE src, + const struct dll_bind_sym **syms_pos, + size_t syms_count) +{ + HRESULT hr; + void *src_result; + void **dest_field; + const struct dll_bind_sym *current_sym; + size_t i; + + assert(dest != NULL); + assert(src != NULL); + assert(syms_pos != NULL); + + hr = S_OK; + current_sym = *syms_pos; + + assert(current_sym != NULL); + + for (i = 0; i < syms_count; i++) { + src_result = GetProcAddress(src, current_sym->sym); + + if (src_result == NULL) { + hr = E_NOTIMPL; + + break; + } + + dest_field = (void **)(((uint8_t *)dest) + current_sym->off); + *dest_field = src_result; + current_sym++; + } + + *syms_pos = current_sym; + + return hr; +} diff --git a/util/dll-bind.h b/util/dll-bind.h new file mode 100644 index 0000000..08e647b --- /dev/null +++ b/util/dll-bind.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include + +struct dll_bind_sym { + /* Symbol name to locate in the source DLL */ + const char *sym; + + /* Offset where the symbol pointer should be written in the target + structure */ + ptrdiff_t off; +}; + +/* + Bind a list of DLL symbols into a structure that contains function + pointers. + + - dest: Pointer to destination structure. + - src: Handle to source DLL. + - syms_pos: Pointer to a (mutable) pointer which points to the start of an + (immutable) table of symbols and structure offsets. This mutable + pointer is advanced until either a symbol fails to bind, in which case + an error is returned, or the end of the table is reached, in which + case success is returned. + - syms_count: Number of entries in the symbol table. +*/ +HRESULT dll_bind( + void *dest, + HINSTANCE src, + const struct dll_bind_sym **syms_pos, + size_t syms_count); diff --git a/util/dprintf.c b/util/dprintf.c new file mode 100644 index 0000000..098988c --- /dev/null +++ b/util/dprintf.c @@ -0,0 +1,85 @@ +#ifndef NDEBUG + +#include + +#include +#include +#include +#include + +#include "util/dprintf.h" + +static long dbg_buf_lock_init; +static CRITICAL_SECTION dbg_buf_lock; +static char dbg_buf[16384]; +static size_t dbg_buf_pos; + +void dprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + dprintfv(fmt, ap); + va_end(ap); +} + +void dprintfv(const char *fmt, va_list ap) +{ + long init; + + /* Static constructors in C are difficult to do in a way that works under + both GCC and MSVC, so we have to use atomic ops to ensure that the + buffer mutex is correctly initialized instead. */ + + do { + init = InterlockedCompareExchange(&dbg_buf_lock_init, 0, 1); + + if (init == 0) { + /* We won the init race, global variable is now set to 1, other + threads will spin until it becomes -1. */ + InitializeCriticalSection(&dbg_buf_lock); + dbg_buf_lock_init = -1; + init = -1; + } + } while (init >= 0); + + EnterCriticalSection(&dbg_buf_lock); + + dbg_buf_pos += vsnprintf_s( + dbg_buf + dbg_buf_pos, + sizeof(dbg_buf) - dbg_buf_pos, + sizeof(dbg_buf) - dbg_buf_pos - 1, + fmt, + ap); + + if (dbg_buf_pos + 1 > sizeof(dbg_buf)) { + abort(); + } + + if (strchr(dbg_buf, '\n') != NULL) { + OutputDebugStringA(dbg_buf); + dbg_buf_pos = 0; + dbg_buf[0] = '\0'; + } + + LeaveCriticalSection(&dbg_buf_lock); +} + +void dwprintf(const wchar_t *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + dwprintfv(fmt, ap); + va_end(ap); +} + +void dwprintfv(const wchar_t *fmt, va_list ap) +{ + wchar_t msg[512]; + + _vsnwprintf_s(msg, _countof(msg), _countof(msg) - 1, fmt, ap); + OutputDebugStringW(msg); +} + +#endif diff --git a/util/dprintf.h b/util/dprintf.h new file mode 100644 index 0000000..af071b1 --- /dev/null +++ b/util/dprintf.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#ifdef __GNUC__ +#define DPRINTF_CHK __attribute__(( format(printf, 1, 2) )) +#else +#define DPRINTF_CHK +#endif + +#ifndef NDEBUG +void dprintf(const char *fmt, ...) DPRINTF_CHK; +void dprintfv(const char *fmt, va_list ap); +void dwprintf(const wchar_t *fmt, ...); +void dwprintfv(const wchar_t *fmt, va_list ap); +#else +#define dprintf(...) +#define dprintfv(fmt, ap) +#define dwprintf(...) +#define dwprintfv(fmt, ap) +#endif diff --git a/util/dump.c b/util/dump.c new file mode 100644 index 0000000..e42448c --- /dev/null +++ b/util/dump.c @@ -0,0 +1,74 @@ +#ifndef NDEBUG + +#include +#include + +#include "hook/iobuf.h" + +#include "util/dprintf.h" +#include "util/dump.h" + +void dump(const void *ptr, size_t nbytes) +{ + const uint8_t *bytes; + uint8_t c; + size_t i; + size_t j; + + assert(ptr != NULL || nbytes == 0); + + if (nbytes == 0) { + dprintf("\t--- Empty ---\n"); + } + + bytes = ptr; + + for (i = 0 ; i < nbytes ; i += 16) { + dprintf(" %08x:", (int) i); + + for (j = 0 ; i + j < nbytes && j < 16 ; j++) { + dprintf(" %02x", bytes[i + j]); + } + + while (j < 16) { + dprintf(" "); + j++; + } + + dprintf(" "); + + for (j = 0 ; i + j < nbytes && j < 16 ; j++) { + c = bytes[i + j]; + + if (c < 0x20 || c >= 0x7F) { + c = '.'; + } + + dprintf("%c", c); + } + + dprintf("\n"); + } + + dprintf("\n"); +} + +void dump_iobuf(const struct iobuf *iobuf) +{ + assert(iobuf != NULL); + assert(iobuf->bytes != NULL || iobuf->nbytes == 0); + assert(iobuf->pos <= iobuf->nbytes); + + dump(iobuf->bytes, iobuf->pos); +} + +void dump_const_iobuf(const struct const_iobuf *iobuf) +{ + assert(iobuf != NULL); + assert(iobuf->bytes != NULL || iobuf->nbytes == 0); + assert(iobuf->pos <= iobuf->nbytes); + + dump(&iobuf->bytes[iobuf->pos], iobuf->nbytes - iobuf->pos); +} + +#endif diff --git a/util/dump.h b/util/dump.h new file mode 100644 index 0000000..bfc3c69 --- /dev/null +++ b/util/dump.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "hook/iobuf.h" + +#ifndef NDEBUG +void dump(const void *ptr, size_t nbytes); +void dump_iobuf(const struct iobuf *iobuf); +void dump_const_iobuf(const struct const_iobuf *iobuf); +#else +#define dump(ptr, nbytes) +#define dump_iobuf(iobuf) +#define dump_const_iobuf(iobuf) +#endif diff --git a/util/lib.c b/util/lib.c new file mode 100644 index 0000000..bd7a5eb --- /dev/null +++ b/util/lib.c @@ -0,0 +1,29 @@ +#include + +#include + +wchar_t *module_file_name(HMODULE module) +{ + size_t buf_len; + DWORD len; + wchar_t *buf; + + buf_len = MAX_PATH; + buf = malloc(buf_len * sizeof(*buf)); + + while (true) { + len = GetModuleFileNameW(module, buf, buf_len); + + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + buf_len = len; + buf = realloc(buf, buf_len * sizeof(*buf)); + + break; + } + + buf_len *= 2; + buf = realloc(buf, buf_len * sizeof(*buf)); + } + + return buf; +} diff --git a/util/lib.h b/util/lib.h new file mode 100644 index 0000000..f3d717e --- /dev/null +++ b/util/lib.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +wchar_t *module_file_name(HMODULE module); diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 0000000..575d123 --- /dev/null +++ b/util/meson.build @@ -0,0 +1,25 @@ +util_lib = static_library( + 'util', + include_directories : inc, + implicit_include_directories : false, + c_pch : '../precompiled.h', + dependencies : [ + capnhook.get_variable('hook_dep'), + ], + sources : [ + 'async.c', + 'async.h', + 'crc.c', + 'crc.h', + 'dll-bind.c', + 'dll-bind.h', + 'dprintf.c', + 'dprintf.h', + 'dump.c', + 'dump.h', + 'lib.c', + 'lib.h', + 'str.c', + 'str.h', + ], +) diff --git a/util/str.c b/util/str.c new file mode 100644 index 0000000..41ea6c2 --- /dev/null +++ b/util/str.c @@ -0,0 +1,11 @@ +#include +#include +#include +#include + +#include "util/str.h" + +extern inline bool str_eq(const char *lhs, const char *rhs); +extern inline bool str_ieq(const char *lhs, const char *rhs); +extern inline bool wstr_eq(const wchar_t *lhs, const wchar_t *rhs); +extern inline bool wstr_ieq(const wchar_t *lhs, const wchar_t *rhs); diff --git a/util/str.h b/util/str.h new file mode 100644 index 0000000..b07948a --- /dev/null +++ b/util/str.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +inline bool str_eq(const char *lhs, const char *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } + + return strcmp(lhs, rhs) == 0; +} + +inline bool str_ieq(const char *lhs, const char *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } + + return _stricmp(lhs, rhs) == 0; +} + +inline bool wstr_eq(const wchar_t *lhs, const wchar_t *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } + + return wcscmp(lhs, rhs) == 0; +} + +inline bool wstr_ieq(const wchar_t *lhs, const wchar_t *rhs) +{ + if (lhs == NULL || rhs == NULL) { + return lhs == rhs; + } + + return _wcsicmp(lhs, rhs) == 0; +}