diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..094640d --- /dev/null +++ b/.clang-format @@ -0,0 +1,45 @@ +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: 'false' +AlignConsecutiveDeclarations: 'false' +AlignEscapedNewlines: Left +AlignOperands: 'false' +AlignTrailingComments: 'false' +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: 'false' +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'true' +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Linux +BreakBeforeTernaryOperators: 'false' +BreakStringLiterals: 'true' +ColumnLimit: '80' +ContinuationIndentWidth: '4' +IncludeBlocks: Preserve +IndentCaseLabels: 'true' +IndentPPDirectives: None +IndentWidth: '2' +IndentWrappedFunctionNames: 'false' +KeepEmptyLinesAtTheStartOfBlocks: 'true' +Language: Cpp +MaxEmptyLinesToKeep: '1' +PointerAlignment: Right +ReflowComments: 'true' +SortIncludes: 'true' +SpaceAfterCStyleCast: 'true' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: 'false' +SpacesBeforeTrailingComments: '1' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'false' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +TabWidth: '2' +UseTab: Never diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ca3661d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.idea +build +.vscode +*.iml +*.ipr +*.iws +.DS_Store +tmp +utils +cmake-build-debug \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..649adff --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode +build/ +cmake-build-debug/ +.idea/ +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..56634c9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,56 @@ +# Release history +## 1.08 +* Documentation updates +* Public release, working hooks: mk3, exc, x2, zero, nx, nx2, pro, pro2 + +## 1.07 +* Bugfix nx2hook usb thumb drives not being detected on all kernel versions correctly +* Include piubtn library and api (piu pro button board) in distribution for SGL +* Various documentation improvements +* Various minor bugfixes and improvements + +## 1.06 +* nx2hook: Add hook library for NX2 +* pumpnet: Support usbprofiles over network for NX2 +* Various bugfixes + +## 1.05 +* Minor improvements +* Improve log/debug output +* piueb: Add `valgrind` run mode for debugging +* Improve documentation +* ptapi/piuio: Bugfix resolving path to configuration file. Config file local to piuio library. +* usbhooking: Bugfix to make PIUIO work without using ptapi-io-piuio-real.so on newer games, Exceed 2, Zero, NX + +## 1.04 +* nxhook: Add hook library for NX +* Minor bugfixes + +## 1.03 +* zerohook: Add hook library for Zero +* Bugfix linking error ptapi piuio real impl +* Bugfix piueb environment variable order with LD_LOAD_LIBRARY +* Readme/documentation cleanup, merged duplicate documentation for hooks +* Various other bugfixes + +## 1.02 +* x2hook: Add hook library for Exceed 2 +* Various bugfixes +* Documentation and code cleanup + +## 1.01 +* exchook: Add hook library for Exceed +* mk3hook: Add support for piuio emulation -> configurable keyboard and joystick supported +* Add game documentation: Trivia, unlocks and pad codes about all games released so far (check game-doc.zip) +* Update API documentation +* Add stub example for implementing your own piuio module +* Add configurable joystick/gamepad support for piuio emulation (see readme files for instructions) +* Add configurable keyboard support for piuio emulation (see readme files for instructions) +* Refactor pumptools API (ptapi) piuio and piubtn modules +* Build releases using docker container with ubuntu 16.04 to enhance compatibility with older libc versions +* piueb: Add commands "debug" and "strace" to start game with a gdb server or strace attached for quick debugging +* Various bugfixes + +## 1.00 +* Release of mk3hook for Linux ports +* Initial public release of binary distribution of pumptools \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cca0961 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 2.8) + +add_subdirectory(cmake) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..dd306c4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing +Please have a look at the issue board about what's currently going on. If you see an issue that is not assigned yet and +that you can and want to work on, please leave a comment to reach out to one of our developers. You can also ask +questions and discuss the issue. + +# Bug reports +Please check if the bug was already reported by searching the existing issues. If it does, feel free to leave a comment +that you also have this issue. Add additional information, screenshots, log files etc. The more information and details +we have, the easier it might get to solve the issue. + +When creating new issues, use our template for reporting bugs. It tells you what kind of information we need. + +# Pull requests: bugfixes, new features or other code contributions +Pull requests are welcome! May it be a PR to an already known issue or a new feature that you consider as a valuable +contribution, please open a PR. 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 developers 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. + +Please read our [development guidelines](doc/development/development.md) as they contain valuable information that your +contribution meets our standards. + +Fork the upstream repository and start developing on your personal fork. Make sure to test your changes with games that +are affected by them. Pay attention to the target platform/hardware you are testing on! This is valuable information +that should be included in the PR. + +Once you completed your implementation, open a pull request on the upstream repository to propose your changes. We will +review your contribution and get back to you about any changes or when we merge them to our upstream repository. Your +changes, once approved, will be included in the next release. + +# Roadmap +No concrete roadmap or timeline exists. When the time is right, we continue adding support for newer games as well. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0b80944 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +# Use a rather old version of ubuntu to ensure compatibility regarding libc +FROM ubuntu:16.04 + +LABEL description="Build environment for pumptools" + +# Install build dependencies, multilib to get 32-bit versions +RUN dpkg --add-architecture i386 +RUN apt-get update && apt-get install -y \ + g++-multilib \ + gcc-multilib \ + zip \ + cmake \ + make \ + git \ + libc6-dev-i386 \ + libusb-1.0-0-dev:i386 \ + libusb-dev:i386 \ + libasound2-dev:i386 \ + libconfig++-dev:i386 \ + libx11-dev:i386 \ + libcurl4-gnutls-dev:i386 + +# Copy files for building to container +RUN mkdir /pumptools +WORKDIR /pumptools + +COPY CHANGELOG.md CHANGELOG.md +COPY CMakeLists.txt CMakeLists.txt +COPY CONTRIBUTING.md CONTRIBUTING.md +COPY LICENSE LICENSE +COPY Makefile Makefile +COPY Package.mk Package.mk +COPY README.md README.md +COPY dist dist +COPY doc doc +COPY cmake cmake +COPY src src +# .git folder required or building fails when version is generated +COPY .git .git + +# Building +RUN make build package \ No newline at end of file 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..623df41 --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +V ?= @ +BUILDDIR ?= build + +gitrev := $(shell git rev-parse HEAD) +libcver := $(shell ldd --version | head -n 1) + +all: \ +usage + +.PHONY: \ +build \ +clean \ +test \ +build-docker \ +git-version \ +libc-version \ +clang-format \ +package + +usage: + $(V)echo "Pumptools project build makefile" + $(V)echo " build: Build the pumptools project" + $(V)echo " clean: Clean up all build output" + $(V)echo " test: Run pumptools's unit tests" + $(V)echo " build-docker: Build the pumptools project in a docker container" + $(V)echo " git-version: Generate a text file with the current git revision" + $(V)echo " libc-version: Generate a text file with the libc version available" + $(V)echo " clang-format: Apply code style defined in .clang-format style to all code in src/" + $(V)echo " package: Package the build output into distribution zip files" + +# Standard build target to build pumptools +build: git-version libc-version + $(V)mkdir -p $(BUILDDIR) + $(V)cd $(BUILDDIR) && cmake .. && make -j $(grep -c ^processor /proc/cpuinfo) + +clean: + $(V)rm -rf $(BUILDDIR) + +test: build + $(V)scripts/run-tests.sh $(BUILDDIR) + +# Build pumptools in a docker image and extract the build output +build-docker: + $(V)rm -rf $(BUILDDIR)/docker + $(V)mkdir -p $(BUILDDIR)/docker + $(V)docker build -t pumptools:build . + $(V)docker create --name pumptools-build pumptools:build + $(V)docker cp pumptools-build:/pumptools/build $(BUILDDIR)/docker + $(V)mv $(BUILDDIR)/docker/build/* $(BUILDDIR)/docker + $(V)rm -r $(BUILDDIR)/docker/build + $(V)docker rm -f pumptools-build + $(V)echo "Build output of docker build can be found in build/docker subfolder." + +# Generate a version file to identify the build +git-version: + $(V)mkdir -p $(BUILDDIR) + $(V)echo "$(gitrev)" > $(BUILDDIR)/git-version + +# Generate a version file to identify the libc runtime used to build +libc-version: + $(V)mkdir -p $(BUILDDIR) + $(V)echo "$(libcver)" > $(BUILDDIR)/libc-version + +clang-format: + $(V)find src/ -iname *.h -o -iname *.c | xargs clang-format -i -style=file + +include Package.mk \ No newline at end of file diff --git a/Package.mk b/Package.mk new file mode 100644 index 0000000..9ac4cfe --- /dev/null +++ b/Package.mk @@ -0,0 +1,269 @@ +# +# Distribution build rules +# + +builddir := $(BUILDDIR) +zipdir := $(BUILDDIR)/zip + +$(zipdir)/: + $(V)mkdir -p $@ + +################################################ +# Hooks: Lexical sorting + +$(zipdir)/exchook.zip: \ + $(builddir)/bin/exchook.so \ + dist/conf/exchook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/f2hook.zip: \ + $(builddir)/bin/f2hook.so \ + dist/conf/f2hook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/fexhook.zip: \ + $(builddir)/bin/fexhook.so \ + dist/conf/fexhook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/fsthook.zip: \ + $(builddir)/bin/fsthook.so \ + dist/conf/fsthook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/mk3hook.zip: \ + $(builddir)/bin/mk3hook.so \ + dist/piueb \ + dist/conf/mk3hook.conf \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/nx2hook.zip: \ + $(builddir)/bin/nx2hook.so \ + dist/conf/nx2hook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/nxahook.zip: \ + $(builddir)/bin/nxahook.so \ + dist/conf/nxahook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/nxhook.zip: \ + $(builddir)/bin/nxhook.so \ + dist/conf/nxhook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/prihook.zip: \ + $(builddir)/bin/prihook.so \ + dist/conf/prihook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/prohook.zip: \ + $(builddir)/bin/prohook.so \ + dist/conf/prohook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/pro2hook.zip: \ + $(builddir)/bin/pro2hook.so \ + dist/conf/pro2hook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/x2hook.zip: \ + $(builddir)/bin/x2hook.so \ + dist/conf/x2hook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/zerohook.zip: \ + $(builddir)/bin/zerohook.so \ + dist/conf/zerohook.conf \ + dist/piueb \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +################################################ +# API: Lexical sorting + +# Very messed up and hacky way to preserve the sub-directories as zip does not support this. +# Feel free to improve if you got a more sophisticated solution. +$(zipdir)/api.zip: \ + src/api/ptapi \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)cp -r src/api/ptapi $(builddir) + $(V)cd $(builddir) && pwd && zip -r api.zip ptapi && cp api.zip zip/api.zip + +$(zipdir)/piubtn.zip: \ + $(builddir)/bin/ptapi-io-piubtn-joystick.so \ + $(builddir)/bin/ptapi-io-piubtn-joystick-conf \ + $(builddir)/bin/ptapi-io-piubtn-keyboard.so \ + $(builddir)/bin/ptapi-io-piubtn-keyboard-conf \ + $(builddir)/bin/ptapi-io-piubtn-null.so \ + $(builddir)/bin/ptapi-io-piubtn-real.so \ + $(builddir)/bin/ptapi-io-piubtn-test \ + dist/api/ptapi-io-piubtn-stub.c \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(zipdir)/piuio.zip: \ + $(builddir)/bin/ptapi-io-piuio-joystick.so \ + $(builddir)/bin/ptapi-io-piuio-joystick-conf \ + $(builddir)/bin/ptapi-io-piuio-keyboard.so \ + $(builddir)/bin/ptapi-io-piuio-keyboard-conf \ + $(builddir)/bin/ptapi-io-piuio-null.so \ + $(builddir)/bin/ptapi-io-piuio-real.so \ + $(builddir)/bin/ptapi-io-piuio-test \ + dist/api/ptapi-io-piuio-stub.c \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +################################################ +# (USB) profile: Lexical sorting + +# $(builddir)/bin/fex-profile-tool \ + +$(zipdir)/profile-tools.zip: \ + $(builddir)/bin/nx2-profile-tool \ + $(builddir)/bin/nxa-profile-tool \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +################################################ +# pumpnet: Lexical sorting + +$(zipdir)/pumpnet.zip: \ + $(builddir)/bin/pumpnet-client \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip -j $@ $^ + +################################################ +# Documentation + +$(zipdir)/doc.zip: \ + doc/api/io/piuio.md \ + doc/api/io/x11-input-hook.md \ + doc/game/image \ + doc/game/01-1st.md \ + doc/game/02-2nd.md \ + doc/game/03-3rd.md \ + doc/game/04-3se.md \ + doc/game/05-tc.md \ + doc/game/06-pc.md \ + doc/game/07-extra.md \ + doc/game/08-prem1.md \ + doc/game/09-prex1.md \ + doc/game/10-reb.md \ + doc/game/11-prem2.md \ + doc/game/12-prex2.md \ + doc/game/13-prem3.md \ + doc/game/14-prex3.md \ + doc/game/15-exc.md \ + doc/game/16-exc2.md \ + doc/game/17-zero.md \ + doc/game/18-nx.md \ + doc/game/20-nx2.md \ + doc/game/game.md \ + doc/hook/exchook.md \ + doc/hook/hook.md \ + doc/hook/mk3hook.md \ + doc/hook/nxhook.md \ + doc/hook/nx2hook.md \ + doc/hook/os.md \ + doc/hook/x2hook.md \ + doc/hook/zerohook.md \ + doc/README.md \ + | $(zipdir)/ + $(V)echo ... $@ + $(V)zip $@ $^ + +################################################ + +$(builddir)/pumptools.zip: \ + $(zipdir)/api.zip \ + $(zipdir)/doc.zip \ + $(zipdir)/exchook.zip \ + $(zipdir)/f2hook.zip \ + $(zipdir)/fexhook.zip \ + $(zipdir)/fsthook.zip \ + $(zipdir)/mk3hook.zip \ + $(zipdir)/nx2hook.zip \ + $(zipdir)/nxahook.zip \ + $(zipdir)/nxhook.zip \ + $(zipdir)/piubtn.zip \ + $(zipdir)/piuio.zip \ + $(zipdir)/profile-tools.zip \ + $(zipdir)/prihook.zip \ + $(zipdir)/pro2hook.zip \ + $(zipdir)/prohook.zip \ + $(zipdir)/pumpnet.zip \ + $(zipdir)/x2hook.zip \ + $(zipdir)/zerohook.zip \ + CHANGELOG.md \ + CONTRIBUTING.md \ + LICENSE \ + README.md \ + $(builddir)/git-version \ + $(builddir)/libc-version + $(V)echo ... $@ + $(V)zip -j $@ $^ + +$(builddir)/pumptools-public.zip: \ + $(zipdir)/api.zip \ + $(zipdir)/doc.zip \ + $(zipdir)/exchook.zip \ + $(zipdir)/mk3hook.zip \ + $(zipdir)/nxhook.zip \ + $(zipdir)/nx2hook.zip \ + $(zipdir)/piubtn.zip \ + $(zipdir)/piuio.zip \ + $(zipdir)/x2hook.zip \ + $(zipdir)/zerohook.zip \ + CHANGELOG.md \ + LICENSE \ + README.md \ + $(builddir)/git-version \ + $(builddir)/libc-version + $(V)echo ... $@ + $(V)zip -j $@ $^ + +################################################ + +package: $(builddir)/pumptools.zip $(builddir)/pumptools-public.zip \ No newline at end of file diff --git a/README.md b/README.md index 31bda43..4fc5be6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,117 @@ -This is currently a placeholder repository for the source release of pumptools. +# Pumptools: The ultimate toolset to run all Linux based PIU games on any* kernel and distribution +Version: 1.08
+[Release history](CHANGELOG.md) -In the meantime, use the issue tracker to report any bugs you encounter with -the binary releases. \ No newline at end of file +This tool-set provides hook libraries that can be pre-loaded to run vanilla dumps of all Pump It Up games running on +Linux. This also includes the only exclusive MK5 hardware based game Exceed and the MK3 Linux ports. + +## Public release note +Currently, the public release contains all source code required to run all games listed +[here](#games-and-latest-versions-supported). The following games however are currently not working due to missing +dongle key tables and raw hdd areas (dummy files exist to allow full compilation of the project): +* NXA +* Fiesta +* FiestaEX +* Fiesta 2 +* Prime + +The required assets to run these games will follow with future releases. + +## Documentation +Additional documentation on different topics related to pumptools are located in the `doc` subfolder +[here](doc/README.md). + +For release distribution packages, you can find a `doc.zip` file inside the `pumptools.zip` package that contains all +documentation also referenced from here: +* [pumptools api](doc/api/api.md) +* [pumptools development](doc/development/development.md) +* [pumptools's architecture](doc/development/architecture.md) +* [general game related stuff like pad codes, unlocks etc](doc/game/game.md) +* [game hook libraries](doc/hook/hook.md) + +## Hook libraries +### Features +* Run all supported games on recent hardware, Linux distributions and kernel versions +* Environment sandboxing to increase compatibility and portability +* API for hardware abstraction: Write your own drivers for IO devices +* Full dongle emulation in software +* Works with **vanilla** data, no hex edits +* Various (bug)fixes and quality of live improvements for many games + +### Games and latest versions supported +All hooks share a major set of features provided by pumptools. Therefore, common topics are covered in the +[main hook readme](doc/hook/hook.md). Start there when you are setting up a game with pumptools. Additionally, refer to +each dedicated hook readme for further important and required information to successfully setup and run the games. + +The following games are supported and tested with the latest versions available. Earlier version might also be support +and work because of the nature the hooks are implemented. + +* [MK3 Linux Ports: 1st to Prem 3/Prex 3](doc/hook/mk3hook.md): MK3 Linux ports (there is only one version officially +available per game version) +* [Exceed](doc/hook/exchook.md): 20040408 +* [Exceed 2](doc/hook/x2hook.md): 102 +* [Zero](doc/hook/zerohook.md): 103 +* [NX](doc/hook/nxhook.md): 108 +* [NX2](doc/hook/nx2hook.md): 154 + +Not available, yet: +* [Pro](doc/hook/prohook.md): R5 nodongle +* [NXA](doc/hook/nxahook.md): 110 +* [Pro2](doc/hook/pro2hook.md): R5 nodongle +* [Fiesta](doc/hook/fsthook.md): 120 +* [FiestaEX](doc/hook/fexhook.md): 151 +* [Fiesta 2](doc/hook/f2hook.md): 161 +* [Prime](doc/hook/prihook.md): 122 + +### Prepare your operating system +Before you start installing and configuring anything of pumptools, read [this readme](doc/hook/os.md) on how to prepare +your operating system and environment. + +### Get and prepare game data +You need **vanilla**, i.e. non-tempered and non-decrypted, game assets as well as non-tempered executable binaries +which can be pulled from legit and clean physical disks or images of them. + +For setting up the games, have a look at the dedicated readme files for +[each hook and game](#games-and-latest-versions-supported). + +### Setup hook libraries +**READ THE WHOLE README OF THE HOOK BUILD YOU WANT TO USE FIRST BEFORE DOING ANYTHING!!!** + +The readme files go into great detail how to setup things with various potential relevant aspects spread across the +document. Being aware of them will save you a lot of puzzling and time. Furthermore, read the FAQ/troubleshooting +section before as well to be aware of any known issues when you encounter them. + +## Building +Make sure you have the [required tools](doc/development.md) installed. Once that's done, simply run +```shell script +make build +``` + +to build it on your current host from the root directory of this project. + +The build output is located in the `build` subdirectory. The built binaries can be found in `build/bin` and a +distribution package called `pumptools.zip` can be found in the `build` subfolder. + +You can also build in a docker container. The container provides the necessary build environment and also ensures +[compatibility regarding glibc versions](doc/development/development.md#compatibility-issues-with-newer-glibc-versions). +```shell script +make build-docker +``` + +The build output from the docker image is located in `build/docker`. + +## Development +## API +Please refer to the [API documentation](doc/api/api.md). + +## Architecture +A dedicate [architecture document](doc/architecture.md) outlines the architecture of pumptools and points out the most +important aspects you should know before you get started with development. + +## Pumptools development +Please refer to the [development readme](doc/development.md) regarding what you have to know when you want to develop +something for pumptools. + +## License +Source code license is the Unlicense; you are permitted to do with this as thou wilt. For details, please refer to the +[LICENSE file](LICENSE) included with the source code. \ No newline at end of file diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt new file mode 100644 index 0000000..b47daf9 --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 2.8) + +option(PT_PUBLIC_RELEASE "PT_PUBLIC_RELEASE" "0") + +if (PT_PUBLIC_RELEASE STREQUAL "1") + message(WARNING "!!! Compiling public release !!!\n!!! Compiling public release !!!\n!!! Compiling public release !!!") +endif () + +# set git revision +execute_process(COMMAND git log -1 --format=%h --date=short HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GITREV ERROR_QUIET) +string(STRIP "${GITREV}" GITREV) +if (GITREV STREQUAL "") + set(GITREV "not available") +endif () + +include(modules/AddResources.cmake) + +set(PT_ROOT_SRC "${CMAKE_CURRENT_LIST_DIR}/../src") + +set(PT_ROOT_IMPORTS "${PT_ROOT_SRC}/imports") +set(PT_ROOT_PTAPI "${PT_ROOT_SRC}/api") +set(PT_ROOT_MAIN "${PT_ROOT_SRC}/main") +set(PT_ROOT_TEST "${PT_ROOT_SRC}/test") + +# force out of source build +message(STATUS "Force out of source build check...") +string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" insource) + +if (insource) + message(FATAL_ERROR "Do not build in your source dir. Use a dedicated build folder. Now go cleanup the cache in the CMakeCache.txt and CMakeFiles folder which don't belong there!") +endif () + +# binary output dir +set(PT_BIN_OUTPUT_PATH "${CMAKE_BINARY_DIR}/bin" CACHE STRING "Output path for compiled binaries") + +# For generic no-config case +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PT_BIN_OUTPUT_PATH}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PT_BIN_OUTPUT_PATH}) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PT_BIN_OUTPUT_PATH}) + +# Common C/C++ save +set(CMAKE_C_STANDARD 99) +set(CMAKE_CXX_STANDARD 14) + +# TODO cleanup flags and add them to each module separately +# Important note: Don't use -O3 because that will break the dongle emulation code for microdog40 (wtf) +# Also, -O3 seems to optimize too much stuff and kills the amixer-block module entirely (the system() preload +# can't be called anymore) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DGITREV=${GITREV} -pipe -Wall -Werror -std=gnu99 -D_GNU_SOURCE -Wno-attributes -m32") +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined -m32") + +# Enable debug flags on non public builds +if (PT_PUBLIC_RELEASE STREQUAL "0") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -g -rdynamic") +endif () + +# Disable asserts and strip binary +if (PT_PUBLIC_RELEASE STREQUAL "1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNDEBUG -DPT_PUBLIC_RELEASE") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -s") +endif () + +add_subdirectory(src) + +# Add a text file containing the git commit rev +file(WRITE ${CMAKE_BINARY_DIR}/version ${GITREV}) \ No newline at end of file diff --git a/cmake/modules/AddResources.cmake b/cmake/modules/AddResources.cmake new file mode 100644 index 0000000..57d546a --- /dev/null +++ b/cmake/modules/AddResources.cmake @@ -0,0 +1,12 @@ +# Compilation step for static resources +FUNCTION(ADD_RESOURCES out_var path file) + SET(result) + SET(result "${PROJECT_BINARY_DIR}/${file}.o") + ADD_CUSTOM_COMMAND(OUTPUT ${result} + COMMAND ld -m elf_i386 -L"${path}" -r -b binary -o ${result} ${file} + DEPENDS ${path}/${file} + WORKING_DIRECTORY ${path} + COMMENT "Building binary data object ${result}" + VERBATIM) + SET(${out_var} "${result}" PARENT_SCOPE) +ENDFUNCTION() diff --git a/cmake/src/CMakeLists.txt b/cmake/src/CMakeLists.txt new file mode 100644 index 0000000..5995d66 --- /dev/null +++ b/cmake/src/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(imports) +add_subdirectory(main) +add_subdirectory(test) \ No newline at end of file diff --git a/cmake/src/imports/CMakeLists.txt b/cmake/src/imports/CMakeLists.txt new file mode 100644 index 0000000..0838b25 --- /dev/null +++ b/cmake/src/imports/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(cmocka) \ No newline at end of file diff --git a/cmake/src/imports/cmocka/CMakeLists.txt b/cmake/src/imports/cmocka/CMakeLists.txt new file mode 100644 index 0000000..847a4cf --- /dev/null +++ b/cmake/src/imports/cmocka/CMakeLists.txt @@ -0,0 +1,13 @@ +project(cmocka) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_SRC}/imports/cmocka) + +include_directories(${SRC}) + +MESSAGE(STATUS, "${PT_IMPORTS_SRC}") + +set(SOURCE_FILES + ${SRC}/cmocka.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) diff --git a/cmake/src/main/CMakeLists.txt b/cmake/src/main/CMakeLists.txt new file mode 100644 index 0000000..ce40bd0 --- /dev/null +++ b/cmake/src/main/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(${PT_ROOT_IMPORTS}) +include_directories(${PT_ROOT_PTAPI}) +include_directories(${PT_ROOT_MAIN}) + +add_subdirectory(asset) +add_subdirectory(capnhook) +add_subdirectory(crypt) +add_subdirectory(hook) +add_subdirectory(io) +add_subdirectory(ptapi) +add_subdirectory(pumpnet) +add_subdirectory(sec) +add_subdirectory(util) \ No newline at end of file diff --git a/cmake/src/main/asset/CMakeLists.txt b/cmake/src/main/asset/CMakeLists.txt new file mode 100644 index 0000000..71c5dad --- /dev/null +++ b/cmake/src/main/asset/CMakeLists.txt @@ -0,0 +1,7 @@ +add_subdirectory(f2) +add_subdirectory(fex) +add_subdirectory(nx) +add_subdirectory(nx2) +add_subdirectory(nxa) +add_subdirectory(x2) +add_subdirectory(zero) \ No newline at end of file diff --git a/cmake/src/main/asset/f2/CMakeLists.txt b/cmake/src/main/asset/f2/CMakeLists.txt new file mode 100644 index 0000000..10b87d1 --- /dev/null +++ b/cmake/src/main/asset/f2/CMakeLists.txt @@ -0,0 +1,2 @@ +#add_subdirectory(lib) +#add_subdirectory(tool) \ No newline at end of file diff --git a/cmake/src/main/asset/f2/lib/CMakeLists.txt b/cmake/src/main/asset/f2/lib/CMakeLists.txt new file mode 100644 index 0000000..988c3af --- /dev/null +++ b/cmake/src/main/asset/f2/lib/CMakeLists.txt @@ -0,0 +1,11 @@ +project(asset-f2) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/f2/lib) + +set(SOURCE_FILES + ${SRC}/usb-rank.c + ${SRC}/usb-save.c + ${SRC}/util.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) diff --git a/cmake/src/main/asset/fex/CMakeLists.txt b/cmake/src/main/asset/fex/CMakeLists.txt new file mode 100644 index 0000000..83ad3ac --- /dev/null +++ b/cmake/src/main/asset/fex/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(lib) +add_subdirectory(tool) \ No newline at end of file diff --git a/cmake/src/main/asset/fex/lib/CMakeLists.txt b/cmake/src/main/asset/fex/lib/CMakeLists.txt new file mode 100644 index 0000000..09a18d7 --- /dev/null +++ b/cmake/src/main/asset/fex/lib/CMakeLists.txt @@ -0,0 +1,11 @@ +project(asset-fex) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/fex/lib) + +set(SOURCE_FILES + ${SRC}/usb-rank.c + ${SRC}/usb-save.c + ${SRC}/util.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) diff --git a/cmake/src/main/asset/fex/tool/CMakeLists.txt b/cmake/src/main/asset/fex/tool/CMakeLists.txt new file mode 100644 index 0000000..69a87ae --- /dev/null +++ b/cmake/src/main/asset/fex/tool/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(usb-profile) \ No newline at end of file diff --git a/cmake/src/main/asset/fex/tool/usb-profile/CMakeLists.txt b/cmake/src/main/asset/fex/tool/usb-profile/CMakeLists.txt new file mode 100644 index 0000000..d859695 --- /dev/null +++ b/cmake/src/main/asset/fex/tool/usb-profile/CMakeLists.txt @@ -0,0 +1,11 @@ +project(fex-usb-profile-tool) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/fex/tool) + +set(SOURCE_FILES + ${SRC}/usb-profile.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} asset-fex util) diff --git a/cmake/src/main/asset/nx/CMakeLists.txt b/cmake/src/main/asset/nx/CMakeLists.txt new file mode 100644 index 0000000..83ad3ac --- /dev/null +++ b/cmake/src/main/asset/nx/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(lib) +add_subdirectory(tool) \ No newline at end of file diff --git a/cmake/src/main/asset/nx/lib/CMakeLists.txt b/cmake/src/main/asset/nx/lib/CMakeLists.txt new file mode 100644 index 0000000..f8611d9 --- /dev/null +++ b/cmake/src/main/asset/nx/lib/CMakeLists.txt @@ -0,0 +1,10 @@ +project(asset-nx) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/nx/lib) + +set(SOURCE_FILES + ${SRC}/settings.c + ${SRC}/util.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) diff --git a/cmake/src/main/asset/nx/tool/CMakeLists.txt b/cmake/src/main/asset/nx/tool/CMakeLists.txt new file mode 100644 index 0000000..2508341 --- /dev/null +++ b/cmake/src/main/asset/nx/tool/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(settings) \ No newline at end of file diff --git a/cmake/src/main/asset/nx/tool/settings/CMakeLists.txt b/cmake/src/main/asset/nx/tool/settings/CMakeLists.txt new file mode 100644 index 0000000..ce7a039 --- /dev/null +++ b/cmake/src/main/asset/nx/tool/settings/CMakeLists.txt @@ -0,0 +1,11 @@ +project(nx-settings-tool) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/nx/tool) + +set(SOURCE_FILES + ${SRC}/settings.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} asset-nx util) diff --git a/cmake/src/main/asset/nx2/CMakeLists.txt b/cmake/src/main/asset/nx2/CMakeLists.txt new file mode 100644 index 0000000..83ad3ac --- /dev/null +++ b/cmake/src/main/asset/nx2/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(lib) +add_subdirectory(tool) \ No newline at end of file diff --git a/cmake/src/main/asset/nx2/lib/CMakeLists.txt b/cmake/src/main/asset/nx2/lib/CMakeLists.txt new file mode 100644 index 0000000..b565742 --- /dev/null +++ b/cmake/src/main/asset/nx2/lib/CMakeLists.txt @@ -0,0 +1,13 @@ +project(asset-nx2) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/nx2/lib) + +set(SOURCE_FILES + ${SRC}/usb-rank.c + ${SRC}/usb-save.c + ${SRC}/util.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} util) diff --git a/cmake/src/main/asset/nx2/tool/CMakeLists.txt b/cmake/src/main/asset/nx2/tool/CMakeLists.txt new file mode 100644 index 0000000..69a87ae --- /dev/null +++ b/cmake/src/main/asset/nx2/tool/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(usb-profile) \ No newline at end of file diff --git a/cmake/src/main/asset/nx2/tool/usb-profile/CMakeLists.txt b/cmake/src/main/asset/nx2/tool/usb-profile/CMakeLists.txt new file mode 100644 index 0000000..887c6fd --- /dev/null +++ b/cmake/src/main/asset/nx2/tool/usb-profile/CMakeLists.txt @@ -0,0 +1,11 @@ +project(nx2-profile-tool) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/nx2/tool) + +set(SOURCE_FILES + ${SRC}/usb-profile.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} asset-nx2 util) diff --git a/cmake/src/main/asset/nxa/CMakeLists.txt b/cmake/src/main/asset/nxa/CMakeLists.txt new file mode 100644 index 0000000..83ad3ac --- /dev/null +++ b/cmake/src/main/asset/nxa/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(lib) +add_subdirectory(tool) \ No newline at end of file diff --git a/cmake/src/main/asset/nxa/lib/CMakeLists.txt b/cmake/src/main/asset/nxa/lib/CMakeLists.txt new file mode 100644 index 0000000..02efdf0 --- /dev/null +++ b/cmake/src/main/asset/nxa/lib/CMakeLists.txt @@ -0,0 +1,13 @@ +project(asset-nxa) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/nxa/lib) + +set(SOURCE_FILES + ${SRC}/usb-rank.c + ${SRC}/usb-save.c + ${SRC}/util.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} util) diff --git a/cmake/src/main/asset/nxa/tool/CMakeLists.txt b/cmake/src/main/asset/nxa/tool/CMakeLists.txt new file mode 100644 index 0000000..69a87ae --- /dev/null +++ b/cmake/src/main/asset/nxa/tool/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(usb-profile) \ No newline at end of file diff --git a/cmake/src/main/asset/nxa/tool/usb-profile/CMakeLists.txt b/cmake/src/main/asset/nxa/tool/usb-profile/CMakeLists.txt new file mode 100644 index 0000000..6069fa0 --- /dev/null +++ b/cmake/src/main/asset/nxa/tool/usb-profile/CMakeLists.txt @@ -0,0 +1,11 @@ +project(nxa-profile-tool) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/nxa/tool) + +set(SOURCE_FILES + ${SRC}/usb-profile.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} asset-nxa util) diff --git a/cmake/src/main/asset/x2/CMakeLists.txt b/cmake/src/main/asset/x2/CMakeLists.txt new file mode 100644 index 0000000..8570eeb --- /dev/null +++ b/cmake/src/main/asset/x2/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(lib) \ No newline at end of file diff --git a/cmake/src/main/asset/x2/lib/CMakeLists.txt b/cmake/src/main/asset/x2/lib/CMakeLists.txt new file mode 100644 index 0000000..a173f0e --- /dev/null +++ b/cmake/src/main/asset/x2/lib/CMakeLists.txt @@ -0,0 +1,9 @@ +project(asset-x2) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/x2/lib) + +set(SOURCE_FILES + ${SRC}/settings.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) diff --git a/cmake/src/main/asset/zero/CMakeLists.txt b/cmake/src/main/asset/zero/CMakeLists.txt new file mode 100644 index 0000000..8570eeb --- /dev/null +++ b/cmake/src/main/asset/zero/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(lib) \ No newline at end of file diff --git a/cmake/src/main/asset/zero/lib/CMakeLists.txt b/cmake/src/main/asset/zero/lib/CMakeLists.txt new file mode 100644 index 0000000..c9cf062 --- /dev/null +++ b/cmake/src/main/asset/zero/lib/CMakeLists.txt @@ -0,0 +1,9 @@ +project(asset-zero) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/asset/zero/lib) + +set(SOURCE_FILES + ${SRC}/settings.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) diff --git a/cmake/src/main/capnhook/CMakeLists.txt b/cmake/src/main/capnhook/CMakeLists.txt new file mode 100644 index 0000000..bd338bc --- /dev/null +++ b/cmake/src/main/capnhook/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(hook) +add_subdirectory(hooklib) \ No newline at end of file diff --git a/cmake/src/main/capnhook/hook/CMakeLists.txt b/cmake/src/main/capnhook/hook/CMakeLists.txt new file mode 100644 index 0000000..2395d71 --- /dev/null +++ b/cmake/src/main/capnhook/hook/CMakeLists.txt @@ -0,0 +1,20 @@ +project(capnhook-hook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/capnhook/hook) + +set(SOURCE_FILES + ${PT_ROOT_MAIN}/capnhook/hook/fshook.c + ${PT_ROOT_MAIN}/capnhook/hook/filehook.c + ${PT_ROOT_MAIN}/capnhook/hook/iobuf.c + ${PT_ROOT_MAIN}/capnhook/hook/iohook.c + ${PT_ROOT_MAIN}/capnhook/hook/lib.c + ${PT_ROOT_MAIN}/capnhook/hook/result.c + ${PT_ROOT_MAIN}/capnhook/hook/sig.c + ${PT_ROOT_MAIN}/capnhook/hook/usbhook.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} dl util) \ No newline at end of file diff --git a/cmake/src/main/capnhook/hooklib/CMakeLists.txt b/cmake/src/main/capnhook/hooklib/CMakeLists.txt new file mode 100644 index 0000000..4c7cd29 --- /dev/null +++ b/cmake/src/main/capnhook/hooklib/CMakeLists.txt @@ -0,0 +1,20 @@ +project(capnhook-hooklib) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/capnhook/hooklib) + +set(SOURCE_FILES + ${PT_ROOT_MAIN}/capnhook/hooklib/filehook-mon.c + ${PT_ROOT_MAIN}/capnhook/hooklib/fileopen-mon.c + ${PT_ROOT_MAIN}/capnhook/hooklib/fshook-mon.c + ${PT_ROOT_MAIN}/capnhook/hooklib/iohook-mon.c + ${PT_ROOT_MAIN}/capnhook/hooklib/redir.c + ${PT_ROOT_MAIN}/capnhook/hooklib/usb-emu.c + ${PT_ROOT_MAIN}/capnhook/hooklib/usb-init-fix.c + ${PT_ROOT_MAIN}/capnhook/hooklib/usbhook-mon.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} capnhook-hook util) \ No newline at end of file diff --git a/cmake/src/main/crypt/CMakeLists.txt b/cmake/src/main/crypt/CMakeLists.txt new file mode 100644 index 0000000..99e0f15 --- /dev/null +++ b/cmake/src/main/crypt/CMakeLists.txt @@ -0,0 +1,12 @@ +project(crypt) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/crypt) + +set(SOURCE_FILES + ${SRC}/lib/md5.c + ${SRC}/lib/rijndael.c + ${SRC}/aes.c + ${SRC}/md5.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) diff --git a/cmake/src/main/hook/CMakeLists.txt b/cmake/src/main/hook/CMakeLists.txt new file mode 100644 index 0000000..e86398f --- /dev/null +++ b/cmake/src/main/hook/CMakeLists.txt @@ -0,0 +1,16 @@ +add_subdirectory(core) +add_subdirectory(exc) +add_subdirectory(f2) +add_subdirectory(fex) +add_subdirectory(fst) +add_subdirectory(mk3) +add_subdirectory(nx2) +add_subdirectory(nxa) +add_subdirectory(nx) +add_subdirectory(patch) +add_subdirectory(pri) +add_subdirectory(pro2) +add_subdirectory(propatch) +add_subdirectory(pro) +add_subdirectory(x2) +add_subdirectory(zero) \ No newline at end of file diff --git a/cmake/src/main/hook/core/CMakeLists.txt b/cmake/src/main/hook/core/CMakeLists.txt new file mode 100644 index 0000000..626ef81 --- /dev/null +++ b/cmake/src/main/hook/core/CMakeLists.txt @@ -0,0 +1,14 @@ +project(hook-core) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/core) + +set(SOURCE_FILES + ${SRC}/options.c + ${SRC}/piu-utils.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} util) \ No newline at end of file diff --git a/cmake/src/main/hook/exc/CMakeLists.txt b/cmake/src/main/hook/exc/CMakeLists.txt new file mode 100644 index 0000000..076bf52 --- /dev/null +++ b/cmake/src/main/hook/exc/CMakeLists.txt @@ -0,0 +1,19 @@ +project(exchook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/exc) + +set(SOURCE_FILES + ${SRC}/eeprom.c + ${SRC}/io.c + ${SRC}/lockchip.c + ${SRC}/main.c + ${SRC}/mempatch.c + ${SRC}/options.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} hook-core lockchip ptapi-io-piuio-util patch util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/f2/CMakeLists.txt b/cmake/src/main/hook/f2/CMakeLists.txt new file mode 100644 index 0000000..a0d14d7 --- /dev/null +++ b/cmake/src/main/hook/f2/CMakeLists.txt @@ -0,0 +1,19 @@ +project(f2hook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/f2) + +add_resources(HDD_RAW ${SRC} f2hook.hdd.raw) +add_resources(DOG_KEY ${SRC} f2hook.hasp.key) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${HDD_RAW} ${DOG_KEY}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-shared") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} crypt hook-core microdog34 patch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/fex/CMakeLists.txt b/cmake/src/main/hook/fex/CMakeLists.txt new file mode 100644 index 0000000..b06027c --- /dev/null +++ b/cmake/src/main/hook/fex/CMakeLists.txt @@ -0,0 +1,18 @@ +project(fexhook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/fex) + +add_resources(HDD_RAW ${SRC} fexhook.hdd.raw) +add_resources(DOG_KEY ${SRC} fexhook.dog.key) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${HDD_RAW} ${DOG_KEY}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} crypt hook-core microdog40 patch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/fst/CMakeLists.txt b/cmake/src/main/hook/fst/CMakeLists.txt new file mode 100644 index 0000000..9cb5a70 --- /dev/null +++ b/cmake/src/main/hook/fst/CMakeLists.txt @@ -0,0 +1,18 @@ +project(fsthook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/fst) + +add_resources(HDD_RAW ${SRC} fsthook.hdd.raw) +add_resources(DOG_KEY ${SRC} fsthook.dog.key) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${HDD_RAW} ${DOG_KEY}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} crypt hook-core microdog40 patch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/mk3/CMakeLists.txt b/cmake/src/main/hook/mk3/CMakeLists.txt new file mode 100644 index 0000000..a451a44 --- /dev/null +++ b/cmake/src/main/hook/mk3/CMakeLists.txt @@ -0,0 +1,19 @@ +project(mk3hook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/mk3) + +set(SOURCE_FILES + ${SRC}/1st-2nd-fs-fix.c + ${SRC}/config.c + ${SRC}/fmodex.c + ${SRC}/main.c + ${SRC}/ogl-fix.c + ${SRC}/options.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} hook-core patch util dl ptapi-io-piuio-util) \ No newline at end of file diff --git a/cmake/src/main/hook/nx/CMakeLists.txt b/cmake/src/main/hook/nx/CMakeLists.txt new file mode 100644 index 0000000..4ea5cd7 --- /dev/null +++ b/cmake/src/main/hook/nx/CMakeLists.txt @@ -0,0 +1,19 @@ +project(nxhook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/nx) + +add_resources(HDD_RAW ${SRC} nxhook.hdd.raw) +add_resources(DOG_KEY ${SRC} nxhook.dog.key) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c + ${SRC}/unlock.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${HDD_RAW} ${DOG_KEY}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} asset-nx hook-core microdog34 patch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/nx2/CMakeLists.txt b/cmake/src/main/hook/nx2/CMakeLists.txt new file mode 100644 index 0000000..6cab2ae --- /dev/null +++ b/cmake/src/main/hook/nx2/CMakeLists.txt @@ -0,0 +1,19 @@ +project(nx2hook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/nx2) + +add_resources(HDD_RAW ${SRC} nx2hook.hdd.raw) +add_resources(DOG_KEY ${SRC} nx2hook.dog.key) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c + ${SRC}/profile-gen.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${HDD_RAW} ${DOG_KEY}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} capnhook-hook capnhook-hooklib crypt hook-core microdog40 asset-nx2 patch ptapi-io-piuio-util pumpnet-lib util dl pthread curl) \ No newline at end of file diff --git a/cmake/src/main/hook/nxa/CMakeLists.txt b/cmake/src/main/hook/nxa/CMakeLists.txt new file mode 100644 index 0000000..8f57a75 --- /dev/null +++ b/cmake/src/main/hook/nxa/CMakeLists.txt @@ -0,0 +1,19 @@ +project(nxahook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/nxa) + +add_resources(HDD_RAW ${SRC} nxahook.hdd.raw) +add_resources(DOG_KEY ${SRC} nxahook.dog.key) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c + ${SRC}/profile-gen.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${HDD_RAW} ${DOG_KEY}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} asset-nxa crypt hook-core microdog40 patch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/patch/CMakeLists.txt b/cmake/src/main/hook/patch/CMakeLists.txt new file mode 100644 index 0000000..17c2234 --- /dev/null +++ b/cmake/src/main/hook/patch/CMakeLists.txt @@ -0,0 +1,39 @@ +project(patch) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/patch) + +set(SOURCE_FILES + ${SRC}/asound-fix.c + ${SRC}/amixer-block.c + ${SRC}/blacklist-url.c + ${SRC}/block-keyboard-grab.c + ${SRC}/gfx.c + ${SRC}/hasp.c + ${SRC}/hdd-check.c + ${SRC}/hook-mon.c + ${SRC}/main-loop.c + ${SRC}/microdog34.c + ${SRC}/microdog40.c + ${SRC}/mounts.c + ${SRC}/net-profile.c + ${SRC}/network.c + ${SRC}/piubtn.c + ${SRC}/piuio.c + ${SRC}/piuio-exit.c + ${SRC}/ram-wipe.c + ${SRC}/redir.c + ${SRC}/sigsegv.c + ${SRC}/sound.c + ${SRC}/usb-emu.c + ${SRC}/usb-init-fix.c + ${SRC}/usb-mnt.c + ${SRC}/usb-unlock.c + ${SRC}/usbfs.c + ${SRC}/x11-event-loop.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} capnhook-hook capnhook-hooklib hasp-old microdog34 microdog40) diff --git a/cmake/src/main/hook/pri/CMakeLists.txt b/cmake/src/main/hook/pri/CMakeLists.txt new file mode 100644 index 0000000..8502c22 --- /dev/null +++ b/cmake/src/main/hook/pri/CMakeLists.txt @@ -0,0 +1,19 @@ +project(prihook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/pri) + +add_resources(HDD_RAW ${SRC} prihook.hdd.raw) +add_resources(DOG_KEY ${SRC} prihook.hasp.key) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c + ${SRC}/usb-updates.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${HDD_RAW} ${DOG_KEY}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} crypt hasp-old hook-core patch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/pro/CMakeLists.txt b/cmake/src/main/hook/pro/CMakeLists.txt new file mode 100644 index 0000000..b519ced --- /dev/null +++ b/cmake/src/main/hook/pro/CMakeLists.txt @@ -0,0 +1,15 @@ +project(prohook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/pro) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} hook-core patch propatch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/pro2/CMakeLists.txt b/cmake/src/main/hook/pro2/CMakeLists.txt new file mode 100644 index 0000000..45a1c31 --- /dev/null +++ b/cmake/src/main/hook/pro2/CMakeLists.txt @@ -0,0 +1,15 @@ +project(pro2hook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/pro2) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} hook-core patch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/propatch/CMakeLists.txt b/cmake/src/main/hook/propatch/CMakeLists.txt new file mode 100644 index 0000000..6197c19 --- /dev/null +++ b/cmake/src/main/hook/propatch/CMakeLists.txt @@ -0,0 +1,13 @@ +project(propatch) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/propatch) + +set(SOURCE_FILES + ${SRC}/usb-fix.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} capnhook-hook capnhook-hooklib) diff --git a/cmake/src/main/hook/x2/CMakeLists.txt b/cmake/src/main/hook/x2/CMakeLists.txt new file mode 100644 index 0000000..4987bd7 --- /dev/null +++ b/cmake/src/main/hook/x2/CMakeLists.txt @@ -0,0 +1,19 @@ +project(x2hook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/x2) + +add_resources(DOG_KEY ${SRC} x2hook.dog.key) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/ogl-fix.c + ${SRC}/options.c + ${SRC}/unlock.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${DOG_KEY}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} asset-x2 hook-core microdog34 patch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/hook/zero/CMakeLists.txt b/cmake/src/main/hook/zero/CMakeLists.txt new file mode 100644 index 0000000..eba0b2a --- /dev/null +++ b/cmake/src/main/hook/zero/CMakeLists.txt @@ -0,0 +1,19 @@ +project(zerohook) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/hook/zero) + +add_resources(HDD_RAW ${SRC} zerohook.hdd.raw) +add_resources(DOG_KEY ${SRC} zerohook.dog.key) + +set(SOURCE_FILES + ${SRC}/main.c + ${SRC}/options.c + ${SRC}/unlock.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${DOG_KEY} ${HDD_RAW}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} asset-zero hook-core microdog34 patch ptapi-io-piuio-util util dl pthread) \ No newline at end of file diff --git a/cmake/src/main/io/CMakeLists.txt b/cmake/src/main/io/CMakeLists.txt new file mode 100644 index 0000000..ee6ad67 --- /dev/null +++ b/cmake/src/main/io/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(piubtn) +add_subdirectory(piuio) +add_subdirectory(util) \ No newline at end of file diff --git a/cmake/src/main/io/piubtn/CMakeLists.txt b/cmake/src/main/io/piubtn/CMakeLists.txt new file mode 100644 index 0000000..433ce8d --- /dev/null +++ b/cmake/src/main/io/piubtn/CMakeLists.txt @@ -0,0 +1,14 @@ +project(io-piubtn) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/io/piubtn) + +set(SOURCE_FILES + ${SRC}/device.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) +add_library(piubtn SHARED ${SOURCE_FILES}) + +set_target_properties(piubtn PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} io-util util usb-1.0) \ No newline at end of file diff --git a/cmake/src/main/io/piuio/CMakeLists.txt b/cmake/src/main/io/piuio/CMakeLists.txt new file mode 100644 index 0000000..32b348b --- /dev/null +++ b/cmake/src/main/io/piuio/CMakeLists.txt @@ -0,0 +1,14 @@ +project(io-piuio) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/io/piuio) + +set(SOURCE_FILES + ${SRC}/device.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) +add_library(piuio SHARED ${SOURCE_FILES}) + +set_target_properties(piuio PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} io-util util usb-1.0) \ No newline at end of file diff --git a/cmake/src/main/io/util/CMakeLists.txt b/cmake/src/main/io/util/CMakeLists.txt new file mode 100644 index 0000000..786b24a --- /dev/null +++ b/cmake/src/main/io/util/CMakeLists.txt @@ -0,0 +1,11 @@ +project(io-util) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/io/util) + +set(SOURCE_FILES + ${SRC}/joystick.c + ${SRC}/joystick-util.c + ${SRC}/usb.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) \ No newline at end of file diff --git a/cmake/src/main/ptapi/CMakeLists.txt b/cmake/src/main/ptapi/CMakeLists.txt new file mode 100644 index 0000000..39c7fda --- /dev/null +++ b/cmake/src/main/ptapi/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(piubtn) +add_subdirectory(piuio) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/CMakeLists.txt new file mode 100644 index 0000000..f4ea518 --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/CMakeLists.txt @@ -0,0 +1,10 @@ +add_subdirectory(joystick-conf) +add_subdirectory(joystick) +add_subdirectory(joystick-util) +add_subdirectory(keyboard) +add_subdirectory(keyboard-conf) +add_subdirectory(keyboard-util) +add_subdirectory(null) +add_subdirectory(real) +add_subdirectory(test) +add_subdirectory(util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/joystick-conf/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/joystick-conf/CMakeLists.txt new file mode 100644 index 0000000..34861f7 --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/joystick-conf/CMakeLists.txt @@ -0,0 +1,11 @@ +project(ptapi-io-piubtn-joystick-conf) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/joystick-conf) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} ptapi-io-piubtn-joystick-util io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/joystick-util/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/joystick-util/CMakeLists.txt new file mode 100644 index 0000000..e5be6aa --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/joystick-util/CMakeLists.txt @@ -0,0 +1,13 @@ +project(ptapi-io-piubtn-joystick-util) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/joystick-util) + +set(SOURCE_FILES + ${SRC}/joystick-conf.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/joystick/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/joystick/CMakeLists.txt new file mode 100644 index 0000000..1ed0445 --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/joystick/CMakeLists.txt @@ -0,0 +1,15 @@ +project(ptapi-io-piubtn-joystick) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/joystick) + +set(SOURCE_FILES + ${SRC}/joystick.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +# Remove library name "lib" prefix +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} ptapi-io-piubtn-joystick-util io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/keyboard-conf/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/keyboard-conf/CMakeLists.txt new file mode 100644 index 0000000..c2a5bc7 --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/keyboard-conf/CMakeLists.txt @@ -0,0 +1,11 @@ +project(ptapi-io-piubtn-keyboard-conf) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/keyboard-conf) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} ptapi-io-piubtn-keyboard-util io-util util X11) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/keyboard-util/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/keyboard-util/CMakeLists.txt new file mode 100644 index 0000000..7fac733 --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/keyboard-util/CMakeLists.txt @@ -0,0 +1,13 @@ +project(ptapi-io-piubtn-keyboard-util) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/keyboard-util) + +set(SOURCE_FILES + ${SRC}/keyboard-conf.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/keyboard/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/keyboard/CMakeLists.txt new file mode 100644 index 0000000..33e50de --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/keyboard/CMakeLists.txt @@ -0,0 +1,15 @@ +project(ptapi-io-piubtn-keyboard) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/keyboard) + +set(SOURCE_FILES + ${SRC}/keyboard.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +# Remove library name "lib" prefix +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} ptapi-io-piubtn-keyboard-util io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/null/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/null/CMakeLists.txt new file mode 100644 index 0000000..e10bd5f --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/null/CMakeLists.txt @@ -0,0 +1,15 @@ +project(ptapi-io-piubtn-null) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/null) + +set(SOURCE_FILES + ${SRC}/null.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +# Remove library name "lib" prefix +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/real/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/real/CMakeLists.txt new file mode 100644 index 0000000..b5eea68 --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/real/CMakeLists.txt @@ -0,0 +1,15 @@ +project(ptapi-io-piubtn-real) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/real) + +set(SOURCE_FILES + ${SRC}/piubtn.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +# Remove library name "lib" prefix +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} io-util util usb-1.0) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/test/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/test/CMakeLists.txt new file mode 100644 index 0000000..ec9eb63 --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/test/CMakeLists.txt @@ -0,0 +1,11 @@ +project(ptapi-io-piubtn-test) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/test) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} capnhook-hook ptapi-io-piubtn-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piubtn/util/CMakeLists.txt b/cmake/src/main/ptapi/piubtn/util/CMakeLists.txt new file mode 100644 index 0000000..b600a98 --- /dev/null +++ b/cmake/src/main/ptapi/piubtn/util/CMakeLists.txt @@ -0,0 +1,13 @@ +project(ptapi-io-piubtn-util) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piubtn/util) + +set(SOURCE_FILES + ${SRC}/lib.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} capnhook-hook dl util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/CMakeLists.txt b/cmake/src/main/ptapi/piuio/CMakeLists.txt new file mode 100644 index 0000000..b62b082 --- /dev/null +++ b/cmake/src/main/ptapi/piuio/CMakeLists.txt @@ -0,0 +1,10 @@ +add_subdirectory(joystick-conf) +add_subdirectory(joystick) +add_subdirectory(joystick-util) +add_subdirectory(keyboard-conf) +add_subdirectory(keyboard) +add_subdirectory(keyboard-util) +add_subdirectory(null) +add_subdirectory(real) +add_subdirectory(test) +add_subdirectory(util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/joystick-conf/CMakeLists.txt b/cmake/src/main/ptapi/piuio/joystick-conf/CMakeLists.txt new file mode 100644 index 0000000..e78cf18 --- /dev/null +++ b/cmake/src/main/ptapi/piuio/joystick-conf/CMakeLists.txt @@ -0,0 +1,11 @@ +project(ptapi-io-piuio-joystick-conf) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/joystick-conf) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} ptapi-io-piuio-joystick-util io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/joystick-util/CMakeLists.txt b/cmake/src/main/ptapi/piuio/joystick-util/CMakeLists.txt new file mode 100644 index 0000000..093cb52 --- /dev/null +++ b/cmake/src/main/ptapi/piuio/joystick-util/CMakeLists.txt @@ -0,0 +1,13 @@ +project(ptapi-io-piuio-joystick-util) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/joystick-util) + +set(SOURCE_FILES + ${SRC}/joystick-conf.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/joystick/CMakeLists.txt b/cmake/src/main/ptapi/piuio/joystick/CMakeLists.txt new file mode 100644 index 0000000..9dc3c1b --- /dev/null +++ b/cmake/src/main/ptapi/piuio/joystick/CMakeLists.txt @@ -0,0 +1,15 @@ +project(ptapi-io-piuio-joystick) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/joystick) + +set(SOURCE_FILES + ${SRC}/joystick.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +# Remove library name "lib" prefix +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} ptapi-io-piuio-joystick-util io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/keyboard-conf/CMakeLists.txt b/cmake/src/main/ptapi/piuio/keyboard-conf/CMakeLists.txt new file mode 100644 index 0000000..18e541f --- /dev/null +++ b/cmake/src/main/ptapi/piuio/keyboard-conf/CMakeLists.txt @@ -0,0 +1,11 @@ +project(ptapi-io-piuio-keyboard-conf) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/keyboard-conf) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} ptapi-io-piuio-keyboard-util io-util util X11) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/keyboard-util/CMakeLists.txt b/cmake/src/main/ptapi/piuio/keyboard-util/CMakeLists.txt new file mode 100644 index 0000000..5cb797d --- /dev/null +++ b/cmake/src/main/ptapi/piuio/keyboard-util/CMakeLists.txt @@ -0,0 +1,13 @@ +project(ptapi-io-piuio-keyboard-util) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/keyboard-util) + +set(SOURCE_FILES + ${SRC}/keyboard-conf.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/keyboard/CMakeLists.txt b/cmake/src/main/ptapi/piuio/keyboard/CMakeLists.txt new file mode 100644 index 0000000..b8a40c3 --- /dev/null +++ b/cmake/src/main/ptapi/piuio/keyboard/CMakeLists.txt @@ -0,0 +1,15 @@ +project(ptapi-io-piuio-keyboard) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/keyboard) + +set(SOURCE_FILES + ${SRC}/keyboard.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +# Remove library name "lib" prefix +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} ptapi-io-piuio-keyboard-util io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/null/CMakeLists.txt b/cmake/src/main/ptapi/piuio/null/CMakeLists.txt new file mode 100644 index 0000000..3d5fe29 --- /dev/null +++ b/cmake/src/main/ptapi/piuio/null/CMakeLists.txt @@ -0,0 +1,15 @@ +project(ptapi-io-piuio-null) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/null) + +set(SOURCE_FILES + ${SRC}/null.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +# Remove library name "lib" prefix +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} io-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/real/CMakeLists.txt b/cmake/src/main/ptapi/piuio/real/CMakeLists.txt new file mode 100644 index 0000000..4edee67 --- /dev/null +++ b/cmake/src/main/ptapi/piuio/real/CMakeLists.txt @@ -0,0 +1,15 @@ +project(ptapi-io-piuio-real) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/real) + +set(SOURCE_FILES + ${SRC}/piuio.c) + +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") +# Remove library name "lib" prefix +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") + +target_link_libraries(${PROJECT_NAME} io-piuio io-util util usb-1.0) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/test/CMakeLists.txt b/cmake/src/main/ptapi/piuio/test/CMakeLists.txt new file mode 100644 index 0000000..7baf69c --- /dev/null +++ b/cmake/src/main/ptapi/piuio/test/CMakeLists.txt @@ -0,0 +1,11 @@ +project(ptapi-io-piuio-test) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/test) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} capnhook-hook ptapi-io-piuio-util util) \ No newline at end of file diff --git a/cmake/src/main/ptapi/piuio/util/CMakeLists.txt b/cmake/src/main/ptapi/piuio/util/CMakeLists.txt new file mode 100644 index 0000000..e7cb6a4 --- /dev/null +++ b/cmake/src/main/ptapi/piuio/util/CMakeLists.txt @@ -0,0 +1,13 @@ +project(ptapi-io-piuio-util) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/ptapi/io/piuio/util) + +set(SOURCE_FILES + ${SRC}/lib.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} capnhook-hook dl util) \ No newline at end of file diff --git a/cmake/src/main/pumpnet/CMakeLists.txt b/cmake/src/main/pumpnet/CMakeLists.txt new file mode 100644 index 0000000..54a82bc --- /dev/null +++ b/cmake/src/main/pumpnet/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(client) +add_subdirectory(lib) \ No newline at end of file diff --git a/cmake/src/main/pumpnet/client/CMakeLists.txt b/cmake/src/main/pumpnet/client/CMakeLists.txt new file mode 100644 index 0000000..0c0f36a --- /dev/null +++ b/cmake/src/main/pumpnet/client/CMakeLists.txt @@ -0,0 +1,11 @@ +project(pumpnet-client) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/pumpnet/client) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} pumpnet-lib -lcurl) \ No newline at end of file diff --git a/cmake/src/main/pumpnet/lib/CMakeLists.txt b/cmake/src/main/pumpnet/lib/CMakeLists.txt new file mode 100644 index 0000000..d376102 --- /dev/null +++ b/cmake/src/main/pumpnet/lib/CMakeLists.txt @@ -0,0 +1,15 @@ +project(pumpnet-lib) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/pumpnet/lib) + +set(SOURCE_FILES + ${SRC}/http.c + ${SRC}/profile-token.c + ${SRC}/pumpnet.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} asset-nx2 util) diff --git a/cmake/src/main/sec/CMakeLists.txt b/cmake/src/main/sec/CMakeLists.txt new file mode 100644 index 0000000..d487339 --- /dev/null +++ b/cmake/src/main/sec/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(hasp) +add_subdirectory(lockchip) +add_subdirectory(microdog34) +add_subdirectory(microdog40) \ No newline at end of file diff --git a/cmake/src/main/sec/hasp/CMakeLists.txt b/cmake/src/main/sec/hasp/CMakeLists.txt new file mode 100644 index 0000000..0bd40f8 --- /dev/null +++ b/cmake/src/main/sec/hasp/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(lib) +add_subdirectory(old) +add_subdirectory(server) \ No newline at end of file diff --git a/cmake/src/main/sec/hasp/lib/CMakeLists.txt b/cmake/src/main/sec/hasp/lib/CMakeLists.txt new file mode 100644 index 0000000..72a299a --- /dev/null +++ b/cmake/src/main/sec/hasp/lib/CMakeLists.txt @@ -0,0 +1,17 @@ +project(hasp) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/sec/hasp/lib) + +set(SOURCE_FILES + ${SRC}/object/client-id-req.c + ${SRC}/object/client-id-resp.c + ${SRC}/asn1.c + ${SRC}/handler.c + ${SRC}/server.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} crypt) diff --git a/cmake/src/main/sec/hasp/old/CMakeLists.txt b/cmake/src/main/sec/hasp/old/CMakeLists.txt new file mode 100644 index 0000000..ef17dcc --- /dev/null +++ b/cmake/src/main/sec/hasp/old/CMakeLists.txt @@ -0,0 +1,13 @@ +project(hasp-old) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/sec/hasp/old) + +set(SOURCE_FILES + ${SRC}/hasp.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} crypt) diff --git a/cmake/src/main/sec/hasp/server/CMakeLists.txt b/cmake/src/main/sec/hasp/server/CMakeLists.txt new file mode 100644 index 0000000..b4367e2 --- /dev/null +++ b/cmake/src/main/sec/hasp/server/CMakeLists.txt @@ -0,0 +1,11 @@ +project(hasp-server) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/sec/hasp/server) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} hasp util) \ No newline at end of file diff --git a/cmake/src/main/sec/lockchip/CMakeLists.txt b/cmake/src/main/sec/lockchip/CMakeLists.txt new file mode 100644 index 0000000..9376e41 --- /dev/null +++ b/cmake/src/main/sec/lockchip/CMakeLists.txt @@ -0,0 +1,13 @@ +project(lockchip) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/sec/lockchip) + +set(SOURCE_FILES + ${SRC}/lockchip.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} crypt) diff --git a/cmake/src/main/sec/microdog34/CMakeLists.txt b/cmake/src/main/sec/microdog34/CMakeLists.txt new file mode 100644 index 0000000..6355141 --- /dev/null +++ b/cmake/src/main/sec/microdog34/CMakeLists.txt @@ -0,0 +1,13 @@ +project(microdog34) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/sec/microdog34) + +set(SOURCE_FILES + ${SRC}/microdog34.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} crypt) diff --git a/cmake/src/main/sec/microdog40/CMakeLists.txt b/cmake/src/main/sec/microdog40/CMakeLists.txt new file mode 100644 index 0000000..6813f2a --- /dev/null +++ b/cmake/src/main/sec/microdog40/CMakeLists.txt @@ -0,0 +1,14 @@ +project(microdog40) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/sec/microdog40) + +set(SOURCE_FILES + ${SRC}/microdog40.c + ${SRC}/microdog40d.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} crypt) diff --git a/cmake/src/main/util/CMakeLists.txt b/cmake/src/main/util/CMakeLists.txt new file mode 100644 index 0000000..2219ccc --- /dev/null +++ b/cmake/src/main/util/CMakeLists.txt @@ -0,0 +1,28 @@ +project(util) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_MAIN}/util) + +set(SOURCE_FILES + ${SRC}/adler32.c + ${SRC}/array.c + ${SRC}/base64.c + ${SRC}/fs.c + ${SRC}/glibc.c + ${SRC}/hex.c + ${SRC}/iobuf.c + ${SRC}/list.c + ${SRC}/log.c + ${SRC}/mem.c + ${SRC}/net.c + ${SRC}/options.c + ${SRC}/patch.c + ${SRC}/proc.c + ${SRC}/rand.c + ${SRC}/sock-tcp.c + ${SRC}/str.c + ${SRC}/sys-info.c + ${SRC}/test.c + ${SRC}/time.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) diff --git a/cmake/src/test/CMakeLists.txt b/cmake/src/test/CMakeLists.txt new file mode 100644 index 0000000..a462985 --- /dev/null +++ b/cmake/src/test/CMakeLists.txt @@ -0,0 +1,9 @@ +include_directories(${PT_ROOT_IMPORTS}) +include_directories(${PT_ROOT_PTAPI}) +include_directories(${PT_ROOT_MAIN}) +include_directories(${PT_ROOT_TEST}) + +add_subdirectory(capnhook) +add_subdirectory(hook) +add_subdirectory(test-util) +add_subdirectory(util) \ No newline at end of file diff --git a/cmake/src/test/capnhook/CMakeLists.txt b/cmake/src/test/capnhook/CMakeLists.txt new file mode 100644 index 0000000..30d014b --- /dev/null +++ b/cmake/src/test/capnhook/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(hook) \ No newline at end of file diff --git a/cmake/src/test/capnhook/hook/CMakeLists.txt b/cmake/src/test/capnhook/hook/CMakeLists.txt new file mode 100644 index 0000000..8570eeb --- /dev/null +++ b/cmake/src/test/capnhook/hook/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(lib) \ No newline at end of file diff --git a/cmake/src/test/capnhook/hook/lib/CMakeLists.txt b/cmake/src/test/capnhook/hook/lib/CMakeLists.txt new file mode 100644 index 0000000..f9aa7fb --- /dev/null +++ b/cmake/src/test/capnhook/hook/lib/CMakeLists.txt @@ -0,0 +1,11 @@ +project(test-capnhook-hook-lib) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_TEST}/capnhook/hook/lib) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} capnhook-hooklib cmocka util dl) diff --git a/cmake/src/test/hook/CMakeLists.txt b/cmake/src/test/hook/CMakeLists.txt new file mode 100644 index 0000000..694f307 --- /dev/null +++ b/cmake/src/test/hook/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(propatch) \ No newline at end of file diff --git a/cmake/src/test/hook/propatch/CMakeLists.txt b/cmake/src/test/hook/propatch/CMakeLists.txt new file mode 100644 index 0000000..90b3f52 --- /dev/null +++ b/cmake/src/test/hook/propatch/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(usb-fix) \ No newline at end of file diff --git a/cmake/src/test/hook/propatch/usb-fix/CMakeLists.txt b/cmake/src/test/hook/propatch/usb-fix/CMakeLists.txt new file mode 100644 index 0000000..1ecf120 --- /dev/null +++ b/cmake/src/test/hook/propatch/usb-fix/CMakeLists.txt @@ -0,0 +1,11 @@ +project(test-hook-propatch-usb-fix) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_TEST}/hook/propatch/usb-fix) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} cmocka propatch test-util util) diff --git a/cmake/src/test/test-util/CMakeLists.txt b/cmake/src/test/test-util/CMakeLists.txt new file mode 100644 index 0000000..99a2bc5 --- /dev/null +++ b/cmake/src/test/test-util/CMakeLists.txt @@ -0,0 +1,13 @@ +project(test-util) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_TEST}/test-util) + +set(SOURCE_FILES + ${SRC}/mem.c) + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC") + +target_link_libraries(${PROJECT_NAME} cmocka util) diff --git a/cmake/src/test/util/CMakeLists.txt b/cmake/src/test/util/CMakeLists.txt new file mode 100644 index 0000000..2466eca --- /dev/null +++ b/cmake/src/test/util/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(mem) +add_subdirectory(str) \ No newline at end of file diff --git a/cmake/src/test/util/mem/CMakeLists.txt b/cmake/src/test/util/mem/CMakeLists.txt new file mode 100644 index 0000000..44cc750 --- /dev/null +++ b/cmake/src/test/util/mem/CMakeLists.txt @@ -0,0 +1,11 @@ +project(test-util-mem) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_TEST}/util/mem) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} cmocka test-util util) diff --git a/cmake/src/test/util/str/CMakeLists.txt b/cmake/src/test/util/str/CMakeLists.txt new file mode 100644 index 0000000..74ca99d --- /dev/null +++ b/cmake/src/test/util/str/CMakeLists.txt @@ -0,0 +1,11 @@ +project(test-util-str) +message(STATUS "Project " ${PROJECT_NAME}) + +set(SRC ${PT_ROOT_TEST}/util/str) + +set(SOURCE_FILES + ${SRC}/main.c) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) + +target_link_libraries(${PROJECT_NAME} cmocka test-util util) diff --git a/dist/api/ptapi-io-piubtn-stub.c b/dist/api/ptapi-io-piubtn-stub.c new file mode 100644 index 0000000..fc05de7 --- /dev/null +++ b/dist/api/ptapi-io-piubtn-stub.c @@ -0,0 +1,62 @@ +/** + * Example implementation of the piubtn API. This doesn't really implement anything but rather provides a stub file that + * helps anyone to get started with API development/prototyping. + * + * A simple setup for a quick start: + * 1. Ensure that the API folders with the headers are located next to this file, specifically ptapi/io/piubtn.h. + * 2. Compile using: gcc -I. -m32 -shared -fPIC -o ptapi-io-piubtn-stub.so ptapi-io-piubtn-stub.c + * 3. Follow the steps in the readme to configure it for the game of your choice. + */ +#include +#include + +#include "ptapi/io/piubtn.h" + +const char* ptapi_io_piubtn_ident(void) +{ + return "stub"; +} + +bool ptapi_io_piubtn_open(void) +{ + printf("piubtn-stub: ptapi_io_piubtn_open\n"); + + return true; +} + + +void ptapi_io_piubtn_close(void) +{ + printf("piubtn-stub: ptapi_io_piubtn_close\n"); +} + +bool ptapi_io_piubtn_recv(void) +{ + return true; +} + +bool ptapi_io_piubtn_send(void) +{ + return true; +} + +void ptapi_io_piubtn_get_input(uint8_t player, struct ptapi_io_piubtn_inputs* inputs) +{ + // Set all inputs to off + if (player == 0) { + inputs->left = false; + inputs->right = false; + inputs->start = false; + inputs->back = false; + } else { + inputs->left = false; + inputs->right = false; + inputs->start = false; + inputs->back = false; + } +} + +void ptapi_io_piubtn_set_output(uint8_t player, const struct ptapi_io_piubtn_outputs* outputs) +{ + +} \ No newline at end of file diff --git a/dist/api/ptapi-io-piuio-stub.c b/dist/api/ptapi-io-piuio-stub.c new file mode 100644 index 0000000..b25e381 --- /dev/null +++ b/dist/api/ptapi-io-piuio-stub.c @@ -0,0 +1,79 @@ +/** + * Example implementation of the piuio API. This doesn't really implement anything but rather provides a stub file that + * helps anyone to get started with API development/prototyping. + * + * A simple setup for a quick start: + * 1. Ensure that the API folders with the headers are located next to this file, specifically ptapi/io/piuio.h. + * 2. Compile using: gcc -I. -m32 -shared -fPIC -o ptapi-io-piuio-stub.so ptapi-io-piuio-stub.c + * 3. Follow the steps in the readme to configure it for the game of your choice. + */ +#include +#include + +#include "ptapi/io/piuio.h" + +const char* ptapi_io_piuio_ident(void) +{ + return "stub"; +} + +bool ptapi_io_piuio_open(void) +{ + printf("piuio-stub: ptapi_io_piuio_open\n"); + + return true; +} + +void ptapi_io_piuio_close(void) +{ + printf("piuio-stub: ptapi_io_piuio_close\n"); +} + +bool ptapi_io_piuio_recv(void) +{ + return true; +} + +bool ptapi_io_piuio_send(void) +{ + return true; +} + +void ptapi_io_piuio_get_input_pad(uint8_t player, enum ptapi_io_piuio_sensor_group sensor_group, + struct ptapi_io_piuio_pad_inputs* inputs) +{ + // Set all inputs to off + if (player == 0) { + inputs->lu = false; + inputs->ru = false; + inputs->cn = false; + inputs->ld = false; + inputs->rd = false; + } else { + inputs->lu = false; + inputs->ru = false; + inputs->cn = false; + inputs->ld = false; + inputs->rd = false; + } +} + +void ptapi_io_piuio_get_input_sys(struct ptapi_io_piuio_sys_inputs* inputs) +{ + // Set all inputs to off + inputs->test = false; + inputs->service = false; + inputs->clear = false; + inputs->coin = false; + inputs->coin2 = false; +} + +void ptapi_io_piuio_set_output_pad(uint8_t player, const struct ptapi_io_piuio_pad_outputs* outputs) +{ + +} + +void ptapi_io_piuio_set_output_cab(const struct ptapi_io_piuio_cab_outputs* outputs) +{ + +} \ No newline at end of file diff --git a/dist/conf/exchook.conf b/dist/conf/exchook.conf new file mode 100644 index 0000000..f1b55b9 --- /dev/null +++ b/dist/conf/exchook.conf @@ -0,0 +1,47 @@ +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [str]: Version of the game (format YYYYMMDD). Make sure this matches exactly the version you are running because the hook lib has to apply memory patches. +game.version=20040408 + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=dmix + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/f2hook.conf b/dist/conf/f2hook.conf new file mode 100644 index 0000000..1007f99 --- /dev/null +++ b/dist/conf/f2hook.conf @@ -0,0 +1,50 @@ +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Address and port of pumpnet server (e.g. 127.0.0.1:1234) +patch.net_profile.server= + +# [int]: Machine id for pumpnet +patch.net_profile.machine_id=0 + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=dmix + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/fexhook.conf b/dist/conf/fexhook.conf new file mode 100644 index 0000000..1007f99 --- /dev/null +++ b/dist/conf/fexhook.conf @@ -0,0 +1,50 @@ +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Address and port of pumpnet server (e.g. 127.0.0.1:1234) +patch.net_profile.server= + +# [int]: Machine id for pumpnet +patch.net_profile.machine_id=0 + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=dmix + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/fsthook.conf b/dist/conf/fsthook.conf new file mode 100644 index 0000000..1007f99 --- /dev/null +++ b/dist/conf/fsthook.conf @@ -0,0 +1,50 @@ +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Address and port of pumpnet server (e.g. 127.0.0.1:1234) +patch.net_profile.server= + +# [int]: Machine id for pumpnet +patch.net_profile.machine_id=0 + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=dmix + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/mk3hook.conf b/dist/conf/mk3hook.conf new file mode 100644 index 0000000..c65ec22 --- /dev/null +++ b/dist/conf/mk3hook.conf @@ -0,0 +1,62 @@ +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [int]: Sync offset in ms +game.sync_offset=115 + +# [double]: Sync multiplier +game.sync_multiplier=4.166667 + +# [double]: Volume of music playback +game.music_vol=1.000000 + +# [double]: Volume of sound effects playback +game.sfx_vol=1.000000 + +# [bool (0/1)]: Enable this option when running 1st or 2nd. This uses different file system redirects for the game directory as well as enables a fix. Without this, the game throws errors about not finding files +game.1st_2nd_fs=0 + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device= + +# [bool (0/1)]: Enable debug output for the fmod library. This requires you to use the fmodexL.so instead of the standard fmodex.so +patch.sound.debug_output=0 + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/nx2hook.conf b/dist/conf/nx2hook.conf new file mode 100644 index 0000000..2c23960 --- /dev/null +++ b/dist/conf/nx2hook.conf @@ -0,0 +1,56 @@ +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Address and port of pumpnet server (e.g. 127.0.0.1:1234) +patch.net_profile.server= + +# [int]: Machine id for pumpnet +patch.net_profile.machine_id=0 + +# [bool (0/1/)]: Enable verbose log output for pumpnet related things, e.g. logging network traffic +patch.net_profile.verbose_log_output=0 + +# [str]: Path to a folder containing the client key, certificate and CA bundle to enable https communication +patch.net_profile.cert_dir_path= + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=dmix + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/nxahook.conf b/dist/conf/nxahook.conf new file mode 100644 index 0000000..1007f99 --- /dev/null +++ b/dist/conf/nxahook.conf @@ -0,0 +1,50 @@ +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Address and port of pumpnet server (e.g. 127.0.0.1:1234) +patch.net_profile.server= + +# [int]: Machine id for pumpnet +patch.net_profile.machine_id=0 + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=dmix + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/nxhook.conf b/dist/conf/nxhook.conf new file mode 100644 index 0000000..2bf1dc3 --- /dev/null +++ b/dist/conf/nxhook.conf @@ -0,0 +1,47 @@ +# [bool (0/1)]: Force unlock the game. Modifies the current and any new PIUNX.INI settings file permanently +game.force_unlock=0 + +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=dmix + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/prihook.conf b/dist/conf/prihook.conf new file mode 100644 index 0000000..dcb224d --- /dev/null +++ b/dist/conf/prihook.conf @@ -0,0 +1,47 @@ +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (PRI) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Address and port of pumpnet server (e.g. 127.0.0.1:1234) +patch.net.server= + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=hw:0 + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/pro2hook.conf b/dist/conf/pro2hook.conf new file mode 100644 index 0000000..7242f84 --- /dev/null +++ b/dist/conf/pro2hook.conf @@ -0,0 +1,38 @@ +# [str]: Path to game data (game) folder +game.data=./game + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [str]: Path to library implementing the piubtn api for piubtn emulation +patch.piubtn.emu_lib= + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_x11_event_loop.input_handler_lib= + +# [str]: Path to a second library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_x11_event_loop.input_handler_lib2= + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/prohook.conf b/dist/conf/prohook.conf new file mode 100644 index 0000000..17b3602 --- /dev/null +++ b/dist/conf/prohook.conf @@ -0,0 +1,50 @@ +# [str]: Path to game data (game) folder +game.data=./game + +# [str]: Path to game settings (data) folder +game.settings=./save + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [str]: Path to library implementing the piubtn api for piubtn emulation +patch.piubtn.emu_lib= + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Bus and port (format: X-X, e.g. 1-2) of the USB slot to assign to Player 1 when a USB thumb drive is plugged in +patch.usb_profile.p1.bus_port= + +# [str]: Bus and port (format: X-X, e.g. 1-2) of the USB slot to assign to Player 2 when a USB thumb drive is plugged in +patch.usb_profile.p2.bus_port= + +# [str]: Device nodes of plugged in USB thumb drives to consider for mounting for usb profiles, format: sdX,sdY e.g. sde,sdf +patch.usb_profile.dev_nodes= + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_x11_event_loop.input_handler_lib= + +# [str]: Path to a second library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_x11_event_loop.input_handler_lib2= + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/x2hook.conf b/dist/conf/x2hook.conf new file mode 100644 index 0000000..65e6777 --- /dev/null +++ b/dist/conf/x2hook.conf @@ -0,0 +1,47 @@ +# [bool (0/1)]: Force unlock the game. Modifies the current and any new PIUEXCEED.INI settings file permanently +game.force_unlock=0 + +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=dmix + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/conf/zerohook.conf b/dist/conf/zerohook.conf new file mode 100644 index 0000000..8bd93de --- /dev/null +++ b/dist/conf/zerohook.conf @@ -0,0 +1,47 @@ +# [bool (0/1)]: Force unlock the game. Modifies the current and any new PIUZERO.INI settings file permanently +game.force_unlock=0 + +# [str]: Path to game settings (SETTINGS) folder +game.settings=./save + +# [bool (0/1)]: Force game into window mode +patch.gfx.windowed=0 + +# [bool (0/1)]: Enable file call monitoring +patch.hook_mon.file=0 + +# [bool (0/1)]: Enable file system related call monitoring +patch.hook_mon.fs=0 + +# [bool (0/1)]: Enable IO syscall monitoring +patch.hook_mon.io=0 + +# [bool (0/1)]: Enable any file/IO open call monitoring +patch.hook_mon.open=0 + +# [bool (0/1)]: Enable libusb call monitoring +patch.hook_mon.usb=0 + +# [bool (0/1)]: Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) +patch.hook_main_loop.disable_built_in_inputs=0 + +# [str]: Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs +patch_hook_main_loop.x11_input_handler= + +# [str]: Path to library implementing the piuio api for piuio emulation +patch.piuio.emu_lib= + +# [bool (0/1)]: Enable game exit on Test + Service +patch.piuio_exit.test_serv=0 + +# [str]: Select the sound device to open on snd_pcm_open +patch.sound.device=dmix + +# [bool (0/1)]: Halt on sigsegv to attach a debugger to the process +patch.sigsegv.halt_on_segv=0 + +# [str]: Print the log output to the specified file +util.log.file=pumptools.log + +# [int]: Set the log level (0-4) +util.log.level=4 diff --git a/dist/piueb b/dist/piueb new file mode 100755 index 0000000..1b1bde1 --- /dev/null +++ b/dist/piueb @@ -0,0 +1,407 @@ +#!/usr/bin/env bash + +### +# Pump It Up Environment Bootstrap (piueb) +# +# This script sets up an environment to run all (MK5/6) Linux based PIU games as well as the MK3 linux ports. +# It takes care of the following tasks: +# - Check if all necessary files and directories are available +# - Verify that local dependencies for the game are used +# - Force the game into VSYNC +# - Bootstrap the application executable with: +# - A a subset of local libs or even a private ld-linux to ensure highest compatibility accross distros +# - A pumptools hook.so +# +# Therefore, this script expects the following folder structure and naming (!) to run the game: +# - game: The game folder with vanilla assets +# - lib: All (additional) dependencies the game needs to run +# - (save): Depending on your hook configuration, this can be located somewhere else, too +# - hook.conf: Configuration file with hook specific settings +# - hook.so: The game specific hook library compiled from pumptools +# - piu: The game's executable +# - piueb: This script +# +# To get usage/help output from piueb: +# ./piueb help +# +# To run the game, simply run this script with the "run" command: +# ./piueb run +# +# Additional arguments to hook.so are also supported: +# ./piueb run arg1 arg2 +# +# For example, to get help/usage output from the hook library: +# ./piueb run -h +# +# For debugging purpose, a log file (piueb.log) is written to the folder this script is in. +### + +readonly VERSION="1.0" + +readonly LOG_LEVEL_ERROR="E" +readonly LOG_LEVEL_WARN="W" +readonly LOG_LEVEL_INFO="I" +readonly LOG_LEVEL_DEBUG="M" + +################################################################################## + +SELF_DIR="$(dirname "$(readlink -f "$0")")" + +LOG_FILE="$SELF_DIR/piueb.log" + +GAME_EXEC="$SELF_DIR/piu" + +GAME_DIR="$SELF_DIR/game" +LIBS_DIR="$SELF_DIR/lib" + +HOOK_FILE="$SELF_DIR/hook.so" +HOOK_CONFIG_FILE="$SELF_DIR/hook.conf" + +LIBS_LINKER="$SELF_DIR/lib/ld-linux.so.2" + +################################################################################## + +log_init() +{ + rm -f "$LOG_FILE" +} + +log() +{ + local level="$1" + local msg="$2" + + local timestamp="$(date "+%Y/%m/%d-%H:%M:%S:%3N")" + + local color="" + + case $level in + $LOG_LEVEL_ERROR) + color="\e[1m\e[31m" + ;; + $LOG_LEVEL_WARN) + color="\e[33m" + ;; + $LOG_LEVEL_INFO) + color="\e[34m" + ;; + $LOG_LEVEL_DEBUG) + color="\e[37m" + ;; + esac + + echo -e "${color}[$level]\e[0m[$timestamp] ${msg}" + echo "[$level][$timestamp] ${msg}" >> "$LOG_FILE" +} + +log_error() +{ + log "$LOG_LEVEL_ERROR" "$@" +} + +log_warn() +{ + log "$LOG_LEVEL_WARN" "$@" +} + +log_info() +{ + log "$LOG_LEVEL_INFO" "$@" +} + +log_debug() +{ + log "$LOG_LEVEL_DEBUG" "$@" +} + +verify_running_as_root() +{ + log_debug "Verify running as root user/with sudo..." + + if [ "$EUID" -ne 0 ]; then + log_error "Running as non-root user, game likely to crash!" + fi +} + +verify_file_exists() +{ + local file_path="$1" + local warn_only="$2" + + log_debug "Verify file/dir exists $file_path..." + + if [ ! -e "$file_path" ]; then + if [ "$warn_only" ]; then + log_warn "Missing file \"$file_path\"" + else + log_error "Missing required file \"$file_path\"" + exit 1 + fi + fi +} + +verify_dir_not_empty() +{ + local dir_path="$1" + + if [ -z "$(ls -A $dir_path)" ]; then + log_error "Directory empty: $dir_path" + exit 1 + fi +} + +verify_deps_all_local() +{ + local exec_path="$1" + local lib_path="$2" + + local ldd_output="$(ldd $exec_path >&1)" + + log_debug "Verify dependencies all local..." + + log_debug "ldd output for $exec_path:\\n$ldd_output" + + while read -r line; do + # Skip any additional output/error messages that are not part of the library listing + if [[ "$line:0:1" != "\t" ]]; then + continue + fi + + local libname="$(echo "$line" | cut -d '.' -f 1)" + local libpath="$(echo "$line" | cut -d ' ' -f 3)" + + # Skip a bunch of libs that are kernel specific like bound to GPU drivers + case $libname in + "linux-gate") ;& # fallthrough + "libGL") ;& # fallthrough + "libGLX") ;& # fallthrough + "libGLdispatch") ;& # fallthrough + # That will still point to the current machine's linker + # but we will use our own further down to bootstrap the elf + "ld-linux") ;& # fallthrough + "/lib/ld-linux") + log_debug "Skipping $libname" + continue + ;; + *) + ;; + esac + + log_debug "Checking $libname: $libpath..." + + if [[ $libpath != $LIBS_DIR* ]]; then + log_warn "$libname is not local to the game, path $libpath" + fi + done <<< "$ldd_output" +} + +verify_game_executable() +{ + local exec_path="$1" + + log_debug "Verifying that game is executable: $exec_path..." + + if [ ! -x "$exec_path" ]; then + log_warn "Game exec $exec_path not executable, fixing..." + chmod +x "$exec_path" + fi +} + +print_usage() +{ + echo "Pump It Up Environment Bootstrap (piueb), version $VERSION" + echo "Easily bootstrap linux based Pump games using pumptools." + echo "Usage: piueb ..." + echo "Commands:" + echo " run - Run the game located next to this script" + echo " strace - Run the game with local libraries and strace attached for debugging" + echo " debug - Run the game with local libraries and a GDB debugger server attached for debugging" + echo " help - Print this usage message" +} + +# Supports native mk6 linux games as well as the mk3 linux ports using mk3hook +cmd_run() +{ + local mode="$1" + + log_init + + log_info "Pump It Up Environment Bootstrap (piueb), version $VERSION" + + verify_running_as_root + + log_info "Current path: $SELF_DIR" + + log_debug "File and path verification..." + + # Some file and folder verfication + + verify_file_exists "$GAME_EXEC" + + verify_file_exists "$GAME_DIR" + verify_dir_not_empty "$GAME_DIR" + verify_file_exists "$LIBS_DIR" + verify_dir_not_empty "$LIBS_DIR" + + verify_file_exists "$HOOK_FILE" + # Only warn to allow creating of default hook.conf if no file exists + verify_file_exists "$HOOK_CONFIG_FILE" 1 + + # Support two modes of setting up the runtime environment with the game's + # dependencies: + # 1. Using a separate linker application which might be necessary when having + # to run the application with different libc (and/or other library) versions. + # This avoids incompatibilities with newer libc runtime and ld-linux versions + # (see f2-crashing-on-modern-linux-post-mortem.md). + # + # 2. Use only a subset of local libraries and take everything else from the + # current system. Your typical LD_LIBRARY_PATH method. + local lib_mode="" + + if [ -e "$LIBS_LINKER" ]; then + lib_mode="ld" + else + lib_mode="local" + fi + + log_info "Resolving dependencies mode: $lib_mode" + + # LD_LIBRARY_PATH setup to use local libraries instead of system ones + + if [ -z "$LD_LIBRARY_PATH" ]; then + LD_LIBRARY_PATH="$LIBS_DIR" + else + LD_LIBRARY_PATH="$LIBS_DIR:$LD_LIBRARY_PATH" + fi + + log_debug "LD_LIBRARY_PATH: $LD_LIBRARY_PATH" + + # Required that ldd shows us the correct paths + export LD_LIBRARY_PATH + + # Check if all dependencies used are actually bundled with the game + verify_deps_all_local "$GAME_EXEC" "$LIBS_DIR" + + local LD_PRELOAD="$HOOK_FILE" + + local ldd_output_hook="$(ldd $HOOK_FILE >&1)" + + log_debug "ldd output for $HOOK_FILE:\\n$ldd_output_hook" + + if [ "${*:1}" ]; then + log_info "Additional cmd arguments provided: ${*:1}" + fi + + log_info "Executing game $GAME_EXEC, mode $mode, lib_mode $lib_mode...\n==============================================================================================================================" + + # Because people forget this when setting up the data manually + chmod +x "$GAME_EXEC" + + # Force the game to run to vsync blank interval for nvidia cards: __GL_SYNC_TO_VBLANK=1 + + if [ "$mode" = "normal" ] && [ "$lib_mode" = "ld" ]; then + # / after GAME_DIR required + # Use private linker to avoid platform incompatibility issues (see above) + exec \ + env __GL_SYNC_TO_VBLANK=1 \ + env LD_PRELOAD="$LD_PRELOAD" \ + "$LIBS_LINKER" \ + --library-path "$LD_LIBRARY_PATH" \ + "$GAME_EXEC" \ + "${GAME_DIR}/" \ + "--options" "$HOOK_CONFIG_FILE" \ + "${@:2}" + elif [ "$mode" = "normal" ] && [ "$lib_mode" = "local" ]; then + # / after GAME_DIR required + exec \ + env __GL_SYNC_TO_VBLANK=1 \ + env LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ + env LD_PRELOAD="$LD_PRELOAD" \ + "$GAME_EXEC" \ + "${GAME_DIR}/" \ + "--options" "$HOOK_CONFIG_FILE" \ + "${@:2}" + elif [ "$mode" = "debug" ] && [ "$lib_mode" = "ld" ]; then + log_error "debug mode with lib type ld not supported" + elif [ "$mode" = "debug" ] && [ "$lib_mode" = "local" ]; then + gdbserver \ + --wrapper \ + env \ + __GL_SYNC_TO_VBLANK=1 \ + LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ + LD_PRELOAD="$LD_PRELOAD" \ + -- 0.0.0.0:1234 \ + "$GAME_EXEC" \ + "${GAME_DIR}/" \ + "--options" "$HOOK_CONFIG_FILE" \ + "${@:2}" + elif [ "$mode" = "strace" ] && [ "$lib_mode" = "ld" ]; then + log_error "strace mode with lib type ld not supported" + elif [ "$mode" = "strace" ] && [ "$lib_mode" = "local" ]; then + strace \ + -E __GL_SYNC_TO_VBLANK=1 \ + -E LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ + -E LD_PRELOAD="$LD_PRELOAD" \ + "$GAME_EXEC" \ + "${GAME_DIR}/" \ + "--options" "$HOOK_CONFIG_FILE" \ + "${@:2}" + elif [ "$mode" = "valgrind" ] && [ "$lib_mode" = "ld" ]; then + log_error "valgrind mode with lib type ld not supported" + elif [ "$mode" = "valgrind" ] && [ "$lib_mode" = "local" ]; then + valgrind \ + --trace-children=yes \ + env \ + __GL_SYNC_TO_VBLANK=1 \ + LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ + LD_PRELOAD="$LD_PRELOAD" \ + "$GAME_EXEC" \ + "${GAME_DIR}/" \ + "--options" "$HOOK_CONFIG_FILE" \ + "${@:2}" + else + log_error "Unsupported mode ($mode) or lib_mode (($lib_mode)" + fi +} + +cmd_help() +{ + print_usage +} + +################################################################################## + +CMD="$1" + +case $CMD in + "") + echo "Insufficient arguments." + print_usage + exit 1 + ;; + "help") + cmd_help + exit 0 + ;; + "run") + cmd_run "normal" "${@:2}" + exit 0 + ;; + "debug") + cmd_run "debug" "${@:2}" + exit 0 + ;; + "strace") + cmd_run "strace" "${@:2}" + exit 0 + ;; + "valgrind") + cmd_run "valgrind" "${@:2}" + exit 0 + ;; + *) + echo "Invalid command." + print_usage + exit 1 + ;; +esac \ No newline at end of file diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..a52e1ac --- /dev/null +++ b/doc/README.md @@ -0,0 +1,10 @@ +# Pumptools documentation +This folder contains all documentation for pumptools in markdown format. + +Index: +* [api](api/api.md): Various documentation about the API, using and configuring the provided API implementations, e.g. +PIUIO, PIUBTN +* [development](development/development.md): Get started with developing for pumptools, development setup, architecture +and notes about various efforts for future reference. +* [game](game/game.md): Information about the games including trivia, pad codes, unlocks etc. +* [hook](hook/hook.md): How to setup and configure pumptools with the games supported. \ No newline at end of file diff --git a/doc/api/api.md b/doc/api/api.md new file mode 100644 index 0000000..ba798dd --- /dev/null +++ b/doc/api/api.md @@ -0,0 +1,8 @@ +# Pumptools API +The *api* sub-folder contains various headers that allow you to develop your own libraries to hook into various places +inside pumptools. + +## IO +* [PIUBTN](io/piubtn.md): The button board introduced with Pump It Up Pro +* [PIUIO](io/piuio.md): Also known as the MK6 IO, the main IO board for pads and lights +* [X11 input hook](io/x11-input-hook.md): Hook input processing to X11 to receive keyboard events \ No newline at end of file diff --git a/doc/api/io/piubtn.md b/doc/api/io/piubtn.md new file mode 100644 index 0000000..b88e24c --- /dev/null +++ b/doc/api/io/piubtn.md @@ -0,0 +1,58 @@ +# PIUBTN API +This contains various PIUBTN related tools and libraries. PIUBTN is the button board that was first introduced with +Pump PRO and officially works as an optional peripheral on all classic pump games starting Fiesta. + +## Pumptools PIUBTN API implementations +The following libraries implement pumptool's PIUBTN API interface. Set the configuration value `patch.piubtn.emu_lib` +in your hook configuration to point to the library you want to use, e.g. +`patch.piubtn.emu_lib=./ptapi-io-piubtn-null.so`. + +### Null: ptapi-io-piubtn-null.so +A null implementation for API dummy testing against libraries/applications calling API implementations. + +### Real PIUIO: ptapi-io-piubtn-real.so +Support for a real USB PIUBTN. This library is not useful for games that already support the hardware natively. +However, games that require an emulation layer need this. + +### Joystick/Gamepad: ptapi-io-piubtn-joystick.so +Support all (USB) Joysticks and Gamepads that are detected by the Linux kernel. This uses the Kernel's joystick API. + +Button mappings are configured by using the `ptapi-io-piubtn-joystick-conf` (this is an executable!) tool. You might +have to `chmod +x ptapi-io-piubtn-joystick-conf` it prior being able to run it: `./ptapi-io-piubtn-joystick-conf`. Run +the tool and follow the instructions. Once successfully completed, a `piubtn-joystick-conf.bin` configuration file is +located in the same folder. This must be located next to the `ptapi-io-piubtn-joystick.so` file which should be located +next to your `piu` executable. + +Configure your `hook.conf` file accordingly: +``` +patch.piubtn.emu_lib=./ptapi-io-piubtn-joystick.so +``` + +### Keyboard: ptapi-io-piubtn-keyboard.so +Support for inputs via your standard keyboard using the X11 API. + +Button mappings are configured by using the `ptapi-io-piubtn-keyboard-conf` (this is an executable!) tool. You might +have to `chmod +x ptapi-io-piubtn-keyboard-conf` it prior being able to run it: `./ptapi-io-piubtn-keyboard-conf`. Run +the tool and follow the instructions. Once successfully completed, a `piubtn-keyboard-conf.bin` configuration file is +located in the same folder. This must be located next to the `ptapi-io-piubtn-keyboard.so` file which should be located +next to your `piu` executable. + +Configure your `hook.conf` file accordingly: +``` +patch.piubtn.emu_lib=./ptapi-io-piubtn-keyboard.so +patch_hook_main_loop.x11_input_handler=./ptapi-io-piubtn-keyboard.so +``` + +Setting the key `patch_hook_main_loop.x11_input_handler` is important. Otherwise, the library does not receive any +input events from X11 and your configured keyboard input does not work in the game. + +## Pumptools PIUBTN API tester: ptapi-io-piubtn-test +The tool *ptapi-io-piubtn-test* lets you easily test and debug your library implementing pumptool's piubtn API without +having to setup and/or run any games. + +## Development +The [piubtn header](../../src/api/ptapi/io/piubtn.h) contains all the prototypes to be implemented by your library to +provide your own implementation for a PIUBTN. Checkout the header file for documentation. + +Checkout the [stub implementation](../../dist/api/ptapi-io-piubtn-stub.c) to get started with a simple example. +Instructions are located in the header comment of the file. \ No newline at end of file diff --git a/doc/api/io/piuio.md b/doc/api/io/piuio.md new file mode 100644 index 0000000..d74bd7b --- /dev/null +++ b/doc/api/io/piuio.md @@ -0,0 +1,57 @@ +# PIUIO API +This contains various MK6 USB PIUIO related tools and libraries. The hardware was first introduced with Exceed 2 and +is supported by all games up to this day. + +## Pumptools PIUIO API implementations +The following libraries implement pumptool's PIUIO API interface. Set the configuration value `patch.piuio.emu_lib` +in your hook configuration to point to the library you want to use, e.g. `patch.piuio.emu_lib=./ptapi-io-piuio-null.so`. + +### Null: ptapi-io-piuio-null.so +A null implementation for API dummy testing against libraries/applications calling API implementations. + +### Real PIUIO: ptapi-io-piuio-real.so +Support for a real USB PIUIO. This library is not useful for games that already support the hardware natively. +However, games that require an emulation layer need this to run on real PIUIO hardware, e.g. Exceed (1). + +### Joystick/Gamepad: ptapi-io-piuio-joystick.so +Support all (USB) Joysticks and Gamepads that are detected by the Linux kernel. This uses the Kernel's joystick API. + +Button mappings are configured by using the `ptapi-io-piuio-joystick-conf` (this is an executable!) tool. You might have +to `chmod +x ptapi-io-piuio-joystick-conf` it prior being able to run it: `./ptapi-io-piuio-joystick-conf`. Run the tool +and follow the instructions. Once successfully completed, a `piuio-joystick-conf.bin` configuration file is located in +the same folder. This must be located next to the `ptapi-io-piuio-joystick.so` file which should be located next to your +`piu` executable. + +Configure your `hook.conf` file accordingly: +``` +patch.piuio.emu_lib=./ptapi-io-piuio-joystick.so +``` + +### Keyboard: ptapi-io-piuio-keyboard.so +Support for inputs via your standard keyboard using the X11 API. + +Button mappings are configured by using the `ptapi-io-piuio-keyboard-conf` (this is an executable!) tool. You might have +to `chmod +x ptapi-io-piuio-keyboard-conf` it prior being able to run it: `./ptapi-io-piuio-keyboard-conf`. Run the tool +and follow the instructions. Once successfully completed, a `piuio-keyboard-conf.bin` configuration file is located in +the same folder. This must be located next to the `ptapi-io-piuio-keyboard.so` file which should be located next to your +`piu` executable. + +Configure your `hook.conf` file accordingly: +``` +patch.piuio.emu_lib=./ptapi-io-piuio-keyboard.so +patch_hook_main_loop.x11_input_handler=./ptapi-io-piuio-keyboard.so +``` + +Setting the key `patch_hook_main_loop.x11_input_handler` is important. Otherwise, the library does not receive any +input events from X11 and your configured keyboard input does not work in the game. + +## Pumptools PIUIO API tester: ptapi-io-piuio-test +The tool *ptapi-io-piuio-test* lets you easily test and debug your library implementing pumptool's piuio API without +having to setup and/or run any games. + +## Development +The [piuio header](../../src/api/ptapi/io/piuio.h) contains all the prototypes to be implemented by your library to +provide your own implementation for a PIUIO. Checkout the header file for documentation. + +Checkout the [stub implementation](../../dist/api/ptapi-io-piuio-stub.c) to get started with a simple example. +Instructions are located in the header comment of the file. \ No newline at end of file diff --git a/doc/api/io/x11-input-hook.md b/doc/api/io/x11-input-hook.md new file mode 100644 index 0000000..20f6a63 --- /dev/null +++ b/doc/api/io/x11-input-hook.md @@ -0,0 +1,9 @@ +# X11 input hook API +The [x11-input-hook header](../../src/api/ptapi/io/x11-input-hook.h) allows you to hook into the main loop of the game +to receive X11 keyboard inputs. This might be useful for implementations of other APIs, e.g. PIUIO, to trigger PIUIO +inputs when pressing keys on the keyboard. + +You have to configure your `hook.conf` accordingly that your implementation gets loaded by the hook library: +``` +patch_hook_main_loop.x11_input_handler=./ptapi-io-my-x11-input-hook.so +``` \ No newline at end of file diff --git a/doc/development/architecture.md b/doc/development/architecture.md new file mode 100644 index 0000000..46c9ba6 --- /dev/null +++ b/doc/development/architecture.md @@ -0,0 +1,43 @@ +# Architecture +The follow image shows a rough outline of pumptools's architecture based on the project's namespacing. Arrows indicate +architectural dependencies. Modules in red colored boxes expose APIs and blue colored boxes expose application/library +binaries. + +![](images/architecture.png) + +## util +The kitchen sink, basically used by everything. Not going to elaborate the details here as it is quite self-explanatory +when taking a look at its modules. + +## crypt +Kitchen sink for everything crypto related. Could be with `util` but I somewhat felt the need to have this separated. + +## asset +Modules for handling various types of asserts from pump games, e.g. usb profiles. + +## pumpnet +Modules to talk to pumpnet for usb profiles over sockets. Used by NX2 to Fiesta 2. + +## sec +Contains several sub-modules for security/dongle related topics such as full software emulation. + +## capnhook +This is the heart of pumptools to enable hooking of system and library calls. `capnhook` has two sub-modules, `hook` +and `hooklib`. `hook` provides the foundation for hooking various things, typically IO related, such as files, +filesystem, hardware IO (e.g. USB), signal handlers. `hooklib` provides helper modules based on `hook` that take care +of common tasks such as path based redirects, USB device emulation and IO call monitoring. + +## ptapi +Pumptools's API package exposing APIs to the end-user. Also contains any libraries and tools used by the APIs. + +## io +Contains modules to talk to real IO hardware, e.g. `piuio`, `piubtn`. + +## patch +`patch` is a common package for various modules that are shared with multiple pump versions. A patch module takes care +of patching, i.e. hooking and detouring usually, a single aspect like fixing a API quirk, enabling window mode etc. + +# game hooks +`game hooks` are composed of multiple `patch` modules making up the final hook library that you preload to the target +application. The structure of each `game hook` is identical and only the patches and hooks applied differ per game +version. diff --git a/doc/development/development.md b/doc/development/development.md new file mode 100644 index 0000000..7a31fbd --- /dev/null +++ b/doc/development/development.md @@ -0,0 +1,163 @@ +# Development +This document covers various aspects of developing for pumptools. For developing using the API, see +[the dedicated readme](../api/api.md). + +## Architecture +[This document](architecture.md) gives an introduction to the architecture of pumptools. This helps you to get familiar +with the codebase and its features, definitely recommended to read this first. + +## Development environment setup +Pumptools is written entirely in C and built using cmake. Various helper scripts are written as bash/shell scripts. +Therefore, you need the following tools on your development setup: +* gcc +* cmake +* make +* bash or compatible shell + +Pumptools requires the following dependencies to be installed on your development OS: +* libcurl4 i386 +* libusb 1.0 i386 + +Furthermore, header files, which come with the development packages of the libs, of the following libraries are +required: +* fmodex +* libconfig +* X11 +* libalsa +* libusb-0.1 (**not** 1.0) + +The project does not depend on any specific IDE. However, if you use CLion, the project can be imported easily. + +## Code style +The main style guidelines are defined by the `.clang-format` file and the code style can be easily applied to the +whole `src` directory using the following command from the root folder: +```shell script +make clang-format +``` + +Make sure that your contributions follow the style and apply it before submitting your changes. + +For other source files like shell scripts, cmake, makefile or markdown documents, please follow the already established +style in the existing documents. + +## Building +See the [main readme](../../README.md#building). + +## Compatibility issues with newer glibc versions +We have experienced various issues with different glibc versions which had a major impact on the builds. The following +table gives you a list of tested glibc versions and issues that are either present or not with each version listed. +"ok" indicates that the specified glibc version is compatible and the specific issue does not occur, "bad" indicates +incompatibility and that the specific issue does occur when using a binary compiled with that glibc version. + +| Issue/glibc version | 2.23 | 2.30 | +|---------------------|------|------| +| Issue 1 | ok | bad | +| Issue 2 | ok | bad | + +The issues are further described in the following sub-sections + +### Issue 1: Crash during static initialization +For a detailed report on this issue, see the [dedicated notes](notes/f2-crashing-on-modern-linux-post-mortem.md). + +Summarized: Fiesta 2 and newer will segfault before even running the application's main method. The application fails +on runtime initialization when initializing the static context. This is caused by ABI incompatibility of inlined code +of C++'s STL, first occurring with string allocations during static initialization. + +### Issue 2: sscanf not being detoured +With NX2, Andamiro locked the USB drives to be used to a specific brand which they sold back then. That lock is patched +out by detouring `sscanf` and manipulating the VID/PID and serial number parsing logic in the game to always return +the values, the game wants to see to accept the connected USB drive. + +However, some incompatibility on newer glibc versions does not setup the `sscanf` detour properly. The detour function +is there and gets called by various parts of the game code in the beginning, but later calls to `sscanf` always go +to the real function instead. Thus, the usb unlock feature does not work. + +## Unit tests +Unfortunately, this was started very late in the development of pumptools, but it's not too late to keep extending this +with every change to ensure existing features don't break and future ones work. + +When you have a change to commit, check if there is an existing unit-test which needs to be aligned with your change(s). +If not, create one. + +For testing code that gets hooked, there is a specific pattern that needs to be applied in order to allow testing +hooks without having to actually hook them. As an example, you can take a look at the +[usb-fix module](../../src/main/hook/propatch/usb-fix.h) for piu pro and the +[corresponding test](../../src/test/hook/propatch/usb-fix/main.c). It shows you how to setup the hooks and create +mocks for the functions that you can trap the calls to the real detoured functions to check if they are getting called +or return fixed values for testing. + +## Debugging +### Pumptool's built-in tooling +Pumptools has a few basic debugging features built in which helps analyzing common issues very quickly without the +need of external tools. + +#### Help and usage message of hook library +When running a game with any of pumptools's `hook.so` libs attached, you can always add the parameter `-h` to display a +help/usage message with all available command line and configuration parameters. + +For example, when using `piueb` for bootstrapping: +``` +./piueb run -h +``` + +The `-h` is passed on as a parameter and appended to the execution command. You can make use of that with other +available cmd parameters as well (see usage information). + +#### Logging +Logging to console and file as well as the level of verbosity can be controlled using the `hook.conf` configuration file +or via command line arguments provided on execution. + +All hooks implement various sanity checks for parameters provided, file checks etc. so there is a good coverage of +common mistakes and misconfiguration that can be easily detected by watching out for any ERROR or WARNING level messages +showing up in the log. + +Example in configuration file: +``` +util.log.file=pumptools.log +util.log.level=4 +``` + +Furthermore, `piueb` outputs a separate log file `piueb.log` which contains logging data of the environment setup +process of `piueb`. + +#### File and IO hook monitoring +In the configuration file or via cmd args, you can enable monitoring of file, filesystem, usb and/or open system calls +for tracing and debugging. This can be useful, to check if the game cannot find certain files like game assets. + +Example in configuration file: +``` +patch.hook_mon.file=0 +patch.hook_mon.fs=0 +patch.hook_mon.io=0 +patch.hook_mon.open=1 +patch.hook_mon.usb=0 +``` + +#### Halt on SIGSEGV and attach debugger +Also enabled via configuration or cmd args, the application is halted on SIGSEGV to allow attaching a debugger. + +Example in configuration file: +``` +patch.sigsegv.halt_on_segv=1 +``` + +### External tooling +#### Remote debugging +You can run the application with `gdbserver` using `piueb`: +```shell script +./piueb debug +``` + +And connect with your favorite debugger (e.g. IDA). + +#### strace +You can run the application with `strace` using `piueb`: +```shell script +./piueb strace +``` + +#### valgrind +You can run the application with `valgrind` using `piueb`: +```shell script +./piueb valgrind +``` \ No newline at end of file diff --git a/doc/development/images/architecture.dia b/doc/development/images/architecture.dia new file mode 100644 index 0000000..f025681 Binary files /dev/null and b/doc/development/images/architecture.dia differ diff --git a/doc/development/images/architecture.png b/doc/development/images/architecture.png new file mode 100644 index 0000000..0e668b8 Binary files /dev/null and b/doc/development/images/architecture.png differ diff --git a/doc/development/notes/exc-sound-manager-notes.md b/doc/development/notes/exc-sound-manager-notes.md new file mode 100644 index 0000000..b0d3837 --- /dev/null +++ b/doc/development/notes/exc-sound-manager-notes.md @@ -0,0 +1,55 @@ +# Notes about Exceed's implementation of a sound manager +## Pre-face +To understand what the developers of AM did here, you should have some knowledge about how they had to handle sound on +the MK3 hardware which is the predecessor of the MK5 hardware that was used with Exceed. Exceed builds on the codebase +of the previous game(s), Prex 3 and Premiere 3, that ran on MK3/MK5. However, moving from Dos (MK3)/Windows XP Embedded +(MK5) to Linux (MK5), the code had to be adjusted to work on the different kind of hardware and operating system. +Their engine was targeted and optimized for the MK3 hardware platform and had to use various pieces of custom hardware +to implement stuff like: security using the lock chip, MP3 decoding and effect playback and IO. + +## Sound playback, higher level view +For sound playback, the game had the following requirements: +1. For playing a song, menu background music, title screen intro music or any other music tracks, sound data encoded as +MP3 files are used. These needed to be loaded, decoded and played back whenever the game reaches certain points in the +game scene. +1. Various short sound effects, e.g. to give the user confirmation about hitting a button, have to be played back when +a certain event is triggered, e.g. user input or a scripted event in a scene. On exceed, these are stored as WAV files. +1. Audio played back by the above two items needs to be mixed because playback of both can happen simultaneously. + +## MK3 hardware for decoding and audio playback +The MK3 hardware came with a dedicated piece of hardware, a [MP3 decoder and playback chip](hardware.md). Therefore, +audio data from disc was streamed encoded to the hardware for decoding and playback, and effects were triggered by +commands to the appropriate chips as their audio data is stored in a ROM chip on the board. + +## Exceed's sound manager +With the move to Linux and less specialized hardware closer to a normal PC, the audio pipeline changed a lot. On Linux, +alsa/libasound is used for sending data to the underlying audio driver. Decoding of the MP3 song data is done by the +CPU. Effects are stored as WAV files and also played back using libasound. + +The sound manager relies on hardware mixing using the same sound device, e.g. hw:0. This device is opened once on game +init for playing back sound effects and another time for every song/background music to be played back for the current +scene. + +### Hardware sound tracks specs +#### Effect track +* Channels: 2 +* Buffer size in frames: 4096 +* Periods: 2 +* Sample format: 16-bit signed LE +* Sample rate: 44100 +* Sample order: FL, FR + +#### Song track +* Channels: 2 +* Buffer size in frames: 16384 +* Periods: 8 +* Sample format: 16-bit signed LE +* Sample rate: 44100 +* Sample order: FL, FR + +### Issues on todays distros +However, this way of mixing is not supported anymore by alsa. Instead, you have to use software mixing using dmix which +is supported by alsa's built in plugin system today. But, that doesn't seem to work properly with different +configurations from different versions of alsa on today's available distros. + +Fortunately, this got fixed already on Exceed 2 which implements its own software mixer in the pump engine. \ No newline at end of file diff --git a/doc/development/notes/exc2-unlock-songs.md b/doc/development/notes/exc2-unlock-songs.md new file mode 100644 index 0000000..acf73e3 --- /dev/null +++ b/doc/development/notes/exc2-unlock-songs.md @@ -0,0 +1,217 @@ +# Notes about song unlocks in Exceed 2 +Executable of v1.02, build 7th December 2004. + +A bunch of copy-pastes from IDA checking which songs can be unlocked permanently. + +## Internal song list and state structure +``` +00000000 SongList struc ; (sizeof=0x48, mappedto_187) ; XREF: .data:songList/r +00000000 trackNum dd ? +00000004 ptrArtistNameKr dd ? +00000008 ptrArtistNameEn dd ? +0000000C ptrSongNameKr dd ? ; XREF: sub_805186E+7A/r +0000000C ; sub_805186E+B4/r ... +00000010 ptrSongNameEn dd ? +00000014 ptrBpm dd ? +00000018 channelInfo dd ? +0000001C level dd 6 dup(?) +00000034 enableSong db ? +00000035 hiddenSong db ? +00000036 enableDemoPlay db ? +00000037 censor db ? +00000038 pad db ? +00000039 censorLock db ? +0000003A enable_play db ? +0000003B hidden_play db ? +0000003C enableMode db 6 dup(?) ; string(C) +00000042 enableMode_play db 6 dup(?) ; string(C) +00000048 SongList ends +``` + +## Internal song list +``` +.data:080890E4 ; SongList songList +.data:080890E4 songList dd 101h ; trackNum +.data:080890E4 ; DATA XREF: sub_8062D22+38o +.data:080890E4 ; GetSongRealIndex+33o ... +.data:080890E4 dd 807BC28h ; ptrArtistNameKr +.data:080890E4 dd 807BC2Dh ; ptrArtistNameEn +.data:080890E4 dd 807BC33h ; ptrSongNameKr +.data:080890E4 dd 0 ; ptrSongNameEn +.data:080890E4 dd 0 ; ptrBpm +.data:080890E4 dd 40624000h ; channelInfo +.data:080890E4 dd 0, 0FFFFFFFFh, 6, 0Ch, 7, 0FFFFFFFFh; level +.data:080890E4 db 1 ; enableSong +.data:080890E4 db 0 ; hiddenSong +.data:080890E4 db 1 ; enableDemoPlay +.data:080890E4 db 0 ; censor +.data:080890E4 db 0FFh ; pad +.data:080890E4 db 0FFh ; censorLock +.data:080890E4 db 0FFh ; enable_play +.data:080890E4 db 0FFh ; hidden_play +.data:080890E4 db 0,0,0,0,0,0 ; enableMode +.data:080890E4 db 0,0,1,0,0,0 ; enableMode_play +.data:080890E4 dd 102h ; trackNum +.data:080890E4 dd 807BC28h ; ptrArtistNameKr +.data:080890E4 dd 807BC2Dh ; ptrArtistNameEn +.data:080890E4 dd 807BC44h ; ptrSongNameKr +.data:080890E4 dd 0 ; ptrSongNameEn +.data:080890E4 dd 0 ; ptrBpm +.data:080890E4 dd 40668000h ; channelInfo +.data:080890E4 dd 0, 0FFFFFFFFh, 6, 0Ch, 7, 0Fh; level +.data:080890E4 db 1 ; enableSong +.data:080890E4 db 0 ; hiddenSong +.data:080890E4 db 1 ; enableDemoPlay +.data:080890E4 db 0 ; censor +.data:080890E4 db 0FFh ; pad +.data:080890E4 db 0FFh ; censorLock +.data:080890E4 db 0FFh ; enable_play +.data:080890E4 db 0FFh ; hidden_play +.data:080890E4 db 0,0,0,0,0,0 ; enableMode +.data:080890E4 db 0,0,2,0,0,0 ; enableMode_play +... +``` + +## Settings struct first few fields +``` +00000000 settings_struct struc ; (sizeof=0x162B, mappedto_186) +00000000 ff_area db 1307 dup(?) ; string(C) +0000051B unlocks_maybe db 16 dup(?) ; string(C) +0000052B blacklist db 139 dup(?) ; string(C) +000005B6 unkn2 db 117 dup(?) ; string(C) +0000062B remain db 4096 dup(?) ; string(C) +0000162B settings_struct ends +``` + +## Function that checks for song unlocks +Located at 0x0806307E + +``` +int __usercall unlock_song@(int a1@, _DWORD *arg_0, _DWORD *songid) +{ + int v3; // eax@1 + int v4; // ecx@1 + signed int v5; // edi@3 + int v6; // eax@6 + signed int v7; // eax@16 + signed int v8; // eax@23 + signed int v9; // eax@30 + signed int v10; // eax@37 + signed int v11; // eax@42 + int v13; // [sp+0h] [bp-18h]@3 + char result_unlocked_song; // [sp+7h] [bp-11h]@1 + + result_unlocked_song = 0; + v3 = sub_8061C62(dword_80BED54, "PLAY"); + v4 = v3 - 200696; + if ( !v3 ) + v4 = 0; + v5 = 0; + v13 = v4; + do + { + if ( v5 ) + { + if ( v5 != 1 ) + goto LABEL_11; + if ( !(dword_80BECD4 & 2) ) + goto LABEL_43; + v6 = sub_8056E76(v13, 1); + } + else + { + if ( !(dword_80BECD4 & 1) ) + goto LABEL_43; + v6 = sub_8056E76(v13, 0); + } + a1 = v6; +LABEL_11: + if ( currentStateCount > 2 + && !stationCurrentlySelected // 0 = arcade station + && *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECE4)) == 0xB13// ill give you all my love + && byte_80BECF5 & 2 + && *(_DWORD *)(a1 + 456) <= 10 ) + { + v7 = GetSongRealIndex(0xB19); // canon-d + *arg_0 = 0; + result_unlocked_song = 1; + *songid = 0xB19; // canon-d + songList.enableMode[72 * v7 + 4] = 0; // +4 mode offset = nightmare + } + if ( currentStateCount == 1 ) + { + if ( stationCurrentlySelected == 1 // 1 = remix station + && *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECDC)) == 0xB31// diva's remix + && *(_DWORD *)(a1 + 400) <= 20 + && !*(_DWORD *)(a1 + 448) + && *(float *)(a1 + 368) >= 1.0 ) + { + v8 = GetSongRealIndex(0xB54); // 2nd hidden remix + *arg_0 = 3; + result_unlocked_song = 1; + *songid = 0xB54; // 2nd hidden remix + songList.enableMode[72 * v8 + 4] = 0; // +4 mode offset = nightmare + } + if ( currentStateCount == 1 ) + { + if ( stationCurrentlySelected == 1 + && *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECDC)) == 0xB30// deux remix + && *(_DWORD *)(a1 + 400) <= 20 + && !*(_DWORD *)(a1 + 448) + && *(float *)(a1 + 368) >= 1.0 ) + { + v9 = GetSongRealIndex(0xB29); // banya classic remix + *arg_0 = 3; + result_unlocked_song = 1; + *songid = 0xB29; // banya classic remix + songList.enableMode[72 * v9 + 4] = 0; // +4 mode offset = nightmare + } + if ( currentStateCount == 1 ) + { + if ( stationCurrentlySelected == 1 + && *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECDC)) == 0xB29// banya classic remix + && *(_DWORD *)(a1 + 400) <= 20 + && !*(_DWORD *)(a1 + 448) + && *(float *)(a1 + 368) >= 1.0 ) + { + v10 = GetSongRealIndex(0xB51); // dignity full remix + *arg_0 = 3; + result_unlocked_song = 1; + *songid = 0xB51; // dignity full remix + songList.enableMode[72 * v10 + 2] = 0;// +2 mode offset = crazy + } + if ( currentStateCount == 1 + && stationCurrentlySelected == 1 + && *(&songList.trackNum + 18 * sub_8062DC6(dword_80BECDC)) == 0xB28// treme vook of the war + && byte_80BECED & 2 ) + { + *(&songList.hiddenSong + 72 * GetSongRealIndex(0xB58)) = 0;// disable this song as a hidden song + v11 = GetSongRealIndex(0xB58); // treme of the vook war another + *arg_0 = 3; + result_unlocked_song = 1; + *(&songList.enableSong + 72 * v11) = 1; + *songid = 0xB58; + } + } + } + } +LABEL_43: + ++v5; + } + while ( v5 <= 1 ); + if ( result_unlocked_song ) + sub_8074CA8(9, 0); + if ( settings->unlocks_maybe[10] == 1 && stationCurrentlySelected == 1 ) + { + *(&songList.hiddenSong + 0x48 * GetSongRealIndex(0xB57)) = 0;// disable this song as a hidden song + *(&songList.enableSong + 0x48 * GetSongRealIndex(0xB57)) = 1; + if ( !result_unlocked_song ) + { + *arg_0 = 3; + *songid = 0xB57; // canon-d full mix + } + result_unlocked_song = 1; + } + return (unsigned __int8)result_unlocked_song; +} +``` \ No newline at end of file diff --git a/doc/development/notes/extract-song-list.md b/doc/development/notes/extract-song-list.md new file mode 100644 index 0000000..d2af521 --- /dev/null +++ b/doc/development/notes/extract-song-list.md @@ -0,0 +1,141 @@ +# Extracting song list from game +## NX2 and NXA +The song list is compiled into the game's executable as a big table with the following structure: +```c +struct SongList_t +{ + int IndexNum; // Index in the song list array + int TrackNum; // Song ID + int szArtistName_kr; // const char* to the null terminated string + int szArtistName_en; // const char* to the null terminated string + int szSongName_kr; // const char* to the null terminated string + int szSongName_en; // const char* to the null terminated string + int BPM; + int ChannelInfo; + int Level[6]; // Array indices correspond to the following and the content is the difficulty level for that mode + // See GetRandomTrack() + // 0 - eStepNormal + // 1 - eStepHard + // 2 - eStepCrazy + // 3 - eStepHalfDouble + // 4 - eStepFreestyle + // 5 - eStepNightmare + bool bEnable_m; + bool bHidden_m; + bool bEnableDemoPlay; // Allow playing in attract mode + bool bCensor; // Don't show song in game (configurable) + bool bPad; + bool bCensorLock; // Permanently censored (not configurable) + bool bEnable_play; + bool bHidden_play; + int TexID; + bool bEnableMode_m[6]; // Modes where this song is allowed + bool bEnableMode_play[6]; + short unknown1; + short time; // Time value used to calculate kcal and VO2 + short SongMileage; // Cost to unlock this song + short ModeMileage[3]; // Mileage required to play this song on the different modes +} __attribute__((packed)); +``` + +Probably the easiest method to locate that table is to find a string that any of the entries points to using your +favorite decompiler. Song names are good candidates as most of them should be unique and easy to locate when your tool +can expose all strings of the binary. Follow any references to the string and you should easily find a chunk of that +that looks like this: +```text +.data:08130FDF db 0 +.data:08130FE0 ; int g_songListWithouBlazeEmotion[] +.data:08130FE0 g_songListWithouBlazeEmotion dd 0F02h ; DATA XREF: HandleSongUnlocksMaybe:loc_806A5B5r +.data:08130FE0 ; HandleSongUnlocksMaybe+26Br ... +.data:08130FE4 dd offset aACn ; "8¥¦ 8ûì " +.data:08130FE8 dd offset aYahpp ; "YAHPP " +.data:08130FEC dd offset aRieX_1 ; "8¦Édà+ X.1 " +.data:08130FF0 dd offset aCannonX_1 ; "Cannon X.1 " +.data:08130FF4 dd offset a185 ; "185 " +.data:08130FF8 dd 0 +.data:08130FFC dd 3 +.data:08131000 dd 0Ah +.data:08131004 dd 14h +.data:08131008 dd 0Dh +.data:0813100C dd 16h +.data:08131010 db 1 +.data:08131011 db 1 +.data:08131012 db 0 +.data:08131013 db 1 +.data:08131014 db 0 +.data:08131015 db 1 +.data:08131016 db 0 +.data:08131017 db 0 +.data:08131018 dd 0FFFFFFFFh +.data:0813101C db 1 +.data:0813101D db 1 +.data:0813101E db 1 +.data:0813101F db 1 +.data:08131020 db 1 +.data:08131021 db 0 +.data:08131022 db 0 +.data:08131023 db 0 +.data:08131024 db 0 +.data:08131025 db 0 +.data:08131026 db 0 +.data:08131027 db 0 +.data:08131028 dw 79h +.data:0813102A dw 0 +.data:0813102C dw 0 +.data:0813102E dw 0 +.data:08131030 dw 0 +.data:08131032 dw 0 +.data:08131034 dw 0 +.data:08131036 dw 0 +.data:08131038 dw 2 +.data:0813103A dw 0 +.data:0813103C dd 0F03h +``` + +Find the beginning by going backwards and checking where the first valid entry with pointers to artist, title etc +start. Usually, the first entry is also the primary reference for the game to address the song list. + +This should allow you to find the function `GetSongRealIndex`. From there, you can easily find out the total length +of the song list. +```c +int __cdecl GetSongRealIndex(int songNo) +{ + signed int v1; // ebx@1 + int v2; // ecx@2 + int v3; // edx@3 + int v4; // eax@4 + + v1 = GetTotalTrackNum(); // 324 + if ( v1 <= 0 ) + return -1; + v2 = 0; + if ( g_songIdBlazingEmotion[0] != songNo ) + { + v2 = 0; + v3 = 0; + while ( 1 ) + { + ++v2; + if ( v2 == v1 ) + break; + v4 = g_songListWithouBlazeEmotion[v3]; + v3 += 23; + if ( v4 == songNo ) + return *(&g_songList[0].TrackNum + 23 * v2); + } + return -1; + } + return *(&g_songList[0].TrackNum + 23 * v2); +} +``` + +With that info, you can dump the list straight from the binary, e.g. using `dd`: +```shell script +dd if=./piu bs=1 skip=954240 count=31200 of=songlist.bin +``` + +Requires you to manually calculate the size by using the above struct as reference. + +Note: This part does not cover the dumping of the referenced strings to build a full song list. This process was mainly +used to get a song index mapping table for mapping absolute indices of the song table array to the song IDs and vice +versa (for pumpnet). \ No newline at end of file diff --git a/doc/development/notes/f2-crashing-on-modern-linux-post-mortem.md b/doc/development/notes/f2-crashing-on-modern-linux-post-mortem.md new file mode 100644 index 0000000..1562f4f --- /dev/null +++ b/doc/development/notes/f2-crashing-on-modern-linux-post-mortem.md @@ -0,0 +1,255 @@ +# Post mortem: Fiesta 2 crashing on modern linux +Date of writing: 17th November 2019 + +This is a port mortem about an issue that was puzzling me for months. In the following sections, I am documenting the +whole story as detailed as possible hoping this is going to be useful for anyone else (or even myself) in the future. + +## What happened? +I have been running a multidisk setup with all pump games on it for a few years now and have been maintaining and +improving pumptools in the process. I always kept things quite up to date because once you need some more tooling +even on something like a dedicated machine, updating the system after it has not been updated for months or years +becomes very difficult. Furthermore, things might stop working that have worked before because of one or even multiple +issues that got introduced with the update. + +Anyway, my cabinet with the multidisk setup yields the following on `uname -a`: +`Linux piumd 4.15.11-1-ARCH #1 SMP PREEMPT Mon Mar 19 18:21:03 UTC 2018 x86_64 GNU/Linux` + +My workstation is a bit more up-to-date with (though, I am behind a few months...): +`Linux ambient 5.2.3-arch1-1-ARCH #1 SMP PREEMPT Fri Jul 26 08:13:47 UTC 2019 x86_64 GNU/Linux` + +In the past, like more than a year ago, my workstation was also running a newer kernel, version of runtime libraries +etc. but running Fiesta 2 and newer with pumptools wasn't an issue. However, Fiesta 2 and newer stopped working when +I was continuing with my development efforts and just crashed with a segmentation fault. All old versions prior +Fiesta 2 were still working perfectly fine. + +## Error analysis: first information and debugging steps +First, I enabled f2hooks built in logging to yield more output `util.log.level=4` and enabled halting on crashes to +be able to attach a debugger `patch.sigsegv.halt_on_segv=1`. This is the output I got: +``` +[I][f2hook][main.c:155]: Hooking finished +[M][f2hook][main.c:168]: Calling orig_init +[E][patch-sigsegv][sigsegv.c:18]: =================== +[E][patch-sigsegv][sigsegv.c:19]: !!!!! SIGSEGV !!!!! +[E][patch-sigsegv][sigsegv.c:28]: Backtrace (3 frames): +[E][patch-sigsegv][sigsegv.c:31]: ./f2hook.so(+0x6a02) [0xf7e20a02] +[E][patch-sigsegv][sigsegv.c:31]: ./f2hook.so(+0xf827) [0xf7e29827] +[E][patch-sigsegv][sigsegv.c:31]: linux-gate.so.1(__kernel_sigreturn+0) [0xf7f07940] +[E][patch-sigsegv][sigsegv.c:37]: Halting for debugger... +``` + +Ok, so it seems like something crashed in f2hook.so. However, hooking up the debugger did not yield anything useful +and this was just pointing to the sigsegv handler (thought it worked with debugging other issues and actually pointed +to the location where it crashed...strange). + +Next, I started removing stuff from f2hook to get a minimal version which still boots up to that point, no changes. + +Then, I hooked up gdb, set a breakpoint before the crash happened and stepped my way through the whole libc +runtime setup. I figured out, that the crash happens when the libc runtime is calling handler functions of the +target application which setup various parts of the runtime environment, like static initialization. + +And this is the place where the crash happened. There is one function in the piu executable which setups up all static +variables, for example constructing static strings or other objects. Once it tried to construct the first static string +of that static setup function, it crashed. To be more precise, it crashed in the string constructor when allocating +memory for the string. + +Great, we found the root cause for that. Let's figure out why that happens and what's different now that it crashes +there. + +## Tracing the bug down to libstdc++ and being totally mislead by various things +Because there have been issues with incompatible libc/libcstd++ versions previously, I quickly came to the idea of +comparing the version that worked on my cabinet and the version that did not work on my workstation. Indeed, these +were different versions (considering the age gap of the two OSes, that wasn't very surprising). + +I started digging into the libstdc++ source code, especially looking at what is going on when strings are allocated. +Navigating the libstdc++ source is not a nice task. The naming is very confusing and, imo, non intuitive. With +templating all over the place, this makes it even worse. + +Finally, I found something that sounded very related to what I have discovered so far: +In the file include/bits/basic_string.h, search for +``` +_GLIBCXX_RESOLVE_LIB_DEFECTS +3076. basic_string CTAD ambiguity +``` + +and you will end up at a string constructor call. From that, I concluded that something was fixed related to +constructor calls. I compared the string allocation call of both versions, the non working and working ones. The +allocation for strings has changed. + +Going back to the location of the crash in the piu executable, I saw that the string allocation was even inlined and +not going to a library call of...boost filesystem. The first static object created is the path "../" which is used by +a path object by boost filesystem. Because boost filesystem is a header only library, I assume that's why this got +inlined. + +However, I was using the libstdc++ version of my local machine where the string allocation scheme was different. This +still did not truly explain why it crashed there with a jump to address 0, but things aligned pretty well so far. + +So, the first idea for a solution was to use the old libstdc++ version from the machine where everything was fine +and working. However, using it did not change anything. I went on and grabbed more libraries from the machine until +I had all of them on my workstation and hooked them up with a `LD_LIBRARY_PATH`. But now, the application did crash +very early and did not even get to setting up the runtime at all. I did some poking but could not figure out what +was happening there. It just crashed with another jump to 0 very early in launching the elf. + +Debugging continued for a bunch of weeks, on and off. I got more focused on digging deeper into the string allocation +thing as this was giving me a direction and was aligning with my findings so far. I came up with a "solution" to hook +and overwrite the string constructor calls of the newer libstdc++ version. + +I started replacing them one by one to re-implement the old allocation scheme. However, this was not enough as many +function calls that are working with string objects rely on the used allocation scheme, too. Therefore, I also started +patching these until I realized that I have to re-implement, at least, everything basic_string related. + +At that point, I was quite sure that this is the wrong way to go and stopped with this approach. + +A few more months passed, I got back at this multiple times but couldn't come up with another solution or even +direction. + +## AppImage +In a talk with a buddy, I had the idea of using some sort of sandboxing or environment boxing to pack things up nicely +into a single blob to make it easier to handle, even distributable and maybe even more platform independent. + +After some internet research, I found a solution that looked like it fits the pump game use-case very well which was +AppImage. AppImage packs all your stuff into a single binary blob which is basically just a sqash fs image with a +special runtime bootstrapping the whole image and running a bash script once the image is mounted. The provided features +are very minimalistic but solve all our needs for packing pump games nicely. + +The reference documentation also pointed out a few things about packing runtime libraries and how to set them up using +`LD_LIBRARY_PATH` which I was already doing but just using a naked bash script. However, this reading was starting to +point me into the right direction. + +## Providing a platform/distro independent environment for your application down to the linker +I started playing around with the libraries I used for Fiesta 2. First, I grabbed _all_ libraries from my cabinet which +are required to run the game (using `ldd`). Then I started trying different combinations of including them using +`LD_LIBRARY_PATH` and some used by my native system. Nope, either I was running into the static init crashing issue +or things crashed even earlier on. + +Wait, have I ever looked into why the application crashed so early, even before spinning up the runtime environment? +After working on this on and off for months and totally losing track by getting to deep into this libstdc++ thing, I +realized I didn't. + +I set up the application to use all libraries from my cabinet and started debugging. First, I tried my luck with `gdb` +but didn't get very far as it just showed me a jump to 0, again. Next, I got pointed out to using `valgrind`. This was +a great hint as valgrind hooks into the bootstrapping process of the application and even displays a lot of information +about library loading and setting up the environment even way before the runtime is set up. Recommendation: Use +`valgrind -v -v -v` to get a lot of output. + +However, in the end, valgrind also just pointed out the jump to 0 being the reason for the crash but not why this +happened. The additional information was good to figure out the steps happening in the application setup process but +did not yield any additional insights. + +After giving this some more thought and making sure I used all runtime libraries from the cabinet, I did some +internet research again and tried go get more information on how ELFs are bootstrapped and what happens. It turned out +that information on that topic very sparse and difficult to find. However, this brought me to the following thread +from the gnu mailing list: +``` +Hi all; + +I have a need to use a different runtime linker (ld-linux.so) for some +of my applications. I _don't_ want to change the executables themselves +(changing the path to the runtime linker in the ELF image for example). + +I know that I can just run my application as an argument to ld-linux.so, +and that actually does work very well. But, I am left with two +problems: + + * Some of my applications fork/exec other programs, and those other + programs also need to use the other runtime linker. Just as I don't + want to change the ELF image, I certainly don't want to have to + recompile the programs to exec the runtime linker! + + * Debugging: I can't debug because I can't find a way to convince GDB + to invoke the program-to-be-debugged using an alternative runtime + linker. + +I was wondering if anyone has any ideas about how to do this? I checked +the man pages and didn't see anything (I was looking for something like +an environment variable that ld-linux.so might obey to re-exec a +different version, or some such thing--not likely, I know). + +If there's nothing like that available another idea is to use LD_PRELOAD +to install a private version of the execve() system call, which would +set the alternative ld-linux.so as the first argument. Any thoughts on +this approach? Do you think it would solve the GDB problem as well? +I'm going to proceed to work on this idea but I wanted to see if anyone +had other thoughts about it. + + + +PS. In case anyone's curious, I'm trying to run some programs natively + on my desktop system which were actually compiled for a different + system, and the native and target systems have different versions of + glibc installed. I can't change either of the versions of glibc, + but I do have the complete root filesystem of the target box in a + subdirectory of my native box and I can use LD_LIBRARY_PATH, etc. to + use them... I just need a way to get the runtime environment to + initialize properly. + +-- +------------------------------------------------------------------------------- + Paul D. Smith HASMAT--HA Software Mthds & Tools + "Please remain calm...I may be mad, but I am a professional." --Mad Scientist +------------------------------------------------------------------------------- + These are my opinions---Nortel Networks takes no responsibility for them. +``` + +Source: https://lists.gnu.org/archive/html/help-gnu-utils/2004-06/msg00020.html + +Oh, wow. I was amazed that someone else was having a very similar use-case of running applications from legacy systems +on a newer machine with different library versions and conflicts. + +Reading on, this answer was finally revealing the last piece of the puzzle to understand the issue I was facing with +Fiesta 2: +``` +If you have an old a.elf main executable program that was linked +to run with libc.so.6, and you want to run that a.elf on a machine +whose libc.so.6 differs from the one which a.elf was linked against, +and you want a reasonable guarantee that it will work, then it may +be necessary to use something like rtldi to choose the matching +ld-linux.so.2 and libc.so.6 and other libraries as of the time of +static binding of a.elf. In theory, libc.so.6 is backwards compatibile +(a given a.elf should run the same under any subsequent libc.so.6), +but in practice there have been too many bugs in {ld-linux.so.2, +libc.so.6, ...}. For example, many programs linked for libc.so.6 +under RedHat Linux 7.2 and earlier won't run under glibc-2.3.2. +The most effective way to run such a program may be to install the +old glibc-x.y.z[.rpm] in a different location, binary edit the PT_INTERP +string of the a.elf, and use rtldi. This makes the old [edited] a.elf +simultaneously interoperable with other programs from different +libc.so.6 generations, and only the "oddball" program has to know. + +... + +ld-linux.so.2, libc.so.6, libdl.so, libnss*.so, and _many_ other shared +libraries of a given released version, are a matched set. They must be +used together, all from the same release, or not at all. Mixing and +matching pieces from different glibc-x.y.z need not work. In order to +use a consistent set that differs from the default set, you must invoke +the ld-linux.so.2 using "--library-path PATH" to specify the location +of the rest of the set. Using --library-path supersedes LD_LIBRARY_PATH +for one execve() only, and does not interfere with chilren, execve() +with no fork(), or other processes. +``` + +Oh, I was not aware about ld-linux.so being THAT important to this set of libc.sp, libdl.so etc. Therefore, I grabbed +the ld-linux.so file from my cabinet and ran the application with all libraries (except the GPU driver specific ones) +on my workstation like this: +``` +LD_PRELOAD="./f2hook.so" ./ld-linux.so.2 --library-path ./lib ./piu ./game/ --options ./hook.conf +``` + +And the application starts up, passes runtime initializing, hits main and the game works. + +I don't think there is anything else to add to the copy-pasted explanations above. Make sure to read them carefully to +understand the whole issue. Once you know what is going on, the whole thing is not even super complex. + +## Conclusions +I would consider this being one of the major issues I have resolved so far for pumptools as this was so misleading, +difficult to debug and find any general information on. The reason behind that and the solution are super simple, once +you know them. + +As we have already discussed various times in the past, this issue further points out how important proper solutions +for sandboxing, containerizing or how you want to call it for legacy applications like pump games are. The naked +bash script setting up everything is already quite good and doing the job. AppImage further enhances this by making +the whole application more portable. + +However, understanding the whole process behind ELF loading, library linking, setting up the process environment and +setting up the runtime environment is very important. Otherwise, rather simple incompatibilities are the result which +have a major impact on running the application. \ No newline at end of file diff --git a/doc/development/notes/hardware.md b/doc/development/notes/hardware.md new file mode 100644 index 0000000..6e453eb --- /dev/null +++ b/doc/development/notes/hardware.md @@ -0,0 +1,107 @@ +# Hardware used by Pump It Up Games +## Main PC/CPU hardware +### MK 1 +We have not seen this piece of hardware and the chances these boards still exist and are sold somewhere is rather low. +However, there are pointers in the code of the game and the evolution of the hardware that it existed for the first +few versions of PIU: +* Songs were played back entirely from CD requiring to store them as 16-bit PCM audio. Therefore, the hardware did not +have a chip for decoding MP3. +* Absence of hardware security/lockchip. + +We assume that this hardware was shipped and used with 1st, 2nd and 3rd. Starting 3rd SE, song files were stored as +encoded MP3 files on the CD requiring a MP3 decoder available with [MK3 hardware](###MK 3). + +### MK 3 +All games from 1st to Prex3/Premiere 3 are running on this revision of hardware. 1st to 3rd are compatible with this +hardware. + +System configuration dump using PC-DOCTOR DOS 3.0 +``` +SYSTEM CONFIGURATION===================================================== + + Operating System - DOS 7.10 in HMA + CPU Type - 333 MHz Intel Celeron 333 + CPUID - "GenuineIntel", Family 6, Model 6, Step 5 + - MMX available + Coprocessor Type - 686 + Expansion Bus Type - ISA, PCI + ROM BIOS Date - 04/20/99 + ROM BIOS Copyright - COPYRIGHT Award Software Inc. + Additional ROM - C800[8kB] + Base Memory - 640 kB + Expanded Memory - N/A + Extended Memory - 64512 kB (CMOS Configuration) + XMS Memory - 62400 kB (XMS 3.00, Driver 3.95) A20=ON + Serial Ports - None Installed + Parallel Ports - None Installed + Video Adapter - VGA: 3Dfx Interactive, Inc. + - Total Memory : 8192 kB + Fixed Disk Drives - 42 MB + Floppy Disk Drives - 1 - 1=3.5"/1.44M + Mouse - No Mouse + Joysticks - None + Sound Card - N/A + CAS Fax/Modem Card - N/A + Disk Compression - N/A +CD-ROM Driver Version - MSCDEX V2.25, Drive: D: + Disk Cache - Smartdrive 5.02, Hits 76%, Size 2048 kB + Primary IDE Master - ATAPI: HL-DT-STDVD-ROM GDR8164B + Primary IDE Slave - IDE: QUANTUM FIREBALL_TM1280A, S/N:692708422782 + Secondary IDE Master - No Drive + Secondary IDE Slave - No Drive + SCSI - N/A + Network - N/A + Power Management - APM V1.2, Power On + USB Port - Installed on IRQ 10 + Chipset Type - Intel 440BX + L2 Cache Type - 128 kB + DRAM Row 0 - 64 MB + DRAM Row 1 - Empty + DRAM Row 2 - Empty + DRAM Row 3 - Empty + DRAM Row 4 - Empty + DRAM Row 5 - Empty + DRAM Row 6 - Empty + DRAM Row 7 - Empty + + +PC-DOCTOR DOS 3.0 Copyr 2004 PC-Doctor, Inc. 16:15 9/25/2000 +``` + +A bunch of custom chips for handling the following: +* Sound + * YAMAHA YMZ280B-F (8-Channel PCM/ADPCM Decoder) + * 16-bit PCM sound effects are played from external memory + * YAMAHA YAC516 (Delta Sigma Modulation D/A Converter with 8 times Over-sampling Filter) + * MICRONAS DAC3550A C2 (Stereo Audio DAC) + * MICRONAS MAS3507D F10 (MPEG 1/2 Layer 2/3 Audio Decoder) + * Songs are stored MP3 encoded + +### MK 5 + +### MK 6 + +### MK 7 + +### MK 9 + +### MK X + +## IO hardware +### ISA PIUIO + +### USB PIUIO + +### USB PIUIO 2 + +### PIUBTN + +### XX Card reader + +## Security +### Lockchip + +### USB dongles + +### ds1963s +Used for Pro 1/2 (and ITG 1/2). \ No newline at end of file diff --git a/doc/development/notes/nx2-usb-profile-loading-saving.md b/doc/development/notes/nx2-usb-profile-loading-saving.md new file mode 100644 index 0000000..4a57100 --- /dev/null +++ b/doc/development/notes/nx2-usb-profile-loading-saving.md @@ -0,0 +1,141 @@ +# Notes about how NX2 handles saving and loading usb profile data +Executable of v1.54. + +This document outlines the relevant parts regarding how NX2 loads and stores profiles and how this had an impact on the +design of the patch module `net-profile`, that hooks up usb profile loading over socket to a remote server. + +## Syscall flow +The game calls the following system calls in order per player, here player 1 (id 0), when loading usb profile data +(assume all calls are successful here): +```c +// Simplified pseudo-like C code omitting non relevant parameters +fopen("/mnt/0/nx2save.bin") +fread(fd_save) // stats area +fread(fd_save) // encrypted area +fclose(fd_save) + +fopen("/mnt/0/nx2rank.bin") +fread(fd_rank) // encrypted area +fclose(fd_rank) +``` + +For saving the profile, this is the call flow: +```c +// Simplified pseudo-like C code omitting non relevant parameters +fopen("/mnt/0/nx2save.bin") +fseek(fd_save) // beginning +fwrite(fd_save) // stats area +fwrite(fd_save) // encrypted area +fclose(fd_save) + +fopen("/mnt/0/nx2rank.bin") +fseek(fd_rank) // beginning +fwrite(fd_rank) // encrypted area +fclose(fd_rank) +``` + +We can see that in both scenarios, for loading and saving, the game first takes care of a single file before moving on +to the next one. + +There is another flow that is slightly different when playing World Max. After playing a song in World Max, the game +writes the changes of the `nx2save.bin` file back: +```c +// Simplified pseudo-like C code omitting non relevant parameters +fopen("/mnt/0/nx2save.bin") +fseek(fd_save) // beginning +fwrite(fd_save) // stats area +fwrite(fd_save) // encrypted area +fclose(fd_save) +``` + +## Pumpnet RPCs +The above set the requirements for how pumpnet needs to handle file loading and storing. + +### First solution +The initial solution was combining the loading and storing process of both files, `nx2save.bin` and `nx2rank.bin` +into a single download/upload call. + +Load phase: +```c +// Simplified pseudo-like C code omitting non relevant parameters +fopen("/mnt/0/nx2save.bin") +pumpnet_download_profiles() +fread(fd_save) // read from buffered data +fread(fd_save) // read from buffered data +fclose(fd_save) + +fopen("/mnt/0/nx2rank.bin") // skip, data already buffered +fread(fd_rank) // read from buffered data +fclose(fd_rank) // clear all buffers and reset internal state +``` + +Save phase: +```c +// Simplified pseudo-like C code omitting non relevant parameters +fopen("/mnt/0/nx2save.bin") +pumpnet_download_profiles() +fseek(fd_save) // seek for buffers +fwrite(fd_save) // write to buffers +fwrite(fd_save) // write to buffers +fclose(fd_save) // skip, do nothing + +fopen("/mnt/0/nx2rank.bin") // skip, data already buffered +fseek(fd_rank) // seek for buffers +fwrite(fd_rank) // write to buffers +pumpnet_upload_profiles() +fclose(fd_rank) // clear all buffers and reset internal state +``` + +However, with the additional flow during World Max, this solution didn't work and broke internal state handling. +Modifying this solution to incorporate saving just the `nx2save.bin` file wasn't feasible with the above architecture. + +### Second solution +Therefore, the pumpnet backend was re-designed to operate on on a single file level and separate for saving and +loading. + +Load phase: +```c +// Simplified pseudo-like C code omitting non relevant parameters +fopen("/mnt/0/nx2save.bin") +pumpnet_download_save() +fread(fd_save) // read from buffered data +fread(fd_save) // read from buffered data +fclose(fd_save) +pumpnet_clear_save_cache() + +fopen("/mnt/0/nx2rank.bin") +pumpnet_download_rank() +fread(fd_rank) // read from buffered data +fclose(fd_rank) +pumpnet_clear_rank_cache() +``` + +Save phase: +```c +// Simplified pseudo-like C code omitting non relevant parameters +fopen("/mnt/0/nx2save.bin") +pumpnet_download_save() +fseek(fd_save) // seek for buffers +fwrite(fd_save) // write to buffers +fwrite(fd_save) // write to buffers +fclose(fd_save) +pumpnet_upload_save() +pumpnet_clear_save_cache() + +fopen("/mnt/0/nx2rank.bin") +fseek(fd_rank) // seek for buffers +fwrite(fd_rank) // write to buffers +fclose(fd_rank) +pumpnet_upload_rank() +pumpnet_clear_rank_cache() +``` + +Buffer modifications are detected when `fwrite` is called and flagged accordingly that the module is aware of having +to write back dirty data on the call to `fclose`. + +State handling is also simplified as it only needs to be taken care of on a file level between `fopen` and `fclose` +calls and not on a multi-file level. + +This solution is definitely more robust and flexible than the previous one that operated on a full profile level. +However, the call overhead to the remote server is four times higher compared to the previous solution. Considering +that these calls are not being issued on a high frequency, server load is unlikely to be an issue. \ No newline at end of file diff --git a/doc/development/notes/pad-command-codes-notes.md b/doc/development/notes/pad-command-codes-notes.md new file mode 100644 index 0000000..60694b0 --- /dev/null +++ b/doc/development/notes/pad-command-codes-notes.md @@ -0,0 +1,448 @@ +# Pad command codes notes +Raw data and notes about pad command codes which were extracted directly from the games because I could not find a +complete and 100% accurate list per game. + +All codes extracted from the MK3 games are based on the Linux port executables. + +How did I find these? I used the `ac_sound_play_effect` with the second parameter set to the value `3` as my entry +point. This plays the sound effect that you can hear when you entered the modifier code correctly. After I have found +the right call, it's next to some code like this (taken from 03_3rd): +``` +... + if ( modFlags & 1 ) + { + if ( modFlagsSpeed1P & 1 ) + result = func_2a2dc(0, 1); + if ( modFlagsSpeed1P & 2 ) + result = func_2a2dc(0, 2); + if ( modFlagsSpeed1P & 4 ) + result = func_2a2dc(0, 4); + if ( modFlagsSpeed1P & 8 ) + result = func_2a2dc(0, 8); + if ( modFlagsSpeed1P & 0x10 ) + result = func_2a2dc(0, 16); + } + if ( modFlags & 2 ) + { + if ( modFlagsSpeed2P & 1 ) + result = func_2a2dc(1, 1); + if ( modFlagsSpeed2P & 2 ) + result = func_2a2dc(1, 2); + if ( modFlagsSpeed2P & 4 ) + result = func_2a2dc(1, 4); + if ( modFlagsSpeed2P & 8 ) + result = func_2a2dc(1, 8); + if ( modFlagsSpeed2P & 0x10 ) + result = func_2a2dc(1, 16); + } + if ( modFlagsSpeed1P & 0x1F ) + { + checkIfModifierSet2(); + result = ac_sound_play_effect(0, 3u, 2u); + } + if ( modFlagsSpeed2P & 0x1F ) + { + checkIfModifierSet(); + result = ac_sound_play_effect(0, 3u, 2u); + } + return result; +} +``` + +I already renamed the function that is called prior the `ac_sound_play_effect` to `checkIfModifierSet` here. That +function checks the last eight pad inputs from a list against a list of command codes. Once a code matches, flags are +set accordingly. + +Excerpt, again from 03_3rd: +``` +signed int checkIfModifierSet() +{ + const char *v0; // edx@1 + int v1; // ebp@1 + signed int v2; // ebx@1 + const char *v3; // edx@19 + unsigned int v4; // ebx@19 + signed int result; // eax@41 + signed int v6; // [sp+0h] [bp-1Ch]@1 + + v0 = (const char *)&codeList; // THIS is the code list + v1 = *(_DWORD *)&modFlags; + v2 = 0; + v6 = *(_DWORD *)&data_3299a4; + while ( memcmp((const char *)&data_44e9e8, v0, 8) )// iterate command list and currently post 8 inputs + { + ++v2; + v0 += 8; // each modifier code is 8 commands + if ( v2 >= 13 ) // beyond command list size + goto jump_2a17f; + } + switch ( v2 ) // found command id + { + case 0: + *(_DWORD *)&data_3299a4 = 0; + *(_DWORD *)&modFlags = *(_WORD *)&modFlags & 0xFFF; + break; +``` + +## 03_3rd +``` +1 = DL +2 = DR +4 = C +8 = UL +16 = UR + +-- cancel +.data:0810833A db 4 +.data:0810833B db 4 +.data:0810833C db 4 +.data:0810833D db 4 + +.data:0810833E db 4 +.data:0810833F db 4 +.data:08108340 db 4 +.data:08108341 db 4 +--------------------------- vanish +.data:08108342 db 4 center +.data:08108343 db 4 center +.data:08108344 db 10h up right +.data:08108345 db 8 up left +.data:08108346 db 4 center +.data:08108347 db 2 down right +.data:08108348 db 1 down left +.data:08108349 db 4 center +----------------- mirror +.data:0810834A db 4 +.data:0810834B db 4 +.data:0810834C db 1 +.data:0810834D db 4 +.data:0810834E db 10h +.data:0810834F db 2 +.data:08108350 db 4 +.data:08108351 db 8 +---- 8X +.data:08108352 db 4 +.data:08108353 db 4 +.data:08108354 db 1 +.data:08108355 db 4 +.data:08108356 db 2 +.data:08108357 db 4 +.data:08108358 db 4 +.data:08108359 db 4 +- 4 X +.data:0810835A db 4 +.data:0810835B db 4 +.data:0810835C db 2 +.data:0810835D db 4 +.data:0810835E db 4 +.data:0810835F db 8 +.data:08108360 db 4 +.data:08108361 db 4 +2X +.data:08108362 db 4 +.data:08108363 db 4 +.data:08108364 db 8 +.data:08108365 db 4 +.data:08108366 db 4 +.data:08108367 db 4 +.data:08108368 db 10h +.data:08108369 db 4 +random step +.data:0810836A db 4 +.data:0810836B db 1 +.data:0810836C db 4 +.data:0810836D db 2 +.data:0810836E db 4 +.data:0810836F db 10h +.data:08108370 db 4 +.data:08108371 db 8 +nonstep +.data:08108372 db 8 +.data:08108373 db 10h +.data:08108374 db 4 +.data:08108375 db 8 +.data:08108376 db 10h +.data:08108377 db 4 +.data:08108378 db 8 +.data:08108379 db 10h +synchro +.data:0810837A db 4 +.data:0810837B db 4 +.data:0810837C db 8 +.data:0810837D db 10h +.data:0810837E db 8 +.data:0810837F db 10h +.data:08108380 db 4 +.data:08108381 db 4 +couple +.data:08108382 db 4 +.data:08108383 db 4 +.data:08108384 db 10h +.data:08108385 db 8 +.data:08108386 db 10h +.data:08108387 db 8 +.data:08108388 db 4 +.data:08108389 db 4 +union +.data:0810838A db 1 +.data:0810838B db 2 +.data:0810838C db 4 +.data:0810838D db 8 +.data:0810838E db 2 +.data:0810838F db 1 +.data:08108390 db 4 +.data:08108391 db 10h +random vanish +.data:08108392 db 8 +.data:08108393 db 10h +.data:08108394 db 4 +.data:08108395 db 1 +.data:08108396 db 2 +.data:08108397 db 4 +.data:08108398 db 8 +.data:08108399 db 10h +random speed +.data:0810839A db 8 +.data:0810839B db 10h +.data:0810839C db 2 +.data:0810839D db 4 +.data:0810839E db 1 +.data:0810839F db 4 +.data:081083A0 db 2 +.data:081083A1 db 1 + +``` + +Looking further into the code, there seems to be another command list with three entries right after that and it's +checked in the same function. This time, the number of inputs are 10 instead of 8: +``` + v3 = (const char *)&data_108d84; // THIS is the code list + v4 = 0; + while ( memcmp(data_44e9f8, v3, 10) ) + { + ++v4; + v3 += 10; + if ( (signed int)v4 >= 3 ) + goto jump_2a289; + } + if ( v4 < 1 ) + { + if ( !v4 && data_44ea30 == 2 ) + data_108bad |= 0x80u; + } + else if ( v4 <= 1 ) + { + if ( data_108bad & 4 ) + { + *(_WORD *)&modFlags |= 0x820u; + qmemcpy(data_44e488, &data_388e00[20 * (4 * data_108ba8 + 52)], 0x50u); + } + } + else if ( v4 == 2 + && (data_108bad & 2 && data_108ba8 >= 2 || data_108bad & 4 && !(modFlags & 0x20) && data_108ba8 == 1) ) + { + data_108bae |= 1u; + } +``` + +Code list: +``` +1 = DL +2 = DR +4 = C +8 = UL +16 = UR + +.data:081083A2 data_108d84 db 2 ; DATA XREF: func_29ce8:jump_29e70o +.data:081083A2 ; checkIfModifierSet:jump_2a17fo +.data:081083A3 db 1 +.data:081083A4 db 8 +.data:081083A5 db 10h +.data:081083A6 db 18h +.data:081083A7 db 20h +.data:081083A8 db 12h +.data:081083A9 db 11h +.data:081083AA db 10h +.data:081083AB db 18h + +.data:081083AC db 12h +.data:081083AD db 18h +.data:081083AE db 10h +.data:081083AF db 1 +.data:081083B0 db 8 +.data:081083B1 db 2 +.data:081083B2 db 11h +.data:081083B3 db 20h +.data:081083B4 db 11h +.data:081083B5 db 10h + +.data:081083B6 db 8 +.data:081083B7 db 2 +.data:081083B8 db 18h +.data:081083B9 db 12h +.data:081083BA db 1 +.data:081083BB db 10h +.data:081083BC db 11h +.data:081083BD db 20h +.data:081083BE db 2 +.data:081083BF db 18h +``` + +However, the code list seems to have inputs with multiple bit fields set, e.g. 0x12, 0x11. Untested. + +## 04_3se +List of previous version extended by 3 more codes (last ones in the list). + +``` +1 = DL +2 = DR +4 = C +8 = UL +16 = UR + +data_12ae8c db 4 ; DATA XREF: func_2a8c0+23o +.data:08123643 ; func_2ab98+23o +.data:08123644 db 4 +.data:08123645 db 4 +.data:08123646 db 4 +.data:08123647 db 4 +.data:08123648 db 4 +.data:08123649 db 4 +.data:0812364A db 4 + +.data:0812364B db 4 +.data:0812364C db 4 +.data:0812364D db 10h +.data:0812364E db 8 +.data:0812364F db 4 +.data:08123650 db 2 +.data:08123651 db 1 +.data:08123652 db 4 + +.data:08123653 db 4 +.data:08123654 db 4 +.data:08123655 db 1 +.data:08123656 db 4 +.data:08123657 db 10h +.data:08123658 db 2 +.data:08123659 db 4 +.data:0812365A db 8 + +.data:0812365B db 4 +.data:0812365C db 4 +.data:0812365D db 1 +.data:0812365E db 4 +.data:0812365F db 2 +.data:08123660 db 4 +.data:08123661 db 4 +.data:08123662 db 4 + +.data:08123663 db 4 +.data:08123664 db 4 +.data:08123665 db 2 +.data:08123666 db 4 +.data:08123667 db 4 +.data:08123668 db 8 +.data:08123669 db 4 +.data:0812366A db 4 + +.data:0812366B db 4 +.data:0812366C db 4 +.data:0812366D db 8 +.data:0812366E db 4 +.data:0812366F db 4 +.data:08123670 db 4 +.data:08123671 db 10h +.data:08123672 db 4 + +.data:08123673 db 4 +.data:08123674 db 1 +.data:08123675 db 4 +.data:08123676 db 2 +.data:08123677 db 4 +.data:08123678 db 10h +.data:08123679 db 4 +.data:0812367A db 8 + +.data:0812367B db 8 +.data:0812367C db 10h +.data:0812367D db 4 +.data:0812367E db 8 +.data:0812367F db 10h +.data:08123680 db 4 +.data:08123681 db 8 +.data:08123682 db 10h + +.data:08123683 db 4 +.data:08123684 db 4 +.data:08123685 db 8 +.data:08123686 db 10h +.data:08123687 db 8 +.data:08123688 db 10h +.data:08123689 db 4 +.data:0812368A db 4 + +.data:0812368B db 4 +.data:0812368C db 4 +.data:0812368D db 10h +.data:0812368E db 8 +.data:0812368F db 10h +.data:08123690 db 8 +.data:08123691 db 4 +.data:08123692 db 4 + +.data:08123693 db 1 +.data:08123694 db 2 +.data:08123695 db 4 +.data:08123696 db 8 +.data:08123697 db 2 +.data:08123698 db 1 +.data:08123699 db 4 +.data:0812369A db 10h + +.data:0812369B db 8 +.data:0812369C db 10h +.data:0812369D db 4 +.data:0812369E db 1 +.data:0812369F db 2 +.data:081236A0 db 4 +.data:081236A1 db 8 +.data:081236A2 db 10h + +.data:081236A3 db 8 +.data:081236A4 db 10h +.data:081236A5 db 2 +.data:081236A6 db 4 +.data:081236A7 db 1 +.data:081236A8 db 4 +.data:081236A9 db 2 +.data:081236AA db 1 +--- remix double +.data:081236AB db 1 +.data:081236AC db 8 +.data:081236AD db 1 +.data:081236AE db 4 +.data:081236AF db 10h +.data:081236B0 db 2 +.data:081236B1 db 10h +.data:081236B2 db 4 +---- crazy mode +.data:081236B3 db 4 +.data:081236B4 db 1 +.data:081236B5 db 4 +.data:081236B6 db 2 +.data:081236B7 db 4 +.data:081236B8 db 1 +.data:081236B9 db 4 +.data:081236BA db 2 +--- all select code? doesn't make sense and doesn't seem to work, probably unused +.data:081236BB db 10h +.data:081236BC db 4 +.data:081236BD db 1 +.data:081236BE db 10h +.data:081236BF db 8 +.data:081236C0 db 4 +.data:081236C1 data_12af0a db 2 ; DATA XREF: func_2c380+139r +.data:081236C1 ; func_2c51c+559r +.data:081236C2 db 8 +``` \ No newline at end of file diff --git a/doc/development/notes/pro-notes.md b/doc/development/notes/pro-notes.md new file mode 100644 index 0000000..d326fad --- /dev/null +++ b/doc/development/notes/pro-notes.md @@ -0,0 +1,9 @@ +# Notes about working with Pump It Up Pro +## Enable game's log output in arcade mode +Go to `data0.zip/Data/Static/ini` and change the following lines under the category `[Options-arcade]`: +```text +LogToDisk=1 +ShowLogOutput=1 +``` + +The game logs all log levels including trace level. \ No newline at end of file diff --git a/doc/game/01-1st.md b/doc/game/01-1st.md new file mode 100644 index 0000000..dc4d4f5 --- /dev/null +++ b/doc/game/01-1st.md @@ -0,0 +1,134 @@ +# Pump it Up: The 1st Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: September 1999 +* Korea exclusive +* First game in the series +* 15 songs +* 4 non-stop remixes +* 4 battle songs + +## Game modes +* Easy +* Hard +* Double +* Non-stop remix +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. + +## Unlock and modifier codes +All codes must be entered in the song select menu (after selecting easy, hard, double, non-stop remix or battle). When +a code was entered successfully, a sound effect is played. For some modifiers, an icon is also displayed on left side +of the screen for player 1 and on the right side of the screen for player 2. + +### Velocity 2x +Arrow scroll speed 2x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Velocity 4x +Arrow scroll speed 4x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Velocity 8x +Arrow scroll speed 8x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Synchro +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Couple +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) \ No newline at end of file diff --git a/doc/game/02-2nd.md b/doc/game/02-2nd.md new file mode 100644 index 0000000..33dcd8a --- /dev/null +++ b/doc/game/02-2nd.md @@ -0,0 +1,188 @@ +# Pump it Up: The 2nd Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: December 1999 +* Korea exclusive +* Only 4 songs and one remix taken over from previous version +* 17 new songs +* 6 new non-stop remixes + +# Game modes +* Easy +* Hard +* Double +* Non-stop remix +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. +* Non-stop remix double: Needs to be unlocked [using a code](###Non-stop remix double) which needs to be entered on the +non-stop remix screen. + +When the second player joined by also pressing the center panel, battle modes is available on the mode select screen +instead of double. + +## Unlock and modifier codes +All codes must be entered in the song select menu (after selecting easy, hard, double, non-stop remix or battle) except +the [non-stop remix double code](###Non-stop remix double). When a code was entered successfully, a sound effect is +played. For some modifiers, an icon is also displayed on left side of the screen for player 1 and on the right side of +the screen for player 2. + +### Velocity 2x +Arrow scroll speed 2x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Velocity 4x +Arrow scroll speed 4x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Velocity 8x +Arrow scroll speed 8x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Synchro +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Couple +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Non-stop remix double +Must be entered on the non-step remix select screen. + +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Union +??? + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) + +### All Select +Enter this code on the select mode screen (easy, hard, double, non-stop). When entered correctly, the game transitions +automatically to a selection screen with all available songs. + +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) \ No newline at end of file diff --git a/doc/game/03-3rd.md b/doc/game/03-3rd.md new file mode 100644 index 0000000..3ac509b --- /dev/null +++ b/doc/game/03-3rd.md @@ -0,0 +1,182 @@ +# Pump it Up The O.B.G: The 3rd Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: May 2000 +* Korea exclusive +* 23 new songs but only 2 old songs taken over + +# Game modes +* Easy +* Hard +* Double +* Non-stop remix +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. + +When the second player joined by also pressing the center panel, battle modes is available on the mode select screen +instead of double. + +## Unlock and modifier codes +All codes must be entered in the song select menu (after selecting easy, hard, double, non-stop remix or battle). When a +code was entered successfully, a sound effect is played. For some modifiers, an icon is also displayed on left side of +the screen for player 1 and on the right side of the screen for player 2. + +### Velocity 2x +Arrow scroll speed 2x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Velocity 4x +Arrow scroll speed 4x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Velocity 8x +Arrow scroll speed 8x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Synchro +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Couple +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Union +??? + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) + +### Random Vanish +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Random Speed +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) \ No newline at end of file diff --git a/doc/game/04-3se.md b/doc/game/04-3se.md new file mode 100644 index 0000000..ac5d0e5 --- /dev/null +++ b/doc/game/04-3se.md @@ -0,0 +1,211 @@ +# Pump it Up The O.B.G: The Season Evolution Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: September 2000 +* Korea exclusive +* First version to feature the `Crazy` game mode +* 14 new songs, 1 new remix +* First game to introduce a non-unlockable songwheel + +# Game modes +* Easy +* Hard +* Crazy: Must be unlocked [using a code](#crazy-mode) +* Double +* Non-stop remix double: Needs to be unlocked [using a code](#non-stop-remix-double) which needs to be entered on the +non-stop remix screen. +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. + +When the second player joined by also pressing the center panel, battle modes is available on the mode select screen +instead of double. + +## Unlock and modifier codes +All codes must be entered in the song select menu (after selecting easy, hard, double, non-stop remix or battle) except +the [non-stop remix double code](#non-stop-remix-double). When a code was entered successfully, a sound effect is +played. For some modifiers, an icon is also displayed on left side of the screen for player 1 and on the right side of +the screen for player 2. + +### Velocity 2x +Arrow scroll speed 2x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Velocity 4x +Arrow scroll speed 4x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Velocity 8x +Arrow scroll speed 8x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Synchro +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Couple +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Union +??? + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) + +### Random Vanish +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Random Speed +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) + +### Non-stop remix double +Must be entered on the non-step remix select screen. + +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Crazy mode +Must be entered on the mode select screen. When entered successful, the "Non-stop remix" panel is replaced with "Crazy". + +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) \ No newline at end of file diff --git a/doc/game/05-tc.md b/doc/game/05-tc.md new file mode 100644 index 0000000..724ee79 --- /dev/null +++ b/doc/game/05-tc.md @@ -0,0 +1,211 @@ +# Pump it Up: The Collection +This document collects various information about the game itself. + +## Trivia +* Release: November 2000 +* Korea exclusive +* As the name implies, this game is a collection of all the songs of the past versions except for the song "Kiss" +* Same interface as O.B.G SE +* A total of 66 songs and 18 non-stop remixes + +# Game modes +* Easy +* Hard +* Crazy: Must be unlocked [using a code](#crazy-mode) +* Double +* Non-stop remix double: Needs to be unlocked [using a code](#non-stop-remix-double) which needs to be entered on the +non-stop remix screen. +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. + +When the second player joined by also pressing the center panel, battle modes is available on the mode select screen +instead of double. + +## Unlock and modifier codes +All codes must be entered in the song select menu (after selecting easy, hard, double, non-stop remix or battle) except +the [non-stop remix double code](#non-stop-remix-double). When a code was entered successfully, a sound effect is +played. For some modifiers, an icon is also displayed on left side of the screen for player 1 and on the right side of +the screen for player 2. + +### Velocity 2x +Arrow scroll speed 2x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Velocity 4x +Arrow scroll speed 4x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Velocity 8x +Arrow scroll speed 8x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Synchro +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Couple +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Union +??? + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) + +### Random Vanish +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Random Speed +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) + +### Non-stop remix double +Must be entered on the non-step remix select screen. + +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Crazy mode +Must be entered on the mode select screen. When entered successful, the "Non-stop remix" panel is replaced with "Crazy". + +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) \ No newline at end of file diff --git a/doc/game/06-pc.md b/doc/game/06-pc.md new file mode 100644 index 0000000..80e23cb --- /dev/null +++ b/doc/game/06-pc.md @@ -0,0 +1,199 @@ +# Pump it Up: The Perfect Collection +This document collects various information about the game itself. + +## Trivia +* Release: December 2000 +* Korea exclusive +* As the name implies, this game extends the previous version "The Collection" by adding 17 new songs +* Game mode select re-designed to allow selecting `Crazy` mode without a code. +* A total of 83 songs and 18 non-stop remixes + +# Game modes +* Easy +* Hard +* Crazy +* Double +* Non-stop remix double: Needs to be unlocked [using a code](#non-stop-remix-double) which needs to be entered on the +non-stop remix screen. +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. + +When the second player joined by also pressing the center panel, battle modes is available on the mode select screen +instead of double. + +## Unlock and modifier codes +All codes must be entered in the song select menu (after selecting easy, hard, double, non-stop remix or battle) except +the [non-stop remix double code](#non-stop-remix-double). When a code was entered successfully, a sound effect is +played. For some modifiers, an icon is also displayed on left side of the screen for player 1 and on the right side of +the screen for player 2. + +### Velocity 2x +Arrow scroll speed 2x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Velocity 4x +Arrow scroll speed 4x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Velocity 8x +Arrow scroll speed 8x the BPM. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Synchro +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Couple +??? + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Union +??? + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) + +### Random Vanish +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Random Speed +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) + +### Non-stop remix double +Must be entered on the non-step remix select screen. + +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) \ No newline at end of file diff --git a/doc/game/07-extra.md b/doc/game/07-extra.md new file mode 100644 index 0000000..8a421a3 --- /dev/null +++ b/doc/game/07-extra.md @@ -0,0 +1,183 @@ +# Pump it Up Extra +This document collects various information about the game itself. + +## Trivia +* Release: February 2001 +* Korea exclusive +* Crazy-mode was renamed to "Extra Expert" (XX) +* Introduction of hold/long arrows +* Double charts for Extra Expert and Non-stop Remix are available in the songwheel (no unlock code needed) +* New interface and pad navigation +* A game mode does not have to be selected prior selecting a song but can be switched on a per song basis +* This version of the game was produced by F2System, a partner of Andamiro. F2System also produced Techno Motion +* 58 songs and 9 non-stop remixes + +# Game modes +* Normal +* Hard +* Extra Expert +* Double +* Non-stop remix + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. When +a modifier was activated, an icon is displayed either on the left side of the screen for player 1 and on the right side +of the screen for player 2. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +### Velocity 2x, 4x, 8x +Arrow scroll speed 2x, 4x or 8x the BPM. The following code must be entered one time for 2x, two times for 4x, +three times for 8x. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Mirror +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Synchro +??? + +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + + +### Random step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Random Speed +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Random Vanish +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Rush +??? + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Noteskin select +Select a different noteskin. For the blank (second to last arrow), fill in one of the four directions to get a different +noteskin. There are two noteskin select codes. + +#### Code 1 +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/qm.png) +![](image/arrow/c.png) + +For blank:
+![](image/arrow/dl.png): Star and moon
+![](image/arrow/dr.png): UFO
+![](image/arrow/ul.png): Snowman
+![](image/arrow/ur.png): Restroom sign and poop (yep, poop)
+ +#### Code 2: +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/qm.png) +![](image/arrow/c.png) + +For blank:
+![](image/arrow/dl.png): Octopus and fish
+![](image/arrow/dr.png): Card
+![](image/arrow/ul.png): Pineapple
+![](image/arrow/ur.png): Disk jockey
\ No newline at end of file diff --git a/doc/game/08-prem1.md b/doc/game/08-prem1.md new file mode 100644 index 0000000..2116438 --- /dev/null +++ b/doc/game/08-prem1.md @@ -0,0 +1,113 @@ +# Pump it Up The Premiere: The International Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: June 2001 +* First official international release +* New interface +* 87 songs + +# Game modes +* Normal +* Hard +* Crazy +* Double +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. When +a modifier was activated, an icon is displayed either on the left side of the screen for player 1 and on the right side +of the screen for player 2. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) + +### Change arrow scroll speed +Using the two codes below, you cycle through the following scroll speeds: off (1x), 2x, 4x, 8x, random +Random changes the scroll speed during the song to any of the four speeds above. + +#### Next/increase scroll speed +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) + +#### Previous/decrease scroll speed +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) + +### Vanish +The steps fade out about halfway to the target. + +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Synchro +When choosing a single player game mode and a second player joins, this plays the same step chart on the other players +side. If this is not activated, no step chart is played for the second player. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Couple +When choosing a single player game mode and a second player joins, this plays the same step chart on the other players +side by mirrored along the "y axis". If this or [synchro](#synchro) is not activated, no step chart is played for the +second player. + +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Random Vanish +Steps are visible at the bottom of the screen, disappear midfield and re-appear before hitting the targets. + +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) \ No newline at end of file diff --git a/doc/game/09-prex1.md b/doc/game/09-prex1.md new file mode 100644 index 0000000..d3ece5f --- /dev/null +++ b/doc/game/09-prex1.md @@ -0,0 +1,112 @@ +# Pump it Up The PREX: The International Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: November 2001 +* PREX = The **PR**emiere and **EX**tra +* 87 songs + +# Game modes +* Normal +* Hard +* Crazy +* Double +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. When +a modifier was activated, an icon is displayed either on the left side of the screen for player 1 and on the right side +of the screen for player 2. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) + +### Change arrow scroll speed +Using the two codes below, you cycle through the following scroll speeds: off (1x), 2x, 4x, 8x, random +Random changes the scroll speed during the song to any of the four speeds above. + +#### Next/increase scroll speed +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) + +#### Previous/decrease scroll speed +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) + +### Vanish +The steps fade out about halfway to the target. + +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Synchro +When choosing a single player game mode and a second player joins, this plays the same step chart on the other players +side. If this is not activated, no step chart is played for the second player. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Couple +When choosing a single player game mode and a second player joins, this plays the same step chart on the other players +side by mirrored along the "y axis". If this or [synchro](#synchro) is not activated, no step chart is played for the +second player. + +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Random Vanish +Steps are visible at the bottom of the screen, disappear midfield and re-appear before hitting the targets. + +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) \ No newline at end of file diff --git a/doc/game/10-reb.md b/doc/game/10-reb.md new file mode 100644 index 0000000..b2901fd --- /dev/null +++ b/doc/game/10-reb.md @@ -0,0 +1,92 @@ +# Pump it Up The Rebirth: The 8th Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: January 2002 +* Korea exclusive version +* 87 songs + +# Game modes +* Normal +* Hard +* Crazy +* Double +* Half-Double +* Division +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. When +a modifier was activated, an icon is displayed either on the left side of the screen for player 1 and on the right side +of the screen for player 2. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +#### Change arrow scroll speed: Next/increase scroll speed +Using this code to cycle through the following scroll speeds: off (1x), 2x, 3x, 4x. + +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) + +#### Random arrow scroll speed +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) + +#### Change difficulty +Allows you to change the difficulty on the song select screen. + +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) + +### Vanish +The steps fade out about halfway to the target. + +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) \ No newline at end of file diff --git a/doc/game/11-prem2.md b/doc/game/11-prem2.md new file mode 100644 index 0000000..251cae7 --- /dev/null +++ b/doc/game/11-prem2.md @@ -0,0 +1,92 @@ +# Pump it Up The Premiere 2: The International 2nd Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: March 2002 +* International version of Rebirth +* 102 songs + +# Game modes +* Normal +* Hard +* Crazy +* Double +* Half-Double +* Division +* Battle: When player 2 joins in the mode select screen, battle mode becomes visible by replacing double mode. + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. When +a modifier was activated, an icon is displayed either on the left side of the screen for player 1 and on the right side +of the screen for player 2. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +#### Change arrow scroll speed: Next/increase scroll speed +Using this code to cycle through the following scroll speeds: off (1x), 2x, 3x, 4x. + +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) + +#### Random arrow scroll speed +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) + +#### Change difficulty +Allows you to change the difficulty on the song select screen. + +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) + +### Vanish +The steps fade out about halfway to the target. + +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) + +### Mirror +All steps are mirrored along the "x axis", i.e. arrows
+![](image/arrow/ul.png) <-> ![](image/arrow/ur.png) +
and
+![](image/arrow/dl.png) <-> ![](image/arrow/dr.png) +
are switched. + +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) + +### Random step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) \ No newline at end of file diff --git a/doc/game/12-prex2.md b/doc/game/12-prex2.md new file mode 100644 index 0000000..ca92ffb --- /dev/null +++ b/doc/game/12-prex2.md @@ -0,0 +1,98 @@ +# Pump it Up The PREX 2 +This document collects various information about the game itself. + +## Trivia +* Release: November 2002 +* 93 songs + +# Game modes/difficulties +* Normal +* Hard +* Crazy +* Double +* Battle: Can be selected once player 2 joined the game + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. For +some modifiers, an icon is also displayed on left side of the screen for player 1 and on the right side of the screen +for player 2. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +### Velocity 2x, 3x, 4x +Arrow scroll speed 2x, 3x or 4x or 8x the BPM. The following code must be entered one time for 2x, two times for 3x and +three times for 4x. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Mirror +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random Step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Random Velocity +The arrow scroll speed changes randomly during the song. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) \ No newline at end of file diff --git a/doc/game/13-prem3.md b/doc/game/13-prem3.md new file mode 100644 index 0000000..0ea0278 --- /dev/null +++ b/doc/game/13-prem3.md @@ -0,0 +1,99 @@ +# Pump it Up The Premiere 3: The International 3rd Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: May 2003 +* 56 songs + +# Game modes/difficulties +* Normal +* Hard +* Crazy +* Double +* Half-Double +* Battle: Can be selected once player 2 joined the game + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. For +some modifiers, an icon is also displayed on left side of the screen for player 1 and on the right side of the screen +for player 2. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +### Velocity 2x, 3x, 4x +Arrow scroll speed 2x, 3x or 4x or 8x the BPM. The following code must be entered one time for 2x, two times for 3x and +three times for 4x. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Mirror +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random Step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Random Velocity +The arrow scroll speed changes randomly during the song. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) \ No newline at end of file diff --git a/doc/game/14-prex3.md b/doc/game/14-prex3.md new file mode 100644 index 0000000..27235a9 --- /dev/null +++ b/doc/game/14-prex3.md @@ -0,0 +1,100 @@ +# Pump it Up The PREX 3: The International 4th Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: October 2003 +* 77 songs + +# Game modes/difficulties +* Normal +* Hard +* Crazy +* Freestyle +* Nightmare +* Half-Double +* Battle: Can be selected once player 2 joined the game + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. For +some modifiers, an icon is also displayed on left side of the screen for player 1 and on the right side of the screen +for player 2. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +### Velocity 2x, 3x, 4x +Arrow scroll speed 2x, 3x or 4x or 8x the BPM. The following code must be entered one time for 2x, two times for 3x and +three times for 4x. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Mirror +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random Step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Random Velocity +The arrow scroll speed changes randomly during the song. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) \ No newline at end of file diff --git a/doc/game/15-exc.md b/doc/game/15-exc.md new file mode 100644 index 0000000..0231388 --- /dev/null +++ b/doc/game/15-exc.md @@ -0,0 +1,145 @@ +# Pump it Up Exceed: The International 5th Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: April 2004 +* First game to run on Linux and MK5 hardware +* 24 new songs +* 102 songs total +* Songs are separated into channels: Banya, Pop and K-Pop +* Three hidden songs that have to be [unlocked using a code](#hidden-songs-unlock) + +# Game modes/difficulties +* Normal +* Hard +* Crazy +* Freestyle +* Nightmare +* Battle: Can be selected once player 2 joined the game + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. For +some modifiers, an icon is also displayed on left side of the screen for player 1 and on the right side of the screen +for player 2. + +### Hidden songs unlock +This code unlocks the following songs: +* Monkey Fingers +* Oh Rosa (same song/steps on the select screen but music switchd to spanish version) +* First Love (same song/steps on the select screen but music switchd to spanish version) + +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +### Velocity 2x, 3x, 4x +Arrow scroll speed 2x, 3x, 4x or 8x the BPM. The following code must be entered one time for 2x, two times for 3x, +three times for 4x and four times for 8x. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Mirror +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random Step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Random Velocity +The arrow scroll speed changes randomly during the song. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Earthworm +Makes the arrows wave back and fourth as they scroll to the top. + +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Exceed Mode +Arrows travel diagonally towards the top. + +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) \ No newline at end of file diff --git a/doc/game/16-exc2.md b/doc/game/16-exc2.md new file mode 100644 index 0000000..3c62372 --- /dev/null +++ b/doc/game/16-exc2.md @@ -0,0 +1,295 @@ +# Pump it Up Exceed 2: The International 6th Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: November 2004 +* First game to run on Linux, MK6 hardware and the USB PIUIO +* 18 new songs +* 136 songs total +* Based on Exceed's interface + +# Game modes/difficulties +* Normal +* Hard +* Crazy +* Freestyle +* Nightmare +* Battle: Can be selected once player 2 joined the game +* Remix Station + +## Game play unlocks +The following songs are unlockable by fulfilling the described conditions. + +### Arcade station: Canon-D, nightmare +Naturally, this only works as a single player. This unlock is not permanent. + +1. Select arcade station +1. Play any two songs and get an A ranking on both +1. Play "I'll Give You All My Love" on crazy difficulty as your 3rd song and get an A ranking with less than 10 misses +1. You will receive a bonus stage and the nightmare difficulty on Canon-D is unlocked + +### Remix station: 2nd Hidden Remix, nightmare +Naturally, this only works as a single player. This unlock is only temporary. + +1. Select remix station +1. Play "Diva Remix" on freestyle difficult and get an S ranking +1. The nightmare difficulty on "2nd Hidden Remix" is unlocked + +### Remix station: Raw, nightmare +Raw (as it's known) is the same song (and background movie) as the opening movie of Exceed 2. Auto-fail (Stage Break) +is not on during this song, and the game will not stop if you get a 50+ miss combo. This song is not a selectable song +(ever), and you cannot choose your modifiers or difficulties before this song, as the game will bleed into it after your +last remix song and will start automatically after giving you a greeting screen for the song. It is set on double mode. + +This unlock is not permanent. + +1. Select remix station +1. Play any remix songs and get an S ranking on all of them +1. The game automatically starts the song "Raw" as your last song + +### Remix station: Banya Classic Remix, nightmare +Naturally, this only works as a single player. This unlock is only temporary. + +1. Select remix station +1. Play "Deux Remix" on fresstyle and get an S ranking +1. The nightmare difficulty on "Banya Classic Remix" is unlocked + +### Remix station: Dignity Remix, crazy +Naturally, this only works as a single player. This unlock is only temporary. + +1. Select remix station +1. Play "Banya Classic Remix" on hard and get an S ranking with a full combo +1. The crazy difficulty on "Dignity Remix" is unlocked + +### Remix station: Treme Vook of the War Another +Naturally, this only works as a single player. This unlock is only temporary. + +1. Select remix station +1. Play "Treme Vook of the War" on crazy and get an A ranking +1. The songs "Treme Vook of the War Another" is unlocked + +### Remix station: Turbo Remix, nightmare +Naturally, this only works as a single player. This unlock is only temporary. + +1. Select remix station +1. Play "Diva Remix" on crazy and get an S ranking, without any goods or bads +1. The nightmare difficulty on "Turbo Remix" is unlocked + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. For +some modifiers, an icon is also displayed on left side of the screen for player 1 and on the right side of the screen +for player 2. + +### Unlock: Solitary 2 +This code unlocks the song "Solitary 2" and must be entered in the song select menu. When entered correctly, the song +appears immediately. This unlock is only temporary. + +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Unlock: Canon-D Full Mix +This code unlocks the song "Canon-D Full Mix" and must be entered on the select screen of the **Remix Station**. When +entered correctly. This unlock is permanent. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) + +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +### Velocity 2x, 3x, 4x, 8x +Arrow scroll speed 2x, 3x, 4x or 8x the BPM. The following code must be entered one time for 2x, two times for 3x, +three times for 4x and four times for 8x. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Mirror +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random Step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Random Velocity +The arrow scroll speed changes randomly during the song. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Earthworm +Makes the arrows wave back and fourth as they scroll to the top. + +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Exceed Mode +Arrows travel diagonally towards the top. + +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Freedom +Stationary arrows at the top are invisible. + +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Acceleration +Arrows start scrolling slowly at the bottom of the screen and accelerate when scrolling towards the top stationary +arrows. + +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Deceleration +Arrows start scrolling fast at the bottom of the screen and decelerate when scrolling towards the top stationary +arrows. + +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Extra mix noteskin +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Flower card noteskin +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) \ No newline at end of file diff --git a/doc/game/17-zero.md b/doc/game/17-zero.md new file mode 100644 index 0000000..c4b2f05 --- /dev/null +++ b/doc/game/17-zero.md @@ -0,0 +1,312 @@ +# Pump it Up Zero: International 7th Dance Floor +This document collects various information about the game itself. + +## Trivia +* Release: February 2006 +* Entirely new interface. First mix to feature BG video preview playback on the song select screen. Furthermore, when +playing with a second player, each player can select a different difficulty level. +* 130 songs total + +# Game modes +* Arcade Station: Separated into 4 channels. Each channel contains songs with chart difficulties Normal, Hard, Crazy, Freestyle, +Nightmare + * Banya + * K-Pop + * Pop + * Another: Many songs/charts of this channel must be unlocked, first. +* Easy Station +* Remix Station +* Mission Station: Clear missions to unlock content + +## Game play unlocks +The game has an internal point system. Points are earned by simply playing the game, passing songs etc. A lot of +content can be unlocked using this point system. However, certain content is bound to specific missions and cannot be +unlocked by earning points. + +### Point unlocks +These are either unlocked by just playing the game or once a certain mission becomes available. + +* Eres Para Mi - Another Freestyle - unlocked upon unlock of Another station +* Final Audition 2 - Another Crazy - unlocked upon unlock of Another station +* Final Audition 3 U.F - Another Nightmare - unlocked upon unlock of Another station +* Mexi Mexi - Another Nightmare - unlocked upon unlock of Another station +* My Friend - Another Freestyle - unlocked upon unlock of Another station +* Solitary 2 - Another Crazy - unlocked upon unlock of Another station +* Extravaganza - Another Crazy - time/point-release unlock - after opening of mission 7 +* Final Audition 2 - Another Nightmare - time/point-release unlock - after opening of mission 11 +* Love is a Danger Zone 2 - Another Hard - time/point-release unlock - after opening of mission 13 +* Love is a Danger Zone 2 - Another Crazy - time/point-release unlock - after opening of mission 13 +* Love is a Danger Zone 2 - Another Nightmare - time/point-release unlock - after opening of mission 13 +* Naissance 2 - Another Nightmare - time/point-release unlock - after opening of mission 15 +* Final Audition - Another Nightmare - time/point-release unlock - after opening of mission 17 +* Do it Yourself - Another Nightmare - time/point-release unlock - after opening of mission 19 +* Solitary 2 - Another Nightmare - time/point-release unlock - after opening of mission 21 +* Mr. Larpus - Another Nightmare - time/point-release unlock - after opening of mission 23 +* Extravaganza - Another Nightmare - time/point-release unlock - after opening of mission 25 +* Another Truth - Another Nightmare - time/point-release unlock - after opening of mission 27 +* Witch Doctor - Another Nightmare - time/point-release unlock - after opening of mission 29 + +### Mission station unlocks +The following songs/step charts can only be unlocked by unlocking specific missions. + +* [New arrow skin](#new-arrow-skin) - clear mission 9 +* [Music note skin](#musical-note-skin) - clear mission 10 +* [Extra mix skin](#extra-mix-skin) - clear mission 11 +* [Grade reverse modifier](#grade-reverse) - clear mission 12 +* Love is a Danger Zone - Another Freestyle - mission unlock (mission 16) +* Love is a Danger Zone - Another Nightmare - mission unlock (mission 16) +* Witch Doctor - Another Crazy - mission unlock (mission 17) +* [Canon-D eye skin](#canon-d-eye-skin) - clear mission 18 +* Final Audition - Another Crazy - mission unlock (mission 19) +* Join The Party - Another Nightmare - mission unlock (mission 20) +* [Flower card skin](#flower-card-skin) - clear mission 24 +* Circus Magic - Another Nightmare - mission unlock (mission 26), note: only mode playable for this song in the entire +game +* Vook - Another Freestyle - mission unlock (mission 27) +* Vook - Another Nightmare - mission unlock (mission 27) +* I'll Give You All My Love - Another Nightmare - mission unlock (mission 28) + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. An +icon on the left/right side of the screen indicates if the modifier is active for player 1/2. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +### Velocity 2x, 3x, 4x, 8x +Arrow scroll speed 2x, 3x, 4x or 8x the BPM. The following code must be entered one time for 2x, two times for 3x, +three times for 4x and four times for 8x. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Mirror +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random Step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Random Velocity +The arrow scroll speed changes randomly during the song. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Earthworm +Makes the arrows wave back and fourth as they scroll to the top. + +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Exceed Mode +Arrows travel diagonally towards the top. + +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Freedom +Stationary arrows at the top are invisible. + +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Acceleration +Arrows start scrolling slowly at the bottom of the screen and accelerate when scrolling towards the top stationary +arrows. + +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Deceleration +Arrows start scrolling fast at the bottom of the screen and decelerate when scrolling towards the top stationary +arrows. + +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Battle mode +In arcade station, player 2 must have joined that the game shows "1P + 2P mode" at the top left. Entering the code +below actives battle mode and shows "1P VS. 2P mode" at the top left. + +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Grade reverse +Shows inverse judgements during game play, i.e. perfect = miss, great = bad + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### New arrow skin +Requirement: Clear mission 9 + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) + +### Musical note skin +Requirement: Clear mission 10 + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) + +### Extra mix skin +Requirement: Clear mission 11 + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Canon-D eye skin +Requirement: Clear mission 18 + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) + +### Flower card skin +Requirement: Clear mission 24 + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) \ No newline at end of file diff --git a/doc/game/18-nx.md b/doc/game/18-nx.md new file mode 100644 index 0000000..7452e33 --- /dev/null +++ b/doc/game/18-nx.md @@ -0,0 +1,281 @@ +# Pump it Up NX: New Xenesis +This document collects various information about the game itself. + +## Trivia +* Release: December 2006 + +# Game modes +* Arcade Station: Separated into 3 channels. Each channel contains songs with chart difficulties Normal, Hard, Crazy, +Freestyle, Nightmare + * New Tunes + * Banya + * K-Pop + * Pop +* Training Station +* Special Station: Full versions, remixes and another step (charts) +* World Tour: Clear missions to unlock further locked missions and content in Special Station + +## Game play unlocks +The song *Final Audition Episode 2-2* is the only locked song in the whole game. This song can be unlocked by playing +the song *Final Audition Episode 2-1* 100 times. You do not have to pass the song, failing the song also counts +towards this play count. + +Clearing missions in World Tour unlocks further missions and content in Special Zone. However, all locked content of +World Tour and Special Station is also unlocked when the total play count on the machine reaches about 20000 plays. + +## Unlock and modifier codes +All codes must be entered in the song select menu. When a code was entered successfully, a sound effect is played. An +icon on the left/right side of the screen indicates if the modifier is active for player 1/2. These codes do not work +on World Tour (Station). + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +### Velocity 2x, 3x, 4x, 8x +Arrow scroll speed 2x, 3x, 4x or 8x the BPM. The following code must be entered one time for 2x, two times for 3x, +three times for 4x and four times for 8x. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Mirror +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random Step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Random Velocity +The arrow scroll speed changes randomly during the song. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Earthworm +Makes the arrows wave back and fourth as they scroll to the top. + +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Exceed Mode +Arrows travel diagonally towards the top. + +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Freedom +Stationary arrows at the top are invisible. + +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Acceleration +Arrows start scrolling slowly at the bottom of the screen and accelerate when scrolling towards the top stationary +arrows. + +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Deceleration +Arrows start scrolling fast at the bottom of the screen and decelerate when scrolling towards the top stationary +arrows. + +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Battle mode +In arcade station, player 2 must have joined that the game shows "1P + 2P mode" at the top left. Entering the code +below actives battle mode and shows "1P VS. 2P mode" at the top left. + +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Under attack +Point mirror the target (grey) area with the center of the screen as the origin. This results in the P1 target area +positioned upside down in bottom right corner and the P2 target area positioned upside down in the bottom left corner. + +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Flash +Arrows are disappearing and appearing in short intervals ("flashing") when moving towards the top. + +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### NX mode +Tilts the perspective of the arrows scrolling towards the targets from an "overhead" perspective to a "distant" +perspective. + +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) + +### Grade reverse +Shows inverse judgements during game play, i.e. perfect = miss, great = bad + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + + +### Extra mix skin +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Canon-D eye skin +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) + +### Flower card skin +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) \ No newline at end of file diff --git a/doc/game/20-nx2.md b/doc/game/20-nx2.md new file mode 100644 index 0000000..a6fcf7a --- /dev/null +++ b/doc/game/20-nx2.md @@ -0,0 +1,295 @@ +# Pump it Up NX2: Next Xenesis +This document collects various information about the game itself. + +## Trivia +* Release: October 2007 +* First version to allow storing player scores and unlock state on a usb thumb drive. The original game only allows +special brand of usb thumb drives. Other drives are not accepted by the game. Profiles were managed and uploaded by +the player using a proprietary software called "PUMBI" which is no longer supported. + +# Game modes +* Arcade Station: Separated into 3 channels. Each channel contains songs with chart difficulties Normal, Hard, Crazy, +Freestyle, Nightmare + * New Tunes + * Banya + * K-Pop + * Pop +* Training Station +* Special Station: Full versions, remixes and another step (charts) +* World Max: Clear missions to unlock further locked missions and content in Special Station + +## World max guide and unlocks +Either take a look at the `missions.txt` file in the `game` data folder or use [Turkeyslam's guide](guide/nx2.pdf) which +was published on ph-online back in the days. + +### Cancel +Clear all modifiers previously enabled. + +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) + +### Increase arrow scroll speed +Arrow scroll speed 2x, 3x, 4x or 8x the BPM. The following code must be entered one time for 2x, two times for 3x, +three times for 4x and four times for 8x. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Decrease arrow scroll speed + +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Vanish +The arrows vanish before reaching the target at the top. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Nonstep +Hide all steps. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Mirror +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Random Step +Randomizes the step chart. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) + +### Random Velocity +The arrow scroll speed changes randomly during the song. + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Earthworm +Makes the arrows wave back and fourth as they scroll to the top. + +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Exceed Mode +Arrows travel diagonally towards the top. + +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Freedom +Stationary arrows at the top are invisible. + +![](image/arrow/ul.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Acceleration +Arrows start scrolling slowly at the bottom of the screen and accelerate when scrolling towards the top stationary +arrows. + +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Deceleration +Arrows start scrolling fast at the bottom of the screen and decelerate when scrolling towards the top stationary +arrows. + +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + +### Under attack +Point mirror the target (grey) area with the center of the screen as the origin. This results in the P1 target area +positioned upside down in bottom right corner and the P2 target area positioned upside down in the bottom left corner. + +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### Flash +Arrows are disappearing and appearing in short intervals ("flashing") when moving towards the top. + +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) + +### NX mode +Tilts the perspective of the arrows scrolling towards the targets from an "overhead" perspective to a "distant" +perspective. + +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) + +### Grade reverse +Shows inverse judgements during game play, i.e. perfect = miss, great = bad + +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/ur.png) +![](image/arrow/ul.png) +![](image/arrow/c.png) + + +### Extra mix skin +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) + +### Canon-D eye skin +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/c.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) + +### Flower card skin +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) + +### Playing card skin +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/ur.png) + +### Random skin +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ul.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/ur.png) +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/dl.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/dr.png) +![](image/arrow/c.png) \ No newline at end of file diff --git a/doc/game/game.md b/doc/game/game.md new file mode 100644 index 0000000..e5f0055 --- /dev/null +++ b/doc/game/game.md @@ -0,0 +1,23 @@ +# Game Documentations +This kind of documentation provides various information about the game itself like some trivia, available features like +game modes, unlock and modifier codes etc. + +* [The 1st Dance Floor](01-1st.md) +* [The 2nd Dance Floor](02-2nd.md) +* [The O.B.G: The 3rd Dance Floor](03-3rd.md) +* [The O.B.G: The Season Evolution Dance Floor](04-3se.md) +* [The Collection](05-tc.md) +* [The Perfect Collection](06-pc.md) +* [Extra](07-extra.md) +* [The Premiere: The International Dance Floor](08-prem1.md) +* [The PREX: The International Dance Floor](09-prex1.md) +* [The Rebirth: The 8th Dance Floor](10-reb.md) +* [The Premiere 2: The International 2nd Dance Floor](11-prem2.md) +* [The PREX 2](12-prex2.md) +* [The Premiere 3: The International 3rd Dance Floor](13-prem3.md) +* [The PREX 3: The International 4th Dance Floor](14-prex3.md) +* [Exceed: The International 5th Dance Floor](15-exc.md) +* [Exceed 2: The International 6th Dance Floor](16-exc2.md) +* [Pump it Up Zero: International 7th Dance Floor](17-zero.md) +* [Pump it Up NX: New Xenesis](18-nx.md) +* [Pump it Up NX2: Next Xenesis](20-nx2.md) \ No newline at end of file diff --git a/doc/game/guide/nx2.pdf b/doc/game/guide/nx2.pdf new file mode 100644 index 0000000..9e7a28d Binary files /dev/null and b/doc/game/guide/nx2.pdf differ diff --git a/doc/game/image/arrow/c.png b/doc/game/image/arrow/c.png new file mode 100644 index 0000000..d74a397 Binary files /dev/null and b/doc/game/image/arrow/c.png differ diff --git a/doc/game/image/arrow/dl.png b/doc/game/image/arrow/dl.png new file mode 100644 index 0000000..c46632d Binary files /dev/null and b/doc/game/image/arrow/dl.png differ diff --git a/doc/game/image/arrow/dr.png b/doc/game/image/arrow/dr.png new file mode 100644 index 0000000..dd7fb86 Binary files /dev/null and b/doc/game/image/arrow/dr.png differ diff --git a/doc/game/image/arrow/qm.png b/doc/game/image/arrow/qm.png new file mode 100644 index 0000000..0bcf7d9 Binary files /dev/null and b/doc/game/image/arrow/qm.png differ diff --git a/doc/game/image/arrow/ul.png b/doc/game/image/arrow/ul.png new file mode 100644 index 0000000..6dec94a Binary files /dev/null and b/doc/game/image/arrow/ul.png differ diff --git a/doc/game/image/arrow/ur.png b/doc/game/image/arrow/ur.png new file mode 100644 index 0000000..408e8d8 Binary files /dev/null and b/doc/game/image/arrow/ur.png differ diff --git a/doc/game_old/(NXA)World_Max_Guide.pdf b/doc/game_old/(NXA)World_Max_Guide.pdf new file mode 100644 index 0000000..b4e8539 Binary files /dev/null and b/doc/game_old/(NXA)World_Max_Guide.pdf differ diff --git a/doc/game_old/NX Absolute Guide v1.0.xls b/doc/game_old/NX Absolute Guide v1.0.xls new file mode 100644 index 0000000..f570767 Binary files /dev/null and b/doc/game_old/NX Absolute Guide v1.0.xls differ diff --git a/doc/game_old/piu new codes.pdf b/doc/game_old/piu new codes.pdf new file mode 100644 index 0000000..c47d955 Binary files /dev/null and b/doc/game_old/piu new codes.pdf differ diff --git a/doc/game_old/piu old coes.pdf b/doc/game_old/piu old coes.pdf new file mode 100644 index 0000000..b0b56e8 Binary files /dev/null and b/doc/game_old/piu old coes.pdf differ diff --git a/doc/hook/exchook.md b/doc/hook/exchook.md new file mode 100644 index 0000000..f680a60 --- /dev/null +++ b/doc/hook/exchook.md @@ -0,0 +1,54 @@ +# exchook: Exceed +This readme covers any matters that are relevant for this hook, only. Anything that applies to **all** hooks is covered +in a [main hook readme file](../hook.md) including general data setup and a quick start guide. + +## Additional notable features +* Full MK5IO emulation with API hook: Keyboard, MK6 PIUIO or your own custom IO +* Fixed infamous hold glitch, i.e. make the damn game playable, finally + +## Versions supported +* 20040325 +* 20040408 + +Any other version won't work, period. Pumptools has to memory patch various things like I/O because hooking them is not +possible (at least not right now). + +## Dependencies +Make sure to read the different methods of dependency resolution available in the [main hook readme file](../hook.md), +first. + +The following **direct** dependencies (cmd: `readelf -d piu`) are required: +* libGL.so.1 +* libGLU.so.1 +* libpthread.so.0 +* libasound.so.2 +* libz.so.1 +* libpng12.so.0 +* libc.so.6 +* libm.so.6 +* libX11.so.6 +* libstdc++.so.5 + +Additionally, when using `piuio.so`, you need the following library as well: +* libusb-1.0.so.0 + +As for method 1, when using Ubuntu, the dependencies can be found in the following packages: +* libc-bin (or gcc-multilib on a 64-bit platform) +* libusb-1.0-0 +* libx11-6 +* zlib1g +* libasound2 + +## Data setup +In additional to the [general information applying to **all** hooks](../hook.md#data-setup), this game requires all +files and folders in the `game` folder to be in **UPPERCASE** on a case-sensitive file system. + +## Troubleshooting and FAQ +Make sure to also check the +[troubleshooting and FAQ section of the main hook readme](../hook.md#troubleshooting-and-faq). This covers various +things that apply to **all** hooks. The following sub-sections apply mainly to this hook. + +### The game crashes when using the sound device hw:0 +The game's sound manager relies on hardware mixing using the same device which is not supported by newer versions of +alsa anymore. Instead, you have to use software mixing using the `dmix` device, +[see here](#the-game's-music-plays-too-fast-or-sounds-weird). \ No newline at end of file diff --git a/doc/hook/f2hook.md b/doc/hook/f2hook.md new file mode 100644 index 0000000..a526997 --- /dev/null +++ b/doc/hook/f2hook.md @@ -0,0 +1,96 @@ +# Notable features + +* Runs on recent kernel versions thanks to various fixes +* Runs on 32-bit and 64-bit distros (64-bit distros require additional 32-bit libs to be installed) +* Full dongle emulation +* Remove HDD checks to run this on "non legit" drives +* Full MK6IO emulation with API hook: Keyboard or your own custom IO +* Real IO passthrough (for MK6 usb io) + +# Versions supported +All known versions supported. + +# Data setup +You are expected to get a clean set of data from a prestine drive. Ensure that +the game version matches one of the supported versions listed. + +You need two main folders: +* data +* settings + +## Data folder +Contents of the folder: +* game: The binaries of the custom "AMFS". File names must be lower case. +* lib: Put any libraries (especially older versions of libraries that can't be +installed anymore using the package manager) the game uses and aren't installed +on your system in here. +* piu: The piu executable + +## Settings folder +The contents of the folder are auto generated if the files don't exist. +Otherwise, this folder contains: +* PIUFESTA2.INI +* RANK.DATA + +## Executable dependencies +All dependencies must be compiled as 32-bit binaries. Here is a list of +dependencies (with versions) required to run the game: +* libGL.so.1 +* libGLU.so.1 +* libasound.so.2 +* libusb-0.1.so.4 +* libmad.so.0 +* libmpeg2.so.0 +* libmpeg2convert.so.0 +* libftgl.so.2 +* libstdc++.so.6 +* libgcc_s.so.1 +* libc.so.6 +* libm.so.6 +* libX11.so.6 +* libpthread.so.0 +* libdl.so.2 +* librt.so.1 +* libnvidia-tls.so.340.96 +* libnvidia-glcore.so.340.96 +* libXext.so.6 +* libusb-1.0.so.0 +* libfreetype.so.6 +* libxcb.so.1 +* libudev.so.1 +* libz.so.1 +* libbz2.so.1.0 +* libpng16.so.16 +* libharfbuzz.so.0 +* libXau.so.6 +* libXdmcp.so.6 +* libcap.so.2 +* libresolv.so.2 +* libglib-2.0.so.0 +* libgraphite2.so.3 +* libpcre.so.1 + +# Hook module configuration file +Checkout the usage information of the hook and set the option values according +to your needs. Here is an example option configuration file: +``` +log_file_path=/tmp/pumptools.log +log_level=3 +enable_file_monitor=0 +enable_io_monitor=0 +piuio_emu_lib_path=/pumptools/lib/piuio-emu.so +piuio_exit_test_service=1 +game_settings_path=/save/f2 +sound_device=hw:0 +keyboard_dev=/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse +halt_on_segv=0 +``` + +# Run the game +Ensure you are running an X screen. Otherwise, you have to start one along with the game. Various library/system-calls +require root privileges. Make sure to run the game as root or with sudo (otherwise you get various sorts of errors, +typically permission denied). Use the included *run.sh* file to start the game on a desktop environment. + +# Further notes +## Vsync +The game is required to run with vsync on. \ No newline at end of file diff --git a/doc/hook/fexhook.md b/doc/hook/fexhook.md new file mode 100644 index 0000000..95139e1 --- /dev/null +++ b/doc/hook/fexhook.md @@ -0,0 +1,88 @@ +# Notable features + +* Runs on recent kernel versions thanks to various fixes +* Runs on 32-bit and 64-bit distros (64-bit distros require additional 32-bit libs to be installed) +* Full dongle emulation +* Remove HDD checks to run this on "non legit" drives +* Full MK6IO emulation with API hook: Keyboard or your own custom IO +* Real IO passthrough (for MK6 usb io) + +# Versions supported +All known versions supported. + +# Data setup +You are expected to get a clean set of data from a prestine drive. Ensure that +the game version matches one of the supported versions listed. + +You need two main folders: +* data +* settings + +## Data folder +Contents of the folder: +* game: The binaries of the custom "Fiesta FS". These are stored in raw areas +of the HDD and have to be extraced. File names must be lower case. +* lib: Put any libraries (especially older versions of libraries that can't be +installed anymore using the package manager) the game uses and aren't installed +on your system in here. +* piu: The piu executable + +## Settings folder +The contents of the folder are auto generated if the files don't exist. +Otherwise, this folder contains: +* PIUFESTAEX.INI +* RANK.DATA + +## Executable dependencies +All dependencies must be compiled as 32-bit binaries. Here is a list of +dependencies (with versions) required to run the game: +* libGL.so.1 +* libGLU.so.1 +* libasound.so.2 +* libusb-0.1.so.4 +* libmad.so.0 +* libmpeg2.so.0 +* libmpeg2convert.so.0 +* libstdc++.so.6 +* libm.so.6 +* libgcc_s.so.1 +* libc.so.6 +* libX11.so.6 +* libpthread.so.0 +* libdl.so.2 +* librt.so.1 +* libnvidia-tls.so.340.96 +* libnvidia-glcore.so.340.96 +* libXext.so.6 +* libusb-1.0.so.0 +* libxcb.so.1 +* libudev.so.1 +* libXau.so.6 +* libXdmcp.so.6 +* libcap.so.2 +* libresolv.so.2 + +# Hook module configuration file +Checkout the usage information of the hook and set the option values according +to your needs. Here is an example option configuration file: +``` +log_file_path=/tmp/pumptools.log +log_level=3 +enable_file_monitor=0 +enable_io_monitor=0 +piuio_emu_lib_path=/pumptools/lib/piuio-emu.so +piuio_exit_test_service=1 +game_settings_path=/save/fex +sound_device=hw:0 +keyboard_dev=/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse +halt_on_segv=0 +``` + +# Run the game +Ensure you are running an X screen. Otherwise, you have to start one along with the game. Various library/system-calls +require root privileges. Make sure to run the game as root or with sudo (otherwise you get various sorts of errors, +typically permission denied). Use the included *run.sh* file to start the game on a desktop environment. + +# Further notes +## Vsync +The game is required to run with vsync on. \ No newline at end of file diff --git a/doc/hook/fsthook.md b/doc/hook/fsthook.md new file mode 100644 index 0000000..9f355a7 --- /dev/null +++ b/doc/hook/fsthook.md @@ -0,0 +1,89 @@ +# Notable features + +* Runs on recent kernel versions thanks to various fixes +* Runs on 32-bit and 64-bit distros (64-bit distros require additional 32-bit libs to be installed) +* Full dongle emulation +* Remove HDD checks to run this on "non legit" drives +* Full MK6IO emulation with API hook: Keyboard or your own custom IO +* Real IO passthrough (for MK6 usb io) + +# Versions supported +All known versions supported. + +# Data setup +You are expected to get a clean set of data from a prestine drive. Ensure that +the game version matches one of the supported versions listed. + +You need two main folders: +* data +* settings + +## Data folder +Contents of the folder: +* game: The binaries of the custom "Fiesta FS". These are stored in raw areas +of the HDD and have to be extraced. File names must be lower case. +* lib: Put any libraries (especially older versions of libraries that can't be +installed anymore using the package manager) the game uses and aren't installed +on your system in here. +* piu: The piu executable + +## Settings folder +The contents of the folder are auto generated if the files don't exist. +Otherwise, this folder contains: +* PIUFESTA.INI +* RANK.DATA + +## Executable dependencies +All dependencies must be compiled as 32-bit binaries. Here is a list of +dependencies (with versions) required to run the game: +* libGL.so.1 +* libGLU.so.1 +* libasound.so.2 +* libusb-0.1.so.4 +* libmad.so.0 +* libmpeg2.so.0 +* libmpeg2convert.so.0 +* libstdc++.so.6 +* libm.so.6 +* libgcc_s.so.1 +* libc.so.6 +* libX11.so.6 +* libpthread.so.0 +* libdl.so.2 +* librt.so.1 +* libnvidia-tls.so.340.96 +* libnvidia-glcore.so.340.96 +* libXext.so.6 +* libusb-1.0.so.0 +* libxcb.so.1 +* libudev.so.1 +* libXau.so.6 +* libXdmcp.so.6 +* libcap.so.2 +* libresolv.so.2 + +# Hook module configuration file +Checkout the usage information of the hook and set the option values according +to your needs. Here is an example option configuration file: +``` +log_file_path=/tmp/pumptools.log +log_level=3 +enable_file_monitor=0 +enable_io_monitor=0 +piuio_emu_lib_path=/pumptools/lib/piuio-emu.so +piuio_exit_test_service=1 +game_settings_path=/save/fst +sound_device=hw:0 +keyboard_dev=/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse +halt_on_segv=0 +``` + +# Run the game +Ensure you are running an X screen. Run the game using +Ensure you are running an X screen. Otherwise, you have to start one along with the game. Various library/system-calls +require root privileges. Make sure to run the game as root or with sudo (otherwise you get various sorts of errors, +typically permission denied). Use the included *run.sh* file to start the game on a desktop environment. + +# Further notes +## Vsync +The game is required to run with vsync on. \ No newline at end of file diff --git a/doc/hook/hook.md b/doc/hook/hook.md new file mode 100644 index 0000000..a247f38 --- /dev/null +++ b/doc/hook/hook.md @@ -0,0 +1,201 @@ +# Pumptool's hook libraries +A collection of libraries that need to be pre-loaded when running vanilla dumps of Pump It Up games. These hooks allow +you to run any of the supported games on any* Linux distribution and hardware. + +Each game might require a different hook library as the software evolved as well as the hardware and original +operating system changed a few times as well. + +## General features +A few notable features: + +* Run any supported gam on recent kernel versions thanks to various fixes +* Run any supported game on 32-bit and 64-bit distros (64-bit distros require additional 32-bit libs to be installed) +* Full dongle emulation +* Remove HDD checks to run this on "non legit" drives +* Full IO hardware emulation: MK6 PIUIO, Pro Button board (PIUBTN) +* Real IO passthrough +* API: Implement support for your own custom IO + +## Supported games and versions +Check each of the dedicated hook readmes which games and versions are supported. + +## Hardware, operating system and environment +A general outline is given by [this readme](os.md) if you want to setup something yourself. Otherwise, you should +checkout the `pumpos` project in a repository nearby which takes care of installing a fully configured OS to a physical +disk to run the games on dedicated hardware for cabinets. + +## Quick start: how to run (official release) +The following steps apply to any game of the "officially" supported release data. + +1. Install the required dependencies which can vary per game. Check the section "required dependencies" in the dedicated +readme files of each hook. +1. Unpack `game.zip` and `lib-local.zip` to a folder of your choice. +1. Your folder should contain the following files and folders: `game`, `lib`, `piu`, `version` +1. Unpack the hook which supports the game you have chosen, e.g. for Exceed use `exchook.zip`, from the +`pumptools-X.XX.zip` release package next to the `piu` executable +1. Unpack the `piuio.zip` from the `pumptools-X.XX.zip` release package next to the `piu` executable +1. Rename the hook library, e.g. for Exceed `exchook.so`, to `hook.so` and the hook configuration file, e.g. for +Exceed `exchook.conf`, to `hook.conf` +1. Open `hook.conf` with a text editor and set the `patch.piuio.emu_lib` property accordingly: + * For keyboard usage: `patch.piuio.emu_lib=./ptapi-io-piuio-keyboard.so` and + `patch_hook_main_loop.x11_input_handler=./ptapi-io-piuio-keyboard.so"` + * Configure your keyboard mappings using `./ptapi-io-piuio-keyboard-conf` + * For USB PIUIO usage: Do not set the `patch.piuio.emu_lib` property and have the hardware plugged in. +1. Run the game as logged in root user: `./piueb run` or if you have `sudo` installed and configured: `sudo ./piueb run` + +Details to specific games are given in the hook read files dedicated to each supported version. Further general +configuration and technical details as well as troubleshooting known issues are described in following sections. + +## Dependencies +A list of dependencies is provided in the dedicated hook readme files for each game. The following is a general guide +on how dependencies of the games can be resolved to run them. + +Right now, there are two methods for resolving the dependencies and which dependencies to use for the game: +* __Method 1__: Install as many dependencies as possible using the package manager of your distribution. Usually, you want +to go for method 1 and if the game runs, you don't have to bother with method 2. A few less common libraries are +provided with the official data release and are loaded from the local `lib` folder instead. +* __Method 2__: Provide **all** libraries except GPU related ones and a dedicated ld-linux loader with the game +independent from your system. Theoretically, this gives you full distribution independence but it is more complicated +and comes with a few unresolved issues so far. If method 1 doesn't work for you, try this method. For details, see the +[following section](#local-data-folder). + +## Data setup +You are expected to get a clean set of data from a pristine drive. Ensure that the game is supported by one of the +hook libraries coming with pumptools and the game's version is on the list supported versions. + +You are not required to have the pulled data in the same locations as on the original drive as the hook library can +be configured to have everything in a single local folder. These settings can be found and tweaked in the `hook.conf` +file which is created after you started the game once. + +### Local data folder +Your local data folder must contain the following folders and files: +* `game`: The `game` asset folder from the HDD. Filename casing depends on the games and is relevant on case-sensitive +file systems. Exact requirements are explained in the readme files of each hook. Otherwise, the game crashes because of +files/folders it cannot find. Furthermore, put any additional files/folders that are game assets and not located in +the original `game` folder into the `game` folder, e.g. `mission.txt`, `SCRIPT` folder etc. which are located in +the cramfs on some games. +* `lib`: Put any libraries (especially older versions of libraries that can't be installed anymore using the package +manager) the game uses and aren't installed on your system in here. Using piueb, you have two options with potential +different (in-)compatibility issues: + 1. Have **all** libraries the game requires to run (except GPU driver specific libs) with compatible version in that + folder including a dedicated `ld-linux.so`. See [piueb script header documentation](../../dist/piueb). + 1. Have only additional libraries that are not common/available on with your package manager **without** a dedicated + `ld-linux.so`. +* `save`: Empty folder where the the game stores configuration. These files are created by the game automatically and +contain default values if missing. +* `piu`: The Linux port `piu` executable. + +## Configure IO +The hooks allow you to hook any implementation of [pumptools's API](../api/api.md) to the game to drive any type of IO +hardware. See the [dedicated readme](../api/io/piuio.md) on how to configure the PIUIO with the different types +of implementations available, e.g. keyboard, joystick, ... + +## Troubleshooting and FAQ +The following sub-sections apply to all hooks. + +### USB 3.0 vs 2.0 issues with USB thumb drives/profiles +This affects *ALL* games that make use of USB thumb drives for storing player profile related data. + +Due to how the Linux kernel treats bus-port mappings for USB 3.0 and USB 2.0 different, it is recommended to limit the +usage of USB thumb drives either to 3.0 or 2.0 drives only after having configured the port assignment for the affected +games. + +For example, you use a USB 2.0 thumb drive to assign one physical USB ports on your machine to each player side using +NX2's operator menu configuration option. However, the configured assignment is only working for USB 2.0 drives. If +you repeat this configuration step with a USB 3.0 drive (if your mainboard supports them because it has a USB 3.0 +host controller), you will get different `bus:port` values shown on the configuration screen. Keep this in mind when +setting up the game, assigning the ports and using USB thumb drives with the games. + +### My USB thumb drive is not detected by the game at all +Which means you cannot use it to even map the USB ports in the test menu. + +Make sure to try at least another one by a different brand. There have been reports of some thumb drives simply not +working, e.g. a fairly old Kingston 1GB. In general, everything that gets detected fine by Linux should work. + +### My USB thumb drive works when mapping the ports in the test menu but is not recognized on the game login screen +Might be the same issue as [here](#my-usb-thumb-drive-is-not-detected-by-the-game-at-all). Try out different USB thumb +drives. Be aware of the [USB 3.0 vs 2.0 issue](#usb-30-vs-20-issues-with-usb-thumb-drivesprofiles) as well. + +### The game enters the operator menu when I start it +This is known to happen consistently on NX but was observed on other versions like Exceed 2 and Zero in the past. The +cause for this is unknown so far. Even with all usb emulation layers removed from pumptools and a real MK6 PIUIO +attached this still happens. If the game is run without the PIUIO attached, everything's fine. Therefore, we suspect +this is a bug within the game's PIUIO driver, likely some uninitialized buffers (due to the randomness of this bug +being triggered). + +### How to I generate a fresh configuration file with default values +If you deleted your `hook.conf` file or you just want a clean start with default values, the hook creates a clean +`hook.conf` file if none exists once you run `piueb`. + +### What are the command line arguments supported by the hook +Just run `piueb run -h` to get help output from the hook library. This also provides you with shortened command line +parameters for all options available from the configuration file. + +### How do I provide command line arguments to quickly change configuration settings for the game +Just run `piueb run` with the shortened command line parameter, e.g. `piueb run -w`. + +### The game's music plays too fast, too slow, or sounds weird +Depending on the game version you play, the audio subsystem is set to either render to an output device with a +frequency of 44100hz or 48000hz in SE16_LE format. Alsa needs to be configured accordingly to play back the audio +data at the right sample rate to make it sound right. + +Games and audio settings required: +* 44100hz: MK3 Linux ports 1st to Prex 3, Exceed (1), Pro 1 and Pro 2 +* 48000hz: Exceed 2 and newer + +One possibility to fix that is change the values in your config, e.g. `/usr/share/alsa/pcm/dmix.conf` when using the +`dmix` device (the location and config can differ depending on your setup). In case you have to set them to 48000hz, +search for the following configuration values and replace them with these values: +``` +format S16_LE +rate 48000 +``` + +If the contents of the files are just variables, check `/usr/share/alsa/alsa.conf` something similar like this: +```text +defaults.pcm.dmix.rate 44100 +defaults.pcm.dmix.format "S16_LE" +``` + +The same method applies to replacing 44100hz with 48000hz. + +### How do I figure out which sound device to select +You can list the currently connected devices/sound cards using the following command: +```shell script +cat /proc/asound/cards +``` + +Example output: +```text + 0 [PCH ]: HDA-Intel - HDA Intel PCH + HDA Intel PCH at 0xfb610000 irq 51 + 1 [NVidia ]: HDA-Intel - HDA NVidia + HDA NVidia at 0xfb080000 irq 52 +``` + +You can see two audio devices available: `0` being the built-in sound chip and `1` the audio output on the installed +GPU (i.e. HDMI audio out). + +To route the audio to the device of your choice, e.g. device `0`, add the number to the `hw:` path: `hw:0` for device +`0`. Set `hw:0` in the configuration file: + +```text +patch.sound.device=hw:0 +``` + +### The game plays/renders too fast +The game relies on vsync to lock to the target framerate of 60 FPS. Ensure vsync is turned on in your GPU settings. + +### libGL.so.1: cannot open shared object file: No such file or directory +Install your GPU drivers. This library depends on the GPU driver and is not included with the distributed data. + +### There's no sound at all, even with the correct sound device selected +On certain setups, the sound output only seems to work after already having attempted to use the sound device once. +In a shell, try running the following command twice: + +```shell script +aplay -q /usr/share/sounds/alsa/Front_Center.wav +``` + +If after a fresh boot the sound plays after the second attempt, your setup suffers from this issue. +Simply putting the above command once in a boot script will make sure the sound device is activated. \ No newline at end of file diff --git a/doc/hook/mk3hook.md b/doc/hook/mk3hook.md new file mode 100644 index 0000000..329d98b --- /dev/null +++ b/doc/hook/mk3hook.md @@ -0,0 +1,283 @@ +# mk3hook: 1st to Prex 3/Premiere 3 Linux ports +This readme covers any matters that are relevant for this hook, only. Anything that applies to **all** hooks is covered +in a [main hook readme file](../hook.md) including general data setup and a quick start guide. + +First and foremost, the MK3 Linux ports are runnable without this hook as well. The ports were created from the original +fully disassembled DOS binaries and reassembled for 32-bit Linux. To make the games run on modern non-MK3 hardware, +additional features were added to the disassembly: +* Software lockchip emulation +* ISA PIUIO to USB PIUIO (over libusb) +* MP3 audio decoding and playback using fmodex +* Software sound effect playback +* Software EEPROM data reading/writing + +However, since modifying the (decompiled) source code is a very complex task, various additional features and fixes +are applied using a hook library. This lacks certain flexibility but is so far sufficient to fix various bugs and add +some more quality of life features. + +## Additional notable features +* Audio subsystem using fmodex: Bugfixes and audio device selection +* Relocate game data to `game` sub-folder to have identical data layout to newer generation games +* Additional development/debugging features, e.g. file, io, open logging and tracing +* Hardcoded OpenGL bugfix +* Utilize pumptools configuration infrastructure to unify configuration of all games + +## Versions supported +Basically, "all" versions are supported considering there was only one Linux binary per game released so far. + +## Quick start: how to run (official release), additional steps +Start with the quick start guide from the [main hook readme](../hook.md#quick-start-how-to-run-official-release) and +add the additional steps at the very end: +1. **1st and 2nd only**: Open the `hook.conf` file and set the following property: `game.1st_2nd_fs=1`. For details, see +[this section](#1st-or-2nd-errors-about-failed-resource-loading) +1. **1st only**: Go straight to the operator menu by pressing `G` on your keyboard. Go to `GAME OPTION`, select and +confirm `DEFAULT SETTING` and `SAVE AND EXIT`. Next, go to `COIN OPTION`, `DEFAULT SETTING` and `SAVE AND EXIT`. See +[this section](#1st-crashes-right-after-the-andamiro-logo-or-the-intro-sequence-when-i-should-see-the-title-screen-with-a-floating-point-exception) +for details. + +## Dependencies +Make sure to read the different methods of dependency resolution available in the [main hook readme file](../hook.md), +first. + +The following **direct** dependencies (cmd: `readelf -d piu`) are required by all Linux ports: +* libGL.so.1 +* libX11.so.6 +* libXxf86vm.so.1 +* libXrandr.so.2 +* libpthread.so.0 +* libXi.so.6 +* libXcursor.so.1 +* libXinerama.so.1 +* libm.so.6 +* libncurses.so.5 +* libtinfo.so.5 +* libfmodex.so +* libconfig.so.9 +* libusb-0.1.so.4 +* libc.so.6 + +As for method 1, when using Ubuntu, the dependencies can be found in the following packages: +* libc-bin (or gcc-multilib on a 64-bit platform) +* libusb-0.1-4 +* libconfig++9v5 +* tinfo5 +* ncurses5 +* libxcursor1 +* libxinerama1 +* libxi6 +* libxrandr2 +* libxxf86vm1 +* libx11-6 +* libasound2 + +## Data setup +In additional to the [general information applying to **all** hooks](../hook.md#data-setup), the following information +goes for a clean set of data from a pristine/non-bootleg CD. + +If you have the official release, this might not be relevant to you as it just explains some important technical details +about the data. + +In general, get a clean set of data from a pristine/non-bootleg CD. + +However, 1st, 2nd and 3rd and Extra (IIRC) are the exception here. These games have their textures packed in an odd +format (IIRC something about an odd color format and/or textures being flipped) that cannot be used with the Linux port +versions. These versions require file modification and repacking to make everything look correct with the linux +binaries. + +Furthermore, because the old games ran on case-insensitive file systems, everything goes with file naming depending on +the version of the game, e.g. full upper/lower-case files, folders and even mixed ones, e.g. Stage.cfg (*sigh*). +In order to make this less painful for the the hook library, some file names were modified to create some sort of +consistency. + +You are not required to have the pulled data in the same location and layout as given by the original layout on the +disc as this can be configured with the hook library. Everything can be located locally in a single folder. The settings +for this can be found and tweaked in the `hook.conf` file which is created after you started the game once +([see below](#mk3hook-features)). + +#### The game data subfolder +This differs slightly from newer generation games, i.e. MK6-based and newer. The hook library takes care of this by +implementing a consistent directory structure on all versions of the game. Therefore, all game data from the CD of MK3 +games goes into a `game` folder as well. Example of `games` contents for Prex 3: +* `AUDIO` +* `BGA` +* `STEP` +* `TITLE` +* `PIU.BIN` +* `STAGE.CFG` + +Furthermore, the game needs to have the sound effects available as files. These were stored on a ROM chip on the MK3 +hardware. Luckily, newer PC-based games have these as normal files with the correct naming in the `game/WAVE` subfolder. +These also go into `game/EFFECTS`. + +Some other and older versions come with different files and folders but the process is identical. The first three games, +1st, 2nd and 3rd, had their audio files stored on disc as PCM audio data next to the other game assets. These audio +files need to be ripped and provided as MP3 encoded audio files in the sub-folder `game/track`. + +## Linux port features +This section covers the features that were added to the Linux ports and therefore not available on the original DOS +versions. + +### Configuration file +A configuration file with different options can be provided to the game's bare executable. The configuration is provided +as a command line argument when running the game, e.g. +``` +./piu ./save/config.cfg +``` + +Example contents: +``` +fullscreen = 1; +allow_exit = 1; +save_file = "./save/EEPROM.BIN"; +effect_path = "./EFFECTS/"; +track_path = "./track/"; +sync_offset = 115; +sync_multiplier = 4.16666666666666696272613990004; +music_volume = 1.0; +sfx_volume = 1.0; +``` + +Most entries are self-explanatory. However, there are two parameters that can be tweaked to control synchronisation of +the stepchart to the music. + +The `sync_offset` parameter (in ms) is used to offset the stepchart to the music. However, as you might be used to such +a parameter from other music games, this offset is **NOT** entirely identical to adjusting the music to your sound +system's latency. You can use this value to shift the synchronisation point forward and backward but keep in mind that +it will not match any other offset values you determined in other games. + +The `sync_multiplier` is also a variable that was depending on the sound hardware of the target platform. It was +already determined and there should be **NO** reason to change it unless you know what it does and how it is used in +synchronizing the stepchart to the song in the engine. + +All these configuration values are exposed via the `hook.conf` file and hooked to the pumptools configuration +infrastructure. Usually, there is no need to deal with this directly, so this is section is mainly for documenting its +presence. + +### Built-in keyboard controls +Ignore this section if you are using the `mk3hook` because it will disable this feature entirely. See +[this section](#configure-io). + +The following keyboard controls are available with the Linux ports. + +PIUIO hooked controls: +``` +Test: Key G +Service: Key H +Clear: Key J +Coin 1: Key K +Coin 2: Key L + +Pads (Player 2 on numpad): +Q E 7 9 + S 5 +Z C 1 3 +``` + +Keyboard controls supported by the game engine (won't work on very early versions of the game, e.g. 1st, 2nd, 3rd): +``` +Test: F1 +Service: F2 +Clear: F3 +``` + +### USB PIUIO +Ignore this section if you are using the `mk3hook` because it will disable this feature entirely. See +[this section](#configure-io). + +When a USB PIUIO is plugged it, the game will automatically detect and use it. + +### Exit the game +Use `Test` + `Service` on the USB PIUIO (controls) to exit the game. This can also be blocked in the configuration. + +## mk3hook features +Check the `hook.conf` file which is located in the same folder as the `piu` executable once you have started the game. +The available settings are explained in the `hook.conf` file. + +### Configure IO +See [this section of the main hook readme](../hook.md#configure-io). + +### Select another audio device +If the default sound card is not working, e.g. see [here](#no-sound-and-fmod-errors-in-log), you might want to +select another audio device/card instead. + +In order to find out which cards are available and which configuration value to set, you have to run the game once. +Check the log and you will see a list of available devices printed somewhere at the beginning of the log, for example: +``` +Output type: 11 +Num available drivers 43 +Driver 0: default +Driver 1: null +Driver 2: jack +Driver 3: sysdefault:CARD=PCH +Driver 4: front:CARD=PCH,DEV=0 +... +``` + +By default and if you have not configured any sound device, yet, this will pick the first, usually, `default` device. +Pick another one by copying the name, e.g. `sysdefault:CARD=PCH`, and setting it in the `hook.conf` file: +``` +patch.sound.device=sysdefault:CARD=PCH +``` + +When you restart the game, check the log if the device got picked up properly. This is indicated by a log message +right after the device list is printed. + +### Debugging fmodex issues +You can also debug issues with fmodex, e.g. why +[the selected sound device does not work](#no-sound-and-fmod-errors-in-log). + +You need to replace the normal `fmodex.so` library in the game local `lib` folder with a `fmodexL.so` version. Just +rename the "L"-version to `fmodex.so` and have it in the game local `lib` folder. Furthermore, you have to enable +debug output by setting the following configuration value in the `hook.conf` file: +``` +patch.sound.debug_output=0 +``` + +After that's done, you should see additional log output by fmodex on the console. + +## Troubleshooting and FAQ +Make sure to also check the +[troubleshooting and FAQ section of the main hook readme](../hook.md#troubleshooting-and-faq). This covers various +things that apply to **all** hooks. The following sub-sections apply mainly to this hook. + +### No sound and FMOD errors in log +If you see one or multiple of the following error messages in the log output +``` +FMOD error! (60) Error initializing output device. +FMOD error! (79) This command failed because System::init or System::setDriver was not called. +FMOD error! (37) An invalid parameter was passed to this function. +``` +it is very likely that fmod cannot use the default/currently selected audio device for playback. This can have various +reasons from non suitable configuration to device being used by another process and therefore blocked if not supporting +software mixing. + +Choose another device for playback instead, see [this section](#select-another-audio-device), or you can enable +fmodex debug output to further debug this issue, see [this section](#debugging-fmodex-issues). + +### 1st/2nd is missing the "Insert Coin" text on the title screen and several other graphics, e.g. life bar during gameplay +This is a known bug and we do not have a fix for this so far. However, this is not always triggered and if you keep +restarting the game (with a few seconds of waiting between restarts), you will start the game without this bug being +active eventually. + +### 1st or 2nd errors about failed resource loading +For example: +``` +FAIL: res_load( piu.dat ) +``` + +This error indicates that one or multiple game files are not placed in the right location(s), your configured `game` +sub-directory path is not correct or you forgot to switch on the "1st/2nd filesystem" feature switch in the hook +configuration. For the latter, set the following in your `hook.conf` file: +``` +game.1st_2nd_fs=1 +``` + +### 1st crashes right after the Andamiro logo or the intro sequence when I should see the title screen with a floating point exception +1st does not detect if no EEPROM data is available and does not write defaults on first start or when you deleted the +`save/EEPROM.BIN` file. Therefore, all settings values for the game are considered "0". This is fine with all settings +except the coin settings. This causes the floating point exception though I assume it was actually caused by a division +by zero. + +To fix this, go right to the operator menu when you booted up the game and are still on the Andamiro logo screen. Go +to `GAME OPTION`, select and confirm `DEFAULT SETTING` and `SAVE AND EXIT`. Next, go to `COIN OPTION`, `DEFAULT SETTING` +and `SAVE AND EXIT`. This writes a fresh `EEPROM.bin` file with all default settings and you are good to go. \ No newline at end of file diff --git a/doc/hook/nx2hook.md b/doc/hook/nx2hook.md new file mode 100644 index 0000000..9ef978e --- /dev/null +++ b/doc/hook/nx2hook.md @@ -0,0 +1,154 @@ +# nx2hook: NX2 +This readme covers any matters that are relevant for this hook, only. Anything that applies to **all** hooks is covered +in a [main hook readme file](../hook.md) including general data setup and a quick start guide. + +## Additional notable features +* Removed USB flash drive vendor lock, i.e. use _ANY_ USB flash drive to store game profiles +* Auto generate new profiles if no profile is found on the connected USB flash drive + +## Versions supported +All known versions supported. + +## Dependencies +Make sure to read the different methods of dependency resolution available in the [main hook readme file](../hook.md), +first. + +The following **direct** dependencies (cmd: `readelf -d piu`) are required: +* libfreetype.so.6 +* librt.so.1 +* libGL.so.1 +* libGLU.so.1 +* libusb-0.1.so.4 +* libpthread.so.0 +* libXxf86vm.so.1 +* libpng12.so.0 +* libasound.so.2 +* libmad.so.0 +* libgcc_s.so.1 +* libc.so.6 +* libm.so.6 +* libX11.so.6 +* libstdc++.so.6 +* libz.so.1 + +As for method 1, when using Ubuntu, the dependencies can be found in the following packages: +* libc-bin (or gcc-multilib on a 64-bit platform) +* libx11-6 +* zlib1g +* libusb-0.1-4 +* libasound2 + +## Data setup +In additional to the [general information applying to **all** hooks](../hook.md#data-setup), this game requires all +files and folders from the original `game` folder to be in **UPPERCASE** on a case-sensitive file system with the +exception of `*.txt` and `*.ttf` files in the root of game. Further game asset files and folders from cramfs need to be +copied to the `game` directory. `nx.ttf`, `nxcn.ttf`, `nxpt.ttf`, `nxtw.ttf` and `mission.txt` must be **lowercase** but +`SCRIPT` and its contents must be **UPPERCASE**. + +## Pumpnet setup +Instead of using plain usb profiles like on the vanilla version without pumptools (which is still possible with +pumptools), pumptools provides a patch module to upload/download usb profiles to/from a remote server over TCP IP +networks. + +This feature can be enabled by setting the following configuration properties in the `hook.conf` file: +```text +patch.net_profile.server= +patch.net_profile.machine_id= +``` + +If the network offers or even requires secure communication using https, you should have received a package with a +client certificate, a client key and a certificate authority bundle. Place the files named `ca-bundle-crt.pem`, +`client-crt.pem` and `client-key.pem` in a folder, e.g. `cert` next to the `piu` executable and configure the hook +to use these for the encrypted communication by setting the property key `patch.net_profile.cert_dir_path` +accordingly, e.g. `patch.net_profile.cert_dir_path=./cert`. Note: The server URL that you set for the properties key +`patch.net_profile.server` has to be a https based URL in order to work correctly, e.g. `https://localhost`. Your +network provided of your choice should provide you the URL you have to use. + +When everything's configured correctly, the log tells you so: +```text +Initialized pumpnet for game 20, serveraddr localhost:8080, main endpoint version /usbprofile/v1 +Initialized: game 20, server XXXXXXX, machine id XXXXXXX +``` + +How you acquire an address to a remote server and a machine ID is out of this document's scope. + +Once these parameters are present, pumptools is looking for a file called `pumpnet.bin` on connected usb sticks. As +long as this file is in the root directory of your usb drive, the game will ignore the the regular `nx2save.bin` and +`nx2rank.bin` files and always try to connect to the remote server. When you remove `pumpnet.bin`, it will pick up the +local profiles and not connect to the server, even if enabled. + +`pumpnet.bin` contains an identifier for the player to login. This file needs to be provided by the network service +somehow. The how is out of the scope of this document. + +Ensure you have mapped the usb ports in the game correctly. Go to the operator menu and the "usb drive" item. Plug +**exactly one** usb thumb drive into the usb port you want to assign as P1 and hit the test button. The menu item +should change from `---` to a numerical value, e.g. `03:00` which describes the bus and port the drive got detected on. +Repeat this for P2. + +When you start the game, have your usb stick plugged in and it is recognized correctly by the game, the log output +tells you if it found the `pumpnet.bin` file and connected to the server correctly for downloading your profile data: +```text +Found pumpnet profile file /mnt/0/pumpnet.bin +Profile file player 0, file_type 0, refId XXXXXXXXXXXXXX downloading from server... +Downloading file player 0, file_type 0, refId XXXXXXXXXXXXXX successful +``` + +If it cannot connect to the server, the log tells you that as well and retries to connect to it a few times before +giving up: +```text +Performing curl request failed: Couldn't connect to server +Failed, http code 0, retrying (0)... +... +``` + +## Troubleshooting and FAQ +Make sure to also check the +[troubleshooting and FAQ section of the main hook readme](../hook.md#troubleshooting-and-faq). This covers various +things that apply to **all** hooks. The following sub-sections apply mainly to this hook. + +### How do I unlock 'Tell Me' on the non korean version? +Put an empty file called `KEY.LUA` into the `game/SCRIPT` folder and change the language to korean in the operator menu. +This loads the korean version of the game (yes, that's how it actually worked originally) and 'Tell Me' will be +available. + +Note: You can't switch back to English unless you delete the file `game/SCRIPT/KEY.LUA`. + +### When using pumpnet, the game does not detect any usb sticks +Make sure that you have configured the usb ports in the game's operator menu `USB DRIVE` menu item. For instructions, +refer to the [above section](#pumpnet-setup). + +### When using pumpnet, the game tells me to register my usb drive on login +If you see the message "Please REGISTER your USB drive at www.piugame.com prior to using the product." when logging in +with pumpnet enable, your login got rejected for one of the following reasons: +* Your machine ID is invalid or not whitelisted on the server +* Your player ID is invalid or no such player exists on the server +* No NX2 profile was created for the player ID on the server + +On any of the above cases, verify that you ensured you have configured everything correctly on the user web interface +of pumpnet. Otherwise, contact the server administrator. + +### When using pumpnet, the game apparently gets stuck on the usb drive login screen +This can happen if the game cannot reach the server or on some other errors that might be temporarily. Therefore, the +game retries for up to ten times currently to reach the server or complete an outstanding operation. This is also +reflected in the logs with warning messages telling you the game is retrying. + +### Curl requests fail: Problem with the SSL CA cert +When you get the following error message: +```text +Performing curl request failed: Problem with the SSL CA cert (path? access rights?) +``` + +Make sure the path you configured for the property key `patch.net_profile.cert_dir_path` is pointing to an existing +directory containing the files `ca-bundle-crt.pem`, `client-crt.pem` and `client-key.pem` that you received from the +the network you are trying to connect to. + +### Curl requests fail: SSL peer certificate or SSH remote key was not OK +When you get the following error message: +```text +Performing curl request failed: SSL peer certificate or SSH remote key was not OK +``` + +Make sure that you have the files `ca-bundle-crt.pem`, `client-crt.pem` and `client-key.pem` that you have received +from the network you are trying to connect to placed in a directory. The path to the **directory** needs to be set in +the hook configuration file by setting the key `patch.net_profile.cert_dir_path`, e.g. +`patch.net_profile.cert_dir_path=./cert` if the files are placed next to the `piu` exec in the folder `cert`. \ No newline at end of file diff --git a/doc/hook/nxahook.md b/doc/hook/nxahook.md new file mode 100644 index 0000000..df9f414 --- /dev/null +++ b/doc/hook/nxahook.md @@ -0,0 +1,55 @@ +# nxahook: NXA +This readme covers any matters that are relevant for this hook, only. Anything that applies to **all** hooks is covered +in a [main hook readme file](../hook.md) including general data setup and a quick start guide. + +## Additional notable features +* Removed USB flash drive vendor lock, i.e. use _ANY_ USB flash drive to store game profiles +* Auto generate new profiles if no profile is found on the connected USB flash drive + +# Versions supported +All known versions supported. + +## Dependencies +Make sure to read the different methods of dependency resolution available in the [main hook readme file](../hook.md), +first. + +The following **direct** dependencies (cmd: `readelf -d piu`) are required: +* libfreetype.so.6 +* librt.so.1 +* libGL.so.1 +* libGLU.so.1 +* libusb-0.1.so.4 +* libpthread.so.0 +* libXxf86vm.so.1 +* libpng12.so.0 +* libasound.so.2 +* libmad.so.0 +* libboost_regex-mt.so.3 +* libgcc_s.so.1 +* libc.so.6 +* libm.so.6 +* libX11.so.6 +* libdl.so.2 +* libstdc++.so.6 +* libz.so.1 + +As for method 1, when using Ubuntu, the dependencies can be found in the following packages: +* libc-bin (or gcc-multilib on a 64-bit platform) +* libx11-6 +* zlib1g +* libusb-0.1-4 +* libasound2 + +## Data setup +In additional to the [general information applying to **all** hooks](../hook.md#data-setup), this game requires all +files and folders from the original `game` folder to be in **UPPERCASE** on a case-sensitive file system. Further game +asset files and folders from cramfs need to be copied to the `game` directory. `nx.ttf`, `nxcn.ttf`, `nxpt.ttf`, +`nxtw.ttf`, `mission.txt` and `ufo.txt` must be **lowercase** but `SCRIPT` and its contents must be **UPPERCASE**. + +The `config` (or `CONFIG`) folder and its contents must be available in **UPPER** _AND_ **lowercase**. The `BrainQuest` +folder name must be kept like this and its contents must be **lowercase**. + +## Troubleshooting and FAQ +Make sure to also check the +[troubleshooting and FAQ section of the main hook readme](../hook.md#troubleshooting-and-faq). This covers various +things that apply to **all** hooks. The following sub-sections apply mainly to this hook. \ No newline at end of file diff --git a/doc/hook/nxhook.md b/doc/hook/nxhook.md new file mode 100644 index 0000000..69eeec5 --- /dev/null +++ b/doc/hook/nxhook.md @@ -0,0 +1,52 @@ +# nxhook: NX +This readme covers any matters that are relevant for this hook, only. Anything that applies to **all** hooks is covered +in a [main hook readme file](../hook.md) including general data setup and a quick start guide. + +## Versions supported +All known versions supported. + +## Dependencies +Make sure to read the different methods of dependency resolution available in the [main hook readme file](../hook.md), +first. + +The following **direct** dependencies (cmd: `readelf -d piu`) are required: +* librt.so.1 +* liblua50.so.5.0 +* liblualib50.so.5.0 +* libGL.so.1 +* libGLU.so.1 +* libusb-0.1.so.4 +* libpthread.so.0 +* libpng12.so.0 +* libasound.so.2 +* libgcc_s.so.1 +* libc.so.6 +* libm.so.6 +* libXext.so.6 +* libX11.so.6 +* libstdc++.so.5 +* libz.so.1 + +As for method 1, when using Ubuntu, the dependencies can be found in the following packages: +* libc-bin (or gcc-multilib on a 64-bit platform) +* libx11-6 +* zlib1g +* libusb-0.1-4 +* libasound2 + +## Data setup +In additional to the [general information applying to **all** hooks](../hook.md#data-setup), this game requires all +files and folders in the `game` folder to be in **UPPERCASE** on a case-sensitive file system. + +## Troubleshooting and FAQ +Make sure to also check the +[troubleshooting and FAQ section of the main hook readme](../hook.md#troubleshooting-and-faq). This covers various +things that apply to **all** hooks. The following sub-sections apply mainly to this hook. + +### Fully unlock the game +This hook has an option to fully unlock the game by setting the following property in the configuration file: +```text +game.force_unlock=1 +``` + +This will unlock all missions in "World Tour" as well any locked songs in "Special Zone", and the song "FAEP 2-2". \ No newline at end of file diff --git a/doc/hook/os.md b/doc/hook/os.md new file mode 100644 index 0000000..66b49dd --- /dev/null +++ b/doc/hook/os.md @@ -0,0 +1,59 @@ +# Hardware +Everything runs on original hardware MK6 and up. However, newer games might be slightly more demanding and will not +run smoothly on the old MK6 anymore. + +As for custom hardware, everything that's at least of MK6 specs should do the job. GPU-wise you are not bound to nVidia +cards. Though, the card of your choice has to support at least the OpenGL 2.0 standard. + +However, not that we have seen graphical glitches on AMD and Intel GPUs with NX2 which we haven't seen on older pump +versions. The game starts and runs but after some time you might encounter graphical glitches. + +You can also run the games in a VM. Recommended settings: 2 vCPUs, 2 GB RAM, OpenGL 2.0 hardware acceleration enabled +(requires installing drivers/kernel modules on your guest). + +# Operating system and environment setup +This document outlines common stuff that is required to run any of the games supported by pumptools. Make sure to follow +these properly and have your system prepared for the hooks before you continue with their dedicated readme files. + +## Linux distributions supported +If you have the required knowledge, you should be able to get this to run on any distribution available. However, there +are a few things to consider: +* The game is a 32-bit binary, you need to be able to run 32-bit binaries +* Naturally, the game requires 32-bit versions of the libraries it depends on +* The game is built with glibc. You might have a hard time on distributions not shipping with them by default, e.g. +Alpine Linux +* Kernel-wise, the latest kernels and everything that's not pre 2.6 should work. Due to various hook sub-modules, many +quirks in different games have been fixed to run all games on the latest upstream kernel versions of Linux. + +Recommendation: Use a 64-bit Linux distribution of your choice. You can also use a 32-bit distribution as this is the +arch these games were built for but it does not come with any benefits. Prepare yourself to upgrade to 64-bit once any +of the newer games (finally) moves to that target architecture. + +## Dependencies +Each game and hook comes with a list of dependencies you might have to install. This depends on which type of dependency +resolution you pick. + +When running this on a 64-bit Ubuntu, all packages must be installed as lib32 packages! When you are on Debian/Ubuntu +(based) distros and you are using apt, you have to enable multiarch support first: +```shell script +sudo dpkg --add-architecture i386 +apt update +``` + +Then, install the 32-bit packages like this where `` must be replaced accordingly: +```shell script +apt install pkgname:i386 +``` + +Note: The `:386` postfix does not apply to packages like `gccmultilib` as this already covers the i386 part. + +## GPU drivers +For `libGL`, you need to install a GPU driver and have hardware acceleration enabled. The `libGL` library provided also +needs to be 32-bit compatible. + +To check if everything's installed and HW acceleration is working, just run `glxgears`. If that doesn't error and you +see the rendered gears spinning, you should be fine. + +## Sound drivers +Naturally, you also have to have sound stuff installed. Having alsa with its libraries available is sufficient. Pulse +and similar audio systems are not required, e.g. when running on a cabinet. \ No newline at end of file diff --git a/doc/hook/prihook.md b/doc/hook/prihook.md new file mode 100644 index 0000000..2c6e23a --- /dev/null +++ b/doc/hook/prihook.md @@ -0,0 +1,108 @@ +# Notable features + +* Runs on recent kernel versions thanks to various fixes +* Runs on 32-bit and 64-bit distros (64-bit distros require additional 32-bit libs to be installed) +* Full dongle emulation +* Remove HDD checks to run this on "non legit" drives +* Full MK6IO emulation with API hook: Keyboard or your own custom IO +* Real IO passthrough (for MK6 usb io) + +# Versions supported +All known versions supported. + +# Data setup +You are expected to get a clean set of data from a prestine drive. Ensure that +the game version matches one of the supported versions listed. + +You need two main folders: +* data +* settings + +## Data folder +Contents of the folder: +* game: The binaries of the custom "AMFS". File names must be lower case. +* lib: Put any libraries (especially older versions of libraries that can't be +installed anymore using the package manager) the game uses and aren't installed +on your system in here. +* piu: The piu executable + +## Settings folder +The contents of the folder are auto generated if the files don't exist. +Otherwise, this folder contains: +* PIUPRIME.INI +* RANK.DATA + +## Executable dependencies +All dependencies must be compiled as 32-bit binaries. Here is a list of +dependencies (with versions) required to run the game: +* libGL.so.1 +* libGLU.so.1 +* libasound.so.2 +* libcurl.so.4 +* libsodium.so.13 +* libusb-0.1.so.4 +* libmad.so.0 +* libmpeg2.so.0 +* libmpeg2convert.so.0 +* libftgl.so.2 +* libstdc++.so.6 +* libgcc_s.so.1 +* libc.so.6 +* libm.so.6 +* libXext.so.6 +* libX11.so.6 +* libpthread.so.0 +* libdl.so.2 +* librt.so.1 +* libnvidia-tls.so.340.96 +* libnvidia-glcore.so.340.96 +* libidn.so.11 +* libssh2.so.1 +* libssl.so.1.0.0 +* libcrypto.so.1.0.0 +* libgssapi_krb5.so.2 +* libkrb5.so.3 +* libk5crypto.so.3 +* libcom_err.so.2 +* libz.so.1 +* libusb-1.0.so.0 +* libfreetype.so.6 +* libxcb.so.1 +* libkrb5support.so.0 +* libkeyutils.so.1 +* libresolv.so.2 +* libudev.so.1 +* libbz2.so.1.0 +* libpng16.so.16 +* libharfbuzz.so.0 +* libXau.so.6 +* libXdmcp.so.6 +* libcap.so.2 +* libglib-2.0.so.0 +* libgraphite2.so.3 +* libpcre.so.1 + +# Hook module configuration file +Checkout the usage information of the hook and set the option values according +to your needs. Here is an example option configuration file: +``` +log_file_path=/tmp/pumptools.log +log_level=3 +enable_file_monitor=0 +enable_io_monitor=0 +piuio_emu_lib_path=/pumptools/lib/piuio-emu.so +piuio_exit_test_service=1 +game_settings_path=/save/pri +sound_device=hw:0 +keyboard_dev=/dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-mouse +halt_on_segv=0 +``` + +# Run the game +Ensure you are running an X screen. Otherwise, you have to start one along with the game. Various library/system-calls +require root privileges. Make sure to run the game as root or with sudo (otherwise you get various sorts of errors, +typically permission denied). Use the included *run.sh* file to start the game on a desktop environment. + +# Further notes +## Vsync +The game is required to run with vsync on. \ No newline at end of file diff --git a/doc/hook/pro2hook.md b/doc/hook/pro2hook.md new file mode 100644 index 0000000..05e7dee --- /dev/null +++ b/doc/hook/pro2hook.md @@ -0,0 +1,91 @@ +# Notable features + +* Runs on recent kernel versions +* Runs on 32-bit and 64-bit distros (64-bit distros require additional 32-bit libs to be installed) +* Dongle stuff must be patched out +* You need decrypted data zips to run this +* Full MK6IO emulation with API hook: Keyboard or your own custom IO +* Full PIUBTN emulation with API hook: Keyboard or your own custom IO +* Real IO passthrough (for MK6 usb io and PIUBTN) + +# Versions supported +Currently, only the latest revision is supported (I think that's R5?) + +# Data setup +A clean set of data won't work here. Glenn protected the data with his own life +(pretty much) and made it as obnoxious as possible to unpack it. So good luck +getting the original data out of a drive image (if you didn't get it already +from somewhere). + +All access to files and folders are detoured to two folders which makes setting +up everything easier. + +You need one main folder: +* game/pro2 (contains all the game data, stepmania layout) + +## Executable dependencies +All dependencies must be compiled as 32-bit binaries. Here is a list of +dependencies (with versions) required to run the game: +* libXtst.so.6 +* libXrandr.so.2 +* libGL.so.1 +* libGLU.so.1 +* libdl.so.2 +* libavformat.so.51 +* libavcodec.so.51 +* libavutil.so.49 +* libusb-0.1.so.4 +* libpthread.so.0 +* librt.so.1 +* libstdc++.so.5 +* libm.so.6 +* libgcc_s.so.1 +* libc.so.6 +* libX11.so.6 +* libXext.so.6 +* libXi.so.6 +* libXrender.so.1 +* libnvidia-tls.so.340.106 +* libnvidia-glcore.so.340.106 +* libstdc++.so.6 +* libxcb.so.1 +* libXau.so.6 +* libXdmcp.so.6 + +# Hook module configuration file +Checkout the usage information of the hook and set the option values according +to your needs. Here is an example option configuration file: +``` +log_file_path=./pumptools.log +log_level=3 +enable_file_monitor=0 +enable_io_monitor=0 +piubtn_emu_lib_path= +piubtn_real_passthrough=1 +piuio_emu_lib_path=./piuio.so +piuio_real_passthrough=1 +piuio_exit_test_service=1 +game_data_path=./game +``` + +# Run the game +Ensure you are running an X screen. Run the game using +Ensure you are running an X screen. Otherwise, you have to start one along with the game. Various library/system-calls +require root privileges. Make sure to run the game as root or with sudo (otherwise you get various sorts of errors, +typically permission denied). Use the included *run.sh* file to start the game on a desktop environment. + +# Further notes +## ITG2 PIUIO kernel module hack +If you don't have the original kernel module installed and you are running a +real PIUIO, you have to hook the *piuio.so* lib which implements the piuio api. +The "emulation" part which simply calls back to a real piuio driver takes +care of handling the kernel hack path then. If you run on a real IO without +hooking that module, you won't get an inputs or outputs. + +## The game is crashing very early +This is probably due to not having the right IO hardware connected/emulated. +The game doesn't have any proper error handling if either the PIUIO or the +button IO is missing/not connected (or not emulated). + +## Vsync +The game is required to run with vsync on. \ No newline at end of file diff --git a/doc/hook/prohook.md b/doc/hook/prohook.md new file mode 100644 index 0000000..886134c --- /dev/null +++ b/doc/hook/prohook.md @@ -0,0 +1,107 @@ +# prohook: Pro (1) +This readme covers any matters that are relevant for this hook, only. Anything that applies to **all** hooks is covered +in a [main hook readme file](../hook.md) including general data setup and a quick start guide. + +## Versions supported +All known versions supported if no-dongle patched at this time. + +## Dependencies +Make sure to read the different methods of dependency resolution available in the [main hook readme file](../hook.md), +first. + +The following **direct** dependencies (cmd: `readelf -d piu`) are required: +* libXtst.so.6 +* libXrandr.so.2 +* libGL.so.1 +* libGLU.so.1 +* libdl.so.2 +* libavformat.so.51 +* libavcodec.so.51 +* libavutil.so.49 +* libusb-0.1.so.4 +* libpthread.so.0 +* librt.so.1 +* libstdc++.so.5 +* libm.so.6 +* libgcc_s.so.1 +* libc.so.6 +* libX11.so.6 + +As for method 1, when using Ubuntu, the dependencies can be found in the following packages: +* libc-bin (or gcc-multilib on a 64-bit platform) +* libx11-6 +* libusb-0.1-4 +* libasound2 + +## Data setup +A clean set of data won't work here. You need the decrypted data zip files and a patched executable that uses plain zip +files instead of encrypted ones. + +All access to files and folders are detoured to two folders which makes setting up everything easier. + +You need two main folders: +* `game` folder with the following contents + * All data zip files (e.g. `data0.zip`, `data1.zip`, ..., `encore.zip`) + * An empty file `FX` (if you run this on a FX or other cabinet with a widescreen monitor) +* `save ` folder. Contents are generated automatically if empty. + +## USB thumb drive/profile support +Without any patches, the game's way of handling USB thumb drives is incompatible to the kernels of the last years. +This is fixed by prohook but requires some additional configuration effort. + +1. Decide which physical USB ports of your machine you are going to use for the player 1 and player 2 sides. +1. Plug in *one* USB thumb drive into the player 1 port. Now you have to figure out two things: + 1. The device node the drive got assigned to by the kernel, e.g. `sdb`. For example, use the command `fdisk` or + `lsblk` for this and take a note. + 1. The USB bus and port number the thumb drive is plugged into. `ls -la /sys/block` shows you all currently + available block devices, including USB thumb drives. Find the thumb drive you plugged into the physical port and + check the path the symlink gets resolved to. For example: + `sdc -> ../devices/pci0000:00/0000:00:14.0/usb2/2-1/2-1:1.0/host7/target7:0:0/7:0:0:0/block/sdd` + You can identify the bus and port by taking a look at the part after the `usb2` directory here: `2-2`. The format + is `bus-port` which means that in this example the drive is plugged into port 1 of bus 2. Take a note of that. +1. Repeat the previous step for the physical port you consider using for player 2. +1. Create two folders that will serve as mount points for the two player sides in the `/mnt` directory, e.g. +`mkdir /mnt/0 && mkdir /mnt/1` (or re-using these folders if they already exist from other piu games). +1. Add the following entry to the `/etc/fstab` file on the machine you want to run the game on: +```text +/dev/sdb1 /mnt/0 auto noauto,owner 0 0 +/dev/sdc1 /mnt/1 auto noauto,owner 0 0 +``` +Note: This does not create a fixed assignment of `/dev/sdb1` or `/dev/sdc1` to a specific physical USB port. It just +ensures that any block device that gets enumerated as `/dev/sdb` will get its first partition mounted to `/mnt/0` +(same concept applies to `/dev/sdc1/`). +1. Modify the `hook.conf` file used with `prohook.so` by inserting the device nodes and bus-port combinations into the +respective fields, for example: +```text +patch.usb_profile.p1.bus_port=2-1 +patch.usb_profile.p2.bus_port=2-2 +# Note: No partitions, e.g. sdb1, here +patch.usb_profile.dev_nodes=sdb,sdc +``` + +Once you plug-in a USB thumb drive to the configured ports, it should show up on the correct player side. + +## Troubleshooting and FAQ +Make sure to also check the +[troubleshooting and FAQ section of the main hook readme](../hook.md#troubleshooting-and-faq). This covers various +things that apply to **all** hooks. The following sub-sections apply mainly to this hook. + +## Enable log output from the game +If you run into any issues, consider enabling the game's log output to console which might help you figuring out +what's going on. This can be done by editing the `data.Static.ini` file in the `data0.zip` file. Go to the section +`[Options-arcade]` and change the property `ShowLogOutput=0` to `ShowLogOutput=1`. + +## Switch screen aspect ratio: 4:3 and 16:9 +The game supports 4:3 and 16:9 aspect ratios. To enable 16:9 mode, ensure that an empty file called `FX` is placed +next to the data zip files inside the `game` directory. For 4:3 mode, simply delete the `FX` file if it exists. + +## ITG2 PIUIO kernel module hack +If you don't have the original kernel module installed and you are running a real PIUIO, you have to hook the +`ptapi-io-piuio-real.so` lib which implements the piuio api. The "emulation" part which simply calls back to a real +piuio driver takes care of handling the kernel hack path then. If you run on a real IO without hooking that module, +you won't get any inputs or outputs. + +## The game is crashing very early +The log might show something about "ptrace failing" and not being able to "load libformat". However, the actual problem +is probably not having all the necessary IO hardware connected (or emulation enabled). The game doesn't have any proper +error handling if either the PIUIO or the button IO is missing/not connected (or not emulated). \ No newline at end of file diff --git a/doc/hook/x2hook.md b/doc/hook/x2hook.md new file mode 100644 index 0000000..2fad993 --- /dev/null +++ b/doc/hook/x2hook.md @@ -0,0 +1,60 @@ +# x2hook: Exceed 2 +This readme covers any matters that are relevant for this hook, only. Anything that applies to **all** hooks is covered +in a [main hook readme file](../hook.md) including general data setup and a quick start guide. + +## Versions supported +* 102 + +Any other version won't work, period. The hook has to memory patch a weird bug causing the sound thread to crash. + +## Dependencies +Make sure to read the different methods of dependency resolution available in the [main hook readme file](../hook.md), +first. + +The following **direct** dependencies (cmd: `readelf -d piu`) are required: +* libGL.so.1 +* libGLU.so.1 +* libpthread.so.0 +* libmad.so.0 +* libasound.so.2 +* libz.so.1 +* libpng12.so.0 +* libmpeg2.so.0 +* libmpeg2convert.so.0 +* libusb-0.1.so.4 +* liblua50.so.5.0 +* liblualib50.so.5.0 +* libgcc_s.so.1 +* libc.so.6 +* libm.so.6 +* libX11.so.6 +* libstdc++.so.5 + +As for method 1, when using Ubuntu, the dependencies can be found in the following packages: +* libc-bin (or gcc-multilib on a 64-bit platform) +* libx11-6 +* zlib1g +* libusb-0.1-4 +* libasound2 + +## Data setup +In additional to the [general information applying to **all** hooks](../hook.md#data-setup), this game requires all +files and folders in the `game` folder to be in **UPPERCASE** on a case-sensitive file system. + +## Troubleshooting and FAQ +Make sure to also check the +[troubleshooting and FAQ section of the main hook readme](../hook.md#troubleshooting-and-faq). This covers various +things that apply to **all** hooks. The following sub-sections apply mainly to this hook. + +### Fully unlock the game +This hook has an option to fully unlock the game (well, unlock Canon-D Full Mix without entering the code because that's +the only permanent unlock) by setting the following property in the configuration file: +```text +game.force_unlock=1 +``` + +### Using the unlock option to unlock Canon-D full remix does not work when enabled +When the game cannot read from an existing `PIUEXCEED.INI` file, the settings are created in-memory and written to disk. +Therefore, the unlock mechanism of the hook cannot apply this to the contents in-memory but only when the +`PIUEXCEED.INI` file is re-loaded. To achieve this, once you started the game to create a new `PIUEXCEED.INI` with +default values, simply quit the game and restart. \ No newline at end of file diff --git a/doc/hook/zerohook.md b/doc/hook/zerohook.md new file mode 100644 index 0000000..b31f20b --- /dev/null +++ b/doc/hook/zerohook.md @@ -0,0 +1,50 @@ +# zerohook: Zero +This readme covers any matters that are relevant for this hook, only. Anything that applies to **all** hooks is covered +in a [main hook readme file](../hook.md) including general data setup and a quick start guide. + +## Versions supported +All known versions supported. + +## Dependencies +Make sure to read the different methods of dependency resolution available in the [main hook readme file](../hook.md), +first. + +The following **direct** dependencies (cmd: `readelf -d piu`) are required: +* libGL.so.1 +* libGLU.so.1 +* libpthread.so.0 +* libasound.so.2 +* libz.so.1 +* libpng12.so.0 +* libusb-0.1.so.4 +* liblua50.so.5.0 +* liblualib50.so.5.0 +* libgcc_s.so.1 +* libc.so.6 +* libm.so.6 +* libX11.so.6 +* libstdc++.so.5 + +As for method 1, when using Ubuntu, the dependencies can be found in the following packages: +* libc-bin (or gcc-multilib on a 64-bit platform) +* libx11-6 +* zlib1g +* libusb-0.1-4 +* libasound2 + +## Data setup +In additional to the [general information applying to **all** hooks](../hook.md#data-setup), this game requires all +files and folders in the `game` folder to be in **UPPERCASE** on a case-sensitive file system. + +## Troubleshooting and FAQ +Make sure to also check the +[troubleshooting and FAQ section of the main hook readme](../hook.md#troubleshooting-and-faq). This covers various +things that apply to **all** hooks. The following sub-sections apply mainly to this hook. + +### Fully unlock the game +This hook has an option to fully unlock the game by setting the following property in the configuration file: +```text +game.force_unlock=1 +``` + +This will unlock all missions on mission station as well as any locked songs and stepcharts. \ No newline at end of file diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh new file mode 100755 index 0000000..da24dd0 --- /dev/null +++ b/scripts/run-tests.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +for i in "$1"/bin/test-*; do + test="$(basename $i)" + + echo "Running test $test..." + + "$i" || exit 1 +done + +echo "All tests successful" + +exit 0 \ No newline at end of file diff --git a/src/api/ptapi/io/piubtn.h b/src/api/ptapi/io/piubtn.h new file mode 100644 index 0000000..62d8e60 --- /dev/null +++ b/src/api/ptapi/io/piubtn.h @@ -0,0 +1,105 @@ +/** + * Common API to use for any devices implementing piu (pro) button controls + * (e.g. keyboard emulation, custom IOs) + */ +#ifndef PTAPI_IO_PIUBTN_H +#define PTAPI_IO_PIUBTN_H + +#include +#include + +/** + * Button inputs of a single player + */ +struct ptapi_io_piubtn_inputs { + bool start; + bool back; + bool left; + bool right; +}; + +/** + * Button light outputs of a single player + */ +struct ptapi_io_piubtn_outputs { + bool start; + bool back; + bool left; + bool right; +}; + +typedef const char* (*ptapi_io_piubtn_ident_t)(void); +typedef bool (*ptapi_io_piubtn_open_t)(void); +typedef void (*ptapi_io_piubtn_close_t)(void); +typedef bool (*ptapi_io_piubtn_recv_t)(void); +typedef bool (*ptapi_io_piubtn_send_t)(void); +typedef void (*ptapi_io_piubtn_get_input_t)(uint8_t player, struct ptapi_io_piubtn_inputs* inputs); +typedef void (*ptapi_io_piubtn_set_output_t)(uint8_t player, const struct ptapi_io_piubtn_outputs* outputs); + +/** + * API functions in a struct to make them easier to handle and have less boilerplate + */ +struct ptapi_io_piubtn_api { + ptapi_io_piubtn_ident_t ident; + ptapi_io_piubtn_open_t open; + ptapi_io_piubtn_close_t close; + ptapi_io_piubtn_recv_t recv; + ptapi_io_piubtn_send_t send; + ptapi_io_piubtn_get_input_t get_input; + ptapi_io_piubtn_set_output_t set_output; +}; + +/** + * Return an identifier of the implementation. Used for logging and debugging. + * + * @return Identifier string for your implementation (e.g. "my-custom-btnio") + */ +const char* ptapi_io_piubtn_ident(void); + +/** + * Open the piubtn device + * + * Open any file handles, spawn threads and/or initialize your device or emulation code to be ready to accept further + * commands. + * + * @return True if opening/initializing your implementation was sucessful, false on failure + */ +bool ptapi_io_piubtn_open(void); + +/** + * Close and cleanup your device, shut down threads, free resources etc + */ +void ptapi_io_piubtn_close(void); + +/** + * Get _ALL_ readable data from your device in this single recv call. + * + * @return True on success, false on error + */ +bool ptapi_io_piubtn_recv(void); + +/** + * Send any output data (e.g. lights) to your device + * + * @return True on success, false on error + */ +bool ptapi_io_piubtn_send(void); + +/** + * Get buffered button input data + * + * @param player Player id (0, 1) + * @param inputs Pointer to buffer to copy inputs to. Every time this is invoked, this buffer is nulled. If you do not + * write any data to this, all inputs remain un-triggered. + */ +void ptapi_io_piubtn_get_input(uint8_t player, struct ptapi_io_piubtn_inputs* inputs); + +/** + * Set button light outputs (i.e. buffer them) + * + * @param player Player id (0, 1) + * @param outputs Pointer to output data to copy to your private buffers. + */ +void ptapi_io_piubtn_set_output(uint8_t player, const struct ptapi_io_piubtn_outputs* outputs); + +#endif \ No newline at end of file diff --git a/src/api/ptapi/io/piuio.h b/src/api/ptapi/io/piuio.h new file mode 100644 index 0000000..95964cd --- /dev/null +++ b/src/api/ptapi/io/piuio.h @@ -0,0 +1,163 @@ +/** + * Common API to use for any devices implementing (MK6) piuio controls + * (e.g. keyboard emulation, custom IOs) + */ +#ifndef PTAPI_IO_PIUIO_H +#define PTAPI_IO_PIUIO_H + +#include +#include + +/** + * Enum for pad input sensor groups (four sensors per button/panel) + */ +enum ptapi_io_piuio_sensor_group { + PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT = 0, + PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT = 1, + PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN = 2, + PTAPI_IO_PIUIO_SENSOR_GROUP_UP = 3, + PTAPI_IO_PIUIO_SENSOR_GROUP_NUM = 4, +}; + +/** + * Inputs of all arrows of a single pad (per sensor group) + */ +struct ptapi_io_piuio_pad_inputs { + bool lu; + bool ru; + bool cn; + bool ld; + bool rd; +}; + +/** + * System/operator inputs + */ +struct ptapi_io_piuio_sys_inputs { + bool test; + bool service; + bool clear; + bool coin; + bool coin2; +}; + +/** + * Pad light outputs + */ +struct ptapi_io_piuio_pad_outputs { + bool lu; + bool ru; + bool cn; + bool ld; + bool rd; +}; + +/** + * Cabinet light outputs + */ +struct ptapi_io_piuio_cab_outputs { + bool bass; + bool halo_r2; + bool halo_r1; + bool halo_l2; + bool halo_l1; +}; + +typedef const char* (*ptapi_io_piuio_ident_t)(void); +typedef bool (*ptapi_io_piuio_open_t)(void); +typedef void (*ptapi_io_piuio_close_t)(void); +typedef bool (*ptapi_io_piuio_recv_t)(void); +typedef bool (*ptapi_io_piuio_send_t)(void); +typedef void (*ptapi_io_piuio_get_input_pad_t)(uint8_t player, enum ptapi_io_piuio_sensor_group sensor_group, + struct ptapi_io_piuio_pad_inputs* inputs); +typedef void (*ptapi_io_piuio_get_input_sys_t)(struct ptapi_io_piuio_sys_inputs* inputs); +typedef void (*ptapi_io_piuio_set_output_pad_t)(uint8_t player, struct ptapi_io_piuio_pad_outputs* outputs); +typedef void (*ptapi_io_piuio_set_output_cab_t)(const struct ptapi_io_piuio_cab_outputs* outputs); + +/** + * API functions in a struct to make them easier to handle and have less boilerplate + */ +struct ptapi_io_piuio_api { + ptapi_io_piuio_ident_t ident; + ptapi_io_piuio_open_t open; + ptapi_io_piuio_close_t close; + ptapi_io_piuio_recv_t recv; + ptapi_io_piuio_send_t send; + ptapi_io_piuio_get_input_pad_t get_input_pad; + ptapi_io_piuio_get_input_sys_t get_input_sys; + ptapi_io_piuio_set_output_pad_t set_output_pad; + ptapi_io_piuio_set_output_cab_t set_output_cab; +}; + +/** + * Return an identifier of the implementation. Used for logging and debugging. + * + * @return Identifier string for your implementation (e.g. "my-custom-io") + */ +const char* ptapi_io_piuio_ident(void); + +/** + * Open the piuio device + * + * Open any file handles, spawn threads and/or initialize your device or emulation code to be ready to accept further + * commands. + * + * @return True if opening/initializing your implementation was sucessful, false on failure + */ +bool ptapi_io_piuio_open(void); + +/** + * Close and cleanup your device, shut down threads, free resources etc + */ +void ptapi_io_piuio_close(void); + +/** + * Get _ALL_ readable data from your device in this single recv call. If you have to address single sensores like the + * real piuio, you have to do this on a single call to this function. + * + * @return True on success, false on error + */ +bool ptapi_io_piuio_recv(void); + +/** + * Send any output data (e.g. lights) to your device + * + * @return True on success, false on error + */ +bool ptapi_io_piuio_send(void); + +/** + * Get buffered pad input data + * + * @param player Player id (0, 1) + * @param sensor_group Sensor group states to get + * @param inputs Pointer to buffer to copy inputs of the selected sensor group to. Every time this is invoked, this + * buffer is nulled. If you do not write any data to this, all inputs remain un-triggered. + */ +void ptapi_io_piuio_get_input_pad(uint8_t player, enum ptapi_io_piuio_sensor_group sensor_group, + struct ptapi_io_piuio_pad_inputs* inputs); + +/** + * Get buffered system (test, service, clear, coin) inputs + * + * @param inputs Pointer to buffer to copy inputs to. Every time this is invoked, this buffer is nulled. If you do not + * write any data to this, all inputs remain un-triggered. + */ +void ptapi_io_piuio_get_input_sys(struct ptapi_io_piuio_sys_inputs* inputs); + +/** + * Set pad light outputs (i.e. buffer them) + * + * @param player Player id (0, 1) + * @param outputs Pointer to output data to copy to your private buffers. + */ +void ptapi_io_piuio_set_output_pad(uint8_t player, const struct ptapi_io_piuio_pad_outputs* outputs); + +/** + * Set cabinet light outputs (i.e. buffer them) + * + * @param outputs Pointer to output data to copy to your private buffers. + */ +void ptapi_io_piuio_set_output_cab(const struct ptapi_io_piuio_cab_outputs* outputs); + +#endif \ No newline at end of file diff --git a/src/api/ptapi/io/x11-input-hook.h b/src/api/ptapi/io/x11-input-hook.h new file mode 100644 index 0000000..2a4d02a --- /dev/null +++ b/src/api/ptapi/io/x11-input-hook.h @@ -0,0 +1,39 @@ +/** + * Pumptools hooks into the X11 event dispatch loop to grab different events and exposes + * keyboard input events. This allows external modules to react to these inputs. + */ +#ifndef PTAPI_IO_X11_INPUT_HOOK_H +#define PTAPI_IO_X11_INPUT_HOOK_H + +#include + +/** + * Input hooking structure with dispatch functions. + * Set functions you do not use to NULL. + */ +struct ptapi_io_x11_input_hook_handler { + /** + * Dispatch the key press event + * + * @param key Key pressed + */ + void (*dispatch_key_press)(KeySym key); + + /** + * Dispatch the key release event + * + * @param key Key released + */ + void (*dispatch_key_release)(KeySym key); +}; + +typedef const struct ptapi_io_x11_input_hook_handler* (*ptapi_io_x11_input_handler_hook_t)(void); + +/** + * Get an input hook handler to receive keyboard inputs from the X11 event loop. + * + * @return Either a valid handler structure with dispatch functions or NULL if unused. + */ +const struct ptapi_io_x11_input_hook_handler* ptapi_io_x11_input_handler_hook(void); + +#endif diff --git a/src/imports/cmocka/cmocka.c b/src/imports/cmocka/cmocka.c new file mode 100644 index 0000000..e0d7cc4 --- /dev/null +++ b/src/imports/cmocka/cmocka.c @@ -0,0 +1,3522 @@ +/* + * Copyright 2008 Google Inc. + * Copyright 2014-2018 Andreas Schneider + * Copyright 2015 Jakub Hrozek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#ifdef HAVE_MALLOC_H +#include +#endif + +#ifdef HAVE_INTTYPES_H +#include +#endif + +#ifdef HAVE_SIGNAL_H +#include +#endif + +#ifdef HAVE_STRINGS_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This allows to add a platform specific header file. Some embedded platforms + * sometimes miss certain types and definitions. + * + * Example: + * + * typedef unsigned long int uintptr_t + * #define _UINTPTR_T 1 + * #define _UINTPTR_T_DEFINED 1 + */ +#ifdef CMOCKA_PLATFORM_INCLUDE +# include "cmocka_platform.h" +#endif /* CMOCKA_PLATFORM_INCLUDE */ + +#include +#include + +/* Size of guard bytes around dynamically allocated blocks. */ +#define MALLOC_GUARD_SIZE 16 +/* Pattern used to initialize guard blocks. */ +#define MALLOC_GUARD_PATTERN 0xEF +/* Pattern used to initialize memory allocated with test_malloc(). */ +#define MALLOC_ALLOC_PATTERN 0xBA +#define MALLOC_FREE_PATTERN 0xCD +/* Alignment of allocated blocks. NOTE: This must be base2. */ +#define MALLOC_ALIGNMENT sizeof(size_t) + +/* Printf formatting for source code locations. */ +#define SOURCE_LOCATION_FORMAT "%s:%u" + +#if defined(HAVE_GCC_THREAD_LOCAL_STORAGE) +# define CMOCKA_THREAD __thread +#elif defined(HAVE_MSVC_THREAD_LOCAL_STORAGE) +# define CMOCKA_THREAD __declspec(thread) +#else +# define CMOCKA_THREAD +#endif + +#ifdef HAVE_CLOCK_REALTIME +#define CMOCKA_CLOCK_GETTIME(clock_id, ts) clock_gettime((clock_id), (ts)) +#else +#define CMOCKA_CLOCK_GETTIME(clock_id, ts) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) < (b) ? (b) : (a)) +#endif + +/** + * POSIX has sigsetjmp/siglongjmp, while Windows only has setjmp/longjmp. + */ +#ifdef HAVE_SIGLONGJMP +# define cm_jmp_buf sigjmp_buf +# define cm_setjmp(env) sigsetjmp(env, 1) +# define cm_longjmp(env, val) siglongjmp(env, val) +#else +# define cm_jmp_buf jmp_buf +# define cm_setjmp(env) setjmp(env) +# define cm_longjmp(env, val) longjmp(env, val) +#endif + + +/* + * Declare and initialize the pointer member of ValuePointer variable name + * with ptr. + */ +#define declare_initialize_value_pointer_pointer(name, ptr) \ + ValuePointer name ; \ + name.value = 0; \ + name.x.pointer = (void*)(ptr) + +/* + * Declare and initialize the value member of ValuePointer variable name + * with val. + */ +#define declare_initialize_value_pointer_value(name, val) \ + ValuePointer name ; \ + name.value = val + +/* Cast a LargestIntegralType to pointer_type via a ValuePointer. */ +#define cast_largest_integral_type_to_pointer( \ + pointer_type, largest_integral_type) \ + ((pointer_type)((ValuePointer*)&(largest_integral_type))->x.pointer) + +/* Used to cast LargetIntegralType to void* and vice versa. */ +typedef union ValuePointer { + LargestIntegralType value; + struct { +#if defined(WORDS_BIGENDIAN) && (WORDS_SIZEOF_VOID_P == 4) + unsigned int padding; +#endif + void *pointer; + } x; +} ValuePointer; + +/* Doubly linked list node. */ +typedef struct ListNode { + const void *value; + int refcount; + struct ListNode *next; + struct ListNode *prev; +} ListNode; + +/* Debug information for malloc(). */ +struct MallocBlockInfoData { + void* block; /* Address of the block returned by malloc(). */ + size_t allocated_size; /* Total size of the allocated block. */ + size_t size; /* Request block size. */ + SourceLocation location; /* Where the block was allocated. */ + ListNode node; /* Node within list of all allocated blocks. */ +}; + +typedef union { + struct MallocBlockInfoData *data; + char *ptr; +} MallocBlockInfo; + +/* State of each test. */ +typedef struct TestState { + const ListNode *check_point; /* Check point of the test if there's a */ + /* setup function. */ + void *state; /* State associated with the test. */ +} TestState; + +/* Determines whether two values are the same. */ +typedef int (*EqualityFunction)(const void *left, const void *right); + +/* Value of a symbol and the place it was declared. */ +typedef struct SymbolValue { + SourceLocation location; + LargestIntegralType value; +} SymbolValue; + +/* + * Contains a list of values for a symbol. + * NOTE: Each structure referenced by symbol_values_list_head must have a + * SourceLocation as its' first member. + */ +typedef struct SymbolMapValue { + const char *symbol_name; + ListNode symbol_values_list_head; +} SymbolMapValue; + +/* Where a particular ordering was located and its symbol name */ +typedef struct FuncOrderingValue { + SourceLocation location; + const char * function; +} FuncOrderingValue; + +/* Used by list_free() to deallocate values referenced by list nodes. */ +typedef void (*CleanupListValue)(const void *value, void *cleanup_value_data); + +/* Structure used to check the range of integer types.a */ +typedef struct CheckIntegerRange { + CheckParameterEvent event; + LargestIntegralType minimum; + LargestIntegralType maximum; +} CheckIntegerRange; + +/* Structure used to check whether an integer value is in a set. */ +typedef struct CheckIntegerSet { + CheckParameterEvent event; + const LargestIntegralType *set; + size_t size_of_set; +} CheckIntegerSet; + +/* Used to check whether a parameter matches the area of memory referenced by + * this structure. */ +typedef struct CheckMemoryData { + CheckParameterEvent event; + const void *memory; + size_t size; +} CheckMemoryData; + +static ListNode* list_initialize(ListNode * const node); +static ListNode* list_add(ListNode * const head, ListNode *new_node); +static ListNode* list_add_value(ListNode * const head, const void *value, + const int count); +static ListNode* list_remove( + ListNode * const node, const CleanupListValue cleanup_value, + void * const cleanup_value_data); +static void list_remove_free( + ListNode * const node, const CleanupListValue cleanup_value, + void * const cleanup_value_data); +static int list_empty(const ListNode * const head); +static int list_find( + ListNode * const head, const void *value, + const EqualityFunction equal_func, ListNode **output); +static int list_first(ListNode * const head, ListNode **output); +static ListNode* list_free( + ListNode * const head, const CleanupListValue cleanup_value, + void * const cleanup_value_data); + +static void add_symbol_value( + ListNode * const symbol_map_head, const char * const symbol_names[], + const size_t number_of_symbol_names, const void* value, const int count); +static int get_symbol_value( + ListNode * const symbol_map_head, const char * const symbol_names[], + const size_t number_of_symbol_names, void **output); +static void free_value(const void *value, void *cleanup_value_data); +static void free_symbol_map_value( + const void *value, void *cleanup_value_data); +static void remove_always_return_values(ListNode * const map_head, + const size_t number_of_symbol_names); + +static size_t check_for_leftover_values_list(const ListNode * head, + const char * const error_message); + +static size_t check_for_leftover_values( + const ListNode * const map_head, const char * const error_message, + const size_t number_of_symbol_names); + +static void remove_always_return_values_from_list(ListNode * const map_head); + +/* + * This must be called at the beginning of a test to initialize some data + * structures. + */ +static void initialize_testing(const char *test_name); + +/* This must be called at the end of a test to free() allocated structures. */ +static void teardown_testing(const char *test_name); + +static enum cm_message_output cm_get_output(void); + +static int cm_error_message_enabled = 1; +static CMOCKA_THREAD char *cm_error_message; + +void cm_print_error(const char * const format, ...) CMOCKA_PRINTF_ATTRIBUTE(1, 2); + +/* + * Keeps track of the calling context returned by setenv() so that the fail() + * method can jump out of a test. + */ +static CMOCKA_THREAD cm_jmp_buf global_run_test_env; +static CMOCKA_THREAD int global_running_test = 0; + +/* Keeps track of the calling context returned by setenv() so that */ +/* mock_assert() can optionally jump back to expect_assert_failure(). */ +jmp_buf global_expect_assert_env; +int global_expecting_assert = 0; +const char *global_last_failed_assert = NULL; +static int global_skip_test; + +/* Keeps a map of the values that functions will have to return to provide */ +/* mocked interfaces. */ +static CMOCKA_THREAD ListNode global_function_result_map_head; +/* Location of the last mock value returned was declared. */ +static CMOCKA_THREAD SourceLocation global_last_mock_value_location; + +/* Keeps a map of the values that functions expect as parameters to their + * mocked interfaces. */ +static CMOCKA_THREAD ListNode global_function_parameter_map_head; +/* Location of last parameter value checked was declared. */ +static CMOCKA_THREAD SourceLocation global_last_parameter_location; + +/* List (acting as FIFO) of call ordering. */ +static CMOCKA_THREAD ListNode global_call_ordering_head; +/* Location of last call ordering that was declared. */ +static CMOCKA_THREAD SourceLocation global_last_call_ordering_location; + +/* List of all currently allocated blocks. */ +static CMOCKA_THREAD ListNode global_allocated_blocks; + +static enum cm_message_output global_msg_output = CM_OUTPUT_STDOUT; + +static const char *global_test_filter_pattern; + +static const char *global_skip_filter_pattern; + +#ifndef _WIN32 +/* Signals caught by exception_handler(). */ +static const int exception_signals[] = { + SIGFPE, + SIGILL, + SIGSEGV, +#ifdef SIGBUS + SIGBUS, +#endif +#ifdef SIGSYS + SIGSYS, +#endif +}; + +/* Default signal functions that should be restored after a test is complete. */ +typedef void (*SignalFunction)(int signal); +static SignalFunction default_signal_functions[ + ARRAY_SIZE(exception_signals)]; + +#else /* _WIN32 */ + +/* The default exception filter. */ +static LPTOP_LEVEL_EXCEPTION_FILTER previous_exception_filter; + +/* Fatal exceptions. */ +typedef struct ExceptionCodeInfo { + DWORD code; + const char* description; +} ExceptionCodeInfo; + +#define EXCEPTION_CODE_INFO(exception_code) {exception_code, #exception_code} + +static const ExceptionCodeInfo exception_codes[] = { + EXCEPTION_CODE_INFO(EXCEPTION_ACCESS_VIOLATION), + EXCEPTION_CODE_INFO(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), + EXCEPTION_CODE_INFO(EXCEPTION_DATATYPE_MISALIGNMENT), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_DENORMAL_OPERAND), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_DIVIDE_BY_ZERO), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_INEXACT_RESULT), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_INVALID_OPERATION), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_OVERFLOW), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_STACK_CHECK), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_UNDERFLOW), + EXCEPTION_CODE_INFO(EXCEPTION_GUARD_PAGE), + EXCEPTION_CODE_INFO(EXCEPTION_ILLEGAL_INSTRUCTION), + EXCEPTION_CODE_INFO(EXCEPTION_INT_DIVIDE_BY_ZERO), + EXCEPTION_CODE_INFO(EXCEPTION_INT_OVERFLOW), + EXCEPTION_CODE_INFO(EXCEPTION_INVALID_DISPOSITION), + EXCEPTION_CODE_INFO(EXCEPTION_INVALID_HANDLE), + EXCEPTION_CODE_INFO(EXCEPTION_IN_PAGE_ERROR), + EXCEPTION_CODE_INFO(EXCEPTION_NONCONTINUABLE_EXCEPTION), + EXCEPTION_CODE_INFO(EXCEPTION_PRIV_INSTRUCTION), + EXCEPTION_CODE_INFO(EXCEPTION_STACK_OVERFLOW), +}; +#endif /* !_WIN32 */ + +enum CMUnitTestStatus { + CM_TEST_NOT_STARTED, + CM_TEST_PASSED, + CM_TEST_FAILED, + CM_TEST_ERROR, + CM_TEST_SKIPPED, +}; + +struct CMUnitTestState { + const ListNode *check_point; /* Check point of the test if there's a setup function. */ + const struct CMUnitTest *test; /* Point to array element in the tests we get passed */ + void *state; /* State associated with the test */ + const char *error_message; /* The error messages by the test */ + enum CMUnitTestStatus status; /* PASSED, FAILED, ABORT ... */ + double runtime; /* Time calculations */ +}; + +/* Exit the currently executing test. */ +static void exit_test(const int quit_application) +{ + const char *env = getenv("CMOCKA_TEST_ABORT"); + int abort_test = 0; + + if (env != NULL && strlen(env) == 1) { + abort_test = (env[0] == '1'); + } + + if (global_skip_test == 0 && + abort_test == 1) { + print_error("%s", cm_error_message); + abort(); + } else if (global_running_test) { + cm_longjmp(global_run_test_env, 1); + } else if (quit_application) { + exit(-1); + } +} + +void _skip(const char * const file, const int line) +{ + cm_print_error(SOURCE_LOCATION_FORMAT ": Skipped!\n", file, line); + global_skip_test = 1; + exit_test(1); +} + +/* Initialize a SourceLocation structure. */ +static void initialize_source_location(SourceLocation * const location) { + assert_non_null(location); + location->file = NULL; + location->line = 0; +} + + +/* Determine whether a source location is currently set. */ +static int source_location_is_set(const SourceLocation * const location) { + assert_non_null(location); + return location->file && location->line; +} + + +/* Set a source location. */ +static void set_source_location( + SourceLocation * const location, const char * const file, + const int line) { + assert_non_null(location); + location->file = file; + location->line = line; +} + + +static int c_strreplace(char *src, + size_t src_len, + const char *pattern, + const char *repl, + int *str_replaced) +{ + char *p = NULL; + + p = strstr(src, pattern); + if (p == NULL) { + return -1; + } + + do { + size_t of = p - src; + size_t l = strlen(src); + size_t pl = strlen(pattern); + size_t rl = strlen(repl); + + /* overflow check */ + if (src_len <= l + MAX(pl, rl) + 1) { + return -1; + } + + if (rl != pl) { + memmove(src + of + rl, src + of + pl, l - of - pl + 1); + } + + memcpy(src + of, repl, rl); + + if (str_replaced != NULL) { + *str_replaced = 1; + } + p = strstr(src, pattern); + } while (p != NULL); + + return 0; +} + +static int c_strmatch(const char *str, const char *pattern) +{ + int ok; + + if (str == NULL || pattern == NULL) { + return 0; + } + + for (;;) { + /* Check if pattern is done */ + if (*pattern == '\0') { + /* If string is at the end, we're good */ + if (*str == '\0') { + return 1; + } + + return 0; + } + + if (*pattern == '*') { + /* Move on */ + pattern++; + + /* If we are at the end, everything is fine */ + if (*pattern == '\0') { + return 1; + } + + /* Try to match each position */ + for (; *str != '\0'; str++) { + ok = c_strmatch(str, pattern); + if (ok) { + return 1; + } + } + + /* No match */ + return 0; + } + + /* If we are at the end, leave */ + if (*str == '\0') { + return 0; + } + + /* Check if we have a single wildcard or matching char */ + if (*pattern != '?' && *str != *pattern) { + return 0; + } + + /* Move string and pattern */ + str++; + pattern++; + } + + return 0; +} + +/* Create function results and expected parameter lists. */ +void initialize_testing(const char *test_name) { + (void)test_name; + list_initialize(&global_function_result_map_head); + initialize_source_location(&global_last_mock_value_location); + list_initialize(&global_function_parameter_map_head); + initialize_source_location(&global_last_parameter_location); + list_initialize(&global_call_ordering_head); + initialize_source_location(&global_last_parameter_location); +} + + +static void fail_if_leftover_values(const char *test_name) { + int error_occurred = 0; + (void)test_name; + remove_always_return_values(&global_function_result_map_head, 1); + if (check_for_leftover_values( + &global_function_result_map_head, + "%s() has remaining non-returned values.\n", 1)) { + error_occurred = 1; + } + + remove_always_return_values(&global_function_parameter_map_head, 2); + if (check_for_leftover_values( + &global_function_parameter_map_head, + "'%s' parameter still has values that haven't been checked.\n", + 2)) { + error_occurred = 1; + } + + remove_always_return_values_from_list(&global_call_ordering_head); + if (check_for_leftover_values_list(&global_call_ordering_head, + "%s function was expected to be called but was not.\n")) { + error_occurred = 1; + } + if (error_occurred) { + exit_test(1); + } +} + + +static void teardown_testing(const char *test_name) { + (void)test_name; + list_free(&global_function_result_map_head, free_symbol_map_value, + (void*)0); + initialize_source_location(&global_last_mock_value_location); + list_free(&global_function_parameter_map_head, free_symbol_map_value, + (void*)1); + initialize_source_location(&global_last_parameter_location); + list_free(&global_call_ordering_head, free_value, + (void*)0); + initialize_source_location(&global_last_call_ordering_location); +} + +/* Initialize a list node. */ +static ListNode* list_initialize(ListNode * const node) { + node->value = NULL; + node->next = node; + node->prev = node; + node->refcount = 1; + return node; +} + + +/* + * Adds a value at the tail of a given list. + * The node referencing the value is allocated from the heap. + */ +static ListNode* list_add_value(ListNode * const head, const void *value, + const int refcount) { + ListNode * const new_node = (ListNode*)malloc(sizeof(ListNode)); + assert_non_null(head); + assert_non_null(value); + new_node->value = value; + new_node->refcount = refcount; + return list_add(head, new_node); +} + + +/* Add new_node to the end of the list. */ +static ListNode* list_add(ListNode * const head, ListNode *new_node) { + assert_non_null(head); + assert_non_null(new_node); + new_node->next = head; + new_node->prev = head->prev; + head->prev->next = new_node; + head->prev = new_node; + return new_node; +} + + +/* Remove a node from a list. */ +static ListNode* list_remove( + ListNode * const node, const CleanupListValue cleanup_value, + void * const cleanup_value_data) { + assert_non_null(node); + node->prev->next = node->next; + node->next->prev = node->prev; + if (cleanup_value) { + cleanup_value(node->value, cleanup_value_data); + } + return node; +} + + +/* Remove a list node from a list and free the node. */ +static void list_remove_free( + ListNode * const node, const CleanupListValue cleanup_value, + void * const cleanup_value_data) { + assert_non_null(node); + free(list_remove(node, cleanup_value, cleanup_value_data)); +} + + +/* + * Frees memory kept by a linked list The cleanup_value function is called for + * every "value" field of nodes in the list, except for the head. In addition + * to each list value, cleanup_value_data is passed to each call to + * cleanup_value. The head of the list is not deallocated. + */ +static ListNode* list_free( + ListNode * const head, const CleanupListValue cleanup_value, + void * const cleanup_value_data) { + assert_non_null(head); + while (!list_empty(head)) { + list_remove_free(head->next, cleanup_value, cleanup_value_data); + } + return head; +} + + +/* Determine whether a list is empty. */ +static int list_empty(const ListNode * const head) { + assert_non_null(head); + return head->next == head; +} + + +/* + * Find a value in the list using the equal_func to compare each node with the + * value. + */ +static int list_find(ListNode * const head, const void *value, + const EqualityFunction equal_func, ListNode **output) { + ListNode *current; + assert_non_null(head); + for (current = head->next; current != head; current = current->next) { + if (equal_func(current->value, value)) { + *output = current; + return 1; + } + } + return 0; +} + +/* Returns the first node of a list */ +static int list_first(ListNode * const head, ListNode **output) { + ListNode *target_node = NULL; + assert_non_null(head); + if (list_empty(head)) { + return 0; + } + target_node = head->next; + *output = target_node; + return 1; +} + + +/* Deallocate a value referenced by a list. */ +static void free_value(const void *value, void *cleanup_value_data) { + (void)cleanup_value_data; + assert_non_null(value); + free((void*)value); +} + + +/* Releases memory associated to a symbol_map_value. */ +static void free_symbol_map_value(const void *value, + void *cleanup_value_data) { + SymbolMapValue * const map_value = (SymbolMapValue*)value; + const LargestIntegralType children = cast_ptr_to_largest_integral_type(cleanup_value_data); + assert_non_null(value); + list_free(&map_value->symbol_values_list_head, + children ? free_symbol_map_value : free_value, + (void *) ((uintptr_t)children - 1)); + free(map_value); +} + + +/* + * Determine whether a symbol name referenced by a symbol_map_value matches the + * specified function name. + */ +static int symbol_names_match(const void *map_value, const void *symbol) { + return !strcmp(((SymbolMapValue*)map_value)->symbol_name, + (const char*)symbol); +} + +/* + * Adds a value to the queue of values associated with the given hierarchy of + * symbols. It's assumed value is allocated from the heap. + */ +static void add_symbol_value(ListNode * const symbol_map_head, + const char * const symbol_names[], + const size_t number_of_symbol_names, + const void* value, const int refcount) { + const char* symbol_name; + ListNode *target_node; + SymbolMapValue *target_map_value; + assert_non_null(symbol_map_head); + assert_non_null(symbol_names); + assert_true(number_of_symbol_names); + symbol_name = symbol_names[0]; + + if (!list_find(symbol_map_head, symbol_name, symbol_names_match, + &target_node)) { + SymbolMapValue * const new_symbol_map_value = + (SymbolMapValue*)malloc(sizeof(*new_symbol_map_value)); + new_symbol_map_value->symbol_name = symbol_name; + list_initialize(&new_symbol_map_value->symbol_values_list_head); + target_node = list_add_value(symbol_map_head, new_symbol_map_value, + 1); + } + + target_map_value = (SymbolMapValue*)target_node->value; + if (number_of_symbol_names == 1) { + list_add_value(&target_map_value->symbol_values_list_head, + value, refcount); + } else { + add_symbol_value(&target_map_value->symbol_values_list_head, + &symbol_names[1], number_of_symbol_names - 1, value, + refcount); + } +} + + +/* + * Gets the next value associated with the given hierarchy of symbols. + * The value is returned as an output parameter with the function returning the + * node's old refcount value if a value is found, 0 otherwise. This means that + * a return value of 1 indicates the node was just removed from the list. + */ +static int get_symbol_value( + ListNode * const head, const char * const symbol_names[], + const size_t number_of_symbol_names, void **output) { + const char* symbol_name = NULL; + ListNode *target_node = NULL; + assert_non_null(head); + assert_non_null(symbol_names); + assert_true(number_of_symbol_names); + assert_non_null(output); + symbol_name = symbol_names[0]; + + if (list_find(head, symbol_name, symbol_names_match, &target_node)) { + SymbolMapValue *map_value = NULL; + ListNode *child_list = NULL; + int return_value = 0; + assert_non_null(target_node); + assert_non_null(target_node->value); + + map_value = (SymbolMapValue*)target_node->value; + child_list = &map_value->symbol_values_list_head; + + if (number_of_symbol_names == 1) { + ListNode *value_node = NULL; + return_value = list_first(child_list, &value_node); + assert_true(return_value); + /* Add a check to silence clang analyzer */ + if (return_value == 0) { + goto out; + } + *output = (void*) value_node->value; + return_value = value_node->refcount; + if (value_node->refcount - 1 == 0) { + list_remove_free(value_node, NULL, NULL); + } else if (value_node->refcount > WILL_RETURN_ONCE) { + --value_node->refcount; + } + } else { + return_value = get_symbol_value( + child_list, &symbol_names[1], number_of_symbol_names - 1, + output); + } + if (list_empty(child_list)) { + list_remove_free(target_node, free_symbol_map_value, (void*)0); + } + return return_value; + } +out: + cm_print_error("No entries for symbol %s.\n", symbol_name); + return 0; +} + +/** + * Taverse a list of nodes and remove first symbol value in list that has a + * refcount < -1 (i.e. should always be returned and has been returned at + * least once). + */ + +static void remove_always_return_values_from_list(ListNode * const map_head) +{ + ListNode * current = NULL; + ListNode * next = NULL; + assert_non_null(map_head); + + for (current = map_head->next, next = current->next; + current != map_head; + current = next, next = current->next) { + if (current->refcount < -1) { + list_remove_free(current, free_value, NULL); + } + } +} + +/* + * Traverse down a tree of symbol values and remove the first symbol value + * in each branch that has a refcount < -1 (i.e should always be returned + * and has been returned at least once). + */ +static void remove_always_return_values(ListNode * const map_head, + const size_t number_of_symbol_names) { + ListNode *current; + assert_non_null(map_head); + assert_true(number_of_symbol_names); + current = map_head->next; + while (current != map_head) { + SymbolMapValue * const value = (SymbolMapValue*)current->value; + ListNode * const next = current->next; + ListNode *child_list; + assert_non_null(value); + child_list = &value->symbol_values_list_head; + + if (!list_empty(child_list)) { + if (number_of_symbol_names == 1) { + ListNode * const child_node = child_list->next; + /* If this item has been returned more than once, free it. */ + if (child_node->refcount < -1) { + list_remove_free(child_node, free_value, NULL); + } + } else { + remove_always_return_values(child_list, + number_of_symbol_names - 1); + } + } + + if (list_empty(child_list)) { + list_remove_free(current, free_value, NULL); + } + current = next; + } +} + +static size_t check_for_leftover_values_list(const ListNode * head, + const char * const error_message) +{ + ListNode *child_node; + size_t leftover_count = 0; + if (!list_empty(head)) + { + for (child_node = head->next; child_node != head; + child_node = child_node->next, ++leftover_count) { + const FuncOrderingValue *const o = + (const FuncOrderingValue*) child_node->value; + cm_print_error(error_message, "%s", o->function); + cm_print_error(SOURCE_LOCATION_FORMAT + ": note: remaining item was declared here\n", + o->location.file, o->location.line); + } + } + return leftover_count; +} + +/* + * Checks if there are any leftover values set up by the test that were never + * retrieved through execution, and fail the test if that is the case. + */ +static size_t check_for_leftover_values( + const ListNode * const map_head, const char * const error_message, + const size_t number_of_symbol_names) { + const ListNode *current; + size_t symbols_with_leftover_values = 0; + assert_non_null(map_head); + assert_true(number_of_symbol_names); + + for (current = map_head->next; current != map_head; + current = current->next) { + const SymbolMapValue * const value = + (SymbolMapValue*)current->value; + const ListNode *child_list; + assert_non_null(value); + child_list = &value->symbol_values_list_head; + + if (!list_empty(child_list)) { + if (number_of_symbol_names == 1) { + const ListNode *child_node; + cm_print_error(error_message, "%s", value->symbol_name); + + for (child_node = child_list->next; child_node != child_list; + child_node = child_node->next) { + const SourceLocation * const location = + (const SourceLocation*)child_node->value; + cm_print_error(SOURCE_LOCATION_FORMAT + ": note: remaining item was declared here\n", + location->file, location->line); + } + } else { + cm_print_error("%s: ", value->symbol_name); + check_for_leftover_values(child_list, error_message, + number_of_symbol_names - 1); + } + symbols_with_leftover_values ++; + } + } + return symbols_with_leftover_values; +} + + +/* Get the next return value for the specified mock function. */ +LargestIntegralType _mock(const char * const function, const char* const file, + const int line) { + void *result; + const int rc = get_symbol_value(&global_function_result_map_head, + &function, 1, &result); + if (rc) { + SymbolValue * const symbol = (SymbolValue*)result; + const LargestIntegralType value = symbol->value; + global_last_mock_value_location = symbol->location; + if (rc == 1) { + free(symbol); + } + return value; + } else { + cm_print_error(SOURCE_LOCATION_FORMAT ": error: Could not get value " + "to mock function %s\n", file, line, function); + if (source_location_is_set(&global_last_mock_value_location)) { + cm_print_error(SOURCE_LOCATION_FORMAT + ": note: Previously returned mock value was declared here\n", + global_last_mock_value_location.file, + global_last_mock_value_location.line); + } else { + cm_print_error("There were no previously returned mock values for " + "this test.\n"); + } + exit_test(1); + } + return 0; +} + +/* Ensure that function is being called in proper order */ +void _function_called(const char *const function, + const char *const file, + const int line) +{ + ListNode *first_value_node = NULL; + ListNode *value_node = NULL; + int rc; + + rc = list_first(&global_call_ordering_head, &value_node); + first_value_node = value_node; + if (rc) { + FuncOrderingValue *expected_call; + int cmp; + + expected_call = (FuncOrderingValue *)value_node->value; + + cmp = strcmp(expected_call->function, function); + if (value_node->refcount < -1) { + /* + * Search through value nodes until either function is found or + * encounter a non-zero refcount greater than -2 + */ + if (cmp != 0) { + value_node = value_node->next; + expected_call = (FuncOrderingValue *)value_node->value; + + cmp = strcmp(expected_call->function, function); + while (value_node->refcount < -1 && + cmp != 0 && + value_node != first_value_node->prev) { + value_node = value_node->next; + if (value_node == NULL) { + break; + } + expected_call = (FuncOrderingValue *)value_node->value; + if (expected_call == NULL) { + continue; + } + cmp = strcmp(expected_call->function, function); + } + + if (expected_call == NULL || value_node == first_value_node->prev) { + cm_print_error(SOURCE_LOCATION_FORMAT + ": error: No expected mock calls matching " + "called() invocation in %s", + file, line, + function); + exit_test(1); + } + } + } + + if (cmp == 0) { + if (value_node->refcount > -2 && --value_node->refcount == 0) { + list_remove_free(value_node, free_value, NULL); + } + } else { + cm_print_error(SOURCE_LOCATION_FORMAT + ": error: Expected call to %s but received called() " + "in %s\n", + file, line, + expected_call->function, + function); + exit_test(1); + } + } else { + cm_print_error(SOURCE_LOCATION_FORMAT + ": error: No mock calls expected but called() was " + "invoked in %s\n", + file, line, + function); + exit_test(1); + } +} + +/* Add a return value for the specified mock function name. */ +void _will_return(const char * const function_name, const char * const file, + const int line, const LargestIntegralType value, + const int count) { + SymbolValue * const return_value = + (SymbolValue*)malloc(sizeof(*return_value)); + assert_true(count != 0); + return_value->value = value; + set_source_location(&return_value->location, file, line); + add_symbol_value(&global_function_result_map_head, &function_name, 1, + return_value, count); +} + + +/* + * Add a custom parameter checking function. If the event parameter is NULL + * the event structure is allocated internally by this function. If event + * parameter is provided it must be allocated on the heap and doesn't need to + * be deallocated by the caller. + */ +void _expect_check( + const char* const function, const char* const parameter, + const char* const file, const int line, + const CheckParameterValue check_function, + const LargestIntegralType check_data, + CheckParameterEvent * const event, const int count) { + CheckParameterEvent * const check = + event ? event : (CheckParameterEvent*)malloc(sizeof(*check)); + const char* symbols[] = {function, parameter}; + check->parameter_name = parameter; + check->check_value = check_function; + check->check_value_data = check_data; + set_source_location(&check->location, file, line); + add_symbol_value(&global_function_parameter_map_head, symbols, 2, check, + count); +} + +/* + * Add an call expectations that a particular function is called correctly. + * This is used for code under test that makes calls to several functions + * in depended upon components (mocks). + */ + +void _expect_function_call( + const char * const function_name, + const char * const file, + const int line, + const int count) +{ + FuncOrderingValue *ordering; + + assert_non_null(function_name); + assert_non_null(file); + assert_true(count != 0); + + ordering = (FuncOrderingValue *)malloc(sizeof(*ordering)); + + set_source_location(&ordering->location, file, line); + ordering->function = function_name; + + list_add_value(&global_call_ordering_head, ordering, count); +} + +/* Returns 1 if the specified float values are equal, else returns 0. */ +static int float_compare(const float left, + const float right, + const float epsilon) { + float absLeft; + float absRight; + float largest; + float relDiff; + + float diff = left - right; + diff = (diff >= 0.f) ? diff : -diff; + + // Check if the numbers are really close -- needed + // when comparing numbers near zero. + if (diff <= epsilon) { + return 1; + } + + absLeft = (left >= 0.f) ? left : -left; + absRight = (right >= 0.f) ? right : -right; + + largest = (absRight > absLeft) ? absRight : absLeft; + relDiff = largest * FLT_EPSILON; + + if (diff > relDiff) { + return 0; + } + return 1; +} + +/* Returns 1 if the specified float values are equal. If the values are not equal + * an error is displayed and 0 is returned. */ +static int float_values_equal_display_error(const float left, + const float right, + const float epsilon) { + const int equal = float_compare(left, right, epsilon); + if (!equal) { + cm_print_error(FloatPrintfFormat " != " + FloatPrintfFormat "\n", left, right); + } + return equal; +} + +/* Returns 1 if the specified float values are different. If the values are equal + * an error is displayed and 0 is returned. */ +static int float_values_not_equal_display_error(const float left, + const float right, + const float epsilon) { + const int not_equal = (float_compare(left, right, epsilon) == 0); + if (!not_equal) { + cm_print_error(FloatPrintfFormat " == " + FloatPrintfFormat "\n", left, right); + } + return not_equal; +} + +/* Returns 1 if the specified values are equal. If the values are not equal + * an error is displayed and 0 is returned. */ +static int values_equal_display_error(const LargestIntegralType left, + const LargestIntegralType right) { + const int equal = left == right; + if (!equal) { + cm_print_error(LargestIntegralTypePrintfFormat " != " + LargestIntegralTypePrintfFormat "\n", left, right); + } + return equal; +} + +/* + * Returns 1 if the specified values are not equal. If the values are equal + * an error is displayed and 0 is returned. */ +static int values_not_equal_display_error(const LargestIntegralType left, + const LargestIntegralType right) { + const int not_equal = left != right; + if (!not_equal) { + cm_print_error(LargestIntegralTypePrintfFormat " == " + LargestIntegralTypePrintfFormat "\n", left, right); + } + return not_equal; +} + + +/* + * Determine whether value is contained within check_integer_set. + * If invert is 0 and the value is in the set 1 is returned, otherwise 0 is + * returned and an error is displayed. If invert is 1 and the value is not + * in the set 1 is returned, otherwise 0 is returned and an error is + * displayed. + */ +static int value_in_set_display_error( + const LargestIntegralType value, + const CheckIntegerSet * const check_integer_set, const int invert) { + int succeeded = invert; + assert_non_null(check_integer_set); + { + const LargestIntegralType * const set = check_integer_set->set; + const size_t size_of_set = check_integer_set->size_of_set; + size_t i; + for (i = 0; i < size_of_set; i++) { + if (set[i] == value) { + /* If invert = 0 and item is found, succeeded = 1. */ + /* If invert = 1 and item is found, succeeded = 0. */ + succeeded = !succeeded; + break; + } + } + if (succeeded) { + return 1; + } + cm_print_error(LargestIntegralTypePrintfFormatDecimal + " is %sin the set (", + value, invert ? "" : "not "); + for (i = 0; i < size_of_set; i++) { + cm_print_error(LargestIntegralTypePrintfFormat ", ", set[i]); + } + cm_print_error(")\n"); + } + return 0; +} + + +/* + * Determine whether a value is within the specified range. If the value is + * within the specified range 1 is returned. If the value isn't within the + * specified range an error is displayed and 0 is returned. + */ +static int integer_in_range_display_error( + const LargestIntegralType value, const LargestIntegralType range_min, + const LargestIntegralType range_max) { + if (value >= range_min && value <= range_max) { + return 1; + } + cm_print_error(LargestIntegralTypePrintfFormatDecimal + " is not within the range " + LargestIntegralTypePrintfFormatDecimal "-" + LargestIntegralTypePrintfFormatDecimal "\n", + value, range_min, range_max); + return 0; +} + + +/* + * Determine whether a value is within the specified range. If the value + * is not within the range 1 is returned. If the value is within the + * specified range an error is displayed and zero is returned. + */ +static int integer_not_in_range_display_error( + const LargestIntegralType value, const LargestIntegralType range_min, + const LargestIntegralType range_max) { + if (value < range_min || value > range_max) { + return 1; + } + cm_print_error(LargestIntegralTypePrintfFormatDecimal + " is within the range " + LargestIntegralTypePrintfFormatDecimal "-" + LargestIntegralTypePrintfFormatDecimal "\n", + value, range_min, range_max); + return 0; +} + + +/* + * Determine whether the specified strings are equal. If the strings are equal + * 1 is returned. If they're not equal an error is displayed and 0 is + * returned. + */ +static int string_equal_display_error( + const char * const left, const char * const right) { + if (strcmp(left, right) == 0) { + return 1; + } + cm_print_error("\"%s\" != \"%s\"\n", left, right); + return 0; +} + + +/* + * Determine whether the specified strings are equal. If the strings are not + * equal 1 is returned. If they're not equal an error is displayed and 0 is + * returned + */ +static int string_not_equal_display_error( + const char * const left, const char * const right) { + if (strcmp(left, right) != 0) { + return 1; + } + cm_print_error("\"%s\" == \"%s\"\n", left, right); + return 0; +} + + +/* + * Determine whether the specified areas of memory are equal. If they're equal + * 1 is returned otherwise an error is displayed and 0 is returned. + */ +static int memory_equal_display_error(const char* const a, const char* const b, + const size_t size) { + size_t differences = 0; + size_t i; + for (i = 0; i < size; i++) { + const char l = a[i]; + const char r = b[i]; + if (l != r) { + if (differences < 16) { + cm_print_error("difference at offset %" PRIdS " 0x%02x 0x%02x\n", + i, l, r); + } + differences ++; + } + } + if (differences > 0) { + if (differences >= 16) { + cm_print_error("...\n"); + } + cm_print_error("%"PRIdS " bytes of %p and %p differ\n", + differences, (void *)a, (void *)b); + return 0; + } + return 1; +} + + +/* + * Determine whether the specified areas of memory are not equal. If they're + * not equal 1 is returned otherwise an error is displayed and 0 is + * returned. + */ +static int memory_not_equal_display_error( + const char* const a, const char* const b, const size_t size) { + size_t same = 0; + size_t i; + for (i = 0; i < size; i++) { + const char l = a[i]; + const char r = b[i]; + if (l == r) { + same ++; + } + } + if (same == size) { + cm_print_error("%"PRIdS "bytes of %p and %p the same\n", + same, (void *)a, (void *)b); + return 0; + } + return 1; +} + + +/* CheckParameterValue callback to check whether a value is within a set. */ +static int check_in_set(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return value_in_set_display_error(value, + cast_largest_integral_type_to_pointer(CheckIntegerSet*, + check_value_data), 0); +} + + +/* CheckParameterValue callback to check whether a value isn't within a set. */ +static int check_not_in_set(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return value_in_set_display_error(value, + cast_largest_integral_type_to_pointer(CheckIntegerSet*, + check_value_data), 1); +} + + +/* Create the callback data for check_in_set() or check_not_in_set() and + * register a check event. */ +static void expect_set( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType values[], const size_t number_of_values, + const CheckParameterValue check_function, const int count) { + CheckIntegerSet * const check_integer_set = + (CheckIntegerSet*)malloc(sizeof(*check_integer_set) + + (sizeof(values[0]) * number_of_values)); + LargestIntegralType * const set = (LargestIntegralType*)( + check_integer_set + 1); + declare_initialize_value_pointer_pointer(check_data, check_integer_set); + assert_non_null(values); + assert_true(number_of_values); + memcpy(set, values, number_of_values * sizeof(values[0])); + check_integer_set->set = set; + check_integer_set->size_of_set = number_of_values; + _expect_check( + function, parameter, file, line, check_function, + check_data.value, &check_integer_set->event, count); +} + + +/* Add an event to check whether a value is in a set. */ +void _expect_in_set( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType values[], const size_t number_of_values, + const int count) { + expect_set(function, parameter, file, line, values, number_of_values, + check_in_set, count); +} + + +/* Add an event to check whether a value isn't in a set. */ +void _expect_not_in_set( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType values[], const size_t number_of_values, + const int count) { + expect_set(function, parameter, file, line, values, number_of_values, + check_not_in_set, count); +} + + +/* CheckParameterValue callback to check whether a value is within a range. */ +static int check_in_range(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + CheckIntegerRange * const check_integer_range = + cast_largest_integral_type_to_pointer(CheckIntegerRange*, + check_value_data); + assert_non_null(check_integer_range); + return integer_in_range_display_error(value, check_integer_range->minimum, + check_integer_range->maximum); +} + + +/* CheckParameterValue callback to check whether a value is not within a range. */ +static int check_not_in_range(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + CheckIntegerRange * const check_integer_range = + cast_largest_integral_type_to_pointer(CheckIntegerRange*, + check_value_data); + assert_non_null(check_integer_range); + return integer_not_in_range_display_error( + value, check_integer_range->minimum, check_integer_range->maximum); +} + + +/* Create the callback data for check_in_range() or check_not_in_range() and + * register a check event. */ +static void expect_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, + const CheckParameterValue check_function, const int count) { + CheckIntegerRange * const check_integer_range = + (CheckIntegerRange*)malloc(sizeof(*check_integer_range)); + declare_initialize_value_pointer_pointer(check_data, check_integer_range); + check_integer_range->minimum = minimum; + check_integer_range->maximum = maximum; + _expect_check(function, parameter, file, line, check_function, + check_data.value, &check_integer_range->event, count); +} + + +/* Add an event to determine whether a parameter is within a range. */ +void _expect_in_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, + const int count) { + expect_range(function, parameter, file, line, minimum, maximum, + check_in_range, count); +} + + +/* Add an event to determine whether a parameter is not within a range. */ +void _expect_not_in_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, + const int count) { + expect_range(function, parameter, file, line, minimum, maximum, + check_not_in_range, count); +} + + +/* CheckParameterValue callback to check whether a value is equal to an + * expected value. */ +static int check_value(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return values_equal_display_error(value, check_value_data); +} + + +/* Add an event to check a parameter equals an expected value. */ +void _expect_value( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType value, const int count) { + _expect_check(function, parameter, file, line, check_value, value, NULL, + count); +} + + +/* CheckParameterValue callback to check whether a value is not equal to an + * expected value. */ +static int check_not_value(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return values_not_equal_display_error(value, check_value_data); +} + + +/* Add an event to check a parameter is not equal to an expected value. */ +void _expect_not_value( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType value, const int count) { + _expect_check(function, parameter, file, line, check_not_value, value, + NULL, count); +} + + +/* CheckParameterValue callback to check whether a parameter equals a string. */ +static int check_string(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return string_equal_display_error( + cast_largest_integral_type_to_pointer(char*, value), + cast_largest_integral_type_to_pointer(char*, check_value_data)); +} + + +/* Add an event to check whether a parameter is equal to a string. */ +void _expect_string( + const char* const function, const char* const parameter, + const char* const file, const int line, const char* string, + const int count) { + declare_initialize_value_pointer_pointer(string_pointer, + discard_const(string)); + _expect_check(function, parameter, file, line, check_string, + string_pointer.value, NULL, count); +} + + +/* CheckParameterValue callback to check whether a parameter is not equals to + * a string. */ +static int check_not_string(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return string_not_equal_display_error( + cast_largest_integral_type_to_pointer(char*, value), + cast_largest_integral_type_to_pointer(char*, check_value_data)); +} + + +/* Add an event to check whether a parameter is not equal to a string. */ +void _expect_not_string( + const char* const function, const char* const parameter, + const char* const file, const int line, const char* string, + const int count) { + declare_initialize_value_pointer_pointer(string_pointer, + discard_const(string)); + _expect_check(function, parameter, file, line, check_not_string, + string_pointer.value, NULL, count); +} + +/* CheckParameterValue callback to check whether a parameter equals an area of + * memory. */ +static int check_memory(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + CheckMemoryData * const check = cast_largest_integral_type_to_pointer( + CheckMemoryData*, check_value_data); + assert_non_null(check); + return memory_equal_display_error( + cast_largest_integral_type_to_pointer(const char*, value), + (const char*)check->memory, check->size); +} + + +/* Create the callback data for check_memory() or check_not_memory() and + * register a check event. */ +static void expect_memory_setup( + const char* const function, const char* const parameter, + const char* const file, const int line, + const void * const memory, const size_t size, + const CheckParameterValue check_function, const int count) { + CheckMemoryData * const check_data = + (CheckMemoryData*)malloc(sizeof(*check_data) + size); + void * const mem = (void*)(check_data + 1); + declare_initialize_value_pointer_pointer(check_data_pointer, check_data); + assert_non_null(memory); + assert_true(size); + memcpy(mem, memory, size); + check_data->memory = mem; + check_data->size = size; + _expect_check(function, parameter, file, line, check_function, + check_data_pointer.value, &check_data->event, count); +} + + +/* Add an event to check whether a parameter matches an area of memory. */ +void _expect_memory( + const char* const function, const char* const parameter, + const char* const file, const int line, const void* const memory, + const size_t size, const int count) { + expect_memory_setup(function, parameter, file, line, memory, size, + check_memory, count); +} + + +/* CheckParameterValue callback to check whether a parameter is not equal to + * an area of memory. */ +static int check_not_memory(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + CheckMemoryData * const check = cast_largest_integral_type_to_pointer( + CheckMemoryData*, check_value_data); + assert_non_null(check); + return memory_not_equal_display_error( + cast_largest_integral_type_to_pointer(const char*, value), + (const char*)check->memory, + check->size); +} + + +/* Add an event to check whether a parameter doesn't match an area of memory. */ +void _expect_not_memory( + const char* const function, const char* const parameter, + const char* const file, const int line, const void* const memory, + const size_t size, const int count) { + expect_memory_setup(function, parameter, file, line, memory, size, + check_not_memory, count); +} + + +/* CheckParameterValue callback that always returns 1. */ +static int check_any(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + (void)value; + (void)check_value_data; + return 1; +} + + +/* Add an event to allow any value for a parameter. */ +void _expect_any( + const char* const function, const char* const parameter, + const char* const file, const int line, const int count) { + _expect_check(function, parameter, file, line, check_any, 0, NULL, + count); +} + + +void _check_expected( + const char * const function_name, const char * const parameter_name, + const char* file, const int line, const LargestIntegralType value) { + void *result = NULL; + const char* symbols[] = {function_name, parameter_name}; + const int rc = get_symbol_value(&global_function_parameter_map_head, + symbols, 2, &result); + if (rc) { + CheckParameterEvent * const check = (CheckParameterEvent*)result; + int check_succeeded; + global_last_parameter_location = check->location; + check_succeeded = check->check_value(value, check->check_value_data); + if (rc == 1) { + free(check); + } + if (!check_succeeded) { + cm_print_error(SOURCE_LOCATION_FORMAT + ": error: Check of parameter %s, function %s failed\n" + SOURCE_LOCATION_FORMAT + ": note: Expected parameter declared here\n", + file, line, + parameter_name, function_name, + global_last_parameter_location.file, + global_last_parameter_location.line); + _fail(file, line); + } + } else { + cm_print_error(SOURCE_LOCATION_FORMAT ": error: Could not get value " + "to check parameter %s of function %s\n", file, line, + parameter_name, function_name); + if (source_location_is_set(&global_last_parameter_location)) { + cm_print_error(SOURCE_LOCATION_FORMAT + ": note: Previously declared parameter value was declared here\n", + global_last_parameter_location.file, + global_last_parameter_location.line); + } else { + cm_print_error("There were no previously declared parameter values " + "for this test.\n"); + } + exit_test(1); + } +} + + +/* Replacement for assert. */ +void mock_assert(const int result, const char* const expression, + const char* const file, const int line) { + if (!result) { + if (global_expecting_assert) { + global_last_failed_assert = expression; + longjmp(global_expect_assert_env, result); + } else { + cm_print_error("ASSERT: %s\n", expression); + _fail(file, line); + } + } +} + + +void _assert_true(const LargestIntegralType result, + const char * const expression, + const char * const file, const int line) { + if (!result) { + cm_print_error("%s\n", expression); + _fail(file, line); + } +} + +void _assert_return_code(const LargestIntegralType result, + size_t rlen, + const LargestIntegralType error, + const char * const expression, + const char * const file, + const int line) +{ + LargestIntegralType valmax; + + + switch (rlen) { + case 1: + valmax = 255; + break; + case 2: + valmax = 32767; + break; + case 4: + valmax = 2147483647; + break; + case 8: + default: + if (rlen > sizeof(valmax)) { + valmax = 2147483647; + } else { + valmax = 9223372036854775807L; + } + break; + } + + if (result > valmax - 1) { + if (error > 0) { + cm_print_error("%s < 0, errno(" + LargestIntegralTypePrintfFormatDecimal "): %s\n", + expression, error, strerror((int)error)); + } else { + cm_print_error("%s < 0\n", expression); + } + _fail(file, line); + } +} + +void _assert_float_equal(const float a, + const float b, + const float epsilon, + const char * const file, + const int line) { + if (!float_values_equal_display_error(a, b, epsilon)) { + _fail(file, line); + } +} + +void _assert_float_not_equal(const float a, + const float b, + const float epsilon, + const char * const file, + const int line) { + if (!float_values_not_equal_display_error(a, b, epsilon)) { + _fail(file, line); + } +} + +void _assert_int_equal( + const LargestIntegralType a, const LargestIntegralType b, + const char * const file, const int line) { + if (!values_equal_display_error(a, b)) { + _fail(file, line); + } +} + + +void _assert_int_not_equal( + const LargestIntegralType a, const LargestIntegralType b, + const char * const file, const int line) { + if (!values_not_equal_display_error(a, b)) { + _fail(file, line); + } +} + + +void _assert_string_equal(const char * const a, const char * const b, + const char * const file, const int line) { + if (!string_equal_display_error(a, b)) { + _fail(file, line); + } +} + + +void _assert_string_not_equal(const char * const a, const char * const b, + const char *file, const int line) { + if (!string_not_equal_display_error(a, b)) { + _fail(file, line); + } +} + + +void _assert_memory_equal(const void * const a, const void * const b, + const size_t size, const char* const file, + const int line) { + if (!memory_equal_display_error((const char*)a, (const char*)b, size)) { + _fail(file, line); + } +} + + +void _assert_memory_not_equal(const void * const a, const void * const b, + const size_t size, const char* const file, + const int line) { + if (!memory_not_equal_display_error((const char*)a, (const char*)b, + size)) { + _fail(file, line); + } +} + + +void _assert_in_range( + const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char* const file, + const int line) { + if (!integer_in_range_display_error(value, minimum, maximum)) { + _fail(file, line); + } +} + +void _assert_not_in_range( + const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char* const file, + const int line) { + if (!integer_not_in_range_display_error(value, minimum, maximum)) { + _fail(file, line); + } +} + +void _assert_in_set(const LargestIntegralType value, + const LargestIntegralType values[], + const size_t number_of_values, const char* const file, + const int line) { + CheckIntegerSet check_integer_set; + check_integer_set.set = values; + check_integer_set.size_of_set = number_of_values; + if (!value_in_set_display_error(value, &check_integer_set, 0)) { + _fail(file, line); + } +} + +void _assert_not_in_set(const LargestIntegralType value, + const LargestIntegralType values[], + const size_t number_of_values, const char* const file, + const int line) { + CheckIntegerSet check_integer_set; + check_integer_set.set = values; + check_integer_set.size_of_set = number_of_values; + if (!value_in_set_display_error(value, &check_integer_set, 1)) { + _fail(file, line); + } +} + + +/* Get the list of allocated blocks. */ +static ListNode* get_allocated_blocks_list(void) { + /* If it initialized, initialize the list of allocated blocks. */ + if (!global_allocated_blocks.value) { + list_initialize(&global_allocated_blocks); + global_allocated_blocks.value = (void*)1; + } + return &global_allocated_blocks; +} + +static void *libc_calloc(size_t nmemb, size_t size) +{ +#undef calloc + return calloc(nmemb, size); +#define calloc test_calloc +} + +static void libc_free(void *ptr) +{ +#undef free + free(ptr); +#define free test_free +} + +static void *libc_realloc(void *ptr, size_t size) +{ +#undef realloc + return realloc(ptr, size); +#define realloc test_realloc +} + +static void vcm_print_error(const char* const format, + va_list args) CMOCKA_PRINTF_ATTRIBUTE(1, 0); + +/* It's important to use the libc malloc and free here otherwise + * the automatic free of leaked blocks can reap the error messages + */ +static void vcm_print_error(const char* const format, va_list args) +{ + char buffer[1024]; + size_t msg_len = 0; + va_list ap; + int len; + va_copy(ap, args); + + len = vsnprintf(buffer, sizeof(buffer), format, args); + if (len < 0) { + /* TODO */ + goto end; + } + + if (cm_error_message == NULL) { + /* CREATE MESSAGE */ + + cm_error_message = libc_calloc(1, len + 1); + if (cm_error_message == NULL) { + /* TODO */ + goto end; + } + } else { + /* APPEND MESSAGE */ + char *tmp; + + msg_len = strlen(cm_error_message); + tmp = libc_realloc(cm_error_message, msg_len + len + 1); + if (tmp == NULL) { + goto end; + } + cm_error_message = tmp; + } + + if (((size_t)len) < sizeof(buffer)) { + /* Use len + 1 to also copy '\0' */ + memcpy(cm_error_message + msg_len, buffer, len + 1); + } else { + vsnprintf(cm_error_message + msg_len, len, format, ap); + } +end: + va_end(ap); + +} + +static void vcm_free_error(char *err_msg) +{ + libc_free(err_msg); +} + +/* Use the real malloc in this function. */ +#undef malloc +void* _test_malloc(const size_t size, const char* file, const int line) { + char *ptr = NULL; + MallocBlockInfo block_info; + ListNode * const block_list = get_allocated_blocks_list(); + size_t allocate_size; + char *block = NULL; + + allocate_size = size + (MALLOC_GUARD_SIZE * 2) + + sizeof(struct MallocBlockInfoData) + MALLOC_ALIGNMENT; + assert_true(allocate_size > size); + + block = (char *)malloc(allocate_size); + assert_non_null(block); + + /* Calculate the returned address. */ + ptr = (char*)(((size_t)block + MALLOC_GUARD_SIZE + + sizeof(struct MallocBlockInfoData) + + MALLOC_ALIGNMENT) & ~(MALLOC_ALIGNMENT - 1)); + + /* Initialize the guard blocks. */ + memset(ptr - MALLOC_GUARD_SIZE, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE); + memset(ptr + size, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE); + memset(ptr, MALLOC_ALLOC_PATTERN, size); + + block_info.ptr = ptr - (MALLOC_GUARD_SIZE + + sizeof(struct MallocBlockInfoData)); + set_source_location(&block_info.data->location, file, line); + block_info.data->allocated_size = allocate_size; + block_info.data->size = size; + block_info.data->block = block; + block_info.data->node.value = block_info.ptr; + list_add(block_list, &block_info.data->node); + return ptr; +} +#define malloc test_malloc + + +void* _test_calloc(const size_t number_of_elements, const size_t size, + const char* file, const int line) { + void* const ptr = _test_malloc(number_of_elements * size, file, line); + if (ptr) { + memset(ptr, 0, number_of_elements * size); + } + return ptr; +} +#define calloc test_calloc + + +/* Use the real free in this function. */ +#undef free +void _test_free(void* const ptr, const char* file, const int line) { + unsigned int i; + char *block = discard_const_p(char, ptr); + MallocBlockInfo block_info; + + if (ptr == NULL) { + return; + } + + _assert_true(cast_ptr_to_largest_integral_type(ptr), "ptr", file, line); + block_info.ptr = block - (MALLOC_GUARD_SIZE + + sizeof(struct MallocBlockInfoData)); + /* Check the guard blocks. */ + { + char *guards[2] = {block - MALLOC_GUARD_SIZE, + block + block_info.data->size}; + for (i = 0; i < ARRAY_SIZE(guards); i++) { + unsigned int j; + char * const guard = guards[i]; + for (j = 0; j < MALLOC_GUARD_SIZE; j++) { + const char diff = guard[j] - MALLOC_GUARD_PATTERN; + if (diff) { + cm_print_error(SOURCE_LOCATION_FORMAT + ": error: Guard block of %p size=%lu is corrupt\n" + SOURCE_LOCATION_FORMAT ": note: allocated here at %p\n", + file, + line, + ptr, + (unsigned long)block_info.data->size, + block_info.data->location.file, + block_info.data->location.line, + (void *)&guard[j]); + _fail(file, line); + } + } + } + } + list_remove(&block_info.data->node, NULL, NULL); + + block = discard_const_p(char, block_info.data->block); + memset(block, MALLOC_FREE_PATTERN, block_info.data->allocated_size); + free(block); +} +#define free test_free + +#undef realloc +void *_test_realloc(void *ptr, + const size_t size, + const char *file, + const int line) +{ + MallocBlockInfo block_info; + char *block = ptr; + size_t block_size = size; + void *new_block; + + if (ptr == NULL) { + return _test_malloc(size, file, line); + } + + if (size == 0) { + _test_free(ptr, file, line); + return NULL; + } + + block_info.ptr = block - (MALLOC_GUARD_SIZE + + sizeof(struct MallocBlockInfoData)); + + new_block = _test_malloc(size, file, line); + if (new_block == NULL) { + return NULL; + } + + if (block_info.data->size < size) { + block_size = block_info.data->size; + } + + memcpy(new_block, ptr, block_size); + + /* Free previous memory */ + _test_free(ptr, file, line); + + return new_block; +} +#define realloc test_realloc + +/* Crudely checkpoint the current heap state. */ +static const ListNode* check_point_allocated_blocks(void) { + return get_allocated_blocks_list()->prev; +} + + +/* Display the blocks allocated after the specified check point. This + * function returns the number of blocks displayed. */ +static size_t display_allocated_blocks(const ListNode * const check_point) { + const ListNode * const head = get_allocated_blocks_list(); + const ListNode *node; + size_t allocated_blocks = 0; + assert_non_null(check_point); + assert_non_null(check_point->next); + + for (node = check_point->next; node != head; node = node->next) { + const MallocBlockInfo block_info = { + .ptr = discard_const(node->value), + }; + assert_non_null(block_info.ptr); + + if (allocated_blocks == 0) { + cm_print_error("Blocks allocated...\n"); + } + cm_print_error(SOURCE_LOCATION_FORMAT ": note: block %p allocated here\n", + block_info.data->location.file, + block_info.data->location.line, + block_info.data->block); + allocated_blocks++; + } + return allocated_blocks; +} + + +/* Free all blocks allocated after the specified check point. */ +static void free_allocated_blocks(const ListNode * const check_point) { + const ListNode * const head = get_allocated_blocks_list(); + const ListNode *node; + assert_non_null(check_point); + + node = check_point->next; + assert_non_null(node); + + while (node != head) { + const MallocBlockInfo block_info = { + .ptr = discard_const(node->value), + }; + node = node->next; + free(discard_const_p(char, block_info.data) + + sizeof(struct MallocBlockInfoData) + + MALLOC_GUARD_SIZE); + } +} + + +/* Fail if any any blocks are allocated after the specified check point. */ +static void fail_if_blocks_allocated(const ListNode * const check_point, + const char * const test_name) { + const size_t allocated_blocks = display_allocated_blocks(check_point); + if (allocated_blocks > 0) { + free_allocated_blocks(check_point); + cm_print_error("ERROR: %s leaked %zu block(s)\n", test_name, + allocated_blocks); + exit_test(1); + } +} + + +void _fail(const char * const file, const int line) { + enum cm_message_output output = cm_get_output(); + + switch(output) { + case CM_OUTPUT_STDOUT: + cm_print_error("[ LINE ] --- " SOURCE_LOCATION_FORMAT ": error: Failure!", file, line); + break; + default: + cm_print_error(SOURCE_LOCATION_FORMAT ": error: Failure!", file, line); + break; + } + exit_test(1); +} + + +#ifndef _WIN32 +static void exception_handler(int sig) { + const char *sig_strerror = ""; + +#ifdef HAVE_STRSIGNAL + sig_strerror = strsignal(sig); +#endif + + cm_print_error("Test failed with exception: %s(%d)", + sig_strerror, sig); + exit_test(1); +} + +#else /* _WIN32 */ + +static LONG WINAPI exception_filter(EXCEPTION_POINTERS *exception_pointers) { + EXCEPTION_RECORD * const exception_record = + exception_pointers->ExceptionRecord; + const DWORD code = exception_record->ExceptionCode; + unsigned int i; + for (i = 0; i < ARRAY_SIZE(exception_codes); i++) { + const ExceptionCodeInfo * const code_info = &exception_codes[i]; + if (code == code_info->code) { + static int shown_debug_message = 0; + fflush(stdout); + cm_print_error("%s occurred at %p.\n", code_info->description, + exception_record->ExceptionAddress); + if (!shown_debug_message) { + cm_print_error( + "\n" + "To debug in Visual Studio...\n" + "1. Select menu item File->Open Project\n" + "2. Change 'Files of type' to 'Executable Files'\n" + "3. Open this executable.\n" + "4. Select menu item Debug->Start\n" + "\n" + "Alternatively, set the environment variable \n" + "UNIT_TESTING_DEBUG to 1 and rebuild this executable, \n" + "then click 'Debug' in the popup dialog box.\n" + "\n"); + shown_debug_message = 1; + } + exit_test(0); + return EXCEPTION_EXECUTE_HANDLER; + } + } + return EXCEPTION_CONTINUE_SEARCH; +} +#endif /* !_WIN32 */ + +void cm_print_error(const char * const format, ...) +{ + va_list args; + va_start(args, format); + if (cm_error_message_enabled) { + vcm_print_error(format, args); + } else { + vprint_error(format, args); + } + va_end(args); +} + +/* Standard output and error print methods. */ +void vprint_message(const char* const format, va_list args) { + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), format, args); + printf("%s", buffer); + fflush(stdout); +#ifdef _WIN32 + OutputDebugString(buffer); +#endif /* _WIN32 */ +} + + +void vprint_error(const char* const format, va_list args) { + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), format, args); + fprintf(stderr, "%s", buffer); + fflush(stderr); +#ifdef _WIN32 + OutputDebugString(buffer); +#endif /* _WIN32 */ +} + + +void print_message(const char* const format, ...) { + va_list args; + va_start(args, format); + vprint_message(format, args); + va_end(args); +} + + +void print_error(const char* const format, ...) { + va_list args; + va_start(args, format); + vprint_error(format, args); + va_end(args); +} + +/* New formatter */ +static enum cm_message_output cm_get_output(void) +{ + enum cm_message_output output = global_msg_output; + char *env; + + env = getenv("CMOCKA_MESSAGE_OUTPUT"); + if (env != NULL) { + if (strcasecmp(env, "STDOUT") == 0) { + output = CM_OUTPUT_STDOUT; + } else if (strcasecmp(env, "SUBUNIT") == 0) { + output = CM_OUTPUT_SUBUNIT; + } else if (strcasecmp(env, "TAP") == 0) { + output = CM_OUTPUT_TAP; + } else if (strcasecmp(env, "XML") == 0) { + output = CM_OUTPUT_XML; + } + } + + return output; +} + +enum cm_printf_type { + PRINTF_TEST_START, + PRINTF_TEST_SUCCESS, + PRINTF_TEST_FAILURE, + PRINTF_TEST_ERROR, + PRINTF_TEST_SKIPPED, +}; + +static int xml_printed; +static int file_append; + +static void cmprintf_group_finish_xml(const char *group_name, + size_t total_executed, + size_t total_failed, + size_t total_errors, + size_t total_skipped, + double total_runtime, + struct CMUnitTestState *cm_tests) +{ + FILE *fp = stdout; + int file_opened = 0; + int multiple_files = 0; + char *env; + size_t i; + + env = getenv("CMOCKA_XML_FILE"); + if (env != NULL) { + char buf[1024]; + int rc; + + snprintf(buf, sizeof(buf), "%s", env); + + rc = c_strreplace(buf, sizeof(buf), "%g", group_name, &multiple_files); + if (rc < 0) { + snprintf(buf, sizeof(buf), "%s", env); + } + + fp = fopen(buf, "r"); + if (fp == NULL) { + fp = fopen(buf, "w"); + if (fp != NULL) { + file_append = 1; + file_opened = 1; + } else { + fp = stderr; + } + } else { + fclose(fp); + if (file_append) { + fp = fopen(buf, "a"); + if (fp != NULL) { + file_opened = 1; + xml_printed = 1; + } else { + fp = stderr; + } + } else { + fp = stderr; + } + } + } + + if (!xml_printed || (file_opened && !file_append)) { + fprintf(fp, "\n"); + if (!file_opened) { + xml_printed = 1; + } + } + + fprintf(fp, "\n"); + fprintf(fp, " \n", + group_name, + total_runtime, /* seconds */ + (unsigned)total_executed, + (unsigned)total_failed, + (unsigned)total_errors, + (unsigned)total_skipped); + + for (i = 0; i < total_executed; i++) { + struct CMUnitTestState *cmtest = &cm_tests[i]; + + fprintf(fp, " \n", + cmtest->test->name, cmtest->runtime); + + switch (cmtest->status) { + case CM_TEST_ERROR: + case CM_TEST_FAILED: + if (cmtest->error_message != NULL) { + fprintf(fp, " \n", + cmtest->error_message); + } else { + fprintf(fp, " \n"); + } + break; + case CM_TEST_SKIPPED: + fprintf(fp, " \n"); + break; + + case CM_TEST_PASSED: + case CM_TEST_NOT_STARTED: + break; + } + + fprintf(fp, " \n"); + } + + fprintf(fp, " \n"); + fprintf(fp, "\n"); + + if (file_opened) { + fclose(fp); + } +} + +static void cmprintf_group_start_standard(const size_t num_tests) +{ + print_message("[==========] Running %u test(s).\n", + (unsigned)num_tests); +} + +static void cmprintf_group_finish_standard(size_t total_executed, + size_t total_passed, + size_t total_failed, + size_t total_errors, + size_t total_skipped, + struct CMUnitTestState *cm_tests) +{ + size_t i; + + print_message("[==========] %u test(s) run.\n", (unsigned)total_executed); + print_error("[ PASSED ] %u test(s).\n", + (unsigned)(total_passed)); + + if (total_skipped) { + print_error("[ SKIPPED ] %"PRIdS " test(s), listed below:\n", total_skipped); + for (i = 0; i < total_executed; i++) { + struct CMUnitTestState *cmtest = &cm_tests[i]; + + if (cmtest->status == CM_TEST_SKIPPED) { + print_error("[ SKIPPED ] %s\n", cmtest->test->name); + } + } + print_error("\n %u SKIPPED TEST(S)\n", (unsigned)(total_skipped)); + } + + if (total_failed) { + print_error("[ FAILED ] %"PRIdS " test(s), listed below:\n", total_failed); + for (i = 0; i < total_executed; i++) { + struct CMUnitTestState *cmtest = &cm_tests[i]; + + if (cmtest->status == CM_TEST_FAILED) { + print_error("[ FAILED ] %s\n", cmtest->test->name); + } + } + print_error("\n %u FAILED TEST(S)\n", + (unsigned)(total_failed + total_errors)); + } +} + +static void cmprintf_standard(enum cm_printf_type type, + const char *test_name, + const char *error_message) +{ + switch (type) { + case PRINTF_TEST_START: + print_message("[ RUN ] %s\n", test_name); + break; + case PRINTF_TEST_SUCCESS: + print_message("[ OK ] %s\n", test_name); + break; + case PRINTF_TEST_FAILURE: + if (error_message != NULL) { + print_error("[ ERROR ] --- %s\n", error_message); + } + print_message("[ FAILED ] %s\n", test_name); + break; + case PRINTF_TEST_SKIPPED: + print_message("[ SKIPPED ] %s\n", test_name); + break; + case PRINTF_TEST_ERROR: + if (error_message != NULL) { + print_error("%s\n", error_message); + } + print_error("[ ERROR ] %s\n", test_name); + break; + } +} + +static void cmprintf_group_start_tap(const size_t num_tests) +{ + print_message("1..%u\n", (unsigned)num_tests); +} + +static void cmprintf_group_finish_tap(const char *group_name, + size_t total_executed, + size_t total_passed, + size_t total_skipped) +{ + const char *status = "not ok"; + if (total_passed + total_skipped == total_executed) { + status = "ok"; + } + print_message("# %s - %s\n", status, group_name); +} + +static void cmprintf_tap(enum cm_printf_type type, + uint32_t test_number, + const char *test_name, + const char *error_message) +{ + switch (type) { + case PRINTF_TEST_START: + break; + case PRINTF_TEST_SUCCESS: + print_message("ok %u - %s\n", (unsigned)test_number, test_name); + break; + case PRINTF_TEST_FAILURE: + print_message("not ok %u - %s\n", (unsigned)test_number, test_name); + if (error_message != NULL) { + char *msg; + char *p; + + msg = strdup(error_message); + if (msg == NULL) { + return; + } + p = msg; + + while (p[0] != '\0') { + char *q = p; + + p = strchr(q, '\n'); + if (p != NULL) { + p[0] = '\0'; + } + + print_message("# %s\n", q); + + if (p == NULL) { + break; + } + p++; + } + libc_free(msg); + } + break; + case PRINTF_TEST_SKIPPED: + print_message("not ok %u # SKIP %s\n", (unsigned)test_number, test_name); + break; + case PRINTF_TEST_ERROR: + print_message("not ok %u - %s %s\n", + (unsigned)test_number, test_name, error_message); + break; + } +} + +static void cmprintf_subunit(enum cm_printf_type type, + const char *test_name, + const char *error_message) +{ + switch (type) { + case PRINTF_TEST_START: + print_message("test: %s\n", test_name); + break; + case PRINTF_TEST_SUCCESS: + print_message("success: %s\n", test_name); + break; + case PRINTF_TEST_FAILURE: + print_message("failure: %s", test_name); + if (error_message != NULL) { + print_message(" [\n%s\n]\n", error_message); + } + break; + case PRINTF_TEST_SKIPPED: + print_message("skip: %s\n", test_name); + break; + case PRINTF_TEST_ERROR: + print_message("error: %s [ %s ]\n", test_name, error_message); + break; + } +} + +static void cmprintf_group_start(const size_t num_tests) +{ + enum cm_message_output output; + + output = cm_get_output(); + + switch (output) { + case CM_OUTPUT_STDOUT: + cmprintf_group_start_standard(num_tests); + break; + case CM_OUTPUT_SUBUNIT: + break; + case CM_OUTPUT_TAP: + cmprintf_group_start_tap(num_tests); + break; + case CM_OUTPUT_XML: + break; + } +} + +static void cmprintf_group_finish(const char *group_name, + size_t total_executed, + size_t total_passed, + size_t total_failed, + size_t total_errors, + size_t total_skipped, + double total_runtime, + struct CMUnitTestState *cm_tests) +{ + enum cm_message_output output; + + output = cm_get_output(); + + switch (output) { + case CM_OUTPUT_STDOUT: + cmprintf_group_finish_standard(total_executed, + total_passed, + total_failed, + total_errors, + total_skipped, + cm_tests); + break; + case CM_OUTPUT_SUBUNIT: + break; + case CM_OUTPUT_TAP: + cmprintf_group_finish_tap(group_name, total_executed, total_passed, total_skipped); + break; + case CM_OUTPUT_XML: + cmprintf_group_finish_xml(group_name, + total_executed, + total_failed, + total_errors, + total_skipped, + total_runtime, + cm_tests); + break; + } +} + +static void cmprintf(enum cm_printf_type type, + size_t test_number, + const char *test_name, + const char *error_message) +{ + enum cm_message_output output; + + output = cm_get_output(); + + switch (output) { + case CM_OUTPUT_STDOUT: + cmprintf_standard(type, test_name, error_message); + break; + case CM_OUTPUT_SUBUNIT: + cmprintf_subunit(type, test_name, error_message); + break; + case CM_OUTPUT_TAP: + cmprintf_tap(type, test_number, test_name, error_message); + break; + case CM_OUTPUT_XML: + break; + } +} + +void cmocka_set_message_output(enum cm_message_output output) +{ + global_msg_output = output; +} + +void cmocka_set_test_filter(const char *pattern) +{ + global_test_filter_pattern = pattern; +} + +void cmocka_set_skip_filter(const char *pattern) +{ + global_skip_filter_pattern = pattern; +} + +/**************************************************************************** + * TIME CALCULATIONS + ****************************************************************************/ + +#ifdef HAVE_STRUCT_TIMESPEC +static struct timespec cm_tspecdiff(struct timespec time1, + struct timespec time0) +{ + struct timespec ret; + int xsec = 0; + int sign = 1; + + if (time0.tv_nsec > time1.tv_nsec) { + xsec = (int) ((time0.tv_nsec - time1.tv_nsec) / (1E9 + 1)); + time0.tv_nsec -= (long int) (1E9 * xsec); + time0.tv_sec += xsec; + } + + if ((time1.tv_nsec - time0.tv_nsec) > 1E9) { + xsec = (int) ((time1.tv_nsec - time0.tv_nsec) / 1E9); + time0.tv_nsec += (long int) (1E9 * xsec); + time0.tv_sec -= xsec; + } + + ret.tv_sec = time1.tv_sec - time0.tv_sec; + ret.tv_nsec = time1.tv_nsec - time0.tv_nsec; + + if (time1.tv_sec < time0.tv_sec) { + sign = -1; + } + + ret.tv_sec = ret.tv_sec * sign; + + return ret; +} + +static double cm_secdiff(struct timespec clock1, struct timespec clock0) +{ + double ret; + struct timespec diff; + + diff = cm_tspecdiff(clock1, clock0); + + ret = diff.tv_sec; + ret += (double) diff.tv_nsec / (double) 1E9; + + return ret; +} +#endif /* HAVE_STRUCT_TIMESPEC */ + +/**************************************************************************** + * CMOCKA TEST RUNNER + ****************************************************************************/ +static int cmocka_run_one_test_or_fixture(const char *function_name, + CMUnitTestFunction test_func, + CMFixtureFunction setup_func, + CMFixtureFunction teardown_func, + void ** const volatile state, + const void *const heap_check_point) +{ + const ListNode * const volatile check_point = (const ListNode*) + (heap_check_point != NULL ? + heap_check_point : check_point_allocated_blocks()); + int handle_exceptions = 1; + void *current_state = NULL; + int rc = 0; + + /* FIXME check only one test or fixture is set */ + + /* Detect if we should handle exceptions */ +#ifdef _WIN32 + handle_exceptions = !IsDebuggerPresent(); +#endif /* _WIN32 */ +#ifdef UNIT_TESTING_DEBUG + handle_exceptions = 0; +#endif /* UNIT_TESTING_DEBUG */ + + + if (handle_exceptions) { +#ifndef _WIN32 + unsigned int i; + for (i = 0; i < ARRAY_SIZE(exception_signals); i++) { + default_signal_functions[i] = signal( + exception_signals[i], exception_handler); + } +#else /* _WIN32 */ + previous_exception_filter = SetUnhandledExceptionFilter( + exception_filter); +#endif /* !_WIN32 */ + } + + /* Init the test structure */ + initialize_testing(function_name); + + global_running_test = 1; + + if (cm_setjmp(global_run_test_env) == 0) { + if (test_func != NULL) { + test_func(state != NULL ? state : ¤t_state); + + fail_if_blocks_allocated(check_point, function_name); + rc = 0; + } else if (setup_func != NULL) { + rc = setup_func(state != NULL ? state : ¤t_state); + + /* + * For setup we can ignore any allocated blocks. We just need to + * ensure they're deallocated on tear down. + */ + } else if (teardown_func != NULL) { + rc = teardown_func(state != NULL ? state : ¤t_state); + + fail_if_blocks_allocated(check_point, function_name); + } else { + /* ERROR */ + } + fail_if_leftover_values(function_name); + global_running_test = 0; + } else { + /* TEST FAILED */ + global_running_test = 0; + rc = -1; + } + teardown_testing(function_name); + + if (handle_exceptions) { +#ifndef _WIN32 + unsigned int i; + for (i = 0; i < ARRAY_SIZE(exception_signals); i++) { + signal(exception_signals[i], default_signal_functions[i]); + } +#else /* _WIN32 */ + if (previous_exception_filter) { + SetUnhandledExceptionFilter(previous_exception_filter); + previous_exception_filter = NULL; + } +#endif /* !_WIN32 */ + } + + return rc; +} + +static int cmocka_run_group_fixture(const char *function_name, + CMFixtureFunction setup_func, + CMFixtureFunction teardown_func, + void **state, + const void *const heap_check_point) +{ + int rc; + + if (setup_func != NULL) { + rc = cmocka_run_one_test_or_fixture(function_name, + NULL, + setup_func, + NULL, + state, + heap_check_point); + } else { + rc = cmocka_run_one_test_or_fixture(function_name, + NULL, + NULL, + teardown_func, + state, + heap_check_point); + } + + return rc; +} + +static int cmocka_run_one_tests(struct CMUnitTestState *test_state) +{ +#ifdef HAVE_STRUCT_TIMESPEC + struct timespec start = { + .tv_sec = 0, + .tv_nsec = 0, + }; + struct timespec finish = { + .tv_sec = 0, + .tv_nsec = 0, + }; +#endif + int rc = 0; + + /* Run setup */ + if (test_state->test->setup_func != NULL) { + /* Setup the memory check point, it will be evaluated on teardown */ + test_state->check_point = check_point_allocated_blocks(); + + rc = cmocka_run_one_test_or_fixture(test_state->test->name, + NULL, + test_state->test->setup_func, + NULL, + &test_state->state, + test_state->check_point); + if (rc != 0) { + test_state->status = CM_TEST_ERROR; + cm_print_error("Test setup failed"); + } + } + + /* Run test */ +#ifdef HAVE_STRUCT_TIMESPEC + CMOCKA_CLOCK_GETTIME(CLOCK_REALTIME, &start); +#endif + + if (rc == 0) { + rc = cmocka_run_one_test_or_fixture(test_state->test->name, + test_state->test->test_func, + NULL, + NULL, + &test_state->state, + NULL); + if (rc == 0) { + test_state->status = CM_TEST_PASSED; + } else { + if (global_skip_test) { + test_state->status = CM_TEST_SKIPPED; + global_skip_test = 0; /* Do not skip the next test */ + } else { + test_state->status = CM_TEST_FAILED; + } + } + rc = 0; + } + + test_state->runtime = 0.0; + +#ifdef HAVE_STRUCT_TIMESPEC + CMOCKA_CLOCK_GETTIME(CLOCK_REALTIME, &finish); + test_state->runtime = cm_secdiff(finish, start); +#endif + + /* Run teardown */ + if (rc == 0 && test_state->test->teardown_func != NULL) { + rc = cmocka_run_one_test_or_fixture(test_state->test->name, + NULL, + NULL, + test_state->test->teardown_func, + &test_state->state, + test_state->check_point); + if (rc != 0) { + test_state->status = CM_TEST_ERROR; + cm_print_error("Test teardown failed"); + } + } + + test_state->error_message = cm_error_message; + cm_error_message = NULL; + + return rc; +} + +int _cmocka_run_group_tests(const char *group_name, + const struct CMUnitTest * const tests, + const size_t num_tests, + CMFixtureFunction group_setup, + CMFixtureFunction group_teardown) +{ + struct CMUnitTestState *cm_tests; + const ListNode *group_check_point = check_point_allocated_blocks(); + void *group_state = NULL; + size_t total_tests = 0; + size_t total_failed = 0; + size_t total_passed = 0; + size_t total_executed = 0; + size_t total_errors = 0; + size_t total_skipped = 0; + double total_runtime = 0; + size_t i; + int rc; + + /* Make sure LargestIntegralType is at least the size of a pointer. */ + assert_true(sizeof(LargestIntegralType) >= sizeof(void*)); + + cm_tests = libc_calloc(1, sizeof(struct CMUnitTestState) * num_tests); + if (cm_tests == NULL) { + return -1; + } + + /* Setup cmocka test array */ + for (i = 0; i < num_tests; i++) { + if (tests[i].name != NULL && + (tests[i].test_func != NULL + || tests[i].setup_func != NULL + || tests[i].teardown_func != NULL)) { + if (global_test_filter_pattern != NULL) { + int match; + + match = c_strmatch(tests[i].name, global_test_filter_pattern); + if (!match) { + continue; + } + } + if (global_skip_filter_pattern != NULL) { + int match; + + match = c_strmatch(tests[i].name, global_skip_filter_pattern); + if (match) { + continue; + } + } + cm_tests[total_tests] = (struct CMUnitTestState) { + .test = &tests[i], + .status = CM_TEST_NOT_STARTED, + .state = NULL, + }; + total_tests++; + } + } + + cmprintf_group_start(total_tests); + + rc = 0; + + /* Run group setup */ + if (group_setup != NULL) { + rc = cmocka_run_group_fixture("cmocka_group_setup", + group_setup, + NULL, + &group_state, + group_check_point); + } + + if (rc == 0) { + /* Execute tests */ + for (i = 0; i < total_tests; i++) { + struct CMUnitTestState *cmtest = &cm_tests[i]; + size_t test_number = i + 1; + + cmprintf(PRINTF_TEST_START, test_number, cmtest->test->name, NULL); + + if (group_state != NULL) { + cmtest->state = group_state; + } else if (cmtest->test->initial_state != NULL) { + cmtest->state = cmtest->test->initial_state; + } + + rc = cmocka_run_one_tests(cmtest); + total_executed++; + total_runtime += cmtest->runtime; + if (rc == 0) { + switch (cmtest->status) { + case CM_TEST_PASSED: + cmprintf(PRINTF_TEST_SUCCESS, + test_number, + cmtest->test->name, + cmtest->error_message); + total_passed++; + break; + case CM_TEST_SKIPPED: + cmprintf(PRINTF_TEST_SKIPPED, + test_number, + cmtest->test->name, + cmtest->error_message); + total_skipped++; + break; + case CM_TEST_FAILED: + cmprintf(PRINTF_TEST_FAILURE, + test_number, + cmtest->test->name, + cmtest->error_message); + total_failed++; + break; + default: + cmprintf(PRINTF_TEST_ERROR, + test_number, + cmtest->test->name, + "Internal cmocka error"); + total_errors++; + break; + } + } else { + char err_msg[2048] = {0}; + + snprintf(err_msg, sizeof(err_msg), + "Could not run test: %s", + cmtest->error_message); + + cmprintf(PRINTF_TEST_ERROR, + test_number, + cmtest->test->name, + err_msg); + total_errors++; + } + } + } else { + if (cm_error_message != NULL) { + print_error("[ ERROR ] --- %s\n", cm_error_message); + vcm_free_error(cm_error_message); + cm_error_message = NULL; + } + cmprintf(PRINTF_TEST_ERROR, 0, + group_name, "[ FAILED ] GROUP SETUP"); + total_errors++; + } + + /* Run group teardown */ + if (group_teardown != NULL) { + rc = cmocka_run_group_fixture("cmocka_group_teardown", + NULL, + group_teardown, + &group_state, + group_check_point); + if (rc != 0) { + if (cm_error_message != NULL) { + print_error("[ ERROR ] --- %s\n", cm_error_message); + vcm_free_error(cm_error_message); + cm_error_message = NULL; + } + cmprintf(PRINTF_TEST_ERROR, 0, + group_name, "[ FAILED ] GROUP TEARDOWN"); + } + } + + cmprintf_group_finish(group_name, + total_executed, + total_passed, + total_failed, + total_errors, + total_skipped, + total_runtime, + cm_tests); + + for (i = 0; i < total_tests; i++) { + vcm_free_error(discard_const_p(char, cm_tests[i].error_message)); + } + libc_free(cm_tests); + fail_if_blocks_allocated(group_check_point, "cmocka_group_tests"); + + return total_failed + total_errors; +} + +/**************************************************************************** + * DEPRECATED TEST RUNNER + ****************************************************************************/ + +int _run_test( + const char * const function_name, const UnitTestFunction Function, + void ** const volatile state, const UnitTestFunctionType function_type, + const void* const heap_check_point) { + const ListNode * const volatile check_point = (const ListNode*) + (heap_check_point ? + heap_check_point : check_point_allocated_blocks()); + void *current_state = NULL; + volatile int rc = 1; + int handle_exceptions = 1; +#ifdef _WIN32 + handle_exceptions = !IsDebuggerPresent(); +#endif /* _WIN32 */ +#ifdef UNIT_TESTING_DEBUG + handle_exceptions = 0; +#endif /* UNIT_TESTING_DEBUG */ + + cm_error_message_enabled = 0; + + if (handle_exceptions) { +#ifndef _WIN32 + unsigned int i; + for (i = 0; i < ARRAY_SIZE(exception_signals); i++) { + default_signal_functions[i] = signal( + exception_signals[i], exception_handler); + } +#else /* _WIN32 */ + previous_exception_filter = SetUnhandledExceptionFilter( + exception_filter); +#endif /* !_WIN32 */ + } + + if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST) { + print_message("[ RUN ] %s\n", function_name); + } + initialize_testing(function_name); + global_running_test = 1; + if (cm_setjmp(global_run_test_env) == 0) { + Function(state ? state : ¤t_state); + fail_if_leftover_values(function_name); + + /* If this is a setup function then ignore any allocated blocks + * only ensure they're deallocated on tear down. */ + if (function_type != UNIT_TEST_FUNCTION_TYPE_SETUP) { + fail_if_blocks_allocated(check_point, function_name); + } + + global_running_test = 0; + + if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST) { + print_message("[ OK ] %s\n", function_name); + } + rc = 0; + } else { + global_running_test = 0; + print_message("[ FAILED ] %s\n", function_name); + } + teardown_testing(function_name); + + if (handle_exceptions) { +#ifndef _WIN32 + unsigned int i; + for (i = 0; i < ARRAY_SIZE(exception_signals); i++) { + signal(exception_signals[i], default_signal_functions[i]); + } +#else /* _WIN32 */ + if (previous_exception_filter) { + SetUnhandledExceptionFilter(previous_exception_filter); + previous_exception_filter = NULL; + } +#endif /* !_WIN32 */ + } + + return rc; +} + + +int _run_tests(const UnitTest * const tests, const size_t number_of_tests) { + /* Whether to execute the next test. */ + int run_next_test = 1; + /* Whether the previous test failed. */ + int previous_test_failed = 0; + /* Whether the previous setup failed. */ + int previous_setup_failed = 0; + /* Check point of the heap state. */ + const ListNode * const check_point = check_point_allocated_blocks(); + /* Current test being executed. */ + size_t current_test = 0; + /* Number of tests executed. */ + size_t tests_executed = 0; + /* Number of failed tests. */ + size_t total_failed = 0; + /* Number of setup functions. */ + size_t setups = 0; + /* Number of teardown functions. */ + size_t teardowns = 0; + size_t i; + /* + * A stack of test states. A state is pushed on the stack + * when a test setup occurs and popped on tear down. + */ + TestState* test_states = + (TestState*)malloc(number_of_tests * sizeof(*test_states)); + /* The number of test states which should be 0 at the end */ + long number_of_test_states = 0; + /* Names of the tests that failed. */ + const char** failed_names = (const char**)malloc(number_of_tests * + sizeof(*failed_names)); + void **current_state = NULL; + + /* Count setup and teardown functions */ + for (i = 0; i < number_of_tests; i++) { + const UnitTest * const test = &tests[i]; + + if (test->function_type == UNIT_TEST_FUNCTION_TYPE_SETUP) { + setups++; + } + + if (test->function_type == UNIT_TEST_FUNCTION_TYPE_TEARDOWN) { + teardowns++; + } + } + + print_message("[==========] Running %"PRIdS " test(s).\n", + number_of_tests - setups - teardowns); + + /* Make sure LargestIntegralType is at least the size of a pointer. */ + assert_true(sizeof(LargestIntegralType) >= sizeof(void*)); + + while (current_test < number_of_tests) { + const ListNode *test_check_point = NULL; + TestState *current_TestState; + const UnitTest * const test = &tests[current_test++]; + if (!test->function) { + continue; + } + + switch (test->function_type) { + case UNIT_TEST_FUNCTION_TYPE_TEST: + if (! previous_setup_failed) { + run_next_test = 1; + } + break; + case UNIT_TEST_FUNCTION_TYPE_SETUP: { + /* Checkpoint the heap before the setup. */ + current_TestState = &test_states[number_of_test_states++]; + current_TestState->check_point = check_point_allocated_blocks(); + test_check_point = current_TestState->check_point; + current_state = ¤t_TestState->state; + *current_state = NULL; + run_next_test = 1; + break; + } + case UNIT_TEST_FUNCTION_TYPE_TEARDOWN: + /* Check the heap based on the last setup checkpoint. */ + assert_true(number_of_test_states); + current_TestState = &test_states[--number_of_test_states]; + test_check_point = current_TestState->check_point; + current_state = ¤t_TestState->state; + break; + default: + print_error("Invalid unit test function type %d\n", + test->function_type); + exit_test(1); + break; + } + + if (run_next_test) { + int failed = _run_test(test->name, test->function, current_state, + test->function_type, test_check_point); + if (failed) { + failed_names[total_failed] = test->name; + } + + switch (test->function_type) { + case UNIT_TEST_FUNCTION_TYPE_TEST: + previous_test_failed = failed; + total_failed += failed; + tests_executed ++; + break; + + case UNIT_TEST_FUNCTION_TYPE_SETUP: + if (failed) { + total_failed ++; + tests_executed ++; + /* Skip forward until the next test or setup function. */ + run_next_test = 0; + previous_setup_failed = 1; + } + previous_test_failed = 0; + break; + + case UNIT_TEST_FUNCTION_TYPE_TEARDOWN: + /* If this test failed. */ + if (failed && !previous_test_failed) { + total_failed ++; + } + break; + default: +#ifndef _HPUX + assert_null("BUG: shouldn't be here!"); +#endif + break; + } + } + } + + print_message("[==========] %"PRIdS " test(s) run.\n", tests_executed); + print_error("[ PASSED ] %"PRIdS " test(s).\n", tests_executed - total_failed); + + if (total_failed > 0) { + print_error("[ FAILED ] %"PRIdS " test(s), listed below:\n", total_failed); + for (i = 0; i < total_failed; i++) { + print_error("[ FAILED ] %s\n", failed_names[i]); + } + } else { + print_error("\n %"PRIdS " FAILED TEST(S)\n", total_failed); + } + + if (number_of_test_states != 0) { + print_error("[ ERROR ] Mismatched number of setup %"PRIdS " and " + "teardown %"PRIdS " functions\n", setups, teardowns); + total_failed = (size_t)-1; + } + + free(test_states); + free((void*)failed_names); + + fail_if_blocks_allocated(check_point, "run_tests"); + return (int)total_failed; +} + +int _run_group_tests(const UnitTest * const tests, const size_t number_of_tests) +{ + UnitTestFunction setup = NULL; + const char *setup_name; + size_t num_setups = 0; + UnitTestFunction teardown = NULL; + const char *teardown_name = NULL; + size_t num_teardowns = 0; + size_t current_test = 0; + size_t i; + + /* Number of tests executed. */ + size_t tests_executed = 0; + /* Number of failed tests. */ + size_t total_failed = 0; + /* Check point of the heap state. */ + const ListNode * const check_point = check_point_allocated_blocks(); + const char **failed_names = NULL; + void **current_state = NULL; + TestState group_state = { + .check_point = NULL, + }; + + if (number_of_tests == 0) { + return -1; + } + + failed_names = (const char **)malloc(number_of_tests * + sizeof(*failed_names)); + if (failed_names == NULL) { + return -2; + } + + /* Find setup and teardown function */ + for (i = 0; i < number_of_tests; i++) { + const UnitTest * const test = &tests[i]; + + if (test->function_type == UNIT_TEST_FUNCTION_TYPE_GROUP_SETUP) { + if (setup == NULL) { + setup = test->function; + setup_name = test->name; + num_setups = 1; + } else { + print_error("[ ERROR ] More than one group setup function detected\n"); + exit_test(1); + } + } + + if (test->function_type == UNIT_TEST_FUNCTION_TYPE_GROUP_TEARDOWN) { + if (teardown == NULL) { + teardown = test->function; + teardown_name = test->name; + num_teardowns = 1; + } else { + print_error("[ ERROR ] More than one group teardown function detected\n"); + exit_test(1); + } + } + } + + print_message("[==========] Running %"PRIdS " test(s).\n", + number_of_tests - num_setups - num_teardowns); + + if (setup != NULL) { + int failed; + + group_state.check_point = check_point_allocated_blocks(); + current_state = &group_state.state; + *current_state = NULL; + failed = _run_test(setup_name, + setup, + current_state, + UNIT_TEST_FUNCTION_TYPE_SETUP, + group_state.check_point); + if (failed) { + failed_names[total_failed] = setup_name; + } + + total_failed += failed; + tests_executed++; + } + + while (current_test < number_of_tests) { + int run_test = 0; + const UnitTest * const test = &tests[current_test++]; + if (test->function == NULL) { + continue; + } + + switch (test->function_type) { + case UNIT_TEST_FUNCTION_TYPE_TEST: + run_test = 1; + break; + case UNIT_TEST_FUNCTION_TYPE_SETUP: + case UNIT_TEST_FUNCTION_TYPE_TEARDOWN: + case UNIT_TEST_FUNCTION_TYPE_GROUP_SETUP: + case UNIT_TEST_FUNCTION_TYPE_GROUP_TEARDOWN: + break; + default: + print_error("Invalid unit test function type %d\n", + test->function_type); + break; + } + + if (run_test) { + int failed; + + failed = _run_test(test->name, + test->function, + current_state, + test->function_type, + NULL); + if (failed) { + failed_names[total_failed] = test->name; + } + + total_failed += failed; + tests_executed++; + } + } + + if (teardown != NULL) { + int failed; + + failed = _run_test(teardown_name, + teardown, + current_state, + UNIT_TEST_FUNCTION_TYPE_GROUP_TEARDOWN, + group_state.check_point); + if (failed) { + failed_names[total_failed] = teardown_name; + } + + total_failed += failed; + tests_executed++; + } + + print_message("[==========] %"PRIdS " test(s) run.\n", tests_executed); + print_error("[ PASSED ] %"PRIdS " test(s).\n", tests_executed - total_failed); + + if (total_failed) { + print_error("[ FAILED ] %"PRIdS " test(s), listed below:\n", total_failed); + for (i = 0; i < total_failed; i++) { + print_error("[ FAILED ] %s\n", failed_names[i]); + } + } else { + print_error("\n %"PRIdS " FAILED TEST(S)\n", total_failed); + } + + free((void*)failed_names); + fail_if_blocks_allocated(check_point, "run_group_tests"); + + return (int)total_failed; +} diff --git a/src/imports/cmocka/cmocka.h b/src/imports/cmocka/cmocka.h new file mode 100644 index 0000000..5dd6d62 --- /dev/null +++ b/src/imports/cmocka/cmocka.h @@ -0,0 +1,2389 @@ +/* + * Copyright 2008 Google Inc. + * Copyright 2014-2018 Andreas Schneider + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CMOCKA_H_ +#define CMOCKA_H_ + +#include +#include +#include +#include + +#ifdef _WIN32 +# ifdef _MSC_VER + +#define __func__ __FUNCTION__ + +# ifndef inline +#define inline __inline +# endif /* inline */ + +# if _MSC_VER < 1500 +# ifdef __cplusplus +extern "C" { +# endif /* __cplusplus */ +int __stdcall IsDebuggerPresent(); +# ifdef __cplusplus +} /* extern "C" */ +# endif /* __cplusplus */ +# endif /* _MSC_VER < 1500 */ +# endif /* _MSC_VER */ +#endif /* _WIN32 */ + +/** + * @defgroup cmocka The CMocka API + * + * These headers or their equivalents MUST be included prior to including + * this header file. + * @code + * #include + * #include + * #include + * #include + * @endcode + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + * @{ + */ + +/* If __WORDSIZE is not set, try to figure it out and default to 32 bit. */ +#ifndef __WORDSIZE +# if (defined(__x86_64__) && !defined(__ILP32__)) || defined(__sparc_v9__) || defined(__sparcv9) +# define __WORDSIZE 64 +# else +# define __WORDSIZE 32 +# endif +#endif + +#ifdef DOXYGEN +/** + * Largest integral type. This type should be large enough to hold any + * pointer or integer supported by the compiler. + */ +typedef uintmax_t LargestIntegralType; +#else /* DOXGEN */ +#ifndef LargestIntegralType +# if __WORDSIZE == 64 && !defined(_WIN64) +# define LargestIntegralType unsigned long int +# else +# define LargestIntegralType unsigned long long int +# endif +#endif /* LargestIntegralType */ +#endif /* DOXYGEN */ + +/* Printf format used to display LargestIntegralType as a hexidecimal. */ +#ifndef LargestIntegralTypePrintfFormat +# ifdef _WIN32 +# define LargestIntegralTypePrintfFormat "0x%I64x" +# else +# if __WORDSIZE == 64 +# define LargestIntegralTypePrintfFormat "%#lx" +# else +# define LargestIntegralTypePrintfFormat "%#llx" +# endif +# endif /* _WIN32 */ +#endif /* LargestIntegralTypePrintfFormat */ + +/* Printf format used to display LargestIntegralType as a decimal. */ +#ifndef LargestIntegralTypePrintfFormatDecimal +# ifdef _WIN32 +# define LargestIntegralTypePrintfFormatDecimal "%I64u" +# else +# if __WORDSIZE == 64 +# define LargestIntegralTypePrintfFormatDecimal "%lu" +# else +# define LargestIntegralTypePrintfFormatDecimal "%llu" +# endif +# endif /* _WIN32 */ +#endif /* LargestIntegralTypePrintfFormat */ + +#ifndef FloatPrintfFormat +# define FloatPrintfFormat "%f" +#endif /* FloatPrintfFormat */ + +/* Perform an unsigned cast to LargestIntegralType. */ +#define cast_to_largest_integral_type(value) \ + ((LargestIntegralType)(value)) + +/* Smallest integral type capable of holding a pointer. */ +#if !defined(_UINTPTR_T) && !defined(_UINTPTR_T_DEFINED) +# if defined(_WIN32) + /* WIN32 is an ILP32 platform */ + typedef unsigned int uintptr_t; +# elif defined(_WIN64) + typedef unsigned long int uintptr_t; +# else /* _WIN32 */ + +/* ILP32 and LP64 platforms */ +# ifdef __WORDSIZE /* glibc */ +# if __WORDSIZE == 64 + typedef unsigned long int uintptr_t; +# else + typedef unsigned int uintptr_t; +# endif /* __WORDSIZE == 64 */ +# else /* __WORDSIZE */ +# if defined(_LP64) || defined(_I32LPx) + typedef unsigned long int uintptr_t; +# else + typedef unsigned int uintptr_t; +# endif +# endif /* __WORDSIZE */ +# endif /* _WIN32 */ + +# define _UINTPTR_T +# define _UINTPTR_T_DEFINED +#endif /* !defined(_UINTPTR_T) || !defined(_UINTPTR_T_DEFINED) */ + +/* Perform an unsigned cast to uintptr_t. */ +#define cast_to_pointer_integral_type(value) \ + ((uintptr_t)((size_t)(value))) + +/* Perform a cast of a pointer to LargestIntegralType */ +#define cast_ptr_to_largest_integral_type(value) \ +cast_to_largest_integral_type(cast_to_pointer_integral_type(value)) + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define CMOCKA_PRINTF_ATTRIBUTE(a,b) \ + __attribute__ ((__format__ (__printf__, a, b))) +#else +#define CMOCKA_PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +#if defined(__GNUC__) +#define CMOCKA_DEPRECATED __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define CMOCKA_DEPRECATED __declspec(deprecated) +#else +#define CMOCKA_DEPRECATED +#endif + +#define WILL_RETURN_ALWAYS -1 +#define WILL_RETURN_ONCE -2 + +/** + * @defgroup cmocka_mock Mock Objects + * @ingroup cmocka + * + * Mock objects mock objects are simulated objects that mimic the behavior of + * real objects. Instead of calling the real objects, the tested object calls a + * mock object that merely asserts that the correct methods were called, with + * the expected parameters, in the correct order. + * + *
    + *
  • will_return(function, value) - The will_return() macro + * pushes a value onto a stack of mock values. This macro is intended to be + * used by the unit test itself, while programming the behaviour of the mocked + * object.
  • + * + *
  • mock() - the mock macro pops a value from a stack of + * test values. The user of the mock() macro is the mocked object that uses it + * to learn how it should behave.
  • + *
+ * + * Because the will_return() and mock() are intended to be used in pairs, the + * cmocka library would fail the test if there are more values pushed onto the + * stack using will_return() than consumed with mock() and vice-versa. + * + * The following unit test stub illustrates how would a unit test instruct the + * mock object to return a particular value: + * + * @code + * will_return(chef_cook, "hotdog"); + * will_return(chef_cook, 0); + * @endcode + * + * Now the mock object can check if the parameter it received is the parameter + * which is expected by the test driver. This can be done the following way: + * + * @code + * int chef_cook(const char *order, char **dish_out) + * { + * check_expected(order); + * } + * @endcode + * + * For a complete example please take a look + * here. + * + * @{ + */ + +#ifdef DOXYGEN +/** + * @brief Retrieve a return value of the current function. + * + * @return The value which was stored to return by this function. + * + * @see will_return() + */ +LargestIntegralType mock(void); +#else +#define mock() _mock(__func__, __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Retrieve a typed return value of the current function. + * + * The value would be casted to type internally to avoid having the + * caller to do the cast manually. + * + * @param[in] #type The expected type of the return value + * + * @return The value which was stored to return by this function. + * + * @code + * int param; + * + * param = mock_type(int); + * @endcode + * + * @see will_return() + * @see mock() + * @see mock_ptr_type() + */ +#type mock_type(#type); +#else +#define mock_type(type) ((type) mock()) +#endif + +#ifdef DOXYGEN +/** + * @brief Retrieve a typed return value of the current function. + * + * The value would be casted to type internally to avoid having the + * caller to do the cast manually but also casted to uintptr_t to make + * sure the result has a valid size to be used as a pointer. + * + * @param[in] #type The expected type of the return value + * + * @return The value which was stored to return by this function. + * + * @code + * char *param; + * + * param = mock_ptr_type(char *); + * @endcode + * + * @see will_return() + * @see mock() + * @see mock_type() + */ +type mock_ptr_type(#type); +#else +#define mock_ptr_type(type) ((type) (uintptr_t) mock()) +#endif + + +#ifdef DOXYGEN +/** + * @brief Store a value to be returned by mock() later. + * + * @param[in] #function The function which should return the given value. + * + * @param[in] value The value to be returned by mock(). + * + * @code + * int return_integer(void) + * { + * return (int)mock(); + * } + * + * static void test_integer_return(void **state) + * { + * will_return(return_integer, 42); + * + * assert_int_equal(my_function_calling_return_integer(), 42); + * } + * @endcode + * + * @see mock() + * @see will_return_count() + */ +void will_return(#function, LargestIntegralType value); +#else +#define will_return(function, value) \ + _will_return(#function, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Store a value to be returned by mock() later. + * + * @param[in] #function The function which should return the given value. + * + * @param[in] value The value to be returned by mock(). + * + * @param[in] count The parameter indicates the number of times the value should + * be returned by mock(). If count is set to -1, the value + * will always be returned but must be returned at least once. + * If count is set to -2, the value will always be returned + * by mock(), but is not required to be returned. + * + * @see mock() + */ +void will_return_count(#function, LargestIntegralType value, int count); +#else +#define will_return_count(function, value, count) \ + _will_return(#function, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), count) +#endif + +#ifdef DOXYGEN +/** + * @brief Store a value that will be always returned by mock(). + * + * @param[in] #function The function which should return the given value. + * + * @param[in] #value The value to be returned by mock(). + * + * This is equivalent to: + * @code + * will_return_count(function, value, -1); + * @endcode + * + * @see will_return_count() + * @see mock() + */ +void will_return_always(#function, LargestIntegralType value); +#else +#define will_return_always(function, value) \ + will_return_count(function, (value), WILL_RETURN_ALWAYS) +#endif + +#ifdef DOXYGEN +/** + * @brief Store a value that may be always returned by mock(). + * + * This stores a value which will always be returned by mock() but is not + * required to be returned by at least one call to mock(). Therefore, + * in contrast to will_return_always() which causes a test failure if it + * is not returned at least once, will_return_maybe() will never cause a test + * to fail if its value is not returned. + * + * @param[in] #function The function which should return the given value. + * + * @param[in] #value The value to be returned by mock(). + * + * This is equivalent to: + * @code + * will_return_count(function, value, -2); + * @endcode + * + * @see will_return_count() + * @see mock() + */ +void will_return_maybe(#function, LargestIntegralType value); +#else +#define will_return_maybe(function, value) \ + will_return_count(function, (value), WILL_RETURN_ONCE) +#endif +/** @} */ + +/** + * @defgroup cmocka_param Checking Parameters + * @ingroup cmocka + * + * Functionality to store expected values for mock function parameters. + * + * In addition to storing the return values of mock functions, cmocka provides + * functionality to store expected values for mock function parameters using + * the expect_*() functions provided. A mock function parameter can then be + * validated using the check_expected() macro. + * + * Successive calls to expect_*() macros for a parameter queues values to check + * the specified parameter. check_expected() checks a function parameter + * against the next value queued using expect_*(), if the parameter check fails + * a test failure is signalled. In addition if check_expected() is called and + * no more parameter values are queued a test failure occurs. + * + * The following test stub illustrates how to do this. First is the the function + * we call in the test driver: + * + * @code + * static void test_driver(void **state) + * { + * expect_string(chef_cook, order, "hotdog"); + * } + * @endcode + * + * Now the chef_cook function can check if the parameter we got passed is the + * parameter which is expected by the test driver. This can be done the + * following way: + * + * @code + * int chef_cook(const char *order, char **dish_out) + * { + * check_expected(order); + * } + * @endcode + * + * For a complete example please take a look + * here + * + * @{ + */ + +/* + * Add a custom parameter checking function. If the event parameter is NULL + * the event structure is allocated internally by this function. If event + * parameter is provided it must be allocated on the heap and doesn't need to + * be deallocated by the caller. + */ +#ifdef DOXYGEN +/** + * @brief Add a custom parameter checking function. + * + * If the event parameter is NULL the event structure is allocated internally + * by this function. If the parameter is provided it must be allocated on the + * heap and doesn't need to be deallocated by the caller. + * + * @param[in] #function The function to add a custom parameter checking + * function for. + * + * @param[in] #parameter The parameters passed to the function. + * + * @param[in] #check_function The check function to call. + * + * @param[in] check_data The data to pass to the check function. + */ +void expect_check(#function, #parameter, #check_function, const void *check_data); +#else +#define expect_check(function, parameter, check_function, check_data) \ + _expect_check(#function, #parameter, __FILE__, __LINE__, check_function, \ + cast_to_largest_integral_type(check_data), NULL, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter value is part of the provided + * array. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] value_array[] The array to check for the value. + * + * @see check_expected(). + */ +void expect_in_set(#function, #parameter, LargestIntegralType value_array[]); +#else +#define expect_in_set(function, parameter, value_array) \ + expect_in_set_count(function, parameter, value_array, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter value is part of the provided + * array. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] value_array[] The array to check for the value. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_in_set_count(#function, #parameter, LargestIntegralType value_array[], size_t count); +#else +#define expect_in_set_count(function, parameter, value_array, count) \ + _expect_in_set(#function, #parameter, __FILE__, __LINE__, value_array, \ + sizeof(value_array) / sizeof((value_array)[0]), count) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter value is not part of the + * provided array. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] value_array[] The array to check for the value. + * + * @see check_expected(). + */ +void expect_not_in_set(#function, #parameter, LargestIntegralType value_array[]); +#else +#define expect_not_in_set(function, parameter, value_array) \ + expect_not_in_set_count(function, parameter, value_array, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter value is not part of the + * provided array. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] value_array[] The array to check for the value. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_not_in_set_count(#function, #parameter, LargestIntegralType value_array[], size_t count); +#else +#define expect_not_in_set_count(function, parameter, value_array, count) \ + _expect_not_in_set( \ + #function, #parameter, __FILE__, __LINE__, value_array, \ + sizeof(value_array) / sizeof((value_array)[0]), count) +#endif + + +#ifdef DOXYGEN +/** + * @brief Add an event to check a parameter is inside a numerical range. + * The check would succeed if minimum <= value <= maximum. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] minimum The lower boundary of the interval to check against. + * + * @param[in] maximum The upper boundary of the interval to check against. + * + * @see check_expected(). + */ +void expect_in_range(#function, #parameter, LargestIntegralType minimum, LargestIntegralType maximum); +#else +#define expect_in_range(function, parameter, minimum, maximum) \ + expect_in_range_count(function, parameter, minimum, maximum, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to repeatedly check a parameter is inside a + * numerical range. The check would succeed if minimum <= value <= maximum. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] minimum The lower boundary of the interval to check against. + * + * @param[in] maximum The upper boundary of the interval to check against. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_in_range_count(#function, #parameter, LargestIntegralType minimum, LargestIntegralType maximum, size_t count); +#else +#define expect_in_range_count(function, parameter, minimum, maximum, count) \ + _expect_in_range(#function, #parameter, __FILE__, __LINE__, minimum, \ + maximum, count) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check a parameter is outside a numerical range. + * The check would succeed if minimum > value > maximum. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] minimum The lower boundary of the interval to check against. + * + * @param[in] maximum The upper boundary of the interval to check against. + * + * @see check_expected(). + */ +void expect_not_in_range(#function, #parameter, LargestIntegralType minimum, LargestIntegralType maximum); +#else +#define expect_not_in_range(function, parameter, minimum, maximum) \ + expect_not_in_range_count(function, parameter, minimum, maximum, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to repeatedly check a parameter is outside a + * numerical range. The check would succeed if minimum > value > maximum. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] minimum The lower boundary of the interval to check against. + * + * @param[in] maximum The upper boundary of the interval to check against. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_not_in_range_count(#function, #parameter, LargestIntegralType minimum, LargestIntegralType maximum, size_t count); +#else +#define expect_not_in_range_count(function, parameter, minimum, maximum, \ + count) \ + _expect_not_in_range(#function, #parameter, __FILE__, __LINE__, \ + minimum, maximum, count) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if a parameter is the given value. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] value The value to check. + * + * @see check_expected(). + */ +void expect_value(#function, #parameter, LargestIntegralType value); +#else +#define expect_value(function, parameter, value) \ + expect_value_count(function, parameter, value, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to repeatedly check if a parameter is the given value. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] value The value to check. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_value_count(#function, #parameter, LargestIntegralType value, size_t count); +#else +#define expect_value_count(function, parameter, value, count) \ + _expect_value(#function, #parameter, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), count) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if a parameter isn't the given value. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] value The value to check. + * + * @see check_expected(). + */ +void expect_not_value(#function, #parameter, LargestIntegralType value); +#else +#define expect_not_value(function, parameter, value) \ + expect_not_value_count(function, parameter, value, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to repeatedly check if a parameter isn't the given value. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] value The value to check. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_not_value_count(#function, #parameter, LargestIntegralType value, size_t count); +#else +#define expect_not_value_count(function, parameter, value, count) \ + _expect_not_value(#function, #parameter, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), count) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter value is equal to the + * provided string. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] string The string value to compare. + * + * @see check_expected(). + */ +void expect_string(#function, #parameter, const char *string); +#else +#define expect_string(function, parameter, string) \ + expect_string_count(function, parameter, string, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter value is equal to the + * provided string. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] string The string value to compare. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_string_count(#function, #parameter, const char *string, size_t count); +#else +#define expect_string_count(function, parameter, string, count) \ + _expect_string(#function, #parameter, __FILE__, __LINE__, \ + (const char*)(string), count) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter value isn't equal to the + * provided string. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] string The string value to compare. + * + * @see check_expected(). + */ +void expect_not_string(#function, #parameter, const char *string); +#else +#define expect_not_string(function, parameter, string) \ + expect_not_string_count(function, parameter, string, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter value isn't equal to the + * provided string. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] string The string value to compare. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_not_string_count(#function, #parameter, const char *string, size_t count); +#else +#define expect_not_string_count(function, parameter, string, count) \ + _expect_not_string(#function, #parameter, __FILE__, __LINE__, \ + (const char*)(string), count) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter does match an area of memory. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] memory The memory to compare. + * + * @param[in] size The size of the memory to compare. + * + * @see check_expected(). + */ +void expect_memory(#function, #parameter, void *memory, size_t size); +#else +#define expect_memory(function, parameter, memory, size) \ + expect_memory_count(function, parameter, memory, size, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to repeatedly check if the parameter does match an area + * of memory. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] memory The memory to compare. + * + * @param[in] size The size of the memory to compare. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_memory_count(#function, #parameter, void *memory, size_t size, size_t count); +#else +#define expect_memory_count(function, parameter, memory, size, count) \ + _expect_memory(#function, #parameter, __FILE__, __LINE__, \ + (const void*)(memory), size, count) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to check if the parameter doesn't match an area of + * memory. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] memory The memory to compare. + * + * @param[in] size The size of the memory to compare. + * + * @see check_expected(). + */ +void expect_not_memory(#function, #parameter, void *memory, size_t size); +#else +#define expect_not_memory(function, parameter, memory, size) \ + expect_not_memory_count(function, parameter, memory, size, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to repeatedly check if the parameter doesn't match an + * area of memory. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] memory The memory to compare. + * + * @param[in] size The size of the memory to compare. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_not_memory_count(#function, #parameter, void *memory, size_t size, size_t count); +#else +#define expect_not_memory_count(function, parameter, memory, size, count) \ + _expect_not_memory(#function, #parameter, __FILE__, __LINE__, \ + (const void*)(memory), size, count) +#endif + + +#ifdef DOXYGEN +/** + * @brief Add an event to check if a parameter (of any value) has been passed. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @see check_expected(). + */ +void expect_any(#function, #parameter); +#else +#define expect_any(function, parameter) \ + expect_any_count(function, parameter, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to always check if a parameter (of any value) has been passed. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @see check_expected(). + */ +void expect_any_always(#function, #parameter); +#else +#define expect_any_always(function, parameter) \ + expect_any_count(function, parameter, WILL_RETURN_ALWAYS) +#endif + +#ifdef DOXYGEN +/** + * @brief Add an event to repeatedly check if a parameter (of any value) has + * been passed. + * + * The event is triggered by calling check_expected() in the mocked function. + * + * @param[in] #function The function to add the check for. + * + * @param[in] #parameter The name of the parameter passed to the function. + * + * @param[in] count The count parameter returns the number of times the value + * should be returned by check_expected(). If count is set + * to -1 the value will always be returned. + * + * @see check_expected(). + */ +void expect_any_count(#function, #parameter, size_t count); +#else +#define expect_any_count(function, parameter, count) \ + _expect_any(#function, #parameter, __FILE__, __LINE__, count) +#endif + +#ifdef DOXYGEN +/** + * @brief Determine whether a function parameter is correct. + * + * This ensures the next value queued by one of the expect_*() macros matches + * the specified variable. + * + * This function needs to be called in the mock object. + * + * @param[in] #parameter The parameter to check. + */ +void check_expected(#parameter); +#else +#define check_expected(parameter) \ + _check_expected(__func__, #parameter, __FILE__, __LINE__, \ + cast_to_largest_integral_type(parameter)) +#endif + +#ifdef DOXYGEN +/** + * @brief Determine whether a function parameter is correct. + * + * This ensures the next value queued by one of the expect_*() macros matches + * the specified variable. + * + * This function needs to be called in the mock object. + * + * @param[in] #parameter The pointer to check. + */ +void check_expected_ptr(#parameter); +#else +#define check_expected_ptr(parameter) \ + _check_expected(__func__, #parameter, __FILE__, __LINE__, \ + cast_ptr_to_largest_integral_type(parameter)) +#endif + +/** @} */ + +/** + * @defgroup cmocka_asserts Assert Macros + * @ingroup cmocka + * + * This is a set of useful assert macros like the standard C libary's + * assert(3) macro. + * + * On an assertion failure a cmocka assert macro will write the failure to the + * standard error stream and signal a test failure. Due to limitations of the C + * language the general C standard library assert() and cmocka's assert_true() + * and assert_false() macros can only display the expression that caused the + * assert failure. cmocka's type specific assert macros, assert_{type}_equal() + * and assert_{type}_not_equal(), display the data that caused the assertion + * failure which increases data visibility aiding debugging of failing test + * cases. + * + * @{ + */ + +#ifdef DOXYGEN +/** + * @brief Assert that the given expression is true. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if expression is false (i.e., compares equal to + * zero). + * + * @param[in] expression The expression to evaluate. + * + * @see assert_int_equal() + * @see assert_string_equal() + */ +void assert_true(scalar expression); +#else +#define assert_true(c) _assert_true(cast_to_largest_integral_type(c), #c, \ + __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the given expression is false. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if expression is true. + * + * @param[in] expression The expression to evaluate. + * + * @see assert_int_equal() + * @see assert_string_equal() + */ +void assert_false(scalar expression); +#else +#define assert_false(c) _assert_true(!(cast_to_largest_integral_type(c)), #c, \ + __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the return_code is greater than or equal to 0. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the return code is smaller than 0. If the function + * you check sets an errno if it fails you can pass it to the function and + * it will be printed as part of the error message. + * + * @param[in] rc The return code to evaluate. + * + * @param[in] error Pass errno here or 0. + */ +void assert_return_code(int rc, int error); +#else +#define assert_return_code(rc, error) \ + _assert_return_code(cast_to_largest_integral_type(rc), \ + sizeof(rc), \ + cast_to_largest_integral_type(error), \ + #rc, __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the given pointer is non-NULL. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the pointer is NULL. + * + * @param[in] pointer The pointer to evaluate. + * + * @see assert_null() + */ +void assert_non_null(void *pointer); +#else +#define assert_non_null(c) _assert_true(cast_ptr_to_largest_integral_type(c), #c, \ + __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the given pointer is NULL. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the pointer is non-NULL. + * + * @param[in] pointer The pointer to evaluate. + * + * @see assert_non_null() + */ +void assert_null(void *pointer); +#else +#define assert_null(c) _assert_true(!(cast_ptr_to_largest_integral_type(c)), #c, \ +__FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the two given pointers are equal. + * + * The function prints an error message and terminates the test by calling + * fail() if the pointers are not equal. + * + * @param[in] a The first pointer to compare. + * + * @param[in] b The pointer to compare against the first one. + */ +void assert_ptr_equal(void *a, void *b); +#else +#define assert_ptr_equal(a, b) \ + _assert_int_equal(cast_ptr_to_largest_integral_type(a), \ + cast_ptr_to_largest_integral_type(b), \ + __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the two given pointers are not equal. + * + * The function prints an error message and terminates the test by calling + * fail() if the pointers are equal. + * + * @param[in] a The first pointer to compare. + * + * @param[in] b The pointer to compare against the first one. + */ +void assert_ptr_not_equal(void *a, void *b); +#else +#define assert_ptr_not_equal(a, b) \ + _assert_int_not_equal(cast_ptr_to_largest_integral_type(a), \ + cast_ptr_to_largest_integral_type(b), \ + __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the two given integers are equal. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the integers are not equal. + * + * @param[in] a The first integer to compare. + * + * @param[in] b The integer to compare against the first one. + */ +void assert_int_equal(int a, int b); +#else +#define assert_int_equal(a, b) \ + _assert_int_equal(cast_to_largest_integral_type(a), \ + cast_to_largest_integral_type(b), \ + __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the two given integers are not equal. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the integers are equal. + * + * @param[in] a The first integer to compare. + * + * @param[in] b The integer to compare against the first one. + * + * @see assert_int_equal() + */ +void assert_int_not_equal(int a, int b); +#else +#define assert_int_not_equal(a, b) \ + _assert_int_not_equal(cast_to_largest_integral_type(a), \ + cast_to_largest_integral_type(b), \ + __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the two given float are equal given an epsilon. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the float are not equal (given an epsilon). + * + * @param[in] a The first float to compare. + * + * @param[in] b The float to compare against the first one. + * + * @param[in] epsilon The epsilon used as margin for float comparison. + */ +void assert_float_equal(float a, float b, float epsilon); +#else +#define assert_float_equal(a, b, epsilon) \ + _assert_float_equal((float)a, \ + (float)b, \ + (float)epsilon, \ + __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the two given float are not equal given an epsilon. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the float are not equal (given an epsilon). + * + * @param[in] a The first float to compare. + * + * @param[in] b The float to compare against the first one. + * + * @param[in] epsilon The epsilon used as margin for float comparison. + */ +void assert_float_not_equal(float a, float b, float epsilon); +#else +#define assert_float_not_equal(a, b, epsilon) \ + _assert_float_not_equal((float)a, \ + (float)b, \ + (float)epsilon, \ + __FILE__, __LINE__) +#endif + + +#ifdef DOXYGEN +/** + * @brief Assert that the two given strings are equal. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the strings are not equal. + * + * @param[in] a The string to check. + * + * @param[in] b The other string to compare. + */ +void assert_string_equal(const char *a, const char *b); +#else +#define assert_string_equal(a, b) \ + _assert_string_equal((const char*)(a), (const char*)(b), __FILE__, \ + __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the two given strings are not equal. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the strings are equal. + * + * @param[in] a The string to check. + * + * @param[in] b The other string to compare. + */ +void assert_string_not_equal(const char *a, const char *b); +#else +#define assert_string_not_equal(a, b) \ + _assert_string_not_equal((const char*)(a), (const char*)(b), __FILE__, \ + __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the two given areas of memory are equal, otherwise fail. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the memory is not equal. + * + * @param[in] a The first memory area to compare + * (interpreted as unsigned char). + * + * @param[in] b The second memory area to compare + * (interpreted as unsigned char). + * + * @param[in] size The first n bytes of the memory areas to compare. + */ +void assert_memory_equal(const void *a, const void *b, size_t size); +#else +#define assert_memory_equal(a, b, size) \ + _assert_memory_equal((const void*)(a), (const void*)(b), size, __FILE__, \ + __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the two given areas of memory are not equal. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if the memory is equal. + * + * @param[in] a The first memory area to compare + * (interpreted as unsigned char). + * + * @param[in] b The second memory area to compare + * (interpreted as unsigned char). + * + * @param[in] size The first n bytes of the memory areas to compare. + */ +void assert_memory_not_equal(const void *a, const void *b, size_t size); +#else +#define assert_memory_not_equal(a, b, size) \ + _assert_memory_not_equal((const void*)(a), (const void*)(b), size, \ + __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the specified value is not smaller than the minimum + * and and not greater than the maximum. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if value is not in range. + * + * @param[in] value The value to check. + * + * @param[in] minimum The minimum value allowed. + * + * @param[in] maximum The maximum value allowed. + */ +void assert_in_range(LargestIntegralType value, LargestIntegralType minimum, LargestIntegralType maximum); +#else +#define assert_in_range(value, minimum, maximum) \ + _assert_in_range( \ + cast_to_largest_integral_type(value), \ + cast_to_largest_integral_type(minimum), \ + cast_to_largest_integral_type(maximum), __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the specified value is smaller than the minimum or + * greater than the maximum. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if value is in range. + * + * @param[in] value The value to check. + * + * @param[in] minimum The minimum value to compare. + * + * @param[in] maximum The maximum value to compare. + */ +void assert_not_in_range(LargestIntegralType value, LargestIntegralType minimum, LargestIntegralType maximum); +#else +#define assert_not_in_range(value, minimum, maximum) \ + _assert_not_in_range( \ + cast_to_largest_integral_type(value), \ + cast_to_largest_integral_type(minimum), \ + cast_to_largest_integral_type(maximum), __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the specified value is within a set. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if value is not within a set. + * + * @param[in] value The value to look up + * + * @param[in] values[] The array to check for the value. + * + * @param[in] count The size of the values array. + */ +void assert_in_set(LargestIntegralType value, LargestIntegralType values[], size_t count); +#else +#define assert_in_set(value, values, number_of_values) \ + _assert_in_set(value, values, number_of_values, __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Assert that the specified value is not within a set. + * + * The function prints an error message to standard error and terminates the + * test by calling fail() if value is within a set. + * + * @param[in] value The value to look up + * + * @param[in] values[] The array to check for the value. + * + * @param[in] count The size of the values array. + */ +void assert_not_in_set(LargestIntegralType value, LargestIntegralType values[], size_t count); +#else +#define assert_not_in_set(value, values, number_of_values) \ + _assert_not_in_set(value, values, number_of_values, __FILE__, __LINE__) +#endif + +/** @} */ + +/** + * @defgroup cmocka_call_order Call Ordering + * @ingroup cmocka + * + * It is often beneficial to make sure that functions are called in an + * order. This is independent of mock returns and parameter checking as both + * of the aforementioned do not check the order in which they are called from + * different functions. + * + *
    + *
  • expect_function_call(function) - The + * expect_function_call() macro pushes an expectation onto the stack of + * expected calls.
  • + * + *
  • function_called() - pops a value from the stack of + * expected calls. function_called() is invoked within the mock object + * that uses it. + *
+ * + * expect_function_call() and function_called() are intended to be used in + * pairs. Cmocka will fail a test if there are more or less expected calls + * created (e.g. expect_function_call()) than consumed with function_called(). + * There are provisions such as ignore_function_calls() which allow this + * restriction to be circumvented in tests where mock calls for the code under + * test are not the focus of the test. + * + * The following example illustrates how a unit test instructs cmocka + * to expect a function_called() from a particular mock, + * chef_sing(): + * + * @code + * void chef_sing(void); + * + * void code_under_test() + * { + * chef_sing(); + * } + * + * void some_test(void **state) + * { + * expect_function_call(chef_sing); + * code_under_test(); + * } + * @endcode + * + * The implementation of the mock then must check whether it was meant to + * be called by invoking function_called(): + * + * @code + * void chef_sing() + * { + * function_called(); + * } + * @endcode + * + * @{ + */ + +#ifdef DOXYGEN +/** + * @brief Check that current mocked function is being called in the expected + * order + * + * @see expect_function_call() + */ +void function_called(void); +#else +#define function_called() _function_called(__func__, __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Store expected call(s) to a mock to be checked by function_called() + * later. + * + * @param[in] #function The function which should should be called + * + * @param[in] times number of times this mock must be called + * + * @see function_called() + */ +void expect_function_calls(#function, const int times); +#else +#define expect_function_calls(function, times) \ + _expect_function_call(#function, __FILE__, __LINE__, times) +#endif + +#ifdef DOXYGEN +/** + * @brief Store expected single call to a mock to be checked by + * function_called() later. + * + * @param[in] #function The function which should should be called + * + * @see function_called() + */ +void expect_function_call(#function); +#else +#define expect_function_call(function) \ + _expect_function_call(#function, __FILE__, __LINE__, 1) +#endif + +#ifdef DOXYGEN +/** + * @brief Expects function_called() from given mock at least once + * + * @param[in] #function The function which should should be called + * + * @see function_called() + */ +void expect_function_call_any(#function); +#else +#define expect_function_call_any(function) \ + _expect_function_call(#function, __FILE__, __LINE__, -1) +#endif + +#ifdef DOXYGEN +/** + * @brief Ignores function_called() invocations from given mock function. + * + * @param[in] #function The function which should should be called + * + * @see function_called() + */ +void ignore_function_calls(#function); +#else +#define ignore_function_calls(function) \ + _expect_function_call(#function, __FILE__, __LINE__, -2) +#endif + +/** @} */ + +/** + * @defgroup cmocka_exec Running Tests + * @ingroup cmocka + * + * This is the way tests are executed with CMocka. + * + * The following example illustrates this macro's use with the unit_test macro. + * + * @code + * void Test0(void **state); + * void Test1(void **state); + * + * int main(void) + * { + * const struct CMUnitTest tests[] = { + * cmocka_unit_test(Test0), + * cmocka_unit_test(Test1), + * }; + * + * return cmocka_run_group_tests(tests, NULL, NULL); + * } + * @endcode + * + * @{ + */ + +#ifdef DOXYGEN +/** + * @brief Forces the test to fail immediately and quit. + */ +void fail(void); +#else +#define fail() _fail(__FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Forces the test to not be executed, but marked as skipped + */ +void skip(void); +#else +#define skip() _skip(__FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Forces the test to fail immediately and quit, printing the reason. + * + * @code + * fail_msg("This is some error message for test"); + * @endcode + * + * or + * + * @code + * char *error_msg = "This is some error message for test"; + * fail_msg("%s", error_msg); + * @endcode + */ +void fail_msg(const char *msg, ...); +#else +#define fail_msg(msg, ...) do { \ + print_error("ERROR: " msg "\n", ##__VA_ARGS__); \ + fail(); \ +} while (0) +#endif + +#ifdef DOXYGEN +/** + * @brief Generic method to run a single test. + * + * @deprecated This function was deprecated in favor of cmocka_run_group_tests + * + * @param[in] #function The function to test. + * + * @return 0 on success, 1 if an error occured. + * + * @code + * // A test case that does nothing and succeeds. + * void null_test_success(void **state) { + * } + * + * int main(void) { + * return run_test(null_test_success); + * } + * @endcode + */ +int run_test(#function); +#else +#define run_test(f) _run_test(#f, f, NULL, UNIT_TEST_FUNCTION_TYPE_TEST, NULL) +#endif + +static inline void _unit_test_dummy(void **state) { + (void)state; +} + +/** Initializes a UnitTest structure. + * + * @deprecated This function was deprecated in favor of cmocka_unit_test + */ +#define unit_test(f) { #f, f, UNIT_TEST_FUNCTION_TYPE_TEST } + +#define _unit_test_setup(test, setup) \ + { #test "_" #setup, setup, UNIT_TEST_FUNCTION_TYPE_SETUP } + +/** Initializes a UnitTest structure with a setup function. + * + * @deprecated This function was deprecated in favor of cmocka_unit_test_setup + */ +#define unit_test_setup(test, setup) \ + _unit_test_setup(test, setup), \ + unit_test(test), \ + _unit_test_teardown(test, _unit_test_dummy) + +#define _unit_test_teardown(test, teardown) \ + { #test "_" #teardown, teardown, UNIT_TEST_FUNCTION_TYPE_TEARDOWN } + +/** Initializes a UnitTest structure with a teardown function. + * + * @deprecated This function was deprecated in favor of cmocka_unit_test_teardown + */ +#define unit_test_teardown(test, teardown) \ + _unit_test_setup(test, _unit_test_dummy), \ + unit_test(test), \ + _unit_test_teardown(test, teardown) + +/** Initializes a UnitTest structure for a group setup function. + * + * @deprecated This function was deprecated in favor of cmocka_run_group_tests + */ +#define group_test_setup(setup) \ + { "group_" #setup, setup, UNIT_TEST_FUNCTION_TYPE_GROUP_SETUP } + +/** Initializes a UnitTest structure for a group teardown function. + * + * @deprecated This function was deprecated in favor of cmocka_run_group_tests + */ +#define group_test_teardown(teardown) \ + { "group_" #teardown, teardown, UNIT_TEST_FUNCTION_TYPE_GROUP_TEARDOWN } + +/** + * Initialize an array of UnitTest structures with a setup function for a test + * and a teardown function. Either setup or teardown can be NULL. + * + * @deprecated This function was deprecated in favor of + * cmocka_unit_test_setup_teardown + */ +#define unit_test_setup_teardown(test, setup, teardown) \ + _unit_test_setup(test, setup), \ + unit_test(test), \ + _unit_test_teardown(test, teardown) + + +/** Initializes a CMUnitTest structure. */ +#define cmocka_unit_test(f) { #f, f, NULL, NULL, NULL } + +/** Initializes a CMUnitTest structure with a setup function. */ +#define cmocka_unit_test_setup(f, setup) { #f, f, setup, NULL, NULL } + +/** Initializes a CMUnitTest structure with a teardown function. */ +#define cmocka_unit_test_teardown(f, teardown) { #f, f, NULL, teardown, NULL } + +/** + * Initialize an array of CMUnitTest structures with a setup function for a test + * and a teardown function. Either setup or teardown can be NULL. + */ +#define cmocka_unit_test_setup_teardown(f, setup, teardown) { #f, f, setup, teardown, NULL } + +/** + * Initialize a CMUnitTest structure with given initial state. It will be passed + * to test function as an argument later. It can be used when test state does + * not need special initialization or was initialized already. + * @note If the group setup function initialized the state already, it won't be + * overridden by the initial state defined here. + */ +#define cmocka_unit_test_prestate(f, state) { #f, f, NULL, NULL, state } + +/** + * Initialize a CMUnitTest structure with given initial state, setup and + * teardown function. Any of these values can be NULL. Initial state is passed + * later to setup function, or directly to test if none was given. + * @note If the group setup function initialized the state already, it won't be + * overridden by the initial state defined here. + */ +#define cmocka_unit_test_prestate_setup_teardown(f, setup, teardown, state) { #f, f, setup, teardown, state } + +#define run_tests(tests) _run_tests(tests, sizeof(tests) / sizeof((tests)[0])) +#define run_group_tests(tests) _run_group_tests(tests, sizeof(tests) / sizeof((tests)[0])) + +#ifdef DOXYGEN +/** + * @brief Run tests specified by an array of CMUnitTest structures. + * + * @param[in] group_tests[] The array of unit tests to execute. + * + * @param[in] group_setup The setup function which should be called before + * all unit tests are executed. + * + * @param[in] group_teardown The teardown function to be called after all + * tests have finished. + * + * @return 0 on success, or the number of failed tests. + * + * @code + * static int setup(void **state) { + * int *answer = malloc(sizeof(int)); + * if (*answer == NULL) { + * return -1; + * } + * *answer = 42; + * + * *state = answer; + * + * return 0; + * } + * + * static int teardown(void **state) { + * free(*state); + * + * return 0; + * } + * + * static void null_test_success(void **state) { + * (void) state; + * } + * + * static void int_test_success(void **state) { + * int *answer = *state; + * assert_int_equal(*answer, 42); + * } + * + * int main(void) { + * const struct CMUnitTest tests[] = { + * cmocka_unit_test(null_test_success), + * cmocka_unit_test_setup_teardown(int_test_success, setup, teardown), + * }; + * + * return cmocka_run_group_tests(tests, NULL, NULL); + * } + * @endcode + * + * @see cmocka_unit_test + * @see cmocka_unit_test_setup + * @see cmocka_unit_test_teardown + * @see cmocka_unit_test_setup_teardown + */ +int cmocka_run_group_tests(const struct CMUnitTest group_tests[], + CMFixtureFunction group_setup, + CMFixtureFunction group_teardown); +#else +# define cmocka_run_group_tests(group_tests, group_setup, group_teardown) \ + _cmocka_run_group_tests(#group_tests, group_tests, sizeof(group_tests) / sizeof((group_tests)[0]), group_setup, group_teardown) +#endif + +#ifdef DOXYGEN +/** + * @brief Run tests specified by an array of CMUnitTest structures and specify + * a name. + * + * @param[in] group_name The name of the group test. + * + * @param[in] group_tests[] The array of unit tests to execute. + * + * @param[in] group_setup The setup function which should be called before + * all unit tests are executed. + * + * @param[in] group_teardown The teardown function to be called after all + * tests have finished. + * + * @return 0 on success, or the number of failed tests. + * + * @code + * static int setup(void **state) { + * int *answer = malloc(sizeof(int)); + * if (*answer == NULL) { + * return -1; + * } + * *answer = 42; + * + * *state = answer; + * + * return 0; + * } + * + * static int teardown(void **state) { + * free(*state); + * + * return 0; + * } + * + * static void null_test_success(void **state) { + * (void) state; + * } + * + * static void int_test_success(void **state) { + * int *answer = *state; + * assert_int_equal(*answer, 42); + * } + * + * int main(void) { + * const struct CMUnitTest tests[] = { + * cmocka_unit_test(null_test_success), + * cmocka_unit_test_setup_teardown(int_test_success, setup, teardown), + * }; + * + * return cmocka_run_group_tests_name("success_test", tests, NULL, NULL); + * } + * @endcode + * + * @see cmocka_unit_test + * @see cmocka_unit_test_setup + * @see cmocka_unit_test_teardown + * @see cmocka_unit_test_setup_teardown + */ +int cmocka_run_group_tests_name(const char *group_name, + const struct CMUnitTest group_tests[], + CMFixtureFunction group_setup, + CMFixtureFunction group_teardown); +#else +# define cmocka_run_group_tests_name(group_name, group_tests, group_setup, group_teardown) \ + _cmocka_run_group_tests(group_name, group_tests, sizeof(group_tests) / sizeof((group_tests)[0]), group_setup, group_teardown) +#endif + +/** @} */ + +/** + * @defgroup cmocka_alloc Dynamic Memory Allocation + * @ingroup cmocka + * + * Memory leaks, buffer overflows and underflows can be checked using cmocka. + * + * To test for memory leaks, buffer overflows and underflows a module being + * tested by cmocka should replace calls to malloc(), calloc() and free() to + * test_malloc(), test_calloc() and test_free() respectively. Each time a block + * is deallocated using test_free() it is checked for corruption, if a corrupt + * block is found a test failure is signalled. All blocks allocated using the + * test_*() allocation functions are tracked by the cmocka library. When a test + * completes if any allocated blocks (memory leaks) remain they are reported + * and a test failure is signalled. + * + * For simplicity cmocka currently executes all tests in one process. Therefore + * all test cases in a test application share a single address space which + * means memory corruption from a single test case could potentially cause the + * test application to exit prematurely. + * + * @{ + */ + +#ifdef DOXYGEN +/** + * @brief Test function overriding malloc. + * + * @param[in] size The bytes which should be allocated. + * + * @return A pointer to the allocated memory or NULL on error. + * + * @code + * #ifdef UNIT_TESTING + * extern void* _test_malloc(const size_t size, const char* file, const int line); + * + * #define malloc(size) _test_malloc(size, __FILE__, __LINE__) + * #endif + * + * void leak_memory() { + * int * const temporary = (int*)malloc(sizeof(int)); + * *temporary = 0; + * } + * @endcode + * + * @see malloc(3) + */ +void *test_malloc(size_t size); +#else +#define test_malloc(size) _test_malloc(size, __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Test function overriding calloc. + * + * The memory is set to zero. + * + * @param[in] nmemb The number of elements for an array to be allocated. + * + * @param[in] size The size in bytes of each array element to allocate. + * + * @return A pointer to the allocated memory, NULL on error. + * + * @see calloc(3) + */ +void *test_calloc(size_t nmemb, size_t size); +#else +#define test_calloc(num, size) _test_calloc(num, size, __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Test function overriding realloc which detects buffer overruns + * and memoery leaks. + * + * @param[in] ptr The memory block which should be changed. + * + * @param[in] size The bytes which should be allocated. + * + * @return The newly allocated memory block, NULL on error. + */ +void *test_realloc(void *ptr, size_t size); +#else +#define test_realloc(ptr, size) _test_realloc(ptr, size, __FILE__, __LINE__) +#endif + +#ifdef DOXYGEN +/** + * @brief Test function overriding free(3). + * + * @param[in] ptr The pointer to the memory space to free. + * + * @see free(3). + */ +void test_free(void *ptr); +#else +#define test_free(ptr) _test_free(ptr, __FILE__, __LINE__) +#endif + +/* Redirect malloc, calloc and free to the unit test allocators. */ +#ifdef UNIT_TESTING +#define malloc test_malloc +#define realloc test_realloc +#define calloc test_calloc +#define free test_free +#endif /* UNIT_TESTING */ + +/** @} */ + + +/** + * @defgroup cmocka_mock_assert Standard Assertions + * @ingroup cmocka + * + * How to handle assert(3) of the standard C library. + * + * Runtime assert macros like the standard C library's assert() should be + * redefined in modules being tested to use cmocka's mock_assert() function. + * Normally mock_assert() signals a test failure. If a function is called using + * the expect_assert_failure() macro, any calls to mock_assert() within the + * function will result in the execution of the test. If no calls to + * mock_assert() occur during the function called via expect_assert_failure() a + * test failure is signalled. + * + * @{ + */ + +/** + * @brief Function to replace assert(3) in tested code. + * + * In conjuction with check_assert() it's possible to determine whether an + * assert condition has failed without stopping a test. + * + * @param[in] result The expression to assert. + * + * @param[in] expression The expression as string. + * + * @param[in] file The file mock_assert() is called. + * + * @param[in] line The line mock_assert() is called. + * + * @code + * #ifdef UNIT_TESTING + * extern void mock_assert(const int result, const char* const expression, + * const char * const file, const int line); + * + * #undef assert + * #define assert(expression) \ + * mock_assert((int)(expression), #expression, __FILE__, __LINE__); + * #endif + * + * void increment_value(int * const value) { + * assert(value); + * (*value) ++; + * } + * @endcode + * + * @see assert(3) + * @see expect_assert_failure + */ +void mock_assert(const int result, const char* const expression, + const char * const file, const int line); + +#ifdef DOXYGEN +/** + * @brief Ensure that mock_assert() is called. + * + * If mock_assert() is called the assert expression string is returned. + * + * @param[in] fn_call The function will will call mock_assert(). + * + * @code + * #define assert mock_assert + * + * void showmessage(const char *message) { + * assert(message); + * } + * + * int main(int argc, const char* argv[]) { + * expect_assert_failure(show_message(NULL)); + * printf("succeeded\n"); + * return 0; + * } + * @endcode + * + */ +void expect_assert_failure(function fn_call); +#else +#define expect_assert_failure(function_call) \ + { \ + const int result = setjmp(global_expect_assert_env); \ + global_expecting_assert = 1; \ + if (result) { \ + print_message("Expected assertion %s occurred\n", \ + global_last_failed_assert); \ + global_expecting_assert = 0; \ + } else { \ + function_call ; \ + global_expecting_assert = 0; \ + print_error("Expected assert in %s\n", #function_call); \ + _fail(__FILE__, __LINE__); \ + } \ + } +#endif + +/** @} */ + +/* Function prototype for setup, test and teardown functions. */ +typedef void (*UnitTestFunction)(void **state); + +/* Function that determines whether a function parameter value is correct. */ +typedef int (*CheckParameterValue)(const LargestIntegralType value, + const LargestIntegralType check_value_data); + +/* Type of the unit test function. */ +typedef enum UnitTestFunctionType { + UNIT_TEST_FUNCTION_TYPE_TEST = 0, + UNIT_TEST_FUNCTION_TYPE_SETUP, + UNIT_TEST_FUNCTION_TYPE_TEARDOWN, + UNIT_TEST_FUNCTION_TYPE_GROUP_SETUP, + UNIT_TEST_FUNCTION_TYPE_GROUP_TEARDOWN, +} UnitTestFunctionType; + +/* + * Stores a unit test function with its name and type. + * NOTE: Every setup function must be paired with a teardown function. It's + * possible to specify NULL function pointers. + */ +typedef struct UnitTest { + const char* name; + UnitTestFunction function; + UnitTestFunctionType function_type; +} UnitTest; + +typedef struct GroupTest { + UnitTestFunction setup; + UnitTestFunction teardown; + const UnitTest *tests; + const size_t number_of_tests; +} GroupTest; + +/* Function prototype for test functions. */ +typedef void (*CMUnitTestFunction)(void **state); + +/* Function prototype for setup and teardown functions. */ +typedef int (*CMFixtureFunction)(void **state); + +struct CMUnitTest { + const char *name; + CMUnitTestFunction test_func; + CMFixtureFunction setup_func; + CMFixtureFunction teardown_func; + void *initial_state; +}; + +/* Location within some source code. */ +typedef struct SourceLocation { + const char* file; + int line; +} SourceLocation; + +/* Event that's called to check a parameter value. */ +typedef struct CheckParameterEvent { + SourceLocation location; + const char *parameter_name; + CheckParameterValue check_value; + LargestIntegralType check_value_data; +} CheckParameterEvent; + +/* Used by expect_assert_failure() and mock_assert(). */ +extern int global_expecting_assert; +extern jmp_buf global_expect_assert_env; +extern const char * global_last_failed_assert; + +/* Retrieves a value for the given function, as set by "will_return". */ +LargestIntegralType _mock(const char * const function, const char* const file, + const int line); + +void _expect_function_call( + const char * const function_name, + const char * const file, + const int line, + const int count); + +void _function_called(const char * const function, const char* const file, + const int line); + +void _expect_check( + const char* const function, const char* const parameter, + const char* const file, const int line, + const CheckParameterValue check_function, + const LargestIntegralType check_data, CheckParameterEvent * const event, + const int count); + +void _expect_in_set( + const char* const function, const char* const parameter, + const char* const file, const int line, const LargestIntegralType values[], + const size_t number_of_values, const int count); +void _expect_not_in_set( + const char* const function, const char* const parameter, + const char* const file, const int line, const LargestIntegralType values[], + const size_t number_of_values, const int count); + +void _expect_in_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, + const LargestIntegralType maximum, const int count); +void _expect_not_in_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, + const LargestIntegralType maximum, const int count); + +void _expect_value( + const char* const function, const char* const parameter, + const char* const file, const int line, const LargestIntegralType value, + const int count); +void _expect_not_value( + const char* const function, const char* const parameter, + const char* const file, const int line, const LargestIntegralType value, + const int count); + +void _expect_string( + const char* const function, const char* const parameter, + const char* const file, const int line, const char* string, + const int count); +void _expect_not_string( + const char* const function, const char* const parameter, + const char* const file, const int line, const char* string, + const int count); + +void _expect_memory( + const char* const function, const char* const parameter, + const char* const file, const int line, const void* const memory, + const size_t size, const int count); +void _expect_not_memory( + const char* const function, const char* const parameter, + const char* const file, const int line, const void* const memory, + const size_t size, const int count); + +void _expect_any( + const char* const function, const char* const parameter, + const char* const file, const int line, const int count); + +void _check_expected( + const char * const function_name, const char * const parameter_name, + const char* file, const int line, const LargestIntegralType value); + +void _will_return(const char * const function_name, const char * const file, + const int line, const LargestIntegralType value, + const int count); +void _assert_true(const LargestIntegralType result, + const char* const expression, + const char * const file, const int line); +void _assert_return_code(const LargestIntegralType result, + size_t rlen, + const LargestIntegralType error, + const char * const expression, + const char * const file, + const int line); +void _assert_float_equal(const float a, const float n, + const float epsilon, const char* const file, + const int line); +void _assert_float_not_equal(const float a, const float n, + const float epsilon, const char* const file, + const int line); +void _assert_int_equal( + const LargestIntegralType a, const LargestIntegralType b, + const char * const file, const int line); +void _assert_int_not_equal( + const LargestIntegralType a, const LargestIntegralType b, + const char * const file, const int line); +void _assert_string_equal(const char * const a, const char * const b, + const char * const file, const int line); +void _assert_string_not_equal(const char * const a, const char * const b, + const char *file, const int line); +void _assert_memory_equal(const void * const a, const void * const b, + const size_t size, const char* const file, + const int line); +void _assert_memory_not_equal(const void * const a, const void * const b, + const size_t size, const char* const file, + const int line); +void _assert_in_range( + const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char* const file, const int line); +void _assert_not_in_range( + const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char* const file, const int line); +void _assert_in_set( + const LargestIntegralType value, const LargestIntegralType values[], + const size_t number_of_values, const char* const file, const int line); +void _assert_not_in_set( + const LargestIntegralType value, const LargestIntegralType values[], + const size_t number_of_values, const char* const file, const int line); + +void* _test_malloc(const size_t size, const char* file, const int line); +void* _test_realloc(void *ptr, const size_t size, const char* file, const int line); +void* _test_calloc(const size_t number_of_elements, const size_t size, + const char* file, const int line); +void _test_free(void* const ptr, const char* file, const int line); + +void _fail(const char * const file, const int line); + +void _skip(const char * const file, const int line); + +int _run_test( + const char * const function_name, const UnitTestFunction Function, + void ** const volatile state, const UnitTestFunctionType function_type, + const void* const heap_check_point); +CMOCKA_DEPRECATED int _run_tests(const UnitTest * const tests, + const size_t number_of_tests); +CMOCKA_DEPRECATED int _run_group_tests(const UnitTest * const tests, + const size_t number_of_tests); + +/* Test runner */ +int _cmocka_run_group_tests(const char *group_name, + const struct CMUnitTest * const tests, + const size_t num_tests, + CMFixtureFunction group_setup, + CMFixtureFunction group_teardown); + +/* Standard output and error print methods. */ +void print_message(const char* const format, ...) CMOCKA_PRINTF_ATTRIBUTE(1, 2); +void print_error(const char* const format, ...) CMOCKA_PRINTF_ATTRIBUTE(1, 2); +void vprint_message(const char* const format, va_list args) CMOCKA_PRINTF_ATTRIBUTE(1, 0); +void vprint_error(const char* const format, va_list args) CMOCKA_PRINTF_ATTRIBUTE(1, 0); + +enum cm_message_output { + CM_OUTPUT_STDOUT, + CM_OUTPUT_SUBUNIT, + CM_OUTPUT_TAP, + CM_OUTPUT_XML, +}; + +/** + * @brief Function to set the output format for a test. + * + * The ouput format for the test can either be set globally using this + * function or overriden with environment variable CMOCKA_MESSAGE_OUTPUT. + * + * The environment variable can be set to either STDOUT, SUBUNIT, TAP or XML. + * + * @param[in] output The output format to use for the test. + * + */ +void cmocka_set_message_output(enum cm_message_output output); + + +/** + * @brief Set a pattern to only run the test matching the pattern. + * + * This allows to filter tests and only run the ones matching the pattern. The + * pattern can include two wildards. The first is '*', a wildcard that matches + * zero or more characters, or ‘?’, a wildcard that matches exactly one + * character. + * + * @param[in] pattern The pattern to match, e.g. "test_wurst*" + */ +void cmocka_set_test_filter(const char *pattern); + +/** + * @brief Set a pattern to skip tests matching the pattern. + * + * This allows to filter tests and skip the ones matching the pattern. The + * pattern can include two wildards. The first is '*', a wildcard that matches + * zero or more characters, or ‘?’, a wildcard that matches exactly one + * character. + * + * @param[in] pattern The pattern to match, e.g. "test_wurst*" + */ +void cmocka_set_skip_filter(const char *pattern); + +/** @} */ + +#endif /* CMOCKA_H_ */ diff --git a/src/imports/cmocka/cmocka_private.h b/src/imports/cmocka/cmocka_private.h new file mode 100644 index 0000000..d20d841 --- /dev/null +++ b/src/imports/cmocka/cmocka_private.h @@ -0,0 +1,163 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CMOCKA_PRIVATE_H_ +#define CMOCKA_PRIVATE_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#ifdef _WIN32 +#include + +# ifdef _MSC_VER +# include /* _snprintf */ + +# undef inline +# define inline __inline + +# ifndef va_copy +# define va_copy(dest, src) (dest = src) +# endif + +# define strcasecmp _stricmp +# define strncasecmp _strnicmp + +# if defined(HAVE__SNPRINTF_S) +# undef snprintf +# define snprintf(d, n, ...) _snprintf_s((d), (n), _TRUNCATE, __VA_ARGS__) +# else /* HAVE__SNPRINTF_S */ +# if defined(HAVE__SNPRINTF) +# undef snprintf +# define snprintf _snprintf +# else /* HAVE__SNPRINTF */ +# if !defined(HAVE_SNPRINTF) +# error "no snprintf compatible function found" +# endif /* HAVE_SNPRINTF */ +# endif /* HAVE__SNPRINTF */ +# endif /* HAVE__SNPRINTF_S */ + +# if defined(HAVE__VSNPRINTF_S) +# undef vsnprintf +# define vsnprintf(s, n, f, v) _vsnprintf_s((s), (n), _TRUNCATE, (f), (v)) +# else /* HAVE__VSNPRINTF_S */ +# if defined(HAVE__VSNPRINTF) +# undef vsnprintf +# define vsnprintf _vsnprintf +# else +# if !defined(HAVE_VSNPRINTF) +# error "No vsnprintf compatible function found" +# endif /* HAVE_VSNPRINTF */ +# endif /* HAVE__VSNPRINTF */ +# endif /* HAVE__VSNPRINTF_S */ +# endif /* _MSC_VER */ + +/* + * Backwards compatibility with headers shipped with Visual Studio 2005 and + * earlier. + */ +WINBASEAPI BOOL WINAPI IsDebuggerPresent(VOID); + +#ifndef PRIdS +# define PRIdS "Id" +#endif + +#ifndef PRIu64 +# define PRIu64 "I64u" +#endif + +#ifndef PRIuMAX +# define PRIuMAX PRIu64 +#endif + +#ifndef PRIxMAX +#define PRIxMAX "I64x" +#endif + +#ifndef PRIXMAX +#define PRIXMAX "I64X" +#endif + +#else /* _WIN32 */ + +#ifndef __PRI64_PREFIX +# if __WORDSIZE == 64 +# define __PRI64_PREFIX "l" +# else +# define __PRI64_PREFIX "ll" +# endif +#endif + +#ifndef PRIdS +# define PRIdS "zd" +#endif + +#ifndef PRIu64 +# define PRIu64 __PRI64_PREFIX "u" +#endif + +#ifndef PRIuMAX +# define PRIuMAX __PRI64_PREFIX "u" +#endif + +#ifndef PRIxMAX +#define PRIxMAX __PRI64_PREFIX "x" +#endif + +#ifndef PRIXMAX +#define PRIXMAX __PRI64_PREFIX "X" +#endif + +#endif /* _WIN32 */ + +/** Free memory space */ +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) + +/** Zero a structure given a pointer to the structure */ +#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } while(0) + +/** Get the size of an array */ +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +/** Overwrite the complete string with 'X' */ +#define BURN_STRING(x) do { if ((x) != NULL) memset((x), 'X', strlen((x))); } while(0) + +/** + * This is a hack to fix warnings. The idea is to use this everywhere that we + * get the "discarding const" warning by the compiler. That doesn't actually + * fix the real issue, but marks the place and you can search the code for + * discard_const. + * + * Please use this macro only when there is no other way to fix the warning. + * We should use this function in only in a very few places. + * + * Also, please call this via the discard_const_p() macro interface, as that + * makes the return type safe. + */ +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) + +/** + * Type-safe version of discard_const + */ +#define discard_const_p(type, ptr) ((type *)discard_const(ptr)) + +#endif /* CMOCKA_PRIVATE_H_ */ diff --git a/src/imports/cmocka/config.h b/src/imports/cmocka/config.h new file mode 100644 index 0000000..67f7c7a --- /dev/null +++ b/src/imports/cmocka/config.h @@ -0,0 +1,6 @@ +#pragma once + +#define HAVE_MALLOC_H +#define HAVE_INTTYPES_H +#define HAVE_SIGNAL_H +#define HAVE_STRINGS_H \ No newline at end of file diff --git a/src/imports/fmodex/fmod.h b/src/imports/fmodex/fmod.h new file mode 100644 index 0000000..2c80462 --- /dev/null +++ b/src/imports/fmodex/fmod.h @@ -0,0 +1,2499 @@ +/*$ preserve start $*/ + +/* ============================================================================================ */ +/* FMOD Ex - Main C/C++ header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* This header is the base header for all other FMOD headers. If you are programming in C */ +/* use this exclusively, or if you are programming C++ use this in conjunction with FMOD.HPP */ +/* */ +/* ============================================================================================ */ + +#ifndef _FMOD_H +#define _FMOD_H + +/* + FMOD version number. Check this against FMOD::System::getVersion. + 0xaaaabbcc -> aaaa = major version number. bb = minor version number. cc = development version number. +*/ + +#define FMOD_VERSION 0x00044464 + +/* + Compiler specific settings. +*/ + +#if defined(__CYGWIN32__) + #define F_CDECL __cdecl + #define F_STDCALL __stdcall + #define F_DECLSPEC __declspec + #define F_DLLEXPORT ( dllexport ) +#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) + #define F_CDECL _cdecl + #define F_STDCALL _stdcall + #define F_DECLSPEC __declspec + #define F_DLLEXPORT ( dllexport ) +#elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(__QNX__) + #define F_CDECL + #define F_STDCALL + #define F_DECLSPEC + #define F_DLLEXPORT __attribute__ ((visibility("default"))) +#else + #define F_CDECL + #define F_STDCALL + #define F_DECLSPEC + #define F_DLLEXPORT +#endif + +#ifdef DLL_EXPORTS + #if defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) || defined(__QNX__) + #define F_API __attribute__ ((visibility("default"))) + #else + #define F_API __declspec(dllexport) F_STDCALL + #endif +#else + #define F_API F_STDCALL +#endif + +#define F_CALLBACK F_STDCALL + +/* + FMOD types. +*/ + +typedef int FMOD_BOOL; +typedef struct FMOD_SYSTEM FMOD_SYSTEM; +typedef struct FMOD_SOUND FMOD_SOUND; +typedef struct FMOD_CHANNEL FMOD_CHANNEL; +typedef struct FMOD_CHANNELGROUP FMOD_CHANNELGROUP; +typedef struct FMOD_SOUNDGROUP FMOD_SOUNDGROUP; +typedef struct FMOD_REVERB FMOD_REVERB; +typedef struct FMOD_DSP FMOD_DSP; +typedef struct FMOD_DSPCONNECTION FMOD_DSPCONNECTION; +typedef struct FMOD_POLYGON FMOD_POLYGON; +typedef struct FMOD_GEOMETRY FMOD_GEOMETRY; +typedef struct FMOD_SYNCPOINT FMOD_SYNCPOINT; +typedef struct FMOD_ASYNCREADINFO FMOD_ASYNCREADINFO; +typedef unsigned int FMOD_MODE; +typedef unsigned int FMOD_TIMEUNIT; +typedef unsigned int FMOD_INITFLAGS; +typedef unsigned int FMOD_CAPS; +typedef unsigned int FMOD_DEBUGLEVEL; +typedef unsigned int FMOD_MEMORY_TYPE; + +/*$ fmod result start $*/ +/* +[ENUM] +[ + [DESCRIPTION] + error codes. Returned from every function. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] +] +*/ +typedef enum +{ + FMOD_OK, /* No errors. */ + FMOD_ERR_ALREADYLOCKED, /* Tried to call lock a second time before unlock was called. */ + FMOD_ERR_BADCOMMAND, /* Tried to call a function on a data type that does not allow this type of functionality (ie calling Sound::lock on a streaming sound). */ + FMOD_ERR_CDDA_DRIVERS, /* Neither NTSCSI nor ASPI could be initialised. */ + FMOD_ERR_CDDA_INIT, /* An error occurred while initialising the CDDA subsystem. */ + FMOD_ERR_CDDA_INVALID_DEVICE, /* Couldn't find the specified device. */ + FMOD_ERR_CDDA_NOAUDIO, /* No audio tracks on the specified disc. */ + FMOD_ERR_CDDA_NODEVICES, /* No CD/DVD devices were found. */ + FMOD_ERR_CDDA_NODISC, /* No disc present in the specified drive. */ + FMOD_ERR_CDDA_READ, /* A CDDA read error occurred. */ + FMOD_ERR_CHANNEL_ALLOC, /* Error trying to allocate a channel. */ + FMOD_ERR_CHANNEL_STOLEN, /* The specified channel has been reused to play another sound. */ + FMOD_ERR_COM, /* A Win32 COM related error occured. COM failed to initialize or a QueryInterface failed meaning a Windows codec or driver was not installed properly. */ + FMOD_ERR_DMA, /* DMA Failure. See debug output for more information. */ + FMOD_ERR_DSP_CONNECTION, /* DSP connection error. Connection possibly caused a cyclic dependancy. Or tried to connect a tree too many units deep (more than 128). */ + FMOD_ERR_DSP_FORMAT, /* DSP Format error. A DSP unit may have attempted to connect to this network with the wrong format. */ + FMOD_ERR_DSP_NOTFOUND, /* DSP connection error. Couldn't find the DSP unit specified. */ + FMOD_ERR_DSP_RUNNING, /* DSP error. Cannot perform this operation while the network is in the middle of running. This will most likely happen if a connection or disconnection is attempted in a DSP callback. */ + FMOD_ERR_DSP_TOOMANYCONNECTIONS,/* DSP connection error. The unit being connected to or disconnected should only have 1 input or output. */ + FMOD_ERR_FILE_BAD, /* Error loading file. */ + FMOD_ERR_FILE_COULDNOTSEEK, /* Couldn't perform seek operation. This is a limitation of the medium (ie netstreams) or the file format. */ + FMOD_ERR_FILE_DISKEJECTED, /* Media was ejected while reading. */ + FMOD_ERR_FILE_EOF, /* End of file unexpectedly reached while trying to read essential data (truncated data?). */ + FMOD_ERR_FILE_NOTFOUND, /* File not found. */ + FMOD_ERR_FILE_UNWANTED, /* Unwanted file access occured. */ + FMOD_ERR_FORMAT, /* Unsupported file or audio format. */ + FMOD_ERR_HTTP, /* A HTTP error occurred. This is a catch-all for HTTP errors not listed elsewhere. */ + FMOD_ERR_HTTP_ACCESS, /* The specified resource requires authentication or is forbidden. */ + FMOD_ERR_HTTP_PROXY_AUTH, /* Proxy authentication is required to access the specified resource. */ + FMOD_ERR_HTTP_SERVER_ERROR, /* A HTTP server error occurred. */ + FMOD_ERR_HTTP_TIMEOUT, /* The HTTP request timed out. */ + FMOD_ERR_INITIALIZATION, /* FMOD was not initialized correctly to support this function. */ + FMOD_ERR_INITIALIZED, /* Cannot call this command after System::init. */ + FMOD_ERR_INTERNAL, /* An error occured that wasn't supposed to. Contact support. */ + FMOD_ERR_INVALID_ADDRESS, /* On Xbox 360, this memory address passed to FMOD must be physical, (ie allocated with XPhysicalAlloc.) */ + FMOD_ERR_INVALID_FLOAT, /* Value passed in was a NaN, Inf or denormalized float. */ + FMOD_ERR_INVALID_HANDLE, /* An invalid object handle was used. */ + FMOD_ERR_INVALID_PARAM, /* An invalid parameter was passed to this function. */ + FMOD_ERR_INVALID_POSITION, /* An invalid seek position was passed to this function. */ + FMOD_ERR_INVALID_SPEAKER, /* An invalid speaker was passed to this function based on the current speaker mode. */ + FMOD_ERR_INVALID_SYNCPOINT, /* The syncpoint did not come from this sound handle. */ + FMOD_ERR_INVALID_VECTOR, /* The vectors passed in are not unit length, or perpendicular. */ + FMOD_ERR_MAXAUDIBLE, /* Reached maximum audible playback count for this sound's soundgroup. */ + FMOD_ERR_MEMORY, /* Not enough memory or resources. */ + FMOD_ERR_MEMORY_CANTPOINT, /* Can't use FMOD_OPENMEMORY_POINT on non PCM source data, or non mp3/xma/adpcm data if FMOD_CREATECOMPRESSEDSAMPLE was used. */ + FMOD_ERR_MEMORY_SRAM, /* Not enough memory or resources on console sound ram. */ + FMOD_ERR_NEEDS2D, /* Tried to call a command on a 3d sound when the command was meant for 2d sound. */ + FMOD_ERR_NEEDS3D, /* Tried to call a command on a 2d sound when the command was meant for 3d sound. */ + FMOD_ERR_NEEDSHARDWARE, /* Tried to use a feature that requires hardware support. (ie trying to play a GCADPCM compressed sound in software on Wii). */ + FMOD_ERR_NEEDSSOFTWARE, /* Tried to use a feature that requires the software engine. Software engine has either been turned off, or command was executed on a hardware channel which does not support this feature. */ + FMOD_ERR_NET_CONNECT, /* Couldn't connect to the specified host. */ + FMOD_ERR_NET_SOCKET_ERROR, /* A socket error occurred. This is a catch-all for socket-related errors not listed elsewhere. */ + FMOD_ERR_NET_URL, /* The specified URL couldn't be resolved. */ + FMOD_ERR_NET_WOULD_BLOCK, /* Operation on a non-blocking socket could not complete immediately. */ + FMOD_ERR_NOTREADY, /* Operation could not be performed because specified sound/DSP connection is not ready. */ + FMOD_ERR_OUTPUT_ALLOCATED, /* Error initializing output device, but more specifically, the output device is already in use and cannot be reused. */ + FMOD_ERR_OUTPUT_CREATEBUFFER, /* Error creating hardware sound buffer. */ + FMOD_ERR_OUTPUT_DRIVERCALL, /* A call to a standard soundcard driver failed, which could possibly mean a bug in the driver or resources were missing or exhausted. */ + FMOD_ERR_OUTPUT_ENUMERATION, /* Error enumerating the available driver list. List may be inconsistent due to a recent device addition or removal. */ + FMOD_ERR_OUTPUT_FORMAT, /* Soundcard does not support the minimum features needed for this soundsystem (16bit stereo output). */ + FMOD_ERR_OUTPUT_INIT, /* Error initializing output device. */ + FMOD_ERR_OUTPUT_NOHARDWARE, /* FMOD_HARDWARE was specified but the sound card does not have the resources necessary to play it. */ + FMOD_ERR_OUTPUT_NOSOFTWARE, /* Attempted to create a software sound but no software channels were specified in System::init. */ + FMOD_ERR_PAN, /* Panning only works with mono or stereo sound sources. */ + FMOD_ERR_PLUGIN, /* An unspecified error has been returned from a 3rd party plugin. */ + FMOD_ERR_PLUGIN_INSTANCES, /* The number of allowed instances of a plugin has been exceeded. */ + FMOD_ERR_PLUGIN_MISSING, /* A requested output, dsp unit type or codec was not available. */ + FMOD_ERR_PLUGIN_RESOURCE, /* A resource that the plugin requires cannot be found. (ie the DLS file for MIDI playback or other DLLs that it needs to load) */ + FMOD_ERR_PRELOADED, /* The specified sound is still in use by the event system, call EventSystem::unloadFSB before trying to release it. */ + FMOD_ERR_PROGRAMMERSOUND, /* The specified sound is still in use by the event system, wait for the event which is using it finish with it. */ + FMOD_ERR_RECORD, /* An error occured trying to initialize the recording device. */ + FMOD_ERR_REVERB_INSTANCE, /* Specified instance in FMOD_REVERB_PROPERTIES couldn't be set. Most likely because it is an invalid instance number or the reverb doesnt exist. */ + FMOD_ERR_SUBSOUND_ALLOCATED, /* This subsound is already being used by another sound, you cannot have more than one parent to a sound. Null out the other parent's entry first. */ + FMOD_ERR_SUBSOUND_CANTMOVE, /* Shared subsounds cannot be replaced or moved from their parent stream, such as when the parent stream is an FSB file. */ + FMOD_ERR_SUBSOUND_MODE, /* The subsound's mode bits do not match with the parent sound's mode bits. See documentation for function that it was called with. */ + FMOD_ERR_SUBSOUNDS, /* The error occured because the sound referenced contains subsounds when it shouldn't have, or it doesn't contain subsounds when it should have. The operation may also not be able to be performed on a parent sound, or a parent sound was played without setting up a sentence first. */ + FMOD_ERR_TAGNOTFOUND, /* The specified tag could not be found or there are no tags. */ + FMOD_ERR_TOOMANYCHANNELS, /* The sound created exceeds the allowable input channel count. This can be increased using the maxinputchannels parameter in System::setSoftwareFormat. */ + FMOD_ERR_UNIMPLEMENTED, /* Something in FMOD hasn't been implemented when it should be! contact support! */ + FMOD_ERR_UNINITIALIZED, /* This command failed because System::init or System::setDriver was not called. */ + FMOD_ERR_UNSUPPORTED, /* A command issued was not supported by this object. Possibly a plugin without certain callbacks specified. */ + FMOD_ERR_UPDATE, /* An error caused by System::update occured. */ + FMOD_ERR_VERSION, /* The version number of this file format is not supported. */ + + FMOD_ERR_EVENT_FAILED, /* An Event failed to be retrieved, most likely due to 'just fail' being specified as the max playbacks behavior. */ + FMOD_ERR_EVENT_INFOONLY, /* Can't execute this command on an EVENT_INFOONLY event. */ + FMOD_ERR_EVENT_INTERNAL, /* An error occured that wasn't supposed to. See debug log for reason. */ + FMOD_ERR_EVENT_MAXSTREAMS, /* Event failed because 'Max streams' was hit when FMOD_EVENT_INIT_FAIL_ON_MAXSTREAMS was specified. */ + FMOD_ERR_EVENT_MISMATCH, /* FSB mismatches the FEV it was compiled with, the stream/sample mode it was meant to be created with was different, or the FEV was built for a different platform. */ + FMOD_ERR_EVENT_NAMECONFLICT, /* A category with the same name already exists. */ + FMOD_ERR_EVENT_NOTFOUND, /* The requested event, event group, event category or event property could not be found. */ + FMOD_ERR_EVENT_NEEDSSIMPLE, /* Tried to call a function on a complex event that's only supported by simple events. */ + FMOD_ERR_EVENT_GUIDCONFLICT, /* An event with the same GUID already exists. */ + FMOD_ERR_EVENT_ALREADY_LOADED, /* The specified project or bank has already been loaded. Having multiple copies of the same project loaded simultaneously is forbidden. */ + + FMOD_ERR_MUSIC_UNINITIALIZED, /* Music system is not initialized probably because no music data is loaded. */ + FMOD_ERR_MUSIC_NOTFOUND, /* The requested music entity could not be found. */ + FMOD_ERR_MUSIC_NOCALLBACK, /* The music callback is required, but it has not been set. */ + + FMOD_RESULT_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_RESULT; +/*$ fmod result end $*/ + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing a point in 3D space. + + [REMARKS] + FMOD uses a left handed co-ordinate system by default.
+ To use a right handed co-ordinate system specify FMOD_INIT_3D_RIGHTHANDED from FMOD_INITFLAGS in System::init. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::set3DListenerAttributes + System::get3DListenerAttributes + Channel::set3DAttributes + Channel::get3DAttributes + Channel::set3DCustomRolloff + Channel::get3DCustomRolloff + Sound::set3DCustomRolloff + Sound::get3DCustomRolloff + Geometry::addPolygon + Geometry::setPolygonVertex + Geometry::getPolygonVertex + Geometry::setRotation + Geometry::getRotation + Geometry::setPosition + Geometry::getPosition + Geometry::setScale + Geometry::getScale + FMOD_INITFLAGS +] +*/ +typedef struct +{ + float x; /* X co-ordinate in 3D space. */ + float y; /* Y co-ordinate in 3D space. */ + float z; /* Z co-ordinate in 3D space. */ +} FMOD_VECTOR; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing a globally unique identifier. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::getDriverInfo +] +*/ +typedef struct +{ + unsigned int Data1; /* Specifies the first 8 hexadecimal digits of the GUID */ + unsigned short Data2; /* Specifies the first group of 4 hexadecimal digits. */ + unsigned short Data3; /* Specifies the second group of 4 hexadecimal digits. */ + unsigned char Data4[8]; /* Array of 8 bytes. The first 2 bytes contain the third group of 4 hexadecimal digits. The remaining 6 bytes contain the final 12 hexadecimal digits. */ +} FMOD_GUID; + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure that is passed into FMOD_FILE_ASYNCREADCALLBACK. Use the information in this structure to perform + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
+ Members marked with [w] mean the variable can be written to. The user can set the value.
+
+ Instructions: write to 'buffer', and 'bytesread' BEFORE setting 'result'.
+ As soon as result is set, FMOD will asynchronously continue internally using the data provided in this structure.
+
+ Set 'result' to the result expected from a normal file read callback.
+ If the read was successful, set it to FMOD_OK.
+ If it read some data but hit the end of the file, set it to FMOD_ERR_FILE_EOF.
+ If a bad error occurred, return FMOD_ERR_FILE_BAD
+ If a disk was ejected, return FMOD_ERR_FILE_DISKEJECTED.
+ + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_FILE_ASYNCREADCALLBACK + FMOD_FILE_ASYNCCANCELCALLBACK +] +*/ +struct FMOD_ASYNCREADINFO +{ + void *handle; /* [r] The file handle that was filled out in the open callback. */ + unsigned int offset; /* [r] Seek position, make sure you read from this file offset. */ + unsigned int sizebytes; /* [r] how many bytes requested for read. */ + int priority; /* [r] 0 = low importance. 100 = extremely important (ie 'must read now or stuttering may occur') */ + + void *buffer; /* [w] Buffer to read file data into. */ + unsigned int bytesread; /* [w] Fill this in before setting result code to tell FMOD how many bytes were read. */ + FMOD_RESULT result; /* [r/w] Result code, FMOD_OK tells the system it is ready to consume the data. Set this last! Default value = FMOD_ERR_NOTREADY. */ + + void *userdata; /* [r] User data pointer. */ + + const void (*done)(FMOD_ASYNCREADINFO *info, FMOD_RESULT result); /* FMOD file system wake up function. Use instead of 'result' with FMOD_INIT_ASYNCREAD_FAST to get semaphore based performance improvement. Call this when user file read is finished. Pass result of file read as a parameter. */ +}; + + +/* +[ENUM] +[ + [DESCRIPTION] + These output types are used with System::setOutput / System::getOutput, to choose which output method to use. + + [REMARKS] + To pass information to the driver when initializing fmod use the extradriverdata parameter in System::init for the following reasons.
+
  • FMOD_OUTPUTTYPE_WAVWRITER - extradriverdata is a pointer to a char * filename that the wav writer will output to. +
  • FMOD_OUTPUTTYPE_WAVWRITER_NRT - extradriverdata is a pointer to a char * filename that the wav writer will output to. +
  • FMOD_OUTPUTTYPE_DSOUND - extradriverdata is a pointer to a HWND so that FMOD can set the focus on the audio for a particular window. +
  • FMOD_OUTPUTTYPE_PS3 - extradriverdata is a pointer to a FMOD_PS3_EXTRADRIVERDATA struct. This can be found in fmodps3.h. +
  • FMOD_OUTPUTTYPE_GC - extradriverdata is a pointer to a FMOD_GC_INFO struct. This can be found in fmodgc.h. +
  • FMOD_OUTPUTTYPE_WII - extradriverdata is a pointer to a FMOD_WII_INFO struct. This can be found in fmodwii.h. +
  • FMOD_OUTPUTTYPE_ALSA - extradriverdata is a pointer to a FMOD_LINUX_EXTRADRIVERDATA struct. This can be found in fmodlinux.h.
    +
  • FMOD_OUTPUTTYPE_PULSEAUDIO - extradriverdata is a const char * representing the name of the application to appear in PulseAudio mixer GUIs.
    +
    + Currently these are the only FMOD drivers that take extra information. Other unknown plugins may have different requirements. +

    + Note! If FMOD_OUTPUTTYPE_WAVWRITER_NRT or FMOD_OUTPUTTYPE_NOSOUND_NRT are used, and if the System::update function is being called + very quickly (ie for a non realtime decode) it may be being called too quickly for the FMOD streamer thread to respond to. + The result will be a skipping/stuttering output in the captured audio.
    +
    + To remedy this, disable the FMOD Ex streamer thread, and use FMOD_INIT_STREAM_FROM_UPDATE to avoid skipping in the output stream, + as it will lock the mixer and the streamer together in the same thread. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::setOutput + System::getOutput + System::setSoftwareFormat + System::getSoftwareFormat + System::init + System::update + FMOD_INITFLAGS +] +*/ +typedef enum +{ + FMOD_OUTPUTTYPE_AUTODETECT, /* Picks the best output mode for the platform. This is the default. */ + + FMOD_OUTPUTTYPE_UNKNOWN, /* All - 3rd party plugin, unknown. This is for use with System::getOutput only. */ + FMOD_OUTPUTTYPE_NOSOUND, /* All - All calls in this mode succeed but make no sound. */ + FMOD_OUTPUTTYPE_WAVWRITER, /* All - Writes output to fmodoutput.wav by default. Use the 'extradriverdata' parameter in System::init, by simply passing the filename as a string, to set the wav filename. */ + FMOD_OUTPUTTYPE_NOSOUND_NRT, /* All - Non-realtime version of FMOD_OUTPUTTYPE_NOSOUND. User can drive mixer with System::update at whatever rate they want. */ + FMOD_OUTPUTTYPE_WAVWRITER_NRT, /* All - Non-realtime version of FMOD_OUTPUTTYPE_WAVWRITER. User can drive mixer with System::update at whatever rate they want. */ + + FMOD_OUTPUTTYPE_DSOUND, /* Win32/Win64 - DirectSound output. (Default on Windows XP and below) */ + FMOD_OUTPUTTYPE_WINMM, /* Win32/Win64 - Windows Multimedia output. */ + FMOD_OUTPUTTYPE_WASAPI, /* Win32 - Windows Audio Session API. (Default on Windows Vista and above) */ + FMOD_OUTPUTTYPE_ASIO, /* Win32 - Low latency ASIO 2.0 driver. */ + FMOD_OUTPUTTYPE_OSS, /* Linux/Linux64 - Open Sound System output. (Default on Linux, third preference) */ + FMOD_OUTPUTTYPE_ALSA, /* Linux/Linux64 - Advanced Linux Sound Architecture output. (Default on Linux, second preference if available) */ + FMOD_OUTPUTTYPE_ESD, /* Linux/Linux64 - Enlightment Sound Daemon output. */ + FMOD_OUTPUTTYPE_PULSEAUDIO, /* Linux/Linux64 - PulseAudio output. (Default on Linux, first preference if available) */ + FMOD_OUTPUTTYPE_COREAUDIO, /* Mac - Macintosh CoreAudio output. (Default on Mac) */ + FMOD_OUTPUTTYPE_XBOX360, /* Xbox 360 - Native Xbox360 output. (Default on Xbox 360) */ + FMOD_OUTPUTTYPE_PSP, /* PSP - Native PSP output. (Default on PSP) */ + FMOD_OUTPUTTYPE_PS3, /* PS3 - Native PS3 output. (Default on PS3) */ + FMOD_OUTPUTTYPE_NGP, /* NGP - Native NGP output. (Default on NGP) */ + FMOD_OUTPUTTYPE_WII, /* Wii - Native Wii output. (Default on Wii) */ + FMOD_OUTPUTTYPE_3DS, /* 3DS - Native 3DS output (Default on 3DS) */ + FMOD_OUTPUTTYPE_AUDIOTRACK, /* Android - Java Audio Track output. (Default on Android 2.2 and below) */ + FMOD_OUTPUTTYPE_OPENSL, /* Android - OpenSL ES output. (Default on Android 2.3 and above) */ + FMOD_OUTPUTTYPE_NACL, /* Native Client - Native Client output. (Default on Native Client) */ + FMOD_OUTPUTTYPE_WIIU, /* Wii U - Native Wii U output. (Default on Wii U) */ + FMOD_OUTPUTTYPE_ASOUND, /* BlackBerry - Native BlackBerry asound output. (Default on BlackBerry) */ + FMOD_OUTPUTTYPE_AUDIOOUT, /* Orbis - Audio Out output. (Default on Orbis) */ + FMOD_OUTPUTTYPE_XAUDIO, /* Durango - XAudio2 output. */ + + FMOD_OUTPUTTYPE_MAX, /* Maximum number of output types supported. */ + FMOD_OUTPUTTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_OUTPUTTYPE; + + +/* +[DEFINE] +[ + [NAME] + FMOD_CAPS + + [DESCRIPTION] + Bit fields to use with System::getDriverCaps to determine the capabilities of a card / output device. + + [REMARKS] + It is important to check FMOD_CAPS_HARDWARE_EMULATED on windows machines, to then adjust System::setDSPBufferSize to (1024, 10) to compensate for the higher latency. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::getDriverCaps + System::setDSPBufferSize +] +*/ +#define FMOD_CAPS_NONE 0x00000000 /* Device has no special capabilities. */ +#define FMOD_CAPS_HARDWARE 0x00000001 /* Device supports hardware mixing. */ +#define FMOD_CAPS_HARDWARE_EMULATED 0x00000002 /* User has device set to 'Hardware acceleration = off' in control panel, and now extra 200ms latency is incurred. */ +#define FMOD_CAPS_OUTPUT_MULTICHANNEL 0x00000004 /* Device can do multichannel output, ie greater than 2 channels. */ +#define FMOD_CAPS_OUTPUT_FORMAT_PCM8 0x00000008 /* Device can output to 8bit integer PCM. */ +#define FMOD_CAPS_OUTPUT_FORMAT_PCM16 0x00000010 /* Device can output to 16bit integer PCM. */ +#define FMOD_CAPS_OUTPUT_FORMAT_PCM24 0x00000020 /* Device can output to 24bit integer PCM. */ +#define FMOD_CAPS_OUTPUT_FORMAT_PCM32 0x00000040 /* Device can output to 32bit integer PCM. */ +#define FMOD_CAPS_OUTPUT_FORMAT_PCMFLOAT 0x00000080 /* Device can output to 32bit floating point PCM. */ +#define FMOD_CAPS_REVERB_LIMITED 0x00002000 /* Device supports some form of limited hardware reverb, maybe parameterless and only selectable by environment. */ +#define FMOD_CAPS_LOOPBACK 0x00004000 /* Device is a loopback recording device */ +/* [DEFINE_END] */ + +/* +[DEFINE] +[ + [NAME] + FMOD_DEBUGLEVEL + + [DESCRIPTION] + Bit fields to use with FMOD::Debug_SetLevel / FMOD::Debug_GetLevel to control the level of tty debug output with logging versions of FMOD (fmodL). + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Debug_SetLevel + Debug_GetLevel +] +*/ +#define FMOD_DEBUG_LEVEL_NONE 0x00000000 +#define FMOD_DEBUG_LEVEL_LOG 0x00000001 /* Will display generic logging messages. */ +#define FMOD_DEBUG_LEVEL_ERROR 0x00000002 /* Will display errors. */ +#define FMOD_DEBUG_LEVEL_WARNING 0x00000004 /* Will display warnings that are not fatal. */ +#define FMOD_DEBUG_LEVEL_HINT 0x00000008 /* Will hint to you if there is something possibly better you could be doing. */ +#define FMOD_DEBUG_LEVEL_ALL 0x000000FF +#define FMOD_DEBUG_TYPE_MEMORY 0x00000100 /* Show FMOD memory related logging messages. */ +#define FMOD_DEBUG_TYPE_THREAD 0x00000200 /* Show FMOD thread related logging messages. */ +#define FMOD_DEBUG_TYPE_FILE 0x00000400 /* Show FMOD file system related logging messages. */ +#define FMOD_DEBUG_TYPE_NET 0x00000800 /* Show FMOD network related logging messages. */ +#define FMOD_DEBUG_TYPE_EVENT 0x00001000 /* Show FMOD Event related logging messages. */ +#define FMOD_DEBUG_TYPE_ALL 0x0000FFFF +#define FMOD_DEBUG_DISPLAY_TIMESTAMPS 0x01000000 /* Display the timestamp of the log entry in milliseconds. */ +#define FMOD_DEBUG_DISPLAY_LINENUMBERS 0x02000000 /* Display the FMOD Ex source code line numbers, for debugging purposes. */ +#define FMOD_DEBUG_DISPLAY_COMPRESS 0x04000000 /* If a message is repeated more than 5 times it will stop displaying it and instead display the number of times the message was logged. */ +#define FMOD_DEBUG_DISPLAY_THREAD 0x08000000 /* Display the thread ID of the calling function that caused this log entry to appear. */ +#define FMOD_DEBUG_DISPLAY_ALL 0x0F000000 +#define FMOD_DEBUG_ALL 0xFFFFFFFF +/* [DEFINE_END] */ + + +/* +[DEFINE] +[ + [NAME] + FMOD_MEMORY_TYPE + + [DESCRIPTION] + Bit fields for memory allocation type being passed into FMOD memory callbacks. + + [REMARKS] + Remember this is a bitfield. You may get more than 1 bit set (ie physical + persistent) so do not simply switch on the types! You must check each bit individually or clear out the bits that you do not want within the callback.
    + Bits can be excluded if you want during Memory_Initialize so that you never get them. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_MEMORY_ALLOCCALLBACK + FMOD_MEMORY_REALLOCCALLBACK + FMOD_MEMORY_FREECALLBACK + Memory_Initialize + +] +*/ +#define FMOD_MEMORY_NORMAL 0x00000000 /* Standard memory. */ +#define FMOD_MEMORY_STREAM_FILE 0x00000001 /* Stream file buffer, size controllable with System::setStreamBufferSize. */ +#define FMOD_MEMORY_STREAM_DECODE 0x00000002 /* Stream decode buffer, size controllable with FMOD_CREATESOUNDEXINFO::decodebuffersize. */ +#define FMOD_MEMORY_SAMPLEDATA 0x00000004 /* Sample data buffer. Raw audio data, usually PCM/MPEG/ADPCM/XMA data. */ +#define FMOD_MEMORY_DSP_OUTPUTBUFFER 0x00000008 /* DSP memory block allocated when more than 1 output exists on a DSP node. */ +#define FMOD_MEMORY_XBOX360_PHYSICAL 0x00100000 /* Requires XPhysicalAlloc / XPhysicalFree. */ +#define FMOD_MEMORY_PERSISTENT 0x00200000 /* Persistent memory. Memory will be freed when System::release is called. */ +#define FMOD_MEMORY_SECONDARY 0x00400000 /* Secondary memory. Allocation should be in secondary memory. For example RSX on the PS3. */ +#define FMOD_MEMORY_ALL 0xFFFFFFFF +/* [DEFINE_END] */ + + +/* +[ENUM] +[ + [DESCRIPTION] + These are speaker types defined for use with the System::setSpeakerMode or System::getSpeakerMode command. + + [REMARKS] + These are important notes on speaker modes in regards to sounds created with FMOD_SOFTWARE.
    + Note below the phrase 'sound channels' is used. These are the subchannels inside a sound, they are not related and + have nothing to do with the FMOD class "Channel".
    + For example a mono sound has 1 sound channel, a stereo sound has 2 sound channels, and an AC3 or 6 channel wav file have 6 "sound channels".
    +
    + FMOD_SPEAKERMODE_RAW
    + ---------------------
    + This mode is for output devices that are not specifically mono/stereo/quad/surround/5.1 or 7.1, but are multichannel.
    + Use System::setSoftwareFormat to specify the number of speakers you want to address, otherwise it will default to 2 (stereo).
    + Sound channels map to speakers sequentially, so a mono sound maps to output speaker 0, stereo sound maps to output speaker 0 & 1.
    + The user assumes knowledge of the speaker order. FMOD_SPEAKER enumerations may not apply, so raw channel indices should be used.
    + Multichannel sounds map input channels to output channels 1:1.
    + Channel::setPan and Channel::setSpeakerMix do not work.
    + Speaker levels must be manually set with Channel::setSpeakerLevels.
    +
    + FMOD_SPEAKERMODE_MONO
    + ---------------------
    + This mode is for a 1 speaker arrangement.
    + Panning does not work in this speaker mode.
    + Mono, stereo and multichannel sounds have each sound channel played on the one speaker unity.
    + Mix behavior for multichannel sounds can be set with Channel::setSpeakerLevels.
    + Channel::setSpeakerMix does not work.
    +
    + FMOD_SPEAKERMODE_STEREO
    + -----------------------
    + This mode is for 2 speaker arrangements that have a left and right speaker.
    +
  • Mono sounds default to an even distribution between left and right. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the middle, or full left in the left speaker and full right in the right speaker. +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds have each sound channel played on each speaker at unity.
    +
  • Mix behavior for multichannel sounds can be set with Channel::setSpeakerLevels.
    +
  • Channel::setSpeakerMix works but only front left and right parameters are used, the rest are ignored.
    +
    + FMOD_SPEAKERMODE_QUAD
    + ------------------------
    + This mode is for 4 speaker arrangements that have a front left, front right, rear left and a rear right speaker.
    +
  • Mono sounds default to an even distribution between front left and front right. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the left sound channel played on the front left, and the right sound channel played on the front right.
    +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds default to all of their sound channels being played on each speaker in order of input.
    +
  • Mix behavior for multichannel sounds can be set with Channel::setSpeakerLevels.
    +
  • Channel::setSpeakerMix works but side left, side right, center and lfe are ignored.
    +
    + FMOD_SPEAKERMODE_SURROUND
    + ------------------------
    + This mode is for 5 speaker arrangements that have a left/right/center/rear left/rear right.
    +
  • Mono sounds default to the center speaker. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the left sound channel played on the front left, and the right sound channel played on the front right. +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds default to all of their sound channels being played on each speaker in order of input. +
  • Mix behavior for multichannel sounds can be set with Channel::setSpeakerLevels.
    +
  • Channel::setSpeakerMix works but side left / side right are ignored.
    +
    + FMOD_SPEAKERMODE_5POINT1
    + ------------------------
    + This mode is for 5.1 speaker arrangements that have a left/right/center/rear left/rear right and a subwoofer speaker.
    +
  • Mono sounds default to the center speaker. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the left sound channel played on the front left, and the right sound channel played on the front right. +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds default to all of their sound channels being played on each speaker in order of input. +
  • Mix behavior for multichannel sounds can be set with Channel::setSpeakerLevels.
    +
  • Channel::setSpeakerMix works but side left / side right are ignored.
    +
    + FMOD_SPEAKERMODE_7POINT1
    + ------------------------
    + This mode is for 7.1 speaker arrangements that have a left/right/center/rear left/rear right/side left/side right + and a subwoofer speaker.
    +
  • Mono sounds default to the center speaker. They can be panned with Channel::setPan.
    +
  • Stereo sounds default to the left sound channel played on the front left, and the right sound channel played on the front right. +
  • They can be cross faded with Channel::setPan.
    +
  • Multichannel sounds default to all of their sound channels being played on each speaker in order of input. +
  • Mix behavior for multichannel sounds can be set with Channel::setSpeakerLevels.
    +
  • Channel::setSpeakerMix works and every parameter is used to set the balance of a sound in any speaker.
    +
    + FMOD_SPEAKERMODE_SRS5_1_MATRIX
    + ------------------------------------------------------
    + This mode is for mono, stereo, 5.1 and 6.1 speaker arrangements, as it is backwards and forwards compatible with + stereo, but to get a surround effect a SRS 5.1, Prologic or Prologic 2 hardware decoder / amplifier is needed or + a compatible SRS equipped device (e.g., laptop, TV, etc.) or accessory (e.g., headphone).
    + Pan behavior is the same as FMOD_SPEAKERMODE_5POINT1.
    +
    + If this function is called the numoutputchannels setting in System::setSoftwareFormat is overwritten.
    +
    + Output rate must be 44100, 48000 or 96000 for this to work otherwise FMOD_ERR_OUTPUT_INIT will be returned.
    +
    + FMOD_SPEAKERMODE_DOLBY5_1_MATRIX
    + ------------------------------------------------------
    + This mode is for 5.1 speaker arrangements using a stereo signal, to get a surround effect a Dolby Pro Logic II + hardware decoder / amplifier is needed.
    + Pan behavior is the same as FMOD_SPEAKERMODE_5POINT1.
    +
    + If this function is called the numoutputchannels setting in System::setSoftwareFormat is overwritten.
    +
    + Output rate must be 32000, 44100 or 48000 for this to work otherwise FMOD_ERR_OUTPUT_INIT will be returned.
    +
    + FMOD_SPEAKERMODE_MYEARS
    + ------------------------------------------------------
    + This mode is for headphones. This will attempt to load a MyEars profile (see myears.net.au) and use it to generate + surround sound on headphones using a personalized HRTF algorithm, for realistic 3d sound.
    + Pan behavior is the same as FMOD_SPEAKERMODE_7POINT1.
    + MyEars speaker mode will automatically be set if the speakermode is FMOD_SPEAKERMODE_STEREO and the MyEars profile exists.
    + If this mode is set explicitly, FMOD_INIT_DISABLE_MYEARS_AUTODETECT has no effect.
    + If this mode is set explicitly and the MyEars profile does not exist, FMOD_ERR_OUTPUT_DRIVERCALL will be returned. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::setSpeakerMode + System::getSpeakerMode + System::getDriverCaps + System::setSoftwareFormat + Channel::setSpeakerLevels +] +*/ +typedef enum +{ + FMOD_SPEAKERMODE_RAW, /* There is no specific speakermode. Sound channels are mapped in order of input to output. Use System::setSoftwareFormat to specify speaker count. See remarks for more information. */ + FMOD_SPEAKERMODE_MONO, /* The speakers are monaural. */ + FMOD_SPEAKERMODE_STEREO, /* The speakers are stereo (DEFAULT). */ + FMOD_SPEAKERMODE_QUAD, /* 4 speaker setup. This includes front left, front right, rear left, rear right. */ + FMOD_SPEAKERMODE_SURROUND, /* 5 speaker setup. This includes front left, front right, center, rear left, rear right. */ + FMOD_SPEAKERMODE_5POINT1, /* 5.1 speaker setup. This includes front left, front right, center, rear left, rear right and a subwoofer. */ + FMOD_SPEAKERMODE_7POINT1, /* 7.1 speaker setup. This includes front left, front right, center, rear left, rear right, side left, side right and a subwoofer. */ + + FMOD_SPEAKERMODE_SRS5_1_MATRIX, /* Stereo compatible output, embedded with surround information. SRS 5.1/Prologic/Prologic2 decoders will split the signal into a 5.1 speaker set-up or SRS virtual surround will decode into a 2-speaker/headphone setup. See remarks about limitations.*/ + FMOD_SPEAKERMODE_DOLBY5_1_MATRIX, /* Stereo compatible output, embedded with surround information. Dolby Pro Logic II decoders will split the signal into a 5.1 speaker set-up. */ + FMOD_SPEAKERMODE_MYEARS, /* Stereo output, but data is encoded using personalized HRTF algorithms. See myears.net.au */ + + FMOD_SPEAKERMODE_MAX, /* Maximum number of speaker modes supported. */ + FMOD_SPEAKERMODE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SPEAKERMODE; + + +/* +[ENUM] +[ + [DESCRIPTION] + These are speaker types defined for use with the Channel::setSpeakerLevels command. + It can also be used for speaker placement in the System::set3DSpeakerPosition command. + + [REMARKS] + If you are using FMOD_SPEAKERMODE_RAW and speaker assignments are meaningless, just cast a raw integer value to this type.
    + For example (FMOD_SPEAKER)7 would use the 7th speaker (also the same as FMOD_SPEAKER_SIDE_RIGHT).
    + Values higher than this can be used if an output system has more than 8 speaker types / output channels. 15 is the current maximum.
    +
    + NOTE: On Playstation 3 in 7.1, the extra 2 speakers are not side left/side right, they are 'surround back left'/'surround back right' which + locate the speakers behind the listener instead of to the sides like on PC. FMOD_SPEAKER_SBL/FMOD_SPEAKER_SBR are provided to make it + clearer what speaker is being addressed on that platform. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_SPEAKERMODE + Channel::setSpeakerLevels + Channel::getSpeakerLevels + System::set3DSpeakerPosition + System::get3DSpeakerPosition +] +*/ +typedef enum +{ + FMOD_SPEAKER_FRONT_LEFT, + FMOD_SPEAKER_FRONT_RIGHT, + FMOD_SPEAKER_FRONT_CENTER, + FMOD_SPEAKER_LOW_FREQUENCY, + FMOD_SPEAKER_BACK_LEFT, + FMOD_SPEAKER_BACK_RIGHT, + FMOD_SPEAKER_SIDE_LEFT, + FMOD_SPEAKER_SIDE_RIGHT, + + FMOD_SPEAKER_MAX, /* Maximum number of speaker types supported. */ + FMOD_SPEAKER_MONO = FMOD_SPEAKER_FRONT_LEFT, /* For use with FMOD_SPEAKERMODE_MONO and Channel::SetSpeakerLevels. Mapped to same value as FMOD_SPEAKER_FRONT_LEFT. */ + FMOD_SPEAKER_NULL = 65535, /* A non speaker. Use this with ASIO mapping to ignore a speaker. */ + FMOD_SPEAKER_SBL = FMOD_SPEAKER_SIDE_LEFT, /* For use with FMOD_SPEAKERMODE_7POINT1 on PS3 where the extra speakers are surround back inside of side speakers. */ + FMOD_SPEAKER_SBR = FMOD_SPEAKER_SIDE_RIGHT, /* For use with FMOD_SPEAKERMODE_7POINT1 on PS3 where the extra speakers are surround back inside of side speakers. */ + FMOD_SPEAKER_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SPEAKER; + + +/* +[ENUM] +[ + [DESCRIPTION] + These are plugin types defined for use with the System::getNumPlugins, + System::getPluginInfo and System::unloadPlugin functions. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::getNumPlugins + System::getPluginInfo + System::unloadPlugin +] +*/ +typedef enum +{ + FMOD_PLUGINTYPE_OUTPUT, /* The plugin type is an output module. FMOD mixed audio will play through one of these devices */ + FMOD_PLUGINTYPE_CODEC, /* The plugin type is a file format codec. FMOD will use these codecs to load file formats for playback. */ + FMOD_PLUGINTYPE_DSP, /* The plugin type is a DSP unit. FMOD will use these plugins as part of its DSP network to apply effects to output or generate sound in realtime. */ + + FMOD_PLUGINTYPE_MAX, /* Maximum number of plugin types supported. */ + FMOD_PLUGINTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_PLUGINTYPE; + + +/* +[DEFINE] +[ + [NAME] + FMOD_INITFLAGS + + [DESCRIPTION] + Initialization flags. Use them with System::init in the flags parameter to change various behavior. + + [REMARKS] + Use System::setAdvancedSettings to adjust settings for some of the features that are enabled by these flags. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::init + System::update + System::setAdvancedSettings + Channel::set3DOcclusion +] +*/ +#define FMOD_INIT_NORMAL 0x00000000 /* All platforms - Initialize normally */ +#define FMOD_INIT_STREAM_FROM_UPDATE 0x00000001 /* All platforms - No stream thread is created internally. Streams are driven from System::update. Mainly used with non-realtime outputs. */ +#define FMOD_INIT_3D_RIGHTHANDED 0x00000002 /* All platforms - FMOD will treat +X as right, +Y as up and +Z as backwards (towards you). */ +#define FMOD_INIT_SOFTWARE_DISABLE 0x00000004 /* All platforms - Disable software mixer to save memory. Anything created with FMOD_SOFTWARE will fail and DSP will not work. */ +#define FMOD_INIT_OCCLUSION_LOWPASS 0x00000008 /* All platforms - All FMOD_SOFTWARE (and FMOD_HARDWARE on 3DS and NGP) with FMOD_3D based voices will add a software lowpass filter effect into the DSP chain which is automatically used when Channel::set3DOcclusion is used or the geometry API. */ +#define FMOD_INIT_HRTF_LOWPASS 0x00000010 /* All platforms - All FMOD_SOFTWARE (and FMOD_HARDWARE on 3DS and NGP) with FMOD_3D based voices will add a software lowpass filter effect into the DSP chain which causes sounds to sound duller when the sound goes behind the listener. Use System::setAdvancedSettings to adjust cutoff frequency. */ +#define FMOD_INIT_DISTANCE_FILTERING 0x00000200 /* All platforms - All FMOD_SOFTWARE with FMOD_3D based voices will add a software lowpass and highpass filter effect into the DSP chain which will act as a distance-automated bandpass filter. Use System::setAdvancedSettings to adjust the center frequency. */ +#define FMOD_INIT_REVERB_PREALLOCBUFFERS 0x00000040 /* All platforms - FMOD Software reverb will preallocate enough buffers for reverb per channel, rather than allocating them and freeing them at runtime. */ +#define FMOD_INIT_ENABLE_PROFILE 0x00000020 /* All platforms - Enable TCP/IP based host which allows FMOD Designer or FMOD Profiler to connect to it, and view memory, CPU and the DSP network graph in real-time. */ +#define FMOD_INIT_VOL0_BECOMES_VIRTUAL 0x00000080 /* All platforms - Any sounds that are 0 volume will go virtual and not be processed except for having their positions updated virtually. Use System::setAdvancedSettings to adjust what volume besides zero to switch to virtual at. */ +#define FMOD_INIT_WASAPI_EXCLUSIVE 0x00000100 /* Win32 Vista only - for WASAPI output - Enable exclusive access to hardware, lower latency at the expense of excluding other applications from accessing the audio hardware. */ +#define FMOD_INIT_PS3_PREFERDTS 0x00800000 /* PS3 only - Prefer DTS over Dolby Digital if both are supported. Note: 8 and 6 channel LPCM is always preferred over both DTS and Dolby Digital. */ +#define FMOD_INIT_PS3_FORCE2CHLPCM 0x01000000 /* PS3 only - Force PS3 system output mode to 2 channel LPCM. */ +#define FMOD_INIT_DISABLEDOLBY 0x00100000 /* Wii / 3DS - Disable Dolby Pro Logic surround. Speakermode will be set to STEREO even if user has selected surround in the system settings. */ +#define FMOD_INIT_SYSTEM_MUSICMUTENOTPAUSE 0x00200000 /* Xbox 360 / PS3 - The "music" channelgroup which by default pauses when custom 360 dashboard / PS3 BGM music is played, can be changed to mute (therefore continues playing) instead of pausing, by using this flag. */ +#define FMOD_INIT_SYNCMIXERWITHUPDATE 0x00400000 /* Win32/Wii/PS3/Xbox/Xbox 360 - FMOD Mixer thread is woken up to do a mix when System::update is called rather than waking periodically on its own timer. */ +#define FMOD_INIT_GEOMETRY_USECLOSEST 0x04000000 /* All platforms - With the geometry engine, only process the closest polygon rather than accumulating all polygons the sound to listener line intersects. */ +#define FMOD_INIT_DISABLE_MYEARS_AUTODETECT 0x08000000 /* Win32 - Disables automatic setting of FMOD_SPEAKERMODE_STEREO to FMOD_SPEAKERMODE_MYEARS if the MyEars profile exists on the PC. MyEars is HRTF 7.1 downmixing through headphones. */ +#define FMOD_INIT_PS3_DISABLEDTS 0x10000000 /* PS3 only - Disable DTS output mode selection */ +#define FMOD_INIT_PS3_DISABLEDOLBYDIGITAL 0x20000000 /* PS3 only - Disable Dolby Digital output mode selection */ +#define FMOD_INIT_7POINT1_DOLBYMAPPING 0x40000000 /* PS3/PS4 only - FMOD uses the WAVEFORMATEX Microsoft 7.1 speaker mapping where the last 2 pairs of speakers are 'rears' then 'sides', but on PS3/PS4 these are mapped to 'surrounds' and 'backs'. Use this flag to swap fmod's last 2 pair of speakers on PS3/PS4 to avoid needing to do a special case for these platforms. */ +#define FMOD_INIT_ASYNCREAD_FAST 0x80000000 /* All platforms - Rather than setting FMOD_ASYNCREADINFO::result, call FMOD_ASYNCREADINFO::done to enable a semaphore based wait inside fmod, rather than the older method which can incur up to 10ms delay per read. */ +/* [DEFINE_END] */ + + +/* +[ENUM] +[ + [DESCRIPTION] + These definitions describe the type of song being played. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Sound::getFormat +] +*/ +typedef enum +{ + FMOD_SOUND_TYPE_UNKNOWN, /* 3rd party / unknown plugin format. */ + FMOD_SOUND_TYPE_AIFF, /* AIFF. */ + FMOD_SOUND_TYPE_ASF, /* Microsoft Advanced Systems Format (ie WMA/ASF/WMV). */ + FMOD_SOUND_TYPE_AT3, /* Sony ATRAC 3 format */ + FMOD_SOUND_TYPE_CDDA, /* Digital CD audio. */ + FMOD_SOUND_TYPE_DLS, /* Sound font / downloadable sound bank. */ + FMOD_SOUND_TYPE_FLAC, /* FLAC lossless codec. */ + FMOD_SOUND_TYPE_FSB, /* FMOD Sample Bank. */ + FMOD_SOUND_TYPE_GCADPCM, /* Nintendo GameCube/Wii ADPCM */ + FMOD_SOUND_TYPE_IT, /* Impulse Tracker. */ + FMOD_SOUND_TYPE_MIDI, /* MIDI. extracodecdata is a pointer to an FMOD_MIDI_EXTRACODECDATA structure. */ + FMOD_SOUND_TYPE_MOD, /* Protracker / Fasttracker MOD. */ + FMOD_SOUND_TYPE_MPEG, /* MP2/MP3 MPEG. */ + FMOD_SOUND_TYPE_OGGVORBIS, /* Ogg vorbis. */ + FMOD_SOUND_TYPE_PLAYLIST, /* Information only from ASX/PLS/M3U/WAX playlists */ + FMOD_SOUND_TYPE_RAW, /* Raw PCM data. */ + FMOD_SOUND_TYPE_S3M, /* ScreamTracker 3. */ + FMOD_SOUND_TYPE_SF2, /* Sound font 2 format. */ + FMOD_SOUND_TYPE_USER, /* User created sound. */ + FMOD_SOUND_TYPE_WAV, /* Microsoft WAV. */ + FMOD_SOUND_TYPE_XM, /* FastTracker 2 XM. */ + FMOD_SOUND_TYPE_XMA, /* Xbox360 XMA */ + FMOD_SOUND_TYPE_VAG, /* PlayStation Portable ADPCM VAG format. */ + FMOD_SOUND_TYPE_AUDIOQUEUE, /* iPhone hardware decoder, supports AAC, ALAC and MP3. extracodecdata is a pointer to an FMOD_AUDIOQUEUE_EXTRACODECDATA structure. */ + FMOD_SOUND_TYPE_XWMA, /* Xbox360 XWMA */ + FMOD_SOUND_TYPE_BCWAV, /* 3DS BCWAV container format for DSP ADPCM and PCM */ + FMOD_SOUND_TYPE_AT9, /* NGP ATRAC 9 format */ + FMOD_SOUND_TYPE_VORBIS, /* Raw vorbis */ + FMOD_SOUND_TYPE_MEDIA_FOUNDATION,/* Microsoft Media Foundation wrappers, supports ASF/WMA */ + + FMOD_SOUND_TYPE_MAX, /* Maximum number of sound types supported. */ + FMOD_SOUND_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SOUND_TYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + These definitions describe the native format of the hardware or software buffer that will be used. + + [REMARKS] + This is the format the native hardware or software buffer will be or is created in. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::createSound + Sound::getFormat +] +*/ +typedef enum +{ + FMOD_SOUND_FORMAT_NONE, /* Unitialized / unknown. */ + FMOD_SOUND_FORMAT_PCM8, /* 8bit integer PCM data. */ + FMOD_SOUND_FORMAT_PCM16, /* 16bit integer PCM data. */ + FMOD_SOUND_FORMAT_PCM24, /* 24bit integer PCM data. */ + FMOD_SOUND_FORMAT_PCM32, /* 32bit integer PCM data. */ + FMOD_SOUND_FORMAT_PCMFLOAT, /* 32bit floating point PCM data. */ + FMOD_SOUND_FORMAT_GCADPCM, /* Compressed Nintendo 3DS/Wii DSP data. */ + FMOD_SOUND_FORMAT_IMAADPCM, /* Compressed IMA ADPCM data. */ + FMOD_SOUND_FORMAT_VAG, /* Compressed PlayStation Portable ADPCM data. */ + FMOD_SOUND_FORMAT_HEVAG, /* Compressed PSVita ADPCM data. */ + FMOD_SOUND_FORMAT_XMA, /* Compressed Xbox360 XMA data. */ + FMOD_SOUND_FORMAT_MPEG, /* Compressed MPEG layer 2 or 3 data. */ + FMOD_SOUND_FORMAT_CELT, /* Compressed CELT data. */ + FMOD_SOUND_FORMAT_AT9, /* Compressed PSVita ATRAC9 data. */ + FMOD_SOUND_FORMAT_XWMA, /* Compressed Xbox360 xWMA data. */ + FMOD_SOUND_FORMAT_VORBIS, /* Compressed Vorbis data. */ + + FMOD_SOUND_FORMAT_MAX, /* Maximum number of sound formats supported. */ + FMOD_SOUND_FORMAT_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SOUND_FORMAT; + + +/* +[DEFINE] +[ + [NAME] + FMOD_MODE + + [DESCRIPTION] + Sound description bitfields, bitwise OR them together for loading and describing sounds. + + [REMARKS] + By default a sound will open as a static sound that is decompressed fully into memory to PCM. (ie equivalent of FMOD_CREATESAMPLE)
    + To have a sound stream instead, use FMOD_CREATESTREAM, or use the wrapper function System::createStream.
    + Some opening modes (ie FMOD_OPENUSER, FMOD_OPENMEMORY, FMOD_OPENMEMORY_POINT, FMOD_OPENRAW) will need extra information.
    + This can be provided using the FMOD_CREATESOUNDEXINFO structure. +
    + Specifying FMOD_OPENMEMORY_POINT will POINT to your memory rather allocating its own sound buffers and duplicating it internally.
    + This means you cannot free the memory while FMOD is using it, until after Sound::release is called. + With FMOD_OPENMEMORY_POINT, for PCM formats, only WAV, FSB, and RAW are supported. For compressed formats, only those formats supported by FMOD_CREATECOMPRESSEDSAMPLE are supported.
    + With FMOD_OPENMEMORY_POINT and FMOD_OPENRAW or PCM, if using them together, note that you must pad the data on each side by 16 bytes. This is so fmod can modify the ends of the data for looping/interpolation/mixing purposes. If a wav file, you will need to insert silence, and then reset loop points to stop the playback from playing that silence.
    + With FMOD_OPENMEMORY_POINT, For Wii/PSP FMOD_HARDWARE supports this flag for the GCADPCM/VAG formats. On other platforms FMOD_SOFTWARE must be used.
    +
    + Xbox 360 memory On Xbox 360 Specifying FMOD_OPENMEMORY_POINT to a virtual memory address will cause FMOD_ERR_INVALID_ADDRESS + to be returned. Use physical memory only for this functionality.
    +
    + FMOD_LOWMEM is used on a sound if you want to minimize the memory overhead, by having FMOD not allocate memory for certain + features that are not likely to be used in a game environment. These are :
    + 1. Sound::getName functionality is removed. 256 bytes per sound is saved.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::createSound + System::createStream + Sound::setMode + Sound::getMode + Channel::setMode + Channel::getMode + Sound::set3DCustomRolloff + Channel::set3DCustomRolloff + Sound::getOpenState +] +*/ +#define FMOD_DEFAULT 0x00000000 /* Default for all modes listed below. FMOD_LOOP_OFF, FMOD_2D, FMOD_HARDWARE */ +#define FMOD_LOOP_OFF 0x00000001 /* For non looping sounds. (DEFAULT). Overrides FMOD_LOOP_NORMAL / FMOD_LOOP_BIDI. */ +#define FMOD_LOOP_NORMAL 0x00000002 /* For forward looping sounds. */ +#define FMOD_LOOP_BIDI 0x00000004 /* For bidirectional looping sounds. (only works on software mixed static sounds). */ +#define FMOD_2D 0x00000008 /* Ignores any 3d processing. (DEFAULT). */ +#define FMOD_3D 0x00000010 /* Makes the sound positionable in 3D. Overrides FMOD_2D. */ +#define FMOD_HARDWARE 0x00000020 /* Attempts to make sounds use hardware acceleration. (DEFAULT). Note on platforms that don't support FMOD_HARDWARE (only 3DS, PS Vita, PSP, Wii and Wii U support FMOD_HARDWARE), this will be internally treated as FMOD_SOFTWARE. */ +#define FMOD_SOFTWARE 0x00000040 /* Makes the sound be mixed by the FMOD CPU based software mixer. Overrides FMOD_HARDWARE. Use this for FFT, DSP, compressed sample support, 2D multi-speaker support and other software related features. */ +#define FMOD_CREATESTREAM 0x00000080 /* Decompress at runtime, streaming from the source provided (ie from disk). Overrides FMOD_CREATESAMPLE and FMOD_CREATECOMPRESSEDSAMPLE. Note a stream can only be played once at a time due to a stream only having 1 stream buffer and file handle. Open multiple streams to have them play concurrently. */ +#define FMOD_CREATESAMPLE 0x00000100 /* Decompress at loadtime, decompressing or decoding whole file into memory as the target sample format (ie PCM). Fastest for FMOD_SOFTWARE based playback and most flexible. */ +#define FMOD_CREATECOMPRESSEDSAMPLE 0x00000200 /* Load MP2/MP3/IMAADPCM/CELT/Vorbis/AT9 or XMA into memory and leave it compressed. CELT/Vorbis/AT9 encoding only supported in the FSB file format. During playback the FMOD software mixer will decode it in realtime as a 'compressed sample'. Can only be used in combination with FMOD_SOFTWARE. Overrides FMOD_CREATESAMPLE. If the sound data is not one of the supported formats, it will behave as if it was created with FMOD_CREATESAMPLE and decode the sound into PCM. */ +#define FMOD_OPENUSER 0x00000400 /* Opens a user created static sample or stream. Use FMOD_CREATESOUNDEXINFO to specify format and/or read callbacks. If a user created 'sample' is created with no read callback, the sample will be empty. Use Sound::lock and Sound::unlock to place sound data into the sound if this is the case. */ +#define FMOD_OPENMEMORY 0x00000800 /* "name_or_data" will be interpreted as a pointer to memory instead of filename for creating sounds. Use FMOD_CREATESOUNDEXINFO to specify length. If used with FMOD_CREATESAMPLE or FMOD_CREATECOMPRESSEDSAMPLE, FMOD duplicates the memory into its own buffers. Your own buffer can be freed after open. If used with FMOD_CREATESTREAM, FMOD will stream out of the buffer whose pointer you passed in. In this case, your own buffer should not be freed until you have finished with and released the stream.*/ +#define FMOD_OPENMEMORY_POINT 0x10000000 /* "name_or_data" will be interpreted as a pointer to memory instead of filename for creating sounds. Use FMOD_CREATESOUNDEXINFO to specify length. This differs to FMOD_OPENMEMORY in that it uses the memory as is, without duplicating the memory into its own buffers. For Wii/PSP FMOD_HARDWARE supports this flag for the GCADPCM/VAG formats. On other platforms FMOD_SOFTWARE must be used, as sound hardware on the other platforms (ie PC) cannot access main ram. Cannot be freed after open, only after Sound::release. Will not work if the data is compressed and FMOD_CREATECOMPRESSEDSAMPLE is not used. */ +#define FMOD_OPENRAW 0x00001000 /* Will ignore file format and treat as raw pcm. Use FMOD_CREATESOUNDEXINFO to specify format. Requires at least defaultfrequency, numchannels and format to be specified before it will open. Must be little endian data. */ +#define FMOD_OPENONLY 0x00002000 /* Just open the file, dont prebuffer or read. Good for fast opens for info, or when sound::readData is to be used. */ +#define FMOD_ACCURATETIME 0x00004000 /* For System::createSound - for accurate Sound::getLength/Channel::setPosition on VBR MP3, and MOD/S3M/XM/IT/MIDI files. Scans file first, so takes longer to open. FMOD_OPENONLY does not affect this. */ +#define FMOD_MPEGSEARCH 0x00008000 /* For corrupted / bad MP3 files. This will search all the way through the file until it hits a valid MPEG header. Normally only searches for 4k. */ +#define FMOD_NONBLOCKING 0x00010000 /* For opening sounds and getting streamed subsounds (seeking) asyncronously. Use Sound::getOpenState to poll the state of the sound as it opens or retrieves the subsound in the background. */ +#define FMOD_UNIQUE 0x00020000 /* Unique sound, can only be played one at a time */ +#define FMOD_3D_HEADRELATIVE 0x00040000 /* Make the sound's position, velocity and orientation relative to the listener. */ +#define FMOD_3D_WORLDRELATIVE 0x00080000 /* Make the sound's position, velocity and orientation absolute (relative to the world). (DEFAULT) */ +#define FMOD_3D_INVERSEROLLOFF 0x00100000 /* This sound will follow the inverse rolloff model where mindistance = full volume, maxdistance = where sound stops attenuating, and rolloff is fixed according to the global rolloff factor. (DEFAULT) */ +#define FMOD_3D_LINEARROLLOFF 0x00200000 /* This sound will follow a linear rolloff model where mindistance = full volume, maxdistance = silence. Rolloffscale is ignored. */ +#define FMOD_3D_LINEARSQUAREROLLOFF 0x00400000 /* This sound will follow a linear-square rolloff model where mindistance = full volume, maxdistance = silence. Rolloffscale is ignored. */ +#define FMOD_3D_CUSTOMROLLOFF 0x04000000 /* This sound will follow a rolloff model defined by Sound::set3DCustomRolloff / Channel::set3DCustomRolloff. */ +#define FMOD_3D_IGNOREGEOMETRY 0x40000000 /* Is not affect by geometry occlusion. If not specified in Sound::setMode, or Channel::setMode, the flag is cleared and it is affected by geometry again. */ +#define FMOD_UNICODE 0x01000000 /* Filename is double-byte unicode. */ +#define FMOD_IGNORETAGS 0x02000000 /* Skips id3v2/asf/etc tag checks when opening a sound, to reduce seek/read overhead when opening files (helps with CD performance). */ +#define FMOD_LOWMEM 0x08000000 /* Removes some features from samples to give a lower memory overhead, like Sound::getName. See remarks. */ +#define FMOD_LOADSECONDARYRAM 0x20000000 /* Load sound into the secondary RAM of supported platform. On PS3, sounds will be loaded into RSX/VRAM. */ +#define FMOD_VIRTUAL_PLAYFROMSTART 0x80000000 /* For sounds that start virtual (due to being quiet or low importance), instead of swapping back to audible, and playing at the correct offset according to time, this flag makes the sound play from the start. */ + +/* [DEFINE_END] */ + + +/* +[ENUM] +[ + [DESCRIPTION] + These values describe what state a sound is in after FMOD_NONBLOCKING has been used to open it. + + [REMARKS] + With streams, if you are using FMOD_NONBLOCKING, note that if the user calls Sound::getSubSound, a stream will go into FMOD_OPENSTATE_SEEKING state and sound related commands will return FMOD_ERR_NOTREADY.
    + With streams, if you are using FMOD_NONBLOCKING, note that if the user calls Channel::getPosition, a stream will go into FMOD_OPENSTATE_SETPOSITION state and sound related commands will return FMOD_ERR_NOTREADY.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Sound::getOpenState + FMOD_MODE +] +*/ +typedef enum +{ + FMOD_OPENSTATE_READY = 0, /* Opened and ready to play. */ + FMOD_OPENSTATE_LOADING, /* Initial load in progress. */ + FMOD_OPENSTATE_ERROR, /* Failed to open - file not found, out of memory etc. See return value of Sound::getOpenState for what happened. */ + FMOD_OPENSTATE_CONNECTING, /* Connecting to remote host (internet sounds only). */ + FMOD_OPENSTATE_BUFFERING, /* Buffering data. */ + FMOD_OPENSTATE_SEEKING, /* Seeking to subsound and re-flushing stream buffer. */ + FMOD_OPENSTATE_PLAYING, /* Ready and playing, but not possible to release at this time without stalling the main thread. */ + FMOD_OPENSTATE_SETPOSITION, /* Seeking within a stream to a different position. */ + + FMOD_OPENSTATE_MAX, /* Maximum number of open state types. */ + FMOD_OPENSTATE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_OPENSTATE; + + +/* +[ENUM] +[ + [DESCRIPTION] + These flags are used with SoundGroup::setMaxAudibleBehavior to determine what happens when more sounds + are played than are specified with SoundGroup::setMaxAudible. + + [REMARKS] + When using FMOD_SOUNDGROUP_BEHAVIOR_MUTE, SoundGroup::setMuteFadeSpeed can be used to stop a sudden transition. + Instead, the time specified will be used to cross fade between the sounds that go silent and the ones that become audible. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + SoundGroup::setMaxAudibleBehavior + SoundGroup::getMaxAudibleBehavior + SoundGroup::setMaxAudible + SoundGroup::getMaxAudible + SoundGroup::setMuteFadeSpeed + SoundGroup::getMuteFadeSpeed +] +*/ +typedef enum +{ + FMOD_SOUNDGROUP_BEHAVIOR_FAIL, /* Any sound played that puts the sound count over the SoundGroup::setMaxAudible setting, will simply fail during System::playSound. */ + FMOD_SOUNDGROUP_BEHAVIOR_MUTE, /* Any sound played that puts the sound count over the SoundGroup::setMaxAudible setting, will be silent, then if another sound in the group stops the sound that was silent before becomes audible again. */ + FMOD_SOUNDGROUP_BEHAVIOR_STEALLOWEST, /* Any sound played that puts the sound count over the SoundGroup::setMaxAudible setting, will steal the quietest / least important sound playing in the group. */ + + FMOD_SOUNDGROUP_BEHAVIOR_MAX, /* Maximum number of open state types. */ + FMOD_SOUNDGROUP_BEHAVIOR_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SOUNDGROUP_BEHAVIOR; + + +/* +[ENUM] +[ + [DESCRIPTION] + These callback types are used with Channel::setCallback. + + [REMARKS] + Each callback has commanddata parameters passed as int unique to the type of callback.
    + See reference to FMOD_CHANNEL_CALLBACK to determine what they might mean for each type of callback.
    +
    + Note! Currently the user must call System::update for these callbacks to trigger! + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Channel::setCallback + FMOD_CHANNEL_CALLBACK + System::update +] +*/ +typedef enum +{ + FMOD_CHANNEL_CALLBACKTYPE_END, /* Called when a sound ends. */ + FMOD_CHANNEL_CALLBACKTYPE_VIRTUALVOICE, /* Called when a voice is swapped out or swapped in. */ + FMOD_CHANNEL_CALLBACKTYPE_SYNCPOINT, /* Called when a syncpoint is encountered. Can be from wav file markers. */ + FMOD_CHANNEL_CALLBACKTYPE_OCCLUSION, /* Called when the channel has its geometry occlusion value calculated. Can be used to clamp or change the value. */ + + FMOD_CHANNEL_CALLBACKTYPE_MAX, /* Maximum number of callback types supported. */ + FMOD_CHANNEL_CALLBACKTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_CHANNEL_CALLBACKTYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + These callback types are used with System::setCallback. + + [REMARKS] + Each callback has commanddata parameters passed as void* unique to the type of callback.
    + See reference to FMOD_SYSTEM_CALLBACK to determine what they might mean for each type of callback.
    +
    + Note! Using FMOD_SYSTEM_CALLBACKTYPE_DEVICELISTCHANGED (on Mac only) requires the application to be running an event loop which will allow external changes to device list to be detected by FMOD. +
    + Note! The 'system' object pointer will be null for FMOD_SYSTEM_CALLBACKTYPE_MEMORYALLOCATIONFAILED callback. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::setCallback + FMOD_SYSTEM_CALLBACK + System::update + DSP::addInput +] +*/ +typedef enum +{ + FMOD_SYSTEM_CALLBACKTYPE_DEVICELISTCHANGED, /* Called from System::update when the enumerated list of devices has changed. */ + FMOD_SYSTEM_CALLBACKTYPE_DEVICELOST, /* Called from System::update when an output device has been lost due to control panel parameter changes and FMOD cannot automatically recover. */ + FMOD_SYSTEM_CALLBACKTYPE_MEMORYALLOCATIONFAILED, /* Called directly when a memory allocation fails somewhere in FMOD. (NOTE - 'system' will be NULL in this callback type.)*/ + FMOD_SYSTEM_CALLBACKTYPE_THREADCREATED, /* Called directly when a thread is created. */ + FMOD_SYSTEM_CALLBACKTYPE_BADDSPCONNECTION, /* Called when a bad connection was made with DSP::addInput. Usually called from mixer thread because that is where the connections are made. */ + FMOD_SYSTEM_CALLBACKTYPE_BADDSPLEVEL, /* Called when too many effects were added exceeding the maximum tree depth of 128. This is most likely caused by accidentally adding too many DSP effects. Usually called from mixer thread because that is where the connections are made. */ + FMOD_SYSTEM_CALLBACKTYPE_THREADDESTROYED, /* Called directly when a thread is destroyed. */ + + FMOD_SYSTEM_CALLBACKTYPE_MAX, /* Maximum number of callback types supported. */ + FMOD_SYSTEM_CALLBACKTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_SYSTEM_CALLBACKTYPE; + + +/* + FMOD Callbacks +*/ +typedef FMOD_RESULT (F_CALLBACK *FMOD_SYSTEM_CALLBACK) (FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACKTYPE type, void *commanddata1, void *commanddata2); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_CHANNEL_CALLBACK) (FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type, void *commanddata1, void *commanddata2); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_SOUND_NONBLOCKCALLBACK)(FMOD_SOUND *sound, FMOD_RESULT result); +typedef FMOD_RESULT (F_CALLBACK *FMOD_SOUND_PCMREADCALLBACK)(FMOD_SOUND *sound, void *data, unsigned int datalen); +typedef FMOD_RESULT (F_CALLBACK *FMOD_SOUND_PCMSETPOSCALLBACK)(FMOD_SOUND *sound, int subsound, unsigned int position, FMOD_TIMEUNIT postype); + +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_OPENCALLBACK) (const char *name, int unicode, unsigned int *filesize, void **handle, void **userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_CLOSECALLBACK) (void *handle, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_READCALLBACK) (void *handle, void *buffer, unsigned int sizebytes, unsigned int *bytesread, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_SEEKCALLBACK) (void *handle, unsigned int pos, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_ASYNCREADCALLBACK)(FMOD_ASYNCREADINFO *info, void *userdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_FILE_ASYNCCANCELCALLBACK)(void *handle, void *userdata); + +typedef void * (F_CALLBACK *FMOD_MEMORY_ALLOCCALLBACK) (unsigned int size, FMOD_MEMORY_TYPE type, const char *sourcestr); +typedef void * (F_CALLBACK *FMOD_MEMORY_REALLOCCALLBACK)(void *ptr, unsigned int size, FMOD_MEMORY_TYPE type, const char *sourcestr); +typedef void (F_CALLBACK *FMOD_MEMORY_FREECALLBACK) (void *ptr, FMOD_MEMORY_TYPE type, const char *sourcestr); + +typedef float (F_CALLBACK *FMOD_3D_ROLLOFFCALLBACK) (FMOD_CHANNEL *channel, float distance); + + +/* +[ENUM] +[ + [DESCRIPTION] + List of windowing methods used in spectrum analysis to reduce leakage / transient signals intefering with the analysis.
    + This is a problem with analysis of continuous signals that only have a small portion of the signal sample (the fft window size).
    + Windowing the signal with a curve or triangle tapers the sides of the fft window to help alleviate this problem. + + [REMARKS] + Cyclic signals such as a sine wave that repeat their cycle in a multiple of the window size do not need windowing.
    + I.e. If the sine wave repeats every 1024, 512, 256 etc samples and the FMOD fft window is 1024, then the signal would not need windowing.
    + Not windowing is the same as FMOD_DSP_FFT_WINDOW_RECT, which is the default.
    + If the cycle of the signal (ie the sine wave) is not a multiple of the window size, it will cause frequency abnormalities, so a different windowing method is needed.
    + +
    + FMOD_DSP_FFT_WINDOW_RECT.
    +
    +
    + FMOD_DSP_FFT_WINDOW_TRIANGLE.
    +
    +
    + FMOD_DSP_FFT_WINDOW_HAMMING.
    +
    +
    + FMOD_DSP_FFT_WINDOW_HANNING.
    +
    +
    + FMOD_DSP_FFT_WINDOW_BLACKMAN.
    +
    +
    + FMOD_DSP_FFT_WINDOW_BLACKMANHARRIS.
    + +
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::getSpectrum + Channel::getSpectrum +] +*/ +typedef enum +{ + FMOD_DSP_FFT_WINDOW_RECT, /* w[n] = 1.0 */ + FMOD_DSP_FFT_WINDOW_TRIANGLE, /* w[n] = TRI(2n/N) */ + FMOD_DSP_FFT_WINDOW_HAMMING, /* w[n] = 0.54 - (0.46 * COS(n/N) ) */ + FMOD_DSP_FFT_WINDOW_HANNING, /* w[n] = 0.5 * (1.0 - COS(n/N) ) */ + FMOD_DSP_FFT_WINDOW_BLACKMAN, /* w[n] = 0.42 - (0.5 * COS(n/N) ) + (0.08 * COS(2.0 * n/N) ) */ + FMOD_DSP_FFT_WINDOW_BLACKMANHARRIS, /* w[n] = 0.35875 - (0.48829 * COS(1.0 * n/N)) + (0.14128 * COS(2.0 * n/N)) - (0.01168 * COS(3.0 * n/N)) */ + + FMOD_DSP_FFT_WINDOW_MAX, /* Maximum number of FFT window types supported. */ + FMOD_DSP_FFT_WINDOW_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DSP_FFT_WINDOW; + + +/* +[ENUM] +[ + [DESCRIPTION] + List of interpolation types that the FMOD Ex software mixer supports. + + [REMARKS] + The default resampler type is FMOD_DSP_RESAMPLER_LINEAR.
    + Use System::setSoftwareFormat to tell FMOD the resampling quality you require for FMOD_SOFTWARE based sounds. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::setSoftwareFormat + System::getSoftwareFormat +] +*/ +typedef enum +{ + FMOD_DSP_RESAMPLER_NOINTERP, /* No interpolation. High frequency aliasing hiss will be audible depending on the sample rate of the sound. */ + FMOD_DSP_RESAMPLER_LINEAR, /* Linear interpolation (default method). Fast and good quality, causes very slight lowpass effect on low frequency sounds. */ + FMOD_DSP_RESAMPLER_CUBIC, /* Cubic interpolation. Slower than linear interpolation but better quality. */ + FMOD_DSP_RESAMPLER_SPLINE, /* 5 point spline interpolation. Slowest resampling method but best quality. */ + + FMOD_DSP_RESAMPLER_MAX, /* Maximum number of resample methods supported. */ + FMOD_DSP_RESAMPLER_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DSP_RESAMPLER; + + +/* +[ENUM] +[ + [DESCRIPTION] + List of tag types that could be stored within a sound. These include id3 tags, metadata from netstreams and vorbis/asf data. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Sound::getTag +] +*/ +typedef enum +{ + FMOD_TAGTYPE_UNKNOWN = 0, + FMOD_TAGTYPE_ID3V1, + FMOD_TAGTYPE_ID3V2, + FMOD_TAGTYPE_VORBISCOMMENT, + FMOD_TAGTYPE_SHOUTCAST, + FMOD_TAGTYPE_ICECAST, + FMOD_TAGTYPE_ASF, + FMOD_TAGTYPE_MIDI, + FMOD_TAGTYPE_PLAYLIST, + FMOD_TAGTYPE_FMOD, + FMOD_TAGTYPE_USER, + + FMOD_TAGTYPE_MAX, /* Maximum number of tag types supported. */ + FMOD_TAGTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_TAGTYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + List of data types that can be returned by Sound::getTag + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Sound::getTag +] +*/ +typedef enum +{ + FMOD_TAGDATATYPE_BINARY = 0, + FMOD_TAGDATATYPE_INT, + FMOD_TAGDATATYPE_FLOAT, + FMOD_TAGDATATYPE_STRING, + FMOD_TAGDATATYPE_STRING_UTF16, + FMOD_TAGDATATYPE_STRING_UTF16BE, + FMOD_TAGDATATYPE_STRING_UTF8, + FMOD_TAGDATATYPE_CDTOC, + + FMOD_TAGDATATYPE_MAX, /* Maximum number of tag datatypes supported. */ + FMOD_TAGDATATYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_TAGDATATYPE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Types of delay that can be used with Channel::setDelay / Channel::getDelay. + + [REMARKS] + If you haven't called Channel::setDelay yet, if you call Channel::getDelay with FMOD_DELAYTYPE_DSPCLOCK_START it will return the + equivalent global DSP clock value to determine when a channel started, so that you can use it for other channels to sync against.
    +
    + Use System::getDSPClock to also get the current dspclock time, a base for future calls to Channel::setDelay.
    +
    + Use FMOD_64BIT_ADD or FMOD_64BIT_SUB to add a hi/lo combination together and cope with wraparound. +
    + If FMOD_DELAYTYPE_END_MS is specified, the value is not treated as a 64 bit number, just the delayhi value is used and it is treated as milliseconds. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Channel::setDelay + Channel::getDelay + System::getDSPClock +] +*/ +typedef enum +{ + FMOD_DELAYTYPE_END_MS, /* Delay at the end of the sound in milliseconds. Use delayhi only. Channel::isPlaying will remain true until this delay has passed even though the sound itself has stopped playing.*/ + FMOD_DELAYTYPE_DSPCLOCK_START, /* Time the sound started if Channel::getDelay is used, or if Channel::setDelay is used, the sound will delay playing until this exact tick. */ + FMOD_DELAYTYPE_DSPCLOCK_END, /* Time the sound should end. If this is non-zero, the channel will go silent at this exact tick. */ + FMOD_DELAYTYPE_DSPCLOCK_PAUSE, /* Time the sound should pause. If this is non-zero, the channel will pause at this exact tick. */ + + FMOD_DELAYTYPE_MAX, /* Maximum number of tag datatypes supported. */ + FMOD_DELAYTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DELAYTYPE; + + +#define FMOD_64BIT_ADD(_hi1, _lo1, _hi2, _lo2) _hi1 += ((_hi2) + ((((_lo1) + (_lo2)) < (_lo1)) ? 1 : 0)); (_lo1) += (_lo2); +#define FMOD_64BIT_SUB(_hi1, _lo1, _hi2, _lo2) _hi1 -= ((_hi2) + ((((_lo1) - (_lo2)) > (_lo1)) ? 1 : 0)); (_lo1) -= (_lo2); + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing a piece of tag data. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Sound::getTag + FMOD_TAGTYPE + FMOD_TAGDATATYPE +] +*/ +typedef struct FMOD_TAG +{ + FMOD_TAGTYPE type; /* [r] The type of this tag. */ + FMOD_TAGDATATYPE datatype; /* [r] The type of data that this tag contains */ + char *name; /* [r] The name of this tag i.e. "TITLE", "ARTIST" etc. */ + void *data; /* [r] Pointer to the tag data - its format is determined by the datatype member */ + unsigned int datalen; /* [r] Length of the data contained in this tag */ + FMOD_BOOL updated; /* [r] True if this tag has been updated since last being accessed with Sound::getTag */ +} FMOD_TAG; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure describing a CD/DVD table of contents + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Sound::getTag +] +*/ +typedef struct FMOD_CDTOC +{ + int numtracks; /* [r] The number of tracks on the CD */ + int min[100]; /* [r] The start offset of each track in minutes */ + int sec[100]; /* [r] The start offset of each track in seconds */ + int frame[100]; /* [r] The start offset of each track in frames */ +} FMOD_CDTOC; + + +/* +[DEFINE] +[ + [NAME] + FMOD_TIMEUNIT + + [DESCRIPTION] + List of time types that can be returned by Sound::getLength and used with Channel::setPosition or Channel::getPosition. + + [REMARKS] + FMOD_TIMEUNIT_SENTENCE_MS, FMOD_TIMEUNIT_SENTENCE_PCM, FMOD_TIMEUNIT_SENTENCE_PCMBYTES, FMOD_TIMEUNIT_SENTENCE and FMOD_TIMEUNIT_SENTENCE_SUBSOUND are only supported by Channel functions. + Do not combine flags except FMOD_TIMEUNIT_BUFFERED. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Sound::getLength + Channel::setPosition + Channel::getPosition +] +*/ +#define FMOD_TIMEUNIT_MS 0x00000001 /* Milliseconds. */ +#define FMOD_TIMEUNIT_PCM 0x00000002 /* PCM samples, related to milliseconds * samplerate / 1000. */ +#define FMOD_TIMEUNIT_PCMBYTES 0x00000004 /* Bytes, related to PCM samples * channels * datawidth (ie 16bit = 2 bytes). */ +#define FMOD_TIMEUNIT_RAWBYTES 0x00000008 /* Raw file bytes of (compressed) sound data (does not include headers). Only used by Sound::getLength and Channel::getPosition. */ +#define FMOD_TIMEUNIT_PCMFRACTION 0x00000010 /* Fractions of 1 PCM sample. Unsigned int range 0 to 0xFFFFFFFF. Used for sub-sample granularity for DSP purposes. */ +#define FMOD_TIMEUNIT_MODORDER 0x00000100 /* MOD/S3M/XM/IT. Order in a sequenced module format. Use Sound::getFormat to determine the PCM format being decoded to. */ +#define FMOD_TIMEUNIT_MODROW 0x00000200 /* MOD/S3M/XM/IT. Current row in a sequenced module format. Sound::getLength will return the number of rows in the currently playing or seeked to pattern. */ +#define FMOD_TIMEUNIT_MODPATTERN 0x00000400 /* MOD/S3M/XM/IT. Current pattern in a sequenced module format. Sound::getLength will return the number of patterns in the song and Channel::getPosition will return the currently playing pattern. */ +#define FMOD_TIMEUNIT_SENTENCE_MS 0x00010000 /* Currently playing subsound in a sentence time in milliseconds. */ +#define FMOD_TIMEUNIT_SENTENCE_PCM 0x00020000 /* Currently playing subsound in a sentence time in PCM Samples, related to milliseconds * samplerate / 1000. */ +#define FMOD_TIMEUNIT_SENTENCE_PCMBYTES 0x00040000 /* Currently playing subsound in a sentence time in bytes, related to PCM samples * channels * datawidth (ie 16bit = 2 bytes). */ +#define FMOD_TIMEUNIT_SENTENCE 0x00080000 /* Currently playing sentence index according to the channel. */ +#define FMOD_TIMEUNIT_SENTENCE_SUBSOUND 0x00100000 /* Currently playing subsound index in a sentence. */ +#define FMOD_TIMEUNIT_BUFFERED 0x10000000 /* Time value as seen by buffered stream. This is always ahead of audible time, and is only used for processing. */ +/* [DEFINE_END] */ + + +/* +[ENUM] +[ + [DESCRIPTION] + When creating a multichannel sound, FMOD will pan them to their default speaker locations, for example a 6 channel sound will default to one channel per 5.1 output speaker.
    + Another example is a stereo sound. It will default to left = front left, right = front right.
    +
    + This is for sounds that are not 'default'. For example you might have a sound that is 6 channels but actually made up of 3 stereo pairs, that should all be located in front left, front right only. + + [REMARKS] + For full flexibility of speaker assignments, use Channel::setSpeakerLevels. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_CREATESOUNDEXINFO + Channel::setSpeakerLevels +] +*/ +typedef enum +{ + FMOD_SPEAKERMAPTYPE_DEFAULT, /* This is the default, and just means FMOD decides which speakers it puts the source channels. */ + FMOD_SPEAKERMAPTYPE_ALLMONO, /* This means the sound is made up of all mono sounds. All voices will be panned to the front center by default in this case. */ + FMOD_SPEAKERMAPTYPE_ALLSTEREO, /* This means the sound is made up of all stereo sounds. All voices will be panned to front left and front right alternating every second channel. */ + FMOD_SPEAKERMAPTYPE_51_PROTOOLS /* Map a 5.1 sound to use protools L C R Ls Rs LFE mapping. Will return an error if not a 6 channel sound. */ +} FMOD_SPEAKERMAPTYPE; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Use this structure with System::createSound when more control is needed over loading.
    + The possible reasons to use this with System::createSound are:
    +
  • Loading a file from memory. +
  • Loading a file from within another larger (possibly wad/pak) file, by giving the loader an offset and length. +
  • To create a user created / non file based sound. +
  • To specify a starting subsound to seek to within a multi-sample sounds (ie FSB/DLS/SF2) when created as a stream. +
  • To specify which subsounds to load for multi-sample sounds (ie FSB/DLS/SF2) so that memory is saved and only a subset is actually loaded/read from disk. +
  • To specify 'piggyback' read and seek callbacks for capture of sound data as fmod reads and decodes it. Useful for ripping decoded PCM data from sounds as they are loaded / played. +
  • To specify a MIDI DLS/SF2 sample set file to load when opening a MIDI file. + See below on what members to fill for each of the above types of sound you want to create. + + [REMARKS] + This structure is optional! Specify 0 or NULL in System::createSound if you don't need it!
    +
    + Loading a file from memory.
    +
  • Create the sound using the FMOD_OPENMEMORY flag.
    +
  • Mandatory. Specify 'length' for the size of the memory block in bytes. +
  • Other flags are optional. +
    +
    + Loading a file from within another larger (possibly wad/pak) file, by giving the loader an offset and length.
    +
  • Mandatory. Specify 'fileoffset' and 'length'. +
  • Other flags are optional. +
    +
    + To create a user created / non file based sound.
    +
  • Create the sound using the FMOD_OPENUSER flag. +
  • Mandatory. Specify 'defaultfrequency, 'numchannels' and 'format'. +
  • Other flags are optional. +
    +
    + To specify a starting subsound to seek to and flush with, within a multi-sample stream (ie FSB/DLS/SF2).
    +
    +
  • Mandatory. Specify 'initialsubsound'. +
    +
    + To specify which subsounds to load for multi-sample sounds (ie FSB/DLS/SF2) so that memory is saved and only a subset is actually loaded/read from disk.
    +
    +
  • Mandatory. Specify 'inclusionlist' and 'inclusionlistnum'. +
    +
    + To specify 'piggyback' read and seek callbacks for capture of sound data as fmod reads and decodes it. Useful for ripping decoded PCM data from sounds as they are loaded / played.
    +
    +
  • Mandatory. Specify 'pcmreadcallback' and 'pcmseekcallback'. +
    +
    + To specify a MIDI DLS/SF2 sample set file to load when opening a MIDI file.
    +
    +
  • Mandatory. Specify 'dlsname'. +
    +
    + Setting the 'decodebuffersize' is for cpu intensive codecs that may be causing stuttering, not file intensive codecs (ie those from CD or netstreams) which are normally + altered with System::setStreamBufferSize. As an example of cpu intensive codecs, an mp3 file will take more cpu to decode than a PCM wav file.
    + If you have a stuttering effect, then it is using more cpu than the decode buffer playback rate can keep up with. Increasing the decode buffersize will most likely solve this problem.
    +
    +
    + FSB codec. If inclusionlist and numsubsounds are used together, this will trigger a special mode where subsounds are shuffled down to save memory. (useful for large FSB + files where you only want to load 1 sound). There will be no gaps, ie no null subsounds. As an example, if there are 10,000 subsounds and there is an inclusionlist with only 1 entry, + and numsubsounds = 1, then subsound 0 will be that entry, and there will only be the memory allocated for 1 subsound. Previously there would still be 10,000 subsound pointers and other + associated codec entries allocated along with it multiplied by 10,000.
    +
    + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::createSound + System::setStreamBufferSize + FMOD_MODE + FMOD_SOUND_FORMAT + FMOD_SOUND_TYPE + FMOD_SPEAKERMAPTYPE +] +*/ +typedef struct FMOD_CREATESOUNDEXINFO +{ + int cbsize; /* [w] Size of this structure. This is used so the structure can be expanded in the future and still work on older versions of FMOD Ex. */ + unsigned int length; /* [w] Optional. Specify 0 to ignore. Size in bytes of file to load, or sound to create (in this case only if FMOD_OPENUSER is used). Required if loading from memory. If 0 is specified, then it will use the size of the file (unless loading from memory then an error will be returned). */ + unsigned int fileoffset; /* [w] Optional. Specify 0 to ignore. Offset from start of the file to start loading from. This is useful for loading files from inside big data files. */ + int numchannels; /* [w] Optional. Specify 0 to ignore. Number of channels in a sound mandatory if FMOD_OPENUSER or FMOD_OPENRAW is used. */ + int defaultfrequency; /* [w] Optional. Specify 0 to ignore. Default frequency of sound in a sound mandatory if FMOD_OPENUSER or FMOD_OPENRAW is used. Other formats use the frequency determined by the file format. */ + FMOD_SOUND_FORMAT format; /* [w] Optional. Specify 0 or FMOD_SOUND_FORMAT_NONE to ignore. Format of the sound mandatory if FMOD_OPENUSER or FMOD_OPENRAW is used. Other formats use the format determined by the file format. */ + unsigned int decodebuffersize; /* [w] Optional. Specify 0 to ignore. For streams. This determines the size of the double buffer (in PCM samples) that a stream uses. Use this for user created streams if you want to determine the size of the callback buffer passed to you. Specify 0 to use FMOD's default size which is currently equivalent to 400ms of the sound format created/loaded. */ + int initialsubsound; /* [w] Optional. Specify 0 to ignore. In a multi-sample file format such as .FSB/.DLS/.SF2, specify the initial subsound to seek to, only if FMOD_CREATESTREAM is used. */ + int numsubsounds; /* [w] Optional. Specify 0 to ignore or have no subsounds. In a sound created with FMOD_OPENUSER, specify the number of subsounds that are accessable with Sound::getSubSound. If not created with FMOD_OPENUSER, this will limit the number of subsounds loaded within a multi-subsound file. If using FSB, then if FMOD_CREATESOUNDEXINFO::inclusionlist is used, this will shuffle subsounds down so that there are not any gaps. It will mean that the indices of the sounds will be different. */ + int *inclusionlist; /* [w] Optional. Specify 0 to ignore. In a multi-sample format such as .FSB/.DLS/.SF2 it may be desirable to specify only a subset of sounds to be loaded out of the whole file. This is an array of subsound indices to load into memory when created. */ + int inclusionlistnum; /* [w] Optional. Specify 0 to ignore. This is the number of integers contained within the inclusionlist array. */ + FMOD_SOUND_PCMREADCALLBACK pcmreadcallback; /* [w] Optional. Specify 0 to ignore. Callback to 'piggyback' on FMOD's read functions and accept or even write PCM data while FMOD is opening the sound. Used for user sounds created with FMOD_OPENUSER or for capturing decoded data as FMOD reads it. */ + FMOD_SOUND_PCMSETPOSCALLBACK pcmsetposcallback; /* [w] Optional. Specify 0 to ignore. Callback for when the user calls a seeking function such as Channel::setTime or Channel::setPosition within a multi-sample sound, and for when it is opened.*/ + FMOD_SOUND_NONBLOCKCALLBACK nonblockcallback; /* [w] Optional. Specify 0 to ignore. Callback for successful completion, or error while loading a sound that used the FMOD_NONBLOCKING flag. Also called duing seeking, when setPosition is called or a stream is restarted. */ + const char *dlsname; /* [w] Optional. Specify 0 to ignore. Filename for a DLS or SF2 sample set when loading a MIDI file. If not specified, on Windows it will attempt to open /windows/system32/drivers/gm.dls or /windows/system32/drivers/etc/gm.dls, on Mac it will attempt to load /System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls, otherwise the MIDI will fail to open. Current DLS support is for level 1 of the specification. */ + const char *encryptionkey; /* [w] Optional. Specify 0 to ignore. Key for encrypted FSB file. Without this key an encrypted FSB file will not load. */ + int maxpolyphony; /* [w] Optional. Specify 0 to ignore. For sequenced formats with dynamic channel allocation such as .MID and .IT, this specifies the maximum voice count allowed while playing. .IT defaults to 64. .MID defaults to 32. */ + void *userdata; /* [w] Optional. Specify 0 to ignore. This is user data to be attached to the sound during creation. Access via Sound::getUserData. Note: This is not passed to FMOD_FILE_OPENCALLBACK, that is a different userdata that is file specific. */ + FMOD_SOUND_TYPE suggestedsoundtype; /* [w] Optional. Specify 0 or FMOD_SOUND_TYPE_UNKNOWN to ignore. Instead of scanning all codec types, use this to speed up loading by making it jump straight to this codec. */ + FMOD_FILE_OPENCALLBACK useropen; /* [w] Optional. Specify 0 to ignore. Callback for opening this file. */ + FMOD_FILE_CLOSECALLBACK userclose; /* [w] Optional. Specify 0 to ignore. Callback for closing this file. */ + FMOD_FILE_READCALLBACK userread; /* [w] Optional. Specify 0 to ignore. Callback for reading from this file. */ + FMOD_FILE_SEEKCALLBACK userseek; /* [w] Optional. Specify 0 to ignore. Callback for seeking within this file. */ + FMOD_FILE_ASYNCREADCALLBACK userasyncread; /* [w] Optional. Specify 0 to ignore. Callback for seeking within this file. */ + FMOD_FILE_ASYNCCANCELCALLBACK userasynccancel; /* [w] Optional. Specify 0 to ignore. Callback for seeking within this file. */ + FMOD_SPEAKERMAPTYPE speakermap; /* [w] Optional. Specify 0 to ignore. Use this to differ the way fmod maps multichannel sounds to speakers. See FMOD_SPEAKERMAPTYPE for more. */ + FMOD_SOUNDGROUP *initialsoundgroup; /* [w] Optional. Specify 0 to ignore. Specify a sound group if required, to put sound in as it is created. */ + unsigned int initialseekposition;/* [w] Optional. Specify 0 to ignore. For streams. Specify an initial position to seek the stream to. */ + FMOD_TIMEUNIT initialseekpostype; /* [w] Optional. Specify 0 to ignore. For streams. Specify the time unit for the position set in initialseekposition. */ + int ignoresetfilesystem;/* [w] Optional. Specify 0 to ignore. Set to 1 to use fmod's built in file system. Ignores setFileSystem callbacks and also FMOD_CREATESOUNEXINFO file callbacks. Useful for specific cases where you don't want to use your own file system but want to use fmod's file system (ie net streaming). */ + int cddaforceaspi; /* [w] Optional. Specify 0 to ignore. For CDDA sounds only - if non-zero use ASPI instead of NTSCSI to access the specified CD/DVD device. */ + unsigned int audioqueuepolicy; /* [w] Optional. Specify 0 or FMOD_AUDIOQUEUE_CODECPOLICY_DEFAULT to ignore. Policy used to determine whether hardware or software is used for decoding, see FMOD_AUDIOQUEUE_CODECPOLICY for options (iOS >= 3.0 required, otherwise only hardware is available) */ + unsigned int minmidigranularity; /* [w] Optional. Specify 0 to ignore. Allows you to set a minimum desired MIDI mixer granularity. Values smaller than 512 give greater than default accuracy at the cost of more CPU and vice versa. Specify 0 for default (512 samples). */ + int nonblockthreadid; /* [w] Optional. Specify 0 to ignore. Specifies a thread index to execute non blocking load on. Allows for up to 5 threads to be used for loading at once. This is to avoid one load blocking another. Maximum value = 4. */ +} FMOD_CREATESOUNDEXINFO; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure defining a reverb environment.
    + + [REMARKS] + Note the default reverb properties are the same as the FMOD_PRESET_GENERIC preset.
    + Note that integer values that typically range from -10,000 to 1000 are represented in + decibels, and are of a logarithmic scale, not linear, wheras float values are always linear.
    +
    + The numerical values listed below are the maximum, minimum and default values for each variable respectively.
    +
    + SUPPORTED next to each parameter means the platform the parameter can be set on. Some platforms support all parameters and some don't.
    + WII means Nintendo Wii hardware reverb (must use FMOD_HARDWARE).
    + PSP means Playstation Portable hardware reverb (must use FMOD_HARDWARE).
    + SFX means FMOD SFX software reverb. This works on any platform that uses FMOD_SOFTWARE for loading sounds.
    + --- means unsupported/deprecated. Will either be removed or supported by SFX in the future. +
    + Nintendo Wii Notes:
    + This structure supports only limited parameters, and maps them to the Wii hardware reverb as follows.
    + DecayTime = 'time'
    + ReverbDelay = 'predelay'
    + ModulationDepth = 'damping'
    + Reflections = 'coloration'
    + EnvDiffusion = 'crosstalk'
    + Room = 'mix'
    +
    + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + Members marked with [r/w] are either read or write depending on if you are using System::setReverbProperties (w) or System::getReverbProperties (r). + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::setReverbProperties + System::getReverbProperties + FMOD_REVERB_PRESETS + FMOD_REVERB_FLAGS +] +*/ +typedef struct FMOD_REVERB_PROPERTIES +{ /* MIN MAX DEFAULT DESCRIPTION */ + int Instance; /* [w] 0 3 0 Environment Instance. (SUPPORTED:SFX(4 instances) and Wii (3 instances)) */ + int Environment; /* [r/w] -1 25 -1 Sets all listener properties. -1 = OFF. (SUPPORTED:SFX(-1 only)/PSP) */ + float EnvDiffusion; /* [r/w] 0.0 1.0 1.0 Environment diffusion (SUPPORTED:WII) */ + int Room; /* [r/w] -10000 0 -1000 Room effect level (at mid frequencies) (SUPPORTED:SFX/WII/PSP) */ + int RoomHF; /* [r/w] -10000 0 -100 Relative room effect level at high frequencies (SUPPORTED:SFX) */ + int RoomLF; /* [r/w] -10000 0 0 Relative room effect level at low frequencies (SUPPORTED:SFX) */ + float DecayTime; /* [r/w] 0.1 20.0 1.49 Reverberation decay time at mid frequencies (SUPPORTED:SFX/WII) */ + float DecayHFRatio; /* [r/w] 0.1 2.0 0.83 High-frequency to mid-frequency decay time ratio (SUPPORTED:SFX) */ + float DecayLFRatio; /* [r/w] 0.1 2.0 1.0 Low-frequency to mid-frequency decay time ratio (SUPPORTED:---) */ + int Reflections; /* [r/w] -10000 1000 -2602 Early reflections level relative to room effect (SUPPORTED:SFX/WII) */ + float ReflectionsDelay; /* [r/w] 0.0 0.3 0.007 Initial reflection delay time (SUPPORTED:SFX) */ + int Reverb; /* [r/w] -10000 2000 200 Late reverberation level relative to room effect (SUPPORTED:SFX) */ + float ReverbDelay; /* [r/w] 0.0 0.1 0.011 Late reverberation delay time relative to initial reflection (SUPPORTED:SFX/WII) */ + float ModulationTime; /* [r/w] 0.04 4.0 0.25 Modulation time (SUPPORTED:---) */ + float ModulationDepth; /* [r/w] 0.0 1.0 0.0 Modulation depth (SUPPORTED:WII) */ + float HFReference; /* [r/w] 20.0 20000.0 5000.0 Reference high frequency (hz) (SUPPORTED:SFX) */ + float LFReference; /* [r/w] 20.0 1000.0 250.0 Reference low frequency (hz) (SUPPORTED:SFX) */ + float Diffusion; /* [r/w] 0.0 100.0 100.0 Value that controls the echo density in the late reverberation decay. (SUPPORTED:SFX) */ + float Density; /* [r/w] 0.0 100.0 100.0 Value that controls the modal density in the late reverberation decay (SUPPORTED:SFX) */ + unsigned int Flags; /* [r/w] FMOD_REVERB_FLAGS - modifies the behavior of above properties (SUPPORTED:WII) */ +} FMOD_REVERB_PROPERTIES; + + +/* +[DEFINE] +[ + [NAME] + FMOD_REVERB_FLAGS + + [DESCRIPTION] + Values for the Flags member of the FMOD_REVERB_PROPERTIES structure. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_REVERB_PROPERTIES +] +*/ +#define FMOD_REVERB_FLAGS_HIGHQUALITYREVERB 0x00000400 /* Wii. Use high quality reverb */ +#define FMOD_REVERB_FLAGS_HIGHQUALITYDPL2REVERB 0x00000800 /* Wii. Use high quality DPL2 reverb */ +#define FMOD_REVERB_FLAGS_HARDWAREONLY 0x00001000 /* Don't create an SFX reverb for FMOD_SOFTWARE channels, hardware reverb only */ +#define FMOD_REVERB_FLAGS_DEFAULT 0x00000000 +/* [DEFINE_END] */ + + +/* +[DEFINE] +[ + [NAME] + FMOD_REVERB_PRESETS + + [DESCRIPTION] + A set of predefined environment PARAMETERS.
    + These are used to initialize an FMOD_REVERB_PROPERTIES structure statically.
    + i.e.
    + FMOD_REVERB_PROPERTIES prop = FMOD_PRESET_GENERIC; + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::setReverbProperties +] +*/ +/* Inst Env Diffus Room RoomHF RmLF DecTm DecHF DecLF Refl RefDel Revb RevDel ModTm ModDp HFRef LFRef Diffus Densty FLAGS */ +#define FMOD_PRESET_OFF { 0, -1, 1.00f, -10000, -10000, 0, 1.00f, 1.00f, 1.0f, -2602, 0.007f, 200, 0.011f, 0.25f, 0.000f, 5000.0f, 250.0f, 0.0f, 0.0f, 0x33f } +#define FMOD_PRESET_GENERIC { 0, 0, 1.00f, -1000, -100, 0, 1.49f, 0.83f, 1.0f, -2602, 0.007f, 200, 0.011f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_PADDEDCELL { 0, 1, 1.00f, -1000, -6000, 0, 0.17f, 0.10f, 1.0f, -1204, 0.001f, 207, 0.002f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_ROOM { 0, 2, 1.00f, -1000, -454, 0, 0.40f, 0.83f, 1.0f, -1646, 0.002f, 53, 0.003f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_BATHROOM { 0, 3, 1.00f, -1000, -1200, 0, 1.49f, 0.54f, 1.0f, -370, 0.007f, 1030, 0.011f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 60.0f, 0x3f } +#define FMOD_PRESET_LIVINGROOM { 0, 4, 1.00f, -1000, -6000, 0, 0.50f, 0.10f, 1.0f, -1376, 0.003f, -1104, 0.004f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_STONEROOM { 0, 5, 1.00f, -1000, -300, 0, 2.31f, 0.64f, 1.0f, -711, 0.012f, 83, 0.017f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_AUDITORIUM { 0, 6, 1.00f, -1000, -476, 0, 4.32f, 0.59f, 1.0f, -789, 0.020f, -289, 0.030f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_CONCERTHALL { 0, 7, 1.00f, -1000, -500, 0, 3.92f, 0.70f, 1.0f, -1230, 0.020f, -2, 0.029f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_CAVE { 0, 8, 1.00f, -1000, 0, 0, 2.91f, 1.30f, 1.0f, -602, 0.015f, -302, 0.022f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x1f } +#define FMOD_PRESET_ARENA { 0, 9, 1.00f, -1000, -698, 0, 7.24f, 0.33f, 1.0f, -1166, 0.020f, 16, 0.030f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_HANGAR { 0, 10, 1.00f, -1000, -1000, 0, 10.05f, 0.23f, 1.0f, -602, 0.020f, 198, 0.030f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_CARPETTEDHALLWAY { 0, 11, 1.00f, -1000, -4000, 0, 0.30f, 0.10f, 1.0f, -1831, 0.002f, -1630, 0.030f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_HALLWAY { 0, 12, 1.00f, -1000, -300, 0, 1.49f, 0.59f, 1.0f, -1219, 0.007f, 441, 0.011f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_STONECORRIDOR { 0, 13, 1.00f, -1000, -237, 0, 2.70f, 0.79f, 1.0f, -1214, 0.013f, 395, 0.020f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_ALLEY { 0, 14, 0.30f, -1000, -270, 0, 1.49f, 0.86f, 1.0f, -1204, 0.007f, -4, 0.011f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_FOREST { 0, 15, 0.30f, -1000, -3300, 0, 1.49f, 0.54f, 1.0f, -2560, 0.162f, -229, 0.088f, 0.25f, 0.000f, 5000.0f, 250.0f, 79.0f, 100.0f, 0x3f } +#define FMOD_PRESET_CITY { 0, 16, 0.50f, -1000, -800, 0, 1.49f, 0.67f, 1.0f, -2273, 0.007f, -1691, 0.011f, 0.25f, 0.000f, 5000.0f, 250.0f, 50.0f, 100.0f, 0x3f } +#define FMOD_PRESET_MOUNTAINS { 0, 17, 0.27f, -1000, -2500, 0, 1.49f, 0.21f, 1.0f, -2780, 0.300f, -1434, 0.100f, 0.25f, 0.000f, 5000.0f, 250.0f, 27.0f, 100.0f, 0x1f } +#define FMOD_PRESET_QUARRY { 0, 18, 1.00f, -1000, -1000, 0, 1.49f, 0.83f, 1.0f, -10000, 0.061f, 500, 0.025f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } +#define FMOD_PRESET_PLAIN { 0, 19, 0.21f, -1000, -2000, 0, 1.49f, 0.50f, 1.0f, -2466, 0.179f, -1926, 0.100f, 0.25f, 0.000f, 5000.0f, 250.0f, 21.0f, 100.0f, 0x3f } +#define FMOD_PRESET_PARKINGLOT { 0, 20, 1.00f, -1000, 0, 0, 1.65f, 1.50f, 1.0f, -1363, 0.008f, -1153, 0.012f, 0.25f, 0.000f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x1f } +#define FMOD_PRESET_SEWERPIPE { 0, 21, 0.80f, -1000, -1000, 0, 2.81f, 0.14f, 1.0f, 429, 0.014f, 1023, 0.021f, 0.25f, 0.000f, 5000.0f, 250.0f, 80.0f, 60.0f, 0x3f } +#define FMOD_PRESET_UNDERWATER { 0, 22, 1.00f, -1000, -4000, 0, 1.49f, 0.10f, 1.0f, -449, 0.007f, 1700, 0.011f, 1.18f, 0.348f, 5000.0f, 250.0f, 100.0f, 100.0f, 0x3f } + +/* PlayStation Portable Only presets */ +#define FMOD_PRESET_PSP_ROOM { 0, 1, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0, 0.000f, 0, 0.000f, 0.00f, 0.000f, 0000.0f, 0.0f, 0.0f, 0.0f, 0x31f } +#define FMOD_PRESET_PSP_STUDIO_A { 0, 2, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0, 0.000f, 0, 0.000f, 0.00f, 0.000f, 0000.0f, 0.0f, 0.0f, 0.0f, 0x31f } +#define FMOD_PRESET_PSP_STUDIO_B { 0, 3, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0, 0.000f, 0, 0.000f, 0.00f, 0.000f, 0000.0f, 0.0f, 0.0f, 0.0f, 0x31f } +#define FMOD_PRESET_PSP_STUDIO_C { 0, 4, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0, 0.000f, 0, 0.000f, 0.00f, 0.000f, 0000.0f, 0.0f, 0.0f, 0.0f, 0x31f } +#define FMOD_PRESET_PSP_HALL { 0, 5, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0, 0.000f, 0, 0.000f, 0.00f, 0.000f, 0000.0f, 0.0f, 0.0f, 0.0f, 0x31f } +#define FMOD_PRESET_PSP_SPACE { 0, 6, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0, 0.000f, 0, 0.000f, 0.00f, 0.000f, 0000.0f, 0.0f, 0.0f, 0.0f, 0x31f } +#define FMOD_PRESET_PSP_ECHO { 0, 7, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0, 0.000f, 0, 0.000f, 0.00f, 0.000f, 0000.0f, 0.0f, 0.0f, 0.0f, 0x31f } +#define FMOD_PRESET_PSP_DELAY { 0, 8, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0, 0.000f, 0, 0.000f, 0.00f, 0.000f, 0000.0f, 0.0f, 0.0f, 0.0f, 0x31f } +#define FMOD_PRESET_PSP_PIPE { 0, 9, 0, 0, 0, 0, 0.0f, 0.0f, 0.0f, 0, 0.000f, 0, 0.000f, 0.00f, 0.000f, 0000.0f, 0.0f, 0.0f, 0.0f, 0x31f } +/* [DEFINE_END] */ + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure defining the properties for a reverb source, related to a FMOD channel.
    +
    + Note the default reverb properties are the same as the FMOD_PRESET_GENERIC preset.
    + Note that integer values that typically range from -10,000 to 1000 are represented in + decibels, and are of a logarithmic scale, not linear, wheras float values are typically linear.
    + PORTABILITY: Each member has the platform it supports in braces ie (win32/wii).
    +
    + The numerical values listed below are the maximum, minimum and default values for each variable respectively.
    + + [REMARKS] + SUPPORTED next to each parameter means the platform the parameter can be set on. Some platforms support all parameters and some don't.
    + WII means Nintendo Wii hardware reverb (must use FMOD_HARDWARE).
    + PSP means Playstation Portable hardware reverb (must use FMOD_HARDWARE).
    + SFX means FMOD SFX software reverb. This works on any platform that uses FMOD_SOFTWARE for loading sounds.
    + --- means unsupported/deprecated. Will either be removed or supported by SFX in the future. +
    +
    + 'ConnectionPoint' Parameter. This parameter is for the FMOD software reverb only (known as SFX in the list above).
    + By default the dsp network connection for a channel and its reverb is between the 'SFX Reverb' unit, and the channel's wavetable/resampler/dspcodec/oscillator unit (the unit below the channel DSP head). NULL can be used for this parameter to make it use this default behaviour.
    + This parameter allows the user to connect the SFX reverb to somewhere else internally, for example the channel DSP head, or a related channelgroup. The event system uses this so that it can have the output of an event going to the reverb, instead of just the output of the event's channels (thereby ignoring event effects/submixes etc).
    + Do not use if you are unaware of DSP network connection issues. Leave it at the default of NULL instead.
    +
    + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + Members marked with [r/w] are either read or write depending on if you are using Channel::setReverbProperties (w) or Channel::getReverbProperties (r). + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + Channel::setReverbProperties + Channel::getReverbProperties + FMOD_REVERB_CHANNELFLAGS +] +*/ +typedef struct FMOD_REVERB_CHANNELPROPERTIES +{ /* MIN MAX DEFAULT DESCRIPTION */ + int Direct; /* [r/w] -10000 1000 0 Direct path level (SUPPORTED:SFX) */ + int Room; /* [r/w] -10000 1000 0 Room effect level (SUPPORTED:SFX) */ + unsigned int Flags; /* [r/w] FMOD_REVERB_CHANNELFLAGS - modifies the behavior of properties (SUPPORTED:SFX) */ + FMOD_DSP *ConnectionPoint; /* [r/w] See remarks. DSP network location to connect reverb for this channel. (SUPPORTED:SFX).*/ +} FMOD_REVERB_CHANNELPROPERTIES; + + +/* +[DEFINE] +[ + [NAME] + FMOD_REVERB_CHANNELFLAGS + + [DESCRIPTION] + Values for the Flags member of the FMOD_REVERB_CHANNELPROPERTIES structure. + + [REMARKS] + For SFX Reverb, there is support for multiple reverb environments.
    + Use FMOD_REVERB_CHANNELFLAGS_ENVIRONMENT0 to FMOD_REVERB_CHANNELFLAGS_ENVIRONMENT3 in the flags member + of FMOD_REVERB_CHANNELPROPERTIES to specify which environment instance(s) to target.
    + - If you do not specify any instance the first reverb instance will be used.
    + - If you specify more than one instance with getReverbProperties, the first instance will be used.
    + - If you specify more than one instance with setReverbProperties, it will set more than 1 instance at once.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_REVERB_CHANNELPROPERTIES +] +*/ +#define FMOD_REVERB_CHANNELFLAGS_INSTANCE0 0x00000010 /* SFX/Wii. Specify channel to target reverb instance 0. Default target. */ +#define FMOD_REVERB_CHANNELFLAGS_INSTANCE1 0x00000020 /* SFX/Wii. Specify channel to target reverb instance 1. */ +#define FMOD_REVERB_CHANNELFLAGS_INSTANCE2 0x00000040 /* SFX/Wii. Specify channel to target reverb instance 2. */ +#define FMOD_REVERB_CHANNELFLAGS_INSTANCE3 0x00000080 /* SFX. Specify channel to target reverb instance 3. */ + +#define FMOD_REVERB_CHANNELFLAGS_DEFAULT FMOD_REVERB_CHANNELFLAGS_INSTANCE0 +/* [DEFINE_END] */ + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Settings for advanced features like configuring memory and cpu usage for the FMOD_CREATECOMPRESSEDSAMPLE feature. + + [REMARKS] + maxMPEGcodecs / maxADPCMcodecs / maxXMAcodecs will determine the maximum cpu usage of playing realtime samples. Use this to lower potential excess cpu usage and also control memory usage.
    +
    + maxPCMcodecs is for use with PS3 only. It will determine the maximum number of PCM voices that can be played at once. This includes streams of any format and all sounds created + *without* the FMOD_CREATECOMPRESSEDSAMPLE flag. +
    + Memory will be allocated for codecs 'up front' (during System::init) if these values are specified as non zero. If any are zero, it allocates memory for the codec whenever a file of the type in question is loaded. So if maxMPEGcodecs is 0 for example, it will allocate memory for the mpeg codecs the first time an mp3 is loaded or an mp3 based .FSB file is loaded.
    +
    + Due to inefficient encoding techniques on certain .wav based ADPCM files, FMOD can can need an extra 29720 bytes per codec. This means for lowest memory consumption. Use FSB as it uses an optimal/small ADPCM block size.
    +
    + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + Members marked with [r/w] are either read or write depending on if you are using System::setAdvancedSettings (w) or System::getAdvancedSettings (r). + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::setAdvancedSettings + System::getAdvancedSettings + System::init + FMOD_MODE +] +*/ +typedef struct FMOD_ADVANCEDSETTINGS +{ + int cbsize; /* [w] Size of this structure. Use sizeof(FMOD_ADVANCEDSETTINGS) NOTE: This must be set before calling System::getAdvancedSettings! */ + int maxMPEGcodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. Mpeg codecs consume 21,684 bytes per instance and this number will determine how many mpeg channels can be played simultaneously. Default = 32. */ + int maxADPCMcodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. ADPCM codecs consume 2,136 bytes per instance and this number will determine how many ADPCM channels can be played simultaneously. Default = 32. */ + int maxXMAcodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. XMA codecs consume 14,836 bytes per instance and this number will determine how many XMA channels can be played simultaneously. Default = 32. */ + int maxCELTcodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. CELT codecs consume 11,500 bytes per instance and this number will determine how many CELT channels can be played simultaneously. Default = 32. */ + int maxVORBIScodecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. Vorbis codecs consume 12,000 bytes per instance and this number will determine how many Vorbis channels can be played simultaneously. Default = 32. */ + int maxAT9Codecs; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_CREATECOMPRESSEDSAMPLE only. AT9 codecs consume 8,720 bytes per instance and this number will determine how many AT9 channels can be played simultaneously. Default = 32. */ + int maxPCMcodecs; /* [r/w] Optional. Specify 0 to ignore. For use with PS3 only. PCM codecs consume 12,672 bytes per instance and this number will determine how many streams and PCM voices can be played simultaneously. Default = 16. */ + int ASIONumChannels; /* [r/w] Optional. Specify 0 to ignore. Number of channels available on the ASIO device. */ + char **ASIOChannelList; /* [r/w] Optional. Specify 0 to ignore. Pointer to an array of strings (number of entries defined by ASIONumChannels) with ASIO channel names. */ + FMOD_SPEAKER *ASIOSpeakerList; /* [r/w] Optional. Specify 0 to ignore. Pointer to a list of speakers that the ASIO channels map to. This can be called after System::init to remap ASIO output. */ + int max3DReverbDSPs; /* [r/w] Optional. Specify 0 to ignore. The max number of 3d reverb DSP's in the system. (NOTE: CURRENTLY DISABLED / UNUSED) */ + float HRTFMinAngle; /* [r/w] Optional. For use with FMOD_INIT_HRTF_LOWPASS. The angle range (0-360) of a 3D sound in relation to the listener, at which the HRTF function begins to have an effect. 0 = in front of the listener. 180 = from 90 degrees to the left of the listener to 90 degrees to the right. 360 = behind the listener. Default = 180.0. */ + float HRTFMaxAngle; /* [r/w] Optional. For use with FMOD_INIT_HRTF_LOWPASS. The angle range (0-360) of a 3D sound in relation to the listener, at which the HRTF function has maximum effect. 0 = front of the listener. 180 = from 90 degrees to the left of the listener to 90 degrees to the right. 360 = behind the listener. Default = 360.0. */ + float HRTFFreq; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_INIT_HRTF_LOWPASS. The cutoff frequency of the HRTF's lowpass filter function when at maximum effect. (i.e. at HRTFMaxAngle). Default = 4000.0. */ + float vol0virtualvol; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_INIT_VOL0_BECOMES_VIRTUAL. If this flag is used, and the volume is 0.0, then the sound will become virtual. Use this value to raise the threshold to a different point where a sound goes virtual. */ + int eventqueuesize; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD Event system only. Specifies the number of slots available for simultaneous non blocking loads, across all threads. Default = 32. */ + unsigned int defaultDecodeBufferSize; /* [r/w] Optional. Specify 0 to ignore. For streams. This determines the default size of the double buffer (in milliseconds) that a stream uses. Default = 400ms */ + char *debugLogFilename; /* [r/w] Optional. Specify 0 to ignore. Gives fmod's logging system a path/filename. Normally the log is placed in the same directory as the executable and called fmod.log. When using System::getAdvancedSettings, provide at least 256 bytes of memory to copy into. */ + unsigned short profileport; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_INIT_ENABLE_PROFILE. Specify the port to listen on for connections by the profiler application. */ + unsigned int geometryMaxFadeTime; /* [r/w] Optional. Specify 0 to ignore. The maximum time in miliseconds it takes for a channel to fade to the new level when its occlusion changes. */ + unsigned int maxSpectrumWaveDataBuffers; /* [r/w] Optional. Specify 0 to ignore. Tells System::init to allocate a pool of wavedata/spectrum buffers to prevent memory fragmentation, any additional buffers will be allocated normally. */ + unsigned int musicSystemCacheDelay; /* [r/w] Optional. Specify 0 to ignore. The delay the music system should allow for loading a sample from disk (in milliseconds). Default = 400 ms. */ + float distanceFilterCenterFreq; /* [r/w] Optional. Specify 0 to ignore. For use with FMOD_INIT_DISTANCE_FILTERING. The default center frequency in Hz for the distance filtering effect. Default = 1500.0. */ + unsigned int stackSizeStream; /* [r/w] Optional. Specify 0 to ignore. Specify the stack size for the FMOD Stream thread in bytes. Useful for custom codecs that use excess stack. Default 49,152 (48kb) */ + unsigned int stackSizeNonBlocking; /* [r/w] Optional. Specify 0 to ignore. Specify the stack size for the FMOD_NONBLOCKING loading thread. Useful for custom codecs that use excess stack. Default 65,536 (64kb) */ + unsigned int stackSizeMixer; /* [r/w] Optional. Specify 0 to ignore. Specify the stack size for the FMOD mixer thread. Useful for custom dsps that use excess stack. Default 49,152 (48kb) */ +} FMOD_ADVANCEDSETTINGS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Special channel index values for FMOD functions. + + [REMARKS] + To get 'all' of the channels, use System::getMasterChannelGroup. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::playSound + System::playDSP + System::getChannel + System::getMasterChannelGroup +] +*/ +typedef enum +{ + FMOD_CHANNEL_FREE = -1, /* For a channel index, FMOD chooses a free voice using the priority system. */ + FMOD_CHANNEL_REUSE = -2 /* For a channel index, re-use the channel handle that was passed in. */ +} FMOD_CHANNELINDEX; + +#include "fmod_codec.h" +#include "fmod_dsp.h" +#include "fmod_memoryinfo.h" + +/* ========================================================================================== */ +/* FUNCTION PROTOTYPES */ +/* ========================================================================================== */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* + FMOD global system functions (optional). +*/ + +FMOD_RESULT F_API FMOD_Memory_Initialize (void *poolmem, int poollen, FMOD_MEMORY_ALLOCCALLBACK useralloc, FMOD_MEMORY_REALLOCCALLBACK userrealloc, FMOD_MEMORY_FREECALLBACK userfree, FMOD_MEMORY_TYPE memtypeflags); +FMOD_RESULT F_API FMOD_Memory_GetStats (int *currentalloced, int *maxalloced, FMOD_BOOL blocking); +FMOD_RESULT F_API FMOD_Debug_SetLevel (FMOD_DEBUGLEVEL level); +FMOD_RESULT F_API FMOD_Debug_GetLevel (FMOD_DEBUGLEVEL *level); +FMOD_RESULT F_API FMOD_File_SetDiskBusy (int busy); +FMOD_RESULT F_API FMOD_File_GetDiskBusy (int *busy); + +/* + FMOD System factory functions. Use this to create an FMOD System Instance. below you will see FMOD_System_Init/Close to get started. +*/ + +FMOD_RESULT F_API FMOD_System_Create (FMOD_SYSTEM **system); +FMOD_RESULT F_API FMOD_System_Release (FMOD_SYSTEM *system); + +/*$ preserve end $*/ + +/* + 'System' API +*/ + +/* + Pre-init functions. +*/ + +FMOD_RESULT F_API FMOD_System_SetOutput (FMOD_SYSTEM *system, FMOD_OUTPUTTYPE output); +FMOD_RESULT F_API FMOD_System_GetOutput (FMOD_SYSTEM *system, FMOD_OUTPUTTYPE *output); +FMOD_RESULT F_API FMOD_System_GetNumDrivers (FMOD_SYSTEM *system, int *numdrivers); +FMOD_RESULT F_API FMOD_System_GetDriverInfo (FMOD_SYSTEM *system, int id, char *name, int namelen, FMOD_GUID *guid); +FMOD_RESULT F_API FMOD_System_GetDriverInfoW (FMOD_SYSTEM *system, int id, short *name, int namelen, FMOD_GUID *guid); +FMOD_RESULT F_API FMOD_System_GetDriverCaps (FMOD_SYSTEM *system, int id, FMOD_CAPS *caps, int *controlpaneloutputrate, FMOD_SPEAKERMODE *controlpanelspeakermode); +FMOD_RESULT F_API FMOD_System_SetDriver (FMOD_SYSTEM *system, int driver); +FMOD_RESULT F_API FMOD_System_GetDriver (FMOD_SYSTEM *system, int *driver); +FMOD_RESULT F_API FMOD_System_SetHardwareChannels (FMOD_SYSTEM *system, int numhardwarechannels); +FMOD_RESULT F_API FMOD_System_SetSoftwareChannels (FMOD_SYSTEM *system, int numsoftwarechannels); +FMOD_RESULT F_API FMOD_System_GetSoftwareChannels (FMOD_SYSTEM *system, int *numsoftwarechannels); +FMOD_RESULT F_API FMOD_System_SetSoftwareFormat (FMOD_SYSTEM *system, int samplerate, FMOD_SOUND_FORMAT format, int numoutputchannels, int maxinputchannels, FMOD_DSP_RESAMPLER resamplemethod); +FMOD_RESULT F_API FMOD_System_GetSoftwareFormat (FMOD_SYSTEM *system, int *samplerate, FMOD_SOUND_FORMAT *format, int *numoutputchannels, int *maxinputchannels, FMOD_DSP_RESAMPLER *resamplemethod, int *bits); +FMOD_RESULT F_API FMOD_System_SetDSPBufferSize (FMOD_SYSTEM *system, unsigned int bufferlength, int numbuffers); +FMOD_RESULT F_API FMOD_System_GetDSPBufferSize (FMOD_SYSTEM *system, unsigned int *bufferlength, int *numbuffers); +FMOD_RESULT F_API FMOD_System_SetFileSystem (FMOD_SYSTEM *system, FMOD_FILE_OPENCALLBACK useropen, FMOD_FILE_CLOSECALLBACK userclose, FMOD_FILE_READCALLBACK userread, FMOD_FILE_SEEKCALLBACK userseek, FMOD_FILE_ASYNCREADCALLBACK userasyncread, FMOD_FILE_ASYNCCANCELCALLBACK userasynccancel, int blockalign); +FMOD_RESULT F_API FMOD_System_AttachFileSystem (FMOD_SYSTEM *system, FMOD_FILE_OPENCALLBACK useropen, FMOD_FILE_CLOSECALLBACK userclose, FMOD_FILE_READCALLBACK userread, FMOD_FILE_SEEKCALLBACK userseek); +FMOD_RESULT F_API FMOD_System_SetAdvancedSettings (FMOD_SYSTEM *system, FMOD_ADVANCEDSETTINGS *settings); +FMOD_RESULT F_API FMOD_System_GetAdvancedSettings (FMOD_SYSTEM *system, FMOD_ADVANCEDSETTINGS *settings); +FMOD_RESULT F_API FMOD_System_SetSpeakerMode (FMOD_SYSTEM *system, FMOD_SPEAKERMODE speakermode); +FMOD_RESULT F_API FMOD_System_GetSpeakerMode (FMOD_SYSTEM *system, FMOD_SPEAKERMODE *speakermode); +FMOD_RESULT F_API FMOD_System_SetCallback (FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK callback); + +/* + Plug-in support +*/ + +FMOD_RESULT F_API FMOD_System_SetPluginPath (FMOD_SYSTEM *system, const char *path); +FMOD_RESULT F_API FMOD_System_LoadPlugin (FMOD_SYSTEM *system, const char *filename, unsigned int *handle, unsigned int priority); +FMOD_RESULT F_API FMOD_System_UnloadPlugin (FMOD_SYSTEM *system, unsigned int handle); +FMOD_RESULT F_API FMOD_System_GetNumPlugins (FMOD_SYSTEM *system, FMOD_PLUGINTYPE plugintype, int *numplugins); +FMOD_RESULT F_API FMOD_System_GetPluginHandle (FMOD_SYSTEM *system, FMOD_PLUGINTYPE plugintype, int index, unsigned int *handle); +FMOD_RESULT F_API FMOD_System_GetPluginInfo (FMOD_SYSTEM *system, unsigned int handle, FMOD_PLUGINTYPE *plugintype, char *name, int namelen, unsigned int *version); +FMOD_RESULT F_API FMOD_System_SetOutputByPlugin (FMOD_SYSTEM *system, unsigned int handle); +FMOD_RESULT F_API FMOD_System_GetOutputByPlugin (FMOD_SYSTEM *system, unsigned int *handle); +FMOD_RESULT F_API FMOD_System_CreateDSPByPlugin (FMOD_SYSTEM *system, unsigned int handle, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_System_RegisterCodec (FMOD_SYSTEM *system, FMOD_CODEC_DESCRIPTION *description, unsigned int *handle, unsigned int priority); +FMOD_RESULT F_API FMOD_System_RegisterDSP (FMOD_SYSTEM *system, FMOD_DSP_DESCRIPTION *description, unsigned int *handle); + +/* + Init/Close +*/ + +FMOD_RESULT F_API FMOD_System_Init (FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata); +FMOD_RESULT F_API FMOD_System_Close (FMOD_SYSTEM *system); + +/* + General post-init system functions +*/ + +FMOD_RESULT F_API FMOD_System_Update (FMOD_SYSTEM *system); + +FMOD_RESULT F_API FMOD_System_Set3DSettings (FMOD_SYSTEM *system, float dopplerscale, float distancefactor, float rolloffscale); +FMOD_RESULT F_API FMOD_System_Get3DSettings (FMOD_SYSTEM *system, float *dopplerscale, float *distancefactor, float *rolloffscale); +FMOD_RESULT F_API FMOD_System_Set3DNumListeners (FMOD_SYSTEM *system, int numlisteners); +FMOD_RESULT F_API FMOD_System_Get3DNumListeners (FMOD_SYSTEM *system, int *numlisteners); +FMOD_RESULT F_API FMOD_System_Set3DListenerAttributes(FMOD_SYSTEM *system, int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up); +FMOD_RESULT F_API FMOD_System_Get3DListenerAttributes(FMOD_SYSTEM *system, int listener, FMOD_VECTOR *pos, FMOD_VECTOR *vel, FMOD_VECTOR *forward, FMOD_VECTOR *up); +FMOD_RESULT F_API FMOD_System_Set3DRolloffCallback (FMOD_SYSTEM *system, FMOD_3D_ROLLOFFCALLBACK callback); +FMOD_RESULT F_API FMOD_System_Set3DSpeakerPosition (FMOD_SYSTEM *system, FMOD_SPEAKER speaker, float x, float y, FMOD_BOOL active); +FMOD_RESULT F_API FMOD_System_Get3DSpeakerPosition (FMOD_SYSTEM *system, FMOD_SPEAKER speaker, float *x, float *y, FMOD_BOOL *active); + +FMOD_RESULT F_API FMOD_System_SetStreamBufferSize (FMOD_SYSTEM *system, unsigned int filebuffersize, FMOD_TIMEUNIT filebuffersizetype); +FMOD_RESULT F_API FMOD_System_GetStreamBufferSize (FMOD_SYSTEM *system, unsigned int *filebuffersize, FMOD_TIMEUNIT *filebuffersizetype); + +/* + System information functions. +*/ + +FMOD_RESULT F_API FMOD_System_GetVersion (FMOD_SYSTEM *system, unsigned int *version); +FMOD_RESULT F_API FMOD_System_GetOutputHandle (FMOD_SYSTEM *system, void **handle); +FMOD_RESULT F_API FMOD_System_GetChannelsPlaying (FMOD_SYSTEM *system, int *channels); +FMOD_RESULT F_API FMOD_System_GetHardwareChannels (FMOD_SYSTEM *system, int *numhardwarechannels); +FMOD_RESULT F_API FMOD_System_GetCPUUsage (FMOD_SYSTEM *system, float *dsp, float *stream, float *geometry, float *update, float *total); +FMOD_RESULT F_API FMOD_System_GetSoundRAM (FMOD_SYSTEM *system, int *currentalloced, int *maxalloced, int *total); +FMOD_RESULT F_API FMOD_System_GetNumCDROMDrives (FMOD_SYSTEM *system, int *numdrives); +FMOD_RESULT F_API FMOD_System_GetCDROMDriveName (FMOD_SYSTEM *system, int drive, char *drivename, int drivenamelen, char *scsiname, int scsinamelen, char *devicename, int devicenamelen); +FMOD_RESULT F_API FMOD_System_GetSpectrum (FMOD_SYSTEM *system, float *spectrumarray, int numvalues, int channeloffset, FMOD_DSP_FFT_WINDOW windowtype); +FMOD_RESULT F_API FMOD_System_GetWaveData (FMOD_SYSTEM *system, float *wavearray, int numvalues, int channeloffset); + +/* + Sound/DSP/Channel/FX creation and retrieval. +*/ + +FMOD_RESULT F_API FMOD_System_CreateSound (FMOD_SYSTEM *system, const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, FMOD_SOUND **sound); +FMOD_RESULT F_API FMOD_System_CreateStream (FMOD_SYSTEM *system, const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, FMOD_SOUND **sound); +FMOD_RESULT F_API FMOD_System_CreateDSP (FMOD_SYSTEM *system, FMOD_DSP_DESCRIPTION *description, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_System_CreateDSPByType (FMOD_SYSTEM *system, FMOD_DSP_TYPE type, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_System_CreateChannelGroup (FMOD_SYSTEM *system, const char *name, FMOD_CHANNELGROUP **channelgroup); +FMOD_RESULT F_API FMOD_System_CreateSoundGroup (FMOD_SYSTEM *system, const char *name, FMOD_SOUNDGROUP **soundgroup); +FMOD_RESULT F_API FMOD_System_CreateReverb (FMOD_SYSTEM *system, FMOD_REVERB **reverb); + +FMOD_RESULT F_API FMOD_System_PlaySound (FMOD_SYSTEM *system, FMOD_CHANNELINDEX channelid, FMOD_SOUND *sound, FMOD_BOOL paused, FMOD_CHANNEL **channel); +FMOD_RESULT F_API FMOD_System_PlayDSP (FMOD_SYSTEM *system, FMOD_CHANNELINDEX channelid, FMOD_DSP *dsp, FMOD_BOOL paused, FMOD_CHANNEL **channel); +FMOD_RESULT F_API FMOD_System_GetChannel (FMOD_SYSTEM *system, int channelid, FMOD_CHANNEL **channel); +FMOD_RESULT F_API FMOD_System_GetMasterChannelGroup (FMOD_SYSTEM *system, FMOD_CHANNELGROUP **channelgroup); +FMOD_RESULT F_API FMOD_System_GetMasterSoundGroup (FMOD_SYSTEM *system, FMOD_SOUNDGROUP **soundgroup); + +/* + Reverb API +*/ + +FMOD_RESULT F_API FMOD_System_SetReverbProperties (FMOD_SYSTEM *system, const FMOD_REVERB_PROPERTIES *prop); +FMOD_RESULT F_API FMOD_System_GetReverbProperties (FMOD_SYSTEM *system, FMOD_REVERB_PROPERTIES *prop); +FMOD_RESULT F_API FMOD_System_SetReverbAmbientProperties(FMOD_SYSTEM *system, FMOD_REVERB_PROPERTIES *prop); +FMOD_RESULT F_API FMOD_System_GetReverbAmbientProperties(FMOD_SYSTEM *system, FMOD_REVERB_PROPERTIES *prop); + +/* + System level DSP access. +*/ + +FMOD_RESULT F_API FMOD_System_GetDSPHead (FMOD_SYSTEM *system, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_System_AddDSP (FMOD_SYSTEM *system, FMOD_DSP *dsp, FMOD_DSPCONNECTION **connection); +FMOD_RESULT F_API FMOD_System_LockDSP (FMOD_SYSTEM *system); +FMOD_RESULT F_API FMOD_System_UnlockDSP (FMOD_SYSTEM *system); +FMOD_RESULT F_API FMOD_System_GetDSPClock (FMOD_SYSTEM *system, unsigned int *hi, unsigned int *lo); + +/* + Recording API. +*/ + +FMOD_RESULT F_API FMOD_System_GetRecordNumDrivers (FMOD_SYSTEM *system, int *numdrivers); +FMOD_RESULT F_API FMOD_System_GetRecordDriverInfo (FMOD_SYSTEM *system, int id, char *name, int namelen, FMOD_GUID *guid); +FMOD_RESULT F_API FMOD_System_GetRecordDriverInfoW (FMOD_SYSTEM *system, int id, short *name, int namelen, FMOD_GUID *guid); +FMOD_RESULT F_API FMOD_System_GetRecordDriverCaps (FMOD_SYSTEM *system, int id, FMOD_CAPS *caps, int *minfrequency, int *maxfrequency); +FMOD_RESULT F_API FMOD_System_GetRecordPosition (FMOD_SYSTEM *system, int id, unsigned int *position); + +FMOD_RESULT F_API FMOD_System_RecordStart (FMOD_SYSTEM *system, int id, FMOD_SOUND *sound, FMOD_BOOL loop); +FMOD_RESULT F_API FMOD_System_RecordStop (FMOD_SYSTEM *system, int id); +FMOD_RESULT F_API FMOD_System_IsRecording (FMOD_SYSTEM *system, int id, FMOD_BOOL *recording); + +/* + Geometry API. +*/ + +FMOD_RESULT F_API FMOD_System_CreateGeometry (FMOD_SYSTEM *system, int maxpolygons, int maxvertices, FMOD_GEOMETRY **geometry); +FMOD_RESULT F_API FMOD_System_SetGeometrySettings (FMOD_SYSTEM *system, float maxworldsize); +FMOD_RESULT F_API FMOD_System_GetGeometrySettings (FMOD_SYSTEM *system, float *maxworldsize); +FMOD_RESULT F_API FMOD_System_LoadGeometry (FMOD_SYSTEM *system, const void *data, int datasize, FMOD_GEOMETRY **geometry); +FMOD_RESULT F_API FMOD_System_GetGeometryOcclusion (FMOD_SYSTEM *system, const FMOD_VECTOR *listener, const FMOD_VECTOR *source, float *direct, float *reverb); + +/* + Network functions. +*/ + +FMOD_RESULT F_API FMOD_System_SetNetworkProxy (FMOD_SYSTEM *system, const char *proxy); +FMOD_RESULT F_API FMOD_System_GetNetworkProxy (FMOD_SYSTEM *system, char *proxy, int proxylen); +FMOD_RESULT F_API FMOD_System_SetNetworkTimeout (FMOD_SYSTEM *system, int timeout); +FMOD_RESULT F_API FMOD_System_GetNetworkTimeout (FMOD_SYSTEM *system, int *timeout); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_System_SetUserData (FMOD_SYSTEM *system, void *userdata); +FMOD_RESULT F_API FMOD_System_GetUserData (FMOD_SYSTEM *system, void **userdata); + +FMOD_RESULT F_API FMOD_System_GetMemoryInfo (FMOD_SYSTEM *system, unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + +/* + 'Sound' API +*/ + +FMOD_RESULT F_API FMOD_Sound_Release (FMOD_SOUND *sound); +FMOD_RESULT F_API FMOD_Sound_GetSystemObject (FMOD_SOUND *sound, FMOD_SYSTEM **system); + +/* + Standard sound manipulation functions. +*/ + +FMOD_RESULT F_API FMOD_Sound_Lock (FMOD_SOUND *sound, unsigned int offset, unsigned int length, void **ptr1, void **ptr2, unsigned int *len1, unsigned int *len2); +FMOD_RESULT F_API FMOD_Sound_Unlock (FMOD_SOUND *sound, void *ptr1, void *ptr2, unsigned int len1, unsigned int len2); +FMOD_RESULT F_API FMOD_Sound_SetDefaults (FMOD_SOUND *sound, float frequency, float volume, float pan, int priority); +FMOD_RESULT F_API FMOD_Sound_GetDefaults (FMOD_SOUND *sound, float *frequency, float *volume, float *pan, int *priority); +FMOD_RESULT F_API FMOD_Sound_SetVariations (FMOD_SOUND *sound, float frequencyvar, float volumevar, float panvar); +FMOD_RESULT F_API FMOD_Sound_GetVariations (FMOD_SOUND *sound, float *frequencyvar, float *volumevar, float *panvar); +FMOD_RESULT F_API FMOD_Sound_Set3DMinMaxDistance (FMOD_SOUND *sound, float min, float max); +FMOD_RESULT F_API FMOD_Sound_Get3DMinMaxDistance (FMOD_SOUND *sound, float *min, float *max); +FMOD_RESULT F_API FMOD_Sound_Set3DConeSettings (FMOD_SOUND *sound, float insideconeangle, float outsideconeangle, float outsidevolume); +FMOD_RESULT F_API FMOD_Sound_Get3DConeSettings (FMOD_SOUND *sound, float *insideconeangle, float *outsideconeangle, float *outsidevolume); +FMOD_RESULT F_API FMOD_Sound_Set3DCustomRolloff (FMOD_SOUND *sound, FMOD_VECTOR *points, int numpoints); +FMOD_RESULT F_API FMOD_Sound_Get3DCustomRolloff (FMOD_SOUND *sound, FMOD_VECTOR **points, int *numpoints); +FMOD_RESULT F_API FMOD_Sound_SetSubSound (FMOD_SOUND *sound, int index, FMOD_SOUND *subsound); +FMOD_RESULT F_API FMOD_Sound_GetSubSound (FMOD_SOUND *sound, int index, FMOD_SOUND **subsound); +FMOD_RESULT F_API FMOD_Sound_GetSubSoundParent (FMOD_SOUND *sound, FMOD_SOUND **parentsound); +FMOD_RESULT F_API FMOD_Sound_SetSubSoundSentence (FMOD_SOUND *sound, int *subsoundlist, int numsubsounds); +FMOD_RESULT F_API FMOD_Sound_GetName (FMOD_SOUND *sound, char *name, int namelen); +FMOD_RESULT F_API FMOD_Sound_GetLength (FMOD_SOUND *sound, unsigned int *length, FMOD_TIMEUNIT lengthtype); +FMOD_RESULT F_API FMOD_Sound_GetFormat (FMOD_SOUND *sound, FMOD_SOUND_TYPE *type, FMOD_SOUND_FORMAT *format, int *channels, int *bits); +FMOD_RESULT F_API FMOD_Sound_GetNumSubSounds (FMOD_SOUND *sound, int *numsubsounds); +FMOD_RESULT F_API FMOD_Sound_GetNumTags (FMOD_SOUND *sound, int *numtags, int *numtagsupdated); +FMOD_RESULT F_API FMOD_Sound_GetTag (FMOD_SOUND *sound, const char *name, int index, FMOD_TAG *tag); +FMOD_RESULT F_API FMOD_Sound_GetOpenState (FMOD_SOUND *sound, FMOD_OPENSTATE *openstate, unsigned int *percentbuffered, FMOD_BOOL *starving, FMOD_BOOL *diskbusy); +FMOD_RESULT F_API FMOD_Sound_ReadData (FMOD_SOUND *sound, void *buffer, unsigned int lenbytes, unsigned int *read); +FMOD_RESULT F_API FMOD_Sound_SeekData (FMOD_SOUND *sound, unsigned int pcm); + +FMOD_RESULT F_API FMOD_Sound_SetSoundGroup (FMOD_SOUND *sound, FMOD_SOUNDGROUP *soundgroup); +FMOD_RESULT F_API FMOD_Sound_GetSoundGroup (FMOD_SOUND *sound, FMOD_SOUNDGROUP **soundgroup); + +/* + Synchronization point API. These points can come from markers embedded in wav files, and can also generate channel callbacks. +*/ + +FMOD_RESULT F_API FMOD_Sound_GetNumSyncPoints (FMOD_SOUND *sound, int *numsyncpoints); +FMOD_RESULT F_API FMOD_Sound_GetSyncPoint (FMOD_SOUND *sound, int index, FMOD_SYNCPOINT **point); +FMOD_RESULT F_API FMOD_Sound_GetSyncPointInfo (FMOD_SOUND *sound, FMOD_SYNCPOINT *point, char *name, int namelen, unsigned int *offset, FMOD_TIMEUNIT offsettype); +FMOD_RESULT F_API FMOD_Sound_AddSyncPoint (FMOD_SOUND *sound, unsigned int offset, FMOD_TIMEUNIT offsettype, const char *name, FMOD_SYNCPOINT **point); +FMOD_RESULT F_API FMOD_Sound_DeleteSyncPoint (FMOD_SOUND *sound, FMOD_SYNCPOINT *point); + +/* + Functions also in Channel class but here they are the 'default' to save having to change it in Channel all the time. +*/ + +FMOD_RESULT F_API FMOD_Sound_SetMode (FMOD_SOUND *sound, FMOD_MODE mode); +FMOD_RESULT F_API FMOD_Sound_GetMode (FMOD_SOUND *sound, FMOD_MODE *mode); +FMOD_RESULT F_API FMOD_Sound_SetLoopCount (FMOD_SOUND *sound, int loopcount); +FMOD_RESULT F_API FMOD_Sound_GetLoopCount (FMOD_SOUND *sound, int *loopcount); +FMOD_RESULT F_API FMOD_Sound_SetLoopPoints (FMOD_SOUND *sound, unsigned int loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int loopend, FMOD_TIMEUNIT loopendtype); +FMOD_RESULT F_API FMOD_Sound_GetLoopPoints (FMOD_SOUND *sound, unsigned int *loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int *loopend, FMOD_TIMEUNIT loopendtype); + +/* + For MOD/S3M/XM/IT/MID sequenced formats only. +*/ + +FMOD_RESULT F_API FMOD_Sound_GetMusicNumChannels (FMOD_SOUND *sound, int *numchannels); +FMOD_RESULT F_API FMOD_Sound_SetMusicChannelVolume (FMOD_SOUND *sound, int channel, float volume); +FMOD_RESULT F_API FMOD_Sound_GetMusicChannelVolume (FMOD_SOUND *sound, int channel, float *volume); +FMOD_RESULT F_API FMOD_Sound_SetMusicSpeed (FMOD_SOUND *sound, float speed); +FMOD_RESULT F_API FMOD_Sound_GetMusicSpeed (FMOD_SOUND *sound, float *speed); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_Sound_SetUserData (FMOD_SOUND *sound, void *userdata); +FMOD_RESULT F_API FMOD_Sound_GetUserData (FMOD_SOUND *sound, void **userdata); + +FMOD_RESULT F_API FMOD_Sound_GetMemoryInfo (FMOD_SOUND *sound, unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + +/* + 'Channel' API +*/ + +FMOD_RESULT F_API FMOD_Channel_GetSystemObject (FMOD_CHANNEL *channel, FMOD_SYSTEM **system); + +FMOD_RESULT F_API FMOD_Channel_Stop (FMOD_CHANNEL *channel); +FMOD_RESULT F_API FMOD_Channel_SetPaused (FMOD_CHANNEL *channel, FMOD_BOOL paused); +FMOD_RESULT F_API FMOD_Channel_GetPaused (FMOD_CHANNEL *channel, FMOD_BOOL *paused); +FMOD_RESULT F_API FMOD_Channel_SetVolume (FMOD_CHANNEL *channel, float volume); +FMOD_RESULT F_API FMOD_Channel_GetVolume (FMOD_CHANNEL *channel, float *volume); +FMOD_RESULT F_API FMOD_Channel_SetFrequency (FMOD_CHANNEL *channel, float frequency); +FMOD_RESULT F_API FMOD_Channel_GetFrequency (FMOD_CHANNEL *channel, float *frequency); +FMOD_RESULT F_API FMOD_Channel_SetPan (FMOD_CHANNEL *channel, float pan); +FMOD_RESULT F_API FMOD_Channel_GetPan (FMOD_CHANNEL *channel, float *pan); +FMOD_RESULT F_API FMOD_Channel_SetDelay (FMOD_CHANNEL *channel, FMOD_DELAYTYPE delaytype, unsigned int delayhi, unsigned int delaylo); +FMOD_RESULT F_API FMOD_Channel_GetDelay (FMOD_CHANNEL *channel, FMOD_DELAYTYPE delaytype, unsigned int *delayhi, unsigned int *delaylo); +FMOD_RESULT F_API FMOD_Channel_SetSpeakerMix (FMOD_CHANNEL *channel, float frontleft, float frontright, float center, float lfe, float backleft, float backright, float sideleft, float sideright); +FMOD_RESULT F_API FMOD_Channel_GetSpeakerMix (FMOD_CHANNEL *channel, float *frontleft, float *frontright, float *center, float *lfe, float *backleft, float *backright, float *sideleft, float *sideright); +FMOD_RESULT F_API FMOD_Channel_SetSpeakerLevels (FMOD_CHANNEL *channel, FMOD_SPEAKER speaker, float *levels, int numlevels); +FMOD_RESULT F_API FMOD_Channel_GetSpeakerLevels (FMOD_CHANNEL *channel, FMOD_SPEAKER speaker, float *levels, int numlevels); +FMOD_RESULT F_API FMOD_Channel_SetInputChannelMix (FMOD_CHANNEL *channel, float *levels, int numlevels); +FMOD_RESULT F_API FMOD_Channel_GetInputChannelMix (FMOD_CHANNEL *channel, float *levels, int numlevels); +FMOD_RESULT F_API FMOD_Channel_SetMute (FMOD_CHANNEL *channel, FMOD_BOOL mute); +FMOD_RESULT F_API FMOD_Channel_GetMute (FMOD_CHANNEL *channel, FMOD_BOOL *mute); +FMOD_RESULT F_API FMOD_Channel_SetPriority (FMOD_CHANNEL *channel, int priority); +FMOD_RESULT F_API FMOD_Channel_GetPriority (FMOD_CHANNEL *channel, int *priority); +FMOD_RESULT F_API FMOD_Channel_SetPosition (FMOD_CHANNEL *channel, unsigned int position, FMOD_TIMEUNIT postype); +FMOD_RESULT F_API FMOD_Channel_GetPosition (FMOD_CHANNEL *channel, unsigned int *position, FMOD_TIMEUNIT postype); +FMOD_RESULT F_API FMOD_Channel_SetReverbProperties (FMOD_CHANNEL *channel, const FMOD_REVERB_CHANNELPROPERTIES *prop); +FMOD_RESULT F_API FMOD_Channel_GetReverbProperties (FMOD_CHANNEL *channel, FMOD_REVERB_CHANNELPROPERTIES *prop); +FMOD_RESULT F_API FMOD_Channel_SetLowPassGain (FMOD_CHANNEL *channel, float gain); +FMOD_RESULT F_API FMOD_Channel_GetLowPassGain (FMOD_CHANNEL *channel, float *gain); + +FMOD_RESULT F_API FMOD_Channel_SetChannelGroup (FMOD_CHANNEL *channel, FMOD_CHANNELGROUP *channelgroup); +FMOD_RESULT F_API FMOD_Channel_GetChannelGroup (FMOD_CHANNEL *channel, FMOD_CHANNELGROUP **channelgroup); +FMOD_RESULT F_API FMOD_Channel_SetCallback (FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACK callback); + +/* + 3D functionality. +*/ + +FMOD_RESULT F_API FMOD_Channel_Set3DAttributes (FMOD_CHANNEL *channel, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel); +FMOD_RESULT F_API FMOD_Channel_Get3DAttributes (FMOD_CHANNEL *channel, FMOD_VECTOR *pos, FMOD_VECTOR *vel); +FMOD_RESULT F_API FMOD_Channel_Set3DMinMaxDistance (FMOD_CHANNEL *channel, float mindistance, float maxdistance); +FMOD_RESULT F_API FMOD_Channel_Get3DMinMaxDistance (FMOD_CHANNEL *channel, float *mindistance, float *maxdistance); +FMOD_RESULT F_API FMOD_Channel_Set3DConeSettings (FMOD_CHANNEL *channel, float insideconeangle, float outsideconeangle, float outsidevolume); +FMOD_RESULT F_API FMOD_Channel_Get3DConeSettings (FMOD_CHANNEL *channel, float *insideconeangle, float *outsideconeangle, float *outsidevolume); +FMOD_RESULT F_API FMOD_Channel_Set3DConeOrientation (FMOD_CHANNEL *channel, FMOD_VECTOR *orientation); +FMOD_RESULT F_API FMOD_Channel_Get3DConeOrientation (FMOD_CHANNEL *channel, FMOD_VECTOR *orientation); +FMOD_RESULT F_API FMOD_Channel_Set3DCustomRolloff (FMOD_CHANNEL *channel, FMOD_VECTOR *points, int numpoints); +FMOD_RESULT F_API FMOD_Channel_Get3DCustomRolloff (FMOD_CHANNEL *channel, FMOD_VECTOR **points, int *numpoints); +FMOD_RESULT F_API FMOD_Channel_Set3DOcclusion (FMOD_CHANNEL *channel, float directocclusion, float reverbocclusion); +FMOD_RESULT F_API FMOD_Channel_Get3DOcclusion (FMOD_CHANNEL *channel, float *directocclusion, float *reverbocclusion); +FMOD_RESULT F_API FMOD_Channel_Set3DSpread (FMOD_CHANNEL *channel, float angle); +FMOD_RESULT F_API FMOD_Channel_Get3DSpread (FMOD_CHANNEL *channel, float *angle); +FMOD_RESULT F_API FMOD_Channel_Set3DPanLevel (FMOD_CHANNEL *channel, float level); +FMOD_RESULT F_API FMOD_Channel_Get3DPanLevel (FMOD_CHANNEL *channel, float *level); +FMOD_RESULT F_API FMOD_Channel_Set3DDopplerLevel (FMOD_CHANNEL *channel, float level); +FMOD_RESULT F_API FMOD_Channel_Get3DDopplerLevel (FMOD_CHANNEL *channel, float *level); +FMOD_RESULT F_API FMOD_Channel_Set3DDistanceFilter (FMOD_CHANNEL *channel, FMOD_BOOL custom, float customLevel, float centerFreq); +FMOD_RESULT F_API FMOD_Channel_Get3DDistanceFilter (FMOD_CHANNEL *channel, FMOD_BOOL *custom, float *customLevel, float *centerFreq); + +/* + DSP functionality only for channels playing sounds created with FMOD_SOFTWARE. +*/ + +FMOD_RESULT F_API FMOD_Channel_GetDSPHead (FMOD_CHANNEL *channel, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_Channel_AddDSP (FMOD_CHANNEL *channel, FMOD_DSP *dsp, FMOD_DSPCONNECTION **connection); + +/* + Information only functions. +*/ + +FMOD_RESULT F_API FMOD_Channel_IsPlaying (FMOD_CHANNEL *channel, FMOD_BOOL *isplaying); +FMOD_RESULT F_API FMOD_Channel_IsVirtual (FMOD_CHANNEL *channel, FMOD_BOOL *isvirtual); +FMOD_RESULT F_API FMOD_Channel_GetAudibility (FMOD_CHANNEL *channel, float *audibility); +FMOD_RESULT F_API FMOD_Channel_GetCurrentSound (FMOD_CHANNEL *channel, FMOD_SOUND **sound); +FMOD_RESULT F_API FMOD_Channel_GetSpectrum (FMOD_CHANNEL *channel, float *spectrumarray, int numvalues, int channeloffset, FMOD_DSP_FFT_WINDOW windowtype); +FMOD_RESULT F_API FMOD_Channel_GetWaveData (FMOD_CHANNEL *channel, float *wavearray, int numvalues, int channeloffset); +FMOD_RESULT F_API FMOD_Channel_GetIndex (FMOD_CHANNEL *channel, int *index); + +/* + Functions also found in Sound class but here they can be set per channel. +*/ + +FMOD_RESULT F_API FMOD_Channel_SetMode (FMOD_CHANNEL *channel, FMOD_MODE mode); +FMOD_RESULT F_API FMOD_Channel_GetMode (FMOD_CHANNEL *channel, FMOD_MODE *mode); +FMOD_RESULT F_API FMOD_Channel_SetLoopCount (FMOD_CHANNEL *channel, int loopcount); +FMOD_RESULT F_API FMOD_Channel_GetLoopCount (FMOD_CHANNEL *channel, int *loopcount); +FMOD_RESULT F_API FMOD_Channel_SetLoopPoints (FMOD_CHANNEL *channel, unsigned int loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int loopend, FMOD_TIMEUNIT loopendtype); +FMOD_RESULT F_API FMOD_Channel_GetLoopPoints (FMOD_CHANNEL *channel, unsigned int *loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int *loopend, FMOD_TIMEUNIT loopendtype); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_Channel_SetUserData (FMOD_CHANNEL *channel, void *userdata); +FMOD_RESULT F_API FMOD_Channel_GetUserData (FMOD_CHANNEL *channel, void **userdata); + +FMOD_RESULT F_API FMOD_Channel_GetMemoryInfo (FMOD_CHANNEL *channel, unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + +/* + 'ChannelGroup' API +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_Release (FMOD_CHANNELGROUP *channelgroup); +FMOD_RESULT F_API FMOD_ChannelGroup_GetSystemObject (FMOD_CHANNELGROUP *channelgroup, FMOD_SYSTEM **system); + +/* + Channelgroup scale values. (changes attributes relative to the channels, doesn't overwrite them) +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_SetVolume (FMOD_CHANNELGROUP *channelgroup, float volume); +FMOD_RESULT F_API FMOD_ChannelGroup_GetVolume (FMOD_CHANNELGROUP *channelgroup, float *volume); +FMOD_RESULT F_API FMOD_ChannelGroup_SetPitch (FMOD_CHANNELGROUP *channelgroup, float pitch); +FMOD_RESULT F_API FMOD_ChannelGroup_GetPitch (FMOD_CHANNELGROUP *channelgroup, float *pitch); +FMOD_RESULT F_API FMOD_ChannelGroup_Set3DOcclusion (FMOD_CHANNELGROUP *channelgroup, float directocclusion, float reverbocclusion); +FMOD_RESULT F_API FMOD_ChannelGroup_Get3DOcclusion (FMOD_CHANNELGROUP *channelgroup, float *directocclusion, float *reverbocclusion); +FMOD_RESULT F_API FMOD_ChannelGroup_SetPaused (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL paused); +FMOD_RESULT F_API FMOD_ChannelGroup_GetPaused (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL *paused); +FMOD_RESULT F_API FMOD_ChannelGroup_SetMute (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL mute); +FMOD_RESULT F_API FMOD_ChannelGroup_GetMute (FMOD_CHANNELGROUP *channelgroup, FMOD_BOOL *mute); + +/* + Channelgroup override values. (recursively overwrites whatever settings the channels had) +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_Stop (FMOD_CHANNELGROUP *channelgroup); +FMOD_RESULT F_API FMOD_ChannelGroup_OverrideVolume (FMOD_CHANNELGROUP *channelgroup, float volume); +FMOD_RESULT F_API FMOD_ChannelGroup_OverrideFrequency(FMOD_CHANNELGROUP *channelgroup, float frequency); +FMOD_RESULT F_API FMOD_ChannelGroup_OverridePan (FMOD_CHANNELGROUP *channelgroup, float pan); +FMOD_RESULT F_API FMOD_ChannelGroup_OverrideReverbProperties(FMOD_CHANNELGROUP *channelgroup, const FMOD_REVERB_CHANNELPROPERTIES *prop); +FMOD_RESULT F_API FMOD_ChannelGroup_Override3DAttributes(FMOD_CHANNELGROUP *channelgroup, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel); +FMOD_RESULT F_API FMOD_ChannelGroup_OverrideSpeakerMix(FMOD_CHANNELGROUP *channelgroup, float frontleft, float frontright, float center, float lfe, float backleft, float backright, float sideleft, float sideright); + +/* + Nested channel groups. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_AddGroup (FMOD_CHANNELGROUP *channelgroup, FMOD_CHANNELGROUP *group); +FMOD_RESULT F_API FMOD_ChannelGroup_GetNumGroups (FMOD_CHANNELGROUP *channelgroup, int *numgroups); +FMOD_RESULT F_API FMOD_ChannelGroup_GetGroup (FMOD_CHANNELGROUP *channelgroup, int index, FMOD_CHANNELGROUP **group); +FMOD_RESULT F_API FMOD_ChannelGroup_GetParentGroup (FMOD_CHANNELGROUP *channelgroup, FMOD_CHANNELGROUP **group); + +/* + DSP functionality only for channel groups playing sounds created with FMOD_SOFTWARE. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_GetDSPHead (FMOD_CHANNELGROUP *channelgroup, FMOD_DSP **dsp); +FMOD_RESULT F_API FMOD_ChannelGroup_AddDSP (FMOD_CHANNELGROUP *channelgroup, FMOD_DSP *dsp, FMOD_DSPCONNECTION **connection); + +/* + Information only functions. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_GetName (FMOD_CHANNELGROUP *channelgroup, char *name, int namelen); +FMOD_RESULT F_API FMOD_ChannelGroup_GetNumChannels (FMOD_CHANNELGROUP *channelgroup, int *numchannels); +FMOD_RESULT F_API FMOD_ChannelGroup_GetChannel (FMOD_CHANNELGROUP *channelgroup, int index, FMOD_CHANNEL **channel); +FMOD_RESULT F_API FMOD_ChannelGroup_GetSpectrum (FMOD_CHANNELGROUP *channelgroup, float *spectrumarray, int numvalues, int channeloffset, FMOD_DSP_FFT_WINDOW windowtype); +FMOD_RESULT F_API FMOD_ChannelGroup_GetWaveData (FMOD_CHANNELGROUP *channelgroup, float *wavearray, int numvalues, int channeloffset); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_ChannelGroup_SetUserData (FMOD_CHANNELGROUP *channelgroup, void *userdata); +FMOD_RESULT F_API FMOD_ChannelGroup_GetUserData (FMOD_CHANNELGROUP *channelgroup, void **userdata); + +FMOD_RESULT F_API FMOD_ChannelGroup_GetMemoryInfo (FMOD_CHANNELGROUP *channelgroup, unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + +/* + 'SoundGroup' API +*/ + +FMOD_RESULT F_API FMOD_SoundGroup_Release (FMOD_SOUNDGROUP *soundgroup); +FMOD_RESULT F_API FMOD_SoundGroup_GetSystemObject (FMOD_SOUNDGROUP *soundgroup, FMOD_SYSTEM **system); + +/* + SoundGroup control functions. +*/ + +FMOD_RESULT F_API FMOD_SoundGroup_SetMaxAudible (FMOD_SOUNDGROUP *soundgroup, int maxaudible); +FMOD_RESULT F_API FMOD_SoundGroup_GetMaxAudible (FMOD_SOUNDGROUP *soundgroup, int *maxaudible); +FMOD_RESULT F_API FMOD_SoundGroup_SetMaxAudibleBehavior(FMOD_SOUNDGROUP *soundgroup, FMOD_SOUNDGROUP_BEHAVIOR behavior); +FMOD_RESULT F_API FMOD_SoundGroup_GetMaxAudibleBehavior(FMOD_SOUNDGROUP *soundgroup, FMOD_SOUNDGROUP_BEHAVIOR *behavior); +FMOD_RESULT F_API FMOD_SoundGroup_SetMuteFadeSpeed (FMOD_SOUNDGROUP *soundgroup, float speed); +FMOD_RESULT F_API FMOD_SoundGroup_GetMuteFadeSpeed (FMOD_SOUNDGROUP *soundgroup, float *speed); +FMOD_RESULT F_API FMOD_SoundGroup_SetVolume (FMOD_SOUNDGROUP *soundgroup, float volume); +FMOD_RESULT F_API FMOD_SoundGroup_GetVolume (FMOD_SOUNDGROUP *soundgroup, float *volume); +FMOD_RESULT F_API FMOD_SoundGroup_Stop (FMOD_SOUNDGROUP *soundgroup); + +/* + Information only functions. +*/ + +FMOD_RESULT F_API FMOD_SoundGroup_GetName (FMOD_SOUNDGROUP *soundgroup, char *name, int namelen); +FMOD_RESULT F_API FMOD_SoundGroup_GetNumSounds (FMOD_SOUNDGROUP *soundgroup, int *numsounds); +FMOD_RESULT F_API FMOD_SoundGroup_GetSound (FMOD_SOUNDGROUP *soundgroup, int index, FMOD_SOUND **sound); +FMOD_RESULT F_API FMOD_SoundGroup_GetNumPlaying (FMOD_SOUNDGROUP *soundgroup, int *numplaying); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_SoundGroup_SetUserData (FMOD_SOUNDGROUP *soundgroup, void *userdata); +FMOD_RESULT F_API FMOD_SoundGroup_GetUserData (FMOD_SOUNDGROUP *soundgroup, void **userdata); + +FMOD_RESULT F_API FMOD_SoundGroup_GetMemoryInfo (FMOD_SOUNDGROUP *soundgroup, unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + +/* + 'DSP' API +*/ + +FMOD_RESULT F_API FMOD_DSP_Release (FMOD_DSP *dsp); +FMOD_RESULT F_API FMOD_DSP_GetSystemObject (FMOD_DSP *dsp, FMOD_SYSTEM **system); + +/* + Connection / disconnection / input and output enumeration. +*/ + +FMOD_RESULT F_API FMOD_DSP_AddInput (FMOD_DSP *dsp, FMOD_DSP *target, FMOD_DSPCONNECTION **connection); +FMOD_RESULT F_API FMOD_DSP_DisconnectFrom (FMOD_DSP *dsp, FMOD_DSP *target); +FMOD_RESULT F_API FMOD_DSP_DisconnectAll (FMOD_DSP *dsp, FMOD_BOOL inputs, FMOD_BOOL outputs); +FMOD_RESULT F_API FMOD_DSP_Remove (FMOD_DSP *dsp); +FMOD_RESULT F_API FMOD_DSP_GetNumInputs (FMOD_DSP *dsp, int *numinputs); +FMOD_RESULT F_API FMOD_DSP_GetNumOutputs (FMOD_DSP *dsp, int *numoutputs); +FMOD_RESULT F_API FMOD_DSP_GetInput (FMOD_DSP *dsp, int index, FMOD_DSP **input, FMOD_DSPCONNECTION **inputconnection); +FMOD_RESULT F_API FMOD_DSP_GetOutput (FMOD_DSP *dsp, int index, FMOD_DSP **output, FMOD_DSPCONNECTION **outputconnection); + +/* + DSP unit control. +*/ + +FMOD_RESULT F_API FMOD_DSP_SetActive (FMOD_DSP *dsp, FMOD_BOOL active); +FMOD_RESULT F_API FMOD_DSP_GetActive (FMOD_DSP *dsp, FMOD_BOOL *active); +FMOD_RESULT F_API FMOD_DSP_SetBypass (FMOD_DSP *dsp, FMOD_BOOL bypass); +FMOD_RESULT F_API FMOD_DSP_GetBypass (FMOD_DSP *dsp, FMOD_BOOL *bypass); +FMOD_RESULT F_API FMOD_DSP_SetSpeakerActive (FMOD_DSP *dsp, FMOD_SPEAKER speaker, FMOD_BOOL active); +FMOD_RESULT F_API FMOD_DSP_GetSpeakerActive (FMOD_DSP *dsp, FMOD_SPEAKER speaker, FMOD_BOOL *active); +FMOD_RESULT F_API FMOD_DSP_Reset (FMOD_DSP *dsp); + +/* + DSP parameter control. +*/ + +FMOD_RESULT F_API FMOD_DSP_SetParameter (FMOD_DSP *dsp, int index, float value); +FMOD_RESULT F_API FMOD_DSP_GetParameter (FMOD_DSP *dsp, int index, float *value, char *valuestr, int valuestrlen); +FMOD_RESULT F_API FMOD_DSP_GetNumParameters (FMOD_DSP *dsp, int *numparams); +FMOD_RESULT F_API FMOD_DSP_GetParameterInfo (FMOD_DSP *dsp, int index, char *name, char *label, char *description, int descriptionlen, float *min, float *max); +FMOD_RESULT F_API FMOD_DSP_ShowConfigDialog (FMOD_DSP *dsp, void *hwnd, FMOD_BOOL show); + +/* + DSP attributes. +*/ + +FMOD_RESULT F_API FMOD_DSP_GetInfo (FMOD_DSP *dsp, char *name, unsigned int *version, int *channels, int *configwidth, int *configheight); +FMOD_RESULT F_API FMOD_DSP_GetType (FMOD_DSP *dsp, FMOD_DSP_TYPE *type); +FMOD_RESULT F_API FMOD_DSP_SetDefaults (FMOD_DSP *dsp, float frequency, float volume, float pan, int priority); +FMOD_RESULT F_API FMOD_DSP_GetDefaults (FMOD_DSP *dsp, float *frequency, float *volume, float *pan, int *priority); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_DSP_SetUserData (FMOD_DSP *dsp, void *userdata); +FMOD_RESULT F_API FMOD_DSP_GetUserData (FMOD_DSP *dsp, void **userdata); + +FMOD_RESULT F_API FMOD_DSP_GetMemoryInfo (FMOD_DSP *dsp, unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + +/* + 'DSPConnection' API +*/ + +FMOD_RESULT F_API FMOD_DSPConnection_GetInput (FMOD_DSPCONNECTION *dspconnection, FMOD_DSP **input); +FMOD_RESULT F_API FMOD_DSPConnection_GetOutput (FMOD_DSPCONNECTION *dspconnection, FMOD_DSP **output); +FMOD_RESULT F_API FMOD_DSPConnection_SetMix (FMOD_DSPCONNECTION *dspconnection, float volume); +FMOD_RESULT F_API FMOD_DSPConnection_GetMix (FMOD_DSPCONNECTION *dspconnection, float *volume); +FMOD_RESULT F_API FMOD_DSPConnection_SetLevels (FMOD_DSPCONNECTION *dspconnection, FMOD_SPEAKER speaker, float *levels, int numlevels); +FMOD_RESULT F_API FMOD_DSPConnection_GetLevels (FMOD_DSPCONNECTION *dspconnection, FMOD_SPEAKER speaker, float *levels, int numlevels); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_DSPConnection_SetUserData (FMOD_DSPCONNECTION *dspconnection, void *userdata); +FMOD_RESULT F_API FMOD_DSPConnection_GetUserData (FMOD_DSPCONNECTION *dspconnection, void **userdata); + +FMOD_RESULT F_API FMOD_DSPConnection_GetMemoryInfo (FMOD_DSPCONNECTION *dspconnection, unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + +/* + 'Geometry' API +*/ + +FMOD_RESULT F_API FMOD_Geometry_Release (FMOD_GEOMETRY *geometry); + +/* + Polygon manipulation. +*/ + +FMOD_RESULT F_API FMOD_Geometry_AddPolygon (FMOD_GEOMETRY *geometry, float directocclusion, float reverbocclusion, FMOD_BOOL doublesided, int numvertices, const FMOD_VECTOR *vertices, int *polygonindex); +FMOD_RESULT F_API FMOD_Geometry_GetNumPolygons (FMOD_GEOMETRY *geometry, int *numpolygons); +FMOD_RESULT F_API FMOD_Geometry_GetMaxPolygons (FMOD_GEOMETRY *geometry, int *maxpolygons, int *maxvertices); +FMOD_RESULT F_API FMOD_Geometry_GetPolygonNumVertices(FMOD_GEOMETRY *geometry, int index, int *numvertices); +FMOD_RESULT F_API FMOD_Geometry_SetPolygonVertex (FMOD_GEOMETRY *geometry, int index, int vertexindex, const FMOD_VECTOR *vertex); +FMOD_RESULT F_API FMOD_Geometry_GetPolygonVertex (FMOD_GEOMETRY *geometry, int index, int vertexindex, FMOD_VECTOR *vertex); +FMOD_RESULT F_API FMOD_Geometry_SetPolygonAttributes (FMOD_GEOMETRY *geometry, int index, float directocclusion, float reverbocclusion, FMOD_BOOL doublesided); +FMOD_RESULT F_API FMOD_Geometry_GetPolygonAttributes (FMOD_GEOMETRY *geometry, int index, float *directocclusion, float *reverbocclusion, FMOD_BOOL *doublesided); + +/* + Object manipulation. +*/ + +FMOD_RESULT F_API FMOD_Geometry_SetActive (FMOD_GEOMETRY *geometry, FMOD_BOOL active); +FMOD_RESULT F_API FMOD_Geometry_GetActive (FMOD_GEOMETRY *geometry, FMOD_BOOL *active); +FMOD_RESULT F_API FMOD_Geometry_SetRotation (FMOD_GEOMETRY *geometry, const FMOD_VECTOR *forward, const FMOD_VECTOR *up); +FMOD_RESULT F_API FMOD_Geometry_GetRotation (FMOD_GEOMETRY *geometry, FMOD_VECTOR *forward, FMOD_VECTOR *up); +FMOD_RESULT F_API FMOD_Geometry_SetPosition (FMOD_GEOMETRY *geometry, const FMOD_VECTOR *position); +FMOD_RESULT F_API FMOD_Geometry_GetPosition (FMOD_GEOMETRY *geometry, FMOD_VECTOR *position); +FMOD_RESULT F_API FMOD_Geometry_SetScale (FMOD_GEOMETRY *geometry, const FMOD_VECTOR *scale); +FMOD_RESULT F_API FMOD_Geometry_GetScale (FMOD_GEOMETRY *geometry, FMOD_VECTOR *scale); +FMOD_RESULT F_API FMOD_Geometry_Save (FMOD_GEOMETRY *geometry, void *data, int *datasize); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_Geometry_SetUserData (FMOD_GEOMETRY *geometry, void *userdata); +FMOD_RESULT F_API FMOD_Geometry_GetUserData (FMOD_GEOMETRY *geometry, void **userdata); + +FMOD_RESULT F_API FMOD_Geometry_GetMemoryInfo (FMOD_GEOMETRY *geometry, unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + +/* + 'Reverb' API +*/ + +FMOD_RESULT F_API FMOD_Reverb_Release (FMOD_REVERB *reverb); + +/* + Reverb manipulation. +*/ + +FMOD_RESULT F_API FMOD_Reverb_Set3DAttributes (FMOD_REVERB *reverb, const FMOD_VECTOR *position, float mindistance, float maxdistance); +FMOD_RESULT F_API FMOD_Reverb_Get3DAttributes (FMOD_REVERB *reverb, FMOD_VECTOR *position, float *mindistance, float *maxdistance); +FMOD_RESULT F_API FMOD_Reverb_SetProperties (FMOD_REVERB *reverb, const FMOD_REVERB_PROPERTIES *properties); +FMOD_RESULT F_API FMOD_Reverb_GetProperties (FMOD_REVERB *reverb, FMOD_REVERB_PROPERTIES *properties); +FMOD_RESULT F_API FMOD_Reverb_SetActive (FMOD_REVERB *reverb, FMOD_BOOL active); +FMOD_RESULT F_API FMOD_Reverb_GetActive (FMOD_REVERB *reverb, FMOD_BOOL *active); + +/* + Userdata set/get. +*/ + +FMOD_RESULT F_API FMOD_Reverb_SetUserData (FMOD_REVERB *reverb, void *userdata); +FMOD_RESULT F_API FMOD_Reverb_GetUserData (FMOD_REVERB *reverb, void **userdata); + +FMOD_RESULT F_API FMOD_Reverb_GetMemoryInfo (FMOD_REVERB *reverb, unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); +/*$ preserve start $*/ + +#ifdef __cplusplus +} +#endif + +#endif + +/*$ preserve end $*/ diff --git a/src/imports/fmodex/fmod.hpp b/src/imports/fmodex/fmod.hpp new file mode 100644 index 0000000..df19647 --- /dev/null +++ b/src/imports/fmodex/fmod.hpp @@ -0,0 +1,609 @@ +/* ========================================================================================== */ +/* FMOD Ex - C++ header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header in conjunction with fmod.h (which contains all the constants / callbacks) */ +/* to develop using C++ classes. */ +/* ========================================================================================== */ + +#ifndef _FMOD_HPP +#define _FMOD_HPP + +#include "fmod.h" + +/* + Constant and defines +*/ + +/* + FMOD Namespace +*/ +namespace FMOD +{ + class System; + class Sound; + class Channel; + class ChannelGroup; + class SoundGroup; + class Reverb; + class DSP; + class DSPConnection; + class Geometry; + + /* + FMOD global system functions (optional). + */ + inline FMOD_RESULT Memory_Initialize(void *poolmem, int poollen, FMOD_MEMORY_ALLOCCALLBACK useralloc, FMOD_MEMORY_REALLOCCALLBACK userrealloc, FMOD_MEMORY_FREECALLBACK userfree, FMOD_MEMORY_TYPE memtypeflags = FMOD_MEMORY_ALL) { return FMOD_Memory_Initialize(poolmem, poollen, useralloc, userrealloc, userfree, memtypeflags); } + inline FMOD_RESULT Memory_GetStats (int *currentalloced, int *maxalloced, bool blocking = true) { return FMOD_Memory_GetStats(currentalloced, maxalloced, blocking); } + inline FMOD_RESULT Debug_SetLevel(FMOD_DEBUGLEVEL level) { return FMOD_Debug_SetLevel(level); } + inline FMOD_RESULT Debug_GetLevel(FMOD_DEBUGLEVEL *level) { return FMOD_Debug_GetLevel(level); } + inline FMOD_RESULT File_SetDiskBusy(int busy) { return FMOD_File_SetDiskBusy(busy); } + inline FMOD_RESULT File_GetDiskBusy(int *busy) { return FMOD_File_GetDiskBusy(busy); } + + /* + FMOD System factory functions. + */ + inline FMOD_RESULT System_Create(System **system) { return FMOD_System_Create((FMOD_SYSTEM **)system); } + + /* + 'System' API + */ + + class System + { + private: + + System(); /* Constructor made private so user cannot statically instance a System class. + System_Create must be used. */ + public: + + FMOD_RESULT F_API release (); + + // Pre-init functions. + FMOD_RESULT F_API setOutput (FMOD_OUTPUTTYPE output); + FMOD_RESULT F_API getOutput (FMOD_OUTPUTTYPE *output); + FMOD_RESULT F_API getNumDrivers (int *numdrivers); + FMOD_RESULT F_API getDriverInfo (int id, char *name, int namelen, FMOD_GUID *guid); + FMOD_RESULT F_API getDriverInfoW (int id, short *name, int namelen, FMOD_GUID *guid); + FMOD_RESULT F_API getDriverCaps (int id, FMOD_CAPS *caps, int *controlpaneloutputrate, FMOD_SPEAKERMODE *controlpanelspeakermode); + FMOD_RESULT F_API setDriver (int driver); + FMOD_RESULT F_API getDriver (int *driver); + FMOD_RESULT F_API setHardwareChannels (int numhardwarechannels); + FMOD_RESULT F_API setSoftwareChannels (int numsoftwarechannels); + FMOD_RESULT F_API getSoftwareChannels (int *numsoftwarechannels); + FMOD_RESULT F_API setSoftwareFormat (int samplerate, FMOD_SOUND_FORMAT format, int numoutputchannels, int maxinputchannels, FMOD_DSP_RESAMPLER resamplemethod); + FMOD_RESULT F_API getSoftwareFormat (int *samplerate, FMOD_SOUND_FORMAT *format, int *numoutputchannels, int *maxinputchannels, FMOD_DSP_RESAMPLER *resamplemethod, int *bits); + FMOD_RESULT F_API setDSPBufferSize (unsigned int bufferlength, int numbuffers); + FMOD_RESULT F_API getDSPBufferSize (unsigned int *bufferlength, int *numbuffers); + FMOD_RESULT F_API setFileSystem (FMOD_FILE_OPENCALLBACK useropen, FMOD_FILE_CLOSECALLBACK userclose, FMOD_FILE_READCALLBACK userread, FMOD_FILE_SEEKCALLBACK userseek, FMOD_FILE_ASYNCREADCALLBACK userasyncread, FMOD_FILE_ASYNCCANCELCALLBACK userasynccancel, int blockalign); + FMOD_RESULT F_API attachFileSystem (FMOD_FILE_OPENCALLBACK useropen, FMOD_FILE_CLOSECALLBACK userclose, FMOD_FILE_READCALLBACK userread, FMOD_FILE_SEEKCALLBACK userseek); + FMOD_RESULT F_API setAdvancedSettings (FMOD_ADVANCEDSETTINGS *settings); + FMOD_RESULT F_API getAdvancedSettings (FMOD_ADVANCEDSETTINGS *settings); + FMOD_RESULT F_API setSpeakerMode (FMOD_SPEAKERMODE speakermode); + FMOD_RESULT F_API getSpeakerMode (FMOD_SPEAKERMODE *speakermode); + FMOD_RESULT F_API setCallback (FMOD_SYSTEM_CALLBACK callback); + + // Plug-in support + FMOD_RESULT F_API setPluginPath (const char *path); + FMOD_RESULT F_API loadPlugin (const char *filename, unsigned int *handle, unsigned int priority = 0); + FMOD_RESULT F_API unloadPlugin (unsigned int handle); + FMOD_RESULT F_API getNumPlugins (FMOD_PLUGINTYPE plugintype, int *numplugins); + FMOD_RESULT F_API getPluginHandle (FMOD_PLUGINTYPE plugintype, int index, unsigned int *handle); + FMOD_RESULT F_API getPluginInfo (unsigned int handle, FMOD_PLUGINTYPE *plugintype, char *name, int namelen, unsigned int *version); + FMOD_RESULT F_API setOutputByPlugin (unsigned int handle); + FMOD_RESULT F_API getOutputByPlugin (unsigned int *handle); + FMOD_RESULT F_API createDSPByPlugin (unsigned int handle, DSP **dsp); + FMOD_RESULT F_API registerCodec (FMOD_CODEC_DESCRIPTION *description, unsigned int *handle, unsigned int priority = 0); + FMOD_RESULT F_API registerDSP (FMOD_DSP_DESCRIPTION *description, unsigned int *handle); + + // Init/Close + FMOD_RESULT F_API init (int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata); + FMOD_RESULT F_API close (); + + // General post-init system functions + FMOD_RESULT F_API update (); /* IMPORTANT! CALL THIS ONCE PER FRAME! */ + + FMOD_RESULT F_API set3DSettings (float dopplerscale, float distancefactor, float rolloffscale); + FMOD_RESULT F_API get3DSettings (float *dopplerscale, float *distancefactor, float *rolloffscale); + FMOD_RESULT F_API set3DNumListeners (int numlisteners); + FMOD_RESULT F_API get3DNumListeners (int *numlisteners); + FMOD_RESULT F_API set3DListenerAttributes(int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up); + FMOD_RESULT F_API get3DListenerAttributes(int listener, FMOD_VECTOR *pos, FMOD_VECTOR *vel, FMOD_VECTOR *forward, FMOD_VECTOR *up); + FMOD_RESULT F_API set3DRolloffCallback (FMOD_3D_ROLLOFFCALLBACK callback); + FMOD_RESULT F_API set3DSpeakerPosition (FMOD_SPEAKER speaker, float x, float y, bool active); + FMOD_RESULT F_API get3DSpeakerPosition (FMOD_SPEAKER speaker, float *x, float *y, bool *active); + + FMOD_RESULT F_API setStreamBufferSize (unsigned int filebuffersize, FMOD_TIMEUNIT filebuffersizetype); + FMOD_RESULT F_API getStreamBufferSize (unsigned int *filebuffersize, FMOD_TIMEUNIT *filebuffersizetype); + + // System information functions. + FMOD_RESULT F_API getVersion (unsigned int *version); + FMOD_RESULT F_API getOutputHandle (void **handle); + FMOD_RESULT F_API getChannelsPlaying (int *channels); + FMOD_RESULT F_API getHardwareChannels (int *numhardwarechannels); + FMOD_RESULT F_API getCPUUsage (float *dsp, float *stream, float *geometry, float *update, float *total); + FMOD_RESULT F_API getSoundRAM (int *currentalloced, int *maxalloced, int *total); + FMOD_RESULT F_API getNumCDROMDrives (int *numdrives); + FMOD_RESULT F_API getCDROMDriveName (int drive, char *drivename, int drivenamelen, char *scsiname, int scsinamelen, char *devicename, int devicenamelen); + FMOD_RESULT F_API getSpectrum (float *spectrumarray, int numvalues, int channeloffset, FMOD_DSP_FFT_WINDOW windowtype); + FMOD_RESULT F_API getWaveData (float *wavearray, int numvalues, int channeloffset); + + // Sound/DSP/Channel/FX creation and retrieval. + FMOD_RESULT F_API createSound (const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound); + FMOD_RESULT F_API createStream (const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, Sound **sound); + FMOD_RESULT F_API createDSP (FMOD_DSP_DESCRIPTION *description, DSP **dsp); + FMOD_RESULT F_API createDSPByType (FMOD_DSP_TYPE type, DSP **dsp); + FMOD_RESULT F_API createChannelGroup (const char *name, ChannelGroup **channelgroup); + FMOD_RESULT F_API createSoundGroup (const char *name, SoundGroup **soundgroup); + FMOD_RESULT F_API createReverb (Reverb **reverb); + + FMOD_RESULT F_API playSound (FMOD_CHANNELINDEX channelid, Sound *sound, bool paused, Channel **channel); + FMOD_RESULT F_API playDSP (FMOD_CHANNELINDEX channelid, DSP *dsp, bool paused, Channel **channel); + FMOD_RESULT F_API getChannel (int channelid, Channel **channel); + FMOD_RESULT F_API getMasterChannelGroup (ChannelGroup **channelgroup); + FMOD_RESULT F_API getMasterSoundGroup (SoundGroup **soundgroup); + + // Reverb API + FMOD_RESULT F_API setReverbProperties (const FMOD_REVERB_PROPERTIES *prop); + FMOD_RESULT F_API getReverbProperties (FMOD_REVERB_PROPERTIES *prop); + FMOD_RESULT F_API setReverbAmbientProperties(FMOD_REVERB_PROPERTIES *prop); + FMOD_RESULT F_API getReverbAmbientProperties(FMOD_REVERB_PROPERTIES *prop); + + // System level DSP access. + FMOD_RESULT F_API getDSPHead (DSP **dsp); + FMOD_RESULT F_API addDSP (DSP *dsp, DSPConnection **connection); + FMOD_RESULT F_API lockDSP (); + FMOD_RESULT F_API unlockDSP (); + FMOD_RESULT F_API getDSPClock (unsigned int *hi, unsigned int *lo); + + // Recording API. + FMOD_RESULT F_API getRecordNumDrivers (int *numdrivers); + FMOD_RESULT F_API getRecordDriverInfo (int id, char *name, int namelen, FMOD_GUID *guid); + FMOD_RESULT F_API getRecordDriverInfoW (int id, short *name, int namelen, FMOD_GUID *guid); + FMOD_RESULT F_API getRecordDriverCaps (int id, FMOD_CAPS *caps, int *minfrequency, int *maxfrequency); + FMOD_RESULT F_API getRecordPosition (int id, unsigned int *position); + + FMOD_RESULT F_API recordStart (int id, Sound *sound, bool loop); + FMOD_RESULT F_API recordStop (int id); + FMOD_RESULT F_API isRecording (int id, bool *recording); + + // Geometry API. + FMOD_RESULT F_API createGeometry (int maxpolygons, int maxvertices, Geometry **geometry); + FMOD_RESULT F_API setGeometrySettings (float maxworldsize); + FMOD_RESULT F_API getGeometrySettings (float *maxworldsize); + FMOD_RESULT F_API loadGeometry (const void *data, int datasize, Geometry **geometry); + FMOD_RESULT F_API getGeometryOcclusion (const FMOD_VECTOR *listener, const FMOD_VECTOR *source, float *direct, float *reverb); + + // Network functions. + FMOD_RESULT F_API setNetworkProxy (const char *proxy); + FMOD_RESULT F_API getNetworkProxy (char *proxy, int proxylen); + FMOD_RESULT F_API setNetworkTimeout (int timeout); + FMOD_RESULT F_API getNetworkTimeout (int *timeout); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + FMOD_RESULT F_API getMemoryInfo (unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + }; + + /* + 'Sound' API + */ + class Sound + { + private: + + Sound(); /* Constructor made private so user cannot statically instance a Sound class. + Appropriate Sound creation or retrieval function must be used. */ + public: + + FMOD_RESULT F_API release (); + FMOD_RESULT F_API getSystemObject (System **system); + + // Standard sound manipulation functions. + FMOD_RESULT F_API lock (unsigned int offset, unsigned int length, void **ptr1, void **ptr2, unsigned int *len1, unsigned int *len2); + FMOD_RESULT F_API unlock (void *ptr1, void *ptr2, unsigned int len1, unsigned int len2); + FMOD_RESULT F_API setDefaults (float frequency, float volume, float pan, int priority); + FMOD_RESULT F_API getDefaults (float *frequency, float *volume, float *pan, int *priority); + FMOD_RESULT F_API setVariations (float frequencyvar, float volumevar, float panvar); + FMOD_RESULT F_API getVariations (float *frequencyvar, float *volumevar, float *panvar); + FMOD_RESULT F_API set3DMinMaxDistance (float min, float max); + FMOD_RESULT F_API get3DMinMaxDistance (float *min, float *max); + FMOD_RESULT F_API set3DConeSettings (float insideconeangle, float outsideconeangle, float outsidevolume); + FMOD_RESULT F_API get3DConeSettings (float *insideconeangle, float *outsideconeangle, float *outsidevolume); + FMOD_RESULT F_API set3DCustomRolloff (FMOD_VECTOR *points, int numpoints); + FMOD_RESULT F_API get3DCustomRolloff (FMOD_VECTOR **points, int *numpoints); + FMOD_RESULT F_API setSubSound (int index, Sound *subsound); + FMOD_RESULT F_API getSubSound (int index, Sound **subsound); + FMOD_RESULT F_API getSubSoundParent (Sound **parentsound); + FMOD_RESULT F_API setSubSoundSentence (int *subsoundlist, int numsubsounds); + FMOD_RESULT F_API getName (char *name, int namelen); + FMOD_RESULT F_API getLength (unsigned int *length, FMOD_TIMEUNIT lengthtype); + FMOD_RESULT F_API getFormat (FMOD_SOUND_TYPE *type, FMOD_SOUND_FORMAT *format, int *channels, int *bits); + FMOD_RESULT F_API getNumSubSounds (int *numsubsounds); + FMOD_RESULT F_API getNumTags (int *numtags, int *numtagsupdated); + FMOD_RESULT F_API getTag (const char *name, int index, FMOD_TAG *tag); + FMOD_RESULT F_API getOpenState (FMOD_OPENSTATE *openstate, unsigned int *percentbuffered, bool *starving, bool *diskbusy); + FMOD_RESULT F_API readData (void *buffer, unsigned int lenbytes, unsigned int *read); + FMOD_RESULT F_API seekData (unsigned int pcm); + + FMOD_RESULT F_API setSoundGroup (SoundGroup *soundgroup); + FMOD_RESULT F_API getSoundGroup (SoundGroup **soundgroup); + + // Synchronization point API. These points can come from markers embedded in wav files, and can also generate channel callbacks. + FMOD_RESULT F_API getNumSyncPoints (int *numsyncpoints); + FMOD_RESULT F_API getSyncPoint (int index, FMOD_SYNCPOINT **point); + FMOD_RESULT F_API getSyncPointInfo (FMOD_SYNCPOINT *point, char *name, int namelen, unsigned int *offset, FMOD_TIMEUNIT offsettype); + FMOD_RESULT F_API addSyncPoint (unsigned int offset, FMOD_TIMEUNIT offsettype, const char *name, FMOD_SYNCPOINT **point); + FMOD_RESULT F_API deleteSyncPoint (FMOD_SYNCPOINT *point); + + // Functions also in Channel class but here they are the 'default' to save having to change it in Channel all the time. + FMOD_RESULT F_API setMode (FMOD_MODE mode); + FMOD_RESULT F_API getMode (FMOD_MODE *mode); + FMOD_RESULT F_API setLoopCount (int loopcount); + FMOD_RESULT F_API getLoopCount (int *loopcount); + FMOD_RESULT F_API setLoopPoints (unsigned int loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int loopend, FMOD_TIMEUNIT loopendtype); + FMOD_RESULT F_API getLoopPoints (unsigned int *loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int *loopend, FMOD_TIMEUNIT loopendtype); + + // For MOD/S3M/XM/IT/MID sequenced formats only. + FMOD_RESULT F_API getMusicNumChannels (int *numchannels); + FMOD_RESULT F_API setMusicChannelVolume (int channel, float volume); + FMOD_RESULT F_API getMusicChannelVolume (int channel, float *volume); + FMOD_RESULT F_API setMusicSpeed (float speed); + FMOD_RESULT F_API getMusicSpeed (float *speed); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + FMOD_RESULT F_API getMemoryInfo (unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + }; + + /* + 'Channel' API. + */ + class Channel + { + private: + + Channel(); /* Constructor made private so user cannot statically instance a Channel class. + Appropriate Channel creation or retrieval function must be used. */ + public: + + FMOD_RESULT F_API getSystemObject (System **system); + + FMOD_RESULT F_API stop (); + FMOD_RESULT F_API setPaused (bool paused); + FMOD_RESULT F_API getPaused (bool *paused); + FMOD_RESULT F_API setVolume (float volume); + FMOD_RESULT F_API getVolume (float *volume); + FMOD_RESULT F_API setFrequency (float frequency); + FMOD_RESULT F_API getFrequency (float *frequency); + FMOD_RESULT F_API setPan (float pan); + FMOD_RESULT F_API getPan (float *pan); + FMOD_RESULT F_API setDelay (FMOD_DELAYTYPE delaytype, unsigned int delayhi, unsigned int delaylo); + FMOD_RESULT F_API getDelay (FMOD_DELAYTYPE delaytype, unsigned int *delayhi, unsigned int *delaylo); + FMOD_RESULT F_API setSpeakerMix (float frontleft, float frontright, float center, float lfe, float backleft, float backright, float sideleft, float sideright); + FMOD_RESULT F_API getSpeakerMix (float *frontleft, float *frontright, float *center, float *lfe, float *backleft, float *backright, float *sideleft, float *sideright); + FMOD_RESULT F_API setSpeakerLevels (FMOD_SPEAKER speaker, float *levels, int numlevels); + FMOD_RESULT F_API getSpeakerLevels (FMOD_SPEAKER speaker, float *levels, int numlevels); + FMOD_RESULT F_API setInputChannelMix (float *levels, int numlevels); + FMOD_RESULT F_API getInputChannelMix (float *levels, int numlevels); + FMOD_RESULT F_API setMute (bool mute); + FMOD_RESULT F_API getMute (bool *mute); + FMOD_RESULT F_API setPriority (int priority); + FMOD_RESULT F_API getPriority (int *priority); + FMOD_RESULT F_API setPosition (unsigned int position, FMOD_TIMEUNIT postype); + FMOD_RESULT F_API getPosition (unsigned int *position, FMOD_TIMEUNIT postype); + FMOD_RESULT F_API setReverbProperties (const FMOD_REVERB_CHANNELPROPERTIES *prop); + FMOD_RESULT F_API getReverbProperties (FMOD_REVERB_CHANNELPROPERTIES *prop); + FMOD_RESULT F_API setLowPassGain (float gain); + FMOD_RESULT F_API getLowPassGain (float *gain); + + FMOD_RESULT F_API setChannelGroup (ChannelGroup *channelgroup); + FMOD_RESULT F_API getChannelGroup (ChannelGroup **channelgroup); + FMOD_RESULT F_API setCallback (FMOD_CHANNEL_CALLBACK callback); + + // 3D functionality. + FMOD_RESULT F_API set3DAttributes (const FMOD_VECTOR *pos, const FMOD_VECTOR *vel); + FMOD_RESULT F_API get3DAttributes (FMOD_VECTOR *pos, FMOD_VECTOR *vel); + FMOD_RESULT F_API set3DMinMaxDistance (float mindistance, float maxdistance); + FMOD_RESULT F_API get3DMinMaxDistance (float *mindistance, float *maxdistance); + FMOD_RESULT F_API set3DConeSettings (float insideconeangle, float outsideconeangle, float outsidevolume); + FMOD_RESULT F_API get3DConeSettings (float *insideconeangle, float *outsideconeangle, float *outsidevolume); + FMOD_RESULT F_API set3DConeOrientation (FMOD_VECTOR *orientation); + FMOD_RESULT F_API get3DConeOrientation (FMOD_VECTOR *orientation); + FMOD_RESULT F_API set3DCustomRolloff (FMOD_VECTOR *points, int numpoints); + FMOD_RESULT F_API get3DCustomRolloff (FMOD_VECTOR **points, int *numpoints); + FMOD_RESULT F_API set3DOcclusion (float directocclusion, float reverbocclusion); + FMOD_RESULT F_API get3DOcclusion (float *directocclusion, float *reverbocclusion); + FMOD_RESULT F_API set3DSpread (float angle); + FMOD_RESULT F_API get3DSpread (float *angle); + FMOD_RESULT F_API set3DPanLevel (float level); + FMOD_RESULT F_API get3DPanLevel (float *level); + FMOD_RESULT F_API set3DDopplerLevel (float level); + FMOD_RESULT F_API get3DDopplerLevel (float *level); + FMOD_RESULT F_API set3DDistanceFilter (bool custom, float customLevel, float centerFreq); + FMOD_RESULT F_API get3DDistanceFilter (bool *custom, float *customLevel, float *centerFreq); + + // DSP functionality only for channels playing sounds created with FMOD_SOFTWARE. + FMOD_RESULT F_API getDSPHead (DSP **dsp); + FMOD_RESULT F_API addDSP (DSP *dsp, DSPConnection **connection); + + // Information only functions. + FMOD_RESULT F_API isPlaying (bool *isplaying); + FMOD_RESULT F_API isVirtual (bool *isvirtual); + FMOD_RESULT F_API getAudibility (float *audibility); + FMOD_RESULT F_API getCurrentSound (Sound **sound); + FMOD_RESULT F_API getSpectrum (float *spectrumarray, int numvalues, int channeloffset, FMOD_DSP_FFT_WINDOW windowtype); + FMOD_RESULT F_API getWaveData (float *wavearray, int numvalues, int channeloffset); + FMOD_RESULT F_API getIndex (int *index); + + // Functions also found in Sound class but here they can be set per channel. + FMOD_RESULT F_API setMode (FMOD_MODE mode); + FMOD_RESULT F_API getMode (FMOD_MODE *mode); + FMOD_RESULT F_API setLoopCount (int loopcount); + FMOD_RESULT F_API getLoopCount (int *loopcount); + FMOD_RESULT F_API setLoopPoints (unsigned int loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int loopend, FMOD_TIMEUNIT loopendtype); + FMOD_RESULT F_API getLoopPoints (unsigned int *loopstart, FMOD_TIMEUNIT loopstarttype, unsigned int *loopend, FMOD_TIMEUNIT loopendtype); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + FMOD_RESULT F_API getMemoryInfo (unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + }; + + /* + 'ChannelGroup' API + */ + class ChannelGroup + { + private: + + ChannelGroup(); /* Constructor made private so user cannot statically instance a ChannelGroup class. + Appropriate ChannelGroup creation or retrieval function must be used. */ + public: + + FMOD_RESULT F_API release (); + FMOD_RESULT F_API getSystemObject (System **system); + + // Channelgroup scale values. (changes attributes relative to the channels, doesn't overwrite them) + FMOD_RESULT F_API setVolume (float volume); + FMOD_RESULT F_API getVolume (float *volume); + FMOD_RESULT F_API setPitch (float pitch); + FMOD_RESULT F_API getPitch (float *pitch); + FMOD_RESULT F_API set3DOcclusion (float directocclusion, float reverbocclusion); + FMOD_RESULT F_API get3DOcclusion (float *directocclusion, float *reverbocclusion); + FMOD_RESULT F_API setPaused (bool paused); + FMOD_RESULT F_API getPaused (bool *paused); + FMOD_RESULT F_API setMute (bool mute); + FMOD_RESULT F_API getMute (bool *mute); + + // Channelgroup override values. (recursively overwrites whatever settings the channels had) + FMOD_RESULT F_API stop (); + FMOD_RESULT F_API overrideVolume (float volume); + FMOD_RESULT F_API overrideFrequency (float frequency); + FMOD_RESULT F_API overridePan (float pan); + FMOD_RESULT F_API overrideReverbProperties(const FMOD_REVERB_CHANNELPROPERTIES *prop); + FMOD_RESULT F_API override3DAttributes (const FMOD_VECTOR *pos, const FMOD_VECTOR *vel); + FMOD_RESULT F_API overrideSpeakerMix (float frontleft, float frontright, float center, float lfe, float backleft, float backright, float sideleft, float sideright); + + // Nested channel groups. + FMOD_RESULT F_API addGroup (ChannelGroup *group); + FMOD_RESULT F_API getNumGroups (int *numgroups); + FMOD_RESULT F_API getGroup (int index, ChannelGroup **group); + FMOD_RESULT F_API getParentGroup (ChannelGroup **group); + + // DSP functionality only for channel groups playing sounds created with FMOD_SOFTWARE. + FMOD_RESULT F_API getDSPHead (DSP **dsp); + FMOD_RESULT F_API addDSP (DSP *dsp, DSPConnection **connection); + + // Information only functions. + FMOD_RESULT F_API getName (char *name, int namelen); + FMOD_RESULT F_API getNumChannels (int *numchannels); + FMOD_RESULT F_API getChannel (int index, Channel **channel); + FMOD_RESULT F_API getSpectrum (float *spectrumarray, int numvalues, int channeloffset, FMOD_DSP_FFT_WINDOW windowtype); + FMOD_RESULT F_API getWaveData (float *wavearray, int numvalues, int channeloffset); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + FMOD_RESULT F_API getMemoryInfo (unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + }; + + /* + 'SoundGroup' API + */ + class SoundGroup + { + private: + + SoundGroup(); /* Constructor made private so user cannot statically instance a SoundGroup class. + Appropriate SoundGroup creation or retrieval function must be used. */ + public: + + FMOD_RESULT F_API release (); + FMOD_RESULT F_API getSystemObject (System **system); + + // SoundGroup control functions. + FMOD_RESULT F_API setMaxAudible (int maxaudible); + FMOD_RESULT F_API getMaxAudible (int *maxaudible); + FMOD_RESULT F_API setMaxAudibleBehavior (FMOD_SOUNDGROUP_BEHAVIOR behavior); + FMOD_RESULT F_API getMaxAudibleBehavior (FMOD_SOUNDGROUP_BEHAVIOR *behavior); + FMOD_RESULT F_API setMuteFadeSpeed (float speed); + FMOD_RESULT F_API getMuteFadeSpeed (float *speed); + FMOD_RESULT F_API setVolume (float volume); + FMOD_RESULT F_API getVolume (float *volume); + FMOD_RESULT F_API stop (); + + // Information only functions. + FMOD_RESULT F_API getName (char *name, int namelen); + FMOD_RESULT F_API getNumSounds (int *numsounds); + FMOD_RESULT F_API getSound (int index, Sound **sound); + FMOD_RESULT F_API getNumPlaying (int *numplaying); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + FMOD_RESULT F_API getMemoryInfo (unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + }; + + /* + 'DSP' API + */ + class DSP + { + private: + + DSP(); /* Constructor made private so user cannot statically instance a DSP class. + Appropriate DSP creation or retrieval function must be used. */ + public: + + FMOD_RESULT F_API release (); + FMOD_RESULT F_API getSystemObject (System **system); + + // Connection / disconnection / input and output enumeration. + FMOD_RESULT F_API addInput (DSP *target, DSPConnection **connection); + FMOD_RESULT F_API disconnectFrom (DSP *target); + FMOD_RESULT F_API disconnectAll (bool inputs, bool outputs); + FMOD_RESULT F_API remove (); + FMOD_RESULT F_API getNumInputs (int *numinputs); + FMOD_RESULT F_API getNumOutputs (int *numoutputs); + FMOD_RESULT F_API getInput (int index, DSP **input, DSPConnection **inputconnection); + FMOD_RESULT F_API getOutput (int index, DSP **output, DSPConnection **outputconnection); + + // DSP unit control. + FMOD_RESULT F_API setActive (bool active); + FMOD_RESULT F_API getActive (bool *active); + FMOD_RESULT F_API setBypass (bool bypass); + FMOD_RESULT F_API getBypass (bool *bypass); + FMOD_RESULT F_API setSpeakerActive (FMOD_SPEAKER speaker, bool active); + FMOD_RESULT F_API getSpeakerActive (FMOD_SPEAKER speaker, bool *active); + FMOD_RESULT F_API reset (); + + + // DSP parameter control. + FMOD_RESULT F_API setParameter (int index, float value); + FMOD_RESULT F_API getParameter (int index, float *value, char *valuestr, int valuestrlen); + FMOD_RESULT F_API getNumParameters (int *numparams); + FMOD_RESULT F_API getParameterInfo (int index, char *name, char *label, char *description, int descriptionlen, float *min, float *max); + FMOD_RESULT F_API showConfigDialog (void *hwnd, bool show); + + // DSP attributes. + FMOD_RESULT F_API getInfo (char *name, unsigned int *version, int *channels, int *configwidth, int *configheight); + FMOD_RESULT F_API getType (FMOD_DSP_TYPE *type); + FMOD_RESULT F_API setDefaults (float frequency, float volume, float pan, int priority); + FMOD_RESULT F_API getDefaults (float *frequency, float *volume, float *pan, int *priority); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + FMOD_RESULT F_API getMemoryInfo (unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + }; + + + /* + 'DSPConnection' API + */ + class DSPConnection + { + private: + + DSPConnection(); /* Constructor made private so user cannot statically instance a DSPConnection class. + Appropriate DSPConnection creation or retrieval function must be used. */ + + public: + + FMOD_RESULT F_API getInput (DSP **input); + FMOD_RESULT F_API getOutput (DSP **output); + FMOD_RESULT F_API setMix (float volume); + FMOD_RESULT F_API getMix (float *volume); + FMOD_RESULT F_API setLevels (FMOD_SPEAKER speaker, float *levels, int numlevels); + FMOD_RESULT F_API getLevels (FMOD_SPEAKER speaker, float *levels, int numlevels); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + FMOD_RESULT F_API getMemoryInfo (unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + }; + + + /* + 'Geometry' API + */ + class Geometry + { + private: + + Geometry(); /* Constructor made private so user cannot statically instance a Geometry class. + Appropriate Geometry creation or retrieval function must be used. */ + + public: + + FMOD_RESULT F_API release (); + + // Polygon manipulation. + FMOD_RESULT F_API addPolygon (float directocclusion, float reverbocclusion, bool doublesided, int numvertices, const FMOD_VECTOR *vertices, int *polygonindex); + FMOD_RESULT F_API getNumPolygons (int *numpolygons); + FMOD_RESULT F_API getMaxPolygons (int *maxpolygons, int *maxvertices); + FMOD_RESULT F_API getPolygonNumVertices (int index, int *numvertices); + FMOD_RESULT F_API setPolygonVertex (int index, int vertexindex, const FMOD_VECTOR *vertex); + FMOD_RESULT F_API getPolygonVertex (int index, int vertexindex, FMOD_VECTOR *vertex); + FMOD_RESULT F_API setPolygonAttributes (int index, float directocclusion, float reverbocclusion, bool doublesided); + FMOD_RESULT F_API getPolygonAttributes (int index, float *directocclusion, float *reverbocclusion, bool *doublesided); + + // Object manipulation. + FMOD_RESULT F_API setActive (bool active); + FMOD_RESULT F_API getActive (bool *active); + FMOD_RESULT F_API setRotation (const FMOD_VECTOR *forward, const FMOD_VECTOR *up); + FMOD_RESULT F_API getRotation (FMOD_VECTOR *forward, FMOD_VECTOR *up); + FMOD_RESULT F_API setPosition (const FMOD_VECTOR *position); + FMOD_RESULT F_API getPosition (FMOD_VECTOR *position); + FMOD_RESULT F_API setScale (const FMOD_VECTOR *scale); + FMOD_RESULT F_API getScale (FMOD_VECTOR *scale); + FMOD_RESULT F_API save (void *data, int *datasize); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + FMOD_RESULT F_API getMemoryInfo (unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + }; + + + /* + 'Reverb' API + */ + class Reverb + { + private: + + Reverb(); /* Constructor made private so user cannot statically instance a Reverb class. + Appropriate Reverb creation or retrieval function must be used. */ + + public: + + FMOD_RESULT F_API release (); + + // Reverb manipulation. + FMOD_RESULT F_API set3DAttributes (const FMOD_VECTOR *position, float mindistance, float maxdistance); + FMOD_RESULT F_API get3DAttributes (FMOD_VECTOR *position, float *mindistance,float *maxdistance); + FMOD_RESULT F_API setProperties (const FMOD_REVERB_PROPERTIES *properties); + FMOD_RESULT F_API getProperties (FMOD_REVERB_PROPERTIES *properties); + FMOD_RESULT F_API setActive (bool active); + FMOD_RESULT F_API getActive (bool *active); + + // Userdata set/get. + FMOD_RESULT F_API setUserData (void *userdata); + FMOD_RESULT F_API getUserData (void **userdata); + + FMOD_RESULT F_API getMemoryInfo (unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details); + }; +} + +#endif diff --git a/src/imports/fmodex/fmod_codec.h b/src/imports/fmodex/fmod_codec.h new file mode 100644 index 0000000..cd6c282 --- /dev/null +++ b/src/imports/fmodex/fmod_codec.h @@ -0,0 +1,159 @@ +/* ==================================================================================================== */ +/* FMOD Ex - codec development header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header if you are wanting to develop your own file format plugin to use with */ +/* FMOD's codec system. With this header you can make your own fileformat plugin that FMOD */ +/* can register and use. See the documentation and examples on how to make a working plugin. */ +/* */ +/* ==================================================================================================== */ + +#ifndef _FMOD_CODEC_H +#define _FMOD_CODEC_H + +typedef struct FMOD_CODEC_STATE FMOD_CODEC_STATE; +typedef struct FMOD_CODEC_WAVEFORMAT FMOD_CODEC_WAVEFORMAT; + +/* + Codec callbacks +*/ +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_OPENCALLBACK) (FMOD_CODEC_STATE *codec_state, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_CLOSECALLBACK) (FMOD_CODEC_STATE *codec_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_READCALLBACK) (FMOD_CODEC_STATE *codec_state, void *buffer, unsigned int sizebytes, unsigned int *bytesread); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_GETLENGTHCALLBACK) (FMOD_CODEC_STATE *codec_state, unsigned int *length, FMOD_TIMEUNIT lengthtype); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_SETPOSITIONCALLBACK) (FMOD_CODEC_STATE *codec_state, int subsound, unsigned int position, FMOD_TIMEUNIT postype); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_GETPOSITIONCALLBACK) (FMOD_CODEC_STATE *codec_state, unsigned int *position, FMOD_TIMEUNIT postype); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_SOUNDCREATECALLBACK) (FMOD_CODEC_STATE *codec_state, int subsound, FMOD_SOUND *sound); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_METADATACALLBACK) (FMOD_CODEC_STATE *codec_state, FMOD_TAGTYPE tagtype, char *name, void *data, unsigned int datalen, FMOD_TAGDATATYPE datatype, int unique); +typedef FMOD_RESULT (F_CALLBACK *FMOD_CODEC_GETWAVEFORMAT) (FMOD_CODEC_STATE *codec_state, int index, FMOD_CODEC_WAVEFORMAT *waveformat); + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + When creating a codec, declare one of these and provide the relevant callbacks and name for FMOD to use when it opens and reads a file. + + [REMARKS] + Members marked with [in] mean the variable can be written to. The user can set the value.
    + Members marked with [out] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_CODEC_STATE +] +*/ +typedef struct FMOD_CODEC_DESCRIPTION +{ + const char *name; /* [in] Name of the codec. */ + unsigned int version; /* [in] Plugin writer's version number. */ + int defaultasstream; /* [in] Tells FMOD to open the file as a stream when calling System::createSound, and not a static sample. Should normally be 0 (FALSE), because generally the user wants to decode the file into memory when using System::createSound. Mainly used for formats that decode for a very long time, or could use large amounts of memory when decoded. Usually sequenced formats such as mod/s3m/xm/it/midi fall into this category. It is mainly to stop users that don't know what they're doing from getting FMOD_ERR_MEMORY returned from createSound when they should have in fact called System::createStream or used FMOD_CREATESTREAM in System::createSound. */ + FMOD_TIMEUNIT timeunits; /* [in] When setposition codec is called, only these time formats will be passed to the codec. Use bitwise OR to accumulate different types. */ + FMOD_CODEC_OPENCALLBACK open; /* [in] Open callback for the codec for when FMOD tries to open a sound using this codec. */ + FMOD_CODEC_CLOSECALLBACK close; /* [in] Close callback for the codec for when FMOD tries to close a sound using this codec. */ + FMOD_CODEC_READCALLBACK read; /* [in] Read callback for the codec for when FMOD tries to read some data from the file to the destination format (specified in the open callback). */ + FMOD_CODEC_GETLENGTHCALLBACK getlength; /* [in] Callback to return the length of the song in whatever format required when Sound::getLength is called. */ + FMOD_CODEC_SETPOSITIONCALLBACK setposition; /* [in] Seek callback for the codec for when FMOD tries to seek within the file with Channel::setPosition. */ + FMOD_CODEC_GETPOSITIONCALLBACK getposition; /* [in] Tell callback for the codec for when FMOD tries to get the current position within the with Channel::getPosition. */ + FMOD_CODEC_SOUNDCREATECALLBACK soundcreate; /* [in] Sound creation callback for the codec when FMOD finishes creating the sound. (So the codec can set more parameters for the related created sound, ie loop points/mode or 3D attributes etc). */ + FMOD_CODEC_GETWAVEFORMAT getwaveformat; /* [in] Callback to tell FMOD about the waveformat of a particular subsound. This is to save memory, rather than saving 1000 FMOD_CODEC_WAVEFORMAT structures in the codec, the codec might have a more optimal way of storing this information. */ +} FMOD_CODEC_DESCRIPTION; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Set these values marked 'in' to tell fmod what sort of sound to create.
    + The format, channels and frequency tell FMOD what sort of hardware buffer to create when you initialize your code. So if you wrote an MP3 codec that decoded to stereo 16bit integer PCM, you would specify FMOD_SOUND_FORMAT_PCM16, and channels would be equal to 2.
    + Members marked as 'out' are set by fmod. Do not modify these. Simply specify 0 for these values when declaring the structure, FMOD will fill in the values for you after creation with the correct function pointers.
    + + [REMARKS] + Members marked with [in] mean the variable can be written to. The user can set the value.
    + Members marked with [out] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    +
    + An FMOD file might be from disk, memory or network, however the file may be opened by the user.
    +
    + 'numsubsounds' should be 0 if the file is a normal single sound stream or sound. Examples of this would be .WAV, .WMA, .MP3, .AIFF.
    + 'numsubsounds' should be 1+ if the file is a container format, and does not contain wav data itself. Examples of these types would be CDDA (multiple CD tracks), FSB (contains multiple sounds), MIDI/MOD/S3M/XM/IT (contain instruments).
    + The arrays of format, channel, frequency, length and blockalign should point to arrays of information based on how many subsounds are in the format. If the number of subsounds is 0 then it should point to 1 of each attribute, the same as if the number of subsounds was 1. If subsounds was 100 for example, each pointer should point to an array of 100 of each attribute.
    + When a sound has 1 or more subsounds, you must play the individual sounds specified by first obtaining the subsound with Sound::getSubSound. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_SOUND_FORMAT + FMOD_FILE_READCALLBACK + FMOD_FILE_SEEKCALLBACK + FMOD_CODEC_METADATACALLBACK + Sound::getSubSound + Sound::getNumSubSounds +] +*/ +struct FMOD_CODEC_WAVEFORMAT +{ + char name[256]; /* [in] Name of sound.*/ + FMOD_SOUND_FORMAT format; /* [in] Format for (decompressed) codec output, ie FMOD_SOUND_FORMAT_PCM8, FMOD_SOUND_FORMAT_PCM16.*/ + int channels; /* [in] Number of channels used by codec, ie mono = 1, stereo = 2. */ + int frequency; /* [in] Default frequency in hz of the codec, ie 44100. */ + unsigned int lengthbytes; /* [in] Length in bytes of the source data. */ + unsigned int lengthpcm; /* [in] Length in decompressed, PCM samples of the file, ie length in seconds * frequency. Used for Sound::getLength and for memory allocation of static decompressed sample data. */ + int blockalign; /* [in] Blockalign in decompressed, PCM samples of the optimal decode chunk size for this format. The codec read callback will be called in multiples of this value. */ + int loopstart; /* [in] Loopstart in decompressed, PCM samples of file. */ + int loopend; /* [in] Loopend in decompressed, PCM samples of file. */ + FMOD_MODE mode; /* [in] Mode to determine whether the sound should by default load as looping, non looping, 2d or 3d. */ + unsigned int channelmask; /* [in] Microsoft speaker channel mask, as defined for WAVEFORMATEXTENSIBLE and is found in ksmedia.h. Leave at 0 to play in natural speaker order. */ +}; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Codec plugin structure that is passed into each callback.
    +
    + Set these numsubsounds and waveformat members when called in FMOD_CODEC_OPENCALLBACK to tell fmod what sort of sound to create.
    +
    + The format, channels and frequency tell FMOD what sort of hardware buffer to create when you initialize your code. So if you wrote an MP3 codec that decoded to stereo 16bit integer PCM, you would specify FMOD_SOUND_FORMAT_PCM16, and channels would be equal to 2.
    + + [REMARKS] + Members marked with [in] mean the variable can be written to. The user can set the value.
    + Members marked with [out] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    +
    + An FMOD file might be from disk, memory or internet, however the file may be opened by the user.
    +
    + 'numsubsounds' should be 0 if the file is a normal single sound stream or sound. Examples of this would be .WAV, .WMA, .MP3, .AIFF.
    + 'numsubsounds' should be 1+ if the file is a container format, and does not contain wav data itself. Examples of these types would be CDDA (multiple CD tracks), FSB (contains multiple sounds), DLS (contain instruments).
    + The arrays of format, channel, frequency, length and blockalign should point to arrays of information based on how many subsounds are in the format. If the number of subsounds is 0 then it should point to 1 of each attribute, the same as if the number of subsounds was 1. If subsounds was 100 for example, each pointer should point to an array of 100 of each attribute.
    + When a sound has 1 or more subsounds, you must play the individual sounds specified by first obtaining the subsound with Sound::getSubSound. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_SOUND_FORMAT + FMOD_FILE_READCALLBACK + FMOD_FILE_SEEKCALLBACK + FMOD_CODEC_METADATACALLBACK + Sound::getSubSound + Sound::getNumSubSounds +] +*/ +struct FMOD_CODEC_STATE +{ + int numsubsounds; /* [in] Number of 'subsounds' in this sound. Anything other than 0 makes it a 'container' format (ie CDDA/DLS/FSB etc which contain 1 or more su bsounds). For most normal, single sound codec such as WAV/AIFF/MP3, this should be 0 as they are not a container for subsounds, they are the sound by itself. */ + FMOD_CODEC_WAVEFORMAT *waveformat; /* [in] Pointer to an array of format structures containing information about each sample. Can be 0 or NULL if FMOD_CODEC_GETWAVEFORMAT callback is preferred. The number of entries here must equal the number of subsounds defined in the subsound parameter. If numsubsounds = 0 then there should be 1 instance of this structure. */ + void *plugindata; /* [in] Plugin writer created data the codec author wants to attach to this object. */ + + void *filehandle; /* [out] This will return an internal FMOD file handle to use with the callbacks provided. */ + unsigned int filesize; /* [out] This will contain the size of the file in bytes. */ + FMOD_FILE_READCALLBACK fileread; /* [out] This will return a callable FMOD file function to use from codec. */ + FMOD_FILE_SEEKCALLBACK fileseek; /* [out] This will return a callable FMOD file function to use from codec. */ + FMOD_CODEC_METADATACALLBACK metadata; /* [out] This will return a callable FMOD metadata function to use from codec. */ +}; + +#endif + + diff --git a/src/imports/fmodex/fmod_dsp.h b/src/imports/fmodex/fmod_dsp.h new file mode 100644 index 0000000..0779c32 --- /dev/null +++ b/src/imports/fmodex/fmod_dsp.h @@ -0,0 +1,746 @@ +/* ========================================================================================== */ +/* FMOD Ex - DSP header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header if you are interested in delving deeper into the FMOD software mixing / */ +/* DSP engine. In this header you can find parameter structures for FMOD system reigstered */ +/* DSP effects and generators. */ +/* Also use this header if you are wanting to develop your own DSP plugin to use with FMOD's */ +/* dsp system. With this header you can make your own DSP plugin that FMOD can */ +/* register and use. See the documentation and examples on how to make a working plugin. */ +/* */ +/* ========================================================================================== */ + +#ifndef _FMOD_DSP_H +#define _FMOD_DSP_H + +typedef struct FMOD_DSP_STATE FMOD_DSP_STATE; + +/* + DSP callbacks +*/ +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_CREATECALLBACK) (FMOD_DSP_STATE *dsp_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_RELEASECALLBACK) (FMOD_DSP_STATE *dsp_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_RESETCALLBACK) (FMOD_DSP_STATE *dsp_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_READCALLBACK) (FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int outchannels); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SETPOSITIONCALLBACK)(FMOD_DSP_STATE *dsp_state, unsigned int pos); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_SETPARAMCALLBACK) (FMOD_DSP_STATE *dsp_state, int index, float value); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_GETPARAMCALLBACK) (FMOD_DSP_STATE *dsp_state, int index, float *value, char *valuestr); +typedef FMOD_RESULT (F_CALLBACK *FMOD_DSP_DIALOGCALLBACK) (FMOD_DSP_STATE *dsp_state, void *hwnd, int show); + +/* +[ENUM] +[ + [DESCRIPTION] + These definitions can be used for creating FMOD defined special effects or DSP units. + + [REMARKS] + To get them to be active, first create the unit, then add it somewhere into the DSP network, either at the front of the network near the soundcard unit to affect the global output (by using System::getDSPHead), or on a single channel (using Channel::getDSPHead). + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::createDSPByType +] +*/ +typedef enum +{ + FMOD_DSP_TYPE_UNKNOWN, /* This unit was created via a non FMOD plugin so has an unknown purpose. */ + FMOD_DSP_TYPE_MIXER, /* This unit does nothing but take inputs and mix them together then feed the result to the soundcard unit. */ + FMOD_DSP_TYPE_OSCILLATOR, /* This unit generates sine/square/saw/triangle or noise tones. */ + FMOD_DSP_TYPE_LOWPASS, /* This unit filters sound using a high quality, resonant lowpass filter algorithm but consumes more CPU time. */ + FMOD_DSP_TYPE_ITLOWPASS, /* This unit filters sound using a resonant lowpass filter algorithm that is used in Impulse Tracker, but with limited cutoff range (0 to 8060hz). */ + FMOD_DSP_TYPE_HIGHPASS, /* This unit filters sound using a resonant highpass filter algorithm. */ + FMOD_DSP_TYPE_ECHO, /* This unit produces an echo on the sound and fades out at the desired rate. */ + FMOD_DSP_TYPE_FLANGE, /* This unit produces a flange effect on the sound. */ + FMOD_DSP_TYPE_DISTORTION, /* This unit distorts the sound. */ + FMOD_DSP_TYPE_NORMALIZE, /* This unit normalizes or amplifies the sound to a certain level. */ + FMOD_DSP_TYPE_PARAMEQ, /* This unit attenuates or amplifies a selected frequency range. */ + FMOD_DSP_TYPE_PITCHSHIFT, /* This unit bends the pitch of a sound without changing the speed of playback. */ + FMOD_DSP_TYPE_CHORUS, /* This unit produces a chorus effect on the sound. */ + FMOD_DSP_TYPE_VSTPLUGIN, /* This unit allows the use of Steinberg VST plugins */ + FMOD_DSP_TYPE_WINAMPPLUGIN, /* This unit allows the use of Nullsoft Winamp plugins */ + FMOD_DSP_TYPE_ITECHO, /* This unit produces an echo on the sound and fades out at the desired rate as is used in Impulse Tracker. */ + FMOD_DSP_TYPE_COMPRESSOR, /* This unit implements dynamic compression (linked multichannel, wideband) */ + FMOD_DSP_TYPE_SFXREVERB, /* This unit implements SFX reverb */ + FMOD_DSP_TYPE_LOWPASS_SIMPLE, /* This unit filters sound using a simple lowpass with no resonance, but has flexible cutoff and is fast. */ + FMOD_DSP_TYPE_DELAY, /* This unit produces different delays on individual channels of the sound. */ + FMOD_DSP_TYPE_TREMOLO, /* This unit produces a tremolo / chopper effect on the sound. */ + FMOD_DSP_TYPE_LADSPAPLUGIN, /* This unit allows the use of LADSPA standard plugins. */ + FMOD_DSP_TYPE_HIGHPASS_SIMPLE, /* This unit filters sound using a simple highpass with no resonance, but has flexible cutoff and is fast. */ + FMOD_DSP_TYPE_HARDWARE = 1000, /* Offset that platform specific FMOD_HARDWARE DSPs will start at. */ + FMOD_DSP_TYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_DSP_TYPE; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure to define a parameter for a DSP unit. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::createDSP + DSP::setParameter +] +*/ +typedef struct FMOD_DSP_PARAMETERDESC +{ + float min; /* [w] Minimum value of the parameter (ie 100.0). */ + float max; /* [w] Maximum value of the parameter (ie 22050.0). */ + float defaultval; /* [w] Default value of parameter. */ + char name[16]; /* [w] Name of the parameter to be displayed (ie "Cutoff frequency"). */ + char label[16]; /* [w] Short string to be put next to value to denote the unit type (ie "hz"). */ + const char *description; /* [w] Description of the parameter to be displayed as a help item / tooltip for this parameter. */ +} FMOD_DSP_PARAMETERDESC; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + When creating a DSP unit, declare one of these and provide the relevant callbacks and name for FMOD to use when it creates and uses a DSP unit of this type. + + [REMARKS] +
    + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    +
    + IMPORTANT: The 'paramdesc' member should point to static memory, as FMOD references the data internally using the pointer provided. Do not store these parameter description structures on the stack, or in heap memory that is freed while FMOD is using it.
    +
    + There are 2 different ways to change a parameter in this architecture.
    + One is to use DSP::setParameter / DSP::getParameter. This is platform independant and is dynamic, so new unknown plugins can have their parameters enumerated and used.
    + The other is to use DSP::showConfigDialog. This is platform specific and requires a GUI, and will display a dialog box to configure the plugin.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::createDSP + FMOD_DSP_STATE +] +*/ +typedef struct FMOD_DSP_DESCRIPTION +{ + char name[32]; /* [w] Name of the unit to be displayed in the network. */ + unsigned int version; /* [w] Plugin writer's version number. */ + int channels; /* [w] Number of channels. Use 0 to process whatever number of channels is currently in the network. >0 would be mostly used if the unit is a unit that only generates sound. */ + FMOD_DSP_CREATECALLBACK create; /* [w] Create callback. This is called when DSP unit is created. Can be null. */ + FMOD_DSP_RELEASECALLBACK release; /* [w] Release callback. This is called just before the unit is freed so the user can do any cleanup needed for the unit. Can be null. */ + FMOD_DSP_RESETCALLBACK reset; /* [w] Reset callback. This is called by the user to reset any history buffers that may need resetting for a filter, when it is to be used or re-used for the first time to its initial clean state. Use to avoid clicks or artifacts. */ + FMOD_DSP_READCALLBACK read; /* [w] Read callback. Processing is done here. Can be null. */ + FMOD_DSP_SETPOSITIONCALLBACK setposition; /* [w] Set position callback. This is called if the unit wants to update its position info but not process data, or reset a cursor position internally if it is reading data from a certain source. Can be null. */ + + int numparameters; /* [w] Number of parameters used in this filter. The user finds this with DSP::getNumParameters */ + FMOD_DSP_PARAMETERDESC *paramdesc; /* [w] Variable number of parameter structures. */ + FMOD_DSP_SETPARAMCALLBACK setparameter; /* [w] This is called when the user calls DSP::setParameter. Can be null. */ + FMOD_DSP_GETPARAMCALLBACK getparameter; /* [w] This is called when the user calls DSP::getParameter. Can be null. */ + FMOD_DSP_DIALOGCALLBACK config; /* [w] This is called when the user calls DSP::showConfigDialog. Can be used to display a dialog to configure the filter. Can be null. */ + int configwidth; /* [w] Width of config dialog graphic if there is one. 0 otherwise.*/ + int configheight; /* [w] Height of config dialog graphic if there is one. 0 otherwise.*/ + void *userdata; /* [w] Optional. Specify 0 to ignore. This is user data to be attached to the DSP unit during creation. Access via DSP::getUserData. */ +} FMOD_DSP_DESCRIPTION; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + DSP plugin structure that is passed into each callback. + + [REMARKS] + Members marked with [r] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + Members marked with [w] mean the variable can be written to. The user can set the value.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_DSP_DESCRIPTION +] +*/ +struct FMOD_DSP_STATE +{ + FMOD_DSP *instance; /* [r] Handle to the DSP hand the user created. Not to be modified. C++ users cast to FMOD::DSP to use. */ + void *plugindata; /* [w] Plugin writer created data the output author wants to attach to this object. */ + unsigned short speakermask; /* [w] Specifies which speakers the DSP effect is active on */ +}; + + +/* + =================================================================================================== + + FMOD built in effect parameters. + Use DSP::setParameter with these enums for the 'index' parameter. + + =================================================================================================== +*/ + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_OSCILLATOR filter. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_OSCILLATOR_TYPE, /* Waveform type. 0 = sine. 1 = square. 2 = sawup. 3 = sawdown. 4 = triangle. 5 = noise. */ + FMOD_DSP_OSCILLATOR_RATE /* Frequency of the sinewave in hz. 1.0 to 22000.0. Default = 220.0. */ +} FMOD_DSP_OSCILLATOR; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_LOWPASS filter. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_LOWPASS_CUTOFF, /* Lowpass cutoff frequency in hz. 10.0 to 22000.0. Default = 5000.0. */ + FMOD_DSP_LOWPASS_RESONANCE /* Lowpass resonance Q value. 1.0 to 10.0. Default = 1.0. */ +} FMOD_DSP_LOWPASS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_ITLOWPASS filter.
    + This is different to the default FMOD_DSP_TYPE_ITLOWPASS filter in that it uses a different quality algorithm and is + the filter used to produce the correct sounding playback in .IT files.
    + FMOD Ex's .IT playback uses this filter.
    + + [REMARKS] + Note! This filter actually has a limited cutoff frequency below the specified maximum, due to its limited design, + so for a more open range filter use FMOD_DSP_LOWPASS or if you don't mind not having resonance, + FMOD_DSP_LOWPASS_SIMPLE.
    + The effective maximum cutoff is about 8060hz. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_ITLOWPASS_CUTOFF, /* Lowpass cutoff frequency in hz. 1.0 to 22000.0. Default = 5000.0/ */ + FMOD_DSP_ITLOWPASS_RESONANCE /* Lowpass resonance Q value. 0.0 to 127.0. Default = 1.0. */ +} FMOD_DSP_ITLOWPASS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_HIGHPASS filter. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_HIGHPASS_CUTOFF, /* Highpass cutoff frequency in hz. 1.0 to output 22000.0. Default = 5000.0. */ + FMOD_DSP_HIGHPASS_RESONANCE /* Highpass resonance Q value. 1.0 to 10.0. Default = 1.0. */ +} FMOD_DSP_HIGHPASS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_ECHO filter. + + [REMARKS] + Note. Every time the delay is changed, the plugin re-allocates the echo buffer. This means the echo will dissapear at that time while it refills its new buffer.
    + Larger echo delays result in larger amounts of memory allocated.
    +
    + 'maxchannels' also dictates the amount of memory allocated. By default, the maxchannels value is 0. If FMOD is set to stereo, the echo unit will allocate enough memory for 2 channels. If it is 5.1, it will allocate enough memory for a 6 channel echo, etc.
    + If the echo effect is only ever applied to the global mix (ie it was added with System::addDSP), then 0 is the value to set as it will be enough to handle all speaker modes.
    + When the echo is added to a channel (ie Channel::addDSP) then the channel count that comes in could be anything from 1 to 8 possibly. It is only in this case where you might want to increase the channel count above the output's channel count.
    + If a channel echo is set to a lower number than the sound's channel count that is coming in, it will not echo the sound.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_ECHO_DELAY, /* Echo delay in ms. 10 to 5000. Default = 500. */ + FMOD_DSP_ECHO_DECAYRATIO, /* Echo decay per delay. 0 to 1. 1.0 = No decay, 0.0 = total decay (ie simple 1 line delay). Default = 0.5. */ + FMOD_DSP_ECHO_MAXCHANNELS, /* Maximum channels supported. 0 to 16. 0 = same as fmod's default output polyphony, 1 = mono, 2 = stereo etc. See remarks for more. Default = 0. It is suggested to leave at 0! */ + FMOD_DSP_ECHO_DRYMIX, /* Volume of original signal to pass to output. 0.0 to 1.0. Default = 1.0. */ + FMOD_DSP_ECHO_WETMIX /* Volume of echo signal to pass to output. 0.0 to 1.0. Default = 1.0. */ +} FMOD_DSP_ECHO; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_DELAY filter. + + [REMARKS] + Note. Every time MaxDelay is changed, the plugin re-allocates the delay buffer. This means the delay will dissapear at that time while it refills its new buffer.
    + A larger MaxDelay results in larger amounts of memory allocated.
    + Channel delays above MaxDelay will be clipped to MaxDelay and the delay buffer will not be resized.
    +
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_DELAY_CH0, /* Channel #0 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH1, /* Channel #1 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH2, /* Channel #2 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH3, /* Channel #3 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH4, /* Channel #4 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH5, /* Channel #5 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH6, /* Channel #6 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH7, /* Channel #7 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH8, /* Channel #8 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH9, /* Channel #9 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH10, /* Channel #10 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH11, /* Channel #11 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH12, /* Channel #12 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH13, /* Channel #13 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH14, /* Channel #14 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_CH15, /* Channel #15 Delay in ms. 0 to 10000. Default = 0. */ + FMOD_DSP_DELAY_MAXDELAY /* Maximum delay in ms. 0 to 10000. Default = 10. */ +} FMOD_DSP_DELAY; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_FLANGE filter. + + [REMARKS] + Flange is an effect where the signal is played twice at the same time, and one copy slides back and forth creating a whooshing or flanging effect.
    + As there are 2 copies of the same signal, by default each signal is given 50% mix, so that the total is not louder than the original unaffected signal.
    +
    + Flange depth is a percentage of a 10ms shift from the original signal. Anything above 10ms is not considered flange because to the ear it begins to 'echo' so 10ms is the highest value possible.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_FLANGE_DRYMIX, /* Volume of original signal to pass to output. 0.0 to 1.0. Default = 0.45. */ + FMOD_DSP_FLANGE_WETMIX, /* Volume of flange signal to pass to output. 0.0 to 1.0. Default = 0.55. */ + FMOD_DSP_FLANGE_DEPTH, /* Flange depth (percentage of 40ms delay). 0.01 to 1.0. Default = 1.0. */ + FMOD_DSP_FLANGE_RATE /* Flange speed in hz. 0.0 to 20.0. Default = 0.1. */ +} FMOD_DSP_FLANGE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_TREMOLO filter. + + [REMARKS] + The tremolo effect varies the amplitude of a sound. Depending on the settings, this unit can produce a tremolo, chopper or auto-pan effect.
    +
    + The shape of the LFO (low freq. oscillator) can morphed between sine, triangle and sawtooth waves using the FMOD_DSP_TREMOLO_SHAPE and FMOD_DSP_TREMOLO_SKEW parameters.
    + FMOD_DSP_TREMOLO_DUTY and FMOD_DSP_TREMOLO_SQUARE are useful for a chopper-type effect where the first controls the on-time duration and second controls the flatness of the envelope.
    + FMOD_DSP_TREMOLO_SPREAD varies the LFO phase between channels to get an auto-pan effect. This works best with a sine shape LFO.
    + The LFO can be synchronized using the FMOD_DSP_TREMOLO_PHASE parameter which sets its instantaneous phase.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_TREMOLO_FREQUENCY, /* LFO frequency in Hz. 0.1 to 20. Default = 4. */ + FMOD_DSP_TREMOLO_DEPTH, /* Tremolo depth. 0 to 1. Default = 0. */ + FMOD_DSP_TREMOLO_SHAPE, /* LFO shape morph between triangle and sine. 0 to 1. Default = 0. */ + FMOD_DSP_TREMOLO_SKEW, /* Time-skewing of LFO cycle. -1 to 1. Default = 0. */ + FMOD_DSP_TREMOLO_DUTY, /* LFO on-time. 0 to 1. Default = 0.5. */ + FMOD_DSP_TREMOLO_SQUARE, /* Flatness of the LFO shape. 0 to 1. Default = 0. */ + FMOD_DSP_TREMOLO_PHASE, /* Instantaneous LFO phase. 0 to 1. Default = 0. */ + FMOD_DSP_TREMOLO_SPREAD /* Rotation / auto-pan effect. -1 to 1. Default = 0. */ +} FMOD_DSP_TREMOLO; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_DISTORTION filter. + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_DISTORTION_LEVEL /* Distortion value. 0.0 to 1.0. Default = 0.5. */ +} FMOD_DSP_DISTORTION; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_NORMALIZE filter. + + [REMARKS] + Normalize amplifies the sound based on the maximum peaks within the signal.
    + For example if the maximum peaks in the signal were 50% of the bandwidth, it would scale the whole sound by 2.
    + The lower threshold value makes the normalizer ignores peaks below a certain point, to avoid over-amplification if a loud signal suddenly came in, and also to avoid amplifying to maximum things like background hiss.
    +
    + Because FMOD is a realtime audio processor, it doesn't have the luxury of knowing the peak for the whole sound (ie it can't see into the future), so it has to process data as it comes in.
    + To avoid very sudden changes in volume level based on small samples of new data, fmod fades towards the desired amplification which makes for smooth gain control. The fadetime parameter can control this.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_NORMALIZE_FADETIME, /* Time to ramp the silence to full in ms. 0.0 to 20000.0. Default = 5000.0. */ + FMOD_DSP_NORMALIZE_THRESHHOLD, /* Lower volume range threshold to ignore. 0.0 to 1.0. Default = 0.1. Raise higher to stop amplification of very quiet signals. */ + FMOD_DSP_NORMALIZE_MAXAMP /* Maximum amplification allowed. 1.0 to 100000.0. Default = 20.0. 1.0 = no amplifaction, higher values allow more boost. */ +} FMOD_DSP_NORMALIZE; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_PARAMEQ filter. + + [REMARKS] + Parametric EQ is a bandpass filter that attenuates or amplifies a selected frequency and its neighbouring frequencies.
    +
    + To create a multi-band EQ create multiple FMOD_DSP_TYPE_PARAMEQ units and set each unit to different frequencies, for example 1000hz, 2000hz, 4000hz, 8000hz, 16000hz with a range of 1 octave each.
    +
    + When a frequency has its gain set to 1.0, the sound will be unaffected and represents the original signal exactly.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_PARAMEQ_CENTER, /* Frequency center. 20.0 to 22000.0. Default = 8000.0. */ + FMOD_DSP_PARAMEQ_BANDWIDTH, /* Octave range around the center frequency to filter. 0.2 to 5.0. Default = 1.0. */ + FMOD_DSP_PARAMEQ_GAIN /* Frequency Gain. 0.05 to 3.0. Default = 1.0. */ +} FMOD_DSP_PARAMEQ; + + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_PITCHSHIFT filter. + + [REMARKS] + This pitch shifting unit can be used to change the pitch of a sound without speeding it up or slowing it down.
    + It can also be used for time stretching or scaling, for example if the pitch was doubled, and the frequency of the sound was halved, the pitch of the sound would sound correct but it would be twice as slow.
    +
    + Warning! This filter is very computationally expensive! Similar to a vocoder, it requires several overlapping FFT and IFFT's to produce smooth output, and can require around 440mhz for 1 stereo 48khz signal using the default settings.
    + Reducing the signal to mono will half the cpu usage.
    + Reducing this will lower audio quality, but what settings to use are largely dependant on the sound being played. A noisy polyphonic signal will need higher fft size compared to a speaking voice for example.
    +
    + This pitch shifter is based on the pitch shifter code at http://www.dspdimension.com, written by Stephan M. Bernsee.
    + The original code is COPYRIGHT 1999-2003 Stephan M. Bernsee .
    +
    + 'maxchannels' dictates the amount of memory allocated. By default, the maxchannels value is 0. If FMOD is set to stereo, the pitch shift unit will allocate enough memory for 2 channels. If it is 5.1, it will allocate enough memory for a 6 channel pitch shift, etc.
    + If the pitch shift effect is only ever applied to the global mix (ie it was added with System::addDSP), then 0 is the value to set as it will be enough to handle all speaker modes.
    + When the pitch shift is added to a channel (ie Channel::addDSP) then the channel count that comes in could be anything from 1 to 8 possibly. It is only in this case where you might want to increase the channel count above the output's channel count.
    + If a channel pitch shift is set to a lower number than the sound's channel count that is coming in, it will not pitch shift the sound.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_PITCHSHIFT_PITCH, /* Pitch value. 0.5 to 2.0. Default = 1.0. 0.5 = one octave down, 2.0 = one octave up. 1.0 does not change the pitch. */ + FMOD_DSP_PITCHSHIFT_FFTSIZE, /* FFT window size. 256, 512, 1024, 2048, 4096. Default = 1024. Increase this to reduce 'smearing'. This effect is a warbling sound similar to when an mp3 is encoded at very low bitrates. */ + FMOD_DSP_PITCHSHIFT_OVERLAP, /* Removed. Do not use. FMOD now uses 4 overlaps and cannot be changed. */ + FMOD_DSP_PITCHSHIFT_MAXCHANNELS /* Maximum channels supported. 0 to 16. 0 = same as fmod's default output polyphony, 1 = mono, 2 = stereo etc. See remarks for more. Default = 0. It is suggested to leave at 0! */ +} FMOD_DSP_PITCHSHIFT; + + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_CHORUS filter. + + [REMARKS] + Chrous is an effect where the sound is more 'spacious' due to 1 to 3 versions of the sound being played along side the original signal but with the pitch of each copy modulating on a sine wave.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_CHORUS_DRYMIX, /* Volume of original signal to pass to output. 0.0 to 1.0. Default = 0.5. */ + FMOD_DSP_CHORUS_WETMIX1, /* Volume of 1st chorus tap. 0.0 to 1.0. Default = 0.5. */ + FMOD_DSP_CHORUS_WETMIX2, /* Volume of 2nd chorus tap. This tap is 90 degrees out of phase of the first tap. 0.0 to 1.0. Default = 0.5. */ + FMOD_DSP_CHORUS_WETMIX3, /* Volume of 3rd chorus tap. This tap is 90 degrees out of phase of the second tap. 0.0 to 1.0. Default = 0.5. */ + FMOD_DSP_CHORUS_DELAY, /* Chorus delay in ms. 0.1 to 100.0. Default = 40.0 ms. */ + FMOD_DSP_CHORUS_RATE, /* Chorus modulation rate in hz. 0.0 to 20.0. Default = 0.8 hz. */ + FMOD_DSP_CHORUS_DEPTH /* Chorus modulation depth. 0.0 to 1.0. Default = 0.03. */ +} FMOD_DSP_CHORUS; + + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_ITECHO filter.
    + This is effectively a software based echo filter that emulates the DirectX DMO echo effect. Impulse tracker files can support this, and FMOD will produce the effect on ANY platform, not just those that support DirectX effects!
    + + [REMARKS] + Note. Every time the delay is changed, the plugin re-allocates the echo buffer. This means the echo will dissapear at that time while it refills its new buffer.
    + Larger echo delays result in larger amounts of memory allocated.
    +
    + As this is a stereo filter made mainly for IT playback, it is targeted for stereo signals.
    + With mono signals only the FMOD_DSP_ITECHO_LEFTDELAY is used.
    + For multichannel signals (>2) there will be no echo on those channels.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::SetParameter + DSP::GetParameter + FMOD_DSP_TYPE + System::addDSP +] +*/ +typedef enum +{ + FMOD_DSP_ITECHO_WETDRYMIX, /* Ratio of wet (processed) signal to dry (unprocessed) signal. Must be in the range from 0.0 through 100.0 (all wet). The default value is 50. */ + FMOD_DSP_ITECHO_FEEDBACK, /* Percentage of output fed back into input, in the range from 0.0 through 100.0. The default value is 50. */ + FMOD_DSP_ITECHO_LEFTDELAY, /* Delay for left channel, in milliseconds, in the range from 1.0 through 2000.0. The default value is 500 ms. */ + FMOD_DSP_ITECHO_RIGHTDELAY, /* Delay for right channel, in milliseconds, in the range from 1.0 through 2000.0. The default value is 500 ms. */ + FMOD_DSP_ITECHO_PANDELAY /* Value that specifies whether to swap left and right delays with each successive echo. The default value is zero, meaning no swap. Possible values are defined as 0.0 (equivalent to FALSE) and 1.0 (equivalent to TRUE). CURRENTLY NOT SUPPORTED. */ +} FMOD_DSP_ITECHO; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_COMPRESSOR unit. + This is a simple linked multichannel software limiter that is uniform across the whole spectrum.
    + + [REMARKS] + The limiter is not guaranteed to catch every peak above the threshold level, + because it cannot apply gain reduction instantaneously - the time delay is + determined by the attack time. However setting the attack time too short will + distort the sound, so it is a compromise. High level peaks can be avoided by + using a short attack time - but not too short, and setting the threshold a few + decibels below the critical level. +
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::SetParameter + DSP::GetParameter + FMOD_DSP_TYPE + System::addDSP +] +*/ +typedef enum +{ + FMOD_DSP_COMPRESSOR_THRESHOLD, /* Threshold level (dB) in the range from -60 through 0. The default value is 0. */ + FMOD_DSP_COMPRESSOR_ATTACK, /* Gain reduction attack time (milliseconds), in the range from 10 through 200. The default value is 50. */ + FMOD_DSP_COMPRESSOR_RELEASE, /* Gain reduction release time (milliseconds), in the range from 20 through 1000. The default value is 50. */ + FMOD_DSP_COMPRESSOR_GAINMAKEUP /* Make-up gain (dB) applied after limiting, in the range from 0 through 30. The default value is 0. */ +} FMOD_DSP_COMPRESSOR; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_SFXREVERB unit.
    + + [REMARKS] + This is a high quality I3DL2 based reverb.
    + On top of the I3DL2 property set, "Dry Level" is also included to allow the dry mix to be changed.
    +
    + These properties can be set with presets in FMOD_REVERB_PRESETS. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::SetParameter + DSP::GetParameter + FMOD_DSP_TYPE + System::addDSP + FMOD_REVERB_PRESETS +] +*/ +typedef enum +{ + FMOD_DSP_SFXREVERB_DRYLEVEL, /* Dry Level : Mix level of dry signal in output in mB. Ranges from -10000.0 to 0.0. Default is 0. */ + FMOD_DSP_SFXREVERB_ROOM, /* Room : Room effect level at low frequencies in mB. Ranges from -10000.0 to 0.0. Default is -10000.0. */ + FMOD_DSP_SFXREVERB_ROOMHF, /* Room HF : Room effect high-frequency level re. low frequency level in mB. Ranges from -10000.0 to 0.0. Default is 0.0. */ + FMOD_DSP_SFXREVERB_DECAYTIME, /* Decay Time : Reverberation decay time at low-frequencies in seconds. Ranges from 0.1 to 20.0. Default is 1.0. */ + FMOD_DSP_SFXREVERB_DECAYHFRATIO, /* Decay HF Ratio : High-frequency to low-frequency decay time ratio. Ranges from 0.1 to 2.0. Default is 0.5. */ + FMOD_DSP_SFXREVERB_REFLECTIONSLEVEL, /* Reflections : Early reflections level relative to room effect in mB. Ranges from -10000.0 to 1000.0. Default is -10000.0. */ + FMOD_DSP_SFXREVERB_REFLECTIONSDELAY, /* Reflect Delay : Delay time of first reflection in seconds. Ranges from 0.0 to 0.3. Default is 0.02. */ + FMOD_DSP_SFXREVERB_REVERBLEVEL, /* Reverb : Late reverberation level relative to room effect in mB. Ranges from -10000.0 to 2000.0. Default is 0.0. */ + FMOD_DSP_SFXREVERB_REVERBDELAY, /* Reverb Delay : Late reverberation delay time relative to first reflection in seconds. Ranges from 0.0 to 0.1. Default is 0.04. */ + FMOD_DSP_SFXREVERB_DIFFUSION, /* Diffusion : Reverberation diffusion (echo density) in percent. Ranges from 0.0 to 100.0. Default is 100.0. */ + FMOD_DSP_SFXREVERB_DENSITY, /* Density : Reverberation density (modal density) in percent. Ranges from 0.0 to 100.0. Default is 100.0. */ + FMOD_DSP_SFXREVERB_HFREFERENCE, /* HF Reference : Reference high frequency in Hz. Ranges from 20.0 to 20000.0. Default is 5000.0. */ + FMOD_DSP_SFXREVERB_ROOMLF, /* Room LF : Room effect low-frequency level in mB. Ranges from -10000.0 to 0.0. Default is 0.0. */ + FMOD_DSP_SFXREVERB_LFREFERENCE /* LF Reference : Reference low-frequency in Hz. Ranges from 20.0 to 1000.0. Default is 250.0. */ +} FMOD_DSP_SFXREVERB; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_LOWPASS_SIMPLE filter.
    + This is a very simple low pass filter, based on two single-pole RC time-constant modules. + The emphasis is on speed rather than accuracy, so this should not be used for task requiring critical filtering.
    + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_LOWPASS_SIMPLE_CUTOFF /* Lowpass cutoff frequency in hz. 10.0 to 22000.0. Default = 5000.0 */ +} FMOD_DSP_LOWPASS_SIMPLE; + +/* +[ENUM] +[ + [DESCRIPTION] + Parameter types for the FMOD_DSP_TYPE_HIGHPASS_SIMPLE filter.
    + This is a very simple single-order high pass filter. + The emphasis is on speed rather than accuracy, so this should not be used for task requiring critical filtering.
    + + [REMARKS] + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + DSP::setParameter + DSP::getParameter + FMOD_DSP_TYPE +] +*/ +typedef enum +{ + FMOD_DSP_HIGHPASS_SIMPLE_CUTOFF /* Highpass cutoff frequency in hz. 10.0 to 22000.0. Default = 1000.0 */ +} FMOD_DSP_HIGHPASS_SIMPLE; + +#endif + diff --git a/src/imports/fmodex/fmod_errors.h b/src/imports/fmodex/fmod_errors.h new file mode 100644 index 0000000..36c8a6f --- /dev/null +++ b/src/imports/fmodex/fmod_errors.h @@ -0,0 +1,127 @@ +/*$ preserve start $*/ + +/* ============================================================================================== */ +/* FMOD Ex - Error string header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header if you want to store or display a string version / english explanation of */ +/* the FMOD error codes. */ +/* */ +/* ============================================================================================== */ + +#ifndef _FMOD_ERRORS_H +#define _FMOD_ERRORS_H + +#include "fmod.h" + +#ifdef __GNUC__ +static const char *FMOD_ErrorString(FMOD_RESULT errcode) __attribute__((unused)); +#endif + +static const char *FMOD_ErrorString(FMOD_RESULT errcode) +{ + switch (errcode) + { +/*$ preserve end $*/ + case FMOD_ERR_ALREADYLOCKED: return "Tried to call lock a second time before unlock was called. "; + case FMOD_ERR_BADCOMMAND: return "Tried to call a function on a data type that does not allow this type of functionality (ie calling Sound::lock on a streaming sound). "; + case FMOD_ERR_CDDA_DRIVERS: return "Neither NTSCSI nor ASPI could be initialised. "; + case FMOD_ERR_CDDA_INIT: return "An error occurred while initialising the CDDA subsystem. "; + case FMOD_ERR_CDDA_INVALID_DEVICE: return "Couldn't find the specified device. "; + case FMOD_ERR_CDDA_NOAUDIO: return "No audio tracks on the specified disc. "; + case FMOD_ERR_CDDA_NODEVICES: return "No CD/DVD devices were found. "; + case FMOD_ERR_CDDA_NODISC: return "No disc present in the specified drive. "; + case FMOD_ERR_CDDA_READ: return "A CDDA read error occurred. "; + case FMOD_ERR_CHANNEL_ALLOC: return "Error trying to allocate a channel. "; + case FMOD_ERR_CHANNEL_STOLEN: return "The specified channel has been reused to play another sound. "; + case FMOD_ERR_COM: return "A Win32 COM related error occured. COM failed to initialize or a QueryInterface failed meaning a Windows codec or driver was not installed properly. "; + case FMOD_ERR_DMA: return "DMA Failure. See debug output for more information. "; + case FMOD_ERR_DSP_CONNECTION: return "DSP connection error. Connection possibly caused a cyclic dependancy. Or tried to connect a tree too many units deep (more than 128). "; + case FMOD_ERR_DSP_FORMAT: return "DSP Format error. A DSP unit may have attempted to connect to this network with the wrong format. "; + case FMOD_ERR_DSP_NOTFOUND: return "DSP connection error. Couldn't find the DSP unit specified. "; + case FMOD_ERR_DSP_RUNNING: return "DSP error. Cannot perform this operation while the network is in the middle of running. This will most likely happen if a connection or disconnection is attempted in a DSP callback. "; + case FMOD_ERR_DSP_TOOMANYCONNECTIONS: return "DSP connection error. The unit being connected to or disconnected should only have 1 input or output. "; + case FMOD_ERR_EVENT_ALREADY_LOADED: return "The specified project or bank has already been loaded. Having multiple copies of the same project loaded simultaneously is forbidden. "; + case FMOD_ERR_EVENT_FAILED: return "An Event failed to be retrieved, most likely due to 'just fail' being specified as the max playbacks behavior. "; + case FMOD_ERR_EVENT_GUIDCONFLICT: return "An event with the same GUID already exists. "; + case FMOD_ERR_EVENT_INFOONLY: return "Can't execute this command on an EVENT_INFOONLY event. "; + case FMOD_ERR_EVENT_INTERNAL: return "An error occured that wasn't supposed to. See debug log for reason. "; + case FMOD_ERR_EVENT_MAXSTREAMS: return "Event failed because 'Max streams' was hit when FMOD_EVENT_INIT_FAIL_ON_MAXSTREAMS was specified. "; + case FMOD_ERR_EVENT_MISMATCH: return "FSB mismatches the FEV it was compiled with, the stream/sample mode it was meant to be created with was different, or the FEV was built for a different platform. "; + case FMOD_ERR_EVENT_NAMECONFLICT: return "A category with the same name already exists. "; + case FMOD_ERR_EVENT_NEEDSSIMPLE: return "Tried to call a function on a complex event that's only supported by simple events. "; + case FMOD_ERR_EVENT_NOTFOUND: return "The requested event, event group, event category or event property could not be found. "; + case FMOD_ERR_FILE_BAD: return "Error loading file. "; + case FMOD_ERR_FILE_COULDNOTSEEK: return "Couldn't perform seek operation. This is a limitation of the medium (ie netstreams) or the file format. "; + case FMOD_ERR_FILE_DISKEJECTED: return "Media was ejected while reading. "; + case FMOD_ERR_FILE_EOF: return "End of file unexpectedly reached while trying to read essential data (truncated data?). "; + case FMOD_ERR_FILE_NOTFOUND: return "File not found. "; + case FMOD_ERR_FILE_UNWANTED: return "Unwanted file access occured. "; + case FMOD_ERR_FORMAT: return "Unsupported file or audio format. "; + case FMOD_ERR_HTTP: return "A HTTP error occurred. This is a catch-all for HTTP errors not listed elsewhere. "; + case FMOD_ERR_HTTP_ACCESS: return "The specified resource requires authentication or is forbidden. "; + case FMOD_ERR_HTTP_PROXY_AUTH: return "Proxy authentication is required to access the specified resource. "; + case FMOD_ERR_HTTP_SERVER_ERROR: return "A HTTP server error occurred. "; + case FMOD_ERR_HTTP_TIMEOUT: return "The HTTP request timed out. "; + case FMOD_ERR_INITIALIZATION: return "FMOD was not initialized correctly to support this function. "; + case FMOD_ERR_INITIALIZED: return "Cannot call this command after System::init. "; + case FMOD_ERR_INTERNAL: return "An error occured that wasn't supposed to. Contact support. "; + case FMOD_ERR_INVALID_ADDRESS: return "On Xbox 360, this memory address passed to FMOD must be physical, (ie allocated with XPhysicalAlloc.) "; + case FMOD_ERR_INVALID_FLOAT: return "Value passed in was a NaN, Inf or denormalized float. "; + case FMOD_ERR_INVALID_HANDLE: return "An invalid object handle was used. "; + case FMOD_ERR_INVALID_PARAM: return "An invalid parameter was passed to this function. "; + case FMOD_ERR_INVALID_POSITION: return "An invalid seek position was passed to this function. "; + case FMOD_ERR_INVALID_SPEAKER: return "An invalid speaker was passed to this function based on the current speaker mode. "; + case FMOD_ERR_INVALID_SYNCPOINT: return "The syncpoint did not come from this sound handle. "; + case FMOD_ERR_INVALID_VECTOR: return "The vectors passed in are not unit length, or perpendicular. "; + case FMOD_ERR_MAXAUDIBLE: return "Reached maximum audible playback count for this sound's soundgroup. "; + case FMOD_ERR_MEMORY: return "Not enough memory or resources. "; + case FMOD_ERR_MEMORY_CANTPOINT: return "Can't use FMOD_OPENMEMORY_POINT on non PCM source data, or non mp3/xma/adpcm data if FMOD_CREATECOMPRESSEDSAMPLE was used. "; + case FMOD_ERR_MEMORY_SRAM: return "Not enough memory or resources on console sound ram. "; + case FMOD_ERR_MUSIC_NOCALLBACK: return "The music callback is required, but it has not been set. "; + case FMOD_ERR_MUSIC_NOTFOUND: return "The requested music entity could not be found. "; + case FMOD_ERR_MUSIC_UNINITIALIZED: return "Music system is not initialized probably because no music data is loaded. "; + case FMOD_ERR_NEEDS2D: return "Tried to call a command on a 3d sound when the command was meant for 2d sound. "; + case FMOD_ERR_NEEDS3D: return "Tried to call a command on a 2d sound when the command was meant for 3d sound. "; + case FMOD_ERR_NEEDSHARDWARE: return "Tried to use a feature that requires hardware support. (ie trying to play a GCADPCM compressed sound in software on Wii). "; + case FMOD_ERR_NEEDSSOFTWARE: return "Tried to use a feature that requires the software engine. Software engine has either been turned off, or command was executed on a hardware channel which does not support this feature. "; + case FMOD_ERR_NET_CONNECT: return "Couldn't connect to the specified host. "; + case FMOD_ERR_NET_SOCKET_ERROR: return "A socket error occurred. This is a catch-all for socket-related errors not listed elsewhere. "; + case FMOD_ERR_NET_URL: return "The specified URL couldn't be resolved. "; + case FMOD_ERR_NET_WOULD_BLOCK: return "Operation on a non-blocking socket could not complete immediately. "; + case FMOD_ERR_NOTREADY: return "Operation could not be performed because specified sound/DSP connection is not ready. "; + case FMOD_ERR_OUTPUT_ALLOCATED: return "Error initializing output device, but more specifically, the output device is already in use and cannot be reused. "; + case FMOD_ERR_OUTPUT_CREATEBUFFER: return "Error creating hardware sound buffer. "; + case FMOD_ERR_OUTPUT_DRIVERCALL: return "A call to a standard soundcard driver failed, which could possibly mean a bug in the driver or resources were missing or exhausted. "; + case FMOD_ERR_OUTPUT_ENUMERATION: return "Error enumerating the available driver list. List may be inconsistent due to a recent device addition or removal. "; + case FMOD_ERR_OUTPUT_FORMAT: return "Soundcard does not support the minimum features needed for this soundsystem (16bit stereo output). "; + case FMOD_ERR_OUTPUT_INIT: return "Error initializing output device. "; + case FMOD_ERR_OUTPUT_NOHARDWARE: return "FMOD_HARDWARE was specified but the sound card does not have the resources necessary to play it. "; + case FMOD_ERR_OUTPUT_NOSOFTWARE: return "Attempted to create a software sound but no software channels were specified in System::init. "; + case FMOD_ERR_PAN: return "Panning only works with mono or stereo sound sources. "; + case FMOD_ERR_PLUGIN: return "An unspecified error has been returned from a 3rd party plugin. "; + case FMOD_ERR_PLUGIN_INSTANCES: return "The number of allowed instances of a plugin has been exceeded. "; + case FMOD_ERR_PLUGIN_MISSING: return "A requested output, dsp unit type or codec was not available. "; + case FMOD_ERR_PLUGIN_RESOURCE: return "A resource that the plugin requires cannot be found. (ie the DLS file for MIDI playback or other DLLs that it needs to load) "; + case FMOD_ERR_PRELOADED: return "The specified sound is still in use by the event system, call EventSystem::unloadFSB before trying to release it. "; + case FMOD_ERR_PROGRAMMERSOUND: return "The specified sound is still in use by the event system, wait for the event which is using it finish with it. "; + case FMOD_ERR_RECORD: return "An error occured trying to initialize the recording device. "; + case FMOD_ERR_REVERB_INSTANCE: return "Specified instance in FMOD_REVERB_PROPERTIES couldn't be set. Most likely because it is an invalid instance number or the reverb doesnt exist. "; + case FMOD_ERR_SUBSOUNDS: return "The error occured because the sound referenced contains subsounds when it shouldn't have, or it doesn't contain subsounds when it should have. The operation may also not be able to be performed on a parent sound, or a parent sound was played without setting up a sentence first. "; + case FMOD_ERR_SUBSOUND_ALLOCATED: return "This subsound is already being used by another sound, you cannot have more than one parent to a sound. Null out the other parent's entry first. "; + case FMOD_ERR_SUBSOUND_CANTMOVE: return "Shared subsounds cannot be replaced or moved from their parent stream, such as when the parent stream is an FSB file. "; + case FMOD_ERR_SUBSOUND_MODE: return "The subsound's mode bits do not match with the parent sound's mode bits. See documentation for function that it was called with. "; + case FMOD_ERR_TAGNOTFOUND: return "The specified tag could not be found or there are no tags. "; + case FMOD_ERR_TOOMANYCHANNELS: return "The sound created exceeds the allowable input channel count. This can be increased using the maxinputchannels parameter in System::setSoftwareFormat. "; + case FMOD_ERR_UNIMPLEMENTED: return "Something in FMOD hasn't been implemented when it should be! contact support! "; + case FMOD_ERR_UNINITIALIZED: return "This command failed because System::init or System::setDriver was not called. "; + case FMOD_ERR_UNSUPPORTED: return "A command issued was not supported by this object. Possibly a plugin without certain callbacks specified. "; + case FMOD_ERR_UPDATE: return "An error caused by System::update occured. "; + case FMOD_ERR_VERSION: return "The version number of this file format is not supported. "; + case FMOD_OK: return "No errors."; + default : return "Unknown error."; +/*$ preserve start $*/ + }; +} + +#endif +/*$ preserve end $*/ diff --git a/src/imports/fmodex/fmod_memoryinfo.h b/src/imports/fmodex/fmod_memoryinfo.h new file mode 100644 index 0000000..0a40a28 --- /dev/null +++ b/src/imports/fmodex/fmod_memoryinfo.h @@ -0,0 +1,201 @@ +/* ============================================================================================= */ +/* FMOD Ex - Memory info header file. Copyright (c), Firelight Technologies Pty, Ltd. 2008-2016. */ +/* */ +/* Use this header if you are interested in getting detailed information on FMOD's memory */ +/* usage. See the documentation for more details. */ +/* */ +/* ============================================================================================= */ + +#ifndef _FMOD_MEMORYINFO_H +#define _FMOD_MEMORYINFO_H + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Structure to be filled with detailed memory usage information of an FMOD object + + [REMARKS] + Every public FMOD class has a getMemoryInfo function which can be used to get detailed information on what memory resources are associated with the object in question. + On return from getMemoryInfo, each member of this structure will hold the amount of memory used for its type in bytes.
    +
    + Members marked with [in] mean the user sets the value before passing it to the function.
    + Members marked with [out] mean FMOD sets the value to be used after the function exits.
    + + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + System::getMemoryInfo + EventSystem::getMemoryInfo + FMOD_MEMBITS + FMOD_EVENT_MEMBITS +] +*/ +typedef struct FMOD_MEMORY_USAGE_DETAILS +{ + unsigned int other; /* [out] Memory not accounted for by other types */ + unsigned int string; /* [out] String data */ + unsigned int system; /* [out] System object and various internals */ + unsigned int plugins; /* [out] Plugin objects and internals */ + unsigned int output; /* [out] Output module object and internals */ + unsigned int channel; /* [out] Channel related memory */ + unsigned int channelgroup; /* [out] ChannelGroup objects and internals */ + unsigned int codec; /* [out] Codecs allocated for streaming */ + unsigned int file; /* [out] File buffers and structures */ + unsigned int sound; /* [out] Sound objects and internals */ + unsigned int secondaryram; /* [out] Sound data stored in secondary RAM */ + unsigned int soundgroup; /* [out] SoundGroup objects and internals */ + unsigned int streambuffer; /* [out] Stream buffer memory */ + unsigned int dspconnection; /* [out] DSPConnection objects and internals */ + unsigned int dsp; /* [out] DSP implementation objects */ + unsigned int dspcodec; /* [out] Realtime file format decoding DSP objects */ + unsigned int profile; /* [out] Profiler memory footprint. */ + unsigned int recordbuffer; /* [out] Buffer used to store recorded data from microphone */ + unsigned int reverb; /* [out] Reverb implementation objects */ + unsigned int reverbchannelprops; /* [out] Reverb channel properties structs */ + unsigned int geometry; /* [out] Geometry objects and internals */ + unsigned int syncpoint; /* [out] Sync point memory. */ + unsigned int eventsystem; /* [out] EventSystem and various internals */ + unsigned int musicsystem; /* [out] MusicSystem and various internals */ + unsigned int fev; /* [out] Definition of objects contained in all loaded projects e.g. events, groups, categories */ + unsigned int memoryfsb; /* [out] Data loaded with preloadFSB */ + unsigned int eventproject; /* [out] EventProject objects and internals */ + unsigned int eventgroupi; /* [out] EventGroup objects and internals */ + unsigned int soundbankclass; /* [out] Objects used to manage wave banks */ + unsigned int soundbanklist; /* [out] Data used to manage lists of wave bank usage */ + unsigned int streaminstance; /* [out] Stream objects and internals */ + unsigned int sounddefclass; /* [out] Sound definition objects */ + unsigned int sounddefdefclass; /* [out] Sound definition static data objects */ + unsigned int sounddefpool; /* [out] Sound definition pool data */ + unsigned int reverbdef; /* [out] Reverb definition objects */ + unsigned int eventreverb; /* [out] Reverb objects */ + unsigned int userproperty; /* [out] User property objects */ + unsigned int eventinstance; /* [out] Event instance base objects */ + unsigned int eventinstance_complex; /* [out] Complex event instance objects */ + unsigned int eventinstance_simple; /* [out] Simple event instance objects */ + unsigned int eventinstance_layer; /* [out] Event layer instance objects */ + unsigned int eventinstance_sound; /* [out] Event sound instance objects */ + unsigned int eventenvelope; /* [out] Event envelope objects */ + unsigned int eventenvelopedef; /* [out] Event envelope definition objects */ + unsigned int eventparameter; /* [out] Event parameter objects */ + unsigned int eventcategory; /* [out] Event category objects */ + unsigned int eventenvelopepoint; /* [out] Event envelope point objects */ + unsigned int eventinstancepool; /* [out] Event instance pool memory */ +} FMOD_MEMORY_USAGE_DETAILS; + + +/* +[DEFINE] +[ + [NAME] + FMOD_MEMBITS + + [DESCRIPTION] + Bitfield used to request specific memory usage information from the getMemoryInfo function of every public FMOD Ex class. + Use with the "memorybits" parameter of getMemoryInfo to get information on FMOD Ex memory usage. + + [REMARKS] + Every public FMOD class has a getMemoryInfo function which can be used to get detailed information on what memory resources are associated with the object in question. + The FMOD_MEMBITS defines can be OR'd together to specify precisely what memory usage you'd like to get information on. See System::getMemoryInfo for an example. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_EVENT_MEMBITS + System::getMemoryInfo +] +*/ +#define FMOD_MEMBITS_OTHER 0x00000001 /* Memory not accounted for by other types */ +#define FMOD_MEMBITS_STRING 0x00000002 /* String data */ + +#define FMOD_MEMBITS_SYSTEM 0x00000004 /* System object and various internals */ +#define FMOD_MEMBITS_PLUGINS 0x00000008 /* Plugin objects and internals */ +#define FMOD_MEMBITS_OUTPUT 0x00000010 /* Output module object and internals */ +#define FMOD_MEMBITS_CHANNEL 0x00000020 /* Channel related memory */ +#define FMOD_MEMBITS_CHANNELGROUP 0x00000040 /* ChannelGroup objects and internals */ +#define FMOD_MEMBITS_CODEC 0x00000080 /* Codecs allocated for streaming */ +#define FMOD_MEMBITS_FILE 0x00000100 /* Codecs allocated for streaming */ +#define FMOD_MEMBITS_SOUND 0x00000200 /* Sound objects and internals */ +#define FMOD_MEMBITS_SOUND_SECONDARYRAM 0x00000400 /* Sound data stored in secondary RAM */ +#define FMOD_MEMBITS_SOUNDGROUP 0x00000800 /* SoundGroup objects and internals */ +#define FMOD_MEMBITS_STREAMBUFFER 0x00001000 /* Stream buffer memory */ +#define FMOD_MEMBITS_DSPCONNECTION 0x00002000 /* DSPConnection objects and internals */ +#define FMOD_MEMBITS_DSP 0x00004000 /* DSP implementation objects */ +#define FMOD_MEMBITS_DSPCODEC 0x00008000 /* Realtime file format decoding DSP objects */ +#define FMOD_MEMBITS_PROFILE 0x00010000 /* Profiler memory footprint. */ +#define FMOD_MEMBITS_RECORDBUFFER 0x00020000 /* Buffer used to store recorded data from microphone */ +#define FMOD_MEMBITS_REVERB 0x00040000 /* Reverb implementation objects */ +#define FMOD_MEMBITS_REVERBCHANNELPROPS 0x00080000 /* Reverb channel properties structs */ +#define FMOD_MEMBITS_GEOMETRY 0x00100000 /* Geometry objects and internals */ +#define FMOD_MEMBITS_SYNCPOINT 0x00200000 /* Sync point memory. */ +#define FMOD_MEMBITS_ALL 0xffffffff /* All memory used by FMOD Ex */ +/* [DEFINE_END] */ + + +/* +[DEFINE] +[ + [NAME] + FMOD_EVENT_MEMBITS + + [DESCRIPTION] + Bitfield used to request specific memory usage information from the getMemoryInfo function of every public FMOD Event System class. + Use with the "event_memorybits" parameter of getMemoryInfo to get information on FMOD Event System memory usage. + + [REMARKS] + Every public FMOD Event System class has a getMemoryInfo function which can be used to get detailed information on what memory resources are associated with the object in question. + The FMOD_EVENT_MEMBITS defines can be OR'd together to specify precisely what memory usage you'd like to get information on. See EventSystem::getMemoryInfo for an example. + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_MEMBITS + System::getMemoryInfo +] +*/ +#define FMOD_EVENT_MEMBITS_EVENTSYSTEM 0x00000001 /* EventSystem and various internals */ +#define FMOD_EVENT_MEMBITS_MUSICSYSTEM 0x00000002 /* MusicSystem and various internals */ +#define FMOD_EVENT_MEMBITS_FEV 0x00000004 /* Definition of objects contained in all loaded projects e.g. events, groups, categories */ +#define FMOD_EVENT_MEMBITS_MEMORYFSB 0x00000008 /* Data loaded with preloadFSB */ +#define FMOD_EVENT_MEMBITS_EVENTPROJECT 0x00000010 /* EventProject objects and internals */ +#define FMOD_EVENT_MEMBITS_EVENTGROUPI 0x00000020 /* EventGroup objects and internals */ +#define FMOD_EVENT_MEMBITS_SOUNDBANKCLASS 0x00000040 /* Objects used to manage wave banks */ +#define FMOD_EVENT_MEMBITS_SOUNDBANKLIST 0x00000080 /* Data used to manage lists of wave bank usage */ +#define FMOD_EVENT_MEMBITS_STREAMINSTANCE 0x00000100 /* Stream objects and internals */ +#define FMOD_EVENT_MEMBITS_SOUNDDEFCLASS 0x00000200 /* Sound definition objects */ +#define FMOD_EVENT_MEMBITS_SOUNDDEFDEFCLASS 0x00000400 /* Sound definition static data objects */ +#define FMOD_EVENT_MEMBITS_SOUNDDEFPOOL 0x00000800 /* Sound definition pool data */ +#define FMOD_EVENT_MEMBITS_REVERBDEF 0x00001000 /* Reverb definition objects */ +#define FMOD_EVENT_MEMBITS_EVENTREVERB 0x00002000 /* Reverb objects */ +#define FMOD_EVENT_MEMBITS_USERPROPERTY 0x00004000 /* User property objects */ +#define FMOD_EVENT_MEMBITS_EVENTINSTANCE 0x00008000 /* Event instance base objects */ +#define FMOD_EVENT_MEMBITS_EVENTINSTANCE_COMPLEX 0x00010000 /* Complex event instance objects */ +#define FMOD_EVENT_MEMBITS_EVENTINSTANCE_SIMPLE 0x00020000 /* Simple event instance objects */ +#define FMOD_EVENT_MEMBITS_EVENTINSTANCE_LAYER 0x00040000 /* Event layer instance objects */ +#define FMOD_EVENT_MEMBITS_EVENTINSTANCE_SOUND 0x00080000 /* Event sound instance objects */ +#define FMOD_EVENT_MEMBITS_EVENTENVELOPE 0x00100000 /* Event envelope objects */ +#define FMOD_EVENT_MEMBITS_EVENTENVELOPEDEF 0x00200000 /* Event envelope definition objects */ +#define FMOD_EVENT_MEMBITS_EVENTPARAMETER 0x00400000 /* Event parameter objects */ +#define FMOD_EVENT_MEMBITS_EVENTCATEGORY 0x00800000 /* Event category objects */ +#define FMOD_EVENT_MEMBITS_EVENTENVELOPEPOINT 0x01000000 /* Event envelope point object+s */ +#define FMOD_EVENT_MEMBITS_EVENTINSTANCEPOOL 0x02000000 /* Event instance pool data */ +#define FMOD_EVENT_MEMBITS_ALL 0xffffffff /* All memory used by FMOD Event System */ + +/* All event instance memory */ +#define FMOD_EVENT_MEMBITS_EVENTINSTANCE_GROUP (FMOD_EVENT_MEMBITS_EVENTINSTANCE | \ + FMOD_EVENT_MEMBITS_EVENTINSTANCE_COMPLEX | \ + FMOD_EVENT_MEMBITS_EVENTINSTANCE_SIMPLE | \ + FMOD_EVENT_MEMBITS_EVENTINSTANCE_LAYER | \ + FMOD_EVENT_MEMBITS_EVENTINSTANCE_SOUND) + +/* All sound definition memory */ +#define FMOD_EVENT_MEMBITS_SOUNDDEF_GROUP (FMOD_EVENT_MEMBITS_SOUNDDEFCLASS | \ + FMOD_EVENT_MEMBITS_SOUNDDEFDEFCLASS | \ + FMOD_EVENT_MEMBITS_SOUNDDEFPOOL) +/* [DEFINE_END] */ + +#endif diff --git a/src/imports/fmodex/fmod_output.h b/src/imports/fmodex/fmod_output.h new file mode 100644 index 0000000..33748a7 --- /dev/null +++ b/src/imports/fmodex/fmod_output.h @@ -0,0 +1,93 @@ +/* ==================================================================================================== */ +/* FMOD Ex - output development header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ +/* */ +/* Use this header if you are wanting to develop your own output plugin to use with */ +/* FMOD's output system. With this header you can make your own output plugin that FMOD */ +/* can register and use. See the documentation and examples on how to make a working plugin. */ +/* */ +/* ==================================================================================================== */ + +#ifndef _FMOD_OUTPUT_H +#define _FMOD_OUTPUT_H + +#include "fmod.h" + +typedef struct FMOD_OUTPUT_STATE FMOD_OUTPUT_STATE; + +/* + Output callbacks +*/ +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_GETNUMDRIVERSCALLBACK)(FMOD_OUTPUT_STATE *output_state, int *numdrivers); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_GETDRIVERNAMECALLBACK)(FMOD_OUTPUT_STATE *output_state, int id, char *name, int namelen); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_GETDRIVERCAPSCALLBACK)(FMOD_OUTPUT_STATE *output_state, int id, FMOD_CAPS *caps); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_INITCALLBACK) (FMOD_OUTPUT_STATE *output_state, int selecteddriver, FMOD_INITFLAGS flags, int *outputrate, int outputchannels, FMOD_SOUND_FORMAT *outputformat, int dspbufferlength, int dspnumbuffers, void *extradriverdata); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_CLOSECALLBACK) (FMOD_OUTPUT_STATE *output_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_UPDATECALLBACK) (FMOD_OUTPUT_STATE *output_state); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_GETHANDLECALLBACK) (FMOD_OUTPUT_STATE *output_state, void **handle); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_GETPOSITIONCALLBACK) (FMOD_OUTPUT_STATE *output_state, unsigned int *pcm); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_LOCKCALLBACK) (FMOD_OUTPUT_STATE *output_state, unsigned int offset, unsigned int length, void **ptr1, void **ptr2, unsigned int *len1, unsigned int *len2); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_UNLOCKCALLBACK) (FMOD_OUTPUT_STATE *output_state, void *ptr1, void *ptr2, unsigned int len1, unsigned int len2); +typedef FMOD_RESULT (F_CALLBACK *FMOD_OUTPUT_READFROMMIXER) (FMOD_OUTPUT_STATE *output_state, void *buffer, unsigned int length); + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + When creating an output, declare one of these and provide the relevant callbacks and name for FMOD to use when it opens and reads a file of this type. + + [REMARKS] + Members marked with [in] mean the variable can be written to. The user can set the value.
    + Members marked with [out] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_OUTPUT_STATE +] +*/ +typedef struct FMOD_OUTPUT_DESCRIPTION +{ + const char *name; /* [in] Name of the output. */ + unsigned int version; /* [in] Plugin writer's version number. */ + int polling; /* [in] If TRUE (non zero), this tells FMOD to start a thread and call getposition / lock / unlock for feeding data. If 0, the output is probably callback based, so all the plugin needs to do is call readfrommixer to the appropriate pointer. */ + FMOD_OUTPUT_GETNUMDRIVERSCALLBACK getnumdrivers; /* [in] For sound device enumeration. This callback is to give System::getNumDrivers somthing to return. */ + FMOD_OUTPUT_GETDRIVERNAMECALLBACK getdrivername; /* [in] For sound device enumeration. This callback is to give System::getDriverName somthing to return. */ + FMOD_OUTPUT_GETDRIVERCAPSCALLBACK getdrivercaps; /* [in] For sound device enumeration. This callback is to give System::getDriverCaps somthing to return. */ + FMOD_OUTPUT_INITCALLBACK init; /* [in] Initialization function for the output device. This is called from System::init. */ + FMOD_OUTPUT_CLOSECALLBACK close; /* [in] Cleanup / close down function for the output device. This is called from System::close. */ + FMOD_OUTPUT_UPDATECALLBACK update; /* [in] Update function that is called once a frame by the user. This is called from System::update. */ + FMOD_OUTPUT_GETHANDLECALLBACK gethandle; /* [in] This is called from System::getOutputHandle. This is just to return a pointer to the internal system device object that the system may be using.*/ + FMOD_OUTPUT_GETPOSITIONCALLBACK getposition; /* [in] This is called from the FMOD software mixer thread if 'polling' = true. This returns a position value in samples so that FMOD knows where and when to fill its buffer. */ + FMOD_OUTPUT_LOCKCALLBACK lock; /* [in] This is called from the FMOD software mixer thread if 'polling' = true. This function provides a pointer to data that FMOD can write to when software mixing. */ + FMOD_OUTPUT_UNLOCKCALLBACK unlock; /* [in] This is called from the FMOD software mixer thread if 'polling' = true. This optional function accepts the data that has been mixed and copies it or does whatever it needs to before sending it to the hardware. */ +} FMOD_OUTPUT_DESCRIPTION; + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Output plugin structure that is passed into each callback. + + [REMARKS] + Members marked with [in] mean the variable can be written to. The user can set the value.
    + Members marked with [out] mean the variable is modified by FMOD and is for reading purposes only. Do not change this value.
    + + [PLATFORMS] + Win32, Win64, Linux, Linux64, Macintosh, Xbox360, PlayStation Portable, PlayStation 3, Wii, iPhone, 3GS, NGP, Android + + [SEE_ALSO] + FMOD_OUTPUT_DESCRIPTION +] +*/ +struct FMOD_OUTPUT_STATE +{ + void *plugindata; /* [in] Plugin writer created data the output author wants to attach to this object. */ + FMOD_OUTPUT_READFROMMIXER readfrommixer; /* [out] Function to update mixer and write the result to the provided pointer. Used from callback based output only. Polling based output uses lock/unlock/getposition. */ +}; + +#endif + + diff --git a/src/imports/fmodex/fmodlinux.h b/src/imports/fmodex/fmodlinux.h new file mode 100644 index 0000000..22bfc6a --- /dev/null +++ b/src/imports/fmodex/fmodlinux.h @@ -0,0 +1,36 @@ +/* =========================================================================================== */ +/* FMOD Linux Specific header file. Copyright (c), Firelight Technologies Pty, Ltd. 2005-2016. */ +/* =========================================================================================== */ + +#ifndef _FMODLINUX_H +#define _FMODLINUX_H + +#include "fmod.h" + + +/* +[STRUCTURE] +[ + [DESCRIPTION] + Use this structure with System::init to set the information required for linux + initialisation. + + Pass this structure in as the "extradriverdata" parameter in System::init. + + [REMARKS] + + [PLATFORMS] + Linux, Linux64 + + [SEE_ALSO] + System::init +] +*/ +typedef struct FMOD_LINUX_EXTRADRIVERDATA +{ + const char *output_driver_arguments; /* ALSA Only - Arguments to apply to the selected output driver */ + const char *record_driver_arguments; /* ALSA Only - Arguments to apply to the selected input (record) driver */ +} FMOD_LINUX_EXTRADRIVERDATA; + +#endif + diff --git a/src/main/asset/f2/lib/usb-rank.c b/src/main/asset/f2/lib/usb-rank.c new file mode 100644 index 0000000..ebb90d7 --- /dev/null +++ b/src/main/asset/f2/lib/usb-rank.c @@ -0,0 +1,49 @@ +#include "util/adler32.h" + +#include "usb-rank.h" + +#define ENC_DEC_INIT_SEED 0xEBADA1 + +struct asset_f2_usb_rank* asset_f2_usb_rank_new(void) +{ + // TODO + return malloc(sizeof(struct asset_f2_usb_rank)); +} + +void asset_f2_usb_rank_finalize(struct asset_f2_usb_rank* rank) +{ + /* don't include the checksum field */ + /* + rank->header.adler32 = util_adler32_calc(rank->header.adler_seed, + &rank->header.adler_seed, + sizeof(struct f2_profile_rank) - sizeof(uint32_t)); + */ +} + +char* asset_f2_usb_rank_to_string(const struct asset_f2_usb_rank* rank) +{ + // TODO + return ""; +} + +void asset_f2_usb_rank_decrypt(uint8_t* buf, size_t len) +{ + uint32_t seed = ENC_DEC_INIT_SEED; + + for (int i = 0; i < len; i++){ + uint8_t smbuff = buf[i]; + buf[i] ^= (seed >> 8) & 0xff ; + seed = 0x68993 * (smbuff + seed) + 0x4FDCF; + } +} + +void asset_f2_usb_rank_encrypt(uint8_t* buf, size_t len) +{ + uint32_t seed = ENC_DEC_INIT_SEED; + + for (size_t i = 0; i < len; i++){ + uint8_t smbuff = buf[i] ^ ((seed >> 8) & 0xff); + buf[i] = smbuff ; + seed = 0x68993 * (smbuff + seed) + 0x4FDCF; + } +} \ No newline at end of file diff --git a/src/main/asset/f2/lib/usb-rank.h b/src/main/asset/f2/lib/usb-rank.h new file mode 100644 index 0000000..9eb279c --- /dev/null +++ b/src/main/asset/f2/lib/usb-rank.h @@ -0,0 +1,21 @@ +#ifndef ASSET_F2_USB_RANK_H +#define ASSET_F2_USB_RANK_H + +#include + +struct asset_f2_usb_rank { + // TODO +} __attribute__((__packed__)); + +struct asset_f2_usb_rank* asset_f2_usb_rank_new(void); + +// update checksum and prepare profile to get encrypted +void asset_f2_usb_rank_finalize(struct asset_f2_usb_rank* rank); + +char* asset_f2_usb_rank_to_string(const struct asset_f2_usb_rank* rank); + +void asset_f2_usb_rank_decrypt(uint8_t* buf, size_t len); + +void asset_f2_usb_rank_encrypt(uint8_t* buf, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/asset/f2/lib/usb-save.c b/src/main/asset/f2/lib/usb-save.c new file mode 100644 index 0000000..5fd8b5e --- /dev/null +++ b/src/main/asset/f2/lib/usb-save.c @@ -0,0 +1,47 @@ +#include "util/adler32.h" + +#include "usb-save.h" + +#define ENC_DEC_INIT_SEED 0xEBADA1 + +struct asset_f2_usb_save* asset_f2_usb_save_new(void) +{ + // TODO + return malloc(sizeof(struct asset_f2_usb_save)); +} + +void asset_f2_usb_save_finalize(struct asset_f2_usb_save* save) +{ + /* don't include the checksum field */ + save->header.adler32 = util_adler32_calc(save->header.adler_seed, + &save->header.adler_seed, + sizeof(struct asset_f2_usb_save) - sizeof(uint32_t)); +} + +char* asset_f2_usb_save_to_string(const struct asset_f2_usb_save* save) +{ + // TODO + return ""; +} + +void asset_f2_usb_save_decrypt(uint8_t* buf, size_t len) +{ + uint32_t seed = ENC_DEC_INIT_SEED; + + for (int i = 0; i < len; i++){ + uint8_t smbuff = buf[i]; + buf[i] ^= (seed >> 8) & 0xff ; + seed = 0x68993 * (smbuff + seed) + 0x4FDCF; + } +} + +void asset_f2_usb_save_encrypt(uint8_t* buf, size_t len) +{ + uint32_t seed = ENC_DEC_INIT_SEED; + + for (size_t i = 0; i < len; i++){ + uint8_t smbuff = buf[i] ^ (seed >> 8) & 0xff; + buf[i] = smbuff ; + seed = 0x68993 * (smbuff + seed) + 0x4FDCF; + } +} \ No newline at end of file diff --git a/src/main/asset/f2/lib/usb-save.h b/src/main/asset/f2/lib/usb-save.h new file mode 100644 index 0000000..0bc84fc --- /dev/null +++ b/src/main/asset/f2/lib/usb-save.h @@ -0,0 +1,105 @@ +#ifndef ASSET_F2_USB_SAVE_H +#define ASSET_F2_USB_SAVE_H + +#include +#include + +struct asset_f2_usb_save_machine_stats { + uint32_t beginner_mode_play_count; + uint32_t maniac_mode_play_count; + uint32_t unkn; + uint32_t unkn2; + uint32_t battle_mode_play_count; + uint32_t mission_zone_play_count; + uint32_t skill_zone_play_count; + uint32_t coop_mission_play_count; +} __attribute__((__packed__)); + +struct asset_f2_usb_save_machine_info { + /* NULL terminated */ + char version[8]; + /* NULL terminated */ + char cpu[128]; + /* NULL terminated */ + char motherboard[128]; + /* NULL terminated */ + char gfxcard[128]; + /* NULL terminated */ + char hdd[32]; + uint32_t total_ram_bytes; + uint32_t haspkey_id; +} __attribute__((__packed__)); + +struct asset_f2_usb_save_header { + uint32_t adler32; + uint32_t adler_seed; + /* NULL terminated */ + char player_id[8]; + uint32_t region; + uint32_t avatar_id; + uint32_t level; + float total_calories; + float total_v02; + uint64_t num_running_steps; + uint64_t num_games_played; + uint64_t exp; + uint64_t arcade_score; + uint32_t num_missions_completed; + uint32_t num_coop_missions_completed; + uint32_t battle_count_wins; + uint32_t battle_count_loses; + uint32_t battle_count_draws; + /* TODO: modifier stuff? */ + uint64_t unlock; + uint32_t unlock_seed; +} __attribute__((__packed__)); + +struct asset_f2_usb_save_machine_highscore_entry { + uint32_t song_id; + uint8_t difficulty; + uint8_t mode; + uint8_t grade; + uint8_t clear_state; + uint32_t score_total; + uint32_t play_count; + uint32_t unkn; + /* NULL terminated */ + char player_id[8]; + uint32_t unkn2; + uint32_t null_padding; +} __attribute__((__packed__)); + +struct asset_f2_usb_save_highscore_entry { + uint32_t song_id; + uint8_t difficulty; + uint8_t mode; + uint8_t grade; + uint8_t clear_and_unlock_state; + uint32_t score_total; + uint32_t play_count; + uint32_t null_padding; +} __attribute__((__packed__)); + +struct asset_f2_usb_save { + /* start offset: 0x00 */ + struct asset_f2_usb_save_header header; + /* start offset: 0x120 */ + struct asset_f2_usb_save_machine_info machine_info; + struct asset_f2_usb_save_machine_stats machine_stats; + /* start offset: 0x2F0 */ + + // TODO continue +} __attribute__((__packed__)); + +struct asset_f2_usb_save* asset_f2_usb_save_new(void); + +// update checksum and prepare profile to get encrypted +void asset_f2_usb_save_finalize(struct asset_f2_usb_save* save); + +char* asset_f2_usb_save_to_string(const struct asset_f2_usb_save* save); + +void asset_f2_usb_save_decrypt(uint8_t* buf, size_t len); + +void asset_f2_usb_save_encrypt(uint8_t* buf, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/asset/f2/lib/util.c b/src/main/asset/f2/lib/util.c new file mode 100644 index 0000000..3d69017 --- /dev/null +++ b/src/main/asset/f2/lib/util.c @@ -0,0 +1,96 @@ +#define LOG_MODULE "asset-f2-util" + +#include + +#include "util/fs.h" +#include "util/log.h" + +#include "util.h" + +struct asset_f2_usb_rank* asset_f2_util_rank_usb_load_from_file(const char* path, bool encrypted) +{ + struct asset_f2_usb_rank* rank; + size_t size; + + if (!util_file_load(path, (void**) &rank, &size, false)) { + log_error("Loading f2 rank file %s failed", path); + return NULL; + } + + if (size != sizeof(struct asset_f2_usb_rank)) { + log_error("Invalid size of f2 rank file %s: %d != %d", path, size, + sizeof(struct asset_f2_usb_rank)); + free(rank); + return NULL; + } + + if (encrypted) { + asset_f2_usb_rank_decrypt((uint8_t*) rank, size); + } + + return rank; +} + +bool asset_f2_util_rank_usb_save_to_file(const char* path, const struct asset_f2_usb_rank* rank, bool encrypt) +{ + struct asset_f2_usb_rank rank_enc; + + memcpy(&rank_enc, rank, sizeof(struct asset_f2_usb_rank)); + + if (encrypt) { + asset_f2_usb_rank_encrypt((uint8_t*) &rank_enc, + sizeof(struct asset_f2_usb_rank)); + } + + if (!util_file_save(path, (void**) &rank_enc, + sizeof(struct asset_f2_usb_rank))) { + log_error("Storing f2 rank data to %s failed", path); + return false; + } + + return true; +} + +struct asset_f2_usb_save* asset_f2_util_usb_save_load_from_file(const char* path, bool encrypted) +{ + struct asset_f2_usb_save* save; + size_t size; + + if (!util_file_load(path, (void**) &save, &size, false)) { + log_error("Loading f2 save file %s failed", path); + return NULL; + } + + if (size != sizeof(struct asset_f2_usb_save)) { + log_error("Invalid size of f2 save file %s: %d != %d", path, size, + sizeof(struct asset_f2_usb_save)); + free(save); + return NULL; + } + + if (encrypted) { + asset_f2_usb_save_decrypt((uint8_t*) save, size); + } + + return save; +} + +bool asset_f2_util_usb_save_save_to_file(const char* path, const struct asset_f2_usb_save* save, bool encrypt) +{ + struct asset_f2_usb_save save_enc; + + memcpy(&save_enc, save, sizeof(struct asset_f2_usb_save)); + + if (encrypt) { + asset_f2_usb_save_encrypt((uint8_t*) &save_enc, + sizeof(struct asset_f2_usb_save)); + } + + if (!util_file_save(path, (const void*) &save_enc, + sizeof(struct asset_f2_usb_save))) { + log_error("Storing f2 save data to %s failed", path); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/main/asset/f2/lib/util.h b/src/main/asset/f2/lib/util.h new file mode 100644 index 0000000..7870793 --- /dev/null +++ b/src/main/asset/f2/lib/util.h @@ -0,0 +1,21 @@ +#ifndef ASSET_F2_UTIL_H +#define ASSET_F2_UTIL_H + +#include + +#include "usb-rank.h" +#include "usb-save.h" + +// load from file and decrypt, returns decrypted struct +struct asset_f2_usb_rank* asset_f2_util_usb_rank_load_from_file(const char* path, bool encrypted); + +// takes decrypted rank data, encrypts it and writes it to file +bool asset_f2_util_usb_rank_save_to_file(const char* path, const struct asset_f2_usb_rank* rank, bool encrypt); + +// load from file and decrypt, returns decrypted struct +struct asset_f2_usb_save* asset_f2_util_usb_save_load_from_file(const char* path, bool encrypted); + +// takes decrypted save data, encrypts it and writes it to file +bool asset_f2_util_usb_save_save_to_file(const char* path, const struct asset_f2_usb_save* save, bool encrypt); + +#endif \ No newline at end of file diff --git a/src/main/asset/fex/lib/usb-rank.c b/src/main/asset/fex/lib/usb-rank.c new file mode 100644 index 0000000..4d0515c --- /dev/null +++ b/src/main/asset/fex/lib/usb-rank.c @@ -0,0 +1,32 @@ +#define LOG_MODULE "fex-profile-rank" + +#include "usb-rank.h" + +struct asset_fex_usb_rank* asset_fex_usb_rank_new(void) +{ + return malloc(sizeof(struct asset_fex_usb_rank)); +} + +void asset_fex_usb_rank_finalize(struct asset_fex_usb_rank* rank) +{ + +} + +char* asset_fex_usb_rank_to_string(const struct asset_fex_usb_rank* rank) +{ + return malloc(0); +} + +void asset_fex_usb_rank_decrypt(uint8_t* buf, size_t len) +{ + for (size_t a = len - 1; a > 0; --a) { + buf[a] = (buf[a] ^ buf[a - 1]) + ((a * 1234567) >> 8); + } +} + +void asset_fex_usb_rank_encrypt(uint8_t* buf, size_t len) +{ + for (size_t a = 1; a < len; ++a) { + buf[a] = (buf[a] - ((a * 1234567) >> 8)) ^ buf[a - 1]; + } +} \ No newline at end of file diff --git a/src/main/asset/fex/lib/usb-rank.h b/src/main/asset/fex/lib/usb-rank.h new file mode 100644 index 0000000..3154275 --- /dev/null +++ b/src/main/asset/fex/lib/usb-rank.h @@ -0,0 +1,28 @@ +#ifndef ASSET_FEX_USB_RANK_H +#define ASSET_FEX_USB_RANK_H + +#include +#include + +struct asset_fex_usb_rank_header { + uint32_t adler32; + /* NULL terminated */ + char player_id[8]; +} __attribute__((__packed__)); + +struct asset_fex_usb_rank { + struct asset_fex_usb_rank_header header; +} __attribute__((__packed__)); + +struct asset_fex_usb_rank* asset_fex_usb_rank_new(void); + +// update checksum and prepare profile to get encrypted +void asset_fex_usb_rank_finalize(struct asset_fex_usb_rank* rank); + +char* asset_fex_usb_rank_to_string(const struct asset_fex_usb_rank* rank); + +void asset_fex_usb_rank_decrypt(uint8_t* buf, size_t len); + +void asset_fex_usb_rank_encrypt(uint8_t* buf, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/asset/fex/lib/usb-save.c b/src/main/asset/fex/lib/usb-save.c new file mode 100644 index 0000000..9d4007a --- /dev/null +++ b/src/main/asset/fex/lib/usb-save.c @@ -0,0 +1,339 @@ +#include +#include + +#include "util/adler32.h" +#include "util/mem.h" +#include "util/str.h" + +#include "usb-save.h" + +static char* fex_profile_save_header_to_string( + const struct asset_fex_usb_save_header* header) +{ + char* buffer; + + buffer = (char*) util_xmalloc(256); + memset(buffer, 0, 256); + + util_str_format(buffer, 256, + "-------------- header --------------\n" + "adler32: %X\n" + "usb_serial: %s\n" + "dongle_serial: %X\n", + header->adler32, + header->usb_serial, + header->dongle_serial); + + return buffer; +} + +static char* fex_profile_save_player_to_string( + const struct asset_fex_usb_save_player* player) +{ + char* buffer; + + buffer = (char*) util_xmalloc(1024); + memset(buffer, 0, 1024); + + util_str_format(buffer, 1024, + "-------------- player --------------\n" + "unkn: %X\n" + "unkn2: %X\n" + "avatar_id: %d\n" + "player_lvl: %d\n" + "unkn3: %X\n" + "player_id: %s\n" + "unkn4: %X\n" + "unkn5: %X\n" + "unkn6: %X\n" + "unkn7: %X\n" + "unkn8: %X\n" + "unkn9: %X\n" + "unkn10: %X\n" + "unkn11: %X\n" + "unkn12: %X\n" + "unkn13: %X\n" + "unkn14: %X\n" + "unkn15: %X\n" + "total_step: %d\n" + "play_count: %d\n" + "exp: %d\n" + "num_quest_world_passed: %d\n" + "unkn16: %X\n" + "arcade_score: %d\n" + "num_battle_wins: %d\n" + "num_battle_loss: %d\n" + "num_battle_draw: %d\n", + player->unkn, + player->unkn2, + player->avatar_id, + player->player_lvl, + player->unkn3, + player->player_id, + player->unkn4, + player->unkn5, + player->unkn6, + player->unkn7, + player->unkn8, + player->unkn9, + player->unkn10, + player->unkn11, + player->unkn12, + player->unkn13, + player->unkn14, + player->unkn15, + player->total_step, + player->play_count, + player->exp, + player->num_quest_world_passed, + player->unkn16, + player->arcade_score, + player->num_battle_wins, + player->num_battle_loss, + player->num_battle_draw); + + return buffer; +} + +static char* fex_profile_save_mods_to_string( + const struct asset_fex_usb_save_player_mods* mods) +{ + char* buffer; + + buffer = (char*) util_xmalloc(128); + memset(buffer, 0, 128); + + util_str_format(buffer, 128, + "-------------- modifiers --------------\n" + "modifiers: %X\n" + "half_speed_mod: %X\n" + "noteskin: %X\n", + mods->modifiers, + mods->half_speed_mod, + mods->noteskin); + + return buffer; +} + +static char* fex_profile_save_mod_unlocks_to_string( + const struct asset_fex_usb_save_mod_unlocks* unlocks) +{ + char* merged; + char* buffer; + char* tmp; + const size_t buf_size = 512; + + merged = (char*) util_xmalloc(buf_size); + memset(merged, 0, buf_size); + + buffer = (char*) util_xmalloc(buf_size); + memset(buffer, 0, buf_size); + + util_str_format(buffer, buf_size, + "-------------- mod unlocks --------------\n" ); + + tmp = util_str_merge(merged, buffer); + free(merged); + merged = tmp; + + for (uint32_t i = 0; i < 0x12; i++) { + util_str_format(buffer, buf_size, + "unkn0[%d]: %d\n", + i, + unlocks->unkn0[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + util_str_format(buffer, buf_size, + "display_va: %d\n", + unlocks->display_va); + + tmp = util_str_merge(merged, buffer); + free(merged); + merged = tmp; + + for (uint32_t i = 0; i < 0x0D; i++) { + util_str_format(buffer, buf_size, + "unkn1[%d]: %d\n", + i, + unlocks->unkn1[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < 0x10; i++) { + util_str_format(buffer, buf_size, + "noteskins[%d]: %d\n", + i, + unlocks->noteskins[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < 0x04; i++) { + util_str_format(buffer, buf_size, + "unkn2[%d]: %d\n", + i, + unlocks->unkn2[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < 0x03; i++) { + util_str_format(buffer, buf_size, + "path[%d]: %d\n", + i, + unlocks->path[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < 0x06; i++) { + util_str_format(buffer, buf_size, + "unkn3[%d]: %d\n", + i, + unlocks->unkn3[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + util_str_format(buffer, buf_size, + "xj: %d\n", + unlocks->xj); + + tmp = util_str_merge(merged, buffer); + free(merged); + merged = tmp; + + for (uint32_t i = 0; i < 0x02; i++) { + util_str_format(buffer, buf_size, + "system[%d]: %d\n", + i, + unlocks->system[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < 0x08; i++) { + util_str_format(buffer, buf_size, + "rush[%d]: %d\n", + i, + unlocks->rush[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < 0x1A; i++) { + util_str_format(buffer, buf_size, + "unkn4[%d]: %d\n", + i, + unlocks->unkn4[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + return merged; +} + +static char* fex_profile_save_mission_state_to_string( + const struct asset_fex_usb_save_mission_state* state) +{ + char* buffer; + + buffer = (char*) util_xmalloc(128); + memset(buffer, 0, 128); + + util_str_format(buffer, 128, + "-------------- mission state --------------\n" + "quest_world_location: %X\n" + "skill_up_zone_location: %X\n", + state->quest_world_location, + state->skill_up_zone_location); + + return buffer; +} + +struct asset_fex_usb_save* asset_fex_usb_save_new(void) +{ + return malloc(sizeof(struct asset_fex_usb_save)); +} + +void asset_fex_usb_save_finalize(struct asset_fex_usb_save* save) +{ + +} + +char* asset_fex_usb_save_to_string(const struct asset_fex_usb_save* save) +{ + char* header; + char* player; + char* mods; + char* unlocks; + char* mission_state; + char* merged; + char* merged2; + + header = fex_profile_save_header_to_string(&save->header); + player = fex_profile_save_player_to_string(&save->player); + mods = fex_profile_save_mods_to_string(&save->mods); + unlocks = fex_profile_save_mod_unlocks_to_string(&save->mod_unlocks); + mission_state = fex_profile_save_mission_state_to_string(&save->mission_state); + + merged = util_str_merge(header, player); + merged2 = util_str_merge(merged, mods); + free(merged); + merged = util_str_merge(merged2, unlocks); + free(merged2); + merged2 = util_str_merge(merged, mission_state); + free(merged); + + free(header); + free(player); + free(mods); + free(unlocks); + free(mission_state); + + return merged2; +} + +void asset_fex_usb_save_decrypt(uint8_t* buf, size_t len) +{ + for (size_t a = len - 1; a > 0; --a) { + buf[a] = (buf[a] ^ buf[a - 1]) + ((a * 1234567) >> 8); + } +} + +void asset_fex_usb_save_encrypt(uint8_t* buf, size_t len) +{ + for (size_t a = 1; a < len; ++a) { + buf[a] = (buf[a] - ((a * 1234567) >> 8)) ^ buf[a - 1]; + } +} \ No newline at end of file diff --git a/src/main/asset/fex/lib/usb-save.h b/src/main/asset/fex/lib/usb-save.h new file mode 100644 index 0000000..fd509ef --- /dev/null +++ b/src/main/asset/fex/lib/usb-save.h @@ -0,0 +1,191 @@ +#ifndef ASSET_FEX_USB_SAVE_H +#define ASSET_FEX_USB_SAVE_H + +#include +#include + +enum asset_fex_usb_save_noteskins { + ASSET_FEX_USB_SAVE_SKIN_DEFAULT = 0x00, + ASSET_FEX_USB_SAVE_SKIN_HANAFUDA = 0x01, + ASSET_FEX_USB_SAVE_SKIN_CLASSIC = 0x02, + ASSET_FEX_USB_SAVE_SKIN_CANDY = 0x03, + ASSET_FEX_USB_SAVE_SKIN_PORING = 0x04, + ASSET_FEX_USB_SAVE_SKIN_MUSICAL_NOTES = 0x05, + ASSET_FEX_USB_SAVE_SKIN_CANON_D = 0x06, + ASSET_FEX_USB_SAVE_SKIN_PLAYING_CARDS = 0x07, + ASSET_FEX_USB_SAVE_SKIN_NX = 0x08, + ASSET_FEX_USB_SAVE_SKIN_DIGNITY = 0x09, + ASSET_FEX_USB_SAVE_SKIN_HORSE_HEAD = 0x0A, + ASSET_FEX_USB_SAVE_SKIN_DOG = 0x0B, + ASSET_FEX_USB_SAVE_SKIN_GIRL = 0x0C, + ASSET_FEX_USB_SAVE_SKIN_FIRE = 0x0D, + ASSET_FEX_USB_SAVE_SKIN_ICE = 0x0E, + ASSET_FEX_USB_SAVE_SKIN_WIND = 0x0F, + ASSET_FEX_USB_SAVE_SKIN_RED_DP = 0x10, + ASSET_FEX_USB_SAVE_SKIN_BLUE_DP = 0x11, + ASSET_FEX_USB_SAVE_SKIN_YELLOW_DP = 0x12, + ASSET_FEX_USB_SAVE_SKIN_NXA = 0x13, + ASSET_FEX_USB_SAVE_SKIN_NX2 = 0x14, + ASSET_FEX_USB_SAVE_SKIN_ELECTRIC_FIESTA = 0x15, + ASSET_FEX_USB_SAVE_SKIN_DRUM = 0x16, + ASSET_FEX_USB_SAVE_SKIN_BULLET = 0x17, + ASSET_FEX_USB_SAVE_SKIN_BLUE_DRUM = 0x18, + ASSET_FEX_USB_SAVE_SKIN_RED_DRUM = 0x19, + ASSET_FEX_USB_SAVE_SKIN_YELLOW_DRUM = 0x1A, + ASSET_FEX_USB_SAVE_SKIN_WORLD_CUP = 0x1B, + ASSET_FEX_USB_SAVE_SKIN_PREX3 = 0x1C, + ASSET_FEX_USB_SAVE_SKIN_FIESTA_BASIC = 0x1D, +}; + +struct asset_fex_usb_save_header { + /* 0x00 */ + uint32_t adler32; + /* 0x04: NULL terminated */ + char usb_serial[0x40]; + /* 0x44: Deprecated from old games */ + uint32_t dongle_serial; +} __attribute__((__packed__)); + +struct asset_fex_usb_save_player { + /* 0x48 */ + uint32_t unkn; + /* 0x4C */ + uint32_t unkn2; + /* 0x50 */ + uint8_t avatar_id; + /* 0x51: Range: 0x00 - 0x21 */ + uint8_t player_lvl; + /* 0x52: PumBi sets it to 0xC4 */ + uint16_t unkn3; + /* 0x54: NULL terminated */ + char player_id[8]; + /* 0x5C */ + uint32_t unkn4; + /* 0x60 */ + uint32_t unkn5; + /* 0x64: Each time you save, it inc. by 6... */ + uint32_t unkn6; + /* 0x68 */ + uint32_t unkn7; + /* 0x6C */ + uint32_t unkn8; + /* 0x70 */ + uint32_t unkn9; + /* 0x74 */ + uint32_t unkn10; + /* 0x78 */ + uint32_t unkn11; + /* 0x7C */ + uint32_t unkn12; + /* 0x80 */ + uint32_t unkn13; + /* 0x84 */ + uint32_t unkn14; + /* 0x88 */ + uint32_t unkn15; + /* 0x8C */ + uint32_t total_step; + /* 0x90 */ + uint32_t play_count; + /* 0x94 */ + uint32_t exp; + /* 0x98 */ + uint32_t num_quest_world_passed; + /* 0x9C */ + uint32_t unkn16; + /* 0xA0 */ + uint32_t arcade_score; + /* 0xA8 */ + uint32_t num_battle_wins; + /* 0xAC */ + uint32_t num_battle_loss; + /* 0xB0 */ + uint32_t num_battle_draw; +} __attribute__((__packed__)); + +struct asset_fex_usb_save_player_mods { + /* 0xB4: The first set of saved modifiers (see MOD_1 below) */ + uint32_t modifiers; + /* 0xB8: If the +0.5x mod is enabled (boolean) */ + uint32_t half_speed_mod; + /* 0xBC: Which noteskin is currently selected (see NOTE_S below) */ + uint32_t noteskin; +} __attribute__((__packed__)); + +struct asset_fex_usb_save_mod_unlocks { + /* 0xC0: Unknown Unlocks Area */ + uint8_t unkn0[0x12]; + /* 0xD2: Boolean for unlocking VA Mod. */ + uint8_t display_va; + /* 0xD3: Unknown Unlocks Area */ + uint8_t unkn1[0x0D]; + /* 0xE0: Array of bools - Which Noteskins are Unlocked. */ + uint8_t noteskins[0x10]; + /* 0xF0: Unknown Unlocks Area */ + uint8_t unkn2[0x04]; + /* 0xF4: Array of bools - Which Path Mods are Unlocked. */ + uint8_t path[0x03]; + /* 0xF7: Unknown Unlocks Area */ + uint8_t unkn3[0x06]; + /* 0xFE: Boolean for unlocking XJ */ + uint8_t xj; + /* 0x100: Array of bools - Which System Mods are Unlocked. */ + uint8_t system[0x02]; + /* 0x102: Array of bools - Which Rush/NOT Modes are Unlocked. */ + uint8_t rush[0x08]; + /* 0x10A: Unknown Unlocks Area */ + uint8_t unkn4[0x1A]; +} __attribute__((__packed__)); + +struct asset_fex_usb_save_mission_state { + /* 0x124 */ + uint32_t quest_world_location; + /* 0x128 */ + uint32_t skill_up_zone_location; +} __attribute__((__packed__)); + +struct asset_fex_usb_save { + struct asset_fex_usb_save_header header; + struct asset_fex_usb_save_player player; + struct asset_fex_usb_save_player_mods mods; + struct asset_fex_usb_save_mod_unlocks mod_unlocks; + struct asset_fex_usb_save_mission_state mission_state; + + /* Not verified, yet. just notes from old tools */ + + /* 0x17AD (0x0A): Quest world inventory */ + /* 0x17B7: ??? */ + /* 0x17B8 (0x1C): Quest world warp unlocks */ + + /* 0x17AC (0x11): QW inventory */ + + /* 0x17D4 or 0x17D5 ? (0x14): favorites channel */ + /* 0x2E48 - 0x1A5EF: step chart blacklist */ + /* 0x2E48 - 0x1A5EF: step chart unlocks = blacklist */ + + /* pumbi stepcharts */ + /* 0x6047: pumbi stepchart 1009 Level S15 */ + /* 0x6083 */ + /* 0x60AB */ + /* 0x647F */ + /* 0x64BB */ + /* 0x64CF */ + /* 0x650B */ + /* 0x6533 */ + /* 0x655B */ + /* 0x65AB */ + /* 0x660F */ +} __attribute__((__packed__)); + +struct asset_fex_usb_save* asset_fex_usb_save_new(void); + +// update checksum and prepare profile to get encrypted +void asset_fex_usb_save_finalize(struct asset_fex_usb_save* save); + +char* asset_fex_usb_save_to_string(const struct asset_fex_usb_save* save); + +void asset_fex_usb_save_decrypt(uint8_t* buf, size_t len); + +void asset_fex_usb_save_encrypt(uint8_t* buf, size_t len); + +#endif diff --git a/src/main/asset/fex/lib/util.c b/src/main/asset/fex/lib/util.c new file mode 100644 index 0000000..c61ac46 --- /dev/null +++ b/src/main/asset/fex/lib/util.c @@ -0,0 +1,100 @@ +#define LOG_MODULE "asset-fex-util" + +#include + +#include "util/fs.h" +#include "util/log.h" + +#include "util.h" + +struct asset_fex_usb_rank* asset_fex_usb_rank_load_from_file(const char* path, + bool encrypted) +{ + struct asset_fex_usb_rank* rank; + size_t size; + + if (!util_file_load(path, (void**) &rank, &size, false)) { + log_error("Loading fex rank file %s failed", path); + return NULL; + } + + // if (size != sizeof(struct fex_profile_rank)) { + // log_error("Invalid size of fex rank file %s: %d != %d", path, size, + // sizeof(struct fex_profile_rank)); + // free(rank); + // return NULL; + // } + + if (encrypted) { + asset_fex_usb_rank_decrypt((uint8_t*) rank, size); + } + + return rank; +} + +bool asset_fex_usb_rank_save_to_file(const char* path, + const struct asset_fex_usb_rank* rank, bool encrypt) +{ + struct asset_fex_usb_rank rank_enc; + + memcpy(&rank_enc, rank, sizeof(struct asset_fex_usb_rank)); + + if (encrypt) { + asset_fex_usb_rank_encrypt((uint8_t*) &rank_enc, + sizeof(struct asset_fex_usb_rank)); + } + + if (!util_file_save(path, (void**) &rank_enc, + sizeof(struct asset_fex_usb_rank))) { + log_error("Storing fex rank data to %s failed", path); + return false; + } + + return true; +} + +struct asset_fex_usb_save* asset_fex_usb_save_load_from_file(const char* path, + bool encrypted) +{ + struct asset_fex_usb_save* save; + size_t size; + + if (!util_file_load(path, (void**) &save, &size, false)) { + log_error("Loading fex save file %s failed", path); + return NULL; + } + + // if (size != sizeof(struct fex_profile_save)) { + // log_error("Invalid size of fex save file %s: %d != %d", path, size, + // sizeof(struct fex_profile_save)); + // free(save); + // return NULL; + // } + + if (encrypted) { + asset_fex_usb_save_decrypt((uint8_t*) save, size); + } + + return save; +} + +bool asset_fex_usb_save_save_to_file(const char* path, + const struct asset_fex_usb_save* save, bool encrypt) +{ + struct asset_fex_usb_save save_enc; + + memcpy(&save_enc, save, sizeof(struct asset_fex_usb_save)); + + if (encrypt) { + asset_fex_usb_save_encrypt((uint8_t*) &save_enc, + sizeof(struct asset_fex_usb_save)); + } + + if (!util_file_save(path, (const void*) &save_enc, + sizeof(struct asset_fex_usb_save))) { + log_error("Storing fex save data to %s failed", path); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/main/asset/fex/lib/util.h b/src/main/asset/fex/lib/util.h new file mode 100644 index 0000000..be9cbb2 --- /dev/null +++ b/src/main/asset/fex/lib/util.h @@ -0,0 +1,21 @@ +#ifndef ASSET_FEX_UTIL_H +#define ASSET_FEX_UTIL_H + +#include + +#include "usb-rank.h" +#include "usb-save.h" + +// load from file and decrypt, returns decrypted struct +struct asset_fex_usb_rank* asset_fex_usb_rank_load_from_file(const char* path, bool encrypted); + +// takes decrypted rank data, encrypts it and writes it to file +bool asset_fex_usb_rank_save_to_file(const char* path, const struct asset_fex_usb_rank* rank, bool encrypt); + +// load from file and decrypt, returns decrypted struct +struct asset_fex_usb_save* asset_fex_usb_save_load_from_file(const char* path, bool encrypted); + +// takes decrypted save data, encrypts it and writes it to file +bool asset_fex_usb_save_save_to_file(const char* path, const struct asset_fex_usb_save* save, bool encrypt); + +#endif \ No newline at end of file diff --git a/src/main/asset/fex/tool/usb-profile.c b/src/main/asset/fex/tool/usb-profile.c new file mode 100644 index 0000000..7ffa44d --- /dev/null +++ b/src/main/asset/fex/tool/usb-profile.c @@ -0,0 +1,154 @@ +/** + * Tool for FiestaEX profiles: decrypt, encrypt, print profile data + */ +#include +#include +#include + +#include "asset/fex/lib/util.h" + +#include "util/str.h" + +int main(int argc, char** argv) +{ + int ret; + char* rank_path; + char* save_path; + + if (argc < 3) { + printf("Usage: %s [cmd: new, dec, enc, dump] " + "[path containing fiestaex_rank.bin fiestaex_save.bin]\n", argv[0]); + return -1; + } + + ret = 0; + + rank_path = util_str_merge(argv[2], "fiestaex_rank.bin"); + save_path = util_str_merge(argv[2], "fiestaex_save.bin"); + + if (!strcmp(argv[1], "new")) { + struct asset_fex_usb_rank* rank = asset_fex_usb_rank_new(); + struct asset_fex_usb_save* save = asset_fex_usb_save_new(); + + asset_fex_usb_rank_finalize(rank); + asset_fex_usb_save_finalize(save); + + if (!asset_fex_usb_rank_save_to_file(rank_path, rank, true)) { + fprintf(stderr, "Creating rank file %s failed\n", rank_path); + ret = -2; + } + + if (!asset_fex_usb_save_save_to_file(save_path, save, true)) { + fprintf(stderr, "Creating save file %s failed\n", save_path); + ret = -3; + } + + free(rank); + free(save); + } else { + struct asset_fex_usb_rank* rank; + struct asset_fex_usb_save* save; + bool encrypted; + + if (!strcmp(argv[1], "enc")) { + char* tmp; + + encrypted = false; + + tmp = util_str_merge(rank_path, ".dec"); + free(rank_path); + rank_path = tmp; + + tmp = util_str_merge(save_path, ".dec"); + free(save_path); + save_path = tmp; + } else { + encrypted = true; + } + + rank = asset_fex_usb_rank_load_from_file(rank_path, encrypted); + save = asset_fex_usb_save_load_from_file(save_path, encrypted); + + if (!rank) { + fprintf(stderr, "Loading %s failed", rank_path); + ret = -4; + goto cleanup; + } + + if (!save) { + fprintf(stderr, "Loading %s failed", save_path); + ret = -5; + free(rank); + goto cleanup; + } + + if (!strcmp(argv[1], "dec")) { + char* rank_path_dec; + char* save_path_dec; + + rank_path_dec = util_str_merge(rank_path, ".dec"); + save_path_dec = util_str_merge(save_path, ".dec"); + + if (!asset_fex_usb_rank_save_to_file(rank_path_dec, rank, false)) { + fprintf(stderr, "Saving decrypted rank file to %s failed\n", + rank_path_dec); + ret = -6; + } + + if (!asset_fex_usb_save_save_to_file(save_path_dec, save, false)) { + fprintf(stderr, "Saving decrypted save file to %s failed\n", + save_path_dec); + ret = -7; + } + + free(rank_path_dec); + free(save_path_dec); + } else if (!strcmp(argv[1], "enc")) { + char* rank_path_enc; + char* save_path_enc; + + rank_path_enc = util_str_merge(rank_path, ".enc"); + save_path_enc = util_str_merge(save_path, ".enc"); + + if (!asset_fex_usb_rank_save_to_file(rank_path_enc, rank, true)) { + fprintf(stderr, "Saving encrypted rank file to %s failed\n", + rank_path_enc); + ret = -6; + } + + if (!asset_fex_usb_save_save_to_file(save_path_enc, save, true)) { + fprintf(stderr, "Saving encrypted save file to %s failed\n", + save_path_enc); + ret = -7; + } + + free(rank_path_enc); + free(save_path_enc); + } else if (!strcmp(argv[1], "dump")) { + char* rank_str; + char* save_str; + + rank_str = asset_fex_usb_rank_to_string(rank); + save_str = asset_fex_usb_save_to_string(save); + + printf("----------------- rank -----------------\n" + "%s----------------- save -----------------\n%s", + rank_str, save_str); + + free(rank_str); + free(save_str); + } else { + fprintf(stderr, "Unknown command %s\n", argv[1]); + ret = -4; + } + + free(rank); + free(save); + } + +cleanup: + free(rank_path); + free(save_path); + + return ret; +} \ No newline at end of file diff --git a/src/main/asset/game-version.h b/src/main/asset/game-version.h new file mode 100644 index 0000000..c6ff8df --- /dev/null +++ b/src/main/asset/game-version.h @@ -0,0 +1,38 @@ +#ifndef ASSET_GAME_VERSION_H +#define ASSET_GAME_VERSION_H + +/* Note: Version IDs must be sync with server code */ +enum asset_game_version { + ASSET_GAME_VERSION_INVALID = 0, + ASSET_GAME_VERSION_FIRST = 1, + ASSET_GAME_VERSION_SECOND = 2, + ASSET_GAME_VERSION_OBG = 3, + ASSET_GAME_VERSION_OBG_SE = 4, + ASSET_GAME_VERSION_THE_COLLECTION = 5, + ASSET_GAME_VERSION_PERFECT_COLLECTION = 6, + ASSET_GAME_VERSION_EXTRA = 7, + ASSET_GAME_VERSION_PREMIERE_1 = 8, + ASSET_GAME_VERSION_PREX_1 = 9, + ASSET_GAME_VERSION_REBIRTH = 10, + ASSET_GAME_VERSION_PREMIERE_2 = 11, + ASSET_GAME_VERSION_PREX_2 = 12, + ASSET_GAME_VERSION_PREMIERE_3 = 13, + ASSET_GAME_VERSION_PREX_3 = 14, + ASSET_GAME_VERSION_EXCEED = 15, + ASSET_GAME_VERSION_EXCEED_2 = 16, + ASSET_GAME_VERSION_ZERO = 17, + ASSET_GAME_VERSION_NX = 18, + ASSET_GAME_VERSION_PRO = 19, + ASSET_GAME_VERSION_NX2 = 20, + ASSET_GAME_VERSION_NXA = 21, + ASSET_GAME_VERSION_FIESTA = 22, + ASSET_GAME_VERSION_PRO2 = 23, + ASSET_GAME_VERSION_FIESTA_EX = 24, + ASSET_GAME_VERSION_FIESTA_2 = 25, + ASSET_GAME_VERSION_INFINITY = 26, + ASSET_GAME_VERSION_PRIME = 27, + ASSET_GAME_VERSION_PRIME_2 = 28, + ASSET_GAME_VERSION_XX = 29 +}; + +#endif diff --git a/src/main/asset/nx/lib/settings.c b/src/main/asset/nx/lib/settings.c new file mode 100644 index 0000000..f4ddbba --- /dev/null +++ b/src/main/asset/nx/lib/settings.c @@ -0,0 +1,68 @@ +#include + +#include "asset/nx/lib/settings.h" + +#include "util/adler32.h" +#include "util/mem.h" + +struct asset_nx_settings* asset_nx_settings_new(void) +{ + struct asset_nx_settings* settings; + + settings = util_xmalloc(sizeof(struct asset_nx_settings)); + + memset(settings->ff_pad, 0xFF, sizeof(settings->ff_pad)); + memcpy(settings->ident, "NX10", 4); + /* Updated using finalize */ + settings->adler32 = 0; + settings->total_uptime = 0; + settings->language = ASSET_NX_SETTINGS_LANG_ENG; + memset(settings->song_play_count, 0, sizeof(uint32_t) * ASSET_NX_SETTINGS_MAX_SONGS); + settings->game_option_default_station = ASSET_NX_SETTINGS_STATION_TRAIN; + settings->game_mode = ASSET_NX_SETTINGS_GAME_MODE_NORMAL; + settings->game_level = ASSET_NX_SETTINGS_LEVEL_NORMAL; + settings->game_stage_break = ASSET_NX_SETTINGS_STAGE_BREAK_2ND; + settings->game_demo_sound = 1; + settings->game_show_help = 1; + settings->game_mercy_ticket = 0; + settings->game_score_per_ticket = 0; + settings->coin_limit = 0; + settings->coin1_setting = 1; + settings->coin2_setting = 1; + memset(settings->unknown_unlock_flags, 0, ASSET_NX_SETTINGS_NUM_UNLOCK_FLAGS); + settings->unkn2 = 0x2A; + settings->unkn3 = 0; + settings->bookkeeping_coin1_total = 0; + settings->bookkeeping_coin2_total = 0; + settings->bookkeeping_service_total = 0; + settings->bookkeeping_play_total = 0; + settings->bookkeeping_faep21_play_count = 0; + settings->bookkeeping_ticket_1 = 0; + settings->bookkeeping_ticket_2 = 0; + settings->region = 0xFF; + memset(settings->censor_table, 0, ASSET_NX_SETTINGS_MAX_SONGS); + + return settings; +} + +void asset_nx_settings_finalize(struct asset_nx_settings* settings) +{ + /* Seems odd, but they just checksum the game settings part...*/ + settings->adler32 = util_adler32_calc(1, (const uint8_t*) &settings->game_mode, 8); +} + +void asset_nx_settings_unlock_all(struct asset_nx_settings* settings) +{ + /* FAEP 2-1 must be played at least 100 times to unlock FAEP 2-2 */ + settings->bookkeeping_faep21_play_count = 100; + + /* To unlock all world tour missions and special zone songs, set this to 20k. + The first 10k unlock only the first half of the missions while another 10k unlocks the second half (wtf) */ + settings->bookkeeping_play_total = 20000; +} + +char* asset_nx_settings_to_string(const struct asset_nx_settings* settings) +{ + // TODO + return ""; +} \ No newline at end of file diff --git a/src/main/asset/nx/lib/settings.h b/src/main/asset/nx/lib/settings.h new file mode 100644 index 0000000..12c2ffb --- /dev/null +++ b/src/main/asset/nx/lib/settings.h @@ -0,0 +1,101 @@ +#ifndef ASSET_NX_SETTINGS_H +#define ASSET_NX_SETTINGS_H + +#include + +#define ASSET_NX_SETTINGS_FILE_NAME "PIUNX.INI" +#define ASSET_NX_SETTINGS_MAX_SONGS 200 +#define ASSET_NX_SETTINGS_NUM_UNLOCK_FLAGS 59 + +enum asset_nx_settings_language { + ASSET_NX_SETTINGS_LANG_KOR = 0, + ASSET_NX_SETTINGS_LANG_ENG = 1, + ASSET_NX_SETTINGS_LANG_SP = 2, + ASSET_NX_SETTINGS_LANG_CH = 3, +}; + +enum asset_nx_settings_station { + ASSET_NX_SETTINGS_STATION_TRAIN = 0, + ASSET_NX_SETTINGS_STATION_ARCADE = 1, + ASSET_NX_SETTINGS_STATION_WORLD = 2, + ASSET_NX_SETTINGS_STATION_SPECIAL = 3, +}; + +enum asset_nx_settings_game_mode { + ASSET_NX_SETTINGS_GAME_MODE_NORMAL = 0, + ASSET_NX_SETTINGS_GAME_MODE_EVENT = 1, +}; + +enum asset_nx_settings_level { + ASSET_NX_SETTINGS_LEVEL_EASY = 0, + ASSET_NX_SETTINGS_LEVEL_NORMAL = 1, + ASSET_NX_SETTINGS_LEVEL_HARD = 2, +}; + +enum asset_nx_settings_stage_break { + ASSET_NX_SETTINGS_STAGE_BREAK_OFF = 0, + ASSET_NX_SETTINGS_STAGE_BREAK_1ST = 1, + ASSET_NX_SETTINGS_STAGE_BREAK_2ND = 2, + ASSET_NX_SETTINGS_STAGE_BREAK_3RD = 3, + ASSET_NX_SETTINGS_STAGE_BREAK_4TH = 4, +}; + +struct asset_nx_settings { + /* Padding to fill up to 4k EEPROM legacy size */ + uint8_t ff_pad[0xB9D]; + /* NX10 */ + char ident[4]; + uint32_t adler32; + uint32_t total_uptime; + uint8_t language; + uint32_t song_play_count[ASSET_NX_SETTINGS_MAX_SONGS]; + uint32_t game_option_default_station; + uint8_t game_mode; + uint8_t game_level; + uint8_t game_stage_break; + /* 0 off, 1 on */ + uint8_t game_demo_sound; + /* 0 off, 1 on */ + uint8_t game_show_help; + /* 0 off, 1 on */ + uint8_t game_mercy_ticket; + /* 0 off, 1 - 6 for score */ + uint8_t game_score_per_ticket; + /* 0 none */ + uint8_t coin_limit; // 0 none + /* 0 freeplay */ + uint8_t coin1_setting; + /* 0 freeplay */ + uint8_t coin2_setting; + /* Unlock flags for world tour missions? setting them to 1 doesn't unlock anything. + 64 missions total, 55 unlock flags, 0 = locked, 1 = unlocked */ + uint8_t unknown_unlock_flags[ASSET_NX_SETTINGS_NUM_UNLOCK_FLAGS]; + /* Default value 0x2A */ + uint32_t unkn2; + uint32_t unkn3; + uint32_t bookkeeping_coin1_total; + uint32_t bookkeeping_coin2_total; + uint32_t bookkeeping_service_total; + uint32_t bookkeeping_play_total; + /* Separate counter for FAEP 2-1 play counts. Once this hits 100, FAEP 2-2 is unlocked in arcade station */ + uint32_t bookkeeping_faep21_play_count; + uint32_t bookkeeping_ticket_1; + uint32_t bookkeeping_ticket_2; + /* FF -> not used, censoring globally disabled. unused feature though */ + uint8_t region; + /* 0 = censoring of song off */ + uint8_t censor_table[ASSET_NX_SETTINGS_MAX_SONGS]; +} __attribute__((__packed__)); + +struct asset_nx_settings* asset_nx_settings_new(void); + +// update checksum +void asset_nx_settings_finalize(struct asset_nx_settings* settings); + +// unlocks FAEP 2-2 by setting the play count to 100 +// and sets all missions cleared to unlock special stage songs and other missions +void asset_nx_settings_unlock_all(struct asset_nx_settings* settings); + +char* asset_nx_settings_to_string(const struct asset_nx_settings* settings); + +#endif diff --git a/src/main/asset/nx/lib/util.c b/src/main/asset/nx/lib/util.c new file mode 100644 index 0000000..28b8062 --- /dev/null +++ b/src/main/asset/nx/lib/util.c @@ -0,0 +1,35 @@ +#include + +#include "asset/nx/lib/util.h" + +#include "util/fs.h" +#include "util/log.h" + +struct asset_nx_settings* asset_nx_util_load_settings_from_file(const char* path) +{ + struct asset_nx_settings* settings; + size_t size; + + if (!util_file_load(path, (void**) &settings, &size, false)) { + log_error("Loading nx setting file %s failed", path); + return NULL; + } + + if (size != sizeof(struct asset_nx_settings)) { + log_error("Invalid size of nx setting file %s: %d != %d", path, size, sizeof(struct asset_nx_settings)); + free(settings); + return NULL; + } + + return settings; +} + +bool asset_nx_util_save_settings_to_file(const char* path, const struct asset_nx_settings* settings) +{ + if (!util_file_save(path, (void**) settings, sizeof(struct asset_nx_settings))) { + log_error("Storing nx settings data to %s failed", path); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/main/asset/nx/lib/util.h b/src/main/asset/nx/lib/util.h new file mode 100644 index 0000000..37342b9 --- /dev/null +++ b/src/main/asset/nx/lib/util.h @@ -0,0 +1,12 @@ +#ifndef ASSET_NX_UTIL_H +#define ASSET_NX_UTIL_H + +#include + +#include "asset/nx/lib/settings.h" + +struct asset_nx_settings* asset_nx_util_load_settings_from_file(const char* path); + +bool asset_nx_util_save_settings_to_file(const char* path, const struct asset_nx_settings* settings); + +#endif diff --git a/src/main/asset/nx/tool/settings.c b/src/main/asset/nx/tool/settings.c new file mode 100644 index 0000000..d32709e --- /dev/null +++ b/src/main/asset/nx/tool/settings.c @@ -0,0 +1,88 @@ +/** + * Tool for NX settings files + */ +#include +#include +#include + +#include "asset/nx/lib/settings.h" +#include "asset/nx/lib/util.h" + +static int main_settings(char* cmd, char* file); + +int main(int argc, char** argv) +{ + int ret; + char* cmd; + char* type; + char* file; + + if (argc < 4) { + printf("Usage: %s [cmd: new, dump, unlock] [type: ini, mrank, rank] [file]\n", argv[0]); + return -1; + } + + cmd = argv[1]; + type = argv[2]; + file = argv[3]; + + if (!strcmp(type, "ini")) { + ret = main_settings(cmd, file); + } else { + fprintf(stderr, "Unknown file type %s\n", type); + ret = -1; + } + + return ret; +} + +static int main_settings(char* cmd, char* file) +{ + int ret; + + ret = 0; + + if (!strcmp(cmd, "new")) { + struct asset_nx_settings* settings = asset_nx_settings_new(); + + asset_nx_settings_finalize(settings); + + if (!asset_nx_util_save_settings_to_file(file, settings)) { + fprintf(stderr, "Creating nx settings file %s failed\n", file); + ret = -1; + } + + free(settings); + } else { + struct asset_nx_settings* settings = asset_nx_util_load_settings_from_file(file); + + if (!settings) { + fprintf(stderr, "Loading %s failed", file); + ret = -1; + } + + if (!strcmp(cmd, "dump")) { + char* str = asset_nx_settings_to_string(settings); + + printf("----------------- settings -----------------\n%s\n", str); + + free(str); + } else if (!strcmp(cmd, "unlock")) { + asset_nx_settings_unlock_all(settings); + + asset_nx_settings_finalize(settings); + + if (!asset_nx_util_save_settings_to_file(file, settings)) { + fprintf(stderr, "Saving nx settings file %s failed\n", file); + ret = -1; + } + } else { + fprintf(stderr, "Unknown command %s\n", cmd); + ret = -1; + } + + free(settings); + } + + return ret; +} \ No newline at end of file diff --git a/src/main/asset/nx2/lib/usb-rank.c b/src/main/asset/nx2/lib/usb-rank.c new file mode 100644 index 0000000..d11c50c --- /dev/null +++ b/src/main/asset/nx2/lib/usb-rank.c @@ -0,0 +1,93 @@ +#include + +#include "util/adler32.h" +#include "util/mem.h" +#include "util/str.h" + +#include "usb-rank.h" + +struct asset_nx2_usb_rank* asset_nx2_usb_rank_new(void) +{ + struct asset_nx2_usb_rank* rank; + + rank = (struct asset_nx2_usb_rank*) + util_xmalloc(sizeof(struct asset_nx2_usb_rank)); + + memset(rank, 0, sizeof(struct asset_nx2_usb_rank)); + + return rank; +} + +void asset_nx2_usb_rank_finalize(struct asset_nx2_usb_rank* rank) +{ + rank->adler32 = util_adler32_calc(1, ((const uint8_t*) rank) + 4, + sizeof(struct asset_nx2_usb_rank) - 4); +} + +char* asset_nx2_usb_rank_to_string(const struct asset_nx2_usb_rank* rank) +{ + char* buffer; + char* buffer2; + char* merged; + + buffer = (char*) util_xmalloc(128); + memset(buffer, 0, 128); + + util_str_format(buffer, 128, + "adler32: 0x%X\n" + "num_rankings: %d\n" + "ranking entries:\n", + rank->adler32, + rank->num_rankings); + + for (uint32_t i = 0; i < ASSET_NX2_USB_RANK_MAX_RANK_ENTRIES; i++) { + + for (uint32_t j = 0; j < ASSET_NX2_USB_RANK_MAX_STAGES; j++) { + buffer2 = (char*) util_xmalloc(512); + memset(buffer2, 0, 512); + + util_str_format(buffer2, 512, + "%d/%d:\n" + "game_mode: %d\n" + "play_order: %d\n" + "play_score: %d\n" + "grade: %d\n" + "mileage: %d\n" + "play_time: %f\n" + "kcal: %f\n" + "v02: %f\n", + i, j, + rank->entries[i][j].game_mode, + rank->entries[i][j].play_order, + rank->entries[i][j].play_score, + rank->entries[i][j].grade, + rank->entries[i][j].mileage, + rank->entries[i][j].play_time, + rank->entries[i][j].kcal, + rank->entries[i][j].v02); + + merged = util_str_merge(buffer, buffer2); + + free(buffer); + free(buffer2); + + buffer = merged; + } + } + + return buffer; +} + +void asset_nx2_usb_rank_decrypt(uint8_t* buf, size_t len) +{ + for (size_t a = len - 1; a > 0; --a) { + buf[a] = (buf[a] ^ buf[a - 1]) + ((a * 1234567) >> 8); + } +} + +void asset_nx2_usb_rank_encrypt(uint8_t* buf, size_t len) +{ + for (size_t a = 1; a < len; ++a) { + buf[a] = (buf[a] - ((a * 1234567) >> 8)) ^ buf[a - 1]; + } +} \ No newline at end of file diff --git a/src/main/asset/nx2/lib/usb-rank.h b/src/main/asset/nx2/lib/usb-rank.h new file mode 100644 index 0000000..a06da6c --- /dev/null +++ b/src/main/asset/nx2/lib/usb-rank.h @@ -0,0 +1,42 @@ +#ifndef ASSET_NX2_USB_RANK_H +#define ASSET_NX2_USB_RANK_H + +#include +#include + +#define ASSET_NX2_USB_RANK_SIZE 12296 + +enum asset_nx2_usb_rank_sizes { + ASSET_NX2_USB_RANK_MAX_STAGES = 3, + ASSET_NX2_USB_RANK_MAX_RANK_ENTRIES = 128, +}; + +struct asset_nx2_usb_rank_entry { + int32_t game_mode; + int32_t play_order; + int32_t play_score; + int32_t grade; + int32_t mileage; + float play_time; + float kcal; + float v02; +} __attribute__((__packed__)); + +struct asset_nx2_usb_rank { + uint32_t adler32; + uint32_t num_rankings; + struct asset_nx2_usb_rank_entry entries[ASSET_NX2_USB_RANK_MAX_RANK_ENTRIES][ASSET_NX2_USB_RANK_MAX_STAGES]; +} __attribute__((__packed__)); + +struct asset_nx2_usb_rank* asset_nx2_usb_rank_new(void); + +// update checksum and prepare profile to get encrypted +void asset_nx2_usb_rank_finalize(struct asset_nx2_usb_rank* rank); + +char* asset_nx2_usb_rank_to_string(const struct asset_nx2_usb_rank* rank); + +void asset_nx2_usb_rank_decrypt(uint8_t* buf, size_t len); + +void asset_nx2_usb_rank_encrypt(uint8_t* buf, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/asset/nx2/lib/usb-save.c b/src/main/asset/nx2/lib/usb-save.c new file mode 100644 index 0000000..0588f43 --- /dev/null +++ b/src/main/asset/nx2/lib/usb-save.c @@ -0,0 +1,370 @@ +#include +#include + +#include "util/adler32.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +#include "usb-save.h" + +static char* asset_nx2_usb_save_time_to_string( + const struct asset_nx2_usb_save_time* time) +{ + char* buffer; + + buffer = (char*) util_xmalloc(128); + memset(buffer, 0, 128); + + util_str_format(buffer, 128, + "year: %d\n" + "month: %d\n" + "day: %d\n" + "hour: %d\n" + "min: %d\n" + "ms: %d\n", + time->year, + time->month, + time->day, + time->hour, + time->min, + time->ms); + + return buffer; +} + +static char* asset_nx2_usb_save_review_to_string( + const struct asset_nx2_usb_save_review* review) +{ + char* buffer; + char* land; + char* mission; + + buffer = (char*) util_xmalloc(1024); + memset(buffer, 0, 1024); + + land = util_str_buffer((const uint8_t*) review->worldmax_current_land, + ASSET_NX2_USB_SAVE_WORLDMAX_LOC_MAX); + mission = util_str_buffer((const uint8_t*) review->worldmax_current_mission, + ASSET_NX2_USB_SAVE_WORLDMAX_LOC_MAX); + + util_str_format(buffer, 1024, + ">>> review:\n" + "player_id: %s\n" + "mileage: %d\n" + "reward_count: %d\n" + "worldmax_count: %d\n" + "play_count: %d\n" + "worldmax_current_land: %s\n" + "worldmax_current_mission: %s\n" + "kcal: %d\n" + "v02: %d\n", + review->player_id, + review->mileage, + review->reward_count, + review->worldmax_count, + review->play_count, + land, + mission, + review->kcal, + review->v02); + + free(land); + free(mission); + + return buffer; +} + +static char* asset_nx2_usb_save_stats_to_string( + const struct asset_nx2_usb_save_stats* stats) +{ + char* merged; + char* buffer; + char* timestamp; + char* tmp; + const size_t buf_size = 512; + + merged = (char*) util_xmalloc(buf_size); + memset(merged, 0, buf_size); + + buffer = (char*) util_xmalloc(buf_size); + memset(buffer, 0, buf_size); + + timestamp = asset_nx2_usb_save_time_to_string(&stats->timestamp); + + util_str_format(merged, buf_size, + ">>> stats:\n" + "adler32: 0x%X\n" + "usb_serial: %s\n" + "timestamp: %s" + "avatar_id: %d\n" + "rank: %d\n" + "country_id: %d\n" + "player_id: %s\n" + "mileage: %d\n" + "play_count: %d\n" + "kcal: %d\n" + "v02: %d\n" + "worldmax_map_position: 0x%X\n" + "reward_count: %d\n" + "worldmax_count: %d\n", + stats->adler32, + stats->usb_serial, + timestamp, + stats->avatar_id, + stats->rank, + stats->country_id, + stats->player_id, + stats->mileage, + stats->play_count, + stats->kcal, + stats->v02, + stats->worldmax_map_position, + stats->reward_count, + stats->worldmax_count); + + free(timestamp); + + tmp = util_str_merge(merged, "song_unlocks:\n"); + + free(merged); + merged = tmp; + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_SONG_MAX; i++) { + + util_str_format(buffer, buf_size, + "song slot: %d\n" + "mode: 0x%X\n" + "unused: 0x%X\n" + "song: %d\n" + "reveal: %d\n", + i, + stats->song_unlocks[i].mode, + stats->song_unlocks[i].unused, + stats->song_unlocks[i].song, + stats->song_unlocks[i].reveal); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_MISSION_MAX; i++) { + + util_str_format(buffer, buf_size, + "mission slot: %d\n" + "clear_flags_directions: 0x%X\n" + "clear_flag_mission: %d\n" + "unused: 0x%X\n", + i, + stats->worldmax_mission_unlocks[i].clear_flags_directions, + stats->worldmax_mission_unlocks[i].clear_flag_mission, + stats->worldmax_mission_unlocks[i].unused); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_MISSION_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_high_score %d: %d\n", + i, + stats->worldmax_high_score[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_MISSION_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_challenge %d: %d\n", + i, + stats->worldmax_challenge[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_BARRICADE_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_barricade %d:\n" + "flag_open: %d\n" + "unused: 0x%X\n", + i, + stats->worldmax_barricade[i].flag_open, + stats->worldmax_barricade[i].unused); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_EVENT_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_event %d:\n" + "flag_cleared: %d\n" + "unused: 0x%X\n", + i, + stats->worldmax_event[i].flag_cleared, + stats->worldmax_event[i].unused); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_WARP_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_warp %d:\n" + "direction_clear: 0x%X\n" + "clear: %d\n", + "unused: 0x%X\n", + i, + stats->worldmax_warp[i].direction_clear, + stats->worldmax_warp[i].clear, + stats->worldmax_warp[i].unused); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_WORLDMAX_LOC_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_current_land %d: %d\n", + i, + stats->worldmax_current_land[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_WORLDMAX_LOC_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_current_mission %d: %d\n", + i, + stats->worldmax_current_mission[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NX2_USB_SAVE_SONG_MAX; i++) { + + for (uint32_t j = 0; j < ASSET_NX2_USB_SAVE_NUM_MODES; j++) { + util_str_format(buffer, buf_size, + "song_scores %d/%d:\n" + "score: %d\n" + "player_id: %s\n", + i, + j, + stats->song_scores[i][j].score, + stats->song_scores[i][j].player_id); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + } + + free(buffer); + + return merged; +} + +struct asset_nx2_usb_save* asset_nx2_usb_save_new(void) +{ + struct asset_nx2_usb_save* save; + char name[10]; + + save = (struct asset_nx2_usb_save*) util_xmalloc( + sizeof(struct asset_nx2_usb_save)); + + memset(save, 0, sizeof(struct asset_nx2_usb_save)); + + sprintf(name, "NX%06d", rand() % 1000000); + strcpy(save->review.player_id, name); + strcpy(save->stats.player_id, name); + + /* default world map position */ + save->stats.worldmax_map_position = 0xEF001; + + /* nx2 release date as default timestamp */ + save->stats.timestamp.year = 2008; + save->stats.timestamp.month = 1; + save->stats.timestamp.day = 21; + save->stats.timestamp.hour = 0; + save->stats.timestamp.min = 0; + save->stats.timestamp.ms = 0; + + return save; +} + +void asset_nx2_usb_save_finalize(struct asset_nx2_usb_save* save) +{ + /* checksum update */ + save->stats.adler32 = util_adler32_calc(1, + ((const uint8_t*) &save->stats) + 4, + sizeof(struct asset_nx2_usb_save_stats) - 4); +} + +char* asset_nx2_usb_save_to_string(const struct asset_nx2_usb_save* save) +{ + char* review; + char* stats; + char* merged; + + review = asset_nx2_usb_save_review_to_string(&save->review); + stats = asset_nx2_usb_save_stats_to_string(&save->stats); + + merged = util_str_merge(review, stats); + + free(review); + free(stats); + + return merged; +} + +void asset_nx2_usb_save_decrypt(uint8_t* buf, size_t len) +{ + /* skip non encrypted review area */ + buf += sizeof(struct asset_nx2_usb_save_review); + len -= sizeof(struct asset_nx2_usb_save_review); + + for (size_t a = len - 1; a > 0; --a) { + buf[a] = (buf[a] ^ buf[a - 1]) + ((a * 1234567) >> 8); + } +} + +void asset_nx2_usb_save_encrypt(uint8_t* buf, size_t len) +{ + /* skip non encrypted review area */ + buf += sizeof(struct asset_nx2_usb_save_review); + len -= sizeof(struct asset_nx2_usb_save_review); + + for (size_t a = 1; a < len; ++a) { + buf[a] = (buf[a] - ((a * 1234567) >> 8)) ^ buf[a - 1]; + } +} \ No newline at end of file diff --git a/src/main/asset/nx2/lib/usb-save.h b/src/main/asset/nx2/lib/usb-save.h new file mode 100644 index 0000000..01e71b9 --- /dev/null +++ b/src/main/asset/nx2/lib/usb-save.h @@ -0,0 +1,122 @@ +#ifndef ASSET_NX2_USB_SAVE_H +#define ASSET_NX2_USB_SAVE_H + +#include +#include + +#define ASSET_NX2_USB_SAVE_SIZE 30780 + +enum asset_nx2_usb_save_limits { + ASSET_NX2_USB_SAVE_SERIAL_ID_MAX = 64, + ASSET_NX2_USB_SAVE_PLAYER_ID_MAX = 12, + ASSET_NX2_USB_SAVE_WORLDMAX_LOC_MAX = 128, + ASSET_NX2_USB_SAVE_SONG_MAX = 256, + ASSET_NX2_USB_SAVE_MISSION_MAX = 1024, + ASSET_NX2_USB_SAVE_BARRICADE_MAX = 16, + ASSET_NX2_USB_SAVE_EVENT_MAX = 128, + ASSET_NX2_USB_SAVE_WARP_MAX = 8, + ASSET_NX2_USB_SAVE_NUM_MODES = 5, +}; + +struct asset_nx2_usb_save_time { + int16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint16_t ms; +} __attribute__((__packed__)); + +struct asset_nx2_usb_save_review { + char player_id[ASSET_NX2_USB_SAVE_PLAYER_ID_MAX]; + int32_t mileage; + int32_t reward_count; + int32_t worldmax_count; + int32_t play_count; + char worldmax_current_land[ASSET_NX2_USB_SAVE_WORLDMAX_LOC_MAX]; + char worldmax_current_mission[ASSET_NX2_USB_SAVE_WORLDMAX_LOC_MAX]; + int32_t kcal; + int32_t v02; +} __attribute__((__packed__)); + +struct asset_nx2_usb_save_stats { + uint32_t adler32; + char usb_serial[ASSET_NX2_USB_SAVE_SERIAL_ID_MAX]; + struct asset_nx2_usb_save_time timestamp; + int32_t avatar_id; + int32_t rank; + int32_t country_id; + char player_id[ASSET_NX2_USB_SAVE_PLAYER_ID_MAX]; + int32_t mileage; + int32_t play_count; + int32_t kcal; + int32_t v02; + int32_t worldmax_map_position; + int32_t reward_count; + int32_t worldmax_count; + + struct { + /* bits 0-4 for modes/stepcharts */ + uint8_t mode : 5; + /* unused bit */ + uint8_t unused : 1; + /* bit 6 song itself */ + uint8_t song : 1; + /* bit 7 reveal flag */ + uint8_t reveal : 1; + } song_unlocks[ASSET_NX2_USB_SAVE_SONG_MAX] __attribute__((__packed__)); + + struct { + uint8_t clear_flags_directions : 4; + uint8_t clear_flag_mission : 1; + uint8_t unused : 3; + } worldmax_mission_unlocks[ASSET_NX2_USB_SAVE_MISSION_MAX] + __attribute__((__packed__)); + + int32_t worldmax_high_score[ASSET_NX2_USB_SAVE_MISSION_MAX]; + int32_t worldmax_challenge[ASSET_NX2_USB_SAVE_MISSION_MAX]; + + struct { + uint8_t flag_open : 1; + uint8_t unused : 7; + } worldmax_barricade[ASSET_NX2_USB_SAVE_BARRICADE_MAX] + __attribute__((__packed__)); + + struct { + uint8_t flag_cleared : 1; + uint8_t unused : 7; + } worldmax_event[ASSET_NX2_USB_SAVE_EVENT_MAX] __attribute__((__packed__)); + + struct { + uint8_t direction_clear : 4; + uint8_t clear : 1; + uint8_t unused : 3; + } worldmax_warp[ASSET_NX2_USB_SAVE_WARP_MAX] __attribute__((__packed__)); + + char worldmax_current_land[ASSET_NX2_USB_SAVE_WORLDMAX_LOC_MAX]; + char worldmax_current_mission[ASSET_NX2_USB_SAVE_WORLDMAX_LOC_MAX]; + + struct { + int32_t score; + char player_id[ASSET_NX2_USB_SAVE_PLAYER_ID_MAX]; + } song_scores[ASSET_NX2_USB_SAVE_SONG_MAX][ASSET_NX2_USB_SAVE_NUM_MODES] + __attribute__((__packed__)); +} __attribute__((__packed__)); + +struct asset_nx2_usb_save { + struct asset_nx2_usb_save_review review; + struct asset_nx2_usb_save_stats stats; +} __attribute__((__packed__)); + +struct asset_nx2_usb_save* asset_nx2_usb_save_new(void); + +// update checksum and prepare profile to get encrypted +void asset_nx2_usb_save_finalize(struct asset_nx2_usb_save* save); + +char* asset_nx2_usb_save_to_string(const struct asset_nx2_usb_save* save); + +void asset_nx2_usb_save_decrypt(uint8_t* buf, size_t len); + +void asset_nx2_usb_save_encrypt(uint8_t* buf, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/asset/nx2/lib/util.c b/src/main/asset/nx2/lib/util.c new file mode 100644 index 0000000..ca8b04f --- /dev/null +++ b/src/main/asset/nx2/lib/util.c @@ -0,0 +1,100 @@ +#define LOG_MODULE "asset-nx2-util" + +#include + +#include "util/fs.h" +#include "util/log.h" + +#include "util.h" + +struct asset_nx2_usb_rank* asset_nx2_usb_rank_load_from_file(const char* path, + bool encrypted) +{ + struct asset_nx2_usb_rank* rank; + size_t size; + + if (!util_file_load(path, (void**) &rank, &size, false)) { + log_error("Loading nx2 rank file %s failed", path); + return NULL; + } + + if (size != sizeof(struct asset_nx2_usb_rank)) { + log_error("Invalid size of nx2 rank file %s: %d != %d", path, size, + sizeof(struct asset_nx2_usb_rank)); + free(rank); + return NULL; + } + + if (encrypted) { + asset_nx2_usb_rank_decrypt((uint8_t*) rank, size); + } + + return rank; +} + +bool asset_nx2_usb_rank_save_to_file(const char* path, + const struct asset_nx2_usb_rank* rank, bool encrypt) +{ + struct asset_nx2_usb_rank rank_enc; + + memcpy(&rank_enc, rank, sizeof(struct asset_nx2_usb_rank)); + + if (encrypt) { + asset_nx2_usb_rank_encrypt((uint8_t*) &rank_enc, + sizeof(struct asset_nx2_usb_rank)); + } + + if (!util_file_save(path, (void**) &rank_enc, + sizeof(struct asset_nx2_usb_rank))) { + log_error("Storing nx2 rank data to %s failed", path); + return false; + } + + return true; +} + +struct asset_nx2_usb_save* asset_nx2_usb_save_load_from_file(const char* path, + bool encrypted) +{ + struct asset_nx2_usb_save* save; + size_t size; + + if (!util_file_load(path, (void**) &save, &size, false)) { + log_error("Loading nx2 save file %s failed", path); + return NULL; + } + + if (size != sizeof(struct asset_nx2_usb_save)) { + log_error("Invalid size of nx2 save file %s: %d != %d", path, size, + sizeof(struct asset_nx2_usb_save)); + free(save); + return NULL; + } + + if (encrypted) { + asset_nx2_usb_save_decrypt((uint8_t*) save, size); + } + + return save; +} + +bool asset_nx2_usb_save_save_to_file(const char* path, + const struct asset_nx2_usb_save* save, bool encrypt) +{ + struct asset_nx2_usb_save save_enc; + + memcpy(&save_enc, save, sizeof(struct asset_nx2_usb_save)); + + if (encrypt) { + asset_nx2_usb_save_encrypt((uint8_t*) &save_enc, + sizeof(struct asset_nx2_usb_save)); + } + + if (!util_file_save(path, (const void*) &save_enc, + sizeof(struct asset_nx2_usb_save))) { + log_error("Storing nx2 save data to %s failed", path); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/main/asset/nx2/lib/util.h b/src/main/asset/nx2/lib/util.h new file mode 100644 index 0000000..1a42d6d --- /dev/null +++ b/src/main/asset/nx2/lib/util.h @@ -0,0 +1,21 @@ +#ifndef ASSET_NX2_UTIL_H +#define ASSET_NX2_UTIL_H + +#include + +#include "usb-rank.h" +#include "usb-save.h" + +// load from file and decrypt, returns decrypted struct +struct asset_nx2_usb_rank* asset_nx2_usb_rank_load_from_file(const char* path, bool encrypted); + +// takes decrypted rank data, encrypts it and writes it to file +bool asset_nx2_usb_rank_save_to_file(const char* path, const struct asset_nx2_usb_rank* rank, bool encrypt); + +// load from file and decrypt, returns decrypted struct +struct asset_nx2_usb_save* asset_nx2_usb_save_load_from_file(const char* path, bool encrypted); + +// takes decrypted save data, encrypts it and writes it to file +bool asset_nx2_usb_save_save_to_file(const char* path, const struct asset_nx2_usb_save* save, bool encrypt); + +#endif \ No newline at end of file diff --git a/src/main/asset/nx2/tool/usb-profile.c b/src/main/asset/nx2/tool/usb-profile.c new file mode 100644 index 0000000..ba54629 --- /dev/null +++ b/src/main/asset/nx2/tool/usb-profile.c @@ -0,0 +1,154 @@ +/** + * Tool for NX2 profiles: decrypt, encrypt, print profile data + */ +#include +#include +#include + +#include "asset/nx2/lib/util.h" + +#include "util/str.h" + +int main(int argc, char** argv) +{ + int ret; + char* rank_path; + char* save_path; + + if (argc < 3) { + printf("Usage: %s [cmd: new, dec, enc, dump] " + "[path containing nx2rank.bin nx2save.bin]\n", argv[0]); + return -1; + } + + ret = 0; + + rank_path = util_str_merge(argv[2], "nx2rank.bin"); + save_path = util_str_merge(argv[2], "nx2save.bin"); + + if (!strcmp(argv[1], "new")) { + struct asset_nx2_usb_rank* rank = asset_nx2_usb_rank_new(); + struct asset_nx2_usb_save* save = asset_nx2_usb_save_new(); + + asset_nx2_usb_rank_finalize(rank); + asset_nx2_usb_save_finalize(save); + + if (!asset_nx2_usb_rank_save_to_file(rank_path, rank, true)) { + fprintf(stderr, "Creating rank file %s failed\n", rank_path); + ret = -2; + } + + if (!asset_nx2_usb_save_save_to_file(save_path, save, true)) { + fprintf(stderr, "Creating save file %s failed\n", save_path); + ret = -3; + } + + free(rank); + free(save); + } else { + struct asset_nx2_usb_rank* rank; + struct asset_nx2_usb_save* save; + bool encrypted; + + if (!strcmp(argv[1], "enc")) { + char* tmp; + + encrypted = false; + + tmp = util_str_merge(rank_path, ".dec"); + free(rank_path); + rank_path = tmp; + + tmp = util_str_merge(save_path, ".dec"); + free(save_path); + save_path = tmp; + } else { + encrypted = true; + } + + rank = asset_nx2_usb_rank_load_from_file(rank_path, encrypted); + save = asset_nx2_usb_save_load_from_file(save_path, encrypted); + + if (!rank) { + fprintf(stderr, "Loading %s failed", rank_path); + ret = -4; + goto cleanup; + } + + if (!save) { + fprintf(stderr, "Loading %s failed", save_path); + ret = -5; + free(rank); + goto cleanup; + } + + if (!strcmp(argv[1], "dec")) { + char* rank_path_dec; + char* save_path_dec; + + rank_path_dec = util_str_merge(rank_path, ".dec"); + save_path_dec = util_str_merge(save_path, ".dec"); + + if (!asset_nx2_usb_rank_save_to_file(rank_path_dec, rank, false)) { + fprintf(stderr, "Saving decrypted rank file to %s failed\n", + rank_path_dec); + ret = -6; + } + + if (!asset_nx2_usb_save_save_to_file(save_path_dec, save, false)) { + fprintf(stderr, "Saving decrypted save file to %s failed\n", + save_path_dec); + ret = -7; + } + + free(rank_path_dec); + free(save_path_dec); + } else if (!strcmp(argv[1], "enc")) { + char* rank_path_enc; + char* save_path_enc; + + rank_path_enc = util_str_merge(rank_path, ".enc"); + save_path_enc = util_str_merge(save_path, ".enc"); + + if (!asset_nx2_usb_rank_save_to_file(rank_path_enc, rank, true)) { + fprintf(stderr, "Saving encrypted rank file to %s failed\n", + rank_path_enc); + ret = -6; + } + + if (!asset_nx2_usb_save_save_to_file(save_path_enc, save, true)) { + fprintf(stderr, "Saving encrypted save file to %s failed\n", + save_path_enc); + ret = -7; + } + + free(rank_path_enc); + free(save_path_enc); + } else if (!strcmp(argv[1], "dump")) { + char* rank_str; + char* save_str; + + rank_str = asset_nx2_usb_rank_to_string(rank); + save_str = asset_nx2_usb_save_to_string(save); + + printf("----------------- rank -----------------\n" + "%s----------------- save -----------------\n%s", + rank_str, save_str); + + free(rank_str); + free(save_str); + } else { + fprintf(stderr, "Unknown command %s\n", argv[1]); + ret = -4; + } + + free(rank); + free(save); + } + +cleanup: + free(rank_path); + free(save_path); + + return ret; +} \ No newline at end of file diff --git a/src/main/asset/nxa/lib/usb-rank.c b/src/main/asset/nxa/lib/usb-rank.c new file mode 100644 index 0000000..2f17ce2 --- /dev/null +++ b/src/main/asset/nxa/lib/usb-rank.c @@ -0,0 +1,103 @@ +#include +#include + +#include "util/adler32.h" + +#include "util/mem.h" +#include "util/str.h" + +#include "usb-rank.h" + +struct asset_nxa_usb_rank* asset_nxa_usb_rank_new(void) +{ + struct asset_nxa_usb_rank* rank; + + rank = (struct asset_nxa_usb_rank*) + util_xmalloc(sizeof(struct asset_nxa_usb_rank)); + + memset(rank, 0, sizeof(struct asset_nxa_usb_rank)); + + return rank; +} + +void asset_nxa_usb_rank_finalize(struct asset_nxa_usb_rank* rank) +{ + rank->adler32 = util_adler32_calc(1, ((const uint8_t*) rank) + 4, + sizeof(struct asset_nxa_usb_rank) - 4); +} + +char* asset_nxa_usb_rank_to_string(const struct asset_nxa_usb_rank* rank) +{ + char* buffer; + char* buffer2; + char* merged; + char* usb_ser; + + buffer = (char*) util_xmalloc(512); + memset(buffer, 0, 512); + + usb_ser = util_str_buffer((const uint8_t*) rank->usb_serial, + ASSET_NXA_USB_RANK_USB_SERIAL_LEN); + + util_str_format(buffer, 512, + "adler32: 0x%X\n" + // "usb_serial: %s\n", + "num_rankings: %d\n" + "ranking entries:\n", + rank->adler32, + // usb_ser, + rank->num_rankings); + + for (uint32_t i = 0; i < ASSET_NXA_USB_RANK_MAX_RANK_ENTRIES; i++) { + + for (uint32_t j = 0; j < ASSET_NXA_USB_RANK_MAX_STAGES; j++) { + buffer2 = (char*) util_xmalloc(512); + memset(buffer2, 0, 512); + + util_str_format(buffer2, 512, + "%d/%d:\n" + "game_mode: %d\n" + "play_order: %d\n" + "play_score: %d\n" + "grade: %d\n" + "mileage: %d\n" + "play_time: %f\n" + "kcal: %f\n" + "v02: %f\n", + i, j, + rank->entries[i][j].game_mode, + rank->entries[i][j].play_order, + rank->entries[i][j].play_score, + rank->entries[i][j].grade, + rank->entries[i][j].mileage, + rank->entries[i][j].play_time, + rank->entries[i][j].kcal, + rank->entries[i][j].v02); + + merged = util_str_merge(buffer, buffer2); + + free(buffer); + free(buffer2); + + buffer = merged; + } + } + + free(usb_ser); + + return buffer; +} + +void asset_nxa_usb_rank_decrypt(uint8_t* buf, size_t len) +{ + for (size_t a = len - 1; a > 0; --a) { + buf[a] = (buf[a] ^ buf[a - 1]) + ((a * 1234567) >> 8); + } +} + +void asset_nxa_usb_rank_encrypt(uint8_t* buf, size_t len) +{ + for (size_t a = 1; a < len; ++a) { + buf[a] = (buf[a] - ((a * 1234567) >> 8)) ^ buf[a - 1]; + } +} \ No newline at end of file diff --git a/src/main/asset/nxa/lib/usb-rank.h b/src/main/asset/nxa/lib/usb-rank.h new file mode 100644 index 0000000..58d2b96 --- /dev/null +++ b/src/main/asset/nxa/lib/usb-rank.h @@ -0,0 +1,43 @@ +#ifndef ASSET_NXA_USB_RANK_H +#define ASSET_NXA_USB_RANK_H + +#include +#include + +enum asset_nxa_usb_rank_sizes { + ASSET_NXA_USB_RANK_MAX_STAGES = 3, + ASSET_NXA_USB_RANK_MAX_RANK_ENTRIES = 256, + ASSET_NXA_USB_RANK_USB_SERIAL_LEN = 64, +}; + +struct asset_nxa_usb_rank_entry { + int32_t game_mode; + int32_t play_order; + int32_t play_score; + int32_t grade; + int32_t mileage; + float play_time; + float kcal; + float v02; +} __attribute__((__packed__)); + +struct asset_nxa_usb_rank { + uint32_t adler32; + char usb_serial[ASSET_NXA_USB_RANK_USB_SERIAL_LEN]; + uint32_t num_rankings; + struct asset_nxa_usb_rank_entry + entries[ASSET_NXA_USB_RANK_MAX_RANK_ENTRIES][ASSET_NXA_USB_RANK_MAX_STAGES]; +} __attribute__((__packed__)); + +struct asset_nxa_usb_rank* asset_nxa_usb_rank_new(void); + +// update checksum and prepare profile to get encrypted +void asset_nxa_usb_rank_finalize(struct asset_nxa_usb_rank* rank); + +char* asset_nxa_usb_rank_to_string(const struct asset_nxa_usb_rank* rank); + +void asset_nxa_usb_rank_decrypt(uint8_t* buf, size_t len); + +void asset_nxa_usb_rank_encrypt(uint8_t* buf, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/asset/nxa/lib/usb-save.c b/src/main/asset/nxa/lib/usb-save.c new file mode 100644 index 0000000..3376128 --- /dev/null +++ b/src/main/asset/nxa/lib/usb-save.c @@ -0,0 +1,457 @@ +#include +#include + +#include "util/adler32.h" + +#include "util/mem.h" +#include "util/str.h" + +#include "usb-save.h" + +static char* asset_nxa_usb_save_time_to_string( + const struct asset_nxa_usb_save_time* time) +{ + char* buffer; + + buffer = (char*) util_xmalloc(128); + memset(buffer, 0, 128); + + util_str_format(buffer, 128, + "year: %d\n" + "month: %d\n" + "day: %d\n" + "hour: %d\n" + "min: %d\n" + "ms: %d\n", + time->year, + time->month, + time->day, + time->hour, + time->min, + time->ms); + + return buffer; +} + +static char* asset_nxa_usb_save_review_to_string( + const struct asset_nxa_usb_save_review* review) +{ + char* buffer; + char* land; + char* mission; + char* timestamp; + + buffer = (char*) util_xmalloc(1024); + memset(buffer, 0, 1024); + + land = util_str_buffer((const uint8_t*) review->worldmax_current_land, + ASSET_NXA_USB_SAVE_WORLDMAX_LOC_MAX); + mission = util_str_buffer((const uint8_t*) review->worldmax_current_mission, + ASSET_NXA_USB_SAVE_WORLDMAX_LOC_MAX); + timestamp = asset_nxa_usb_save_time_to_string(&review->timestamp); + + util_str_format(buffer, 1024, + ">>> review:\n" + "player_id: %s\n" + "mileage: %d\n" + "reward_count: %d\n" + "worldmax_count: %d\n" + "play_count: %d\n" + "worldmax_current_land: %s\n" + "worldmax_current_mission: %s\n" + "kcal: %d\n" + "v02: %d\n", + "timestamp: \n%s", + "running_step: %d\n", + "play_time_minutes: %f\n", + "total_completion_percentage: %f\n", + "arcade_percentage: %f\n", + "brain_shower_percentage: %f\n", + "special_percentage: %f\n", + review->player_id, + review->mileage, + review->reward_count, + review->worldmax_count, + review->play_count, + land, + mission, + review->kcal, + review->v02, + timestamp, + review->running_step, + review->play_time_minutes, + review->total_completion_percentage, + review->arcade_percentage, + review->brain_shower_percentage, + review->special_percentage); + + free(land); + free(mission); + free(timestamp); + + return buffer; +} + +static char* asset_nxa_usb_save_stats_to_string( + const struct asset_nxa_usb_save_stats* stats) +{ + char* merged; + char* buffer; + char* timestamp; + char* tmp; + char* tmp2; + const size_t buf_size = 16384; + + merged = (char*) util_xmalloc(buf_size); + memset(merged, 0, buf_size); + + buffer = (char*) util_xmalloc(buf_size); + memset(buffer, 0, buf_size); + + timestamp = asset_nxa_usb_save_time_to_string(&stats->timestamp); + + util_str_format(merged, buf_size, + ">>> stats:\n" + "adler32: 0x%X\n" + "usb_serial: %s\n" + "timestamp: %s" + "avatar_id: %d\n" + "rank: %d\n" + "country_id: %d\n" + "player_id: %s\n" + "mileage: %d\n" + "play_count: %d\n" + "kcal: %d\n" + "v02: %d\n" + "worldmax_map_position: 0x%X\n" + "reward_count: %d\n" + "worldmax_count: %d\n", + stats->adler32, + stats->usb_serial, + timestamp, + stats->avatar_id, + stats->rank, + stats->country_id, + stats->player_id, + stats->mileage, + stats->play_count, + stats->kcal, + stats->v02, + stats->worldmax_map_position, + stats->reward_count, + stats->worldmax_count); + + free(timestamp); + + tmp = util_str_merge(merged, "song_unlocks:\n"); + + free(merged); + merged = tmp; + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_SONG_MAX; i++) { + + util_str_format(buffer, buf_size, + "song slot: %d\n" + "mode: 0x%X\n" + "unused: 0x%X\n" + "song: %d\n" + "reveal: %d\n", + i, + stats->song_unlocks[i].mode, + stats->song_unlocks[i].unused, + stats->song_unlocks[i].song, + stats->song_unlocks[i].reveal); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_MISSION_MAX; i++) { + + util_str_format(buffer, buf_size, + "mission slot: %d\n" + "clear_flags_directions: 0x%X\n" + "clear_flag_mission: %d\n" + "unused: 0x%X\n", + i, + stats->worldmax_mission_unlocks[i].clear_flags_directions, + stats->worldmax_mission_unlocks[i].clear_flag_mission, + stats->worldmax_mission_unlocks[i].unused); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_MISSION_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_high_score %d: %d\n", + i, + stats->worldmax_high_score[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_MISSION_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_challenge %d: %d\n", + i, + stats->worldmax_challenge[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_BARRICADE_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_barricade %d:\n" + "flag_open: %d\n" + "unused: 0x%X\n", + i, + stats->worldmax_barricade[i].flag_open, + stats->worldmax_barricade[i].unused); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_EVENT_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_event %d:\n" + "flag_cleared: %d\n" + "unused: 0x%X\n", + i, + stats->worldmax_event[i].flag_cleared, + stats->worldmax_event[i].unused); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_WARP_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_warp %d:\n" + "direction_clear: 0x%X\n" + "clear: %d\n", + "unused: 0x%X\n", + i, + stats->worldmax_warp[i].direction_clear, + stats->worldmax_warp[i].clear, + stats->worldmax_warp[i].unused); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_WORLDMAX_LOC_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_current_land %d: %d\n", + i, + stats->worldmax_current_land[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_WORLDMAX_LOC_MAX; i++) { + + util_str_format(buffer, buf_size, + "worldmax_current_mission %d: %d\n", + i, + stats->worldmax_current_mission[i]); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + + for (uint32_t i = 0; i < ASSET_NXA_USB_SAVE_SONG_MAX; i++) { + + for (uint32_t j = 0; j < ASSET_NXA_USB_SAVE_NUM_MODES; j++) { + util_str_format(buffer, buf_size, + "song_scores %d/%d:\n" + "score: %d\n" + "player_id: %s\n", + i, + j, + stats->song_scores[i][j].score, + stats->song_scores[i][j].player_id); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + } + } + + /* ------------- */ + util_str_format(buffer, buf_size, + "security_dongle_mfgid 0x%X\n" + "unlock_signal: 0x%X\n" + "running_steps: %d\n" + "total_play_time_min: %f\n" + "total_completion_percentage: %f\n" + "arcade_completion_percentage: %f\n" + "brain_shower_completion_percentage: %f\n" + "special_stage_completion_percentage: %f\n", + stats->security_dongle_mfgid, + stats->unlock_signal, + stats->running_steps, + stats->total_play_time_min, + stats->total_completion_percentage, + stats->arcade_completion_percentage, + stats->brain_shower_completion_percentage, + stats->special_stage_completion_percentage); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + + /* ------------- */ + tmp2 = util_str_buffer(stats->unkn_some_map_scores_to_song_ids, + 4096); + + util_str_format(buffer, buf_size, + "unkn_some_map_scores_to_song_ids: %s\n", + tmp2); + + tmp = util_str_merge(merged, buffer); + + free(tmp2); + free(merged); + merged = tmp; + + /* ------------- */ + tmp2 = util_str_buffer(stats->unkn2, sizeof(stats->unkn2)); + + util_str_format(buffer, buf_size, + "unkn2: %s\n", + tmp2); + + tmp = util_str_merge(merged, buffer); + + free(tmp2); + free(merged); + merged = tmp; + + /* ------------- */ + util_str_format(buffer, buf_size, + "break_item_slot 0x%X\n" + "func_item_slot: 0x%X\n" + "bga_item_slot: 0x%X\n" + "time_item_slot: 0x%X\n" + "unknown: 0x%X\n", + stats->break_item_slot, + stats->func_item_slot, + stats->bga_item_slot, + stats->time_item_slot, + stats->unknown); + + tmp = util_str_merge(merged, buffer); + + free(merged); + merged = tmp; + + free(buffer); + + return merged; +} + +struct asset_nxa_usb_save* asset_nxa_usb_save_new(void) +{ + struct asset_nxa_usb_save* save; + char name[10]; + + save = (struct asset_nxa_usb_save*) util_xmalloc( + sizeof(struct asset_nxa_usb_save)); + + memset(save, 0, sizeof(struct asset_nxa_usb_save)); + + sprintf(name, "NX%06d", rand() % 1000000); + strcpy(save->review.player_id, name); + strcpy(save->stats.player_id, name); + + /* default world map position */ + save->stats.worldmax_map_position = 0xEF001; + + /* nxa release date as default timestamp */ + save->stats.timestamp.year = 2008; + save->stats.timestamp.month = 12; + save->stats.timestamp.day = 26; + save->stats.timestamp.hour = 0; + save->stats.timestamp.min = 0; + save->stats.timestamp.ms = 0; + + return save; +} + +void asset_nxa_usb_save_finalize(struct asset_nxa_usb_save* save) +{ + /* checksum update, expected size for area to checksum: 90540 */ + save->stats.adler32 = util_adler32_calc(1, + ((const uint8_t*) &save->stats) + 4, + sizeof(struct asset_nxa_usb_save_stats) - 4); +} + +char* asset_nxa_usb_save_to_string(const struct asset_nxa_usb_save* save) +{ + char* review; + char* stats; + char* merged; + + review = asset_nxa_usb_save_review_to_string(&save->review); + stats = asset_nxa_usb_save_stats_to_string(&save->stats); + + merged = util_str_merge(review, stats); + + free(review); + free(stats); + + return merged; +} + +void asset_nxa_usb_save_decrypt(uint8_t* buf, size_t len) +{ + /* skip non encrypted review area */ + buf += sizeof(struct asset_nxa_usb_save_review); + len -= sizeof(struct asset_nxa_usb_save_review); + + for (size_t a = len - 1; a > 0; --a) { + buf[a] = (buf[a] ^ buf[a - 1]) + ((a * 1234567) >> 8); + } +} + +void asset_nxa_usb_save_encrypt(uint8_t* buf, size_t len) +{ + /* skip non encrypted review area */ + buf += sizeof(struct asset_nxa_usb_save_review); + len -= sizeof(struct asset_nxa_usb_save_review); + + for (size_t a = 1; a < len; ++a) { + buf[a] = (buf[a] - ((a * 1234567) >> 8)) ^ buf[a - 1]; + } +} \ No newline at end of file diff --git a/src/main/asset/nxa/lib/usb-save.h b/src/main/asset/nxa/lib/usb-save.h new file mode 100644 index 0000000..6b04118 --- /dev/null +++ b/src/main/asset/nxa/lib/usb-save.h @@ -0,0 +1,275 @@ +#ifndef ASSET_NXA_USB_SAVE_H +#define ASSET_NXA_USB_SAVE_H + +#include +#include + +enum asset_nxa_usb_save_limits { + ASSET_NXA_USB_SAVE_SERIAL_ID_MAX = 64, + ASSET_NXA_USB_SAVE_PLAYER_ID_MAX = 12, + ASSET_NXA_USB_SAVE_WORLDMAX_LOC_MAX = 128, + ASSET_NXA_USB_SAVE_SONG_MAX = 256, + ASSET_NXA_USB_SAVE_MISSION_MAX = 1024, + ASSET_NXA_USB_SAVE_BARRICADE_MAX = 16, + ASSET_NXA_USB_SAVE_EVENT_MAX = 128, + ASSET_NXA_USB_SAVE_WARP_MAX = 8, + ASSET_NXA_USB_SAVE_NUM_MODES = 5, +}; + +struct asset_nxa_usb_save_time { + int16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint16_t ms; +} __attribute__((__packed__)); + +/* Not encrypted and read only, offset 0x00 to 0x144 (excl) */ +struct asset_nxa_usb_save_review { + /* 0x00, 12 byte char array: max char length is 8 which gets displayed + ingame */ + char player_id[ASSET_NXA_USB_SAVE_PLAYER_ID_MAX]; + /* 0x0C, 4 byte int */ + uint32_t mileage; + /* 0x10, 4 byte int: achieved reward count, max 65 */ + uint32_t reward_count; + /* 0x14, 4 byte int: successful mission count, max 634 */ + uint32_t worldmax_count; + /* 0x18, 4 byte int */ + uint32_t play_count; + /* 0x1C, 128 byte char array */ + char worldmax_current_land[ASSET_NXA_USB_SAVE_WORLDMAX_LOC_MAX]; + /* 0x9C, 128 byte char array */ + char worldmax_current_mission[ASSET_NXA_USB_SAVE_WORLDMAX_LOC_MAX]; + /* 0x11C, 4 byte int */ + uint32_t kcal; + /* 0x120, 4 byte int */ + uint32_t v02; + /* 0x124, 8 byte */ + struct asset_nxa_usb_save_time timestamp; + /* 0x12C, 4 byte int */ + uint32_t running_step; + /* 0x130, 4 byte float */ + float play_time_minutes; + /* 0x134, 4 byte float */ + float total_completion_percentage; + /* 0x138, 4 byte float */ + float arcade_percentage; + /* 0x13C, 4 byte float */ + float brain_shower_percentage; + /* 0x140, 4 byte float */ + float special_percentage; +} __attribute__((__packed__)); + +struct asset_nxa_usb_save_stats { + /* 0x144, 4 byte int */ + uint32_t adler32; + /* 0x148, 64 byte */ + char usb_serial[ASSET_NXA_USB_SAVE_SERIAL_ID_MAX]; + /* 0x188, 8 byte */ + struct asset_nxa_usb_save_time timestamp; + /* 0x190, 4 byte int */ + int32_t avatar_id; + /* 0x194, 4 byte int: setting this non null will give you a trophy */ + int32_t rank; + /* 0x198, 4 byte int: seems unused */ + int32_t country_id; + /* 0x19C, 12 byte char array */ + char player_id[ASSET_NXA_USB_SAVE_PLAYER_ID_MAX]; + /* 0x1A8, 4 byte int */ + int32_t mileage; + /* 0x1AD, 4 byte int */ + int32_t play_count; + /* 0x1B0, 4 byte int */ + int32_t kcal; + /* 0x1B4, 4 byte int */ + int32_t v02; + /* 0x1B8, 4 byte int */ + int32_t worldmax_map_position; + /* 0x1BC, 4 byte int: achieved reward count, max 65 */ + int32_t reward_count; + /* 0x1C0, 4 byte int: successful mission count, max 634 */ + int32_t worldmax_count; + + /* 0x1C4, 256 bytes */ + struct { + /* bits 0-4 for modes/stepcharts */ + uint8_t mode : 5; + /* unused bit */ + uint8_t unused : 1; + /* bit 6 song itself */ + uint8_t song : 1; + /* bit 7 reveal flag */ + uint8_t reveal : 1; + } song_unlocks[ASSET_NXA_USB_SAVE_SONG_MAX] __attribute__((__packed__)); + + /* 0x2C4, 1024 bytes */ + struct { + uint8_t clear_flags_directions : 4; + uint8_t clear_flag_mission : 1; + uint8_t unused : 3; + } worldmax_mission_unlocks[ASSET_NXA_USB_SAVE_MISSION_MAX] + __attribute__((__packed__)); + + /* 0x6C4, 4 byte int * 1024: high score for each mission */ + int32_t worldmax_high_score[ASSET_NXA_USB_SAVE_MISSION_MAX]; + + /* 0x16C4, 4 byte int * 1024 */ + int32_t worldmax_challenge[ASSET_NXA_USB_SAVE_MISSION_MAX]; + + /* 0x26C4, 16 bytes + 0x00 for barricade clear and 0x01 for block */ + struct { + uint8_t flag_open : 1; + uint8_t unused : 7; + } worldmax_barricade[ASSET_NXA_USB_SAVE_BARRICADE_MAX] + __attribute__((__packed__)); + + /* 0x26D4, 128 bytes + 0x00 for event clear and 0x01 for not cleared */ + struct { + uint8_t flag_cleared : 1; + uint8_t unused : 7; + } worldmax_event[ASSET_NXA_USB_SAVE_EVENT_MAX] __attribute__((__packed__)); + + /* 0x2754, 8 bytes + bit 0~3: clear bit for each direction bit:4 clear */ + struct { + uint8_t direction_clear : 4; + uint8_t clear : 1; + uint8_t unused : 3; + } worldmax_warp[ASSET_NXA_USB_SAVE_WARP_MAX] __attribute__((__packed__)); + + /* 0x275C, 128 byte char array */ + char worldmax_current_land[ASSET_NXA_USB_SAVE_WORLDMAX_LOC_MAX]; + + /* 0x27DC, 128 byte char array */ + char worldmax_current_mission[ASSET_NXA_USB_SAVE_WORLDMAX_LOC_MAX]; + + /* 0x285C */ + struct { + int32_t score; + char player_id[ASSET_NXA_USB_SAVE_PLAYER_ID_MAX]; + } song_scores[ASSET_NXA_USB_SAVE_SONG_MAX][ASSET_NXA_USB_SAVE_NUM_MODES] + __attribute__((__packed__)); + + /* 0x785C, 4 byte int */ + uint32_t security_dongle_mfgid; + + /* 0x7860, 4 byte int: <=3 Tells game to unlock new song */ + uint32_t unlock_signal; + + /* 0x7864, 4 byte int */ + uint32_t running_steps; + + /* 0x7868, 4 byte float */ + float total_play_time_min; + + /* 0x786C, 4 byte float */ + float total_completion_percentage; + + /* 0x7870, 4 byte float */ + float arcade_completion_percentage; + + /* 0x7874, 4 byte float */ + float brain_shower_completion_percentage; + + /* 0x7878, 4 byte float */ + float special_stage_completion_percentage; + + /* 0x787C TODO unknown + 0x787C, 1 byte char * 4096 - Used by GetSongIndex - + Assuming Maps Scores to Song IDs */ + uint8_t unkn_some_map_scores_to_song_ids[4096]; + + /* 0x887C TODO unknown + 0x887C, 1 byte char * 576 - Song Locks/Unlocks - 0x40 Unlocks (Special Zone, etc.) + 0x8A70 , 1 byte char * 36 - WorldMax Keys and various other events for completion + 0x8A9C, 8 byte char* - A Worldmax ID (Perhaps one to mark progress in Space) + 0x8AA4, 1 byte char * 24 - More WorldMax Event Switches */ + uint8_t unkn2[576]; + + /* 0x8ABC, 4 byte int: possible values + 0x1 - grey + 0x2 - red + 0x3 - yellow + 0x4 - green + 0x5 - Dark Green + 0x6 - aqua + 0x7 - blue + 0x8 - purple + 0x9 - pink + 0xA - light grey + 0xB - Black + 0xC - Orange + */ + uint32_t break_item_slot; + + /* 0x8AC0, 4 byte int + 0x1 - Shield + 0x2 - PermaShield + 0x3 - Line Searcher + 0x4 - Perma Line Searcher + 0x5 - Life Up + 0x6 - Perma Life Up + 0x7 - Lucky + 0x8 - Perma Lucky + 0x9 - Portal Pass + */ + uint32_t func_item_slot; + + /* 0x8AC4, 4 byte int + 0x1 - BGA OFF + 0x2 - Perma BGA OFF + */ + uint32_t bga_item_slot; + + /* 0x8AC4, 4 byte int + 0x1 - 10+ + 0x2 - 20+ + 0x3 - 30+ + */ + uint32_t time_item_slot; + + /* 0x8AC8, 4 byte int */ + uint32_t unknown; + + /* 0x8ACC, unknown stuff: More WorldMax Event Switches (Unused)? */ + uint8_t unknown2[12]; + + /* 0x8AD8, 4 byte float - Unknown What this Does (Unused) */ + float unknown3; + + /* 0x8ADC, totally unknown stuff, just nulls */ + uint8_t unknown4[0x7F8]; + + /* 0x92D4 */ + uint8_t unknown5[0xD01C]; + + /* TODO missing switches for dark/light world stuff + 0x8ACC, 1 byte char - More WorldMax Event Switches (Unused) + 0x8AD8 , 4 byte float - Unknown What this Does (Unused) + 0x92D4, High Score Table [See Below] + 0x132D0, 5216 bytes - Unknown Table (Unused) + 0x162D0, 24 bytes - Unknown Footer (Unused by Game - Most Likely PumBI) + */ +} __attribute__((__packed__)); + +struct asset_nxa_usb_save { + struct asset_nxa_usb_save_review review; + struct asset_nxa_usb_save_stats stats; +} __attribute__((__packed__)); + +struct asset_nxa_usb_save* asset_nxa_usb_save_new(void); + +/* update checksum and prepare profile to get encrypted */ +void asset_nxa_usb_save_finalize(struct asset_nxa_usb_save* save); + +char* asset_nxa_usb_save_to_string(const struct asset_nxa_usb_save* save); + +void asset_nxa_usb_save_decrypt(uint8_t* buf, size_t len); + +void asset_nxa_usb_save_encrypt(uint8_t* buf, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/asset/nxa/lib/util.c b/src/main/asset/nxa/lib/util.c new file mode 100644 index 0000000..0f1c485 --- /dev/null +++ b/src/main/asset/nxa/lib/util.c @@ -0,0 +1,100 @@ +#define LOG_MODULE "asset-nxa-util" + +#include + +#include "util/fs.h" +#include "util/log.h" + +#include "util.h" + +struct asset_nxa_usb_rank* asset_nxa_usb_rank_load_from_file(const char* path, + bool encrypted) +{ + struct asset_nxa_usb_rank* rank; + size_t size; + + if (!util_file_load(path, (void**) &rank, &size, false)) { + log_error("Loading nxa rank file %s failed", path); + return NULL; + } + + if (size != sizeof(struct asset_nxa_usb_rank)) { + log_error("Invalid size of nxa rank file %s: %d != %d", path, size, + sizeof(struct asset_nxa_usb_rank)); + free(rank); + return NULL; + } + + if (encrypted) { + asset_nxa_usb_rank_decrypt((uint8_t*) rank, size); + } + + return rank; +} + +bool asset_nxa_usb_rank_save_to_file(const char* path, + const struct asset_nxa_usb_rank* rank, bool encrypt) +{ + struct asset_nxa_usb_rank rank_enc; + + memcpy(&rank_enc, rank, sizeof(struct asset_nxa_usb_rank)); + + if (encrypt) { + asset_nxa_usb_rank_encrypt((uint8_t*) &rank_enc, + sizeof(struct asset_nxa_usb_rank)); + } + + if (!util_file_save(path, (void**) &rank_enc, + sizeof(struct asset_nxa_usb_rank))) { + log_error("Storing nxa rank data to %s failed", path); + return false; + } + + return true; +} + +struct asset_nxa_usb_save* asset_nxa_usb_save_load_from_file(const char* path, + bool encrypted) +{ + struct asset_nxa_usb_save* save; + size_t size; + + if (!util_file_load(path, (void**) &save, &size, false)) { + log_error("Loading nxa save file %s failed", path); + return NULL; + } + + if (size != sizeof(struct asset_nxa_usb_save)) { + log_error("Invalid size of nxa save file %s: %d != %d", path, size, + sizeof(struct asset_nxa_usb_save)); + free(save); + return NULL; + } + + if (encrypted) { + asset_nxa_usb_save_decrypt((uint8_t*) save, size); + } + + return save; +} + +bool asset_nxa_usb_save_save_to_file(const char* path, + const struct asset_nxa_usb_save* save, bool encrypt) +{ + struct asset_nxa_usb_save save_enc; + + memcpy(&save_enc, save, sizeof(struct asset_nxa_usb_save)); + + if (encrypt) { + asset_nxa_usb_save_encrypt((uint8_t*) &save_enc, + sizeof(struct asset_nxa_usb_save)); + } + + if (!util_file_save(path, (const void*) &save_enc, + sizeof(struct asset_nxa_usb_save))) { + log_error("Storing nxa save data to %s failed", path); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/main/asset/nxa/lib/util.h b/src/main/asset/nxa/lib/util.h new file mode 100644 index 0000000..15ccd9c --- /dev/null +++ b/src/main/asset/nxa/lib/util.h @@ -0,0 +1,21 @@ +#ifndef ASSET_NXA_UTIL_H +#define ASSET_NXA_UTIL_H + +#include + +#include "usb-rank.h" +#include "usb-save.h" + +// load from file and decrypt, returns decrypted struct +struct asset_nxa_usb_rank* asset_nxa_usb_rank_load_from_file(const char* path, bool encrypted); + +// takes decrypted rank data, encrypts it and writes it to file +bool asset_nxa_usb_rank_save_to_file(const char* path, const struct asset_nxa_usb_rank* rank, bool encrypt); + +// load from file and decrypt, returns decrypted struct +struct asset_nxa_usb_save* asset_nxa_usb_save_load_from_file(const char* path, bool encrypted); + +// takes decrypted save data, encrypts it and writes it to file +bool asset_nxa_usb_save_save_to_file(const char* path, const struct asset_nxa_usb_save* save, bool encrypt); + +#endif \ No newline at end of file diff --git a/src/main/asset/nxa/tool/usb-profile.c b/src/main/asset/nxa/tool/usb-profile.c new file mode 100644 index 0000000..0cf75ae --- /dev/null +++ b/src/main/asset/nxa/tool/usb-profile.c @@ -0,0 +1,154 @@ +/** + * Tool for NXA profiles: decrypt, encrypt, print profile data + */ +#include +#include +#include + +#include "asset/nxa/lib/util.h" + +#include "util/str.h" + +int main(int argc, char** argv) +{ + int ret; + char* rank_path; + char* save_path; + + if (argc < 3) { + printf("Usage: %s [cmd: new, dec, enc, dump] " + "[path containing nxarank.bin nxasave.bin]\n", argv[0]); + return -1; + } + + ret = 0; + + rank_path = util_str_merge(argv[2], "nxarank.bin"); + save_path = util_str_merge(argv[2], "nxasave.bin"); + + if (!strcmp(argv[1], "new")) { + struct asset_nxa_usb_rank* rank = asset_nxa_usb_rank_new(); + struct asset_nxa_usb_save* save = asset_nxa_usb_save_new(); + + asset_nxa_usb_rank_finalize(rank); + asset_nxa_usb_save_finalize(save); + + if (!asset_nxa_usb_rank_save_to_file(rank_path, rank, true)) { + fprintf(stderr, "Creating rank file %s failed\n", rank_path); + ret = -2; + } + + if (!asset_nxa_usb_save_save_to_file(save_path, save, true)) { + fprintf(stderr, "Creating save file %s failed\n", save_path); + ret = -3; + } + + free(rank); + free(save); + } else { + struct asset_nxa_usb_rank* rank; + struct asset_nxa_usb_save* save; + bool encrypted; + + if (!strcmp(argv[1], "enc")) { + char* tmp; + + encrypted = false; + + tmp = util_str_merge(rank_path, ".dec"); + free(rank_path); + rank_path = tmp; + + tmp = util_str_merge(save_path, ".dec"); + free(save_path); + save_path = tmp; + } else { + encrypted = true; + } + + rank = asset_nxa_usb_rank_load_from_file(rank_path, encrypted); + save = asset_nxa_usb_save_load_from_file(save_path, encrypted); + + if (!rank) { + fprintf(stderr, "Loading %s failed", rank_path); + ret = -4; + goto cleanup; + } + + if (!save) { + fprintf(stderr, "Loading %s failed", save_path); + ret = -5; + free(rank); + goto cleanup; + } + + if (!strcmp(argv[1], "dec")) { + char* rank_path_dec; + char* save_path_dec; + + rank_path_dec = util_str_merge(rank_path, ".dec"); + save_path_dec = util_str_merge(save_path, ".dec"); + + if (!asset_nxa_usb_rank_save_to_file(rank_path_dec, rank, false)) { + fprintf(stderr, "Saving decrypted rank file to %s failed\n", + rank_path_dec); + ret = -6; + } + + if (!asset_nxa_usb_save_save_to_file(save_path_dec, save, false)) { + fprintf(stderr, "Saving decrypted save file to %s failed\n", + save_path_dec); + ret = -7; + } + + free(rank_path_dec); + free(save_path_dec); + } else if (!strcmp(argv[1], "enc")) { + char* rank_path_enc; + char* save_path_enc; + + rank_path_enc = util_str_merge(rank_path, ".enc"); + save_path_enc = util_str_merge(save_path, ".enc"); + + if (!asset_nxa_usb_rank_save_to_file(rank_path_enc, rank, true)) { + fprintf(stderr, "Saving encrypted rank file to %s failed\n", + rank_path_enc); + ret = -6; + } + + if (!asset_nxa_usb_save_save_to_file(save_path_enc, save, true)) { + fprintf(stderr, "Saving encrypted save file to %s failed\n", + save_path_enc); + ret = -7; + } + + free(rank_path_enc); + free(save_path_enc); + } else if (!strcmp(argv[1], "dump")) { + char* rank_str; + char* save_str; + + rank_str = asset_nxa_usb_rank_to_string(rank); + save_str = asset_nxa_usb_save_to_string(save); + + printf("----------------- rank -----------------\n" + "%s----------------- save -----------------\n%s", + rank_str, save_str); + + free(rank_str); + free(save_str); + } else { + fprintf(stderr, "Unknown command %s\n", argv[1]); + ret = -4; + } + + free(rank); + free(save); + } + +cleanup: + free(rank_path); + free(save_path); + + return ret; +} \ No newline at end of file diff --git a/src/main/asset/x2/lib/settings.c b/src/main/asset/x2/lib/settings.c new file mode 100644 index 0000000..978b919 --- /dev/null +++ b/src/main/asset/x2/lib/settings.c @@ -0,0 +1,87 @@ +#include + +#include "asset/x2/lib/settings.h" + +#include "util/adler32.h" +#include "util/mem.h" + +static const char* _asset_x2_settings_defaut_high_score_names[] = { + "MKII", + "YAHP", + "KANN", + "ZINY", + "PIAH", + "AHN ", + "PIYO", + "HIKO", + "HALO", + "BAJI", + "YUTE", + "SS ", + "SIHO", + "AZKI", + "JUNE", + "GOON", + "ARI*", + "TAMA", + "YSMA", + "PIU " +}; + +struct asset_x2_settings* asset_x2_settings_new(void) +{ + struct asset_x2_settings* settings; + + settings = util_xmalloc(sizeof(struct asset_x2_settings)); + + memset(settings->ff_pad, 0xFF, sizeof(settings->ff_pad)); + memset(settings->unkn, 0, sizeof(settings->unkn)); + settings->unlock_canond_full = 0; + settings->total_uptime = 0; + settings->unkn69 = 0x69; + memset(settings->song_blacklist, 0, ASSET_X2_SETTINGS_MAX_SONGS); + memset(settings->unkn3, 0, sizeof(settings->unkn3)); + memcpy(settings->ident, "EX02", 4); + /* Updated using finalize */ + settings->adler32 = 0; + memset(settings->song_play_count, 0, sizeof(uint16_t) * ASSET_X2_SETTINGS_MAX_SONGS); + settings->padding = 0; + + for (uint8_t i = 0; i < ASSET_X2_SETTINGS_HIGH_SCORE_COUNT; i++) { + settings->high_score_scores[i] = 10000u - (500u * i); + memcpy(&settings->high_score_names[i], &_asset_x2_settings_defaut_high_score_names[i], + ASSET_X2_SETTINGS_HIGH_SCORE_NAME_LEN); + } + + settings->coin_limit = 0; + settings->game_mode = ASSET_X2_SETTINGS_GAME_MODE_NORMAL; + settings->game_level = ASSET_X2_SETTINGS_LEVEL_NORMAL; + settings->game_stage_break = ASSET_X2_SETTINGS_STAGE_BREAK_2ND; + settings->language = ASSET_X2_SETTINGS_LANG_ENG; + settings->game_demo_sound = 1; + settings->game_show_help = 1; + settings->coin1_setting = 1; + settings->coin2_setting = 1; + settings->bookkeeping_coin1_total = 0; + settings->bookkeeping_coin2_total = 0; + settings->bookkeeping_service_total = 0; + + return settings; +} + +void asset_x2_settings_finalize(struct asset_x2_settings* settings) +{ + /* Seems odd, but they just checksum the game settings part...*/ + settings->adler32 = util_adler32_calc(1, (const uint8_t*) &settings->game_mode, 8); +} + +void asset_x2_settings_unlock_all(struct asset_x2_settings* settings) +{ + settings->unlock_canond_full = 1; +} + +char* asset_x2_settings_to_string(const struct asset_x2_settings* settings) +{ + // TODO + return ""; +} \ No newline at end of file diff --git a/src/main/asset/x2/lib/settings.h b/src/main/asset/x2/lib/settings.h new file mode 100644 index 0000000..6fad54d --- /dev/null +++ b/src/main/asset/x2/lib/settings.h @@ -0,0 +1,82 @@ +#ifndef ASSET_X2_SETTINGS_H +#define ASSET_X2_SETTINGS_H + +#include + +#define ASSET_X2_SETTINGS_FILE_NAME "PIUEXCEED.INI" +#define ASSET_X2_SETTINGS_MAX_SONGS 139 +#define ASSET_X2_SETTINGS_HIGH_SCORE_COUNT 20 +#define ASSET_X2_SETTINGS_HIGH_SCORE_NAME_LEN 4 + +enum asset_x2_settings_language { + ASSET_X2_SETTINGS_LANG_KOR = 0, + ASSET_X2_SETTINGS_LANG_ENG = 1, + ASSET_X2_SETTINGS_LANG_SP = 2, + ASSET_X2_SETTINGS_LANG_CH = 3, +}; + +enum asset_x2_settings_game_mode { + ASSET_X2_SETTINGS_GAME_MODE_NORMAL = 0, + ASSET_X2_SETTINGS_GAME_MODE_EVENT = 1, +}; + +enum asset_x2_settings_level { + ASSET_X2_SETTINGS_LEVEL_EASY = 0, + ASSET_X2_SETTINGS_LEVEL_NORMAL = 1, + ASSET_X2_SETTINGS_LEVEL_HARD = 2, +}; + +enum asset_x2_settings_stage_break { + ASSET_X2_SETTINGS_STAGE_BREAK_OFF = 0, + ASSET_X2_SETTINGS_STAGE_BREAK_1ST = 1, + ASSET_X2_SETTINGS_STAGE_BREAK_2ND = 2, + ASSET_X2_SETTINGS_STAGE_BREAK_3RD = 3, + ASSET_X2_SETTINGS_STAGE_BREAK_4TH = 4, +}; + +struct asset_x2_settings { + /* Padding to fill up to 4k EEPROM legacy size */ + uint8_t ff_pad[0x51B]; + uint8_t unkn[10]; + uint8_t unlock_canond_full; + uint32_t total_uptime; + uint8_t unkn69; + uint8_t song_blacklist[ASSET_X2_SETTINGS_MAX_SONGS]; + uint8_t unkn3[0x75]; + /* EX02 */ + char ident[4]; + uint32_t adler32; + uint16_t song_play_count[ASSET_X2_SETTINGS_MAX_SONGS]; + uint16_t padding; + uint32_t high_score_scores[ASSET_X2_SETTINGS_HIGH_SCORE_COUNT]; + /* 20 names a 4 chars for the default high score list */ + char high_score_names[ASSET_X2_SETTINGS_HIGH_SCORE_COUNT][ASSET_X2_SETTINGS_HIGH_SCORE_NAME_LEN]; + /* 0 none */ + uint8_t coin_limit; + uint8_t game_mode; + uint8_t game_level; + uint8_t game_stage_break; + uint8_t language; + /* 0 off, 1 on */ + uint8_t game_demo_sound; + /* 0 off, 1 on */ + uint8_t game_show_help; + /* 0 freeplay */ + uint8_t coin1_setting; + /* 0 freeplay */ + uint8_t coin2_setting; + uint32_t bookkeeping_coin1_total; + uint32_t bookkeeping_coin2_total; + uint32_t bookkeeping_service_total; +} __attribute__((__packed__)); + +struct asset_x2_settings* asset_x2_settings_new(void); + +// update checksum +void asset_x2_settings_finalize(struct asset_x2_settings* settings); + +void asset_x2_settings_unlock_all(struct asset_x2_settings* settings); + +char* asset_x2_settings_to_string(const struct asset_x2_settings* settings); + +#endif diff --git a/src/main/asset/zero/lib/settings.c b/src/main/asset/zero/lib/settings.c new file mode 100644 index 0000000..049d4c3 --- /dev/null +++ b/src/main/asset/zero/lib/settings.c @@ -0,0 +1,62 @@ +#include + +#include "asset/zero/lib/settings.h" + +#include "util/adler32.h" +#include "util/mem.h" + +struct asset_zero_settings* asset_zero_settings_new(void) +{ + struct asset_zero_settings* settings; + + settings = util_xmalloc(sizeof(struct asset_zero_settings)); + + memset(settings->ff_pad, 0xFF, sizeof(settings->ff_pad)); + memcpy(settings->ident, "ZERO", 4); + /* Updated using finalize */ + settings->adler32 = 0; + settings->total_uptime = 0; + settings->language = ASSET_ZERO_SETTINGS_LANG_ENG; + memset(settings->song_play_count, 0, sizeof(uint32_t) * ASSET_ZERO_SETTINGS_MAX_SONGS); + settings->game_option_default_station = ASSET_ZERO_SETTINGS_STATION_EASY; + settings->game_mode = ASSET_ZERO_SETTINGS_GAME_MODE_NORMAL; + settings->game_level = ASSET_ZERO_SETTINGS_LEVEL_NORMAL; + settings->game_stage_break = ASSET_ZERO_SETTINGS_STAGE_BREAK_2ND; + settings->game_demo_sound = 1; + settings->game_show_help = 1; + settings->game_mercy_ticket = 0; + settings->game_score_per_ticket = 0; + settings->coin_limit = 0; + settings->coin1_setting = 1; + settings->coin2_setting = 1; + memset(settings->mission_unlock_flags, 0, ASSET_ZERO_SETTINGS_NUM_UNLOCK_FLAGS); + settings->unkn2 = 0x2A; + settings->unkn3 = 0; + settings->bookkeeping_coin1_total = 0; + settings->bookkeeping_coin2_total = 0; + settings->bookkeeping_service_total = 0; + settings->bookkeeping_ticket_1 = 0; + settings->bookkeeping_ticket_2 = 0; + settings->region = 0xFF; + memset(settings->censor_table, 0, ASSET_ZERO_SETTINGS_MAX_SONGS); + + return settings; +} + +void asset_zero_settings_finalize(struct asset_zero_settings* settings) +{ + /* Seems odd, but they just checksum the game settings part...*/ + settings->adler32 = util_adler32_calc(1, (const uint8_t*) &settings->game_mode, 8); +} + +void asset_zero_settings_unlock_all(struct asset_zero_settings* settings) +{ + /* Mark all missions cleared to unlock all missions and content locked by some missions */ + memset(settings->mission_unlock_flags, 1, ASSET_ZERO_SETTINGS_NUM_UNLOCK_FLAGS); +} + +char* asset_zero_settings_to_string(const struct asset_zero_settings* settings) +{ + // TODO + return ""; +} \ No newline at end of file diff --git a/src/main/asset/zero/lib/settings.h b/src/main/asset/zero/lib/settings.h new file mode 100644 index 0000000..b675902 --- /dev/null +++ b/src/main/asset/zero/lib/settings.h @@ -0,0 +1,95 @@ +#ifndef ASSET_ZERO_SETTINGS_H +#define ASSET_ZERO_SETTINGS_H + +#include + +#define ASSET_ZERO_SETTINGS_FILE_NAME "PIUZERO.INI" +#define ASSET_ZERO_SETTINGS_MAX_SONGS 200 +#define ASSET_ZERO_SETTINGS_NUM_UNLOCK_FLAGS 59 + +enum asset_zero_settings_language { + ASSET_ZERO_SETTINGS_LANG_KOR = 0, + ASSET_ZERO_SETTINGS_LANG_ENG = 1, + ASSET_ZERO_SETTINGS_LANG_SP = 2, + ASSET_ZERO_SETTINGS_LANG_CH = 3, +}; + +enum asset_zero_settings_station { + ASSET_ZERO_SETTINGS_STATION_EASY = 0, + ASSET_ZERO_SETTINGS_STATION_ARCADE = 1, + ASSET_ZERO_SETTINGS_STATION_REMIX = 2, +}; + +enum asset_zero_settings_game_mode { + ASSET_ZERO_SETTINGS_GAME_MODE_NORMAL = 0, + ASSET_ZERO_SETTINGS_GAME_MODE_EVENT = 1, +}; + +enum asset_zero_settings_level { + ASSET_ZERO_SETTINGS_LEVEL_EASY = 0, + ASSET_ZERO_SETTINGS_LEVEL_NORMAL = 1, + ASSET_ZERO_SETTINGS_LEVEL_HARD = 2, +}; + +enum asset_zero_settings_stage_break { + ASSET_ZERO_SETTINGS_STAGE_BREAK_OFF = 0, + ASSET_ZERO_SETTINGS_STAGE_BREAK_1ST = 1, + ASSET_ZERO_SETTINGS_STAGE_BREAK_2ND = 2, + ASSET_ZERO_SETTINGS_STAGE_BREAK_3RD = 3, + ASSET_ZERO_SETTINGS_STAGE_BREAK_4TH = 4, +}; + +struct asset_zero_settings { + /* Padding to fill up to 4k EEPROM legacy size */ + uint8_t ff_pad[0xBA4]; + /* ZERO */ + char ident[4]; + uint32_t adler32; + uint32_t total_uptime; + uint8_t language; + uint32_t song_play_count[ASSET_ZERO_SETTINGS_MAX_SONGS]; + uint32_t game_option_default_station; + uint8_t game_mode; + uint8_t game_level; + uint8_t game_stage_break; + /* 0 off, 1 on */ + uint8_t game_demo_sound; + /* 0 off, 1 on */ + uint8_t game_show_help; + /* 0 off, 1 on */ + uint8_t game_mercy_ticket; + /* 0 off, 1 - 6 for score */ + uint8_t game_score_per_ticket; + /* 0 none */ + uint8_t coin_limit; // 0 none + /* 0 freeplay */ + uint8_t coin1_setting; + /* 0 freeplay */ + uint8_t coin2_setting; + /* Unlock flags for missions (total of 30) */ + uint8_t mission_unlock_flags[ASSET_ZERO_SETTINGS_NUM_UNLOCK_FLAGS]; + /* Default value 0x2A */ + uint32_t unkn2; + uint32_t unkn3; + uint32_t bookkeeping_coin1_total; + uint32_t bookkeeping_coin2_total; + uint32_t bookkeeping_service_total; + uint32_t bookkeeping_ticket_1; + uint32_t bookkeeping_ticket_2; + /* FF -> not used, censoring globally disabled. unused feature though */ + uint8_t region; + /* 0 = censoring of song off */ + uint8_t censor_table[ASSET_ZERO_SETTINGS_MAX_SONGS]; +} __attribute__((__packed__)); + +struct asset_zero_settings* asset_zero_settings_new(void); + +// update checksum +void asset_zero_settings_finalize(struct asset_zero_settings* settings); + +// sets all missions cleared to unlock special stage songs and other missions +void asset_zero_settings_unlock_all(struct asset_zero_settings* settings); + +char* asset_zero_settings_to_string(const struct asset_zero_settings* settings); + +#endif diff --git a/src/main/capnhook/hook/common.h b/src/main/capnhook/hook/common.h new file mode 100644 index 0000000..59221b0 --- /dev/null +++ b/src/main/capnhook/hook/common.h @@ -0,0 +1,12 @@ +/** + * Common stuff for capnhook/hook module + * + * Based on original capnhook code for windows by decafcode: + * https://github.com/decafcode/capnhook + */ +#ifndef CAPNHOOK_COMMON_H +#define CAPNHOOK_COMMON_H + +#define _countof(array) (sizeof(array) / sizeof((array)[0])) + +#endif \ No newline at end of file diff --git a/src/main/capnhook/hook/filehook.c b/src/main/capnhook/hook/filehook.c new file mode 100644 index 0000000..7d47b9a --- /dev/null +++ b/src/main/capnhook/hook/filehook.c @@ -0,0 +1,662 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "capnhook/hook/common.h" +#include "capnhook/hook/filehook.h" +#include "capnhook/hook/lib.h" + +#include "util/time.h" + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Real funcs typedefs */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +typedef FILE* (*cnh_filehook_fopen_t)(const char* filename, const char* mode); +typedef int (*cnh_filehook_fclose_t)(FILE* stream); +typedef size_t (*cnh_filehook_fread_t)(void* ptr, size_t size, size_t nmemb, FILE* stream); +typedef size_t (*cnh_filehook_fwrite_t)(const void* ptr, size_t size, size_t nmemb, FILE* stream); +typedef char* (*cnh_filehook_fgets_t)(char* s, int size, FILE* stream); +typedef int (*cnh_filehook_fseek_t)(FILE* stream, long offset, int whence); +typedef long (*cnh_filehook_ftell_t)(FILE* stream); +typedef int (*cnh_filehook_feof_t)(FILE* stream); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private helpers */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_filehook_init(void); + +static enum cnh_result _cnh_filehook_invoke_real(struct cnh_filehook_irp *irp); +static enum cnh_result _cnh_filehook_invoke_real_fopen(struct cnh_filehook_irp *irp); +static enum cnh_result _cnh_filehook_invoke_real_fclose(struct cnh_filehook_irp *irp); +static enum cnh_result _cnh_filehook_invoke_real_fread(struct cnh_filehook_irp *irp); +static enum cnh_result _cnh_filehook_invoke_real_fwrite(struct cnh_filehook_irp *irp); +static enum cnh_result _cnh_filehook_invoke_real_fgets(struct cnh_filehook_irp *irp); +static enum cnh_result _cnh_filehook_invoke_real_fseek(struct cnh_filehook_irp *irp); +static enum cnh_result _cnh_filehook_invoke_real_ftell(struct cnh_filehook_irp *irp); +static enum cnh_result _cnh_filehook_invoke_real_feof(struct cnh_filehook_irp *irp); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private state */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static cnh_filehook_fopen_t _cnh_filehook_real_fopen; +static cnh_filehook_fclose_t _cnh_filehook_real_fclose; +static cnh_filehook_fread_t _cnh_filehook_real_fread; +static cnh_filehook_fwrite_t _cnh_filehook_real_fwrite; +static cnh_filehook_fgets_t _cnh_filehook_real_fgets; +static cnh_filehook_fseek_t _cnh_filehook_real_fseek; +static cnh_filehook_ftell_t _cnh_filehook_real_ftell; +static cnh_filehook_feof_t _cnh_filehook_real_feof; + +static const cnh_filehook_fn_t _cnh_filehook_real_handlers[8] = { + [CNH_FILEHOOK_IRP_OP_OPEN] = _cnh_filehook_invoke_real_fopen, + [CNH_FILEHOOK_IRP_OP_CLOSE] = _cnh_filehook_invoke_real_fclose, + [CNH_FILEHOOK_IRP_OP_READ] = _cnh_filehook_invoke_real_fread, + [CNH_FILEHOOK_IRP_OP_WRITE] = _cnh_filehook_invoke_real_fwrite, + [CNH_FILEHOOK_IRP_OP_FGETS] = _cnh_filehook_invoke_real_fgets, + [CNH_FILEHOOK_IRP_OP_SEEK] = _cnh_filehook_invoke_real_fseek, + [CNH_FILEHOOK_IRP_OP_TELL] = _cnh_filehook_invoke_real_ftell, + [CNH_FILEHOOK_IRP_OP_EOF] = _cnh_filehook_invoke_real_feof, +}; + +static atomic_int _cnh_filehook_initted = ATOMIC_VAR_INIT(0); +static atomic_int _cnh_filehook_init_in_progress = ATOMIC_VAR_INIT(0); +static pthread_mutex_t _cnh_filehook_lock; +static cnh_filehook_fn_t *_cnh_filehook_handlers; +static size_t _cnh_filehook_nhandlers; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Public module functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +enum cnh_result cnh_filehook_push_handler(cnh_filehook_fn_t fn) +{ + cnh_filehook_fn_t *new_array; + size_t new_size; + enum cnh_result result; + + assert(fn != NULL); + + _cnh_filehook_init(); + pthread_mutex_lock(&_cnh_filehook_lock); + + new_size = _cnh_filehook_nhandlers + 1; + new_array = realloc(_cnh_filehook_handlers, new_size * sizeof(cnh_filehook_fn_t)); + + if (new_array != NULL) { + _cnh_filehook_handlers = new_array; + _cnh_filehook_handlers[_cnh_filehook_nhandlers++] = fn; + result = CNH_RESULT_SUCCESS; + } else { + result = CNH_RESULT_OUT_OF_MEMORY; + } + + pthread_mutex_unlock(&_cnh_filehook_lock); + + return result; +} + +enum cnh_result cnh_filehook_invoke_next(struct cnh_filehook_irp *irp) +{ + cnh_filehook_fn_t handler; + enum cnh_result result; + + assert(irp != NULL); + assert(_cnh_filehook_initted > 0); + + pthread_mutex_lock(&_cnh_filehook_lock); + + assert(irp->next_handler <= _cnh_filehook_nhandlers); + + if (irp->next_handler < _cnh_filehook_nhandlers) { + handler = _cnh_filehook_handlers[irp->next_handler]; + irp->next_handler++; + } else { + handler = _cnh_filehook_invoke_real; + irp->next_handler = (size_t) -1; + } + + pthread_mutex_unlock(&_cnh_filehook_lock); + + result = handler(irp); + + if (result != CNH_RESULT_SUCCESS) { + irp->next_handler = (size_t) -1; + } + + return result; +} + +FILE* cnh_filehook_open_dummy_file_handle() +{ + _cnh_filehook_init(); + + /* We need to return some sort of valid FD to the + caller, since we're simulating a device that + doesn't exist. Open an FD on /dev/null. */ + + return _cnh_filehook_real_fopen("/dev/null", "r"); +} + +void cnh_filehook_close_dummy_file_handle(FILE* file) +{ + _cnh_filehook_init(); + + /* Avoid hooking pipeline when using dummy handles */ + _cnh_filehook_real_fclose(file); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooked functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +FILE* fopen(const char* filename, const char* mode) +{ + struct cnh_filehook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_filehook_init(); + + if (filename == NULL || mode == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return NULL; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FILEHOOK_IRP_OP_OPEN; + irp.file = NULL; + irp.open_filename = filename; + irp.open_mode = mode; + + result = cnh_filehook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return NULL; + } + + errno = 0; + + return irp.file; +} + +int fclose(FILE* stream) +{ + struct cnh_filehook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_filehook_init(); + + if (stream == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return EOF; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FILEHOOK_IRP_OP_CLOSE; + irp.file = stream; + + result = cnh_filehook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return EOF; + } + + errno = 0; + + return 0; +} + +size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) +{ + struct cnh_filehook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_filehook_init(); + + if (ptr == NULL || stream == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return 0; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FILEHOOK_IRP_OP_READ; + irp.file = stream; + irp.read.bytes = ptr; + irp.read.nbytes = size * nmemb; + irp.read.pos = 0; + /* To ensure we don't mess up any calls that are just passed through, we have to drag along the original size + and nmemb parameters and apply them to the real call at the end of the chain if the iobuffer was not + modified */ + irp.orig_read_write_size = size; + irp.orig_read_write_nmemb = nmemb; + + result = cnh_filehook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return 0; + } + + errno = 0; + + return irp.read.pos / size; +} + +size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) +{ + struct cnh_filehook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_filehook_init(); + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FILEHOOK_IRP_OP_WRITE; + irp.file = stream; + irp.write.bytes = ptr; + irp.write.nbytes = size * nmemb; + irp.write.pos = 0; + /* To ensure we don't mess up any calls that are just passed through, we have to drag along the original size + and nmemb parameters and apply them to the real call at the end of the chain if the iobuffer was not + modified */ + irp.orig_read_write_size = size; + irp.orig_read_write_nmemb = nmemb; + + result = cnh_filehook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return 0; + } + + errno = 0; + + return irp.write.pos / size; +} + +char* fgets(char* s, int size, FILE* stream) +{ + struct cnh_filehook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_filehook_init(); + + if (s == NULL || stream == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return NULL; + } + + /* Redirect this to fread */ + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FILEHOOK_IRP_OP_FGETS; + irp.file = stream; + irp.read.bytes = (uint8_t*) s; + irp.read.nbytes = (size_t) size; + irp.read.pos = 0; + + result = cnh_filehook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return NULL; + } + + errno = 0; + + return (char*) irp.read.bytes; +} + +int fseek(FILE* stream, long offset, int whence) +{ + struct cnh_filehook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_filehook_init(); + + if (stream == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FILEHOOK_IRP_OP_SEEK; + irp.file = stream; + irp.seek_origin = whence; + irp.seek_offset = offset; + + result = cnh_filehook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return 0; +} + +void rewind(FILE* stream) +{ + struct cnh_filehook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_filehook_init(); + + if (stream == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return; + } + + /* Seek to beginning */ + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FILEHOOK_IRP_OP_SEEK; + irp.file = stream; + irp.seek_origin = SEEK_SET; + irp.seek_offset = 0; + + result = cnh_filehook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return; + } +} + +long ftell(FILE* stream) +{ + struct cnh_filehook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_filehook_init(); + + if (stream == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FILEHOOK_IRP_OP_TELL; + irp.file = stream; + /* Return value */ + irp.tell_offset = 0; + + result = cnh_filehook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return (long) irp.tell_offset; +} + +int feof(FILE* stream) +{ + struct cnh_filehook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_filehook_init(); + + if (stream == NULL) { + /* Must be EBADF according to man pages */ + errno = EBADF; + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FILEHOOK_IRP_OP_EOF; + irp.file = stream; + /* Return value */ + irp.eof = false; + + result = cnh_filehook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + /* Function is never supposed to fail and EBADF is the only allowed error code */ + errno = EBADF; + return -1; + } + + errno = 0; + + return irp.eof ? 1 : 0; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Helper functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_filehook_init(void) +{ + int expected; + + if (atomic_load(&_cnh_filehook_initted) > 0) { + return; + } + + expected = 0; + + if (!atomic_compare_exchange_strong(&_cnh_filehook_init_in_progress, &expected, 1)) { + while (atomic_load(&_cnh_filehook_init_in_progress) > 1) { + util_time_sleep_us(100); + } + + return; + } + + /* Check again to ensure the current thread yielded between the init'd check and setting init in progress */ + if (atomic_load(&_cnh_filehook_initted) > 0) { + /* Revert init in progress, because nothing to do anymore */ + atomic_store(&_cnh_filehook_init_in_progress, 0); + return; + } + + _cnh_filehook_real_fopen = (cnh_filehook_fopen_t) cnh_lib_get_func_addr("fopen"); + _cnh_filehook_real_fclose = (cnh_filehook_fclose_t) cnh_lib_get_func_addr("fclose"); + _cnh_filehook_real_fread = (cnh_filehook_fread_t) cnh_lib_get_func_addr("fread"); + _cnh_filehook_real_fwrite = (cnh_filehook_fwrite_t) cnh_lib_get_func_addr("fwrite"); + _cnh_filehook_real_fgets = (cnh_filehook_fgets_t) cnh_lib_get_func_addr("fgets"); + _cnh_filehook_real_fseek = (cnh_filehook_fseek_t) cnh_lib_get_func_addr("fseek"); + _cnh_filehook_real_ftell = (cnh_filehook_ftell_t) cnh_lib_get_func_addr("ftell"); + _cnh_filehook_real_feof = (cnh_filehook_feof_t) cnh_lib_get_func_addr("feof"); + + pthread_mutex_init(&_cnh_filehook_lock, NULL); + + atomic_store(&_cnh_filehook_initted, 1); + atomic_store(&_cnh_filehook_init_in_progress, 0); +} + +static enum cnh_result _cnh_filehook_invoke_real(struct cnh_filehook_irp *irp) +{ + cnh_filehook_fn_t handler; + + assert(irp != NULL); + assert(irp->op < _countof(_cnh_filehook_real_handlers)); + + handler = _cnh_filehook_real_handlers[irp->op]; + + assert(handler != NULL); + + return handler(irp); +} + +static enum cnh_result _cnh_filehook_invoke_real_fopen(struct cnh_filehook_irp *irp) +{ + FILE* file; + + assert(irp != NULL); + + file = _cnh_filehook_real_fopen(irp->open_filename, irp->open_mode); + + if (!file) { + return cnh_errno_to_result(errno); + } + + irp->file = file; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_filehook_invoke_real_fclose(struct cnh_filehook_irp *irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_filehook_real_fclose(irp->file); + + if (res == EOF) { + return cnh_errno_to_result(errno); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_filehook_invoke_real_fread(struct cnh_filehook_irp *irp) +{ + size_t read; + + assert(irp != NULL); + + /* Check if any previous hook modified the buffer and use original parameters otherwise to ensure the original + call is correctly applied to the real function */ + if (irp->read.pos != 0 || irp->read.nbytes != irp->orig_read_write_nmemb * irp->orig_read_write_size) { + read = _cnh_filehook_real_fread(&irp->read.bytes[irp->read.pos], irp->read.nbytes - irp->read.pos, 1, + irp->file); + + if (read < 1 && ferror(irp->file) != 0) { + return cnh_errno_to_result(errno); + } + + irp->read.pos += irp->read.nbytes - irp->read.pos; + } else { + read = _cnh_filehook_real_fread(irp->read.bytes, irp->orig_read_write_size, irp->orig_read_write_nmemb, + irp->file); + + if (read < 1 && ferror(irp->file) != 0) { + return cnh_errno_to_result(errno); + } + + irp->read.pos = read * irp->orig_read_write_size; + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_filehook_invoke_real_fwrite(struct cnh_filehook_irp *irp) +{ + size_t written; + + assert(irp != NULL); + + /* Check if any previous hook modified the buffer and use original parameters otherwise to ensure the original + call is correctly applied to the real function */ + if (irp->write.pos != 0 || irp->write.nbytes != irp->orig_read_write_nmemb * irp->orig_read_write_size) { + written = _cnh_filehook_real_fwrite(&irp->write.bytes[irp->write.pos], irp->write.nbytes - irp->write.pos, 1, + irp->file); + + if (written < 1 && ferror(irp->file) != 0) { + return cnh_errno_to_result(errno); + } + + irp->write.pos += irp->write.nbytes - irp->write.pos; + } else { + written = _cnh_filehook_real_fwrite(irp->write.bytes, irp->orig_read_write_size, irp->orig_read_write_nmemb, + irp->file); + + if (written < 1 && ferror(irp->file) != 0) { + return cnh_errno_to_result(errno); + } + + irp->write.pos += written * irp->orig_read_write_size; + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_filehook_invoke_real_fgets(struct cnh_filehook_irp *irp) +{ + char* res; + + res = _cnh_filehook_real_fgets((char*) irp->read.bytes, irp->read.nbytes, irp->file); + + if (res == NULL) { + /* eof */ + if (errno == 0) { + irp->read.bytes = NULL; + return CNH_RESULT_SUCCESS; + } else { + return cnh_errno_to_result(errno); + } + } else { + return CNH_RESULT_SUCCESS; + } +} + +static enum cnh_result _cnh_filehook_invoke_real_fseek(struct cnh_filehook_irp *irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_filehook_real_fseek(irp->file, (long) irp->seek_offset, irp->seek_origin); + + if (res != 0) { + return cnh_errno_to_result(errno); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_filehook_invoke_real_ftell(struct cnh_filehook_irp *irp) +{ + long res; + + assert(irp != NULL); + + res = _cnh_filehook_real_ftell(irp->file); + + if (res < 0) { + return cnh_errno_to_result(errno); + } + + irp->tell_offset = (uint64_t) res; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_filehook_invoke_real_feof(struct cnh_filehook_irp *irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_filehook_real_feof(irp->file); + + if (res < 0) { + return cnh_errno_to_result(errno); + } + + irp->eof = res > 0; + + return CNH_RESULT_SUCCESS; +} \ No newline at end of file diff --git a/src/main/capnhook/hook/filehook.h b/src/main/capnhook/hook/filehook.h new file mode 100644 index 0000000..94233c8 --- /dev/null +++ b/src/main/capnhook/hook/filehook.h @@ -0,0 +1,90 @@ +/** + * Hook module for hooking file related calls, e.g. fopen, fread, fwrite, etc. + * + * Based on original capnhook code for windows by decafcode: + * https://github.com/decafcode/capnhook + */ +#ifndef CAPNHOOK_FILEHOOK_H +#define CAPNHOOK_FILEHOOK_H + +#include +#include +#include +#include + +#include "capnhook/hook/iobuf.h" + +/** + * Available operations to hook + */ +enum cnh_filehook_irp_op { + CNH_FILEHOOK_IRP_OP_OPEN = 0, + CNH_FILEHOOK_IRP_OP_CLOSE = 1, + CNH_FILEHOOK_IRP_OP_READ = 2, + CNH_FILEHOOK_IRP_OP_WRITE = 3, + CNH_FILEHOOK_IRP_OP_FGETS = 4, + CNH_FILEHOOK_IRP_OP_SEEK = 5, + CNH_FILEHOOK_IRP_OP_TELL = 6, + CNH_FILEHOOK_IRP_OP_EOF = 7, +}; + +/** + * I/O request packet + */ +struct cnh_filehook_irp { + enum cnh_filehook_irp_op op; + size_t next_handler; + FILE* file; + const char *open_filename; + const char* open_mode; + struct cnh_iobuf read; + struct cnh_const_iobuf write; + size_t orig_read_write_size; + size_t orig_read_write_nmemb; + int seek_origin; + int64_t seek_offset; + uint64_t tell_offset; + bool eof; +}; + +/** + * Hook function type + */ +typedef enum cnh_result (*cnh_filehook_fn_t)(struct cnh_filehook_irp *irp); + +/** + * Add a new hook handler to the hook module. + * The handler added is getting called on every hooked operation. + * + * @param fn Pointer to hook function to add + * @return Result/error code if hooking was successful + */ +enum cnh_result cnh_filehook_push_handler(cnh_filehook_fn_t fn); + +/** + * Invoke the next hooked function registered in the hook module. + * Call this from your hook handler if you want to pass on execution to further + * hooked calls reaching the original function at the end of the hook chain. + * + * @param irp I/O request package to dispatch + * @return Result of the following function dispatching the request package + */ +enum cnh_result cnh_filehook_invoke_next(struct cnh_filehook_irp *irp); + +/** + * Create a dummy file handle. Use this function for file access virtualization. + * Do not execute real operations on the handle returned. + * + * @return Actual file handle instance pointing to a dummy endpoint. + */ +FILE* cnh_filehook_open_dummy_file_handle(); + +/** + * Close a dummy file handle. Make sure to free your previously allocated handles + * to avoid resource leaks. Do not free them using the real close function. + * + * @param file Dummy file handle to free + */ +void cnh_filehook_close_dummy_file_handle(FILE* file); + +#endif diff --git a/src/main/capnhook/hook/fshook.c b/src/main/capnhook/hook/fshook.c new file mode 100644 index 0000000..7e01bf5 --- /dev/null +++ b/src/main/capnhook/hook/fshook.c @@ -0,0 +1,447 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "capnhook/hook/common.h" +#include "capnhook/hook/fshook.h" +#include "capnhook/hook/lib.h" + +#include "util/time.h" + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Real funcs typedefs */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +typedef DIR* (*cnh_fshook_opendir_t)(const char* name); +typedef int (*cnh_fshook_lxstat_t)(int version, const char* file, struct stat* buf); +typedef int (*cnh_fshook_xstat_t)(int version, const char* file, struct stat* buf); +typedef int (*cnh_fshook_rename_t)(const char* old, const char* new); +typedef int (*cnh_fshook_remove_t)(const char* pathname); +typedef int (*cnh_fshook_access_t)(const char* path, int amode); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private helpers */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_fshook_init(void); + +static enum cnh_result _cnh_fshook_invoke_real(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_fshook_invoke_real_opendir(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_fshook_invoke_real_lxstat(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_fshook_invoke_real_xstat(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_fshook_invoke_real_rename(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_fshook_invoke_real_remove(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_fshook_invoke_real_access(struct cnh_fshook_irp *irp); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private state */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static cnh_fshook_opendir_t _cnh_fshook_real_opendir; +static cnh_fshook_lxstat_t _cnh_fshook_real_lxstat; +static cnh_fshook_xstat_t _cnh_fshook_real_xstat; +static cnh_fshook_rename_t _cnh_fshook_real_rename; +static cnh_fshook_remove_t _cnh_fshook_real_remove; +static cnh_fshook_access_t _cnh_fshook_real_access; + +static const cnh_fshook_fn_t _cnh_fshook_real_handlers[6] = { + [CNH_FSHOOK_IRP_OP_DIR_OPEN] = _cnh_fshook_invoke_real_opendir, + [CNH_FSHOOK_IRP_OP_LXSTAT] = _cnh_fshook_invoke_real_lxstat, + [CNH_FSHOOK_IRP_OP_XSTAT] = _cnh_fshook_invoke_real_xstat, + [CNH_FSHOOK_IRP_OP_RENAME] = _cnh_fshook_invoke_real_rename, + [CNH_FSHOOK_IRP_OP_REMOVE] = _cnh_fshook_invoke_real_remove, + [CNH_FSHOOK_IRP_OP_ACCESS] = _cnh_fshook_invoke_real_access, +}; + +static atomic_int _cnh_fshook_initted = ATOMIC_VAR_INIT(0); +static atomic_int _cnh_fshook_init_in_progress = ATOMIC_VAR_INIT(0); +static pthread_mutex_t _cnh_fshook_lock; +static cnh_fshook_fn_t *_cnh_fshook_handlers; +static size_t _cnh_fshook_nhandlers; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Public module functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +enum cnh_result cnh_fshook_push_handler(cnh_fshook_fn_t fn) +{ + cnh_fshook_fn_t *new_array; + size_t new_size; + enum cnh_result result; + + assert(fn != NULL); + + _cnh_fshook_init(); + + pthread_mutex_lock(&_cnh_fshook_lock); + + new_size = _cnh_fshook_nhandlers + 1; + new_array = realloc(_cnh_fshook_handlers, new_size * sizeof(cnh_fshook_fn_t)); + + if (new_array != NULL) { + _cnh_fshook_handlers = new_array; + _cnh_fshook_handlers[_cnh_fshook_nhandlers++] = fn; + result = CNH_RESULT_SUCCESS; + } else { + result = CNH_RESULT_OUT_OF_MEMORY; + } + + pthread_mutex_unlock(&_cnh_fshook_lock); + + return result; +} + +enum cnh_result cnh_fshook_invoke_next(struct cnh_fshook_irp *irp) +{ + cnh_fshook_fn_t handler; + enum cnh_result result; + + assert(irp != NULL); + assert(_cnh_fshook_initted > 0); + + pthread_mutex_lock(&_cnh_fshook_lock); + + assert(irp->next_handler <= _cnh_fshook_nhandlers); + + if (irp->next_handler < _cnh_fshook_nhandlers) { + handler = _cnh_fshook_handlers[irp->next_handler]; + irp->next_handler++; + } else { + handler = _cnh_fshook_invoke_real; + irp->next_handler = (size_t) -1; + } + + pthread_mutex_unlock(&_cnh_fshook_lock); + + result = handler(irp); + + if (result != CNH_RESULT_SUCCESS) { + irp->next_handler = (size_t) -1; + } + + return result; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooked functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +DIR* opendir(const char* name) +{ + struct cnh_fshook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_fshook_init(); + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FSHOOK_IRP_OP_DIR_OPEN; + irp.opendir_name = name; + irp.opendir_ret = NULL; + + result = cnh_fshook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return NULL; + } + + errno = 0; + + return irp.opendir_ret; +} + +int __lxstat(int version, const char* file, struct stat* buf) +{ + struct cnh_fshook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_fshook_init(); + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FSHOOK_IRP_OP_LXSTAT; + irp.xstat_version = version; + irp.xstat_file = file; + irp.xstat_buf = buf; + + result = cnh_fshook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return 0; +} + +int __xstat(int version, const char* file, struct stat* buf) +{ + struct cnh_fshook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_fshook_init(); + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FSHOOK_IRP_OP_XSTAT; + irp.xstat_version = version; + irp.xstat_file = file; + irp.xstat_buf = buf; + + result = cnh_fshook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return 0; +} + +int rename(const char* old, const char* new) +{ + struct cnh_fshook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_fshook_init(); + + if (old == NULL || new == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FSHOOK_IRP_OP_RENAME; + irp.rename_old = old; + irp.rename_new = new; + + result = cnh_fshook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return 0; +} + +int remove(const char* pathname) +{ + struct cnh_fshook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_fshook_init(); + + if (pathname == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FSHOOK_IRP_OP_REMOVE; + irp.remove_pathname = pathname; + + result = cnh_fshook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return 0; +} + +int access(const char* path, int amode) +{ + struct cnh_fshook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_fshook_init(); + + if (path == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_FSHOOK_IRP_OP_ACCESS; + irp.access_path = path; + irp.access_amode = amode; + + result = cnh_fshook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return 0; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Helper functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_fshook_init(void) +{ + int expected; + + if (atomic_load(&_cnh_fshook_initted) > 0) { + return; + } + + expected = 0; + + if (!atomic_compare_exchange_strong(&_cnh_fshook_init_in_progress, &expected, 1)) { + while (atomic_load(&_cnh_fshook_init_in_progress) > 1) { + util_time_sleep_us(100); + } + + return; + } + + /* Check again to ensure the current thread yielded between the init'd check and setting init in progress */ + if (atomic_load(&_cnh_fshook_initted) > 0) { + /* Revert init in progress, because nothing to do anymore */ + atomic_store(&_cnh_fshook_init_in_progress, 0); + return; + } + + _cnh_fshook_real_opendir = (cnh_fshook_opendir_t) cnh_lib_get_func_addr("opendir"); + _cnh_fshook_real_lxstat = (cnh_fshook_lxstat_t) cnh_lib_get_func_addr("__lxstat"); + _cnh_fshook_real_xstat = (cnh_fshook_xstat_t) cnh_lib_get_func_addr("__xstat"); + _cnh_fshook_real_rename = (cnh_fshook_rename_t) cnh_lib_get_func_addr("rename"); + _cnh_fshook_real_remove = (cnh_fshook_remove_t) cnh_lib_get_func_addr("remove"); + _cnh_fshook_real_access = (cnh_fshook_access_t) cnh_lib_get_func_addr("access"); + + pthread_mutex_init(&_cnh_fshook_lock, NULL); + + atomic_store(&_cnh_fshook_initted, 1); + atomic_store(&_cnh_fshook_init_in_progress, 0); +} + +static enum cnh_result _cnh_fshook_invoke_real(struct cnh_fshook_irp *irp) +{ + cnh_fshook_fn_t handler; + + assert(irp != NULL); + assert(irp->op < _countof(_cnh_fshook_real_handlers)); + + handler = _cnh_fshook_real_handlers[irp->op]; + + assert(handler != NULL); + + return handler(irp); +} + +static enum cnh_result _cnh_fshook_invoke_real_opendir(struct cnh_fshook_irp *irp) +{ + DIR* dir; + + assert(irp != NULL); + + dir = _cnh_fshook_real_opendir(irp->opendir_name); + + if (dir == NULL) { + return cnh_errno_to_result(errno); + } + + irp->opendir_ret = dir; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_fshook_invoke_real_lxstat(struct cnh_fshook_irp *irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_fshook_real_lxstat(irp->xstat_version, irp->xstat_file, irp->xstat_buf); + + if (res != 0) { + return cnh_errno_to_result(errno); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_fshook_invoke_real_xstat(struct cnh_fshook_irp *irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_fshook_real_xstat(irp->xstat_version, irp->xstat_file, irp->xstat_buf); + + if (res != 0) { + return cnh_errno_to_result(errno); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_fshook_invoke_real_rename(struct cnh_fshook_irp *irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_fshook_real_rename(irp->rename_old, irp->rename_new); + + if (res != 0) { + return cnh_errno_to_result(errno); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_fshook_invoke_real_remove(struct cnh_fshook_irp *irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_fshook_real_remove(irp->remove_pathname); + + if (res != 0) { + return cnh_errno_to_result(errno); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_fshook_invoke_real_access(struct cnh_fshook_irp *irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_fshook_real_access(irp->access_path, irp->access_amode); + + if (res != 0) { + return cnh_errno_to_result(errno); + } + + return CNH_RESULT_SUCCESS; +} \ No newline at end of file diff --git a/src/main/capnhook/hook/fshook.h b/src/main/capnhook/hook/fshook.h new file mode 100644 index 0000000..4b3c85c --- /dev/null +++ b/src/main/capnhook/hook/fshook.h @@ -0,0 +1,72 @@ +/** + * Hook module for hooking file system related calls, + * not counting calls operating on actual opened files, e.g. + * fopen, fread, fwrite etc. + * + * Based on original capnhook code for windows by decafcode: + * https://github.com/decafcode/capnhook + */ +#ifndef CAPNHOOK_FSHOOK_H +#define CAPNHOOK_FSHOOK_H + +#include +#include +#include + +#include "capnhook/hook/result.h" + +/** + * Available operations to hook + */ +enum cnh_fshook_irp_op { + CNH_FSHOOK_IRP_OP_DIR_OPEN = 0, + CNH_FSHOOK_IRP_OP_LXSTAT = 1, + CNH_FSHOOK_IRP_OP_XSTAT = 2, + CNH_FSHOOK_IRP_OP_RENAME = 3, + CNH_FSHOOK_IRP_OP_REMOVE = 4, + CNH_FSHOOK_IRP_OP_ACCESS = 5, +}; + +/** + * I/O request packet + */ +struct cnh_fshook_irp { + enum cnh_fshook_irp_op op; + size_t next_handler; + const char* opendir_name; + DIR* opendir_ret; + int xstat_version; + const char* xstat_file; + struct stat* xstat_buf; + const char* rename_old; + const char* rename_new; + const char* remove_pathname; + const char* access_path; + int access_amode; +}; + +/** + * Hook function type + */ +typedef enum cnh_result (*cnh_fshook_fn_t)(struct cnh_fshook_irp *irp); + +/** + * Add a new hook handler to the hook module. + * The handler added is getting called on every hooked operation. + * + * @param fn Pointer to hook function to add + * @return Result/error code if hooking was successful + */ +enum cnh_result cnh_fshook_push_handler(cnh_fshook_fn_t fn); + +/** + * Invoke the next hooked function registered in the hook module. + * Call this from your hook handler if you want to pass on execution to further + * hooked calls reaching the original function at the end of the hook chain. + * + * @param irp I/O request package to dispatch + * @return Result of the following function dispatching the request package + */ +enum cnh_result cnh_fshook_invoke_next(struct cnh_fshook_irp *irp); + +#endif diff --git a/src/main/capnhook/hook/iobuf.c b/src/main/capnhook/hook/iobuf.c new file mode 100644 index 0000000..fdfc9d3 --- /dev/null +++ b/src/main/capnhook/hook/iobuf.c @@ -0,0 +1,253 @@ +#include +#include + +#include "capnhook/hook/iobuf.h" + +void cnh_iobuf_flip(struct cnh_const_iobuf *child, struct cnh_iobuf *parent) +{ + assert(child != NULL); + assert(parent != NULL); + + child->bytes = parent->bytes; + child->pos = 0; + child->nbytes = parent->pos; +} + +size_t cnh_iobuf_move(struct cnh_iobuf *dest, struct cnh_const_iobuf *src) +{ + size_t dest_avail; + size_t src_avail; + size_t chunksz; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + + assert(src != NULL); + assert(src->bytes != NULL || src->nbytes == 0); + assert(src->pos <= src->nbytes); + + dest_avail = dest->nbytes - dest->pos; + src_avail = src->nbytes - src->pos; + chunksz = dest_avail < src_avail ? dest_avail : src_avail; + + memcpy(&dest->bytes[dest->pos], &src->bytes[src->pos], chunksz); + + dest->pos += chunksz; + src->pos += chunksz; + + return chunksz; +} + +size_t cnh_iobuf_shift(struct cnh_iobuf *dest, struct cnh_iobuf *src) +{ + struct cnh_const_iobuf span; + + assert(dest != NULL); + assert(src != NULL); + + cnh_iobuf_flip(&span, src); + cnh_iobuf_move(dest, &span); + + memmove(src->bytes, &src->bytes[span.pos], span.nbytes - span.pos); + src->pos -= span.pos; + + return span.pos; +} + +enum cnh_result cnh_iobuf_read(struct cnh_const_iobuf *src, void *bytes, size_t nbytes) +{ + assert(src != NULL); + assert(bytes != NULL || nbytes == 0); + + if (src->pos + nbytes > src->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + memcpy(bytes, &src->bytes[src->pos], nbytes); + src->pos += nbytes; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_read_8(struct cnh_const_iobuf *src, uint8_t *out) +{ + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint8_t) > src->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + *out = src->bytes[src->pos++]; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_read_be16(struct cnh_const_iobuf *src, uint16_t *out) +{ + uint16_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint16_t) > src->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + value = src->bytes[src->pos++] << 8; + value |= src->bytes[src->pos++]; + + *out = value; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_read_be32(struct cnh_const_iobuf *src, uint32_t *out) +{ + uint32_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint32_t) > src->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + value = src->bytes[src->pos++] << 24; + value |= src->bytes[src->pos++] << 16; + value |= src->bytes[src->pos++] << 8; + value |= src->bytes[src->pos++]; + + *out = value; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_read_le16(struct cnh_const_iobuf *src, uint16_t *out) +{ + uint16_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint16_t) > src->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + value = src->bytes[src->pos++]; + value |= src->bytes[src->pos++] << 8; + + *out = value; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_read_le32(struct cnh_const_iobuf *src, uint32_t *out) +{ + uint32_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint32_t) > src->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + value = src->bytes[src->pos++]; + value |= src->bytes[src->pos++] << 8; + value |= src->bytes[src->pos++] << 16; + value |= src->bytes[src->pos++] << 24; + + *out = value; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_write(struct cnh_iobuf *dest, const void *bytes, size_t nbytes) +{ + assert(dest != NULL); + assert(bytes != NULL || nbytes == 0); + + if (dest->pos + nbytes > dest->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + memcpy(&dest->bytes[dest->pos], bytes, nbytes); + dest->pos += nbytes; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_write_8(struct cnh_iobuf *dest, uint8_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint8_t) > dest->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + dest->bytes[dest->pos++] = value; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_write_be16(struct cnh_iobuf *dest, uint16_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint16_t) > dest->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + dest->bytes[dest->pos++] = (uint8_t) (value >> 8); + dest->bytes[dest->pos++] = (uint8_t) value; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_write_be32(struct cnh_iobuf *dest, uint32_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint32_t) > dest->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + dest->bytes[dest->pos++] = (uint8_t) (value >> 24); + dest->bytes[dest->pos++] = (uint8_t) (value >> 16); + dest->bytes[dest->pos++] = (uint8_t) (value >> 8); + dest->bytes[dest->pos++] = (uint8_t) value; + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_write_le16(struct cnh_iobuf *dest, uint16_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint16_t) > dest->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + dest->bytes[dest->pos++] = (uint8_t) value; + dest->bytes[dest->pos++] = (uint8_t) (value >> 8); + + return CNH_RESULT_SUCCESS; +} + +enum cnh_result cnh_iobuf_write_le32(struct cnh_iobuf *dest, uint32_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint32_t) > dest->nbytes) { + return CNH_RESULT_ERROR_INSUFFICIENT_BUFFER; + } + + dest->bytes[dest->pos++] = (uint8_t) value; + dest->bytes[dest->pos++] = (uint8_t) (value >> 8); + dest->bytes[dest->pos++] = (uint8_t) (value >> 16); + dest->bytes[dest->pos++] = (uint8_t) (value >> 24); + + return CNH_RESULT_SUCCESS; +} diff --git a/src/main/capnhook/hook/iobuf.h b/src/main/capnhook/hook/iobuf.h new file mode 100644 index 0000000..0f2e459 --- /dev/null +++ b/src/main/capnhook/hook/iobuf.h @@ -0,0 +1,162 @@ +/** + * Implementation of an I/O buffer used by hooking modules + * + * Based on original capnhook code for windows by decafcode: + * https://github.com/decafcode/capnhook + */ +#ifndef CAPNHOOK_IOBUF_H +#define CAPNHOOK_IOBUF_H + +#include +#include + +#include "capnhook/hook/result.h" + +/** + * I/O buffer + */ +struct cnh_iobuf { + uint8_t *bytes; + size_t nbytes; + size_t pos; +}; + +/** + * I/O buffer + */ +struct cnh_const_iobuf { + const uint8_t *bytes; + size_t nbytes; + size_t pos; +}; + +/** + * Flip the I/O buffer. Set the current pos as size and reset the position. + * + * @param child Flipped I/O buffer + * @param parent I/O buffer to flip + */ +void cnh_iobuf_flip(struct cnh_const_iobuf *child, struct cnh_iobuf *parent); + +/** + * Move the contents currently enclosed by pos and end of an I/O buffer to a destination. + * + * @param dest Destination buffer to move the source to, starting at current pos + * @param src Source to move to the destination, starting at current pos up to end of the buffer + * @return Number of bytes copied to destination + */ +size_t cnh_iobuf_move(struct cnh_iobuf *dest, struct cnh_const_iobuf *src); + +size_t cnh_iobuf_shift(struct cnh_iobuf *dest, struct cnh_iobuf *src); + +/** + * Read data from an I/O buffer + * + * @param src Source to read from (starting at current position) + * @param bytes Target to read to + * @param nbytes Number of bytes to read + * @return Result of operation + */ +enum cnh_result cnh_iobuf_read(struct cnh_const_iobuf *src, void *bytes, size_t nbytes); + +/** + * Read a byte from an I/O buffer + * + * @param src Source to read from (starting at current position) + * @param value Pointer to variable to read the data into + * @return Result of operation + */ +enum cnh_result cnh_iobuf_read_8(struct cnh_const_iobuf *src, uint8_t *value); + +/** + * Read a 2 byte value, big endian byte order, from an I/O buffer + * + * @param src Source to read from (starting at current position) + * @param value Pointer to variable to read the data into + * @return Result of operation + */ +enum cnh_result cnh_iobuf_read_be16(struct cnh_const_iobuf *src, uint16_t *value); + +/** + * Read a 4 byte value, big endian byte order, from an I/O buffer + * + * @param src Source to read from (starting at current position) + * @param value Pointer to variable to read the data into + * @return Result of operation + */ +enum cnh_result cnh_iobuf_read_be32(struct cnh_const_iobuf *src, uint32_t *value); + +/** + * Read a 2 byte value, little endian byte order, from an I/O buffer + * + * @param src Source to read from (starting at current position) + * @param value Pointer to variable to read the data into + * @return Result of operation + */ +enum cnh_result cnh_iobuf_read_le16(struct cnh_const_iobuf *src, uint16_t *value); + +/** + * Read a 4 byte value, little endian byte order, from an I/O buffer + * + * @param src Source to read from (starting at current position) + * @param value Pointer to variable to read the data into + * @return Result of operation + */ +enum cnh_result cnh_iobuf_read_le32(struct cnh_const_iobuf *src, uint32_t *value); + +/** + * Write data to an I/O buffer + * + * @param dest I/O buffer to write to (starting at current position) + * @param bytes Pointer to data to write + * @param nbytes Number of bytes to write + * @return Result of operation + */ +enum cnh_result cnh_iobuf_write(struct cnh_iobuf *dest, const void *bytes, size_t nbytes); + +/** + * Write a byte to an I/O buffer + * + * @param dest Destination I/O buffer to write to (starting at current position) + * @param value Value to write + * @return Result of operation + */ +enum cnh_result cnh_iobuf_write_8(struct cnh_iobuf *dest, uint8_t value); + +/** + * Write a 2 byte value, big endian byte order, to an I/O buffer + * + * @param dest Destination I/O buffer to write to (starting at current position) + * @param value Value to write + * @return Result of operation + */ +enum cnh_result cnh_iobuf_write_be16(struct cnh_iobuf *dest, uint16_t value); + +/** + * Write a 4 byte value, big endian byte order, to an I/O buffer + * + * @param dest Destination I/O buffer to write to (starting at current position) + * @param value Value to write + * @return Result of operation + */ +enum cnh_result cnh_iobuf_write_be32(struct cnh_iobuf *dest, uint32_t value); + +/** + * Write a 2 byte value, little endian byte order, to an I/O buffer + * + * @param dest Destination I/O buffer to write to (starting at current position) + * @param value Value to write + * @return Result of operation + */ +enum cnh_result cnh_iobuf_write_le16(struct cnh_iobuf *dest, uint16_t value); + +/** + * Write a 4 byte value, little endian byte order, to an I/O buffer + * + * @param dest Destination I/O buffer to write to (starting at current position) + * @param value Value to write + * @return Result of operation + */ +enum cnh_result cnh_iobuf_write_le32(struct cnh_iobuf *dest, uint32_t value); + +#endif diff --git a/src/main/capnhook/hook/iohook.c b/src/main/capnhook/hook/iohook.c new file mode 100644 index 0000000..faa87cc --- /dev/null +++ b/src/main/capnhook/hook/iohook.c @@ -0,0 +1,552 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "capnhook/hook/common.h" +#include "capnhook/hook/iohook.h" +#include "capnhook/hook/lib.h" + +#include "util/time.h" + +#define INVALID_FILE_DEVICE (-1) + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Real funcs typedefs */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +typedef int (*cnh_iohook_open_t)(const char* path, int oflag); +typedef FILE* (*cnh_iohook_fdopen_t)(int fd, const char* mode); +typedef ssize_t (*cnh_iohook_write_t)(int fd, const void* buf, size_t count); +typedef ssize_t (*cnh_iohook_read_t)(int fd, void* buf, size_t count); +typedef off_t (*cnh_iohook_lseek_t)(int fildes, off_t offset, int whence); +typedef int (*cnh_iohook_ioctl_t)(int fd, int request, void* data); +typedef int (*cnh_iohook_close_t)(int fd); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private helpers */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_iohook_init(void); + +static enum cnh_result _cnh_iohook_invoke_real(struct cnh_iohook_irp *irp); +static enum cnh_result _cnh_iohook_invoke_real_open(struct cnh_iohook_irp *irp); +static enum cnh_result _cnh_iohook_invoke_real_fdopen(struct cnh_iohook_irp *irp); +static enum cnh_result _cnh_iohook_invoke_real_close(struct cnh_iohook_irp *irp); +static enum cnh_result _cnh_iohook_invoke_real_read(struct cnh_iohook_irp *irp); +static enum cnh_result _cnh_iohook_invoke_real_write(struct cnh_iohook_irp *irp); +static enum cnh_result _cnh_iohook_invoke_real_seek(struct cnh_iohook_irp *irp); +static enum cnh_result _cnh_iohook_invoke_real_ioctl(struct cnh_iohook_irp *irp); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private state */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static cnh_iohook_open_t _cnh_iohook_real_open; +static cnh_iohook_fdopen_t _cnh_iohook_real_fdopen; +static cnh_iohook_close_t _cnh_iohook_real_close; +static cnh_iohook_read_t _cnh_iohook_real_read; +static cnh_iohook_write_t _cnh_iohook_real_write; +static cnh_iohook_lseek_t _cnh_iohook_real_lseek; +static cnh_iohook_ioctl_t _cnh_iohook_real_ioctl; + +static const cnh_iohook_fn_t _cnh_iohook_real_handlers[7] = { + [CNH_IOHOOK_IRP_OP_OPEN] = _cnh_iohook_invoke_real_open, + [CNH_IOHOOK_IRP_OP_FDOPEN] = _cnh_iohook_invoke_real_fdopen, + [CNH_IOHOOK_IRP_OP_CLOSE] = _cnh_iohook_invoke_real_close, + [CNH_IOHOOK_IRP_OP_READ] = _cnh_iohook_invoke_real_read, + [CNH_IOHOOK_IRP_OP_WRITE] = _cnh_iohook_invoke_real_write, + [CNH_IOHOOK_IRP_OP_SEEK] = _cnh_iohook_invoke_real_seek, + [CNH_IOHOOK_IRP_OP_IOCTL] = _cnh_iohook_invoke_real_ioctl, +}; + +static atomic_int _cnh_iohook_initted = ATOMIC_VAR_INIT(0); +static atomic_int _cnh_iohook_init_in_progress = ATOMIC_VAR_INIT(0); +static pthread_mutex_t _cnh_iohook_lock; +static cnh_iohook_fn_t *_cnh_iohook_handlers; +static size_t _cnh_iohook_nhandlers; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Public module functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +enum cnh_result cnh_iohook_push_handler(cnh_iohook_fn_t fn) +{ + cnh_iohook_fn_t *new_array; + size_t new_size; + enum cnh_result result; + + assert(fn != NULL); + + _cnh_iohook_init(); + pthread_mutex_lock(&_cnh_iohook_lock); + + new_size = _cnh_iohook_nhandlers + 1; + new_array = realloc(_cnh_iohook_handlers, new_size * sizeof(cnh_iohook_fn_t)); + + if (new_array != NULL) { + _cnh_iohook_handlers = new_array; + _cnh_iohook_handlers[_cnh_iohook_nhandlers++] = fn; + result = CNH_RESULT_SUCCESS; + } else { + result = CNH_RESULT_OUT_OF_MEMORY; + } + + pthread_mutex_unlock(&_cnh_iohook_lock); + + return result; +} + +enum cnh_result cnh_iohook_invoke_next(struct cnh_iohook_irp *irp) +{ + cnh_iohook_fn_t handler; + enum cnh_result result; + + assert(irp != NULL); + assert(_cnh_iohook_initted > 0); + + pthread_mutex_lock(&_cnh_iohook_lock); + + assert(irp->next_handler <= _cnh_iohook_nhandlers); + + if (irp->next_handler < _cnh_iohook_nhandlers) { + handler = _cnh_iohook_handlers[irp->next_handler]; + irp->next_handler++; + } else { + handler = _cnh_iohook_invoke_real; + irp->next_handler = (size_t) -1; + } + + pthread_mutex_unlock(&_cnh_iohook_lock); + + result = handler(irp); + + if (result != CNH_RESULT_SUCCESS) { + irp->next_handler = (size_t) -1; + } + + return result; +} + +int cnh_iohook_open_dummy_fd() +{ + _cnh_iohook_init(); + + /* We need to return some sort of valid FD to the + caller, since we're simulating a device that + doesn't exist. Open an FD on /dev/null. */ + + /* Open read only */ + return _cnh_iohook_real_open("/dev/null", 0); +} + +void cnh_iohook_close_dummy_fd(int fd) +{ + _cnh_iohook_init(); + + /* Avoid hooking pipeline when using dummy handles */ + _cnh_iohook_real_close(fd); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooked functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +int open(const char* path, int oflag) +{ + struct cnh_iohook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_iohook_init(); + + if (path == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return INVALID_FILE_DEVICE; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_IOHOOK_IRP_OP_OPEN; + irp.fd = INVALID_FILE_DEVICE; + irp.open_filename = path; + irp.open_flags = oflag; + + result = cnh_iohook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return INVALID_FILE_DEVICE; + } + + errno = 0; + + return irp.fd; +} + +FILE* fdopen(int fd, const char* mode) +{ + struct cnh_iohook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_iohook_init(); + + if (fd == -1 || mode == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return NULL; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_IOHOOK_IRP_OP_FDOPEN; + irp.fdopen_fd = fd; + irp.fdopen_mode = mode; + irp.fdopen_res = NULL; + + result = cnh_iohook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return NULL; + } + + errno = 0; + + return irp.fdopen_res; +} + +int close(int fd) +{ + struct cnh_iohook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_iohook_init(); + + if (fd == INVALID_FILE_DEVICE) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_IOHOOK_IRP_OP_CLOSE; + irp.fd = fd; + + result = cnh_iohook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return 0; +} + +ssize_t read(int fd, void* buf, size_t count) +{ + struct cnh_iohook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_iohook_init(); + + if (fd == INVALID_FILE_DEVICE || buf == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_IOHOOK_IRP_OP_READ; + irp.fd = fd; + irp.read.bytes = buf; + irp.read.nbytes = count; + irp.read.pos = 0; + + result = cnh_iohook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + assert(irp.read.pos <= irp.read.nbytes); + + errno = 0; + + return irp.read.pos; +} + +ssize_t write(int fd, const void* buf, size_t count) +{ + struct cnh_iohook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_iohook_init(); + + if (fd == INVALID_FILE_DEVICE || buf == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_IOHOOK_IRP_OP_WRITE; + irp.fd = fd; + irp.write.bytes = buf; + irp.write.nbytes = count; + irp.write.pos = 0; + + result = cnh_iohook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + assert(irp.write.pos <= irp.write.nbytes); + + errno = 0; + + return irp.write.pos; +} + +off_t lseek(int fd, off_t offset, int whence) +{ + struct cnh_iohook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_iohook_init(); + + if (fd == INVALID_FILE_DEVICE) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_IOHOOK_IRP_OP_SEEK; + irp.fd = fd; + irp.seek_origin = whence; + irp.seek_offset = offset; + + result = cnh_iohook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return (off_t) irp.seek_pos; +} + +int ioctl(int fd, int request, void* data) +{ + struct cnh_iohook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_iohook_init(); + + if (fd == INVALID_FILE_DEVICE) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + return -1; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_IOHOOK_IRP_OP_IOCTL; + irp.fd = fd; + irp.ioctl_req = request; + irp.ioctl.bytes = data; + + result = cnh_iohook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return -1; + } + + errno = 0; + + return irp.ioctl.pos; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Helper functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_iohook_init(void) +{ + int expected; + + if (atomic_load(&_cnh_iohook_initted) > 0) { + return; + } + + expected = 0; + + if (!atomic_compare_exchange_strong(&_cnh_iohook_init_in_progress, &expected, 1)) { + while (atomic_load(&_cnh_iohook_init_in_progress) > 1) { + util_time_sleep_us(100); + } + + return; + } + + /* Check again to ensure the current thread yielded between the init'd check and setting init in progress */ + if (atomic_load(&_cnh_iohook_initted) > 0) { + /* Revert init in progress, because nothing to do anymore */ + atomic_store(&_cnh_iohook_init_in_progress, 0); + return; + } + + _cnh_iohook_real_open = (cnh_iohook_open_t) cnh_lib_get_func_addr("open"); + _cnh_iohook_real_fdopen = (cnh_iohook_fdopen_t) cnh_lib_get_func_addr("fdopen"); + _cnh_iohook_real_close = (cnh_iohook_close_t) cnh_lib_get_func_addr("close"); + _cnh_iohook_real_read = (cnh_iohook_read_t) cnh_lib_get_func_addr("read"); + _cnh_iohook_real_write = (cnh_iohook_write_t) cnh_lib_get_func_addr("write"); + _cnh_iohook_real_lseek = (cnh_iohook_lseek_t) cnh_lib_get_func_addr("lseek"); + _cnh_iohook_real_ioctl = (cnh_iohook_ioctl_t) cnh_lib_get_func_addr("ioctl"); + + pthread_mutex_init(&_cnh_iohook_lock, NULL); + + atomic_store(&_cnh_iohook_initted, 1); + atomic_store(&_cnh_iohook_init_in_progress, 0); +} + +static enum cnh_result _cnh_iohook_invoke_real(struct cnh_iohook_irp *irp) +{ + cnh_iohook_fn_t handler; + + assert(irp != NULL); + assert(irp->op < _countof(_cnh_iohook_real_handlers)); + + handler = _cnh_iohook_real_handlers[irp->op]; + + assert(handler != NULL); + + return handler(irp); +} + +static enum cnh_result _cnh_iohook_invoke_real_open(struct cnh_iohook_irp *irp) +{ + int fd; + + assert(irp != NULL); + + fd = _cnh_iohook_real_open(irp->open_filename, irp->open_flags); + + if (fd == INVALID_FILE_DEVICE) { + return cnh_errno_to_result(errno); + } + + irp->fd = fd; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_iohook_invoke_real_fdopen(struct cnh_iohook_irp *irp) +{ + FILE* file; + + assert(irp != NULL); + + file = _cnh_iohook_real_fdopen(irp->fdopen_fd, irp->fdopen_mode); + + if (file == NULL) { + return cnh_errno_to_result(errno); + } + + irp->fdopen_res = file; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_iohook_invoke_real_close(struct cnh_iohook_irp *irp) +{ + int ok; + + assert(irp != NULL); + + ok = _cnh_iohook_real_close(irp->fd); + + if (ok < 0) { + return cnh_errno_to_result(errno); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_iohook_invoke_real_read(struct cnh_iohook_irp *irp) +{ + ssize_t read; + + assert(irp != NULL); + + read = _cnh_iohook_real_read(irp->fd, &irp->read.bytes[irp->read.pos], irp->read.nbytes - irp->read.pos); + + if (read < 0) { + return cnh_errno_to_result(errno); + } + + irp->read.pos += read; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_iohook_invoke_real_write(struct cnh_iohook_irp *irp) +{ + ssize_t written; + + assert(irp != NULL); + + written = _cnh_iohook_real_write(irp->fd, &irp->write.bytes[irp->write.pos], irp->write.nbytes - irp->write.pos); + + if (written < 0) { + return cnh_errno_to_result(errno); + } + + irp->write.pos += written; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_iohook_invoke_real_seek(struct cnh_iohook_irp *irp) +{ + off_t result; + + assert(irp != NULL); + + result = _cnh_iohook_real_lseek(irp->fd, (off_t) irp->seek_offset, irp->seek_origin); + + if (result < 0) { + return cnh_errno_to_result(errno); + } + + irp->seek_pos = result; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_iohook_invoke_real_ioctl(struct cnh_iohook_irp *irp) +{ + int result; + + assert(irp != NULL); + + result = _cnh_iohook_real_ioctl(irp->fd, irp->ioctl_req, irp->ioctl.bytes); + + if (result < 0) { + return cnh_errno_to_result(errno); + } + + irp->ioctl.pos = (size_t) result; + + return CNH_RESULT_SUCCESS; +} \ No newline at end of file diff --git a/src/main/capnhook/hook/iohook.h b/src/main/capnhook/hook/iohook.h new file mode 100644 index 0000000..83d7eea --- /dev/null +++ b/src/main/capnhook/hook/iohook.h @@ -0,0 +1,90 @@ +/** + * Hook module for hooking I/O related calls, e.g. open, read, write etc. + * + * Based on original capnhook code for windows by decafcode: + * https://github.com/decafcode/capnhook + */ +#ifndef CAPNHOOK_IOHOOK_H +#define CAPNHOOK_IOHOOK_H + +#include +#include +#include + +#include "capnhook/hook/iobuf.h" + +/** + * Available operations to hook + */ +enum cnh_iohook_irp_op { + CNH_IOHOOK_IRP_OP_OPEN = 0, + CNH_IOHOOK_IRP_OP_FDOPEN = 1, + CNH_IOHOOK_IRP_OP_CLOSE = 2, + CNH_IOHOOK_IRP_OP_READ = 3, + CNH_IOHOOK_IRP_OP_WRITE = 4, + CNH_IOHOOK_IRP_OP_SEEK = 5, + CNH_IOHOOK_IRP_OP_IOCTL = 6, +}; + +/** + * I/O request packet + */ +struct cnh_iohook_irp { + enum cnh_iohook_irp_op op; + size_t next_handler; + int fd; + const char *open_filename; + int open_flags; + int fdopen_fd; + const char* fdopen_mode; + FILE* fdopen_res; + struct cnh_iobuf read; + struct cnh_const_iobuf write; + int seek_origin; + int64_t seek_offset; + uint64_t seek_pos; + int ioctl_req; + struct cnh_iobuf ioctl; +}; + +/** + * Hook function type + */ +typedef enum cnh_result (*cnh_iohook_fn_t)(struct cnh_iohook_irp *irp); + +/** + * Add a new hook handler to the hook module. + * The handler added is getting called on every hooked operation. + * + * @param fn Pointer to hook function to add + * @return Result/error code if hooking was successful + */ +enum cnh_result cnh_iohook_push_handler(cnh_iohook_fn_t fn); + +/** + * Invoke the next hooked function registered in the hook module. + * Call this from your hook handler if you want to pass on execution to further + * hooked calls reaching the original function at the end of the hook chain. + * + * @param irp I/O request package to dispatch + * @return Result of the following function dispatching the request package + */ +enum cnh_result cnh_iohook_invoke_next(struct cnh_iohook_irp *irp); + +/** + * Create a dummy file handle. Use this function for file access virtualization. + * Do not execute real operations on the handle returned. + * + * @return Actual file handle instance pointing to a dummy endpoint. + */ +int cnh_iohook_open_dummy_fd(); + +/** + * Close a dummy file handle. Make sure to free your previously allocated handles + * to avoid resource leaks. Do not free them using the real close function. + * + * @param file Dummy file handle to free + */ +void cnh_iohook_close_dummy_fd(int fd); + +#endif diff --git a/src/main/capnhook/hook/lib-main.h b/src/main/capnhook/hook/lib-main.h new file mode 100644 index 0000000..74f4965 --- /dev/null +++ b/src/main/capnhook/hook/lib-main.h @@ -0,0 +1,57 @@ +/** + * Provides a set of macros to hook into an application right before its + * main function is called. + */ +#ifndef CNH_LIB_MAIN_H +#define CNH_LIB_MAIN_H + +#include +#include +#include + +#include + +#include "util/log.h" + +#define LIB_MAIN_CONSTRUCTOR(func) __attribute__((constructor)) void lib_constructor() { func(); } + +#define LIB_MAIN_DESTRUCTOR(func) __attribute__((destructor)) void lib_destructor() { func(); } + +#define LIB_MAIN_TRAP_MAIN(func_before_main, func_after_main) \ +typedef int (*func_libc_start_main_t)(int *(main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end)); \ +typedef int* (*func_main_t)(int, char**, char**); \ +typedef void (*func_init_t)(void); \ +static func_main_t orig_main; \ +static func_init_t orig_init; \ +static bool trapped; \ +\ +int* trap_main(int argc, char** argv, char** envp) \ +{ \ + log_debug("Trap before main (%s): argc %d", argv[0], argc); \ + func_before_main(argc, argv); \ + log_debug("Calling orig_init"); \ + orig_init(); \ + log_debug("Calling real main (%s): argc %d", argv[0], argc); \ + int* ret = orig_main(argc, argv, envp); \ + log_debug("Calling cleanup after main (%s): ret %d", argv[0], ret); \ + func_after_main(); \ + return ret; \ +} \ +\ +void dummy_init(void) {} \ +\ +int __libc_start_main(int *(main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end)) \ +{ \ + func_libc_start_main_t start = (func_libc_start_main_t) dlsym(RTLD_NEXT, "__libc_start_main"); \ + if (start == NULL) { \ + printf("ERROR finding orig func __libc_start_main\n"); \ + } \ + orig_main = main; \ + main = trap_main; \ + orig_init = init; \ + init = dummy_init; \ + trapped = true; \ + return (*start)(main, argc, ubp_av, init, fini, rtld_fini, stack_end); \ +} + +#endif \ No newline at end of file diff --git a/src/main/capnhook/hook/lib.c b/src/main/capnhook/hook/lib.c new file mode 100644 index 0000000..9ce9435 --- /dev/null +++ b/src/main/capnhook/hook/lib.c @@ -0,0 +1,179 @@ +#define LOG_MODULE "hook-lib" + +#include + +#include "capnhook/hook/lib.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/rand.h" + +typedef void* (*cnh_lib_load_t)(const char* lib); +typedef void* (*cnh_lib_get_func_addr_handle_t)(void* lib_handle, const char* func_name); +typedef void (*cnh_lib_unload_t)(void* handle); +typedef void* (*cnh_lib_get_func_addr_t)(const char* func_name); + +static void* _cnh_lib_load(const char* lib); +static void* _cnh_lib_get_func_addr_handle(void* lib_handle, const char* func_name); +static void _cnh_lib_unload(void* handle); +static void* _cnh_lib_get_func_addr(const char* func_name); + +static void* _cnh_lib_load_test(const char* lib); +static void* _cnh_lib_get_func_addr_handle_test(void* lib_handle, const char* func_name); +static void _cnh_lib_unload_test(void* handle); +static void* _cnh_lib_get_func_addr_test(const char* func_name); + +struct cnh_lib_interface { + cnh_lib_load_t load; + cnh_lib_get_func_addr_handle_t get_func_addr_handle; + cnh_lib_unload_t unload; + cnh_lib_get_func_addr_t get_func_addr; +}; + +static struct cnh_lib_interface _cnh_lib_interface = { + .load = _cnh_lib_load, + .get_func_addr_handle = _cnh_lib_get_func_addr_handle, + .unload = _cnh_lib_unload, + .get_func_addr = _cnh_lib_get_func_addr, +}; + +static struct cnh_lib_unit_test_func_mocks* _cnh_lib_func_mocks; +size_t _cnh_lib_func_mocks_cnt; + +static void* _cnh_lib_load(const char* lib) +{ + void* so = dlopen(lib, RTLD_NOW); + + if (so == NULL) { + log_warn("Failed to open library %s: %s", lib, dlerror()); + } else { + log_debug("Opened lib %s, handle %p", lib, so); + } + + return so; +} + +static void* _cnh_lib_get_func_addr_handle(void* lib_handle, const char* func_name) +{ + void* ret = dlsym(lib_handle, func_name); + + if (ret == NULL) { + log_warn("Could not find func %s in library %p", func_name, lib_handle); + } + + return ret; +} + +static void _cnh_lib_unload(void* handle) +{ + dlclose(handle); + log_debug("Closed lib handle %p", handle); +} + +static void* _cnh_lib_get_func_addr(const char* func_name) +{ + void* ret = dlsym(RTLD_NEXT, func_name); + + if (ret == NULL) { + log_warn("Could not find func %s", func_name); + } + + return ret; +} + +static void* _cnh_lib_load_test(const char* lib) +{ + log_warn("Loading lib %s not supported in test", lib); + + if (sizeof(void*) == 4) { + return (void*) util_rand_gen_32(); + } else { + log_die_illegal_state(); + } +} + +static void* _cnh_lib_get_func_addr_handle_test(void* lib_handle, const char* func_name) +{ + return _cnh_lib_get_func_addr_test(func_name); +} + +static void _cnh_lib_unload_test(void* handle) +{ + log_warn("Unloading lib %p not supported in test", handle); +} + +static void* _cnh_lib_get_func_addr_test(const char* func_name) +{ + for (size_t i = 0; i < _cnh_lib_func_mocks_cnt; i++) { + if (!strcmp(_cnh_lib_func_mocks[i].name, func_name)) { + return _cnh_lib_func_mocks->func; + } + } + + log_warn("Could not find func %s", func_name); + + return NULL; +} + +struct cnh_lib_unit_test_func_mocks* cnh_lib_allocate_func_mocks(size_t count) +{ + log_assert(count >= 0); + + struct cnh_lib_unit_test_func_mocks* func_mocks; + + func_mocks = util_xmalloc(sizeof(struct cnh_lib_unit_test_func_mocks) * count); + + memset(func_mocks, 0, sizeof(struct cnh_lib_unit_test_func_mocks) * count); + + return func_mocks; +} + +void cnh_lib_init_unit_test(struct cnh_lib_unit_test_func_mocks* func_mocks, size_t func_mocks_cnt) +{ + log_warn("Setting up %d unit test func mocks", func_mocks_cnt); + + _cnh_lib_func_mocks = func_mocks; + _cnh_lib_func_mocks_cnt = func_mocks_cnt; + + _cnh_lib_interface.load = _cnh_lib_load_test; + _cnh_lib_interface.get_func_addr_handle = _cnh_lib_get_func_addr_handle_test; + _cnh_lib_interface.unload = _cnh_lib_unload_test; + _cnh_lib_interface.get_func_addr = _cnh_lib_get_func_addr_test; +} + +void cnh_lib_init() +{ + log_warn("Setting up real interface"); + + _cnh_lib_interface.load = _cnh_lib_load; + _cnh_lib_interface.get_func_addr_handle = _cnh_lib_get_func_addr_handle; + _cnh_lib_interface.unload = _cnh_lib_unload; + _cnh_lib_interface.get_func_addr = _cnh_lib_get_func_addr; +} + +void cnh_lib_shutdown_unit_test() +{ + log_assert(_cnh_lib_func_mocks); + + util_xfree((void**) &_cnh_lib_func_mocks); +} + +void* cnh_lib_load(const char* lib) +{ + return _cnh_lib_interface.load(lib); +} + +void* cnh_lib_get_func_addr_handle(void* lib_handle, const char* func_name) +{ + return _cnh_lib_interface.get_func_addr_handle(lib_handle, func_name); +} + +void cnh_lib_unload(void* handle) +{ + _cnh_lib_interface.unload(handle); +} + +void* cnh_lib_get_func_addr(const char* func_name) +{ + return _cnh_lib_interface.get_func_addr(func_name); +} \ No newline at end of file diff --git a/src/main/capnhook/hook/lib.h b/src/main/capnhook/hook/lib.h new file mode 100644 index 0000000..f314d5c --- /dev/null +++ b/src/main/capnhook/hook/lib.h @@ -0,0 +1,84 @@ +/** + * Various functions related to library hooking + */ +#ifndef CAPNHOOK_LIB_H +#define CAPNHOOK_LIB_H + +#include + +/** + * Provide a mock function key'd by it's name and pointing to your actual mock implementation. + */ +struct cnh_lib_unit_test_func_mocks { + const char* name; + void* func; +}; + +/** + * Allocate memory for a list of mock functions to pass to cnh_lib_init_unit_test. + * + * @param count The size of the list. + * @return Allocated memory with the specified number of entries. + */ +struct cnh_lib_unit_test_func_mocks* cnh_lib_allocate_func_mocks(size_t count); + +/** + * Initialize this module when using it in a unit-test. This allows you to add function mocks that are returned on the + * various calls to get function pointers from real library functions. Use this to mock them to test various patching + * modules. + * + * Use the cnh_lib_allocate_func_mocks function to allocate a list that you can fill in. + * + * @param func_mocks A list of functions mocks with function names and pointers to the test mocks you set up. Ensure + * that the function signatures match, otherwise things might crash when getting called. The module + * will cleanup the memory allocated of this for you. + * @param func_mocks_cnt The length of the list of func_mocks. + */ +void cnh_lib_init_unit_test(struct cnh_lib_unit_test_func_mocks* func_mocks, size_t func_mocks_cnt); + +/** + * Optional, used for unit testing. Switch to the default backend that utilizes loading real libraries and getting real + * function pointers from them. This is enabled by default. + */ +void cnh_lib_init(); + +/** + * Cleanup any memory allocated/used on cnh_lib_init_unit_test. + */ +void cnh_lib_shutdown_unit_test(); + +/** + * Load a dynamic library + * + * @param lib Path to library file to load + * @return Pointer to a handle on success, NULL on failure + */ +void* cnh_lib_load(const char* lib); + +/** + * Get the address of a function from a library + * + * @param lib_handle Handle of an opened library + * @param func_name Name of the function to get + * @return Function ptr on success, false on error (function not found) + */ +void*cnh_lib_get_func_addr_handle(void* lib_handle, const char* func_name); + +/** + * Unload a loaded dynamic library + * + * @param handle Handle of the loaded lib to unload + */ +void cnh_lib_unload(void* handle); + +/** + * Get the adress of a function (from a dynamic library) + * + * Note: This will return the first function found matching the specified name + * + * @param func_name Name of the function + * @return Valid ptr to function on success, false on error (not found) + */ +void* cnh_lib_get_func_addr(const char* func_name); + +#endif \ No newline at end of file diff --git a/src/main/capnhook/hook/result.c b/src/main/capnhook/hook/result.c new file mode 100644 index 0000000..3bbbf3f --- /dev/null +++ b/src/main/capnhook/hook/result.c @@ -0,0 +1,99 @@ +#define LOG_MODULE "capnhook" + +#include + +#include "capnhook/hook/result.h" + +#include "util/log.h" + +int cnh_result_to_errno(enum cnh_result result) +{ + switch (result) { + case CNH_RESULT_SUCCESS: + return 0; + case CNH_RESULT_ERROR_INSUFFICIENT_BUFFER: + return EIO; + case CNH_RESULT_INVALID_PARAMETER: + return EINVAL; + case CNH_RESULT_OUT_OF_MEMORY: + return ENOMEM; + case CNH_RESULT_REPEAT_OPERATION: + return EAGAIN; + case CNH_RESULT_NO_SUCH_FILE_OR_DIR: + return ENOENT; + case CNH_RESULT_NOT_TTY: + return ENOTTY; + case CNH_RESULT_BUSY: + return EBUSY; + case CNH_RESULT_IS_A_DIRECTORY: + return EISDIR; + case CNH_RESULT_PERMISSION_DENIED: + return EACCES; + case CNH_RESULT_DIRECTORY_NOT_EMPTY: + return ENOTEMPTY; + case CNH_RESULT_OPERATION_NOT_PERMITTED: + return EPERM; + case CNH_RESULT_NO_SUCH_DEVICE: + return ENODEV; + case CNH_RESULT_BAD_FILE_NUMBER: + return EBADF; + case CNH_RESULT_FILE_DESCRIPTOR_IN_BAD_STATE: + return EBADFD; + case CNH_RESULT_VALUE_TOO_LARGE_FOR_DEFINED_DATATYPE: + return EOVERFLOW; + case CNH_RESULT_NO_SUCH_DEVICE_OR_ADDRESS: + return ENXIO; + case CNH_RESULT_BROKEN_PIPE: + return EPIPE; + case CNH_RESULT_TIMER_EXPIRED: + return ETIME; + default: + log_warn("Unhandled other error %d to errno convert might cause bugs", result); + return EIO; + } +} + +enum cnh_result cnh_errno_to_result(int errn) +{ + switch (errn) { + case 0: + return CNH_RESULT_SUCCESS; + case EINVAL: + return CNH_RESULT_INVALID_PARAMETER; + case ENOMEM: + return CNH_RESULT_OUT_OF_MEMORY; + case EAGAIN: + return CNH_RESULT_REPEAT_OPERATION; + case ENOENT: + return CNH_RESULT_NO_SUCH_FILE_OR_DIR; + case ENOTTY: + return CNH_RESULT_NOT_TTY; + case EBUSY: + return CNH_RESULT_BUSY; + case EISDIR: + return CNH_RESULT_IS_A_DIRECTORY; + case EACCES: + return CNH_RESULT_PERMISSION_DENIED; + case ENOTEMPTY: + return CNH_RESULT_DIRECTORY_NOT_EMPTY; + case EPERM: + return CNH_RESULT_OPERATION_NOT_PERMITTED; + case ENODEV: + return CNH_RESULT_NO_SUCH_DEVICE; + case EBADF: + return CNH_RESULT_BAD_FILE_NUMBER; + case EBADFD: + return CNH_RESULT_FILE_DESCRIPTOR_IN_BAD_STATE; + case EOVERFLOW: + return CNH_RESULT_VALUE_TOO_LARGE_FOR_DEFINED_DATATYPE; + case ENXIO: + return CNH_RESULT_NO_SUCH_DEVICE_OR_ADDRESS; + case EPIPE: + return CNH_RESULT_BROKEN_PIPE; + case ETIME: + return CNH_RESULT_TIMER_EXPIRED; + default: + log_warn("Unhandled errno to general error convert might cause bugs, errno: %d", errn); + return CNH_RESULT_OTHER_ERROR; + } +} \ No newline at end of file diff --git a/src/main/capnhook/hook/result.h b/src/main/capnhook/hook/result.h new file mode 100644 index 0000000..e2f5b0b --- /dev/null +++ b/src/main/capnhook/hook/result.h @@ -0,0 +1,52 @@ +/** + * Results for returning errors on hook modules, converting them to errno and vice versa + * + * Based on original capnhook code for windows by decafcode: + * https://github.com/decafcode/capnhook + */ +#ifndef CAPNHOOK_RESULT_H +#define CAPNHOOK_RESULT_H + +/** + * Result values for hook functions + */ +enum cnh_result { + CNH_RESULT_SUCCESS = 0, + CNH_RESULT_ERROR_INSUFFICIENT_BUFFER = 1, + CNH_RESULT_INVALID_PARAMETER = 2, + CNH_RESULT_OUT_OF_MEMORY = 3, + CNH_RESULT_REPEAT_OPERATION = 4, + CNH_RESULT_NO_SUCH_FILE_OR_DIR = 5, + CNH_RESULT_OTHER_ERROR = 6, + CNH_RESULT_NOT_TTY = 7, + CNH_RESULT_BUSY = 8, + CNH_RESULT_IS_A_DIRECTORY = 9, + CNH_RESULT_PERMISSION_DENIED = 10, + CNH_RESULT_DIRECTORY_NOT_EMPTY = 11, + CNH_RESULT_OPERATION_NOT_PERMITTED = 12, + CNH_RESULT_NO_SUCH_DEVICE = 13, + CNH_RESULT_BAD_FILE_NUMBER = 14, + CNH_RESULT_FILE_DESCRIPTOR_IN_BAD_STATE = 15, + CNH_RESULT_VALUE_TOO_LARGE_FOR_DEFINED_DATATYPE = 16, + CNH_RESULT_NO_SUCH_DEVICE_OR_ADDRESS = 17, + CNH_RESULT_BROKEN_PIPE = 18, + CNH_RESULT_TIMER_EXPIRED = 19, +}; + +/** + * Convert a hook function result to errno + * + * @param result Result to convert + * @return errno value + */ +int cnh_result_to_errno(enum cnh_result result); + +/** + * Convert an errno value to a hook function result + * + * @param errn errno value to convert + * @return Hook function result + */ +enum cnh_result cnh_errno_to_result(int errn); + +#endif diff --git a/src/main/capnhook/hook/sig.c b/src/main/capnhook/hook/sig.c new file mode 100644 index 0000000..f149b17 --- /dev/null +++ b/src/main/capnhook/hook/sig.c @@ -0,0 +1,58 @@ +#define LOG_MODULE "cnh-sig" + +#include "capnhook/hook/lib.h" +#include "capnhook/hook/sig.h" + +#include "util/log.h" + +typedef int (*cnh_sig_handler_sigaction_t) (int sig, const struct sigaction* act, struct sigaction* oact); + +static void _cnh_signal_handler(int sig); + +static cnh_sig_handler_sigaction_t _cnh_sig_handler_real_sigaction; + +static cnh_sig_handler_t _cnh_signal_handlers[32]; +static __sighandler_t _cnh_signal_orig_handlers[32]; + +void cnh_sig_install(int sig, cnh_sig_handler_t handler) +{ + if (sig < 0 || sig >= 32) { + log_die("Can't register handler for invalid signal number %d", sig); + } + + /* Ensure that the application does not install it's own handler */ + _cnh_signal_handlers[sig] = handler; + + /* Install a little detour as our signal handler */ + signal(sig, _cnh_signal_handler); + + log_info("Installed signal handler %p for %d", handler, sig); +} + +int sigaction(int sig, const struct sigaction* act, struct sigaction* oact) +{ + if (_cnh_signal_handlers[sig]) { + log_info("Enforce own signal handler on signal: %d", sig); + + /* save the original handler, we might need it */ + _cnh_signal_orig_handlers[sig] = ((struct sigaction*) act)->sa_handler; + + /* swap */ + ((struct sigaction*) act)->sa_handler = (__sighandler_t) _cnh_signal_handler; + + log_debug("Original handler %p for signal %d", _cnh_signal_orig_handlers[sig], sig); + } + + if (!_cnh_sig_handler_real_sigaction) { + _cnh_sig_handler_real_sigaction = (cnh_sig_handler_sigaction_t) cnh_lib_get_func_addr("sigaction"); + } + + return _cnh_sig_handler_real_sigaction(sig, act, oact); +} + +static void _cnh_signal_handler(int sig) +{ + if (_cnh_signal_handlers[sig]) { + _cnh_signal_handlers[sig](sig, _cnh_signal_orig_handlers[sig]); + } +} \ No newline at end of file diff --git a/src/main/capnhook/hook/sig.h b/src/main/capnhook/hook/sig.h new file mode 100644 index 0000000..8bb9548 --- /dev/null +++ b/src/main/capnhook/hook/sig.h @@ -0,0 +1,26 @@ +/** + * Hook and intercept signal handlers + */ +#ifndef CAPNHOOK_SIG_H +#define CAPNHOOK_SIG_H + +#include + +/** + * Special callback for signal allowing access to a signal handler installed + * by the application + * + * @param signal Signal to handle + * @param orig_handler Original handler of application or NULL if non exists + */ +typedef void (*cnh_sig_handler_t)(int signal, __sighandler_t orig_handler); + +/** + * Install our own signal handler (and possibly override existing handlers) + * + * @param sig Signal to handle + * @param handler Handler to install for specified signal + */ +void cnh_sig_install(int sig, cnh_sig_handler_t handler); + +#endif \ No newline at end of file diff --git a/src/main/capnhook/hook/usbhook.c b/src/main/capnhook/hook/usbhook.c new file mode 100644 index 0000000..89ba9c2 --- /dev/null +++ b/src/main/capnhook/hook/usbhook.c @@ -0,0 +1,624 @@ +#define LOG_MODULE "cnh-usbhook" + +#include +#include +#include +#include +#include +#include + +#include "capnhook/hook/common.h" +#include "capnhook/hook/lib.h" +#include "capnhook/hook/usbhook.h" + +#include "util/time.h" + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Real funcs typedefs */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +typedef int (*cnh_usbhook_usb_init_t)(void); +typedef int (*cnh_usbhook_usb_find_devices_t)(void); +typedef int (*cnh_usbhook_usb_find_busses_t)(void); +typedef usb_dev_handle* (*cnh_usbhook_usb_open_t)(struct usb_device* dev); +typedef int (*cnh_usbhook_usb_close_t)(usb_dev_handle* dev); +typedef int (*cnh_usbhook_usb_reset_t)(usb_dev_handle* dev); +typedef int (*cnh_usbhook_usb_set_altinterface_t)(usb_dev_handle* dev, int interface); +typedef int (*cnh_usbhook_usb_set_configuration_t)(usb_dev_handle* dev, int configuration); +typedef int (*cnh_usbhook_usb_claim_interface_t)(usb_dev_handle* dev, int interface); +typedef int (*cnh_usbhook_usb_control_msg_t)(usb_dev_handle* dev, int requesttype, int request, int value, int index, + char* bytes, int nbytes, int timeout); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private helpers */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_usbhook_init(void); + +static enum cnh_result _cnh_usbhook_invoke_real(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_init(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_find_busses(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_find_devices(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_open(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_close(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_reset(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_set_altinterface(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_set_configuration(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_claim_interface(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbhook_invoke_real_ctrl_msg(struct cnh_usbhook_irp* irp); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private state */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static cnh_usbhook_usb_init_t _cnh_usbhook_real_init; +static cnh_usbhook_usb_find_devices_t _cnh_usbhook_real_find_devices; +static cnh_usbhook_usb_find_busses_t _cnh_usbhook_real_find_busses; +static cnh_usbhook_usb_open_t _cnh_usbhook_real_open; +static cnh_usbhook_usb_close_t _cnh_usbhook_real_close; +static cnh_usbhook_usb_reset_t _cnh_usbhook_real_reset; +static cnh_usbhook_usb_set_altinterface_t _cnh_usbhook_real_set_altinterface; +static cnh_usbhook_usb_set_configuration_t _cnh_usbhook_real_set_configuration; +static cnh_usbhook_usb_claim_interface_t _cnh_usbhook_real_claim_interface; +static cnh_usbhook_usb_control_msg_t _cnh_usbhook_real_control_msg; + +static const cnh_usbhook_fn_t _cnh_usbhook_real_handlers[10] = { + [CNH_USBHOOK_IRP_OP_INIT] = _cnh_usbhook_invoke_real_init, + [CNH_USBHOOK_IRP_OP_FIND_BUSSES ]= _cnh_usbhook_invoke_real_find_busses, + [CNH_USBHOOK_IRP_OP_FIND_DEVICES] = _cnh_usbhook_invoke_real_find_devices, + [CNH_USBHOOK_IRP_OP_OPEN] = _cnh_usbhook_invoke_real_open, + [CNH_USBHOOK_IRP_OP_CLOSE] = _cnh_usbhook_invoke_real_close, + [CNH_USBHOOK_IRP_OP_RESET] = _cnh_usbhook_invoke_real_reset, + [CNH_USBHOOK_IRP_OP_SET_ALTINTERFACE] = _cnh_usbhook_invoke_real_set_altinterface, + [CNH_USBHOOK_IRP_OP_SET_CONFIGURATION] = _cnh_usbhook_invoke_real_set_configuration, + [CNH_USBHOOK_IRP_OP_CLAIM_INTERFACE] = _cnh_usbhook_invoke_real_claim_interface, + [CNH_USBHOOK_IRP_OP_CTRL_MSG] = _cnh_usbhook_invoke_real_ctrl_msg, +}; + +static atomic_int _cnh_usbhook_initted = ATOMIC_VAR_INIT(0); +static atomic_int _cnh_usbhook_init_in_progress = ATOMIC_VAR_INIT(0); +static pthread_mutex_t _cnh_usbhook_lock; +static cnh_usbhook_fn_t *_cnh_usbhook_handlers; +static size_t _cnh_usbhook_nhandlers; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Public module functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +enum cnh_result cnh_usbhook_push_handler(cnh_usbhook_fn_t fn) +{ + cnh_usbhook_fn_t *new_array; + size_t new_size; + enum cnh_result result; + + assert(fn != NULL); + + _cnh_usbhook_init(); + pthread_mutex_lock(&_cnh_usbhook_lock); + + new_size = _cnh_usbhook_nhandlers + 1; + new_array = realloc(_cnh_usbhook_handlers, new_size * sizeof(cnh_usbhook_fn_t)); + + if (new_array != NULL) { + _cnh_usbhook_handlers = new_array; + _cnh_usbhook_handlers[_cnh_usbhook_nhandlers++] = fn; + result = CNH_RESULT_SUCCESS; + } else { + result = CNH_RESULT_OUT_OF_MEMORY; + } + + pthread_mutex_unlock(&_cnh_usbhook_lock); + + return result; +} + +enum cnh_result cnh_usbhook_invoke_next(struct cnh_usbhook_irp *irp) +{ + cnh_usbhook_fn_t handler; + enum cnh_result result; + + assert(irp != NULL); + assert(_cnh_usbhook_initted > 0); + + pthread_mutex_lock(&_cnh_usbhook_lock); + + assert(irp->next_handler <= _cnh_usbhook_nhandlers); + + if (irp->next_handler < _cnh_usbhook_nhandlers) { + handler = _cnh_usbhook_handlers[irp->next_handler]; + irp->next_handler++; + } else { + handler = _cnh_usbhook_invoke_real; + irp->next_handler = (size_t) -1; + } + + pthread_mutex_unlock(&_cnh_usbhook_lock); + + result = handler(irp); + + if (result != CNH_RESULT_SUCCESS) { + irp->next_handler = (size_t) -1; + } + + return result; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooked functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +void usb_init(void) +{ + struct cnh_usbhook_irp irp; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_INIT; + + cnh_usbhook_invoke_next(&irp); + + /* No return value, results ignored */ +} + +int usb_find_busses(void) +{ + struct cnh_usbhook_irp irp; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_FIND_BUSSES; + irp.find_busses_res_num_busses = 0; + + cnh_usbhook_invoke_next(&irp); + + /* No errors processed */ + + return irp.find_busses_res_num_busses; +} + +int usb_find_devices(void) +{ + struct cnh_usbhook_irp irp; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_FIND_DEVICES; + irp.find_devices_res_num_devices = 0; + + cnh_usbhook_invoke_next(&irp); + + /* No errors processed */ + + return irp.find_devices_res_num_devices; +} + +usb_dev_handle* usb_open(struct usb_device* dev) +{ + struct cnh_usbhook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + if (dev == NULL) { + /* No errno is set here (from libusbcompat impl) */ + return NULL; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_OPEN; + irp.open_usb_dev = dev; + irp.handle = NULL; + + result = cnh_usbhook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + return NULL; + } + + return irp.handle; +} + +int usb_close(usb_dev_handle* dev) +{ + struct cnh_usbhook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + if (dev == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + /* Always return negative errno on errors */ + return -errno; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_CLOSE; + irp.handle = dev; + + result = cnh_usbhook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + /* Always return negative errno on errors */ + return -errno; + } + + return 0; +} + +int usb_reset(usb_dev_handle* dev) +{ + struct cnh_usbhook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + if (dev == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + /* Always return negative errno on errors */ + return -errno; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_RESET; + irp.handle = dev; + + result = cnh_usbhook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + /* Always return negative errno on errors */ + return -errno; + } + + return 0; +} + +int usb_set_altinterface(usb_dev_handle* dev, int interface) +{ + struct cnh_usbhook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + if (dev == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + /* Always return negative errno on errors */ + return -errno; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_SET_ALTINTERFACE; + irp.handle = dev; + irp.set_altinterface = interface; + + result = cnh_usbhook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + /* Always return negative errno on errors */ + return -errno; + } + + return 0; +} + +int usb_set_configuration(usb_dev_handle* dev, int configuration) +{ + struct cnh_usbhook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + if (dev == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + /* Always return negative errno on errors */ + return -errno; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_SET_CONFIGURATION; + irp.handle = dev; + irp.set_configuration = configuration; + + result = cnh_usbhook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + /* Always return negative errno on errors */ + return -errno; + } + + return 0; +} + +int usb_claim_interface(usb_dev_handle* dev, int interface) +{ + struct cnh_usbhook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + if (dev == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + /* Always return negative errno on errors */ + return -errno; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_CLAIM_INTERFACE; + irp.handle = dev; + irp.claim_interface = interface; + + result = cnh_usbhook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + /* Always return negative errno on errors */ + return -errno; + } + + return 0; +} + +int usb_control_msg(usb_dev_handle* dev, int requesttype, int request, int value, int index, char* bytes, int nbytes, + int timeout) +{ + struct cnh_usbhook_irp irp; + enum cnh_result result; + + /* Ensure module is initialized */ + _cnh_usbhook_init(); + + if (dev == NULL || bytes == NULL) { + errno = cnh_result_to_errno(CNH_RESULT_INVALID_PARAMETER); + /* Always return negative errno on errors */ + return -errno; + } + + memset(&irp, 0, sizeof(irp)); + irp.op = CNH_USBHOOK_IRP_OP_CTRL_MSG; + irp.handle = dev; + irp.ctrl_req_type = requesttype; + irp.ctrl_req = request; + irp.ctrl_value = value; + irp.ctrl_index = index; + irp.ctrl_buffer.bytes = (uint8_t*) bytes; + irp.ctrl_buffer.nbytes = (size_t) nbytes; + irp.ctrl_buffer.pos = 0; + irp.ctrl_timeout = timeout; + + result = cnh_usbhook_invoke_next(&irp); + + if (result != CNH_RESULT_SUCCESS) { + errno = cnh_result_to_errno(result); + /* Always return negative errno on errors */ + return -errno; + } + + return irp.ctrl_buffer.pos; +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Helper functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_usbhook_init(void) +{ + int expected; + + if (atomic_load(&_cnh_usbhook_initted) > 0) { + return; + } + + expected = 0; + + if (!atomic_compare_exchange_strong(&_cnh_usbhook_init_in_progress, &expected, 1)) { + while (atomic_load(&_cnh_usbhook_init_in_progress) > 1) { + util_time_sleep_us(100); + } + + return; + } + + /* Check again to ensure the current thread yielded between the init'd check and setting init in progress */ + if (atomic_load(&_cnh_usbhook_initted) > 0) { + /* Revert init in progress, because nothing to do anymore */ + atomic_store(&_cnh_usbhook_init_in_progress, 0); + return; + } + + _cnh_usbhook_real_init = (cnh_usbhook_usb_init_t) cnh_lib_get_func_addr("usb_init"); + _cnh_usbhook_real_find_devices = (cnh_usbhook_usb_find_devices_t) cnh_lib_get_func_addr("usb_find_devices"); + _cnh_usbhook_real_find_busses = (cnh_usbhook_usb_find_busses_t) cnh_lib_get_func_addr("usb_find_busses"); + _cnh_usbhook_real_open = (cnh_usbhook_usb_open_t) cnh_lib_get_func_addr("usb_open"); + _cnh_usbhook_real_close = (cnh_usbhook_usb_close_t) cnh_lib_get_func_addr("usb_close"); + _cnh_usbhook_real_reset = (cnh_usbhook_usb_reset_t) cnh_lib_get_func_addr("usb_reset"); + _cnh_usbhook_real_set_altinterface = + (cnh_usbhook_usb_set_altinterface_t) cnh_lib_get_func_addr("usb_set_altinterface"); + _cnh_usbhook_real_set_configuration = + (cnh_usbhook_usb_set_configuration_t) cnh_lib_get_func_addr("usb_set_configuration"); + _cnh_usbhook_real_claim_interface = + (cnh_usbhook_usb_claim_interface_t) cnh_lib_get_func_addr("usb_claim_interface"); + _cnh_usbhook_real_control_msg = (cnh_usbhook_usb_control_msg_t) cnh_lib_get_func_addr("usb_control_msg"); + + pthread_mutex_init(&_cnh_usbhook_lock, NULL); + + atomic_store(&_cnh_usbhook_initted, 1); + atomic_store(&_cnh_usbhook_init_in_progress, 0); +} + +static enum cnh_result _cnh_usbhook_invoke_real(struct cnh_usbhook_irp* irp) +{ + cnh_usbhook_fn_t handler; + + assert(irp != NULL); + assert(irp->op < _countof(_cnh_usbhook_real_handlers)); + + handler = _cnh_usbhook_real_handlers[irp->op]; + + assert(handler != NULL); + + return handler(irp); +} + +static enum cnh_result _cnh_usbhook_invoke_real_init(struct cnh_usbhook_irp* irp) +{ + assert(irp != NULL); + + _cnh_usbhook_real_init(); + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usbhook_invoke_real_find_busses(struct cnh_usbhook_irp* irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_usbhook_real_find_busses(); + + irp->find_busses_res_num_busses = res; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usbhook_invoke_real_find_devices(struct cnh_usbhook_irp* irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_usbhook_real_find_devices(); + + irp->find_devices_res_num_devices = res; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usbhook_invoke_real_open(struct cnh_usbhook_irp* irp) +{ + usb_dev_handle* res; + + assert(irp != NULL); + + res = _cnh_usbhook_real_open(irp->open_usb_dev); + + if (res == NULL) { + irp->handle = NULL; + return cnh_errno_to_result(errno); + } + + irp->handle = res; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usbhook_invoke_real_close(struct cnh_usbhook_irp* irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_usbhook_real_close(irp->handle); + + if (res < 0) { + return cnh_errno_to_result(res); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usbhook_invoke_real_reset(struct cnh_usbhook_irp* irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_usbhook_real_reset(irp->handle); + + if (res < 0) { + return cnh_errno_to_result(res); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usbhook_invoke_real_set_altinterface(struct cnh_usbhook_irp* irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_usbhook_real_set_altinterface(irp->handle, irp->set_altinterface); + + if (res < 0) { + return cnh_errno_to_result(res); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usbhook_invoke_real_set_configuration(struct cnh_usbhook_irp* irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_usbhook_real_set_configuration(irp->handle, irp->set_configuration); + + if (res < 0) { + return cnh_errno_to_result(res); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usbhook_invoke_real_claim_interface(struct cnh_usbhook_irp* irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_usbhook_real_claim_interface(irp->handle, irp->claim_interface); + + if (res < 0) { + return cnh_errno_to_result(res); + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usbhook_invoke_real_ctrl_msg(struct cnh_usbhook_irp* irp) +{ + int res; + + assert(irp != NULL); + + res = _cnh_usbhook_real_control_msg(irp->handle, irp->ctrl_req_type, irp->ctrl_req, irp->ctrl_value, + irp->ctrl_index, (char*) irp->ctrl_buffer.bytes, irp->ctrl_buffer.nbytes, irp->ctrl_timeout); + + irp->ctrl_buffer.pos = 0; + + if (res < 0) { + return cnh_errno_to_result(res); + } + + irp->ctrl_buffer.pos = (size_t) res; + + return CNH_RESULT_SUCCESS; +} \ No newline at end of file diff --git a/src/main/capnhook/hook/usbhook.h b/src/main/capnhook/hook/usbhook.h new file mode 100644 index 0000000..1cd4bf4 --- /dev/null +++ b/src/main/capnhook/hook/usbhook.h @@ -0,0 +1,75 @@ +/** + * Hook module for hooking libusb 0.1 + * + * Based on original capnhook code for windows by decafcode: + * https://github.com/decafcode/capnhook + */ +#ifndef CAPNHOOK_USBHOOK_H +#define CAPNHOOK_USBHOOK_H + +#include + +#include "capnhook/hook/iobuf.h" + +/** + * Available operations to hook + */ +enum cnh_usbhook_irp_op { + CNH_USBHOOK_IRP_OP_INIT = 0, + CNH_USBHOOK_IRP_OP_FIND_BUSSES = 1, + CNH_USBHOOK_IRP_OP_FIND_DEVICES = 2, + CNH_USBHOOK_IRP_OP_OPEN = 3, + CNH_USBHOOK_IRP_OP_CLOSE = 4, + CNH_USBHOOK_IRP_OP_RESET = 5, + CNH_USBHOOK_IRP_OP_SET_ALTINTERFACE = 6, + CNH_USBHOOK_IRP_OP_SET_CONFIGURATION = 7, + CNH_USBHOOK_IRP_OP_CLAIM_INTERFACE = 8, + CNH_USBHOOK_IRP_OP_CTRL_MSG = 9 +}; + +/** + * I/O request packet + */ +struct cnh_usbhook_irp { + enum cnh_usbhook_irp_op op; + size_t next_handler; + int find_busses_res_num_busses; + int find_devices_res_num_devices; + struct usb_device* open_usb_dev; + usb_dev_handle* handle; + int set_altinterface; + int set_configuration; + int claim_interface; + int ctrl_req_type; + int ctrl_req; + int ctrl_value; + int ctrl_index; + struct cnh_iobuf ctrl_buffer; + int ctrl_timeout; +}; + +/** + * Hook function type + */ +typedef enum cnh_result (*cnh_usbhook_fn_t)(struct cnh_usbhook_irp *irp); + +/** + * Add a new hook handler to the hook module. + * The handler added is getting called on every hooked operation. + * + * @param fn Pointer to hook function to add + * @return Result/error code if hooking was successful + */ +enum cnh_result cnh_usbhook_push_handler(cnh_usbhook_fn_t fn); + +/** + * Invoke the next hooked function registered in the hook module. + * Call this from your hook handler if you want to pass on execution to further + * hooked calls reaching the original function at the end of the hook chain. + * + * @param irp I/O request package to dispatch + * @return Result of the following function dispatching the request package + */ +enum cnh_result cnh_usbhook_invoke_next(struct cnh_usbhook_irp *irp); + +#endif diff --git a/src/main/capnhook/hooklib/filehook-mon.c b/src/main/capnhook/hooklib/filehook-mon.c new file mode 100644 index 0000000..63aec36 --- /dev/null +++ b/src/main/capnhook/hooklib/filehook-mon.c @@ -0,0 +1,57 @@ +#define LOG_MODULE "cnh-filehook-mon" + +#include "capnhook/hooklib/filehook-mon.h" + +#include "util/log.h" + +static const char* _cnh_filehook_mon_str_empty = ""; +static const char* _cnh_filehook_mon_irp_op_str[] = { + "fopen", + "fclose", + "fread", + "fwrite", + "fgets", + "fseek", + "ftell", + "feof" +}; + +enum cnh_result cnh_filehook_mon(struct cnh_filehook_irp *irp) +{ + enum cnh_result result; + + /* Skip writes to log file because this results in infinite recursion */ + if (irp->file == util_log_get_file_handle()) { + return cnh_filehook_invoke_next(irp); + } + + log_debug("[before][%s] next_handler %d, file %p, open_filename %s, open_mode %s, read(bytes %p, nbytes %d, pos %d)" + ", write(bytes %p, nbytes %d, pos %d), orig_read_write_size %d, orig_read_write_nmemb %d, seek_origin %d, " + "seek_offset %d, tell_offset %llu, eof %d", _cnh_filehook_mon_irp_op_str[irp->op], irp->next_handler, + irp->file, irp->open_filename ? irp->open_filename : _cnh_filehook_mon_str_empty, + irp->open_mode ? irp->open_mode : _cnh_filehook_mon_str_empty, irp->read.bytes, irp->read.nbytes, + irp->read.pos, irp->write.bytes, irp->write.nbytes, irp->write.pos, irp->orig_read_write_size, + irp->orig_read_write_nmemb, irp->seek_origin, irp->seek_offset, irp->tell_offset, irp->eof); + + result = cnh_filehook_invoke_next(irp); + + if (result != CNH_RESULT_SUCCESS) { + log_error("[after][%s] result %d, next_handler %d, file %p, open_filename %s, open_mode %s, read(bytes %p, " + "nbytes %d, pos %d), write(bytes %p, nbytes %d, pos %d), orig_read_write_size %d, orig_read_write_nmemb %d," + " seek_origin %d, seek_offset %d, tell_offset %llu, eof %d", _cnh_filehook_mon_irp_op_str[irp->op], + result, irp->next_handler, irp->file, irp->open_filename ? irp->open_filename : _cnh_filehook_mon_str_empty, + irp->open_mode ? irp->open_mode : _cnh_filehook_mon_str_empty, irp->read.bytes, irp->read.nbytes, + irp->read.pos, irp->write.bytes, irp->write.nbytes, irp->write.pos, irp->orig_read_write_size, + irp->orig_read_write_nmemb, irp->seek_origin, irp->seek_offset, irp->tell_offset, irp->eof); + } else { + log_debug("[after][%s] result %d, next_handler %d, file %p, open_filename %s, open_mode %s, read(bytes %p, " + "nbytes %d, pos %d), write(bytes %p, nbytes %d, pos %d), orig_read_write_size %d, orig_read_write_nmemb %d," + " seek_origin %d, seek_offset %d, tell_offset %llu, eof %d", _cnh_filehook_mon_irp_op_str[irp->op], + result, irp->next_handler, irp->file, irp->open_filename ? irp->open_filename : _cnh_filehook_mon_str_empty, + irp->open_mode ? irp->open_mode : _cnh_filehook_mon_str_empty, irp->read.bytes, irp->read.nbytes, + irp->read.pos, irp->write.bytes, irp->write.nbytes, irp->write.pos, irp->orig_read_write_size, + irp->orig_read_write_nmemb, irp->seek_origin, irp->seek_offset, irp->tell_offset, irp->eof); + } + + return result; +} \ No newline at end of file diff --git a/src/main/capnhook/hooklib/filehook-mon.h b/src/main/capnhook/hooklib/filehook-mon.h new file mode 100644 index 0000000..1299dee --- /dev/null +++ b/src/main/capnhook/hooklib/filehook-mon.h @@ -0,0 +1,17 @@ +/** + * Hook implementation to monitor file related calls, e.g. fopen, fread, fwrite etc. + */ +#ifndef CAPNHOOK_FILEHOOK_MON_H +#define CAPNHOOK_FILEHOOK_MON_H + +#include "capnhook/hook/filehook.h" + +/** + * Hook function to add to the hook module for monitoring file related calls + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_filehook_mon(struct cnh_filehook_irp *irp); + +#endif diff --git a/src/main/capnhook/hooklib/fileopen-mon.c b/src/main/capnhook/hooklib/fileopen-mon.c new file mode 100644 index 0000000..53cb574 --- /dev/null +++ b/src/main/capnhook/hooklib/fileopen-mon.c @@ -0,0 +1,96 @@ +#include "capnhook/hook/filehook.h" +#include "capnhook/hook/fshook.h" +#include "capnhook/hook/iohook.h" + +#include "util/log.h" + +enum cnh_result cnh_fileopen_mon_filehook(struct cnh_filehook_irp *irp) +{ + enum cnh_result result; + + if (irp->op == CNH_FILEHOOK_IRP_OP_OPEN) { + log_debug("[before][fopen]: %s %s", irp->open_filename, irp->open_mode); + + result = cnh_filehook_invoke_next(irp); + + log_debug("[after][fopen]: %s %s -> %d (%d)", irp->open_filename, irp->open_mode, irp->file, result); + } else { + result = cnh_filehook_invoke_next(irp); + } + + return result; +} + +enum cnh_result cnh_fileopen_mon_fshook(struct cnh_fshook_irp *irp) +{ + enum cnh_result result; + + switch (irp->op) { + case CNH_FSHOOK_IRP_OP_DIR_OPEN: + log_debug("[before][diropen]: %s", irp->opendir_name); + break; + case CNH_FSHOOK_IRP_OP_LXSTAT: + log_debug("[before][lxstat]: %s", irp->xstat_file); + break; + case CNH_FSHOOK_IRP_OP_XSTAT: + log_debug("[before][xstat]: %s", irp->xstat_file); + break; + case CNH_FSHOOK_IRP_OP_RENAME: + log_debug("[before][rename]: %s %s", irp->rename_old, irp->rename_new); + break; + case CNH_FSHOOK_IRP_OP_REMOVE: + log_debug("[before][remove]: %s", irp->remove_pathname); + break; + case CNH_FSHOOK_IRP_OP_ACCESS: + log_debug("[before][access]: %s", irp->access_path); + break; + default: + log_die("Unhandled case: %d", irp->op); + break; + } + + result = cnh_fshook_invoke_next(irp); + + switch (irp->op) { + case CNH_FSHOOK_IRP_OP_DIR_OPEN: + log_debug("[after][diropen]: %s -> %d", irp->opendir_name, result); + break; + case CNH_FSHOOK_IRP_OP_LXSTAT: + log_debug("[after][lxstat]: %s -> %d", irp->xstat_file, result); + break; + case CNH_FSHOOK_IRP_OP_XSTAT: + log_debug("[after][xstat]: %s -> %d", irp->xstat_file, result); + break; + case CNH_FSHOOK_IRP_OP_RENAME: + log_debug("[after][rename]: %s %s -> %d", irp->rename_old, irp->rename_new, result); + break; + case CNH_FSHOOK_IRP_OP_REMOVE: + log_debug("[after][remove]: %s -> %d", irp->remove_pathname, result); + break; + case CNH_FSHOOK_IRP_OP_ACCESS: + log_debug("[after][access]: %s -> %d", irp->access_path, result); + break; + default: + log_die("Unhandled case: %d", irp->op); + break; + } + + return result; +} + +enum cnh_result cnh_fileopen_mon_iohook(struct cnh_iohook_irp *irp) +{ + enum cnh_result result; + + if (irp->op == CNH_IOHOOK_IRP_OP_OPEN) { + log_debug("[before][open]: %s %d", irp->open_filename, irp->open_flags); + + result = cnh_iohook_invoke_next(irp); + + log_debug("[after][open]: %s %d -> %d (%d)", irp->open_filename, irp->open_flags, irp->fd, result); + } else { + result = cnh_iohook_invoke_next(irp); + } + + return result; +} \ No newline at end of file diff --git a/src/main/capnhook/hooklib/fileopen-mon.h b/src/main/capnhook/hooklib/fileopen-mon.h new file mode 100644 index 0000000..bebc145 --- /dev/null +++ b/src/main/capnhook/hooklib/fileopen-mon.h @@ -0,0 +1,35 @@ +/** + * Hook implementation to monitor different kinds of operations opening a file + */ +#ifndef CAPNHOOK_FILEOPEN_MON_H +#define CAPNHOOK_FILEOPEN_MON_H + +#include "capnhook/hook/filehook.h" +#include "capnhook/hook/fshook.h" +#include "capnhook/hook/iohook.h" + +/** + * Hook function to add to the hook module for monitoring open file calls on file operations + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_fileopen_mon_filehook(struct cnh_filehook_irp *irp); + +/** + * Hook function to add to the hook module for monitoring open file calls on file system related operations + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_fileopen_mon_fshook(struct cnh_fshook_irp *irp); + +/** + * Hook function to add to the hook module for monitoring open file/device calls on I/O operations + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_fileopen_mon_iohook(struct cnh_iohook_irp *irp); + +#endif diff --git a/src/main/capnhook/hooklib/fshook-mon.c b/src/main/capnhook/hooklib/fshook-mon.c new file mode 100644 index 0000000..26b30a2 --- /dev/null +++ b/src/main/capnhook/hooklib/fshook-mon.c @@ -0,0 +1,43 @@ +#define LOG_MODULE "cnh-fshook-mon" + +#include "capnhook/hooklib/fshook-mon.h" + +#include "util/log.h" + +static const char* _cnh_fshook_mon_irp_op_str[] = { + "diropen", + "lxstat", + "xstat", + "rename", + "remove", + "access" +}; + +enum cnh_result cnh_fshook_mon(struct cnh_fshook_irp *irp) +{ + enum cnh_result result; + + log_debug("[before][%s] next_handler %d, opendir_name %s, opendir_ret %p, xstat_version %d, xstat_file %s, " + "xstat_buf %p, rename_old %s, rename_new %s, remove_pathname %s, access_path %s, access_amode %d", + _cnh_fshook_mon_irp_op_str[irp->op], irp->next_handler, irp->opendir_name, irp->opendir_ret, + irp->xstat_version, irp->xstat_file, irp->xstat_buf, irp->rename_old, irp->rename_new, irp->remove_pathname, + irp->access_path, irp->access_amode); + + result = cnh_fshook_invoke_next(irp); + + if (result != CNH_RESULT_SUCCESS) { + log_error("[before][%s] result %d, next_handler %d, opendir_name %s, opendir_ret %p, xstat_version %d, " + "xstat_file %s, xstat_buf %p, rename_old %s, rename_new %s, remove_pathname %s, access_path %s, " + "access_amode %d", _cnh_fshook_mon_irp_op_str[irp->op], result, irp->next_handler, irp->opendir_name, + irp->opendir_ret, irp->xstat_version, irp->xstat_file, irp->xstat_buf, irp->rename_old, irp->rename_new, + irp->remove_pathname, irp->access_path, irp->access_amode); + } else { + log_debug("[before][%s] result %d, next_handler %d, opendir_name %s, opendir_ret %p, xstat_version %d, " + "xstat_file %s, xstat_buf %p, rename_old %s, rename_new %s, remove_pathname %s, access_path %s, " + "access_amode %d", _cnh_fshook_mon_irp_op_str[irp->op], result, irp->next_handler, irp->opendir_name, + irp->opendir_ret, irp->xstat_version, irp->xstat_file, irp->xstat_buf, irp->rename_old, irp->rename_new, + irp->remove_pathname, irp->access_path, irp->access_amode); + } + + return result; +} \ No newline at end of file diff --git a/src/main/capnhook/hooklib/fshook-mon.h b/src/main/capnhook/hooklib/fshook-mon.h new file mode 100644 index 0000000..5a5e8e2 --- /dev/null +++ b/src/main/capnhook/hooklib/fshook-mon.h @@ -0,0 +1,17 @@ +/** + * Hook implementation to monitor file system related calls + */ +#ifndef CAPNHOOK_FSHOOK_MON_H +#define CAPNHOOK_FSHOOK_MON_H + +#include "capnhook/hook/fshook.h" + +/** + * Hook function to add to the hook module for monitoring file system related calls + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_fshook_mon(struct cnh_fshook_irp *irp); + +#endif \ No newline at end of file diff --git a/src/main/capnhook/hooklib/iohook-mon.c b/src/main/capnhook/hooklib/iohook-mon.c new file mode 100644 index 0000000..9b617a7 --- /dev/null +++ b/src/main/capnhook/hooklib/iohook-mon.c @@ -0,0 +1,54 @@ +#define LOG_MODULE "cnh-iohook-mon" + +#include "capnhook/hooklib/iohook-mon.h" + +#include "util/log.h" + +static const char* _cnh_iohook_mon_str_empty = ""; +static const char* _cnh_iohook_mon_irp_op_str[] = { + "open", + "fdopen", + "close", + "read", + "write", + "seek", + "ioctl" +}; + +enum cnh_result cnh_iohook_mon(struct cnh_iohook_irp *irp) +{ + enum cnh_result result; + + log_debug("[before][%s] next_handler %d, fd %d, open_filename %s, open_flags %d, fdopen_fd %d, fdopen_mode %s, " + "fdopen_res %p, read(bytes %p, nbytes %d, pos %d), write(bytes %p, nbytes %d, pos %d), seek_origin %d, " + "seek_offset %d, seek_pos %llu, ioctl_req %d, ioctl(bytes %p, nbytes %d, pos %d)", + _cnh_iohook_mon_irp_op_str[irp->op], irp->next_handler, irp->fd, + irp->open_filename ? irp->open_filename : _cnh_iohook_mon_str_empty, irp->open_flags, irp->fdopen_fd, + irp->fdopen_mode ? irp->fdopen_mode : _cnh_iohook_mon_str_empty, irp->fdopen_res, irp->read.bytes, + irp->read.nbytes, irp->read.pos, irp->write.bytes, irp->write.nbytes, irp->write.pos, irp->seek_origin, + irp->seek_offset, irp->seek_pos, irp->ioctl_req, irp->ioctl.bytes, irp->ioctl.nbytes, irp->ioctl.pos); + + result = cnh_iohook_invoke_next(irp); + + if (result != CNH_RESULT_SUCCESS) { + log_error("[after][%s] next_handler %d, res: %d, fd %d, open_filename %s, open_flags %d, fdopen_fd %d, " + "fdopen_mode %s, fdopen_res %p, read(bytes %p, nbytes %d, pos %d), write(bytes %p, nbytes %d, pos %d), " + "seek_origin %d, seek_offset %d, seek_pos %llu, ioctl_req %d, ioctl(bytes %p, nbytes %d, pos %d)", + _cnh_iohook_mon_irp_op_str[irp->op], irp->next_handler, result, irp->fd, + irp->open_filename ? irp->open_filename : _cnh_iohook_mon_str_empty, irp->open_flags, irp->fdopen_fd, + irp->fdopen_mode ? irp->fdopen_mode : _cnh_iohook_mon_str_empty, irp->fdopen_res, irp->read.bytes, + irp->read.nbytes, irp->read.pos, irp->write.bytes, irp->write.nbytes, irp->write.pos, irp->seek_origin, + irp->seek_offset, irp->seek_pos, irp->ioctl_req, irp->ioctl.bytes, irp->ioctl.nbytes, irp->ioctl.pos); + } else { + log_debug("[after][%s] next_handler %d, res %d, fd %d, open_filename %s, open_flags %d, fdopen_fd %d, " + "fdopen_mode %s, fdopen_res %p, read(bytes %p, nbytes %d, pos %d), write(bytes %p, nbytes %d, pos %d), " + "seek_origin %d, seek_offset %d, seek_pos %llu, ioctl_req %d, ioctl(bytes %p, nbytes %d, pos %d)", + _cnh_iohook_mon_irp_op_str[irp->op], irp->next_handler, result, irp->fd, + irp->open_filename ? irp->open_filename : _cnh_iohook_mon_str_empty, irp->open_flags, irp->fdopen_fd, + irp->fdopen_mode ? irp->fdopen_mode : _cnh_iohook_mon_str_empty, irp->fdopen_res, irp->read.bytes, + irp->read.nbytes, irp->read.pos, irp->write.bytes, irp->write.nbytes, irp->write.pos, irp->seek_origin, + irp->seek_offset, irp->seek_pos, irp->ioctl_req, irp->ioctl.bytes, irp->ioctl.nbytes, irp->ioctl.pos); + } + + return result; +} \ No newline at end of file diff --git a/src/main/capnhook/hooklib/iohook-mon.h b/src/main/capnhook/hooklib/iohook-mon.h new file mode 100644 index 0000000..ddb10f6 --- /dev/null +++ b/src/main/capnhook/hooklib/iohook-mon.h @@ -0,0 +1,17 @@ +/** + * Hook implementation to monitor I/O calls, e.g. open, read, write etc. + */ +#ifndef CAPNHOOK_IOHOOK_MON_H +#define CAPNHOOK_IOHOOK_MON_H + +#include "capnhook/hook/iohook.h" + +/** + * Hook function to add to the hook module for monitoring I/O calls + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_iohook_mon(struct cnh_iohook_irp *irp); + +#endif \ No newline at end of file diff --git a/src/main/capnhook/hooklib/redir.c b/src/main/capnhook/hooklib/redir.c new file mode 100644 index 0000000..258d6d3 --- /dev/null +++ b/src/main/capnhook/hooklib/redir.c @@ -0,0 +1,371 @@ +#include +#include +#include +#include +#include + +#include "capnhook/hook/fshook.h" +#include "capnhook/hook/filehook.h" +#include "capnhook/hook/iohook.h" + +#include "util/log.h" +#include "util/str.h" +#include "util/time.h" + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooks to fshook module */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static enum cnh_result _cnh_redir_fshook_diropen(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_redir_fshook_lxstat(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_redir_fshook_xstat(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_redir_fshook_rename(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_redir_fshook_remove(struct cnh_fshook_irp *irp); +static enum cnh_result _cnh_redir_fshook_access(struct cnh_fshook_irp *irp); + +static const cnh_fshook_fn_t _cnh_redir_fshook_handlers[6] = { + [CNH_FSHOOK_IRP_OP_DIR_OPEN] = _cnh_redir_fshook_diropen, + [CNH_FSHOOK_IRP_OP_LXSTAT] = _cnh_redir_fshook_lxstat, + [CNH_FSHOOK_IRP_OP_XSTAT] = _cnh_redir_fshook_xstat, + [CNH_FSHOOK_IRP_OP_RENAME] = _cnh_redir_fshook_rename, + [CNH_FSHOOK_IRP_OP_REMOVE] = _cnh_redir_fshook_remove, + [CNH_FSHOOK_IRP_OP_ACCESS] = _cnh_redir_fshook_access, +}; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooks to filehook module */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static enum cnh_result _cnh_redir_filehook_default(struct cnh_filehook_irp *irp); +static enum cnh_result _cnh_redir_filehook_fopen(struct cnh_filehook_irp *irp); + +static const cnh_filehook_fn_t _cnh_redir_filehook_handlers[8] = { + [CNH_FILEHOOK_IRP_OP_OPEN] = _cnh_redir_filehook_fopen, + [CNH_FILEHOOK_IRP_OP_CLOSE] = _cnh_redir_filehook_default, + [CNH_FILEHOOK_IRP_OP_READ] = _cnh_redir_filehook_default, + [CNH_FILEHOOK_IRP_OP_WRITE] = _cnh_redir_filehook_default, + [CNH_FILEHOOK_IRP_OP_FGETS] = _cnh_redir_filehook_default, + [CNH_FILEHOOK_IRP_OP_SEEK] = _cnh_redir_filehook_default, + [CNH_FILEHOOK_IRP_OP_TELL] = _cnh_redir_filehook_default, + [CNH_FILEHOOK_IRP_OP_EOF] = _cnh_redir_filehook_default, +}; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooks to iohook module */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static enum cnh_result _cnh_redir_iohook_default(struct cnh_iohook_irp *irp); +static enum cnh_result _cnh_redir_iohook_open(struct cnh_iohook_irp *irp); + +static const cnh_iohook_fn_t _cnh_redir_iohook_handlers[7] = { + [CNH_IOHOOK_IRP_OP_OPEN] = _cnh_redir_iohook_open, + [CNH_IOHOOK_IRP_OP_FDOPEN] = _cnh_redir_iohook_default, + [CNH_IOHOOK_IRP_OP_CLOSE] = _cnh_redir_iohook_default, + [CNH_IOHOOK_IRP_OP_READ] = _cnh_redir_iohook_default, + [CNH_IOHOOK_IRP_OP_WRITE] = _cnh_redir_iohook_default, + [CNH_IOHOOK_IRP_OP_SEEK] = _cnh_redir_iohook_default, + [CNH_IOHOOK_IRP_OP_IOCTL] = _cnh_redir_iohook_default, +}; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private state */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +struct cnh_redir_redir { + char* src; + char* dest; +}; + +static atomic_int _cnh_redir_initted = ATOMIC_VAR_INIT(0); +static atomic_int _cnh_redir_init_in_progress = ATOMIC_VAR_INIT(0); +static pthread_mutex_t _cnh_redir_lock; +static struct cnh_redir_redir* _cnh_redir_redirs; +static size_t _cnh_redir_nredirs; + +static void _cnh_redir_init(); +static char* _cnh_redir_check(const char* src); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Public module functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +void cnh_redir_add(const char* src, const char* dest) +{ + struct cnh_redir_redir *new_array; + size_t new_size; + + assert(src != NULL); + assert(dest != NULL); + + /* Ensure module is initialized */ + _cnh_redir_init(); + + pthread_mutex_lock(&_cnh_redir_lock); + + new_size = _cnh_redir_nredirs + 1; + new_array = realloc(_cnh_redir_redirs, new_size * sizeof(struct cnh_redir_redir)); + + if (new_array != NULL) { + _cnh_redir_redirs = new_array; + } + + _cnh_redir_redirs[_cnh_redir_nredirs].src = util_str_dup(src); + _cnh_redir_redirs[_cnh_redir_nredirs].dest = util_str_dup(dest); + + _cnh_redir_nredirs++; + + pthread_mutex_unlock(&_cnh_redir_lock); + + log_info("Added path redirect %s -> %s", src, dest); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooks to fshook module */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +enum cnh_result cnh_redir_fshook(struct cnh_fshook_irp* irp) +{ + cnh_fshook_fn_t handler; + + handler = _cnh_redir_fshook_handlers[irp->op]; + + assert(handler != NULL); + + return handler(irp); +} + +static enum cnh_result _cnh_redir_fshook_diropen(struct cnh_fshook_irp *irp) +{ + char* res; + + /* Ensure module is initialized */ + _cnh_redir_init(); + + res = _cnh_redir_check(irp->opendir_name); + + if (res != NULL) { + irp->opendir_name = res; + } + + return cnh_fshook_invoke_next(irp); +} + +static enum cnh_result _cnh_redir_fshook_lxstat(struct cnh_fshook_irp *irp) +{ + char* res; + + /* Ensure module is initialized */ + _cnh_redir_init(); + + res = _cnh_redir_check(irp->xstat_file); + + if (res != NULL) { + irp->xstat_file = res; + } + + return cnh_fshook_invoke_next(irp); +} + +static enum cnh_result _cnh_redir_fshook_xstat(struct cnh_fshook_irp *irp) +{ + char* res; + + /* Ensure module is initialized */ + _cnh_redir_init(); + + res = _cnh_redir_check(irp->xstat_file); + + if (res != NULL) { + irp->xstat_file = res; + } + + return cnh_fshook_invoke_next(irp); +} + +static enum cnh_result _cnh_redir_fshook_rename(struct cnh_fshook_irp *irp) +{ + char* res_old; + char* res_new; + + /* Ensure module is initialized */ + _cnh_redir_init(); + + res_old = _cnh_redir_check(irp->rename_old); + res_new = _cnh_redir_check(irp->rename_old); + + if (res_old != NULL) { + irp->rename_old = res_old; + } + + if (res_new != NULL) { + irp->rename_new = res_new; + } + + return cnh_fshook_invoke_next(irp); +} + +static enum cnh_result _cnh_redir_fshook_remove(struct cnh_fshook_irp *irp) +{ + char* res; + + /* Ensure module is initialized */ + _cnh_redir_init(); + + res = _cnh_redir_check(irp->remove_pathname); + + if (res != NULL) { + irp->remove_pathname = res; + } + + return cnh_fshook_invoke_next(irp); +} + +static enum cnh_result _cnh_redir_fshook_access(struct cnh_fshook_irp *irp) +{ + char* res; + + /* Ensure module is initialized */ + _cnh_redir_init(); + + res = _cnh_redir_check(irp->access_path); + + if (res != NULL) { + irp->access_path = res; + } + + return cnh_fshook_invoke_next(irp); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooks to iohook module */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +enum cnh_result cnh_redir_iohook(struct cnh_iohook_irp* irp) +{ + cnh_iohook_fn_t handler; + + handler = _cnh_redir_iohook_handlers[irp->op]; + + assert(handler != NULL); + + return handler(irp); +} + +static enum cnh_result _cnh_redir_iohook_default(struct cnh_iohook_irp *irp) +{ + return cnh_iohook_invoke_next(irp); +} + +static enum cnh_result _cnh_redir_iohook_open(struct cnh_iohook_irp *irp) +{ + char* res; + + /* Ensure module is initialized */ + _cnh_redir_init(); + + res = _cnh_redir_check(irp->open_filename); + + if (res != NULL) { + irp->open_filename = res; + } + + return cnh_iohook_invoke_next(irp); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooks to filehook module */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +enum cnh_result cnh_redir_filehook(struct cnh_filehook_irp* irp) +{ + cnh_filehook_fn_t handler; + + handler = _cnh_redir_filehook_handlers[irp->op]; + + assert(handler != NULL); + + return handler(irp); +} + +static enum cnh_result _cnh_redir_filehook_default(struct cnh_filehook_irp *irp) +{ + return cnh_filehook_invoke_next(irp); +} + +static enum cnh_result _cnh_redir_filehook_fopen(struct cnh_filehook_irp *irp) +{ + char* res; + + /* Ensure module is initialized */ + _cnh_redir_init(); + + res = _cnh_redir_check(irp->open_filename); + + if (res != NULL) { + irp->open_filename = res; + } + + return cnh_filehook_invoke_next(irp); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Helper functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_redir_init() +{ + int expected; + + if (atomic_load(&_cnh_redir_initted) > 0) { + return; + } + + expected = 0; + + if (!atomic_compare_exchange_strong(&_cnh_redir_init_in_progress, &expected, 1)) { + while (atomic_load(&_cnh_redir_init_in_progress) > 1) { + util_time_sleep_us(100); + } + + return; + } + + /* Check again to ensure the current thread yielded between the init'd check and setting init in progress */ + if (atomic_load(&_cnh_redir_initted) > 0) { + /* Revert init in progress, because nothing to do anymore */ + atomic_store(&_cnh_redir_init_in_progress, 0); + return; + } + + pthread_mutex_init(&_cnh_redir_lock, NULL); + + atomic_store(&_cnh_redir_initted, 1); + atomic_store(&_cnh_redir_init_in_progress, 0); +} + +static char* _cnh_redir_check(const char* src) +{ + const char* redir_src; + const char* redir_dest; + char* res; + + res = NULL; + + pthread_mutex_lock(&_cnh_redir_lock); + + for (size_t i = 0; i < _cnh_redir_nredirs; i++) { + redir_src = _cnh_redir_redirs[i].src; + redir_dest = _cnh_redir_redirs[i].dest; + + if (strncmp(src, redir_src, strlen(redir_src)) == 0) { + // FIXME memory leak + res = util_str_merge(redir_dest, &src[strlen(redir_src)]); + log_debug("Redirect %s -> %s", src, res); + break; + } + } + + pthread_mutex_unlock(&_cnh_redir_lock); + + return res; +} \ No newline at end of file diff --git a/src/main/capnhook/hooklib/redir.h b/src/main/capnhook/hooklib/redir.h new file mode 100644 index 0000000..ce55dbd --- /dev/null +++ b/src/main/capnhook/hooklib/redir.h @@ -0,0 +1,44 @@ +/** + * Hook implementation to redirect files/paths passed as parameters on various file, file system and I/O calls + */ +#ifndef CAPNHOOK_REDIR_H +#define CAPNHOOK_REDIR_H + +#include "capnhook/hook/fshook.h" +#include "capnhook/hook/filehook.h" +#include "capnhook/hook/iohook.h" + +/** + * Add a new path/file redirect to the module + * + * @param src Source path/file to redirect. This does not have to match the full path. + * Postfix, e.g. just the filename is sufficient as well + * @param dest Destination to redirect the original path to. Recommended to use absolute paths here. + */ +void cnh_redir_add(const char* src, const char* dest); + +/** + * Hook function to add to the hook module for redirecting file system related calls + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_redir_fshook(struct cnh_fshook_irp* irp); + +/** + * Hook function to add to the hook module for redirecting I/O calls + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_redir_iohook(struct cnh_iohook_irp* irp); + +/** + * Hook function to add to the hook module for redirecting file related calls + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_redir_filehook(struct cnh_filehook_irp* irp); + +#endif \ No newline at end of file diff --git a/src/main/capnhook/hooklib/usb-emu.c b/src/main/capnhook/hooklib/usb-emu.c new file mode 100644 index 0000000..aca314f --- /dev/null +++ b/src/main/capnhook/hooklib/usb-emu.c @@ -0,0 +1,494 @@ +#include +#include +#include +#include +#include +#include + +#include "capnhook/hooklib/usb-emu.h" + +#include "util/log.h" +#include "util/time.h" + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private helpers */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static void _cnh_usb_emu_init(void); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooks to usbhook module */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static enum cnh_result _cnh_usb_emu_usbhook_default(struct cnh_usbhook_irp *irp); +static enum cnh_result _cnh_usb_emu_usbhook_noop(struct cnh_usbhook_irp *irp); +static enum cnh_result _cnh_usb_emu_usbhook_find_busses(struct cnh_usbhook_irp *irp); +static enum cnh_result _cnh_usb_emu_usbhook_find_devices(struct cnh_usbhook_irp *irp); +static enum cnh_result _cnh_usb_emu_usbhook_open(struct cnh_usbhook_irp *irp); +static enum cnh_result _cnh_usb_emu_usbhook_close(struct cnh_usbhook_irp *irp); +static enum cnh_result _cnh_usb_emu_usbhook_reset(struct cnh_usbhook_irp *irp); +static enum cnh_result _cnh_usb_emu_usbhook_ctrl_msg(struct cnh_usbhook_irp *irp); + +static const cnh_usbhook_fn_t _cnh_usb_emu_usb_handlers[10] = { + [CNH_USBHOOK_IRP_OP_INIT] = _cnh_usb_emu_usbhook_default, + [CNH_USBHOOK_IRP_OP_FIND_BUSSES] = _cnh_usb_emu_usbhook_find_busses, + [CNH_USBHOOK_IRP_OP_FIND_DEVICES] = _cnh_usb_emu_usbhook_find_devices, + [CNH_USBHOOK_IRP_OP_OPEN] = _cnh_usb_emu_usbhook_open, + [CNH_USBHOOK_IRP_OP_CLOSE] = _cnh_usb_emu_usbhook_close, + [CNH_USBHOOK_IRP_OP_RESET] = _cnh_usb_emu_usbhook_reset, + [CNH_USBHOOK_IRP_OP_SET_ALTINTERFACE] = _cnh_usb_emu_usbhook_noop, + [CNH_USBHOOK_IRP_OP_SET_CONFIGURATION] = _cnh_usb_emu_usbhook_noop, + [CNH_USBHOOK_IRP_OP_CLAIM_INTERFACE] = _cnh_usb_emu_usbhook_noop, + [CNH_USBHOOK_IRP_OP_CTRL_MSG] = _cnh_usb_emu_usbhook_ctrl_msg, +}; + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Private state */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +struct usb_dev_handle { + struct cnh_usb_emu_virtdev* virtdev; +}; + +struct cnh_usb_emu_virtdev { + const struct cnh_usb_emu_virtdev_ep* ep; + bool real_dev_avail; + bool enumerated; + struct usb_dev_handle usb_dev_handle; +}; + +static atomic_int _cnh_usb_emu_initted = ATOMIC_VAR_INIT(0); +static atomic_int _cnh_usb_emu_init_in_progress = ATOMIC_VAR_INIT(0); +static pthread_mutex_t _cnh_usb_emu_virtdevs_lock; +static struct cnh_usb_emu_virtdev *_cnh_usb_emu_virtdevs; +static size_t _cnh_usb_emu_nvirtdevs; + +static struct cnh_usb_emu_virtdev* _cnh_usb_emu_get_virtdev(uint16_t vid, uint16_t pid); +static struct cnh_usb_emu_virtdev* _cnh_usb_emu_get_virtdev_by_handle(usb_dev_handle* handle); +static struct usb_device* _cnh_usb_emu_create_fakedev(uint16_t vid, uint16_t pid); + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Public module functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +void cnh_usb_emu_add_virtdevep(const struct cnh_usb_emu_virtdev_ep* virtdevep) +{ + struct cnh_usb_emu_virtdev *new_array; + size_t new_size; + + assert(virtdevep->enumerate); + assert(virtdevep->open); + assert(virtdevep->reset); + assert(virtdevep->control_msg); + assert(virtdevep->close); + + /* Ensure initialized */ + _cnh_usb_emu_init(); + + pthread_mutex_lock(&_cnh_usb_emu_virtdevs_lock); + + new_size = _cnh_usb_emu_nvirtdevs + 1; + new_array = realloc(_cnh_usb_emu_virtdevs, new_size * sizeof(struct cnh_usb_emu_virtdev)); + + if (new_array != NULL) { + _cnh_usb_emu_virtdevs = new_array; + + _cnh_usb_emu_virtdevs[_cnh_usb_emu_nvirtdevs].enumerated = false; + _cnh_usb_emu_virtdevs[_cnh_usb_emu_nvirtdevs].real_dev_avail = false; + _cnh_usb_emu_virtdevs[_cnh_usb_emu_nvirtdevs].ep = virtdevep; + _cnh_usb_emu_virtdevs[_cnh_usb_emu_nvirtdevs].usb_dev_handle.virtdev = &_cnh_usb_emu_virtdevs[_cnh_usb_emu_nvirtdevs]; + _cnh_usb_emu_nvirtdevs++; + } else { + log_die("Out of memory"); + } + + pthread_mutex_unlock(&_cnh_usb_emu_virtdevs_lock); +} + +enum cnh_result cnh_usb_emu(struct cnh_usbhook_irp *irp) +{ + cnh_usbhook_fn_t handler; + + /* Ensure initialized */ + _cnh_usb_emu_init(); + + handler = _cnh_usb_emu_usb_handlers[irp->op]; + + assert(handler != NULL); + + return handler(irp); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Hooked functions to usbhook module */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +static enum cnh_result _cnh_usb_emu_usbhook_default(struct cnh_usbhook_irp *irp) +{ + return cnh_usbhook_invoke_next(irp); +} + +static enum cnh_result _cnh_usb_emu_usbhook_noop(struct cnh_usbhook_irp *irp) +{ + struct cnh_usb_emu_virtdev* virtdev; + + assert(irp->handle); + + virtdev = _cnh_usb_emu_get_virtdev_by_handle(irp->handle); + + /* Not a virtual device handle, probably real handle from libusb */ + if (virtdev == NULL) { + return cnh_usbhook_invoke_next(irp); + } + + /* No-op */ + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usb_emu_usbhook_find_busses(struct cnh_usbhook_irp *irp) +{ + enum cnh_result result; + + result = cnh_usbhook_invoke_next(irp); + + // inject another custom bus for virtual devices + if (result == CNH_RESULT_SUCCESS) { + log_debug("Injecting usb bus for virtual devices"); + irp->find_busses_res_num_busses++; + } + + return result; +} + +static enum cnh_result _cnh_usb_emu_usbhook_find_devices(struct cnh_usbhook_irp *irp) +{ + enum cnh_result result; + struct usb_bus* it; + int fakedev_count; + + fakedev_count = 0; + + /* first, populate the list with real devices */ + result = cnh_usbhook_invoke_next(irp); + + /* Iterate to the end of the list and add our stuff keeping real devices */ + it = usb_get_busses(); + + /* Iterate all busses */ + do { + /* First, iterate all devices on each bus and check for all real + devices connected which are also part of our hook list */ + if (it != NULL) { + /* Iterate devices on the current bus */ + struct usb_device* dev = it->devices; + + while (dev != NULL) { + struct cnh_usb_emu_virtdev* virt_dev = _cnh_usb_emu_get_virtdev((uint16_t) dev->descriptor.idVendor, + (uint16_t) dev->descriptor.idProduct); + + /* Real device connected for a hook */ + if (virt_dev) { + log_debug("Detected real %04X:%04X device for virtual one", dev->descriptor.idVendor, + dev->descriptor.idProduct); + virt_dev->real_dev_avail = true; + } + + dev = dev->next; + } + } + + /* it == NULL, nothing found on current bus which is our last bus + we added on usb_find_busses. We put our fakedevs on that bus */ + if (it == NULL || it->next == NULL) { + struct usb_bus* fakebus; + struct usb_device* fakebus_dev_tail; + + /* Create an additional bus for our fakedevs */ + fakebus = (struct usb_bus*) util_xmalloc(sizeof(struct usb_bus)); + memset(fakebus, 0, sizeof(struct usb_bus)); + + /* Make sure to modify the linked list correctly. If we fuck up, + this works fine until Fiesta where they started to iterate + everything in a different manner (-> crash) */ + if (it == NULL) { + usb_busses = fakebus; + /* Bus empty, no prev bus */ + fakebus->prev = NULL; + } else { + /* Append at end of bus chain */ + it->next = fakebus; + /* Keep doubly linked list intact */ + fakebus->prev = it->next; + } + + fakebus_dev_tail = NULL; + + pthread_mutex_lock(&_cnh_usb_emu_virtdevs_lock); + + for (size_t i = 0; i < _cnh_usb_emu_nvirtdevs; i++) { + struct cnh_usb_emu_virtdev* virtdev = &_cnh_usb_emu_virtdevs[i]; + + virtdev->enumerated = virtdev->ep->enumerate(virtdev->real_dev_avail); + + if (virtdev->enumerated) { + if (virtdev->real_dev_avail) { + /* Suppress real device and detour everything to + our hook */ + log_debug("Suppressing real device %04X:%04X", virtdev->ep->vid, virtdev->ep->pid); + } else { + /* Create a fakedev and add it to the bus */ + struct usb_device* fakedev; + + { + /* Print this once. Pump Pro calls this + function every second */ + static int counter = 0; + + if (counter++ == 0) { + log_info("Injecting fake usb device %04X:%04X into bus", virtdev->ep->vid, + virtdev->ep->pid); + } else { + counter = 1; + } + } + + fakedev = _cnh_usb_emu_create_fakedev(virtdev->ep->vid, virtdev->ep->pid); + + /* Append to linked list */ + if (!fakebus->devices) { + /* Set root on bus */ + fakebus->devices = fakedev; + } + + /* Cur element */ + fakedev->prev = fakebus_dev_tail; + fakedev->next = NULL; + + /* Update prev element's next */ + if (fakebus_dev_tail) { + fakebus_dev_tail->next = fakedev; + } + + /* tail update */ + fakebus_dev_tail = fakedev; + + fakedev_count++; + } + } else { + log_debug("Reject enumerate hook for %04X:%04X", virtdev->ep->vid, virtdev->ep->pid); + } + } + + pthread_mutex_unlock(&_cnh_usb_emu_virtdevs_lock); + + break; + } + /* End of if block which handles fakedevs and hooking */ + + it = it->next; + } + while (it != NULL); + + { + /* Print this once. Pump Pro calls this + function every second */ + static int counter = 0; + + if (counter++ == 0) { + log_info("Added %d fakedevs to usb bus", fakedev_count); + } else { + counter = 1; + } + } + + /* include our fakedev count as well */ + return result + fakedev_count; +} + +static enum cnh_result _cnh_usb_emu_usbhook_open(struct cnh_usbhook_irp *irp) +{ + struct cnh_usb_emu_virtdev* virtdev; + enum cnh_result result; + + virtdev = _cnh_usb_emu_get_virtdev((uint16_t) irp->open_usb_dev->descriptor.idVendor, + (uint16_t) irp->open_usb_dev->descriptor.idProduct); + + if (virtdev == NULL) { + return cnh_usbhook_invoke_next(irp); + } + + if (!virtdev->enumerated) { + log_debug("Virtdev %04X:%04X not enumerated, skipping open", virtdev->ep->vid, virtdev->ep->pid); + return cnh_usbhook_invoke_next(irp); + } + + result = virtdev->ep->open(); + + if (result == CNH_RESULT_SUCCESS) { + /* Return handle, use virtual device as fake handle */ + irp->handle = &virtdev->usb_dev_handle; + } + + return result; +} + +static enum cnh_result _cnh_usb_emu_usbhook_close(struct cnh_usbhook_irp *irp) +{ + struct cnh_usb_emu_virtdev* virtdev; + + assert(irp->handle); + + virtdev = _cnh_usb_emu_get_virtdev_by_handle(irp->handle); + + /* Not a virtual device handle, probably real handle from libusb */ + if (virtdev == NULL) { + return cnh_usbhook_invoke_next(irp); + } + + virtdev->ep->close(); + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _cnh_usb_emu_usbhook_reset(struct cnh_usbhook_irp *irp) +{ + struct cnh_usb_emu_virtdev* virtdev; + + assert(irp->handle); + + virtdev = _cnh_usb_emu_get_virtdev_by_handle(irp->handle); + + /* Not a virtual device handle, probably real handle from libusb */ + if (virtdev == NULL) { + return cnh_usbhook_invoke_next(irp); + } + + return virtdev->ep->reset(); +} + +static enum cnh_result _cnh_usb_emu_usbhook_ctrl_msg(struct cnh_usbhook_irp *irp) +{ + struct cnh_usb_emu_virtdev* virtdev; + + assert(irp->handle); + + virtdev = _cnh_usb_emu_get_virtdev_by_handle(irp->handle); + + /* Not a virtual device handle, probably real handle from libusb */ + if (virtdev == NULL) { + return cnh_usbhook_invoke_next(irp); + } + + return virtdev->ep->control_msg(irp->ctrl_req_type, irp->ctrl_req, irp->ctrl_value, irp->ctrl_index, + &irp->ctrl_buffer, irp->ctrl_timeout); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ +/* Helper functions */ +/* ------------------------------------------------------------------------------------------------------------------ */ + +void _cnh_usb_emu_init(void) +{ + int expected; + + if (atomic_load(&_cnh_usb_emu_initted) > 0) { + return; + } + + expected = 0; + + if (!atomic_compare_exchange_strong(&_cnh_usb_emu_init_in_progress, &expected, 1)) { + while (atomic_load(&_cnh_usb_emu_init_in_progress) > 1) { + util_time_sleep_us(100); + } + + return; + } + + /* Check again to ensure the current thread yielded between the init'd check and setting init in progress */ + if (atomic_load(&_cnh_usb_emu_initted) > 0) { + /* Revert init in progress, because nothing to do anymore */ + atomic_store(&_cnh_usb_emu_init_in_progress, 0); + return; + } + + pthread_mutex_init(&_cnh_usb_emu_virtdevs_lock, NULL); + + atomic_store(&_cnh_usb_emu_initted, 1); + atomic_store(&_cnh_usb_emu_init_in_progress, 0); +} + +static struct cnh_usb_emu_virtdev* _cnh_usb_emu_get_virtdev(uint16_t vid, uint16_t pid) +{ + struct cnh_usb_emu_virtdev* virtdev; + struct cnh_usb_emu_virtdev* tmp; + + virtdev = NULL; + + pthread_mutex_lock(&_cnh_usb_emu_virtdevs_lock); + + for (size_t i = 0; i < _cnh_usb_emu_nvirtdevs; i++) { + tmp = &_cnh_usb_emu_virtdevs[i]; + + if (tmp->ep->vid == vid && tmp->ep->pid == pid) { + virtdev = tmp; + break; + } + } + + pthread_mutex_unlock(&_cnh_usb_emu_virtdevs_lock); + + return virtdev; +} + +static struct cnh_usb_emu_virtdev* _cnh_usb_emu_get_virtdev_by_handle(usb_dev_handle* handle) +{ + struct cnh_usb_emu_virtdev* virtdev; + struct cnh_usb_emu_virtdev* tmp; + + virtdev = NULL; + + pthread_mutex_lock(&_cnh_usb_emu_virtdevs_lock); + + for (size_t i = 0; i < _cnh_usb_emu_nvirtdevs; i++) { + tmp = &_cnh_usb_emu_virtdevs[i]; + + if (tmp == handle->virtdev) { + virtdev = tmp; + break; + } + } + + pthread_mutex_unlock(&_cnh_usb_emu_virtdevs_lock); + + return virtdev; +} + +static struct usb_device* _cnh_usb_emu_create_fakedev(uint16_t vid, uint16_t pid) +{ + struct usb_device* fakedev = (struct usb_device*) util_xmalloc(sizeof(struct usb_device)); + memset(fakedev, 0, sizeof(struct usb_device)); + + /* Populate fakedev */ + fakedev->descriptor.idVendor = vid; + fakedev->descriptor.idProduct = pid; + + /* Mark as fakedev */ + fakedev->descriptor.bcdDevice = 0xBEEF; + + fakedev->config = (struct usb_config_descriptor*) util_xmalloc( + sizeof(struct usb_config_descriptor)); + memset(fakedev->config, 0, sizeof(struct usb_config_descriptor)); + + fakedev->config->interface = (struct usb_interface*) util_xmalloc( + sizeof(struct usb_interface)); + memset(fakedev->config->interface, 0, sizeof(struct usb_interface)); + + fakedev->config->interface->altsetting = (struct usb_interface_descriptor*) + util_xmalloc(sizeof(struct usb_interface_descriptor)); + memset(fakedev->config->interface->altsetting, 0, + sizeof(struct usb_interface_descriptor)); + + return fakedev; +} \ No newline at end of file diff --git a/src/main/capnhook/hooklib/usb-emu.h b/src/main/capnhook/hooklib/usb-emu.h new file mode 100644 index 0000000..a980567 --- /dev/null +++ b/src/main/capnhook/hooklib/usb-emu.h @@ -0,0 +1,42 @@ +/** + * Hook implementation for emulating usb devices on libusb 0.1 + */ +#ifndef CNH_USB_EMU_H +#define CNH_USB_EMU_H + +#include +#include + +#include "capnhook/hook/iobuf.h" +#include "capnhook/hook/usbhook.h" + +/** + * Virtual device endpoint + */ +struct cnh_usb_emu_virtdev_ep { + uint16_t pid; + uint16_t vid; + bool (*enumerate)(bool real_exists); + enum cnh_result (*open)(void); + enum cnh_result (*reset)(void); + enum cnh_result (*control_msg)(int request_type, int request, int value, int index, struct cnh_iobuf* buffer, + int timeout); + void (*close)(void); +}; + +/** + * Add a pointer to a virtual device endpoint implementing handler functions + * + * @param virtdevep Virtual device endpoint to add + */ +void cnh_usb_emu_add_virtdevep(const struct cnh_usb_emu_virtdev_ep* virtdevep); + +/** + * Hook function to add to the hook module for emulating usb devices + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_usb_emu(struct cnh_usbhook_irp *irp); + +#endif \ No newline at end of file diff --git a/src/main/capnhook/hooklib/usb-init-fix.c b/src/main/capnhook/hooklib/usb-init-fix.c new file mode 100644 index 0000000..eb1b176 --- /dev/null +++ b/src/main/capnhook/hooklib/usb-init-fix.c @@ -0,0 +1,80 @@ +#include + +#include "capnhook/hooklib/usb-init-fix.h" + +#include "util/log.h" + +static enum cnh_result _cnh_usb_init_fix_default(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usb_init_fix_init(struct cnh_usbhook_irp* irp); +static enum cnh_result _cnh_usbio_emu_find_busses(struct cnh_usbhook_irp* irp); + +static const cnh_usbhook_fn_t _hook_usb_init_fix_handlers[10] = { + [CNH_USBHOOK_IRP_OP_INIT] = _cnh_usb_init_fix_init, + [CNH_USBHOOK_IRP_OP_FIND_BUSSES] = _cnh_usbio_emu_find_busses, + [CNH_USBHOOK_IRP_OP_FIND_DEVICES] = _cnh_usb_init_fix_default, + [CNH_USBHOOK_IRP_OP_OPEN] = _cnh_usb_init_fix_default, + [CNH_USBHOOK_IRP_OP_CLOSE] = _cnh_usb_init_fix_default, + [CNH_USBHOOK_IRP_OP_RESET] = _cnh_usb_init_fix_default, + [CNH_USBHOOK_IRP_OP_SET_ALTINTERFACE] = _cnh_usb_init_fix_default, + [CNH_USBHOOK_IRP_OP_SET_CONFIGURATION] = _cnh_usb_init_fix_default, + [CNH_USBHOOK_IRP_OP_CLAIM_INTERFACE] = _cnh_usb_init_fix_default, + [CNH_USBHOOK_IRP_OP_CTRL_MSG] = _cnh_usb_init_fix_default, +}; + +static int _cnh_usb_init_fix_init_count = 0; + +enum cnh_result cnh_usb_init_fix(struct cnh_usbhook_irp *irp) +{ + cnh_usbhook_fn_t handler; + + handler = _hook_usb_init_fix_handlers[irp->op]; + + assert(handler != NULL); + + return handler(irp); +} + +static enum cnh_result _cnh_usb_init_fix_default(struct cnh_usbhook_irp* irp) +{ + return cnh_usbhook_invoke_next(irp); +} + +static enum cnh_result _cnh_usb_init_fix_init(struct cnh_usbhook_irp* irp) +{ + /* On NXA, one programmer/multiple programmers had the bright idea to call + usb_init twice for reasons... + This breaks stuff in the usb-0.1 backend resulting in usb_find_busses + not even finding native devices anymore. + Make sure we call the real usb_init ONCE ONLY to fix that fuck up */ + _cnh_usb_init_fix_init_count++; + + if (_cnh_usb_init_fix_init_count <= 1) { + return cnh_usbhook_invoke_next(irp); + } else { + /* Pump Pro is even worse and calls usb_init approx. every second... + avoid overflow of counter if we really leave this running forever. + Furthermore, this avoids printing the log message below with ever + init call */ + if (_cnh_usb_init_fix_init_count > 2) { + _cnh_usb_init_fix_init_count = 2; + } else { + log_info("usb_init called multiple times, blocking further calls..."); + } + + return CNH_RESULT_SUCCESS; + } +} + +static enum cnh_result _cnh_usbio_emu_find_busses(struct cnh_usbhook_irp* irp) +{ + enum cnh_result result; + + /* Fixes quirks with usb_init called multiple times on NXA */ + if (_cnh_usb_init_fix_init_count <= 1) { + result = cnh_usbhook_invoke_next(irp); + } else { + result = CNH_RESULT_SUCCESS; + } + + return result; +} \ No newline at end of file diff --git a/src/main/capnhook/hooklib/usb-init-fix.h b/src/main/capnhook/hooklib/usb-init-fix.h new file mode 100644 index 0000000..0c74bf2 --- /dev/null +++ b/src/main/capnhook/hooklib/usb-init-fix.h @@ -0,0 +1,17 @@ +/** + * Hook implementation to fix buggy applications that call usb_init repeatetly + */ +#ifndef CNH_USB_INIT_FIX_H +#define CNH_USB_INIT_FIX_H + +#include "capnhook/hook/usbhook.h" + +/** + * Hook function to add to the hook module to fix the usb_init bug + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_usb_init_fix(struct cnh_usbhook_irp *irp); + +#endif \ No newline at end of file diff --git a/src/main/capnhook/hooklib/usbhook-mon.c b/src/main/capnhook/hooklib/usbhook-mon.c new file mode 100644 index 0000000..a87b748 --- /dev/null +++ b/src/main/capnhook/hooklib/usbhook-mon.c @@ -0,0 +1,55 @@ +#define LOG_MODULE "cnh-usbhook-mon" + +#include "capnhook/hooklib/usbhook-mon.h" + +#include "util/log.h" + +static const char* _cnh_usbhook_mon_irp_op_str[] = { + "init", + "find_busses", + "find_devices", + "open", + "close", + "reset", + "set_altinterface", + "set_configuration", + "claim_interface", + "ctrl_msg" +}; + +enum cnh_result cnh_usbhook_mon(struct cnh_usbhook_irp *irp) +{ + enum cnh_result result; + + log_debug("[before][%s] next_handler %d, find_busses_res_num_busses %d, find_devices_res_num_devices %d, " + "open_usb_dev %p, handle %p, set_altinterface %d, set_configuration %d, claim_interface %d, ctrl_req_type %d, " + "ctrl_req %d, ctrl_value %d, ctrl_index %d, ctrl_buffer(bytes %p, nbytes %d, pos %d), ctrl_timeout %d", + _cnh_usbhook_mon_irp_op_str[irp->op], irp->next_handler, irp->find_busses_res_num_busses, + irp->find_devices_res_num_devices, irp->open_usb_dev, irp->handle, irp->set_altinterface, + irp->set_configuration, irp->claim_interface, irp->ctrl_req_type, irp->ctrl_req, irp->ctrl_value, + irp->ctrl_index, irp->ctrl_buffer.bytes, irp->ctrl_buffer.nbytes, irp->ctrl_buffer.pos, irp->ctrl_timeout); + + result = cnh_usbhook_invoke_next(irp); + + if (result != CNH_RESULT_SUCCESS) { + log_error("[after][%s] result %d, next_handler %d, find_busses_res_num_busses %d, " + "find_devices_res_num_devices %d, open_usb_dev %p, handle %p, set_altinterface %d, set_configuration %d, " + "claim_interface %d, ctrl_req_type %d, ctrl_req %d, ctrl_value %d, ctrl_index %d, ctrl_buffer(bytes %p, " + "nbytes %d, pos %d), ctrl_timeout %d", _cnh_usbhook_mon_irp_op_str[irp->op], result, irp->next_handler, + irp->find_busses_res_num_busses, irp->find_devices_res_num_devices, irp->open_usb_dev, irp->handle, + irp->set_altinterface, irp->set_configuration, irp->claim_interface, irp->ctrl_req_type, irp->ctrl_req, + irp->ctrl_value, irp->ctrl_index, irp->ctrl_buffer.bytes, irp->ctrl_buffer.nbytes, irp->ctrl_buffer.pos, + irp->ctrl_timeout); + } else { + log_debug("[after][%s] result %d, next_handler %d, find_busses_res_num_busses %d, " + "find_devices_res_num_devices %d, open_usb_dev %p, handle %p, set_altinterface %d, set_configuration %d, " + "claim_interface %d, ctrl_req_type %d, ctrl_req %d, ctrl_value %d, ctrl_index %d, ctrl_buffer(bytes %p, " + "nbytes %d, pos %d), ctrl_timeout %d", _cnh_usbhook_mon_irp_op_str[irp->op], result, irp->next_handler, + irp->find_busses_res_num_busses, irp->find_devices_res_num_devices, irp->open_usb_dev, irp->handle, + irp->set_altinterface, irp->set_configuration, irp->claim_interface, irp->ctrl_req_type, irp->ctrl_req, + irp->ctrl_value, irp->ctrl_index, irp->ctrl_buffer.bytes, irp->ctrl_buffer.nbytes, irp->ctrl_buffer.pos, + irp->ctrl_timeout); + } + + return result; +} \ No newline at end of file diff --git a/src/main/capnhook/hooklib/usbhook-mon.h b/src/main/capnhook/hooklib/usbhook-mon.h new file mode 100644 index 0000000..d8c58bf --- /dev/null +++ b/src/main/capnhook/hooklib/usbhook-mon.h @@ -0,0 +1,17 @@ +/** + * Hook implementation to monitor libusb 0.1 calls + */ +#ifndef CAPNHOOK_USBHOOK_MON_H +#define CAPNHOOK_USBHOOK_MON_H + +#include "capnhook/hook/usbhook.h" + +/** + * Hook function to add to the hook module for monitoring libusb calls + * + * @param irp I/O request package of hook module received + * @return Result of operation + */ +enum cnh_result cnh_usbhook_mon(struct cnh_usbhook_irp *irp); + +#endif diff --git a/src/main/crypt/aes.c b/src/main/crypt/aes.c new file mode 100644 index 0000000..a20301c --- /dev/null +++ b/src/main/crypt/aes.c @@ -0,0 +1,29 @@ +#define LOG_MODULE "crypt-aes" + +#include "lib/rijndael.h" + +#include "aes.h" + +void crypt_aes_enc(uint8_t* key, enum crypt_aes_key_length key_len, + uint8_t* out, const uint8_t* in, size_t nbytes) +{ + unsigned long rk[RKLENGTH(128)]; + int nrounds; + + nrounds = rijndaelSetupEncrypt(rk, key, key_len * 8); + for (size_t i = 0; i < nbytes / key_len; i++) { + rijndaelEncrypt(rk, nrounds, &in[i * key_len], &out[i * key_len]); + } +} + +void crypt_aes_dec(uint8_t* key, enum crypt_aes_key_length key_len, + uint8_t* out, const uint8_t* in, size_t nbytes) +{ + unsigned long rk[RKLENGTH(128)]; + int nrounds; + + nrounds = rijndaelSetupDecrypt(rk, key, key_len * 8); + for (size_t i = 0; i < nbytes / key_len; i++) { + rijndaelDecrypt(rk, nrounds, &in[i * key_len], &out[i * key_len]); + } +} \ No newline at end of file diff --git a/src/main/crypt/aes.h b/src/main/crypt/aes.h new file mode 100644 index 0000000..cb3c782 --- /dev/null +++ b/src/main/crypt/aes.h @@ -0,0 +1,43 @@ +#ifndef CRYPT_AES_H +#define CRYPT_AES_H + +#include +#include + +/** + * AES enc/dec key lengths + */ +enum crypt_aes_key_length { + /* 128 bit */ + CRYPT_AES_KEY_LENGTH_16_BYTES = 16, + /* 192 bit */ + CRYPT_AES_KEY_LENGTH_24_BYTES = 24, + /* 256 bit */ + CRYPT_AES_KEY_LENGTH_32_BYTES = 32, +}; + +/** + * Encrypt data + * + * @param key AES key to use + * @param key_len Length of the key + * @param out Output buffer encrypted data + * @param in Input buffer to encrypt + * @param nbytes Number of bytes of input buffer to encrypt + */ +void crypt_aes_enc(uint8_t* key, enum crypt_aes_key_length key_len, + uint8_t* out, const uint8_t* in, size_t nbytes); + +/** + * Decrypt data + * + * @param key AES key to use + * @param key_len Length of the key + * @param out Output buffer for decrypted data + * @param in Input buffer to decrypt + * @param nbytes Number of bytes of input buffer to decrypt + */ +void crypt_aes_dec(uint8_t* key, enum crypt_aes_key_length key_len, + uint8_t* out, const uint8_t* in, size_t nbytes); + +#endif \ No newline at end of file diff --git a/src/main/crypt/lib/md5.c b/src/main/crypt/lib/md5.c new file mode 100644 index 0000000..a8565de --- /dev/null +++ b/src/main/crypt/lib/md5.c @@ -0,0 +1,333 @@ +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +#include "md5.h" + +/* Constants for MD5Transform routine. + */ + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform (uint32_t [4], unsigned char [64]); +static void Encode(unsigned char *, uint32_t *, unsigned int); +static void Decode(uint32_t *, unsigned char *, unsigned int); +static void MD5_memcpy(uint8_t *, uint8_t *, unsigned int); +static void MD5_memset(uint8_t *, int, unsigned int); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void MD5Init (context) +MD5_CTX *context; /* context */ +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void MD5Update (context, input, inputLen) +MD5_CTX *context; /* context */ +unsigned char *input; /* input block */ +unsigned int inputLen; /* length of input block */ +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((uint32_t)inputLen << 3)) + + < ((uint32_t)inputLen << 3)) + context->count[1]++; + context->count[1] += ((uint32_t)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. +*/ + if (inputLen >= partLen) { + MD5_memcpy + ((uint8_t *)&context->buffer[index], (uint8_t *)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy + ((uint8_t *)&context->buffer[index], (uint8_t *)&input[i], + inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +void MD5Final (digest, context) +unsigned char digest[16]; /* message digest */ +MD5_CTX *context; /* context */ +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. +*/ + MD5_memset ((uint8_t *)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (state, block) +uint32_t state[4]; +unsigned char block[64]; +{ + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. + +*/ + MD5_memset ((uint8_t *)x, 0, sizeof (x)); +} + +/* Encodes input (uint32_t) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (output, input, len) +unsigned char *output; +uint32_t *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (uint32_t). Assumes len is + a multiple of 4. + */ +static void Decode (output, input, len) +uint32_t *output; +unsigned char *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((uint32_t)input[j]) | (((uint32_t)input[j+1]) << 8) | + (((uint32_t)input[j+2]) << 16) | (((uint32_t)input[j+3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ + +static void MD5_memcpy (output, input, len) +uint8_t * output; +uint8_t * input; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + + output[i] = input[i]; +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset (output, value, len) +uint8_t * output; +int value; +unsigned int len; +{ + unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; +} \ No newline at end of file diff --git a/src/main/crypt/lib/md5.h b/src/main/crypt/lib/md5.h new file mode 100644 index 0000000..5cf313f --- /dev/null +++ b/src/main/crypt/lib/md5.h @@ -0,0 +1,35 @@ +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +#include + +/* MD5 context. */ +typedef struct { + uint32_t state[4]; /* state (ABCD) */ + uint32_t count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, unsigned char *, unsigned int); +void MD5Final(unsigned char [16], MD5_CTX *); + diff --git a/src/main/crypt/lib/rijndael.c b/src/main/crypt/lib/rijndael.c new file mode 100644 index 0000000..5a1ecda --- /dev/null +++ b/src/main/crypt/lib/rijndael.c @@ -0,0 +1,1208 @@ +#define FULL_UNROLL + +#include "rijndael.h" + +typedef unsigned long u32; +typedef unsigned char u8; + +static const u32 Te0[256] = +{ + 0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU, + 0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U, + 0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU, + 0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU, + 0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U, + 0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU, + 0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU, + 0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU, + 0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU, + 0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU, + 0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U, + 0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU, + 0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU, + 0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U, + 0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU, + 0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU, + 0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU, + 0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU, + 0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU, + 0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U, + 0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU, + 0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU, + 0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU, + 0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU, + 0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U, + 0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U, + 0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U, + 0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U, + 0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU, + 0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U, + 0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U, + 0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU, + 0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU, + 0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U, + 0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U, + 0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U, + 0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU, + 0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U, + 0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU, + 0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U, + 0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU, + 0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U, + 0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U, + 0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU, + 0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U, + 0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U, + 0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U, + 0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U, + 0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U, + 0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U, + 0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U, + 0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U, + 0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU, + 0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U, + 0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U, + 0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U, + 0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U, + 0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U, + 0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U, + 0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU, + 0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U, + 0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U, + 0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U, + 0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU, +}; + +static const u32 Te1[256] = +{ + 0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU, + 0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U, + 0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU, + 0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U, + 0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU, + 0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U, + 0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU, + 0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U, + 0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U, + 0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU, + 0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U, + 0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U, + 0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U, + 0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU, + 0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U, + 0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U, + 0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU, + 0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U, + 0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U, + 0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U, + 0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU, + 0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU, + 0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U, + 0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU, + 0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU, + 0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U, + 0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU, + 0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U, + 0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU, + 0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U, + 0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U, + 0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U, + 0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU, + 0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U, + 0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU, + 0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U, + 0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU, + 0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U, + 0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U, + 0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU, + 0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU, + 0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU, + 0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U, + 0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U, + 0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU, + 0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U, + 0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU, + 0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U, + 0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU, + 0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U, + 0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU, + 0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU, + 0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U, + 0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU, + 0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U, + 0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU, + 0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U, + 0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U, + 0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U, + 0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU, + 0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU, + 0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U, + 0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU, + 0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U, +}; + +static const u32 Te2[256] = +{ + 0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU, + 0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U, + 0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU, + 0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U, + 0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU, + 0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U, + 0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU, + 0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U, + 0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U, + 0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU, + 0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U, + 0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U, + 0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U, + 0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU, + 0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U, + 0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U, + 0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU, + 0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U, + 0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U, + 0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U, + 0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU, + 0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU, + 0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U, + 0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU, + 0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU, + 0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U, + 0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU, + 0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U, + 0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU, + 0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U, + 0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U, + 0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U, + 0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU, + 0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U, + 0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU, + 0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U, + 0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU, + 0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U, + 0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U, + 0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU, + 0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU, + 0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU, + 0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U, + 0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U, + 0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU, + 0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U, + 0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU, + 0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U, + 0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU, + 0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U, + 0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU, + 0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU, + 0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U, + 0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU, + 0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U, + 0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU, + 0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U, + 0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U, + 0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U, + 0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU, + 0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU, + 0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U, + 0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU, + 0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U, +}; + +static const u32 Te3[256] = +{ + 0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U, + 0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U, + 0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U, + 0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU, + 0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU, + 0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU, + 0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U, + 0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU, + 0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU, + 0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U, + 0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U, + 0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU, + 0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU, + 0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU, + 0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU, + 0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU, + 0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U, + 0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU, + 0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU, + 0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U, + 0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U, + 0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U, + 0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U, + 0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U, + 0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU, + 0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U, + 0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU, + 0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU, + 0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U, + 0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U, + 0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U, + 0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU, + 0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U, + 0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU, + 0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU, + 0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U, + 0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U, + 0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU, + 0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U, + 0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU, + 0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U, + 0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U, + 0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U, + 0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U, + 0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU, + 0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U, + 0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU, + 0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U, + 0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU, + 0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U, + 0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU, + 0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU, + 0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU, + 0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU, + 0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U, + 0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U, + 0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U, + 0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U, + 0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U, + 0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U, + 0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU, + 0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U, + 0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU, + 0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU, +}; + +static const u32 Te4[256] = +{ + 0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU, + 0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U, + 0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU, + 0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U, + 0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU, + 0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U, + 0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU, + 0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U, + 0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U, + 0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU, + 0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U, + 0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U, + 0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U, + 0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU, + 0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U, + 0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U, + 0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU, + 0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U, + 0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U, + 0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U, + 0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU, + 0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU, + 0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U, + 0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU, + 0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU, + 0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U, + 0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU, + 0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U, + 0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU, + 0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U, + 0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U, + 0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U, + 0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU, + 0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U, + 0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU, + 0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U, + 0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU, + 0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U, + 0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U, + 0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU, + 0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU, + 0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU, + 0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U, + 0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U, + 0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU, + 0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U, + 0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU, + 0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U, + 0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU, + 0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U, + 0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU, + 0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU, + 0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U, + 0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU, + 0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U, + 0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU, + 0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U, + 0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U, + 0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U, + 0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU, + 0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU, + 0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U, + 0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU, + 0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U, +}; + +static const u32 Td0[256] = +{ + 0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U, + 0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U, + 0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U, + 0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU, + 0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U, + 0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U, + 0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU, + 0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U, + 0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU, + 0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U, + 0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U, + 0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U, + 0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U, + 0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU, + 0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U, + 0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU, + 0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U, + 0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU, + 0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U, + 0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U, + 0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U, + 0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU, + 0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U, + 0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU, + 0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U, + 0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU, + 0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U, + 0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU, + 0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU, + 0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U, + 0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU, + 0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U, + 0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU, + 0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U, + 0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U, + 0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U, + 0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU, + 0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U, + 0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U, + 0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU, + 0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U, + 0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U, + 0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U, + 0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U, + 0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U, + 0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU, + 0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U, + 0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U, + 0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U, + 0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U, + 0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U, + 0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU, + 0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU, + 0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU, + 0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU, + 0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U, + 0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U, + 0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU, + 0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU, + 0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U, + 0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU, + 0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U, + 0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U, + 0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U, +}; + +static const u32 Td1[256] = +{ + 0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU, + 0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U, + 0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU, + 0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U, + 0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U, + 0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U, + 0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U, + 0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U, + 0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U, + 0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU, + 0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU, + 0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU, + 0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U, + 0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU, + 0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U, + 0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U, + 0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U, + 0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU, + 0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU, + 0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U, + 0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU, + 0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U, + 0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU, + 0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU, + 0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U, + 0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U, + 0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U, + 0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU, + 0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U, + 0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU, + 0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U, + 0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U, + 0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U, + 0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU, + 0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U, + 0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U, + 0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U, + 0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U, + 0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U, + 0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U, + 0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU, + 0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU, + 0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U, + 0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU, + 0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U, + 0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU, + 0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU, + 0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U, + 0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU, + 0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U, + 0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U, + 0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U, + 0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U, + 0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U, + 0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U, + 0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U, + 0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU, + 0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U, + 0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U, + 0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU, + 0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U, + 0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U, + 0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U, + 0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U, +}; + +static const u32 Td2[256] = +{ + 0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U, + 0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U, + 0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U, + 0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U, + 0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU, + 0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U, + 0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U, + 0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U, + 0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U, + 0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU, + 0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U, + 0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U, + 0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU, + 0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U, + 0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U, + 0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U, + 0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U, + 0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U, + 0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U, + 0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU, + 0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U, + 0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U, + 0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U, + 0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U, + 0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U, + 0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU, + 0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU, + 0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U, + 0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU, + 0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U, + 0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU, + 0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU, + 0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU, + 0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU, + 0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U, + 0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U, + 0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U, + 0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U, + 0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U, + 0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U, + 0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U, + 0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU, + 0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU, + 0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U, + 0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U, + 0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU, + 0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU, + 0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U, + 0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U, + 0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U, + 0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U, + 0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U, + 0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U, + 0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U, + 0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU, + 0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U, + 0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U, + 0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U, + 0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U, + 0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U, + 0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U, + 0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU, + 0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U, + 0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U, +}; + +static const u32 Td3[256] = +{ + 0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU, + 0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU, + 0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U, + 0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U, + 0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU, + 0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU, + 0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U, + 0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU, + 0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U, + 0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU, + 0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U, + 0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U, + 0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U, + 0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U, + 0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U, + 0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU, + 0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU, + 0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U, + 0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U, + 0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU, + 0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU, + 0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U, + 0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U, + 0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U, + 0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U, + 0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU, + 0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U, + 0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U, + 0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU, + 0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU, + 0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U, + 0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U, + 0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U, + 0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU, + 0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U, + 0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U, + 0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U, + 0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U, + 0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U, + 0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U, + 0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U, + 0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU, + 0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U, + 0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U, + 0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU, + 0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU, + 0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U, + 0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU, + 0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U, + 0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U, + 0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U, + 0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U, + 0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U, + 0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U, + 0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU, + 0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU, + 0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU, + 0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU, + 0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U, + 0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U, + 0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U, + 0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU, + 0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U, + 0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U, +}; + +static const u32 Td4[256] = +{ + 0x52525252U, 0x09090909U, 0x6a6a6a6aU, 0xd5d5d5d5U, + 0x30303030U, 0x36363636U, 0xa5a5a5a5U, 0x38383838U, + 0xbfbfbfbfU, 0x40404040U, 0xa3a3a3a3U, 0x9e9e9e9eU, + 0x81818181U, 0xf3f3f3f3U, 0xd7d7d7d7U, 0xfbfbfbfbU, + 0x7c7c7c7cU, 0xe3e3e3e3U, 0x39393939U, 0x82828282U, + 0x9b9b9b9bU, 0x2f2f2f2fU, 0xffffffffU, 0x87878787U, + 0x34343434U, 0x8e8e8e8eU, 0x43434343U, 0x44444444U, + 0xc4c4c4c4U, 0xdedededeU, 0xe9e9e9e9U, 0xcbcbcbcbU, + 0x54545454U, 0x7b7b7b7bU, 0x94949494U, 0x32323232U, + 0xa6a6a6a6U, 0xc2c2c2c2U, 0x23232323U, 0x3d3d3d3dU, + 0xeeeeeeeeU, 0x4c4c4c4cU, 0x95959595U, 0x0b0b0b0bU, + 0x42424242U, 0xfafafafaU, 0xc3c3c3c3U, 0x4e4e4e4eU, + 0x08080808U, 0x2e2e2e2eU, 0xa1a1a1a1U, 0x66666666U, + 0x28282828U, 0xd9d9d9d9U, 0x24242424U, 0xb2b2b2b2U, + 0x76767676U, 0x5b5b5b5bU, 0xa2a2a2a2U, 0x49494949U, + 0x6d6d6d6dU, 0x8b8b8b8bU, 0xd1d1d1d1U, 0x25252525U, + 0x72727272U, 0xf8f8f8f8U, 0xf6f6f6f6U, 0x64646464U, + 0x86868686U, 0x68686868U, 0x98989898U, 0x16161616U, + 0xd4d4d4d4U, 0xa4a4a4a4U, 0x5c5c5c5cU, 0xccccccccU, + 0x5d5d5d5dU, 0x65656565U, 0xb6b6b6b6U, 0x92929292U, + 0x6c6c6c6cU, 0x70707070U, 0x48484848U, 0x50505050U, + 0xfdfdfdfdU, 0xededededU, 0xb9b9b9b9U, 0xdadadadaU, + 0x5e5e5e5eU, 0x15151515U, 0x46464646U, 0x57575757U, + 0xa7a7a7a7U, 0x8d8d8d8dU, 0x9d9d9d9dU, 0x84848484U, + 0x90909090U, 0xd8d8d8d8U, 0xababababU, 0x00000000U, + 0x8c8c8c8cU, 0xbcbcbcbcU, 0xd3d3d3d3U, 0x0a0a0a0aU, + 0xf7f7f7f7U, 0xe4e4e4e4U, 0x58585858U, 0x05050505U, + 0xb8b8b8b8U, 0xb3b3b3b3U, 0x45454545U, 0x06060606U, + 0xd0d0d0d0U, 0x2c2c2c2cU, 0x1e1e1e1eU, 0x8f8f8f8fU, + 0xcacacacaU, 0x3f3f3f3fU, 0x0f0f0f0fU, 0x02020202U, + 0xc1c1c1c1U, 0xafafafafU, 0xbdbdbdbdU, 0x03030303U, + 0x01010101U, 0x13131313U, 0x8a8a8a8aU, 0x6b6b6b6bU, + 0x3a3a3a3aU, 0x91919191U, 0x11111111U, 0x41414141U, + 0x4f4f4f4fU, 0x67676767U, 0xdcdcdcdcU, 0xeaeaeaeaU, + 0x97979797U, 0xf2f2f2f2U, 0xcfcfcfcfU, 0xcecececeU, + 0xf0f0f0f0U, 0xb4b4b4b4U, 0xe6e6e6e6U, 0x73737373U, + 0x96969696U, 0xacacacacU, 0x74747474U, 0x22222222U, + 0xe7e7e7e7U, 0xadadadadU, 0x35353535U, 0x85858585U, + 0xe2e2e2e2U, 0xf9f9f9f9U, 0x37373737U, 0xe8e8e8e8U, + 0x1c1c1c1cU, 0x75757575U, 0xdfdfdfdfU, 0x6e6e6e6eU, + 0x47474747U, 0xf1f1f1f1U, 0x1a1a1a1aU, 0x71717171U, + 0x1d1d1d1dU, 0x29292929U, 0xc5c5c5c5U, 0x89898989U, + 0x6f6f6f6fU, 0xb7b7b7b7U, 0x62626262U, 0x0e0e0e0eU, + 0xaaaaaaaaU, 0x18181818U, 0xbebebebeU, 0x1b1b1b1bU, + 0xfcfcfcfcU, 0x56565656U, 0x3e3e3e3eU, 0x4b4b4b4bU, + 0xc6c6c6c6U, 0xd2d2d2d2U, 0x79797979U, 0x20202020U, + 0x9a9a9a9aU, 0xdbdbdbdbU, 0xc0c0c0c0U, 0xfefefefeU, + 0x78787878U, 0xcdcdcdcdU, 0x5a5a5a5aU, 0xf4f4f4f4U, + 0x1f1f1f1fU, 0xddddddddU, 0xa8a8a8a8U, 0x33333333U, + 0x88888888U, 0x07070707U, 0xc7c7c7c7U, 0x31313131U, + 0xb1b1b1b1U, 0x12121212U, 0x10101010U, 0x59595959U, + 0x27272727U, 0x80808080U, 0xececececU, 0x5f5f5f5fU, + 0x60606060U, 0x51515151U, 0x7f7f7f7fU, 0xa9a9a9a9U, + 0x19191919U, 0xb5b5b5b5U, 0x4a4a4a4aU, 0x0d0d0d0dU, + 0x2d2d2d2dU, 0xe5e5e5e5U, 0x7a7a7a7aU, 0x9f9f9f9fU, + 0x93939393U, 0xc9c9c9c9U, 0x9c9c9c9cU, 0xefefefefU, + 0xa0a0a0a0U, 0xe0e0e0e0U, 0x3b3b3b3bU, 0x4d4d4d4dU, + 0xaeaeaeaeU, 0x2a2a2a2aU, 0xf5f5f5f5U, 0xb0b0b0b0U, + 0xc8c8c8c8U, 0xebebebebU, 0xbbbbbbbbU, 0x3c3c3c3cU, + 0x83838383U, 0x53535353U, 0x99999999U, 0x61616161U, + 0x17171717U, 0x2b2b2b2bU, 0x04040404U, 0x7e7e7e7eU, + 0xbabababaU, 0x77777777U, 0xd6d6d6d6U, 0x26262626U, + 0xe1e1e1e1U, 0x69696969U, 0x14141414U, 0x63636363U, + 0x55555555U, 0x21212121U, 0x0c0c0c0cU, 0x7d7d7d7dU, +}; + +static const u32 rcon[] = +{ + 0x01000000, 0x02000000, 0x04000000, 0x08000000, + 0x10000000, 0x20000000, 0x40000000, 0x80000000, + 0x1B000000, 0x36000000, + /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */ +}; + +#define GETU32(plaintext) (((u32)(plaintext)[0] << 24) ^ \ + ((u32)(plaintext)[1] << 16) ^ \ + ((u32)(plaintext)[2] << 8) ^ \ + ((u32)(plaintext)[3])) + +#define PUTU32(ciphertext, st) { (ciphertext)[0] = (u8)((st) >> 24); \ + (ciphertext)[1] = (u8)((st) >> 16); \ + (ciphertext)[2] = (u8)((st) >> 8); \ + (ciphertext)[3] = (u8)(st); } + +/** + * Expand the cipher key into the encryption key schedule. + * + * @return the number of rounds for the given cipher key size. + */ +int rijndaelSetupEncrypt(u32 *rk, const u8 *key, int keybits) +{ + int i = 0; + u32 temp; + + rk[0] = GETU32(key ); + rk[1] = GETU32(key + 4); + rk[2] = GETU32(key + 8); + rk[3] = GETU32(key + 12); + if (keybits == 128) + { + for (;;) + { + temp = rk[3]; + rk[4] = rk[0] ^ + (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (Te4[(temp ) & 0xff] & 0x0000ff00) ^ + (Te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[5] = rk[1] ^ rk[4]; + rk[6] = rk[2] ^ rk[5]; + rk[7] = rk[3] ^ rk[6]; + if (++i == 10) + return 10; + rk += 4; + } + } + rk[4] = GETU32(key + 16); + rk[5] = GETU32(key + 20); + if (keybits == 192) + { + for (;;) + { + temp = rk[ 5]; + rk[ 6] = rk[ 0] ^ + (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (Te4[(temp ) & 0xff] & 0x0000ff00) ^ + (Te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[ 7] = rk[ 1] ^ rk[ 6]; + rk[ 8] = rk[ 2] ^ rk[ 7]; + rk[ 9] = rk[ 3] ^ rk[ 8]; + if (++i == 8) + return 12; + rk[10] = rk[ 4] ^ rk[ 9]; + rk[11] = rk[ 5] ^ rk[10]; + rk += 6; + } + } + rk[6] = GETU32(key + 24); + rk[7] = GETU32(key + 28); + if (keybits == 256) + { + for (;;) + { + temp = rk[ 7]; + rk[ 8] = rk[ 0] ^ + (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (Te4[(temp ) & 0xff] & 0x0000ff00) ^ + (Te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[ 9] = rk[ 1] ^ rk[ 8]; + rk[10] = rk[ 2] ^ rk[ 9]; + rk[11] = rk[ 3] ^ rk[10]; + if (++i == 7) + return 14; + temp = rk[11]; + rk[12] = rk[ 4] ^ + (Te4[(temp >> 24) ] & 0xff000000) ^ + (Te4[(temp >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(temp >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(temp ) & 0xff] & 0x000000ff); + rk[13] = rk[ 5] ^ rk[12]; + rk[14] = rk[ 6] ^ rk[13]; + rk[15] = rk[ 7] ^ rk[14]; + rk += 8; + } + } + return 0; +} + +/** + * Expand the cipher key into the decryption key schedule. + * + * @return the number of rounds for the given cipher key size. + */ +int rijndaelSetupDecrypt(u32 *rk, const u8 *key, int keybits) +{ + int nrounds, i, j; + u32 temp; + + /* expand the cipher key: */ + nrounds = rijndaelSetupEncrypt(rk, key, keybits); + /* invert the order of the round keys: */ + for (i = 0, j = 4*nrounds; i < j; i += 4, j -= 4) + { + temp = rk[i ]; rk[i ] = rk[j ]; rk[j ] = temp; + temp = rk[i + 1]; rk[i + 1] = rk[j + 1]; rk[j + 1] = temp; + temp = rk[i + 2]; rk[i + 2] = rk[j + 2]; rk[j + 2] = temp; + temp = rk[i + 3]; rk[i + 3] = rk[j + 3]; rk[j + 3] = temp; + } + /* apply the inverse MixColumn transform to all round keys but the first and the last: */ + for (i = 1; i < nrounds; i++) + { + rk += 4; + rk[0] = + Td0[Te4[(rk[0] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[0] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[0] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[0] ) & 0xff] & 0xff]; + rk[1] = + Td0[Te4[(rk[1] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[1] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[1] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[1] ) & 0xff] & 0xff]; + rk[2] = + Td0[Te4[(rk[2] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[2] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[2] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[2] ) & 0xff] & 0xff]; + rk[3] = + Td0[Te4[(rk[3] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[3] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[3] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[3] ) & 0xff] & 0xff]; + } + return nrounds; +} + +void rijndaelEncrypt(const u32 *rk, int nrounds, const u8 plaintext[16], + u8 ciphertext[16]) +{ + u32 s0, s1, s2, s3, t0, t1, t2, t3; + #ifndef FULL_UNROLL + int r; + #endif /* ?FULL_UNROLL */ + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(plaintext ) ^ rk[0]; + s1 = GETU32(plaintext + 4) ^ rk[1]; + s2 = GETU32(plaintext + 8) ^ rk[2]; + s3 = GETU32(plaintext + 12) ^ rk[3]; + #ifdef FULL_UNROLL + /* round 1: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[ 4]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[ 5]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[ 6]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[ 7]; + /* round 2: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[ 8]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[ 9]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[10]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[11]; + /* round 3: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[12]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[13]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[14]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[15]; + /* round 4: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[16]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[17]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[18]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[19]; + /* round 5: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[20]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[21]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[22]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[23]; + /* round 6: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[24]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[25]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[26]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[27]; + /* round 7: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[28]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[29]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[30]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[31]; + /* round 8: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[32]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[33]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[34]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[35]; + /* round 9: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[36]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[37]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[38]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[39]; + if (nrounds > 10) + { + /* round 10: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[40]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[41]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[42]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[43]; + /* round 11: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[44]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[45]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[46]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[47]; + if (nrounds > 12) + { + /* round 12: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[48]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[49]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[50]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[51]; + /* round 13: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[52]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[53]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[54]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[55]; + } + } + rk += nrounds << 2; + #else /* !FULL_UNROLL */ + /* + * nrounds - 1 full rounds: + */ + r = nrounds >> 1; + for (;;) + { + t0 = + Te0[(s0 >> 24) ] ^ + Te1[(s1 >> 16) & 0xff] ^ + Te2[(s2 >> 8) & 0xff] ^ + Te3[(s3 ) & 0xff] ^ + rk[4]; + t1 = + Te0[(s1 >> 24) ] ^ + Te1[(s2 >> 16) & 0xff] ^ + Te2[(s3 >> 8) & 0xff] ^ + Te3[(s0 ) & 0xff] ^ + rk[5]; + t2 = + Te0[(s2 >> 24) ] ^ + Te1[(s3 >> 16) & 0xff] ^ + Te2[(s0 >> 8) & 0xff] ^ + Te3[(s1 ) & 0xff] ^ + rk[6]; + t3 = + Te0[(s3 >> 24) ] ^ + Te1[(s0 >> 16) & 0xff] ^ + Te2[(s1 >> 8) & 0xff] ^ + Te3[(s2 ) & 0xff] ^ + rk[7]; + rk += 8; + if (--r == 0) + break; + s0 = + Te0[(t0 >> 24) ] ^ + Te1[(t1 >> 16) & 0xff] ^ + Te2[(t2 >> 8) & 0xff] ^ + Te3[(t3 ) & 0xff] ^ + rk[0]; + s1 = + Te0[(t1 >> 24) ] ^ + Te1[(t2 >> 16) & 0xff] ^ + Te2[(t3 >> 8) & 0xff] ^ + Te3[(t0 ) & 0xff] ^ + rk[1]; + s2 = + Te0[(t2 >> 24) ] ^ + Te1[(t3 >> 16) & 0xff] ^ + Te2[(t0 >> 8) & 0xff] ^ + Te3[(t1 ) & 0xff] ^ + rk[2]; + s3 = + Te0[(t3 >> 24) ] ^ + Te1[(t0 >> 16) & 0xff] ^ + Te2[(t1 >> 8) & 0xff] ^ + Te3[(t2 ) & 0xff] ^ + rk[3]; + } + #endif /* ?FULL_UNROLL */ + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = + (Te4[(t0 >> 24) ] & 0xff000000) ^ + (Te4[(t1 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t2 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t3 ) & 0xff] & 0x000000ff) ^ + rk[0]; + PUTU32(ciphertext , s0); + s1 = + (Te4[(t1 >> 24) ] & 0xff000000) ^ + (Te4[(t2 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t3 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t0 ) & 0xff] & 0x000000ff) ^ + rk[1]; + PUTU32(ciphertext + 4, s1); + s2 = + (Te4[(t2 >> 24) ] & 0xff000000) ^ + (Te4[(t3 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t0 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t1 ) & 0xff] & 0x000000ff) ^ + rk[2]; + PUTU32(ciphertext + 8, s2); + s3 = + (Te4[(t3 >> 24) ] & 0xff000000) ^ + (Te4[(t0 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t1 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t2 ) & 0xff] & 0x000000ff) ^ + rk[3]; + PUTU32(ciphertext + 12, s3); +} + +void rijndaelDecrypt(const u32 *rk, int nrounds, const u8 ciphertext[16], + u8 plaintext[16]) +{ + u32 s0, s1, s2, s3, t0, t1, t2, t3; + #ifndef FULL_UNROLL + int r; + #endif /* ?FULL_UNROLL */ + + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(ciphertext ) ^ rk[0]; + s1 = GETU32(ciphertext + 4) ^ rk[1]; + s2 = GETU32(ciphertext + 8) ^ rk[2]; + s3 = GETU32(ciphertext + 12) ^ rk[3]; + #ifdef FULL_UNROLL + /* round 1: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[ 4]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[ 5]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[ 6]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[ 7]; + /* round 2: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[ 8]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[ 9]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[10]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[11]; + /* round 3: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[12]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[13]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[14]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[15]; + /* round 4: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[16]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[17]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[18]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[19]; + /* round 5: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[20]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[21]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[22]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[23]; + /* round 6: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[24]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[25]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[26]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[27]; + /* round 7: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[28]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[29]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[30]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[31]; + /* round 8: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[32]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[33]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[34]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[35]; + /* round 9: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[36]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[37]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[38]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[39]; + if (nrounds > 10) + { + /* round 10: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[40]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[41]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[42]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[43]; + /* round 11: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[44]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[45]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[46]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[47]; + if (nrounds > 12) + { + /* round 12: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[48]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[49]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[50]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[51]; + /* round 13: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[52]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[53]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[54]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[55]; + } + } + rk += nrounds << 2; + #else /* !FULL_UNROLL */ + /* + * nrounds - 1 full rounds: + */ + r = nrounds >> 1; + for (;;) + { + t0 = + Td0[(s0 >> 24) ] ^ + Td1[(s3 >> 16) & 0xff] ^ + Td2[(s2 >> 8) & 0xff] ^ + Td3[(s1 ) & 0xff] ^ + rk[4]; + t1 = + Td0[(s1 >> 24) ] ^ + Td1[(s0 >> 16) & 0xff] ^ + Td2[(s3 >> 8) & 0xff] ^ + Td3[(s2 ) & 0xff] ^ + rk[5]; + t2 = + Td0[(s2 >> 24) ] ^ + Td1[(s1 >> 16) & 0xff] ^ + Td2[(s0 >> 8) & 0xff] ^ + Td3[(s3 ) & 0xff] ^ + rk[6]; + t3 = + Td0[(s3 >> 24) ] ^ + Td1[(s2 >> 16) & 0xff] ^ + Td2[(s1 >> 8) & 0xff] ^ + Td3[(s0 ) & 0xff] ^ + rk[7]; + rk += 8; + if (--r == 0) + break; + s0 = + Td0[(t0 >> 24) ] ^ + Td1[(t3 >> 16) & 0xff] ^ + Td2[(t2 >> 8) & 0xff] ^ + Td3[(t1 ) & 0xff] ^ + rk[0]; + s1 = + Td0[(t1 >> 24) ] ^ + Td1[(t0 >> 16) & 0xff] ^ + Td2[(t3 >> 8) & 0xff] ^ + Td3[(t2 ) & 0xff] ^ + rk[1]; + s2 = + Td0[(t2 >> 24) ] ^ + Td1[(t1 >> 16) & 0xff] ^ + Td2[(t0 >> 8) & 0xff] ^ + Td3[(t3 ) & 0xff] ^ + rk[2]; + s3 = + Td0[(t3 >> 24) ] ^ + Td1[(t2 >> 16) & 0xff] ^ + Td2[(t1 >> 8) & 0xff] ^ + Td3[(t0 ) & 0xff] ^ + rk[3]; + } + #endif /* ?FULL_UNROLL */ + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = + (Td4[(t0 >> 24) ] & 0xff000000) ^ + (Td4[(t3 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t2 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t1 ) & 0xff] & 0x000000ff) ^ + rk[0]; + PUTU32(plaintext , s0); + s1 = + (Td4[(t1 >> 24) ] & 0xff000000) ^ + (Td4[(t0 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t3 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t2 ) & 0xff] & 0x000000ff) ^ + rk[1]; + PUTU32(plaintext + 4, s1); + s2 = + (Td4[(t2 >> 24) ] & 0xff000000) ^ + (Td4[(t1 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t0 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t3 ) & 0xff] & 0x000000ff) ^ + rk[2]; + PUTU32(plaintext + 8, s2); + s3 = + (Td4[(t3 >> 24) ] & 0xff000000) ^ + (Td4[(t2 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t1 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t0 ) & 0xff] & 0x000000ff) ^ + rk[3]; + PUTU32(plaintext + 12, s3); +} + + diff --git a/src/main/crypt/lib/rijndael.h b/src/main/crypt/lib/rijndael.h new file mode 100644 index 0000000..9394a5b --- /dev/null +++ b/src/main/crypt/lib/rijndael.h @@ -0,0 +1,17 @@ +#ifndef H__RIJNDAEL +#define H__RIJNDAEL + +int rijndaelSetupEncrypt(unsigned long *rk, const unsigned char *key, + int keybits); +int rijndaelSetupDecrypt(unsigned long *rk, const unsigned char *key, + int keybits); +void rijndaelEncrypt(const unsigned long *rk, int nrounds, + const unsigned char plaintext[16], unsigned char ciphertext[16]); +void rijndaelDecrypt(const unsigned long *rk, int nrounds, + const unsigned char ciphertext[16], unsigned char plaintext[16]); + +#define KEYLENGTH(keybits) ((keybits)/8) +#define RKLENGTH(keybits) ((keybits)/8+28) +#define NROUNDS(keybits) ((keybits)/32+6) + +#endif diff --git a/src/main/crypt/md5.c b/src/main/crypt/md5.c new file mode 100644 index 0000000..418c03a --- /dev/null +++ b/src/main/crypt/md5.c @@ -0,0 +1,14 @@ +#define LOG_MODULE "crypt-md5" + +#include "lib/md5.h" + +#include "md5.h" + +void crypt_md5_hash(uint8_t* out, const uint8_t* in, size_t nbytes) +{ + MD5_CTX ctx; + + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char*) in, (unsigned int) nbytes); + MD5Final(out, &ctx); +} \ No newline at end of file diff --git a/src/main/crypt/md5.h b/src/main/crypt/md5.h new file mode 100644 index 0000000..dbdf982 --- /dev/null +++ b/src/main/crypt/md5.h @@ -0,0 +1,16 @@ +#ifndef CRYPT_MD5_H +#define CRYPT_MD5_H + +#include +#include + +/** + * Create an MD5 hash + * + * @param out Output buffer + * @param in Input buffer to hash + * @param nbytes Number of bytes of input buffer to process + */ +void crypt_md5_hash(uint8_t* out, const uint8_t* in, size_t nbytes); + +#endif \ No newline at end of file diff --git a/src/main/hook/core/options.c b/src/main/hook/core/options.c new file mode 100644 index 0000000..4b4d8b8 --- /dev/null +++ b/src/main/hook/core/options.c @@ -0,0 +1,50 @@ +#include "hook/core/options.h" + +#include "util/options.h" + +#define HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +const struct util_options_def hook_core_options_def[] = { + { + .name = HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +const struct util_options_defs hook_core_options_defs = { + .usage_header = "", + // Just use something because this is not relevant/required here + .usage_param = '\r', + .defs = hook_core_options_def, + .ndefs = lengthof(hook_core_options_def) +}; + +void hook_core_options_init(int argc, char** argv, struct hook_core_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&hook_core_options_defs); + + if (!options_opt) { + return; + } + + options->log.file = util_options_get_str(options_opt, HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, HOOK_CORE_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); +} \ No newline at end of file diff --git a/src/main/hook/core/options.h b/src/main/hook/core/options.h new file mode 100644 index 0000000..f610765 --- /dev/null +++ b/src/main/hook/core/options.h @@ -0,0 +1,15 @@ +#ifndef HOOK_CORE_OPTIONS_H +#define HOOK_CORE_OPTIONS_H + +#include "util/log.h" + +struct hook_core_options { + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +void hook_core_options_init(int argc, char** argv, struct hook_core_options* options); + +#endif diff --git a/src/main/hook/core/piu-utils.c b/src/main/hook/core/piu-utils.c new file mode 100644 index 0000000..44069d0 --- /dev/null +++ b/src/main/hook/core/piu-utils.c @@ -0,0 +1,133 @@ +#define LOG_MODULE "hook-core-piu-utils" + +#include +#include +#include + +#include "hook/core/options.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/log.h" +#include "util/proc.h" +#include "util/str.h" +#include "util/sys-info.h" + +static void hook_core_piu_log_application_args(int argc, char** argv) +{ + const char* sep = "\n"; + char* str = util_str_dup(""); + char* tmp; + + for (int i = 0; i < argc; i++) { + if (strlen(str) > 0) { + tmp = util_str_merge(str, sep); + free(str); + str = tmp; + } + + tmp = util_str_merge(str, argv[i]); + free(str); + str = tmp; + } + + log_info("Application arguments (%d):\n%s", argc, str); + free(str); +} + +void hook_core_piu_log_init(int argc, char** argv) +{ + struct hook_core_options options; + + hook_core_options_init(argc, argv, &options); + + util_log_set_file(options.log.file, false); + util_log_set_level(options.log.level); +} + +void hook_core_piu_log_info(int argc, char** argv) +{ + log_info("\n" + " _ _ \n" + " _ __ _ _ _ __ ___ _ __ | |_ ___ ___ | |___ \n" + " | '_ \\| | | | '_ ` _ \\| '_ \\| __/ _ \\ / _ \\| / __|\n" + " | |_) | |_| | | | | | | |_) | || (_) | (_) | \\__ \\\n" + " | .__/ \\__,_|_| |_| |_| .__/ \\__\\___/ \\___/|_|___/\n" + " |_| |_| "); + + log_info("Pumptools build " __DATE__ " " __TIME__ " gitrev %s", STRINGIFY(GITREV)); + + util_sys_info_log(); + util_proc_log_info(); + util_glibc_info_log(); + hook_core_piu_log_application_args(argc, argv); +} + +char* hook_core_piu_utils_get_and_verify_game_data_path(int argc, char** argv) +{ + if (argc < 2) { + log_error("Missing game data path, usage for piu (stock args): piu (e.g. piu ./game)"); + exit(0); + } + + char* game_data_path = util_str_dup(argv[1]); + + log_info("Game data path: %s", game_data_path); + + /* remove trailing / for the hook. however, the game crashes without it */ + if (util_str_ends_with(game_data_path, "/")) { + game_data_path[strlen(game_data_path) - 1] = '\0'; + } else { + log_error("Game data path '%s' does not end with a trailing /, exiting because the game is going to " + "crash anyway!", game_data_path); + exit(0); + } + + /* depending on the version, the game also just crashes if the game folder or files within it were not found */ + if (!util_fs_path_exists(game_data_path)) { + log_error("Game data path '%s' does not exist, exiting", game_data_path); + exit(0); + } + + return game_data_path; +} + +void hook_core_piu_utils_verify_root_user() +{ + log_info("Verifying process is running as root user."); + + if (!util_proc_is_running_as_root()) { + log_error("Process not running as root which is required for the game to work correctly."); + //exit(0); + } +} + +void hook_core_piu_utils_fix_locale() +{ + log_info("Fixing locale settings."); + + if (setenv("LC_ALL", "C", 1) != 0) { + log_warn("Setting locale LC_ALL failed."); + } + + if (unsetenv("LANGUAGE") != 0) { + log_warn("Unsetting LANGUAGE env var failed."); + } +} + +bool hook_core_piu_block_non_piu_process_recursion(int argc, char** argv) +{ + /* Sanity check for arguments. If further child processes are started + from the current process, and using LD_PRELOAD on the current process, + all children are also getting the preloaded library. This causes + multiple calls to the trap main function and just crashes our + application at some point. + The executable file is required to contain the "piu" substring to make + this work properly */ + if (!strstr(argv[0], "piu")) { + log_warn("Not attaching to non piu process %s", argv[0]); + return true; + } else { + return false; + } +} \ No newline at end of file diff --git a/src/main/hook/core/piu-utils.h b/src/main/hook/core/piu-utils.h new file mode 100644 index 0000000..3608c09 --- /dev/null +++ b/src/main/hook/core/piu-utils.h @@ -0,0 +1,53 @@ +#ifndef HOOK_CORE_PIU_UTILS_H +#define HOOK_CORE_PIU_UTILS_H + +/** + * Initialize the logging system as early as possible to allow recording everything to a log file as well. + */ +void hook_core_piu_log_init(int argc, char** argv); + +/** + * Log process and system relevant information. + */ +void hook_core_piu_log_info(int argc, char** argv); + +/** + * Check and verify the game data path argument passed to the game via argv + * + * Executes various checks and catches errors that might result in the game + * simply crashing without any error indication. + * + * @param argc Argc from piu main + * @param argv Argv from piu main + * @return New string with game data path extracted from argv (caller has to free) + */ +char* hook_core_piu_utils_get_and_verify_game_data_path(int argc, char** argv); + +/** + * Verify that the current process is running as root user because the game + * needs access to various devices that require higher privileges. + * + * Does not return and errors if not running as root user. + */ +void hook_core_piu_utils_verify_root_user(); + +/** + * Fix the locale configuration for Fiesta 2 and newer. + */ +void hook_core_piu_utils_fix_locale(); + +/** + * If further child processes are started from the current process, and using + * LD_PRELOAD on the current process, all children are also getting the preloaded + * library. This causes multiple calls to the trap main function and just crashes our + * application at some point. The executable file is required to contain the "piu" + * substring to make this work properly. + * + * @param argc Argc from piu main + * @param argv Argv from piu main + * @return True if we have to block this call by another process, + * false if the process calling this is the piu process. + */ +bool hook_core_piu_block_non_piu_process_recursion(int argc, char** argv); + +#endif diff --git a/src/main/hook/exc/eeprom.c b/src/main/hook/exc/eeprom.c new file mode 100644 index 0000000..460d0f6 --- /dev/null +++ b/src/main/hook/exc/eeprom.c @@ -0,0 +1,98 @@ +#define LOG_MODULE "exchook-eeprom" + +#include +#include +#include + +#include "hook/exc/mempatch.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/patch.h" + +static int32_t exchook_eeprom_read(uint8_t* buffer, int32_t len); +static int32_t exchook_eeprom_write(const uint8_t* buffer, int32_t len); + +static char* exchook_eeprom_file; +static uint8_t exchook_eeprom_buffer[8192]; + +void exchook_eeprom_init(const struct exchook_mempatch_table* patch_table, const char* eeprom_file) +{ + void* buffer; + size_t len; + + exchook_eeprom_file = util_fs_get_abs_path(eeprom_file); + + /* EEPROM file does not exist, create new */ + if (!exchook_eeprom_file) { + memset(exchook_eeprom_buffer, 0xFF, sizeof(exchook_eeprom_buffer)); + + FILE* file = fopen(eeprom_file, "wb+"); + + if (file == NULL) { + log_error("Creating new eeprom file %s failed: %s", eeprom_file, strerror(errno)); + } + + fwrite(exchook_eeprom_buffer, sizeof(exchook_eeprom_buffer), 1, file); + fclose(file); + + exchook_eeprom_file = util_fs_get_abs_path(eeprom_file); + len = (size_t) -1; + } else { + if (util_file_load(exchook_eeprom_file, &buffer, &len, false)) { + if (len > sizeof(exchook_eeprom_buffer)) { + log_error("Contents of %s eeprom file exceed the max eeprom buffer " + " size of %d bytes", eeprom_file, + sizeof(exchook_eeprom_buffer)); + free(buffer); + return; + } + + /* copy read contents */ + memcpy(exchook_eeprom_buffer, buffer, len); + free(buffer); + } else { + log_error("Loading eeprom file %s failed", exchook_eeprom_file); + } + } + + log_assert(exchook_eeprom_file); + + /* Hook eeprom */ + util_patch_function(patch_table->addr_eeprom_read, exchook_eeprom_read); + util_patch_function(patch_table->addr_eeprom_write, exchook_eeprom_write); + + log_info("Initizlied (%s), eeprom file %s", len == -1 ? "new" : "loaded", eeprom_file); +} + +static int32_t exchook_eeprom_read(uint8_t* buffer, int32_t len) +{ + if (len > sizeof(exchook_eeprom_buffer)) { + log_error("Eeprom read %d exceeds buffer size %d", len, + sizeof(exchook_eeprom_buffer)); + return 0; + } + + memcpy(buffer, exchook_eeprom_buffer, len); + + return len; +} + +static int32_t exchook_eeprom_write(const uint8_t* buffer, int32_t len) +{ + if (len > sizeof(exchook_eeprom_buffer)) { + log_error("Eeprom write %d exceeds buffer size %d", len, + sizeof(exchook_eeprom_buffer)); + return 0; + } + + memcpy(exchook_eeprom_buffer, buffer, len); + + /* Write back to file */ + if (!util_file_save(exchook_eeprom_file, exchook_eeprom_buffer, len)) { + log_error("Storing eeprom contents to file %s failed", + exchook_eeprom_file); + } + + return len; +} \ No newline at end of file diff --git a/src/main/hook/exc/eeprom.h b/src/main/hook/exc/eeprom.h new file mode 100644 index 0000000..e7f0955 --- /dev/null +++ b/src/main/hook/exc/eeprom.h @@ -0,0 +1,19 @@ +/** + * Patch module emulating the MK5 (hardware) eeprom chip used to store + * game settings to on Exceed + */ +#ifndef EXCHOOK_EEPROM_H +#define EXCHOOK_EEPROM_H + +#include "hook/exc/mempatch.h" + +/** + * Init eeprom emulation for exceed + * + * @param patch_table Patch table with memory addresses + * @param eeprom_file Path to existing eeprom file to read from and new eeprom + * file to store to + */ +void exchook_eeprom_init(const struct exchook_mempatch_table* patch_table, const char* eeprom_file); + +#endif \ No newline at end of file diff --git a/src/main/hook/exc/io.c b/src/main/hook/exc/io.c new file mode 100644 index 0000000..e0b1a4a --- /dev/null +++ b/src/main/hook/exc/io.c @@ -0,0 +1,505 @@ +#define LOG_MODULE "exchook-io" + +#include + +#include "ptapi/io/piuio.h" +#include "ptapi/io/piuio/util/lib.h" + +#include "hook/exc/mempatch.h" + +#include "util/log.h" +#include "util/patch.h" + +/* Taken from ioarcade.hpp nx2 src dump */ +#define GI_P1_1 0x00000001 +#define GI_P1_3 0x00000002 +#define GI_P1_5 0x00000004 +#define GI_P1_7 0x00000008 +#define GI_P1_9 0x00000010 + +#define GI_P2_1 0x00000100 +#define GI_P2_3 0x00000200 +#define GI_P2_5 0x00000400 +#define GI_P2_7 0x00000800 +#define GI_P2_9 0x00001000 + +#define GI_TEST 0x00010000 +#define GI_SERVICE 0x00020000 +#define GI_CLEAR 0x00040000 + +#define GI_COIN 0x00100000 +#define GI_COIN2 0x00200000 + +struct exchook_io_sensores { + uint32_t lu : 4; + uint32_t ru : 4; + uint32_t cn : 4; + uint32_t ld : 4; + uint32_t rd : 4; + uint32_t padding : 12; +}; + +static const uint8_t exchook_io_sensor_mappings[] = { + 1, 2, 3, 0 +}; + +static struct ptapi_io_piuio_api exchook_piuio_api; + +static uint16_t exchook_io_check(void* obj); +static int32_t exchook_io_light_all(void* obj, uint8_t on); +static int32_t exchook_io_light_neon(void* obj, uint8_t on); +static int32_t exchook_io_light_halogan(void* obj, uint8_t on, uint16_t light_id); +static int32_t exchook_io_light_p1_ccfl(void* obj, uint8_t on, uint16_t cmd); +static int32_t exchook_io_light_p2_ccfl(void* obj, uint8_t on, uint16_t cmd); +static int32_t exchook_io_input(void* obj); +static int32_t exchook_io_coin_input(void* obj); +static int32_t exchook_io_coin_input_2(void* obj); +static int32_t exchook_io_coin_counter_2(void* obj); + +static uint32_t* exchook_io_game_input_stat; +static uint32_t* exchook_io_game_input_down; +static uint32_t* exchook_io_game_input_up; + +static uint32_t exchook_io_game_input_stat_prev; + +static struct exchook_io_sensores exchook_io_sensores_p1; +static struct exchook_io_sensores exchook_io_sensores_p2; +static struct ptapi_io_piuio_pad_outputs exchook_io_out_pad_p1; +static struct ptapi_io_piuio_pad_outputs exchook_io_out_pad_p2; +static struct ptapi_io_piuio_pad_outputs exchook_io_out_pad_press_p1; +static struct ptapi_io_piuio_pad_outputs exchook_io_out_pad_press_p2; +static struct ptapi_io_piuio_cab_outputs exchook_io_out_cab; + +static bool exchook_io_exit_on_service_test; + +void exchook_io_init(const struct exchook_mempatch_table* patch_table, const char* piuio_lib_path, + bool exit_on_service_test) +{ + if (!piuio_lib_path) { + log_error("NULL piuio (emu) lib path. Check that you have set-up and configured a piuio API implementation " + "correctly."); + return; + } + + /* Load piuio library funcs */ + log_info("Loading piuio api implementation %s", piuio_lib_path); + + if (!ptapi_io_piuio_util_lib_load(piuio_lib_path, &exchook_piuio_api)) { + log_error("Loading piuio API from file %s failed", piuio_lib_path); + return; + } + + exchook_io_exit_on_service_test = exit_on_service_test; + + log_info("Service + Test exit %d", exchook_io_exit_on_service_test); + + /* Hook MK5IO engine calls */ + /* Why so many functions you might ask? Because the MK5 IO code is a pile + of shit created by a monkey */ + + util_patch_function(patch_table->addr_io_check, exchook_io_check); + util_patch_function(patch_table->addr_io_light_all, exchook_io_light_all); + util_patch_function(patch_table->addr_io_light_neon, exchook_io_light_neon); + util_patch_function(patch_table->addr_io_light_halogan, exchook_io_light_halogan); + util_patch_function(patch_table->addr_io_light_p1_ccfl, exchook_io_light_p1_ccfl); + util_patch_function(patch_table->addr_io_light_p2_ccfl, exchook_io_light_p2_ccfl); + util_patch_function(patch_table->addr_io_input, exchook_io_input); + util_patch_function(patch_table->addr_io_coin_input, exchook_io_coin_input); + util_patch_function(patch_table->addr_io_coin_input_2, exchook_io_coin_input_2); + util_patch_function(patch_table->addr_io_coin_counter_2, exchook_io_coin_counter_2); + + exchook_io_game_input_stat = ((uint32_t*) patch_table->addr_io_game_input_stat); + exchook_io_game_input_down = ((uint32_t*) patch_table->addr_io_game_input_down); + exchook_io_game_input_up = ((uint32_t*) patch_table->addr_io_game_input_up); + + /* there is an io init method but obviously requires another address to be + hooked...just init our backend here */ + if (!exchook_piuio_api.open()) { + log_error("Opening io failed"); + return; + } + + log_info("Initialized"); +} + +void exchook_io_shutdown(void) +{ + +} + +/* check? why can't you just call it get_fucking_inputs... */ +static uint16_t exchook_io_check(void* obj) +{ + struct ptapi_io_piuio_pad_inputs p1_pad_in; + struct ptapi_io_piuio_pad_inputs p2_pad_in; + + /* Sensor states are expected to be returned using the object */ + struct exchook_io_sensores* sensores_p1 = + (struct exchook_io_sensores*) obj; + struct exchook_io_sensores* sensores_p2 = + (struct exchook_io_sensores*) (((uint32_t*) obj) + 1); + + /* execute polling here */ + + /* might delay output for a frame but whatever */ + if (!exchook_piuio_api.send()) { + log_error("Piuio send failed"); + } + + if (!exchook_piuio_api.recv()) { + log_error("Piuio receive failed"); + } + + /* MK5 inputs state reset */ + exchook_io_sensores_p1.lu = 0; + exchook_io_sensores_p1.ru = 0; + exchook_io_sensores_p1.cn = 0; + exchook_io_sensores_p1.ld = 0; + exchook_io_sensores_p1.rd = 0; + + exchook_io_sensores_p2.lu = 0; + exchook_io_sensores_p2.ru = 0; + exchook_io_sensores_p2.cn = 0; + exchook_io_sensores_p2.ld = 0; + exchook_io_sensores_p2.rd = 0; + + // State reset + memset(&p1_pad_in, 0, sizeof(struct ptapi_io_piuio_pad_inputs)); + memset(&p2_pad_in, 0, sizeof(struct ptapi_io_piuio_pad_inputs)); + + /* cycle all sensores */ + for (uint8_t i = 0; i < 4; i++) { + exchook_piuio_api.get_input_pad(0, i, &p1_pad_in); + exchook_piuio_api.get_input_pad(1, i, &p2_pad_in); + + /* process inputs */ + + /* Player 1 */ + if (p1_pad_in.lu) { + exchook_io_sensores_p1.lu |= (1 << exchook_io_sensor_mappings[i]); + } + + if (p1_pad_in.ru) { + exchook_io_sensores_p1.ru |= (1 << exchook_io_sensor_mappings[i]); + } + + if (p1_pad_in.cn) { + exchook_io_sensores_p1.cn |= (1 << exchook_io_sensor_mappings[i]); + } + + if (p1_pad_in.ld) { + exchook_io_sensores_p1.ld |= (1 << exchook_io_sensor_mappings[i]); + } + + if (p1_pad_in.rd) { + exchook_io_sensores_p1.rd |= (1 << exchook_io_sensor_mappings[i]); + } + + /* Player 2 */ + if (p2_pad_in.lu) { + exchook_io_sensores_p2.lu |= (1 << exchook_io_sensor_mappings[i]); + } + + if (p2_pad_in.ru) { + exchook_io_sensores_p2.ru |= (1 << exchook_io_sensor_mappings[i]); + } + + if (p2_pad_in.cn) { + exchook_io_sensores_p2.cn |= (1 << exchook_io_sensor_mappings[i]); + } + + if (p2_pad_in.ld) { + exchook_io_sensores_p2.ld |= (1 << exchook_io_sensor_mappings[i]); + } + + if (p2_pad_in.rd) { + exchook_io_sensores_p2.rd |= (1 << exchook_io_sensor_mappings[i]); + } + } + + memcpy(sensores_p1, &exchook_io_sensores_p1, sizeof(exchook_io_sensores_p1)); + memcpy(sensores_p2, &exchook_io_sensores_p2, sizeof(exchook_io_sensores_p2)); + + return 0; +} + +static int32_t exchook_io_light_all(void* obj, uint8_t on) +{ + if (on) { + memset(&exchook_io_out_pad_p1, 1, sizeof(exchook_io_out_pad_p1)); + memset(&exchook_io_out_pad_p2, 1, sizeof(exchook_io_out_pad_p2)); + memset(&exchook_io_out_cab, 1, sizeof(exchook_io_out_cab)); + } else { + memset(&exchook_io_out_pad_p1, 0, sizeof(exchook_io_out_pad_p1)); + memset(&exchook_io_out_pad_p2, 0, sizeof(exchook_io_out_pad_p2)); + memset(&exchook_io_out_cab, 0, sizeof(exchook_io_out_cab)); + } + + exchook_piuio_api.set_output_pad(0, &exchook_io_out_pad_p1); + exchook_piuio_api.set_output_pad(1, &exchook_io_out_pad_p2); + exchook_piuio_api.set_output_cab(&exchook_io_out_cab); + + return 0; +} + +static int32_t exchook_io_light_neon(void* obj, uint8_t on) +{ + if (on) { + exchook_io_out_cab.bass = true; + } else { + exchook_io_out_cab.bass = false; + } + + exchook_piuio_api.set_output_cab(&exchook_io_out_cab); + + return 0; +} + +static int32_t exchook_io_light_halogan(void* obj, uint8_t on, uint16_t light_id) +{ + if (light_id & 0x0400) { + exchook_io_out_cab.halo_l1 = on; + } + + if (light_id & 0x0200) { + exchook_io_out_cab.halo_l2 = on; + } + + if (light_id & 0x0100) { + exchook_io_out_cab.halo_r1 = on; + } + + if (light_id & 0x0080) { + exchook_io_out_cab.halo_r2 = on; + } + + exchook_piuio_api.set_output_cab(&exchook_io_out_cab); + + return 0; +} + +static int32_t exchook_io_light_p1_ccfl(void* obj, uint8_t on, uint16_t cmd) +{ + if (cmd & 0x0004) { + exchook_io_out_pad_p1.lu = on; + } + + if (cmd & 0x0008) { + exchook_io_out_pad_p1.ru = on; + } + + if (cmd & 0x0010) { + exchook_io_out_pad_p1.cn = on; + } + + if (cmd & 0x0020) { + exchook_io_out_pad_p1.ld = on; + } + + if (cmd & 0x040) { + exchook_io_out_pad_p1.rd = on; + } + + return 0; +} + +static int32_t exchook_io_light_p2_ccfl(void* obj, uint8_t on, uint16_t cmd) +{ + if (cmd & 0x0004) { + exchook_io_out_pad_p2.lu = on; + } + + if (cmd & 0x0008) { + exchook_io_out_pad_p2.ru = on; + } + + if (cmd & 0x0010) { + exchook_io_out_pad_p2.cn = on; + } + + if (cmd & 0x0020) { + exchook_io_out_pad_p2.ld = on; + } + + if (cmd & 0x040) { + exchook_io_out_pad_p2.rd = on; + } + + return 0; +} + +static int32_t exchook_io_input(void* obj) +{ + uint32_t stat; + uint32_t stat_down; + uint32_t stat_up; + + struct ptapi_io_piuio_sys_inputs sys_in; + + memset(&sys_in, 0, sizeof(struct ptapi_io_piuio_sys_inputs)); + + exchook_piuio_api.get_input_sys(&sys_in); + + /* For game loaders and stuff */ + if (exchook_io_exit_on_service_test) { + if (sys_in.test && sys_in.service) { + log_info("Exit on service + test enabled and hit, bye"); + exit(0); + } + } + + /* process final input states for the game as well as service, test, clear + and coin which can't be handled in the check function */ + + stat = 0; + stat_down = 0; + stat_up = 0; + + /* Player 1 */ + if (exchook_io_sensores_p1.lu) { + stat |= GI_P1_7; + } + + if (exchook_io_sensores_p1.ru) { + stat |= GI_P1_9; + } + + if (exchook_io_sensores_p1.cn) { + stat |= GI_P1_5; + } + + if (exchook_io_sensores_p1.ld) { + stat |= GI_P1_1; + } + + if (exchook_io_sensores_p1.rd) { + stat |= GI_P1_3; + } + + /* Player 2 */ + if (exchook_io_sensores_p2.lu) { + stat |= GI_P2_7; + } + + if (exchook_io_sensores_p2.ru) { + stat |= GI_P2_9; + } + + if (exchook_io_sensores_p2.cn) { + stat |= GI_P2_5; + } + + if (exchook_io_sensores_p2.ld) { + stat |= GI_P2_1; + } + + if (exchook_io_sensores_p2.rd) { + stat |= GI_P2_3; + } + + /* Sys */ + if (sys_in.test) { + stat |= GI_TEST; + } + + if (sys_in.service) { + stat |= GI_SERVICE; + } + + /* Taken from source but doesn't seem to work (on some versions?) */ + if (sys_in.clear) { + stat |= GI_CLEAR; + } + + if (sys_in.coin) { + stat |= GI_COIN; + } + + if (sys_in.coin2) { + stat |= GI_COIN2; + } + + /* iterate bits for up and down events */ + for (uint8_t i = 0; i < 32; i++) { + + if (!(exchook_io_game_input_stat_prev & (1 << i)) && + (stat & (1 << i))) { + /* New push down */ + stat_down |= (1 << i); + } else if ((exchook_io_game_input_stat_prev & (1 << i)) && + !(stat & (1 << i))) { + /* New release */ + stat_up |= (1 << i); + } + } + + /* Extremely bad IO subsystem design */ + *exchook_io_game_input_stat |= stat; + *exchook_io_game_input_down |= stat_down; + *exchook_io_game_input_up = stat_up; + + /* swap for next iterateion */ + exchook_io_game_input_stat_prev = *exchook_io_game_input_stat; + + /* Welp, the game expects us to do output setting if a button is pressed + here...fuck that mess. Merge (i.e. |= not =) with other states set + by the game */ + + memset(&exchook_io_out_pad_press_p1, 0, sizeof(exchook_io_out_pad_press_p1)); + memset(&exchook_io_out_pad_press_p2, 0, sizeof(exchook_io_out_pad_press_p2)); + + /* Player 1 */ + exchook_io_out_pad_press_p1.lu |= exchook_io_sensores_p1.lu; + exchook_io_out_pad_press_p1.ru |= exchook_io_sensores_p1.ru; + exchook_io_out_pad_press_p1.cn |= exchook_io_sensores_p1.cn; + exchook_io_out_pad_press_p1.ld |= exchook_io_sensores_p1.ld; + exchook_io_out_pad_press_p1.rd |= exchook_io_sensores_p1.rd; + + /* Player 2 */ + exchook_io_out_pad_press_p2.lu |= exchook_io_sensores_p2.lu; + exchook_io_out_pad_press_p2.ru |= exchook_io_sensores_p2.ru; + exchook_io_out_pad_press_p2.cn |= exchook_io_sensores_p2.cn; + exchook_io_out_pad_press_p2.ld |= exchook_io_sensores_p2.ld; + exchook_io_out_pad_press_p2.rd |= exchook_io_sensores_p2.rd; + + /* merge */ + /* Player 1 */ + exchook_io_out_pad_press_p1.lu |= exchook_io_out_pad_p1.lu; + exchook_io_out_pad_press_p1.ru |= exchook_io_out_pad_p1.ru; + exchook_io_out_pad_press_p1.cn |= exchook_io_out_pad_p1.cn; + exchook_io_out_pad_press_p1.ld |= exchook_io_out_pad_p1.ld; + exchook_io_out_pad_press_p1.rd |= exchook_io_out_pad_p1.rd; + + /* Player 2 */ + exchook_io_out_pad_press_p2.lu |= exchook_io_out_pad_p2.lu; + exchook_io_out_pad_press_p2.ru |= exchook_io_out_pad_p2.ru; + exchook_io_out_pad_press_p2.cn |= exchook_io_out_pad_p2.cn; + exchook_io_out_pad_press_p2.ld |= exchook_io_out_pad_p2.ld; + exchook_io_out_pad_press_p2.rd |= exchook_io_out_pad_p2.rd; + + exchook_piuio_api.set_output_pad(0, &exchook_io_out_pad_press_p1); + exchook_piuio_api.set_output_pad(1, &exchook_io_out_pad_press_p2); + + return 0; +} + +static int32_t exchook_io_coin_input(void* obj) +{ + /* Stub */ + + return 0; +} + +static int32_t exchook_io_coin_input_2(void* obj) +{ + /* Stub */ + + return 0; +} + +static int32_t exchook_io_coin_counter_2(void* obj) +{ + /* Stub */ + + return 0; +} \ No newline at end of file diff --git a/src/main/hook/exc/io.h b/src/main/hook/exc/io.h new file mode 100644 index 0000000..248986f --- /dev/null +++ b/src/main/hook/exc/io.h @@ -0,0 +1,26 @@ +/** + * Patch module hooking the MK5IO engine code of exceed to inject our own + * IO code to run the game on newer versions of the piuio as well as custom + * IO devices + */ +#ifndef EXCHOOK_IO_H +#define EXCHOOK_IO_H + +#include "hook/exc/mempatch.h" + +/** + * Initialize MK5IO emulation for exceed + * + * @param patch_table Patch table with memory addresses + * @param piuio_lib_path Path to piuio lib to use for emulation + * @parma exit_on_service_test Exit the game when pressing service + test + */ +void exchook_io_init(const struct exchook_mempatch_table* patch_table, const char* piuio_lib_path, + bool exit_on_service_test); + +/** + * Shut down io emulation + */ +void exchook_io_shutdown(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/exc/lockchip.c b/src/main/hook/exc/lockchip.c new file mode 100644 index 0000000..9c5b844 --- /dev/null +++ b/src/main/hook/exc/lockchip.c @@ -0,0 +1,43 @@ +#define LOG_MODULE "exchook-lockchip" + +#include + +#include "sec/lockchip/lockchip.h" +#include "sec/lockchip/lockchip-defs.h" + +#include "util/log.h" +#include "util/patch.h" + +#include "lockchip.h" + +static void exchook_lockchip_dec_chunk(uint8_t len, const uint8_t* src, + uint8_t* dest); + +void exchook_lockchip_init(const struct exchook_mempatch_table* patch_table) +{ + sec_lockchip_init( + sec_lockchip_defs_transforms[SEC_LOCK_CHIP_DEFS_GAME_EXCEED]); + + /* Hook lockchip */ + util_patch_function(patch_table->addr_lockchip_dec_chunk, exchook_lockchip_dec_chunk); + + log_info("Initialized"); +} + +static void exchook_lockchip_dec_chunk(uint8_t len, const uint8_t* src, + uint8_t* dest) +{ + sec_lockchip_start(); + + uint8_t last_res = sec_lockchip_step(src[0] ^ 0xFF); + + for (size_t i = 1; i < len; i++) + { + uint8_t result = sec_lockchip_step(src[i] ^ 0xFF); + dest[i - 1] = (uint8_t)((last_res >> 3) ^ (result << 5)); + last_res = result; + } + + uint8_t last_byte = sec_lockchip_step(0); + dest[len - 1] = (uint8_t)((last_res >> 3) ^ (last_byte << 5)); +} \ No newline at end of file diff --git a/src/main/hook/exc/lockchip.h b/src/main/hook/exc/lockchip.h new file mode 100644 index 0000000..0721981 --- /dev/null +++ b/src/main/hook/exc/lockchip.h @@ -0,0 +1,19 @@ +/** + * Patch module hooking lockchip calls to a lockchip emulator to decrypt + * game assets and run the game + */ +#ifndef EXCHOOK_LOCKCHIP_H +#define EXCHOOK_LOCKCHIP_H + +#include + +#include "hook/exc/mempatch.h" + +/** + * Initialize lockchip emulation for exceed + * + * @param patch_table Patch table with memory addresses + */ +void exchook_lockchip_init(const struct exchook_mempatch_table* patch_table); + +#endif \ No newline at end of file diff --git a/src/main/hook/exc/main.c b/src/main/hook/exc/main.c new file mode 100644 index 0000000..54bd09d --- /dev/null +++ b/src/main/hook/exc/main.c @@ -0,0 +1,256 @@ +/** + * Hook library for Pump It Up: Exceed + */ +#define LOG_MODULE "exchook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/asound-fix.h" +#include "hook/patch/gfx.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/patch.h" +#include "util/str.h" +#include "util/sys-info.h" + +#include "eeprom.h" +#include "io.h" +#include "lockchip.h" +#include "mempatch.h" +#include "options.h" + +static void exchook_bootstrapping(int argc, char** argv, struct exchook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!exchook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void exchook_game_version_mempatch_table_init( + struct exchook_options* options, + const struct exchook_mempatch_table** patch_table) +{ + log_assert(options); + log_assert(patch_table); + + if (!options->game.version) { + log_die("No game version specified"); + } else { + log_info("Game version configured: %s", options->game.version); + } + + *patch_table = exchook_mempatch_get_table(options->game.version); + + if (*patch_table == NULL) { + log_die("Unsupported game version %s", options->game.version); + } +} + +static void exchook_patch_fs_init(struct exchook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void exchook_fs_redir_settings(struct exchook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void exchook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); +} + +static void exchook_patch_gfx_init(struct exchook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void exchook_patch_main_loop_init(struct exchook_options* options) +{ + log_assert(options); + + patch_main_loop_init(true, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void exchook_patch_sound_init(struct exchook_options* options, const struct exchook_mempatch_table* patch_table) +{ + log_assert(options); + log_assert(patch_table); + + /* exceed crashes with its default device hw:0. use dmix instead */ + if (strcmp(options->patch.sound.device, "dmix") != 0) { + log_warn("Game might crash with selected sound device %s, use 'dmix' instead if that happens", + options->patch.sound.device); + } + + patch_sound_init(options->patch.sound.device); + patch_asound_fix_init(); + + /* return from StopAllEffects to avoid killing the sound thread */ + util_patch_write_memory_byte(patch_table->addr_stop_all_effects_return, 0xC3); +} + +static void exchook_patch_sigsegv_init(struct exchook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void exchook_patch_lockchip_init(const struct exchook_mempatch_table* patch_table) +{ + log_assert(patch_table); + + exchook_lockchip_init(patch_table); +} + +static void exchook_patch_eeprom_init(struct exchook_options* options, const struct exchook_mempatch_table* patch_table) +{ + log_assert(options); + log_assert(patch_table); + + char* eeprom_path; + + if (options->game.settings) { + char* abs_path = util_fs_get_abs_path(options->game.settings); + + eeprom_path = util_str_merge(abs_path, "/eeprom.bin"); + free(abs_path); + } else { + eeprom_path = util_str_dup("/tmp/eeprom.bin"); + log_warn("No settings path specified to store eeprom data, defaulting to %s", eeprom_path); + } + + exchook_eeprom_init(patch_table, eeprom_path); + free(eeprom_path); +} + +static void exchook_patch_piuio_init(struct exchook_options* options, const struct exchook_mempatch_table* patch_table) +{ + log_assert(options); + log_assert(patch_table); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + exchook_io_init(patch_table, abs_path_iolib, options->patch.piuio.exit_test_serv); + free(abs_path_iolib); + } +} + +void exchook_constructor(void) +{ + /* Nothing here */ +} + +void exchook_destructor(void) +{ + /* Nothing here */ +} + +void exchook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct exchook_options options; + const struct exchook_mempatch_table* mempatch_table; + + exchook_bootstrapping(argc, argv, &options, &game_data_path); + exchook_game_version_mempatch_table_init(&options, &mempatch_table); + exchook_patch_fs_init(&options); + exchook_fs_redir_settings(&options); + exchook_fs_redir_usb(); + exchook_patch_gfx_init(&options); + exchook_patch_main_loop_init(&options); + exchook_patch_sound_init(&options, mempatch_table); + exchook_patch_sigsegv_init(&options); + exchook_patch_lockchip_init(mempatch_table); + exchook_patch_eeprom_init(&options, mempatch_table); + exchook_patch_piuio_init(&options, mempatch_table); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void exchook_trap_after_main(void) +{ + exchook_io_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(exchook_constructor); +LIB_MAIN_DESTRUCTOR(exchook_destructor); +LIB_MAIN_TRAP_MAIN(exchook_trap_before_main, exchook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/exc/mempatch.c b/src/main/hook/exc/mempatch.c new file mode 100644 index 0000000..4f10f2f --- /dev/null +++ b/src/main/hook/exc/mempatch.c @@ -0,0 +1,62 @@ +#include +#include + +#include "hook/exc/mempatch.h" + +#include "util/defs.h" + +static const struct exchook_mempatch_table _exchook_mempatch_table[] = { + { + .version = "20040325", + .addr_eeprom_read = 0x080716E0, + .addr_eeprom_write = 0x08071620, + .addr_io_check = 0x0806DEB0, + .addr_io_light_all = 0x0806E5A0, + .addr_io_light_neon = 0x0806E600, + .addr_io_light_halogan = 0x0806E630, + .addr_io_light_p1_ccfl = 0x0806E660, + .addr_io_light_p2_ccfl = 0x0806E690, + .addr_io_input = 0x0806E6C0, + .addr_io_coin_input = 0x0806EE20, + .addr_io_coin_input_2 = 0x0806EEE0, + .addr_io_coin_counter_2 = 0x0806EFA0, + .addr_io_game_input_stat = 0x080B9410, + .addr_io_game_input_down = 0x080B9408, + .addr_io_game_input_up = 0x080B940C, + .addr_lockchip_dec_chunk = 0x08071E80, + .addr_stop_all_effects_return = 0x0806F6E0, + }, + { + .version = "20040408", + .addr_eeprom_read = 0x08071CC0, + .addr_eeprom_write = 0x08071C00, + .addr_io_check = 0x0806E550, + .addr_io_light_all = 0x0806EC40, + .addr_io_light_neon = 0x0806ECA0, + .addr_io_light_halogan = 0x0806ECD0, + .addr_io_light_p1_ccfl = 0x0806ED00, + .addr_io_light_p2_ccfl = 0x0806ED30, + .addr_io_input = 0x0806ED60, + .addr_io_coin_input = 0x0806F4C0, + .addr_io_coin_input_2 = 0x0806F580, + .addr_io_coin_counter_2 = 0x0806F640, + .addr_io_game_input_stat = 0x080B5290, + .addr_io_game_input_down = 0x080B5288, + .addr_io_game_input_up = 0x080B528C, + .addr_lockchip_dec_chunk = 0x08072460, + .addr_stop_all_effects_return = 0x0806FD80, + }, +}; + +const struct exchook_mempatch_table* exchook_mempatch_get_table(const char* version) +{ + size_t entries = lengthof(_exchook_mempatch_table); + + for (uint32_t i = 0; i < entries; i++) { + if (!strcmp(_exchook_mempatch_table[i].version, version)) { + return &_exchook_mempatch_table[i]; + } + } + + return NULL; +} \ No newline at end of file diff --git a/src/main/hook/exc/mempatch.h b/src/main/hook/exc/mempatch.h new file mode 100644 index 0000000..5926952 --- /dev/null +++ b/src/main/hook/exc/mempatch.h @@ -0,0 +1,29 @@ +#ifndef EXCHOOK_MEMPATCH_H +#define EXCHOOK_MEMPATCH_H + +#include + +struct exchook_mempatch_table { + const char* version; + uintptr_t addr_eeprom_read; + uintptr_t addr_eeprom_write; + uintptr_t addr_io_check; + uintptr_t addr_io_light_all; + uintptr_t addr_io_light_neon; + uintptr_t addr_io_light_halogan; + uintptr_t addr_io_light_p1_ccfl; + uintptr_t addr_io_light_p2_ccfl; + uintptr_t addr_io_input; + uintptr_t addr_io_coin_input; + uintptr_t addr_io_coin_input_2; + uintptr_t addr_io_coin_counter_2; + uintptr_t addr_io_game_input_stat; + uintptr_t addr_io_game_input_down; + uintptr_t addr_io_game_input_up; + uintptr_t addr_lockchip_dec_chunk; + uintptr_t addr_stop_all_effects_return; +}; + +const struct exchook_mempatch_table* exchook_mempatch_get_table(const char* version); + +#endif \ No newline at end of file diff --git a/src/main/hook/exc/options.c b/src/main/hook/exc/options.c new file mode 100644 index 0000000..e25cbdd --- /dev/null +++ b/src/main/hook/exc/options.c @@ -0,0 +1,184 @@ +#include "hook/exc/options.h" + +#include "util/options.h" + +#define EXCHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define EXCHOOK_OPTIONS_STR_GAME_VERSION "game.version" +#define EXCHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define EXCHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define EXCHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +static const struct util_options_def exchook_options_def[] = { + { + .name = EXCHOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = EXCHOOK_OPTIONS_STR_GAME_VERSION, + .description = "Version of the game (format YYYYMMDD). Make sure this matches exactly the version you are" + " running because the hook lib has to apply memory patches.", + .param = 'v', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "20040408", + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +static const struct util_options_defs exchook_options_defs = { + .usage_header = + "Pumptools exchook for Pump It Up: Exceed, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./exchook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = exchook_options_def, + .ndefs = lengthof(exchook_options_def) +}; + +bool exchook_options_init(int argc, char** argv, struct exchook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&exchook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.settings = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_GAME_SETTINGS); + options->game.version = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_GAME_VERSION); + options->patch.gfx.windowed = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.piuio.api_lib = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, EXCHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, EXCHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/exc/options.h b/src/main/hook/exc/options.h new file mode 100644 index 0000000..c16ead8 --- /dev/null +++ b/src/main/hook/exc/options.h @@ -0,0 +1,55 @@ +#ifndef EXCHOOK_OPTIONS_H +#define EXCHOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct exchook_options { + struct game { + const char* settings; + const char* version; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool exchook_options_init(int argc, char** argv, struct exchook_options* options); + +#endif diff --git a/src/main/hook/f2/f2hook.hasp.key b/src/main/hook/f2/f2hook.hasp.key new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/f2/f2hook.hasp.key @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/f2/f2hook.hdd.raw b/src/main/hook/f2/f2hook.hdd.raw new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/f2/f2hook.hdd.raw @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/f2/main.c b/src/main/hook/f2/main.c new file mode 100644 index 0000000..ed144c8 --- /dev/null +++ b/src/main/hook/f2/main.c @@ -0,0 +1,244 @@ +/** + * Hook library for Pump It Up: Fiesta 2 + */ +#define LOG_MODULE "f2hook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/amixer-block.h" +#include "hook/patch/gfx.h" +#include "hook/patch/hasp.h" +#include "hook/patch/hdd-check.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/mounts.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" +#include "hook/patch/usb-mnt.h" +#include "hook/patch/usbfs.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/sys-info.h" + +#include "options.h" + +/* Compiled binary data from data folder. Symbols are defined by compiler */ +extern const uint8_t _binary_f2hook_hdd_raw_start[]; +extern const uint8_t _binary_f2hook_hdd_raw_end[]; +extern const uint8_t _binary_f2hook_hasp_key_start[]; +extern const uint8_t _binary_f2hook_hasp_key_end[]; + +static void f2hook_bootstrapping(int argc, char** argv, struct f2hook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!f2hook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + hook_core_piu_utils_fix_locale(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void f2hook_patch_fs_init(struct f2hook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void f2hook_fs_redir_settings(struct f2hook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void f2hook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); + patch_usbfs_init(); +} + +static void f2hook_fs_redirs_init(struct f2hook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + f2hook_fs_redir_settings(options); + + f2hook_fs_redir_usb(); +} + +static void f2hook_patch_fs_mounting_init() +{ + patch_mounts_init(); + + patch_usb_mnt_init(); +} + +static void f2hook_patch_gfx_init(struct f2hook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void f2hook_patch_main_loop_init(struct f2hook_options* options) +{ + log_assert(options); + + patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void f2hook_patch_sound_init(struct f2hook_options* options) +{ + log_assert(options); + + patch_sound_init(options->patch.sound.device); + patch_axmier_block_init(); +} + +static void f2hook_patch_sigsegv_init(struct f2hook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void f2hook_patch_dongle_init() +{ + patch_hasp_init((const uint8_t*) _binary_f2hook_hasp_key_start, + ((uintptr_t) &_binary_f2hook_hasp_key_end - (uintptr_t) &_binary_f2hook_hasp_key_start)); +} + +static void f2hook_patch_hdd_check_init() +{ + patch_hdd_check_init((const uint8_t*) _binary_f2hook_hdd_raw_start, + ((uintptr_t) &_binary_f2hook_hdd_raw_end - (uintptr_t) &_binary_f2hook_hdd_raw_start)); +} + +static void f2hook_patch_piuio_init(struct f2hook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +void f2hook_constructor(void) +{ + /* Nothing here */ +} + +void f2hook_destructor(void) +{ + /* Nothing here */ +} + +void f2hook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct f2hook_options options; + + f2hook_bootstrapping(argc, argv, &options, &game_data_path); + f2hook_patch_fs_init(&options); + f2hook_fs_redirs_init(&options, game_data_path); + f2hook_patch_fs_mounting_init(); + f2hook_patch_gfx_init(&options); + f2hook_patch_main_loop_init(&options); + f2hook_patch_sound_init(&options); + f2hook_patch_sigsegv_init(&options); + f2hook_patch_dongle_init(); + f2hook_patch_hdd_check_init(); + f2hook_patch_piuio_init(&options); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void f2hook_trap_after_main(void) +{ + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(f2hook_constructor); +LIB_MAIN_DESTRUCTOR(f2hook_destructor); +LIB_MAIN_TRAP_MAIN(f2hook_trap_before_main, f2hook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/f2/options.c b/src/main/hook/f2/options.c new file mode 100644 index 0000000..0e9995c --- /dev/null +++ b/src/main/hook/f2/options.c @@ -0,0 +1,195 @@ +#include "hook/f2/options.h" + +#include "util/options.h" + +#define F2HOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define F2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server" +#define F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id" +#define F2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define F2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define F2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define F2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +const struct util_options_def f2hook_options_def[] = { + { + .name = F2HOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER, + .description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)", + .param = 'n', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID, + .description = "Machine id for pumpnet", + .param = 'm', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "0", + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +const struct util_options_defs f2hook_options_defs = { + .usage_header = + "Pumptools f2hook for Pump It Up: Fiesta 2, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./f2hook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = f2hook_options_def, + .ndefs = lengthof(f2hook_options_def) +}; + +bool f2hook_options_init(int argc, char** argv, struct f2hook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&f2hook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.settings = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.net.server = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER); + options->patch.net.machine_id = strtoul( + util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID), + NULL, + 16); + options->patch.piuio.api_lib = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, F2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, F2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/f2/options.h b/src/main/hook/f2/options.h new file mode 100644 index 0000000..65c03d9 --- /dev/null +++ b/src/main/hook/f2/options.h @@ -0,0 +1,59 @@ +#ifndef F2HOOK_OPTIONS_H +#define F2HOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct f2hook_options { + struct game { + const char* settings; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct net { + const char* server; + uint32_t machine_id; + } net; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool f2hook_options_init(int argc, char** argv, struct f2hook_options* options); + +#endif diff --git a/src/main/hook/fex/fexhook.dog.key b/src/main/hook/fex/fexhook.dog.key new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/fex/fexhook.dog.key @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/fex/fexhook.hdd.raw b/src/main/hook/fex/fexhook.hdd.raw new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/fex/fexhook.hdd.raw @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/fex/main.c b/src/main/hook/fex/main.c new file mode 100644 index 0000000..25cde24 --- /dev/null +++ b/src/main/hook/fex/main.c @@ -0,0 +1,242 @@ +/** + * Hook library for Pump It Up: FiestaEX + */ +#define LOG_MODULE "fexhook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/amixer-block.h" +#include "hook/patch/gfx.h" +#include "hook/patch/hdd-check.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/microdog40.h" +#include "hook/patch/mounts.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" +#include "hook/patch/usb-mnt.h" +#include "hook/patch/usbfs.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/sys-info.h" + +#include "options.h" + +/* Compiled binary data from data folder. Symbols are defined by compiler */ +extern const uint8_t _binary_fexhook_hdd_raw_start[]; +extern const uint8_t _binary_fexhook_hdd_raw_end[]; +extern const uint8_t _binary_fexhook_dog_key_start[]; +extern const uint8_t _binary_fexhook_dog_key_end[]; + +static void fexhook_bootstrapping(int argc, char** argv, struct fexhook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!fexhook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void fexhook_patch_fs_init(struct fexhook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void fexhook_fs_redir_settings(struct fexhook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void fexhook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); + patch_usbfs_init(); +} + +static void fexhook_fs_redirs_init(struct fexhook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + fexhook_fs_redir_settings(options); + + fexhook_fs_redir_usb(); +} + +static void fexhook_patch_fs_mounting_init() +{ + patch_mounts_init(); + + patch_usb_mnt_init(); +} + +static void fexhook_patch_gfx_init(struct fexhook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void fexhook_patch_main_loop_init(struct fexhook_options* options) +{ + log_assert(options); + + patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void fexhook_patch_sound_init(struct fexhook_options* options) +{ + log_assert(options); + + patch_sound_init(options->patch.sound.device); + patch_axmier_block_init(); +} + +static void fexhook_patch_sigsegv_init(struct fexhook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void fexhook_patch_dongle_init() +{ + patch_microdog40_init((const uint8_t*) _binary_fexhook_dog_key_start, + ((uintptr_t) &_binary_fexhook_dog_key_end - (uintptr_t) &_binary_fexhook_dog_key_start)); +} + +static void fexhook_patch_hdd_check_init() +{ + patch_hdd_check_init((const uint8_t*) _binary_fexhook_hdd_raw_start, + ((uintptr_t) &_binary_fexhook_hdd_raw_end - (uintptr_t) &_binary_fexhook_hdd_raw_start)); +} + +static void fexhook_patch_piuio_init(struct fexhook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +void fexhook_constructor(void) +{ + /* Nothing here */ +} + +void fexhook_destructor(void) +{ + /* Nothing here */ +} + +void fexhook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct fexhook_options options; + + fexhook_bootstrapping(argc, argv, &options, &game_data_path); + fexhook_patch_fs_init(&options); + fexhook_fs_redirs_init(&options, game_data_path); + fexhook_patch_fs_mounting_init(); + fexhook_patch_gfx_init(&options); + fexhook_patch_main_loop_init(&options); + fexhook_patch_sound_init(&options); + fexhook_patch_sigsegv_init(&options); + fexhook_patch_dongle_init(); + fexhook_patch_hdd_check_init(); + fexhook_patch_piuio_init(&options); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void fexhook_trap_after_main(void) +{ + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(fexhook_constructor); +LIB_MAIN_DESTRUCTOR(fexhook_destructor); +LIB_MAIN_TRAP_MAIN(fexhook_trap_before_main, fexhook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/fex/options.c b/src/main/hook/fex/options.c new file mode 100644 index 0000000..525b8e1 --- /dev/null +++ b/src/main/hook/fex/options.c @@ -0,0 +1,195 @@ +#include "hook/fex/options.h" + +#include "util/options.h" + +#define FEXHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define FEXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server" +#define FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id" +#define FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define FEXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define FEXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +const struct util_options_def fexhook_options_def[] = { + { + .name = FEXHOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER, + .description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)", + .param = 'n', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID, + .description = "Machine id for pumpnet", + .param = 'm', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "0", + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +const struct util_options_defs fexhook_options_defs = { + .usage_header = + "Pumptools fexhook for Pump It Up: FiestaEX, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./fexhook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = fexhook_options_def, + .ndefs = lengthof(fexhook_options_def) +}; + +bool fexhook_options_init(int argc, char** argv, struct fexhook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&fexhook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.settings = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.net.server = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER); + options->patch.net.machine_id = strtoul( + util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID), + NULL, + 16); + options->patch.piuio.api_lib = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, FEXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, FEXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/fex/options.h b/src/main/hook/fex/options.h new file mode 100644 index 0000000..1d97a29 --- /dev/null +++ b/src/main/hook/fex/options.h @@ -0,0 +1,59 @@ +#ifndef FEXHOOK_OPTIONS_H +#define FEXHOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct fexhook_options { + struct game { + const char* settings; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct net { + const char* server; + uint32_t machine_id; + } net; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool fexhook_options_init(int argc, char** argv, struct fexhook_options* options); + +#endif diff --git a/src/main/hook/fst/fsthook.dog.key b/src/main/hook/fst/fsthook.dog.key new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/fst/fsthook.dog.key @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/fst/fsthook.hdd.raw b/src/main/hook/fst/fsthook.hdd.raw new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/fst/fsthook.hdd.raw @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/fst/main.c b/src/main/hook/fst/main.c new file mode 100644 index 0000000..389328c --- /dev/null +++ b/src/main/hook/fst/main.c @@ -0,0 +1,242 @@ +/** + * Hook library for Pump It Up: Fiesta + */ +#define LOG_MODULE "fsthook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/amixer-block.h" +#include "hook/patch/gfx.h" +#include "hook/patch/hdd-check.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/microdog40.h" +#include "hook/patch/mounts.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" +#include "hook/patch/usb-mnt.h" +#include "hook/patch/usbfs.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/sys-info.h" + +#include "options.h" + +/* Compiled binary data from data folder. Symbols are defined by compiler */ +extern const uint8_t _binary_fsthook_hdd_raw_start[]; +extern const uint8_t _binary_fsthook_hdd_raw_end[]; +extern const uint8_t _binary_fsthook_dog_key_start[]; +extern const uint8_t _binary_fsthook_dog_key_end[]; + +static void fsthook_bootstrapping(int argc, char** argv, struct fsthook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!fsthook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void fsthook_patch_fs_init(struct fsthook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void fsthook_fs_redir_settings(struct fsthook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void fsthook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); + patch_usbfs_init(); +} + +static void fsthook_fs_redirs_init(struct fsthook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + fsthook_fs_redir_settings(options); + + fsthook_fs_redir_usb(); +} + +static void fsthook_patch_fs_mounting_init() +{ + patch_mounts_init(); + + patch_usb_mnt_init(); +} + +static void fsthook_patch_gfx_init(struct fsthook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void fsthook_patch_main_loop_init(struct fsthook_options* options) +{ + log_assert(options); + + patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void fsthook_patch_sound_init(struct fsthook_options* options) +{ + log_assert(options); + + patch_sound_init(options->patch.sound.device); + patch_axmier_block_init(); +} + +static void fsthook_patch_sigsegv_init(struct fsthook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void fsthook_patch_dongle_init() +{ + patch_microdog40_init((const uint8_t*) _binary_fsthook_dog_key_start, + ((uintptr_t) &_binary_fsthook_dog_key_end - (uintptr_t) &_binary_fsthook_dog_key_start)); +} + +static void fsthook_patch_hdd_check_init() +{ + patch_hdd_check_init((const uint8_t*) _binary_fsthook_hdd_raw_start, + ((uintptr_t) &_binary_fsthook_hdd_raw_end - (uintptr_t) &_binary_fsthook_hdd_raw_start)); +} + +static void fsthook_patch_piuio_init(struct fsthook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +void fsthook_constructor(void) +{ + /* Nothing here */ +} + +void fsthook_destructor(void) +{ + /* Nothing here */ +} + +void fsthook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct fsthook_options options; + + fsthook_bootstrapping(argc, argv, &options, &game_data_path); + fsthook_patch_fs_init(&options); + fsthook_fs_redirs_init(&options, game_data_path); + fsthook_patch_fs_mounting_init(); + fsthook_patch_gfx_init(&options); + fsthook_patch_main_loop_init(&options); + fsthook_patch_sound_init(&options); + fsthook_patch_sigsegv_init(&options); + fsthook_patch_dongle_init(); + fsthook_patch_hdd_check_init(); + fsthook_patch_piuio_init(&options); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void fsthook_trap_after_main(void) +{ + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(fsthook_constructor); +LIB_MAIN_DESTRUCTOR(fsthook_destructor); +LIB_MAIN_TRAP_MAIN(fsthook_trap_before_main, fsthook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/fst/options.c b/src/main/hook/fst/options.c new file mode 100644 index 0000000..5343c73 --- /dev/null +++ b/src/main/hook/fst/options.c @@ -0,0 +1,195 @@ +#include "hook/fst/options.h" + +#include "util/options.h" + +#define FSTHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define FSTHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server" +#define FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id" +#define FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define FSTHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define FSTHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +const struct util_options_def fsthook_options_def[] = { + { + .name = FSTHOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER, + .description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)", + .param = 'n', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID, + .description = "Machine id for pumpnet", + .param = 'm', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "0", + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +const struct util_options_defs fsthook_options_defs = { + .usage_header = + "Pumptools fsthook for Pump It Up: Fiesta, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./fsthook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = fsthook_options_def, + .ndefs = lengthof(fsthook_options_def) +}; + +bool fsthook_options_init(int argc, char** argv, struct fsthook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&fsthook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.settings = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.net.server = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER); + options->patch.net.machine_id = strtoul( + util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID), + NULL, + 16); + options->patch.piuio.api_lib = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, FSTHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, FSTHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/fst/options.h b/src/main/hook/fst/options.h new file mode 100644 index 0000000..c4bde17 --- /dev/null +++ b/src/main/hook/fst/options.h @@ -0,0 +1,59 @@ +#ifndef FSTHOOK_OPTIONS_H +#define FSTHOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct fsthook_options { + struct game { + const char* settings; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct net { + const char* server; + uint32_t machine_id; + } net; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool fsthook_options_init(int argc, char** argv, struct fsthook_options* options); + +#endif diff --git a/src/main/hook/mk3/1st-2nd-fs-fix.c b/src/main/hook/mk3/1st-2nd-fs-fix.c new file mode 100644 index 0000000..4ac50fe --- /dev/null +++ b/src/main/hook/mk3/1st-2nd-fs-fix.c @@ -0,0 +1,41 @@ +#define LOG_MODULE "mk3hook-1st-2nd-fs-fix" + +#include + +#include "capnhook/hook/lib.h" + +#include "util/log.h" +#include "util/str.h" + +typedef int (*cnh_fshook_chdir_t)(const char *path); +static cnh_fshook_chdir_t mk3hook_1st_2nd_fs_fix_real_chdir; + +static char* mk3hook_1st_2nd_fs_fix_path_game_dir_abs; + +int chdir(const char *path) +{ + if (!mk3hook_1st_2nd_fs_fix_real_chdir) { + mk3hook_1st_2nd_fs_fix_real_chdir = (cnh_fshook_chdir_t) cnh_lib_get_func_addr("chdir"); + } + + // This seems to be some kind of probe the game is doing to determine something...I assume which folder layout to + // use? When we change directory to our "game" dir which contains all stuff at that point, everything's fine. + // If we do not do that, the game will error not being able to find any files. When loading files, the code also + // does some odd iterating the file system starting at / at that point. I assume that this is some sort of search + // trying to look for the file on the filesystem. However, that breaks a lot of other stuf with the hooking code + // and therefore should not be triggered. + if (mk3hook_1st_2nd_fs_fix_path_game_dir_abs && !strcmp(path, "/datas/bga")) { + log_debug("Trapping /datas/bga chdir, chdir to %s", mk3hook_1st_2nd_fs_fix_path_game_dir_abs); + + return mk3hook_1st_2nd_fs_fix_real_chdir(mk3hook_1st_2nd_fs_fix_path_game_dir_abs); + } else { + return mk3hook_1st_2nd_fs_fix_real_chdir(path); + } +} + +void mk3hook_1st_2nd_fs_fix_init(const char* path_game_dir_abs) +{ + mk3hook_1st_2nd_fs_fix_path_game_dir_abs = util_str_dup(path_game_dir_abs); + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/mk3/1st-2nd-fs-fix.h b/src/main/hook/mk3/1st-2nd-fs-fix.h new file mode 100644 index 0000000..5a8d429 --- /dev/null +++ b/src/main/hook/mk3/1st-2nd-fs-fix.h @@ -0,0 +1,14 @@ +/** + * Fixes an issue with the engine of 1st and 2nd when redirecting all game data to one game folder, e.g. ./game/. + */ +#ifndef MK3HOOK_1ST_2ND_FS_FIX_H +#define MK3HOOK_1ST_2ND_FS_FIX_H + +/** + * Initialize the module. + * + * @param path_game_dir_abs The absolute path to the "game" dir containing all game assets. + */ +void mk3hook_1st_2nd_fs_fix_init(const char* path_game_dir_abs); + +#endif diff --git a/src/main/hook/mk3/config.c b/src/main/hook/mk3/config.c new file mode 100644 index 0000000..8b77efc --- /dev/null +++ b/src/main/hook/mk3/config.c @@ -0,0 +1,136 @@ +#define LOG_MODULE "mk3hook-config" + +#include + +#include "hook/mk3/options.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/str.h" + +static int mk3hook_config_sync_offset; +static double mk3hook_config_sync_multiplier; +static double mk3hook_config_music_volume; +static double mk3hook_config_sfx_volume; +static int mk3hook_config_fullscreen; +static int mk3hook_config_allow_exit; +static const char* mk3hook_config_save_file; + +int config_read_file(config_t *config, const char *filename) +{ + // Just stub file loading + log_debug("config_read_file: %s", filename); + + return 1; +} + +int config_lookup_int(const config_t *config, const char *path, int *value) +{ + log_debug("config_lookup_int: %s", path); + + if (!strcmp(path, "sync_offset")) { + *value = mk3hook_config_sync_offset; + } else { + log_error("Unsupported int entry read: %s", path); + } + + return 1; +} + +int config_lookup_float(const config_t *config, const char *path, double *value) +{ + log_debug("config_lookup_float: %s", path); + + if (!strcmp(path, "sync_multiplier")) { + *value = mk3hook_config_sync_multiplier; + } else if (!strcmp(path, "music_volume")) { + *value = mk3hook_config_music_volume; + } else if (!strcmp(path, "sfx_volume")) { + *value = mk3hook_config_sfx_volume; + } else { + log_error("Unsupported float entry read: %s", path); + } + + return 1; +} + +int config_lookup_bool(const config_t *config, const char *path, int *value) +{ + log_debug("config_lookup_bool: %s", path); + + if (!strcmp(path, "fullscreen")) { + *value = mk3hook_config_fullscreen; + } else if (!strcmp(path, "allow_exit")) { + *value = mk3hook_config_allow_exit; + } else { + log_error("Unsupported bool entry read: %s", path); + } + + return 1; +} + +int config_lookup_string(const config_t *config, const char *path, const char **value) +{ + log_debug("config_lookup_string: %s", path); + + if (!strcmp(path, "save_file")) { + // Note: This is fine as the size of the string pointed to by the returned address is not relevant for any + // operations using them, e.g. there is no merging/copying with limited stack'd buffer sizes involved like + // below. + *value = mk3hook_config_save_file; + } else if (!strcmp(path, "effect_path")) { + // Needs to be hardcoded because the stack buffer for this is only 128 bytes large. This is likely fine for most + // paths and even when resolving relative paths to absolute ones. Still, this is a risk for a buffer overflow. + // Utilize the redirect module in the main lib hook function to redirect this to something like a local game dir + // and resolve this to an absolute path + *value = "EFFECTS/"; + } else if (!strcmp(path, "track_path")) { + // Needs to be hardcoded because the stack buffer for this is only 32 bytes large. When combining it with the + // "Track%02i.mp3" part, the buffer will overflow when this path is longer or even resolved to an absolute path. + // Utilize the redirect module in the main lib hook function to redirect this to something like a local game dir + // and resolve this to an absolute path + *value = "track/"; + } else { + log_error("Unsupported string entry read: %s", path); + } + + return 1; +} + +static void mk3hook_config_assign_save_file(const struct mk3hook_options* options) +{ + const char* folder = options->game.settings; + + if (!util_fs_path_exists(folder)) { + log_info("Settings path %s does not exist, creating", folder); + + if (!util_fs_mkdir(folder)) { + log_error("Creating directory %s failed", folder); + } + } + + char* folder_abs = util_fs_get_abs_path(folder); + + if (!folder_abs) { + log_error("Failed to resolve absolute path for settings folder"); + return; + } + + // Don't free this one after assigning the result value as this will still be read from. Keep memory leak. + mk3hook_config_save_file = util_str_merge(folder_abs, "/EEPROM.BIN"); + free(folder_abs); +} + +void mk3hook_config_init(const struct mk3hook_options* options) +{ + mk3hook_config_sync_offset = options->game.sync_offset; + mk3hook_config_sync_multiplier = options->game.sync_multiplier; + mk3hook_config_music_volume = options->game.music_vol; + mk3hook_config_sfx_volume = options->game.sfx_vol; + mk3hook_config_fullscreen = !options->patch.gfx.windowed; + mk3hook_config_allow_exit = options->patch.piuio.exit_test_serv; + + mk3hook_config_assign_save_file(options); + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/mk3/config.h b/src/main/hook/mk3/config.h new file mode 100644 index 0000000..ad4e9d8 --- /dev/null +++ b/src/main/hook/mk3/config.h @@ -0,0 +1,17 @@ +/** + * Patch the configuration file handling which uses libconfig. This hooks libconfig up to pumptools configuration + * infrastructure. + */ +#ifndef MK3HOOK_CONFIG_H +#define MK3HOOK_CONFIG_H + +#include "options.h" + +/** + * Initialize this module. + * + * @param options The options provided to the hook which are injected into libconfig config value reading. + */ +void mk3hook_config_init(const struct mk3hook_options* options); + +#endif diff --git a/src/main/hook/mk3/fmodex.c b/src/main/hook/mk3/fmodex.c new file mode 100644 index 0000000..1d6e58c --- /dev/null +++ b/src/main/hook/mk3/fmodex.c @@ -0,0 +1,126 @@ +#define LOG_MODULE "mk3hook-fmodex" + +#include +#include + +#include "capnhook/hook/lib.h" + +#include "hook/mk3/fmodex.h" + +#include "util/log.h" +#include "util/str.h" + +typedef FMOD_RESULT (*FMOD_System_Init_t)(FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata); + +static FMOD_System_Init_t mk3hook_fmodex_real_FMOD_System_Init; + +static bool mk3hook_fmodex_debug_output; +static const char* mk3hook_fmodex_sound_device; + +FMOD_RESULT FMOD_System_Init(FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata) +{ + if (!mk3hook_fmodex_real_FMOD_System_Init) { + mk3hook_fmodex_real_FMOD_System_Init = (FMOD_System_Init_t) cnh_lib_get_func_addr("FMOD_System_Init"); + } + + FMOD_RESULT res; + + unsigned int version; + + res = FMOD_System_GetVersion(system, &version); + + if (res != FMOD_OK) { + log_error("FMOD_System_GetVersion failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res)); + return res; + } + + log_info("fmodex version: %d.%d.%d", (version >> 16) & 0xFFFF, (version >> 8) & 0xFF, version & 0xFF); + + if (mk3hook_fmodex_debug_output) { + res = FMOD_Debug_SetLevel(FMOD_DEBUG_ALL); + + if (res != FMOD_OK) { + log_error("FMOD_Debug_SetLevel failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res)); + return res; + } + } + + res = FMOD_System_SetOutput(system, FMOD_OUTPUTTYPE_ALSA); + + if (res != FMOD_OK) { + log_error("FMOD_System_SetOutput failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res)); + return res; + } + + FMOD_OUTPUTTYPE output_type; + + res = FMOD_System_GetOutput(system, &output_type); + + if (res != FMOD_OK) { + log_error("FMOD_System_GetOutput failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res)); + return res; + } + + log_info("Output type: %d", output_type); + + int num_drivers; + res = FMOD_System_GetNumDrivers(system, &num_drivers); + + if (res != FMOD_OK) { + log_error("FMOD_System_GetNumDrivers failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res)); + return res; + } + + log_info("Num available drivers %d", num_drivers); + + char buffer[4096]; + FMOD_GUID guid; + int selected_driver = -1; + + for (int i = 0; i < num_drivers; i++) { + res = FMOD_System_GetDriverInfo(system, i, buffer, sizeof(buffer), &guid); + + if (res != FMOD_OK) { + log_error("FMOD_System_GetDriverInfo failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res)); + break; + } + + log_info("Driver %d: %s", i, buffer); + + if (mk3hook_fmodex_sound_device && !strcmp(buffer, mk3hook_fmodex_sound_device)) { + selected_driver = i; + } + } + + if (selected_driver == -1) { + log_info("Selecting default driver (0) because either the selected driver was not found or no driver was " + "selected"); + selected_driver = 0; + } else { + log_info("Found selected driver %s, id %d", mk3hook_fmodex_sound_device, selected_driver); + } + + res = FMOD_System_SetDriver(system, selected_driver); + + if (res != FMOD_OK) { + log_error("FMOD_System_SetDriver failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res)); + return res; + } + + res = FMOD_System_SetSpeakerMode(system, FMOD_SPEAKERMODE_STEREO); + + if (res != FMOD_OK) { + log_error("FMOD_System_SetSpeakerMode failed %d: %s", res, FMOD_ErrorString((FMOD_RESULT) res)); + return res; + } + + return mk3hook_fmodex_real_FMOD_System_Init(system, maxchannels, flags, extradriverdata); +} + +void mk3hook_fmodex_init(bool debug_output, const char* sound_device) +{ + mk3hook_fmodex_debug_output = debug_output; + mk3hook_fmodex_sound_device = sound_device; + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/mk3/fmodex.h b/src/main/hook/mk3/fmodex.h new file mode 100644 index 0000000..e623a5e --- /dev/null +++ b/src/main/hook/mk3/fmodex.h @@ -0,0 +1,19 @@ +/** + * This patch module fixes a bunch of issues with fmodex which is used by the MK3 Linux ports to re-implement + * the sound hardware in software. + */ +#ifndef MK3HOOK_FMODEX_H +#define MK3HOOK_FMODEX_H + +#include + +/** + * Initialize this patch module. + * + * @param debug_output True to enable fmodex's debug output which requires the usage of fmodexL.so instead of fmodex.so. + * @param sound_device Provide a sound device to pick from the list of devices available with fmodex. NULL to go with + * with default device selected automatically by fmodex. + */ +void mk3hook_fmodex_init(bool debug_output, const char* sound_device); + +#endif diff --git a/src/main/hook/mk3/main.c b/src/main/hook/mk3/main.c new file mode 100644 index 0000000..dbd4cc9 --- /dev/null +++ b/src/main/hook/mk3/main.c @@ -0,0 +1,251 @@ +/** + * Hook library for MK3 linux ports of Pump It Up. + */ +#define LOG_MODULE "mk3hook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" +#include "hook/mk3/1st-2nd-fs-fix.h" +#include "hook/mk3/config.h" +#include "hook/mk3/fmodex.h" +#include "hook/mk3/options.h" +#include "hook/patch/asound-fix.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/log.h" +#include "util/patch.h" +#include "util/proc.h" +#include "util/str.h" +#include "util/sys-info.h" + +static void mk3hook_bootstrapping(int argc, char** argv, struct mk3hook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (!mk3hook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_init(argc, argv); + + hook_core_piu_log_info(argc, argv); + + // Have different arguments to align with newer pump games + // argv[1]: game folder path + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void mk3hook_patch_config_init(struct mk3hook_options* options) +{ + mk3hook_config_init(options); +} + +static void mk3hook_patch_fs_init(struct mk3hook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void mk3hook_1st_2nd_fs_redirs(const char* game_data_path_abs) +{ + log_assert(game_data_path_abs); + + // The "datas" directory does not require a redirect as the game uses chdir to move to that folder. For this, the + // additional patch module "1st-2nd-fs-fix" is required (see below). All files usually local to the piu executable, + // which are outside of the "datas" folder, have to be redirected. + cnh_redir_add("INTRO.ANI", util_str_merge(game_data_path_abs, "/INTRO.ANI")); + cnh_redir_add("intro.ani", util_str_merge(game_data_path_abs, "/INTRO.ANI")); + cnh_redir_add("spr.res", util_str_merge(game_data_path_abs, "/SPR.RES")); + cnh_redir_add("SPR.RES", util_str_merge(game_data_path_abs, "/SPR.RES")); + cnh_redir_add("piu.bin", util_str_merge(game_data_path_abs, "/PIU.BIN")); + cnh_redir_add("PIU.BIN", util_str_merge(game_data_path_abs, "/PIU.BIN")); + cnh_redir_add("stage.cfg", util_str_merge(game_data_path_abs, "/STAGE.CFG")); + cnh_redir_add("STAGE.CFG", util_str_merge(game_data_path_abs, "/STAGE.CFG")); + + // For redirecting additional files for linux ports. Configurable via MK3 linux ports config.cfg but causes stack + // overflow on long paths. Redirecting the paths afterwards is save. + cnh_redir_add("EFFECTS", util_str_merge(game_data_path_abs, "/EFFECTS/")); + cnh_redir_add("track", util_str_merge(game_data_path_abs, "/track/")); + + mk3hook_1st_2nd_fs_fix_init(game_data_path_abs); +} + +static void mk3hook_3rd_and_up_fs_redirs(const char* game_data_path_abs) +{ + log_assert(game_data_path_abs); + + // Sometimes, the game decides to read stuff using lower case, sometimes upper case *shrug* + // Also, we don't are about cleaning up the resulting merged strings here. Have some memory leaks! + cnh_redir_add(".", util_str_merge(game_data_path_abs, "/")); + cnh_redir_add("audio", util_str_merge(game_data_path_abs, "/AUDIO")); + cnh_redir_add("AUDIO", util_str_merge(game_data_path_abs, "/AUDIO")); + cnh_redir_add("bga", util_str_merge(game_data_path_abs, "/BGA")); + cnh_redir_add("BGA", util_str_merge(game_data_path_abs, "/BGA")); + cnh_redir_add("step", util_str_merge(game_data_path_abs, "/STEP")); + cnh_redir_add("STEP", util_str_merge(game_data_path_abs, "/STEP")); + cnh_redir_add("title", util_str_merge(game_data_path_abs, "/TITLE")); + cnh_redir_add("TITLE", util_str_merge(game_data_path_abs, "/TITLE")); + cnh_redir_add("piu.bin", util_str_merge(game_data_path_abs, "/PIU.BIN")); + cnh_redir_add("PIU.BIN", util_str_merge(game_data_path_abs, "/PIU.BIN")); + cnh_redir_add("stage.cfg", util_str_merge(game_data_path_abs, "/STAGE.CFG")); + cnh_redir_add("STAGE.CFG", util_str_merge(game_data_path_abs, "/STAGE.CFG")); + // Special treatment for extra because keeping the name stage.cfg was not good enough... + cnh_redir_add("game.cfg", util_str_merge(game_data_path_abs, "/GAME.CFG")); + cnh_redir_add("GAME.CFG", util_str_merge(game_data_path_abs, "/GAME.CFG")); + + // For redirecting additional files for linux ports. Configurable via MK3 linux ports config.cfg but causes stack + // overflow on long paths. Redirecting the paths afterwards is save. + cnh_redir_add("EFFECTS", util_str_merge(game_data_path_abs, "/EFFECTS/")); + cnh_redir_add("track", util_str_merge(game_data_path_abs, "/track/")); +} + +static void mk3hook_patch_1st_and_2nd_fs_init(struct mk3hook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* game_data_path_abs = util_fs_get_abs_path(game_data_path); + + if (options->game.fs_1st_2nd) { + mk3hook_1st_2nd_fs_redirs(game_data_path_abs); + } else { + mk3hook_3rd_and_up_fs_redirs(game_data_path_abs); + } + + free(game_data_path_abs); +} + +static void mk3hook_patch_main_loop_init(struct mk3hook_options* options) +{ + log_assert(options); + + patch_main_loop_init(true, options->patch.main_loop.disable_built_in_inputs, true); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void mk3hook_patch_sound_init(struct mk3hook_options* options) +{ + log_assert(options); + + patch_asound_fix_init(); + mk3hook_fmodex_init(options->patch.sound.debug_output, options->patch.sound.device); +} + +static void mk3hook_patch_sigsegv_init(struct mk3hook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void mk3hook_patch_piuio_init(struct mk3hook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +static void mk3hook_fix_argv(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + // Game exec main expects the following arguments + // argv[1]: cfg1 path + // argv[2]: cfg2 path (optional) + // Config loading is stubbed, so just provide something + + argv[1] = "./save/config.cfg"; + + if (argc > 2) { + argv[2] = "./save/config.cfg"; + } +} + +void mk3hook_constructor(void) +{ + /* Nothing here */ +} + +void mk3hook_destructor(void) +{ + /* Nothing here */ +} + +void mk3hook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + struct mk3hook_options options; + char* game_data_path; + + mk3hook_bootstrapping(argc, argv, &options, &game_data_path); + mk3hook_patch_config_init(&options); + mk3hook_patch_fs_init(&options); + mk3hook_patch_1st_and_2nd_fs_init(&options, game_data_path); + mk3hook_patch_main_loop_init(&options); + mk3hook_patch_sound_init(&options); + mk3hook_patch_sigsegv_init(&options); + mk3hook_patch_piuio_init(&options); + mk3hook_fix_argv(argc, argv); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void mk3hook_trap_after_main(void) +{ + +} + +LIB_MAIN_CONSTRUCTOR(mk3hook_constructor); +LIB_MAIN_DESTRUCTOR(mk3hook_destructor); +LIB_MAIN_TRAP_MAIN(mk3hook_trap_before_main, mk3hook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/mk3/ogl-fix.c b/src/main/hook/mk3/ogl-fix.c new file mode 100644 index 0000000..ec03789 --- /dev/null +++ b/src/main/hook/mk3/ogl-fix.c @@ -0,0 +1,37 @@ +/** + * Module to fix weird OpenGL error that only appears on all MK3 linux ports. + * + * If OpenGL is not forced into single threaded mode, the backend will run in + * multi-threaded mode for some reason and crash in __glDispatchCheckMultithreaded + * when the game calls glXChooseVisual in its GFX init function. + * + * The fix is quite simple (luckily) by just setting the environment variable __GL_SINGLETHREADED. + */ + +#include +#include + +#include "capnhook/hook/lib.h" + +typedef char* (*getenv_t) (const char* name); + +static getenv_t _mk3hook_ogl_fix_real_getenv; + +char* getenv(const char* name) +{ + if (!_mk3hook_ogl_fix_real_getenv) { + _mk3hook_ogl_fix_real_getenv = (getenv_t) cnh_lib_get_func_addr("getenv"); + } + + char* ret = _mk3hook_ogl_fix_real_getenv(name); + + if (!strcmp(name, "__GL_SINGLETHREADED")) { + if (ret == NULL || strcmp(name, "1") != 0) { + printf("!!! mk3hook: __GL_SINGLETHREADED not set or enabled, force patching OpenGL to single " + "threaded to avoid crash !!!\n"); + return "1"; + } + } + + return ret; +} \ No newline at end of file diff --git a/src/main/hook/mk3/options.c b/src/main/hook/mk3/options.c new file mode 100644 index 0000000..a7f0892 --- /dev/null +++ b/src/main/hook/mk3/options.c @@ -0,0 +1,230 @@ +#include "hook/mk3/options.h" + +#include "util/options.h" + +#define MK3HOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define MK3HOOK_OPTIONS_STR_GAME_SYNC_OFFSET "game.sync_offset" +#define MK3HOOK_OPTIONS_STR_GAME_SYNC_MULTIPLIER "game.sync_multiplier" +#define MK3HOOK_OPTIONS_STR_GAME_MUSIC_VOL "game.music_vol" +#define MK3HOOK_OPTIONS_STR_GAME_SFX_VOL "game.sfx_vol" +#define MK3HOOK_OPTIONS_STR_GAME_1ST_2ND_FS "game.1st_2nd_fs" +#define MK3HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEBUG_OUTPUT "patch.sound.debug_output" +#define MK3HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +static const struct util_options_def mk3hook_options_def[] = { + { + .name = MK3HOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = MK3HOOK_OPTIONS_STR_GAME_SYNC_OFFSET, + .description = "Sync offset in ms", + .param = 'y', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = 115, + }, + { + .name = MK3HOOK_OPTIONS_STR_GAME_SYNC_MULTIPLIER, + .description = "Sync multiplier", + .param = 'v', + .type = UTIL_OPTIONS_TYPE_DOUBLE, + .default_value.d = 4.16666666666666696272613990004, + }, + { + .name = MK3HOOK_OPTIONS_STR_GAME_MUSIC_VOL, + .description = "Volume of music playback", + .param = 'm', + .type = UTIL_OPTIONS_TYPE_DOUBLE, + .default_value.d = 1.0, + }, + { + .name = MK3HOOK_OPTIONS_STR_GAME_SFX_VOL, + .description = "Volume of sound effects playback", + .param = 'c', + .type = UTIL_OPTIONS_TYPE_DOUBLE, + .default_value.d = 1.0, + }, + { + .name = MK3HOOK_OPTIONS_STR_GAME_1ST_2ND_FS, + .description = "Enable this option when running 1st or 2nd. This uses different file system redirects for the " + "game directory as well as enables a fix. Without this, the game throws errors about not finding files", + .param = 'x', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEBUG_OUTPUT, + .description = "Enable debug output for the fmod library. This requires you to use the fmodexL.so instead of " + "the standard fmodex.so", + .param = 'r', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +static const struct util_options_defs mk3hook_options_defs = { + .usage_header = + "Pumptools mk3hook for Pump It Up MK3 Linux ports, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./mk3hook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = mk3hook_options_def, + .ndefs = lengthof(mk3hook_options_def) +}; + +bool mk3hook_options_init(int argc, char** argv, struct mk3hook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&mk3hook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.settings = util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_GAME_SETTINGS); + options->game.sync_offset = util_options_get_int(options_opt, MK3HOOK_OPTIONS_STR_GAME_SYNC_OFFSET); + options->game.sync_multiplier = util_options_get_double(options_opt, MK3HOOK_OPTIONS_STR_GAME_SYNC_MULTIPLIER); + options->game.music_vol = util_options_get_double(options_opt, MK3HOOK_OPTIONS_STR_GAME_MUSIC_VOL); + options->game.sfx_vol = util_options_get_double(options_opt, MK3HOOK_OPTIONS_STR_GAME_SFX_VOL); + options->game.fs_1st_2nd = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_GAME_1ST_2ND_FS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.piuio.api_lib = util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sound.debug_output = util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_SOUND_DEBUG_OUTPUT); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, MK3HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, MK3HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/mk3/options.h b/src/main/hook/mk3/options.h new file mode 100644 index 0000000..ead48f5 --- /dev/null +++ b/src/main/hook/mk3/options.h @@ -0,0 +1,60 @@ +#ifndef MK3HOOK_OPTIONS_H +#define MK3HOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct mk3hook_options { + struct game { + const char* settings; + int32_t sync_offset; + double sync_multiplier; + double music_vol; + double sfx_vol; + bool fs_1st_2nd; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + bool debug_output; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool mk3hook_options_init(int argc, char** argv, struct mk3hook_options* options); + +#endif diff --git a/src/main/hook/nx/main.c b/src/main/hook/nx/main.c new file mode 100644 index 0000000..70aa7a0 --- /dev/null +++ b/src/main/hook/nx/main.c @@ -0,0 +1,272 @@ +/** + * Hook library for Pump It Up: NX + */ +#define LOG_MODULE "nxhook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/gfx.h" +#include "hook/patch/hdd-check.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/microdog34.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/str.h" +#include "util/sys-info.h" + +#include "options.h" +#include "unlock.h" + +/* Compiled binary data from data folder. Symbols are defined by compiler */ +extern const uint8_t _binary_nxhook_hdd_raw_start[]; +extern const uint8_t _binary_nxhook_hdd_raw_end[]; +extern const uint8_t _binary_nxhook_dog_key_start[]; +extern const uint8_t _binary_nxhook_dog_key_end[]; + +static void nxhook_bootstrapping(int argc, char** argv, struct nxhook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!nxhook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void nxhook_patch_fs_init(struct nxhook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void nxhook_fs_redir_settings(struct nxhook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void nxhook_fs_redir_script(struct nxhook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + path_tmp = util_str_merge(game_data_path, "/SCRIPT"); + cnh_redir_add("/SCRIPT", path_tmp); + free(path_tmp); +} + +static void nxhook_fs_redir_fonts(struct nxhook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + path_tmp = util_str_merge(game_data_path, "/MICROGBE.TTF"); + cnh_redir_add("MICROGBE.TTF", path_tmp); + free(path_tmp); + + path_tmp = util_str_merge(game_data_path, "/NXTW.TTF"); + cnh_redir_add("NXTW.TTF", path_tmp); + free(path_tmp); +} + +static void nxhook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); +} + +static void nxhook_fs_redirs_init(struct nxhook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + /* Expecting all assets in the piu game (asset) folder. redirects for files + that are not in there by default for reasons */ + nxhook_fs_redir_settings(options); + nxhook_fs_redir_script(options, game_data_path); + nxhook_fs_redir_fonts(options, game_data_path); + + nxhook_fs_redir_usb(); +} + +static void nxhook_patch_gfx_init(struct nxhook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void nxhook_patch_game_init(struct nxhook_options* options) +{ + log_assert(options); + + if (options->game.force_unlock) { + hook_nx_unlock_init(); + } +} + +static void nxhook_patch_main_loop_init(struct nxhook_options* options) +{ + log_assert(options); + + patch_main_loop_init(true, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void nxhook_patch_sound_init(struct nxhook_options* options) +{ + log_assert(options); + + patch_sound_init(options->patch.sound.device); +} + +static void nxhook_patch_sigsegv_init(struct nxhook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void nxhook_patch_dongle_init() +{ + patch_microdog34_init((const uint8_t*) _binary_nxhook_dog_key_start, + ((uintptr_t) &_binary_nxhook_dog_key_end - (uintptr_t) &_binary_nxhook_dog_key_start)); +} + +static void nxhook_patch_hdd_check_init() +{ + patch_hdd_check_init((const uint8_t*) _binary_nxhook_hdd_raw_start, + ((uintptr_t) &_binary_nxhook_hdd_raw_end - (uintptr_t) &_binary_nxhook_hdd_raw_start)); +} + +static void nxhook_patch_piuio_init(struct nxhook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +void nxhook_constructor(void) +{ + /* Nothing here */ +} + +void nxhook_destructor(void) +{ + /* Nothing here */ +} + +void nxhook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct nxhook_options options; + + nxhook_bootstrapping(argc, argv, &options, &game_data_path); + nxhook_patch_fs_init(&options); + nxhook_fs_redirs_init(&options, game_data_path); + nxhook_patch_gfx_init(&options); + nxhook_patch_game_init(&options); + nxhook_patch_main_loop_init(&options); + nxhook_patch_sound_init(&options); + nxhook_patch_sigsegv_init(&options); + nxhook_patch_dongle_init(); + nxhook_patch_hdd_check_init(); + nxhook_patch_piuio_init(&options); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void nxhook_trap_after_main(void) +{ + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(nxhook_constructor); +LIB_MAIN_DESTRUCTOR(nxhook_destructor); +LIB_MAIN_TRAP_MAIN(nxhook_trap_before_main, nxhook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/nx/nxhook.dog.key b/src/main/hook/nx/nxhook.dog.key new file mode 100644 index 0000000..fba1b2e Binary files /dev/null and b/src/main/hook/nx/nxhook.dog.key differ diff --git a/src/main/hook/nx/nxhook.hdd.raw b/src/main/hook/nx/nxhook.hdd.raw new file mode 100755 index 0000000..0e5bb29 Binary files /dev/null and b/src/main/hook/nx/nxhook.hdd.raw differ diff --git a/src/main/hook/nx/options.c b/src/main/hook/nx/options.c new file mode 100644 index 0000000..e365210 --- /dev/null +++ b/src/main/hook/nx/options.c @@ -0,0 +1,183 @@ +#include "hook/nx/options.h" + +#include "util/options.h" + +#define NXHOOK_OPTIONS_STR_GAME_FORCE_UNLOCK "game.force_unlock" +#define NXHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define NXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define NXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define NXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define NXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define NXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +static const struct util_options_def nxhook_options_def[] = { + { + .name = NXHOOK_OPTIONS_STR_GAME_FORCE_UNLOCK, + .description = "Force unlock the game. Modifies the current and any new PIUNX.INI settings file permanently", + .param = 'z', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXHOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +static const struct util_options_defs nxhook_options_defs = { + .usage_header = + "Pumptools nxhook for Pump It Up: NX, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./nxhook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = nxhook_options_def, + .ndefs = lengthof(nxhook_options_def) +}; + +bool nxhook_options_init(int argc, char** argv, struct nxhook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&nxhook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.force_unlock = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_GAME_FORCE_UNLOCK); + options->game.settings = util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.piuio.api_lib = util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, NXHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, NXHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/nx/options.h b/src/main/hook/nx/options.h new file mode 100644 index 0000000..45272ad --- /dev/null +++ b/src/main/hook/nx/options.h @@ -0,0 +1,55 @@ +#ifndef NXHOOK_OPTIONS_H +#define NXHOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct nxhook_options { + struct game { + bool force_unlock; + const char* settings; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool nxhook_options_init(int argc, char** argv, struct nxhook_options* options); + +#endif diff --git a/src/main/hook/nx/unlock.c b/src/main/hook/nx/unlock.c new file mode 100644 index 0000000..272342c --- /dev/null +++ b/src/main/hook/nx/unlock.c @@ -0,0 +1,81 @@ +#define LOG_MODULE "hook-nx-unlock" + +#include +#include + +#include "asset/nx/lib/settings.h" + +#include "capnhook/hook/filehook.h" + +#include "util/log.h" +#include "util/str.h" + +static FILE* _hook_nx_unlock_settings_file = NULL; + +static enum cnh_result _hook_nx_unlock_filehook(struct cnh_filehook_irp *irp); + +void hook_nx_unlock_init(void) +{ + cnh_filehook_push_handler(_hook_nx_unlock_filehook); + + log_info("Initialized"); +} + +static enum cnh_result _hook_nx_unlock_filehook(struct cnh_filehook_irp *irp) +{ + enum cnh_result result; + + assert(irp); + + if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN && irp->file != _hook_nx_unlock_settings_file) { + return cnh_filehook_invoke_next(irp); + } + + switch (irp->op) { + case CNH_FILEHOOK_IRP_OP_OPEN: + { + /* Let it open the real file */ + result = cnh_filehook_invoke_next(irp); + + if (util_str_ends_with(irp->open_filename, ASSET_NX_SETTINGS_FILE_NAME)) { + if (result == CNH_RESULT_SUCCESS) { + log_debug("Intercepting settings file open"); + _hook_nx_unlock_settings_file = irp->file; + } + } + + break; + } + + case CNH_FILEHOOK_IRP_OP_READ: + { + /* Let it read the real file */ + result = cnh_filehook_invoke_next(irp); + + if (result == CNH_RESULT_SUCCESS) { + struct asset_nx_settings* settings = (struct asset_nx_settings*) irp->read.bytes; + + asset_nx_settings_unlock_all(settings); + + log_debug("Applied unlock patch to loaded settings (buffer size %d)", irp->read.pos); + } + + break; + } + + case CNH_FILEHOOK_IRP_OP_CLOSE: + { + _hook_nx_unlock_settings_file = NULL; + + result = cnh_filehook_invoke_next(irp); + + break; + } + + default: + result = cnh_filehook_invoke_next(irp); + break; + } + + return result; +} \ No newline at end of file diff --git a/src/main/hook/nx/unlock.h b/src/main/hook/nx/unlock.h new file mode 100644 index 0000000..5e63b4c --- /dev/null +++ b/src/main/hook/nx/unlock.h @@ -0,0 +1,12 @@ +/** + * Patch module to force unlock all songs/features of the game without modifying the configuration stored on disk. + */ +#ifndef HOOK_NX_UNLOCK_H +#define HOOK_NX_UNLOCK_H + +/** + * Initialize the patch module force unlocking all locked game content. + */ +void hook_nx_unlock_init(void); + +#endif diff --git a/src/main/hook/nx2/main.c b/src/main/hook/nx2/main.c new file mode 100644 index 0000000..6bf5e5f --- /dev/null +++ b/src/main/hook/nx2/main.c @@ -0,0 +1,320 @@ +#define LOG_MODULE "nx2hook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/gfx.h" +#include "hook/patch/hdd-check.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/microdog40.h" +#include "hook/patch/mounts.h" +#include "hook/patch/net-profile.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" +#include "hook/patch/usb-mnt.h" +#include "hook/patch/usb-unlock.h" +#include "hook/patch/usbfs.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/str.h" +#include "util/sys-info.h" + +#include "options.h" +#include "profile-gen.h" + +/* Compiled binary data from data folder. Symbols are defined by compiler */ +extern const uint8_t _binary_nx2hook_hdd_raw_start[]; +extern const uint8_t _binary_nx2hook_hdd_raw_end[]; +extern const uint8_t _binary_nx2hook_dog_key_start[]; +extern const uint8_t _binary_nx2hook_dog_key_end[]; + +static void nx2hook_bootstrapping(int argc, char** argv, struct nx2hook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!nx2hook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void nx2hook_patch_fs_init(struct nx2hook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void nx2hook_fs_redir_settings(struct nx2hook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void nx2hook_fs_redir_script(struct nx2hook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + path_tmp = util_str_merge(game_data_path, "/SCRIPT"); + cnh_redir_add("/SCRIPT", path_tmp); + free(path_tmp); +} + +static void nx2hook_fs_redir_fonts(struct nx2hook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + /* Fonts, game expects lower case files */ + path_tmp = util_str_merge(game_data_path, "/nx.ttf"); + cnh_redir_add("nx.ttf", path_tmp); + free(path_tmp); + + path_tmp = util_str_merge(game_data_path, "/nxcn.ttf"); + cnh_redir_add("nxcn.ttf", path_tmp); + free(path_tmp); + + path_tmp = util_str_merge(game_data_path, "/nxpt.ttf"); + cnh_redir_add("nxpt.ttf", path_tmp); + free(path_tmp); + + path_tmp = util_str_merge(game_data_path, "/nxtw.ttf"); + cnh_redir_add("nxtw.ttf", path_tmp); + free(path_tmp); +} + +static void nx2hook_fs_redir_mission(struct nx2hook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + /* mission.txt, game expects lower case file */ + path_tmp = util_str_merge(game_data_path, "/mission.txt"); + cnh_redir_add("/mission.txt", path_tmp); + free(path_tmp); +} + +static void nx2hook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); + patch_usbfs_init(); +} + +static void nx2hook_fs_redirs_init(struct nx2hook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + /* Expecting all assets in the piu game (asset) folder. redirects for files + that are not in there by default for reasons */ + nx2hook_fs_redir_settings(options); + nx2hook_fs_redir_script(options, game_data_path); + nx2hook_fs_redir_fonts(options, game_data_path); + nx2hook_fs_redir_mission(options, game_data_path); + + nx2hook_fs_redir_usb(); +} + +static void nx2hook_patch_fs_mounting_init() +{ + patch_mounts_init(); + + patch_usb_mnt_init(); + patch_usb_unlock_init(); +} + +static void nx2hook_patch_gfx_init(struct nx2hook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void nx2hook_patch_main_loop_init(struct nx2hook_options* options) +{ + log_assert(options); + + patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void nx2hook_patch_sound_init(struct nx2hook_options* options) +{ + log_assert(options); + + patch_sound_init(options->patch.sound.device); +} + +static void nx2hook_patch_sigsegv_init(struct nx2hook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void nx2hook_patch_dongle_init() +{ + patch_microdog40_init((const uint8_t*) _binary_nx2hook_dog_key_start, + ((uintptr_t) &_binary_nx2hook_dog_key_end - (uintptr_t) &_binary_nx2hook_dog_key_start)); +} + +static void nx2hook_patch_hdd_check_init() +{ + patch_hdd_check_init((const uint8_t*) _binary_nx2hook_hdd_raw_start, + ((uintptr_t) &_binary_nx2hook_hdd_raw_end - (uintptr_t) &_binary_nx2hook_hdd_raw_start)); +} + +static void nx2hook_patch_piuio_init(struct nx2hook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +static void nx2hook_patch_profile() +{ + nx2hook_profile_gen_init(); +} + +static void nx2hook_patch_pumpnet(struct nx2hook_options* options) +{ + if (options->patch.net.server && options->patch.net.machine_id != 0) { + patch_net_profile_init( + ASSET_GAME_VERSION_NX2, + options->patch.net.server, + options->patch.net.machine_id, + options->patch.net.cert_dir_path, + options->patch.net.verbose_log_output); + } +} + +void nx2hook_constructor(void) +{ + /* Nothing here */ +} + +void nx2hook_destructor(void) +{ + /* Nothing here */ +} + +void nx2hook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct nx2hook_options options; + + nx2hook_bootstrapping(argc, argv, &options, &game_data_path); + nx2hook_patch_fs_init(&options); + nx2hook_fs_redirs_init(&options, game_data_path); + nx2hook_patch_fs_mounting_init(); + nx2hook_patch_gfx_init(&options); + nx2hook_patch_main_loop_init(&options); + nx2hook_patch_sound_init(&options); + nx2hook_patch_sigsegv_init(&options); + nx2hook_patch_dongle_init(); + nx2hook_patch_hdd_check_init(); + nx2hook_patch_piuio_init(&options); + // Init order of pumpnet and auto profile generating important: pumpnet hook, if active, needs to be applied before + // the auto gen profile hook. Otherwise, the game detects that no profile is available and auto generates one even + // it should take the pumpnet route to grab a profile from a remote server + nx2hook_patch_pumpnet(&options); + nx2hook_patch_profile(); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void nx2hook_trap_after_main(void) +{ + patch_net_profile_shutdown(); + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(nx2hook_constructor); +LIB_MAIN_DESTRUCTOR(nx2hook_destructor); +LIB_MAIN_TRAP_MAIN(nx2hook_trap_before_main, nx2hook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/nx2/nx2hook.dog.key b/src/main/hook/nx2/nx2hook.dog.key new file mode 100755 index 0000000..0b887a5 Binary files /dev/null and b/src/main/hook/nx2/nx2hook.dog.key differ diff --git a/src/main/hook/nx2/nx2hook.hdd.raw b/src/main/hook/nx2/nx2hook.hdd.raw new file mode 100755 index 0000000..a4fc99f Binary files /dev/null and b/src/main/hook/nx2/nx2hook.hdd.raw differ diff --git a/src/main/hook/nx2/options.c b/src/main/hook/nx2/options.c new file mode 100644 index 0000000..24fbd1c --- /dev/null +++ b/src/main/hook/nx2/options.c @@ -0,0 +1,215 @@ +#include "hook/nx2/options.h" + +#include "util/options.h" + +#define NX2HOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define NX2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server" +#define NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id" +#define NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_VERBOSE_LOG_OUTPUT "patch.net_profile.verbose_log_output" +#define NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_CERT_DIR_PATH "patch.net_profile.cert_dir_path" +#define NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define NX2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define NX2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +const struct util_options_def nx2hook_options_def[] = { + { + .name = NX2HOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER, + .description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)", + .param = 'n', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID, + .description = "Machine id for pumpnet", + .param = 'm', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "0102030405060708", + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_VERBOSE_LOG_OUTPUT, + .description = "Enable verbose log output for pumpnet related things, e.g. logging network traffic", + .param = 'v', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_CERT_DIR_PATH, + .description = "Path to a folder containing the client key, certificate and CA bundle to enable https communication", + .param = 'c', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +const struct util_options_defs nx2hook_options_defs = { + .usage_header = + "Pumptools nx2hook for Pump It Up: NX2, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./nx2hook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = nx2hook_options_def, + .ndefs = lengthof(nx2hook_options_def) +}; + +bool nx2hook_options_init(int argc, char** argv, struct nx2hook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&nx2hook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.settings = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.net.server = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER); + options->patch.net.machine_id = strtoull( + util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID), + NULL, + 16); + options->patch.net.verbose_log_output = + util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_VERBOSE_LOG_OUTPUT); + options->patch.net.cert_dir_path = + util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_NET_PROFILE_CERT_DIR_PATH); + options->patch.piuio.api_lib = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, NX2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, NX2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/nx2/options.h b/src/main/hook/nx2/options.h new file mode 100644 index 0000000..65a28b9 --- /dev/null +++ b/src/main/hook/nx2/options.h @@ -0,0 +1,61 @@ +#ifndef NX2HOOK_OPTIONS_H +#define NX2HOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct nx2hook_options { + struct game { + const char* settings; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct net { + const char* server; + uint64_t machine_id; + bool verbose_log_output; + const char* cert_dir_path; + } net; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool nx2hook_options_init(int argc, char** argv, struct nx2hook_options* options); + +#endif diff --git a/src/main/hook/nx2/profile-gen.c b/src/main/hook/nx2/profile-gen.c new file mode 100644 index 0000000..7994653 --- /dev/null +++ b/src/main/hook/nx2/profile-gen.c @@ -0,0 +1,70 @@ +#define LOG_MODULE "nx2hook-profile-gen" + +#include + +#include "capnhook/hook/filehook.h" + +#include "asset/nx2/lib/util.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/str.h" + +static bool nx2hook_profile_gen_is_creating_file; + +// Do not use util_fs_path_exists here as this fopens and causes an endless recursion +static bool nx2hook_profile_gen_check_if_exists(const char* path) +{ + return access(path, F_OK) != -1; +} + +static enum cnh_result nx2hook_profile_gen_filehook(struct cnh_filehook_irp *irp) +{ + // nx2hook_profile_gen_is_creating_file: Avoid recursion of any file calls when creating the save/rank files + if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN || nx2hook_profile_gen_is_creating_file) { + return cnh_filehook_invoke_next(irp); + } + + if (util_str_ends_with(irp->open_filename, "nx2save.bin") && + !nx2hook_profile_gen_check_if_exists(irp->open_filename)) { + nx2hook_profile_gen_is_creating_file = true; + + struct asset_nx2_usb_save* save = asset_nx2_usb_save_new(); + asset_nx2_usb_save_finalize(save); + + log_info("No %s file found, generate new", irp->open_filename); + + if (!asset_nx2_usb_save_save_to_file(irp->open_filename, save, true)) { + log_error("Saving generated nx2save.bin file to %s failed", irp->open_filename); + } + + free(save); + + nx2hook_profile_gen_is_creating_file = false; + } + + if (util_str_ends_with(irp->open_filename, "nx2rank.bin") && + !nx2hook_profile_gen_check_if_exists(irp->open_filename)) { + nx2hook_profile_gen_is_creating_file = true; + + struct asset_nx2_usb_rank* rank = asset_nx2_usb_rank_new(); + asset_nx2_usb_rank_finalize(rank); + + log_info("No %s file found, generate new", irp->open_filename); + + if (!asset_nx2_usb_rank_save_to_file(irp->open_filename, rank, true)) { + log_error("Saving generated nx2rank.bin file to %s failed", irp->open_filename); + } + + free(rank); + + nx2hook_profile_gen_is_creating_file = false; + } + + return cnh_filehook_invoke_next(irp); +} + +void nx2hook_profile_gen_init(void) +{ + cnh_filehook_push_handler(nx2hook_profile_gen_filehook); +} \ No newline at end of file diff --git a/src/main/hook/nx2/profile-gen.h b/src/main/hook/nx2/profile-gen.h new file mode 100644 index 0000000..d02c04e --- /dev/null +++ b/src/main/hook/nx2/profile-gen.h @@ -0,0 +1,14 @@ +/** + * Patch module for NX2 to enable auto generating of save files on empty/non + * Andamiro usb drives + */ +#ifndef NX2HOOK_PROFILE_GEN_H +#define NX2HOOK_PROFILE_GEN_H + +/** + * Initialize this patch module to allow auto generation of new/empty profiles + * on usb sticks that do not contain stock NX2 profile data + */ +void nx2hook_profile_gen_init(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/nxa/main.c b/src/main/hook/nxa/main.c new file mode 100644 index 0000000..9b20e1b --- /dev/null +++ b/src/main/hook/nxa/main.c @@ -0,0 +1,310 @@ +/** + * Hook library for Pump It Up: NXA + */ +#define LOG_MODULE "nxahook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/gfx.h" +#include "hook/patch/hdd-check.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/microdog40.h" +#include "hook/patch/mounts.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" +#include "hook/patch/usb-mnt.h" +#include "hook/patch/usb-unlock.h" +#include "hook/patch/usbfs.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/str.h" +#include "util/sys-info.h" + +#include "options.h" +#include "profile-gen.h" + +/* Compiled binary data from data folder. Symbols are defined by compiler */ +extern const uint8_t _binary_nxahook_hdd_raw_start[]; +extern const uint8_t _binary_nxahook_hdd_raw_end[]; +extern const uint8_t _binary_nxahook_dog_key_start[]; +extern const uint8_t _binary_nxahook_dog_key_end[]; + +static void nxahook_bootstrapping(int argc, char** argv, struct nxahook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!nxahook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void nxahook_patch_fs_init(struct nxahook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void nxahook_fs_redir_settings(struct nxahook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void nxahook_fs_redir_script(struct nxahook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + path_tmp = util_str_merge(game_data_path, "/SCRIPT"); + cnh_redir_add("/SCRIPT", path_tmp); + free(path_tmp); +} + +static void nxahook_fs_redir_fonts(struct nxahook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + /* Fonts, game expects lower case files */ + path_tmp = util_str_merge(game_data_path, "/nx.ttf"); + cnh_redir_add("nx.ttf", path_tmp); + free(path_tmp); + + path_tmp = util_str_merge(game_data_path, "/nxcn.ttf"); + cnh_redir_add("nxcn.ttf", path_tmp); + free(path_tmp); + + path_tmp = util_str_merge(game_data_path, "/nxpt.ttf"); + cnh_redir_add("nxpt.ttf", path_tmp); + free(path_tmp); + + path_tmp = util_str_merge(game_data_path, "/nxtw.ttf"); + cnh_redir_add("nxtw.ttf", path_tmp); + free(path_tmp); +} + +static void nxahook_fs_redir_mission(struct nxahook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + /* mission.txt, game expects lower case file */ + path_tmp = util_str_merge(game_data_path, "/mission.txt"); + cnh_redir_add("/mission.txt", path_tmp); + free(path_tmp); + + /* ufo.txt, game expects lower case file */ + path_tmp = util_str_merge(game_data_path, "/ufo.txt"); + cnh_redir_add("/ufo.txt", path_tmp); + free(path_tmp); +} + +static void nxahook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); + patch_usbfs_init(); +} + +static void nxahook_fs_redirs_init(struct nxahook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + /* Expecting all assets in the piu game (asset) folder. redirects for files + that are not in there by default for reasons */ + nxahook_fs_redir_settings(options); + nxahook_fs_redir_script(options, game_data_path); + nxahook_fs_redir_fonts(options, game_data_path); + nxahook_fs_redir_mission(options, game_data_path); + + nxahook_fs_redir_usb(); +} + +static void nxahook_patch_fs_mounting_init() +{ + patch_mounts_init(); + + patch_usb_mnt_init(); + patch_usb_unlock_init(); +} + +static void nxahook_patch_gfx_init(struct nxahook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void nxahook_patch_main_loop_init(struct nxahook_options* options) +{ + log_assert(options); + + patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void nxahook_patch_sound_init(struct nxahook_options* options) +{ + log_assert(options); + + patch_sound_init(options->patch.sound.device); +} + +static void nxahook_patch_sigsegv_init(struct nxahook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void nxahook_patch_dongle_init() +{ + patch_microdog40_init((const uint8_t*) _binary_nxahook_dog_key_start, + ((uintptr_t) &_binary_nxahook_dog_key_end - (uintptr_t) &_binary_nxahook_dog_key_start)); +} + +static void nxahook_patch_hdd_check_init() +{ + patch_hdd_check_init((const uint8_t*) _binary_nxahook_hdd_raw_start, + ((uintptr_t) &_binary_nxahook_hdd_raw_end - (uintptr_t) &_binary_nxahook_hdd_raw_start)); +} + +static void nxahook_patch_piuio_init(struct nxahook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +static void nxahook_patch_profile() +{ + nxahook_profile_gen_init(); +} + +void nxahook_constructor(void) +{ + /* Nothing here */ +} + +void nxahook_destructor(void) +{ + /* Nothing here */ +} + +void nxahook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct nxahook_options options; + + nxahook_bootstrapping(argc, argv, &options, &game_data_path); + nxahook_patch_fs_init(&options); + nxahook_fs_redirs_init(&options, game_data_path); + nxahook_patch_fs_mounting_init(); + nxahook_patch_gfx_init(&options); + nxahook_patch_main_loop_init(&options); + nxahook_patch_sound_init(&options); + nxahook_patch_sigsegv_init(&options); + nxahook_patch_dongle_init(); + nxahook_patch_hdd_check_init(); + nxahook_patch_piuio_init(&options); + nxahook_patch_profile(); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void nxahook_trap_after_main(void) +{ + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(nxahook_constructor); +LIB_MAIN_DESTRUCTOR(nxahook_destructor); +LIB_MAIN_TRAP_MAIN(nxahook_trap_before_main, nxahook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/nxa/nxahook.dog.key b/src/main/hook/nxa/nxahook.dog.key new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/nxa/nxahook.dog.key @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/nxa/nxahook.hdd.raw b/src/main/hook/nxa/nxahook.hdd.raw new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/nxa/nxahook.hdd.raw @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/nxa/options.c b/src/main/hook/nxa/options.c new file mode 100644 index 0000000..c3e6451 --- /dev/null +++ b/src/main/hook/nxa/options.c @@ -0,0 +1,195 @@ +#include "hook/nxa/options.h" + +#include "util/options.h" + +#define NXAHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define NXAHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER "patch.net_profile.server" +#define NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID "patch.net_profile.machine_id" +#define NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define NXAHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define NXAHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +const struct util_options_def nxahook_options_def[] = { + { + .name = NXAHOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER, + .description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)", + .param = 'n', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID, + .description = "Machine id for pumpnet", + .param = 'm', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = 0, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +const struct util_options_defs nxahook_options_defs = { + .usage_header = + "Pumptools nxahook for Pump It Up: NXA, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./nxahook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = nxahook_options_def, + .ndefs = lengthof(nxahook_options_def) +}; + +bool nxahook_options_init(int argc, char** argv, struct nxahook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&nxahook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.settings = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.net.server = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_SERVER); + options->patch.net.machine_id = strtoul( + util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_NET_PROFILE_MACHINE_ID), + NULL, + 16); + options->patch.piuio.api_lib = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, NXAHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, NXAHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/nxa/options.h b/src/main/hook/nxa/options.h new file mode 100644 index 0000000..32030e5 --- /dev/null +++ b/src/main/hook/nxa/options.h @@ -0,0 +1,59 @@ +#ifndef NXAHOOK_OPTIONS_H +#define NXAHOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct nxahook_options { + struct game { + const char* settings; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct net { + const char* server; + uint32_t machine_id; + } net; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool nxahook_options_init(int argc, char** argv, struct nxahook_options* options); + +#endif diff --git a/src/main/hook/nxa/profile-gen.c b/src/main/hook/nxa/profile-gen.c new file mode 100644 index 0000000..c9e93ce --- /dev/null +++ b/src/main/hook/nxa/profile-gen.c @@ -0,0 +1,108 @@ +#define LOG_MODULE "nxahook-profile-gen" + +#include + +#include "capnhook/hook/filehook.h" + +#include "asset/nxa/lib/util.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/str.h" + +static bool nxahook_profile_gen_is_creating_file; + +// Do not use util_fs_path_exists here as this fopens and causes an endless recursion +static bool nxahook_profile_gen_check_if_exists(const char* path) +{ + return access(path, F_OK) != -1; +} + +static enum cnh_result nxahook_profile_gen_filehook(struct cnh_filehook_irp *irp) +{ + // nx2hook_profile_gen_is_creating_file: Avoid recursion of any file calls when creating the save/rank files + if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN || nxahook_profile_gen_is_creating_file) { + return cnh_filehook_invoke_next(irp); + } + + if (util_str_ends_with(irp->open_filename, "nxasave.bin") && + !nxahook_profile_gen_check_if_exists(irp->open_filename)) { + char* path_only; + char* path_nx2; + struct asset_nxa_usb_save* save; + + nxahook_profile_gen_is_creating_file = true; + + /* Check if a nx2save.bin exists and skip generating a new nxasave + file because the game migrates everything from the nx2 file */ + path_only = util_fs_get_path_to_file(irp->open_filename); + path_nx2 = util_str_merge(path_only, "/nx2save.bin"); + free(path_only); + + if (util_fs_path_exists(path_nx2)) { + free(path_nx2); + log_info("nx2save.bin exists, don't create new nxasave.bin"); + nxahook_profile_gen_is_creating_file = false; + return cnh_filehook_invoke_next(irp); + } + + free(path_nx2); + + save = asset_nxa_usb_save_new(); + asset_nxa_usb_save_finalize(save); + + log_info("No %s file found, generate new", irp->open_filename); + + if (!asset_nxa_usb_save_save_to_file(irp->open_filename, save, true)) { + log_error("Saving generated nxasave.bin file to %s failed", irp->open_filename); + } + + free(save); + + nxahook_profile_gen_is_creating_file = false; + } + + if (util_str_ends_with(irp->open_filename, "nxarank.bin") && + !nxahook_profile_gen_check_if_exists(irp->open_filename)) { + char* path_only; + char* path_nx2; + struct asset_nxa_usb_rank* rank; + + nxahook_profile_gen_is_creating_file = true; + + /* Check if a nx2rank.bin exists and skip generating a new nxarank + file because the game migrates everything from the nx2 file */ + path_only = util_fs_get_path_to_file(irp->open_filename); + path_nx2 = util_str_merge(path_only, "/nx2rank.bin"); + free(path_only); + + if (util_fs_path_exists(path_nx2)) { + free(path_nx2); + log_info("nx2rank.bin exists, don't create new nxarank.bin"); + nxahook_profile_gen_is_creating_file = false; + return cnh_filehook_invoke_next(irp); + } + + free(path_nx2); + + rank = asset_nxa_usb_rank_new(); + asset_nxa_usb_rank_finalize(rank); + + log_info("No %s file found, generate new", irp->open_filename); + + if (!asset_nxa_usb_rank_save_to_file(irp->open_filename, rank, true)) { + log_error("Saving generated nxarank.bin file to %s failed", irp->open_filename); + } + + free(rank); + + nxahook_profile_gen_is_creating_file = false; + } + + return cnh_filehook_invoke_next(irp); +} + +void nxahook_profile_gen_init(void) +{ + cnh_filehook_push_handler(nxahook_profile_gen_filehook); +} \ No newline at end of file diff --git a/src/main/hook/nxa/profile-gen.h b/src/main/hook/nxa/profile-gen.h new file mode 100644 index 0000000..cf8fbe2 --- /dev/null +++ b/src/main/hook/nxa/profile-gen.h @@ -0,0 +1,14 @@ +/** + * Patch module for NXA to enable auto generating of save files on empty/non + * Andamiro usb drives + */ +#ifndef NXAHOOK_PROFILE_GEN_H +#define NXAHOOK_PROFILE_GEN_H + +/** + * Initialize this patch module to allow auto generation of new/empty profiles + * on usb sticks that do not contain stock NXA profile data + */ +void nxahook_profile_gen_init(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/amixer-block.c b/src/main/hook/patch/amixer-block.c new file mode 100644 index 0000000..6f65140 --- /dev/null +++ b/src/main/hook/patch/amixer-block.c @@ -0,0 +1,53 @@ +#define LOG_MODULE "patch-amixer-block" + +#include "capnhook/hook/lib.h" + +#include "util/log.h" +#include "util/str.h" + +typedef int (*system_t)(const char* command); +typedef int (*execv_t)(const char* path, char* const argv[]); + +static bool patch_axmier_block_initialized; +static system_t patch_amixer_block_real_system; +static execv_t patch_amixer_block_real_execv; + +// for fst and fex +int system(const char* command) +{ + if (!patch_amixer_block_real_system) { + patch_amixer_block_real_system = (system_t) cnh_lib_get_func_addr("system"); + } + + if (patch_axmier_block_initialized) { + if (util_str_starts_with(command, "amixer")) { + log_info("Blocking call to amixer: %s", command); + return 0; + } + } + + return patch_amixer_block_real_system(command); +} + +// for f2 and prime (yes, they figured using system() isn't a great idea...) +int execv(const char* path, char* const argv[]) +{ + if (!patch_amixer_block_real_execv) { + patch_amixer_block_real_execv = (execv_t) cnh_lib_get_func_addr("execv"); + } + + if (patch_axmier_block_initialized) { + if (util_str_starts_with(path, "/usr/bin/amixer")) { + log_info("Blocking call to amixer: %s", path); + return 0; + } + } + + return patch_amixer_block_real_execv(path, argv); +} + +void patch_axmier_block_init(void) +{ + patch_axmier_block_initialized = true; + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/amixer-block.h b/src/main/hook/patch/amixer-block.h new file mode 100644 index 0000000..b820169 --- /dev/null +++ b/src/main/hook/patch/amixer-block.h @@ -0,0 +1,13 @@ +/** + * Block the system call to amixer which messes with the volume levels + * (relevant for Fiesta and up) + */ +#ifndef PATCH_AMIXER_BLOCK_H +#define PATCH_AMIXER_BLOCK_H + +/** + * Initialize patch module + */ +void patch_axmier_block_init(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/asound-fix.c b/src/main/hook/patch/asound-fix.c new file mode 100644 index 0000000..37ebe71 --- /dev/null +++ b/src/main/hook/patch/asound-fix.c @@ -0,0 +1,147 @@ +#define LOG_MODULE "patch-asound-fix" + +#include +#include + +#include "capnhook/hook/lib.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +typedef int (*getgrnam_r_t)(const char *name, struct group *grp, char *buf, size_t buflen, struct group **result); + +static getgrnam_r_t patch_asound_fix_real_getgrnam_r; + +static char** patch_asound_fix_split_user_list_str(const char* user_list_str) +{ + char* user_list_str_cpy = util_str_dup(user_list_str); + char** user_list = util_xmalloc(sizeof(char*)); + size_t len_list = 1; + + char* ptr = strtok(user_list_str_cpy, ","); + + while (ptr != NULL) { + // last token will likely read beyond newline to next : token for next group entry + char* new_line_char = strstr(ptr, "\n"); + + if (new_line_char) { + *new_line_char = '\0'; + } + + len_list++; + user_list = util_xrealloc(user_list, len_list * sizeof(char*)); + + user_list[len_list - 2] = util_str_dup(ptr); + user_list[len_list - 1] = NULL; + + ptr = strtok(NULL, ","); + } + + free(user_list_str_cpy); + + return user_list; +} + +static char* patch_asound_fix_user_list_to_str(char** user_list) +{ + char** ptr = user_list; + char* str = util_str_dup("\0"); + + while (*ptr != NULL) { + char* mrg = util_str_merge(str, *ptr); + free(str); + str = util_str_merge(mrg, ","); + free(mrg); + + ptr++; + } + + return str; +} + +int getgrnam_r(const char *name, struct group *grp, char *buf, size_t buflen, struct group **result) +{ + if (!patch_asound_fix_real_getgrnam_r) { + patch_asound_fix_real_getgrnam_r = (getgrnam_r_t) cnh_lib_get_func_addr("getgrnam_r"); + } + + // We expect to use the standard "audio" group here, otherwise detection of this is not possible. Intercept + // and execute the following re-implementation of the reading logic from file. I don't know if getgrnam_r usually + // reads from a cached version of /etc/group but I suppose that doesn't matter here. + if (!strcmp(name, "audio")) { + log_debug("Trapping getgrnam_r for audio group of libasound"); + + FILE* file = fopen("/etc/group", "r"); + + fseek(file, 0, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + char* buffer = (char*) util_xmalloc(file_size); + + if (fread(buffer, file_size, 1, file) != 1) { + log_error("Reading /etc/group file failed: %s", strerror(errno)); + free(buffer); + *result = NULL; + return -1; + } + + char* str_audio_pos = strstr(buffer, name); + + if (str_audio_pos == NULL) { + log_error("Could not find 'audio' group in /etc/group required by libasound likely for defaults.pcm.ipc_gid" + " from asound.conf. Make sure the group exists."); + free(buffer); + *result = NULL; + return -1; + } + + char* tok = strtok(str_audio_pos, ":"); + uint8_t cnt = 0; + + while (tok != NULL) { + switch (cnt) { + case 0: + grp->gr_name = util_str_dup(tok); + break; + + case 1: + grp->gr_passwd = util_str_dup(tok); + break; + + case 2: + grp->gr_gid = strtol(tok, NULL, 10); + break; + + case 3: + grp->gr_mem = patch_asound_fix_split_user_list_str(tok); + break; + + default: + log_error("audio group entry format in /etc/group invalid. Check your /etc/group file!"); + free(buffer); + *result = NULL; + return -1; + } + + cnt++; + tok = strtok(NULL, ":"); + } + + char* grp_mem_str = patch_asound_fix_user_list_to_str(grp->gr_mem); + log_debug("Resulting group struct: %s %s %d %s", grp->gr_name, grp->gr_passwd, grp->gr_gid, grp_mem_str); + free(grp_mem_str); + + *result = grp; + + return 0; + } + + return patch_asound_fix_real_getgrnam_r(name, grp, buf, buflen, result); +} + +void patch_asound_fix_init() +{ + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/asound-fix.h b/src/main/hook/patch/asound-fix.h new file mode 100644 index 0000000..67b8983 --- /dev/null +++ b/src/main/hook/patch/asound-fix.h @@ -0,0 +1,25 @@ +/** + * Fix a quirk with getgrnam_r in the asound library always failing when loading the asound.conf file and reading the + * /etc/group file. For some reason, it cannot find the audio group entry even if it is available and can be read + * by other applications just fine. + * + * Your typical asound.conf, which is loaded by libasound in a application that uses it, contains the following line: + * defaults.pcm.ipc_gid audio + * + * Finding documentation about what this stuff does it difficult but judging by the name, this seems to be related to + * setting up shared memory IPC for reading and writing data to the sound driver. + * For some reason, this "fails" when the function getgrnam_r is called by libasound. The function itself returns + * successful but with no result. This function reads the entry from /etc/group. libsound queries the "audio" group + * in that case and even if the entry exists, the result is empty for some unknown reason. + * + * This module fixes that issue. + */ +#ifndef PATCH_ASOUND_FIX_H +#define PATCH_ASOUND_FIX_H + +/** + * Initialize the patch module + */ +void patch_asound_fix_init(); + +#endif diff --git a/src/main/hook/patch/blacklist-url.c b/src/main/hook/patch/blacklist-url.c new file mode 100644 index 0000000..cd28d99 --- /dev/null +++ b/src/main/hook/patch/blacklist-url.c @@ -0,0 +1,66 @@ +#define LOG_MODULE "patch-blacklist-url" + +#include + +#include "capnhook/hook/lib.h" + +#include "util/list.h" +#include "util/log.h" +#include "util/mem.h" + +struct patch_blacklist_url_list_elem { + struct util_list_node head; + const char* url; +}; + +typedef int (*curl_easy_setopt_t)(int* handle, int option, void* param); + +static bool patch_blacklist_url_initialized; +static curl_easy_setopt_t patch_blacklist_url_real_curl_easy_setopt; +static struct util_list patch_blacklist_url_list; + +int curl_easy_setopt(int* handle, int option, void* param) +{ + if (!patch_blacklist_url_real_curl_easy_setopt) { + patch_blacklist_url_real_curl_easy_setopt = (curl_easy_setopt_t) cnh_lib_get_func_addr("curl_easy_setopt"); + } + + if (patch_blacklist_url_initialized) { + + /* option == 10002 to identify that param has a string */ + if (option == 10002) { + + struct util_list_node* pos; + struct patch_blacklist_url_list_elem* elem; + + for (pos = patch_blacklist_url_list.head; pos != NULL; pos = pos->next) { + elem = containerof(pos, struct patch_blacklist_url_list_elem, head); + + if (!memcmp(param, elem->url, strlen(elem->url))) { + log_debug("Blocking url %s", elem->url); + return patch_blacklist_url_real_curl_easy_setopt(handle, option, (void*) "does-not-exist"); + } + } + } + } + + return patch_blacklist_url_real_curl_easy_setopt(handle, option, param); +} + +void patch_blacklist_url_init(void) +{ + util_list_init(&patch_blacklist_url_list); + patch_blacklist_url_initialized = true; + log_info("Initialized"); +} + +void patch_blacklist_url_add(const char* url) +{ + struct patch_blacklist_url_list_elem* elem; + + elem = util_xmalloc(sizeof(struct patch_blacklist_url_list_elem)); + elem->url = strdup(url); + + util_list_append(&patch_blacklist_url_list, &elem->head); + log_info("Add url %s to blacklist", url); +} \ No newline at end of file diff --git a/src/main/hook/patch/blacklist-url.h b/src/main/hook/patch/blacklist-url.h new file mode 100644 index 0000000..5e69d7d --- /dev/null +++ b/src/main/hook/patch/blacklist-url.h @@ -0,0 +1,19 @@ +/** + * Blacklist calls to offical update and game server (Prime and up). + */ +#ifndef PATCH_BLACKLIST_URL_H +#define PATCH_BLACKLIST_URL_H + +/** + * Initialize patch module + */ +void patch_blacklist_url_init(void); + +/** + * Add an url to block to the blacklist + * + * @param url Url to block + */ +void patch_blacklist_url_add(const char* url); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/block-keyboard-grab.c b/src/main/hook/patch/block-keyboard-grab.c new file mode 100644 index 0000000..7614fed --- /dev/null +++ b/src/main/hook/patch/block-keyboard-grab.c @@ -0,0 +1,36 @@ +#define LOG_MODULE "block-keyboard-grab" + +#include +#include + +#include "capnhook/hook/lib.h" + +#include "util/log.h" + +typedef int (*XGrabKeyboard_t)(Display* display, Window window, Bool owner_events, int pointer_mode, int keyboard_mode, + Time time); + +static XGrabKeyboard_t _patch_main_loop_real_XGrabKeyboard; + +static bool _patch_block_keyboard_grab_enable; + +int XGrabKeyboard(Display* display, Window window, Bool owner_events, int pointer_mode, int keyboard_mode, Time time) +{ + if (!_patch_main_loop_real_XGrabKeyboard) { + _patch_main_loop_real_XGrabKeyboard = (XGrabKeyboard_t) cnh_lib_get_func_addr("XGrabKeyboard"); + } + + if (_patch_block_keyboard_grab_enable) { + log_debug("Block keyboard grabbing"); + return 0; + } + + return _patch_main_loop_real_XGrabKeyboard(display, window, owner_events, pointer_mode, keyboard_mode, time); +} + +void patch_block_keyboard_grab_init() +{ + _patch_block_keyboard_grab_enable = true; + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/block-keyboard-grab.h b/src/main/hook/patch/block-keyboard-grab.h new file mode 100644 index 0000000..1b9d767 --- /dev/null +++ b/src/main/hook/patch/block-keyboard-grab.h @@ -0,0 +1,10 @@ +#ifndef PATCH_BLOCK_KEYBOARD_GRAB_H +#define PATCH_BLOCK_KEYBOARD_GRAB_H + +/** + * Enable blocking of X11 keyboard grabbing which causes Prime to not go to window mode and behave weird on desktop + * machines. + */ +void patch_block_keyboard_grab_init(); + +#endif diff --git a/src/main/hook/patch/gfx.c b/src/main/hook/patch/gfx.c new file mode 100644 index 0000000..e093bbe --- /dev/null +++ b/src/main/hook/patch/gfx.c @@ -0,0 +1,125 @@ +#define LOG_MODULE "patch-gfx" + +#include +#include + +#include "capnhook/hook/lib.h" + +#include "util/log.h" +#include "util/str.h" + +#include "gfx.h" + +typedef Window (*XCreateWindow_t)(Display *display, Window parent, int x, int y, unsigned int width, + unsigned int height, unsigned int border_width, int depth, unsigned int _class, Visual *visual, + unsigned long valuemask, XSetWindowAttributes *attributes); +typedef Display* (*XOpenDisplay_t)(const char *display_name); +typedef XVisualInfo* (*glXChooseVisual_t)(Display *dpy, int screen, int *attribList); + +static bool patch_gfx_force_window = false; + +static bool patch_gfx_initialized; +static XCreateWindow_t patch_gfx_real_XCreateWindow; +static XOpenDisplay_t patch_gfx_real_XOpenDisplay; +static glXChooseVisual_t patch_gfx_real_GlXChooseVisual; + +static char* patch_gfx_attrib_list_to_str(int* attrib_list) +{ + int* ptr = attrib_list; + char* str = util_str_dup(""); + char buf[32]; + + while (*ptr != None) { + sprintf(buf, "%d, ", *ptr); + + char* tmp = util_str_merge(str, buf); + + free(str); + str = tmp; + + ptr++; + } + + return str; +} + +Window XCreateWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, + unsigned int border_width, int depth, unsigned int _class, Visual *visual, unsigned long valuemask, + XSetWindowAttributes *attributes) +{ + log_debug("XCreateWindow"); + + if (!patch_gfx_real_XCreateWindow) { + patch_gfx_real_XCreateWindow = (XCreateWindow_t) cnh_lib_get_func_addr("XCreateWindow"); + } + + if (patch_gfx_initialized) { + + if (valuemask == 0x80A) { + log_info("Patching to enable usage of non nvidia cards."); + /* enables usage of non nvidia cards and newer nvidia models */ + valuemask = 0x280A; + } + } + + if (patch_gfx_force_window) { + log_info("Forcing window mode"); + attributes->override_redirect = 0; + } + + return patch_gfx_real_XCreateWindow(display, parent, x, y, width, height, border_width, depth, _class, visual, + valuemask, attributes); +} + +Display *XOpenDisplay(const char *display_name) +{ + if (!patch_gfx_real_XOpenDisplay) { + patch_gfx_real_XOpenDisplay = (XOpenDisplay_t) cnh_lib_get_func_addr("XOpenDisplay"); + } + + log_info("XOpenDisplay: %s", display_name); + + Display* res = patch_gfx_real_XOpenDisplay(display_name); + + if (!res) { + log_error("XOpenDisplay returned NULL. This might indicate that your environment is not properly setup. Check " + " that you have GPU drivers installed and configured properly and that OpenGL hardware acceleration is " + " working (use the command line tool \"glxinfo\")."); + } + + return res; +} + +XVisualInfo* glXChooseVisual(Display *dpy, int screen, int *attribList) +{ + if (!patch_gfx_real_GlXChooseVisual) { + patch_gfx_real_GlXChooseVisual = (glXChooseVisual_t) cnh_lib_get_func_addr("glXChooseVisual"); + } + + char* attrib_list_str =patch_gfx_attrib_list_to_str(attribList); + + log_info("glXChooseVisual: dpy %p, screen %d, attribList %s", dpy, screen, attrib_list_str); + + free(attrib_list_str); + + XVisualInfo* res = patch_gfx_real_GlXChooseVisual(dpy, screen, attribList); + + if (!res) { + log_error("glXChooseVisual returned NULL. It is likely that your current environment does support hardware" + "acceleration. Either you do not have any GPU drivers installed or the drivers are not configured " + "properly. Fix that and use the command line tool \"glxinfo\" for debugging this."); + } + + return res; +} + +void patch_gfx_init() +{ + patch_gfx_initialized = true; + log_info("Initialized"); +} + +void patch_gfx_force_window_mode() +{ + patch_gfx_force_window = true; +} \ No newline at end of file diff --git a/src/main/hook/patch/gfx.h b/src/main/hook/patch/gfx.h new file mode 100644 index 0000000..1065794 --- /dev/null +++ b/src/main/hook/patch/gfx.h @@ -0,0 +1,17 @@ +/** + * Hook module to run the games on non NVIDIA cards + */ +#ifndef PATCH_GFX_H +#define PATCH_GFX_H + +/** + * Initialize the patch module + */ +void patch_gfx_init(); + +/** + * Force the game into window mode + */ +void patch_gfx_force_window_mode(); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/hasp.c b/src/main/hook/patch/hasp.c new file mode 100644 index 0000000..a90177e --- /dev/null +++ b/src/main/hook/patch/hasp.c @@ -0,0 +1,63 @@ +#define LOG_MODULE "patch-hasp" + +#include "sec/hasp/old/hasp.h" + +#include "util/log.h" +#include "util/patch.h" + +static const uint8_t patch_hasp_login_sig[] = { + 0x00, 0xB8, 0x16, 0x00, + 0x00, 0x00, 0x8B, 0x4C, + 0x24, 0x14, 0x8B, 0x74, + 0x24, 0x10, 0x8B, 0x54 +}; + +static const uint8_t patch_hasp_id_sig[] = { + 0x53, 0x83, 0xEC, 0x48, + 0xC7, 0x44, 0x24, 0x10 +}; + +void patch_hasp_init(const uint8_t* key_data, size_t len) +{ + void* func_api_login; + void* func_api_logout; + void* func_api_decrypt; + void* func_api_getid; + + func_api_login = util_patch_find_signiture(patch_hasp_login_sig, + sizeof(patch_hasp_login_sig), -16, (void*) 0x80c0000, (void*) 0x80f0000, + 16); + + if (!func_api_login) { + log_error("Could not find ApiLogin address"); + return; + } + + log_debug("ApiLogin at %p", func_api_login); + + func_api_logout = (void*) (((size_t) func_api_login) - 0x130); + log_debug("assuming ApiLogout at %p", func_api_logout); + + func_api_decrypt = (void*) (((size_t) func_api_login) - 0x200); + log_debug("assuming ApiDecrypt at %p", func_api_decrypt); + + func_api_getid = util_patch_find_signiture(patch_hasp_id_sig, + sizeof(patch_hasp_id_sig), 0, (void*) 0x8060000, (void*) 0x8090000, + 16); + + if (!func_api_getid) { + log_error("Could not find ApiGetid address"); + return; + } + + log_debug("ApiGetid at %p", func_api_getid); + + util_patch_function((uintptr_t) func_api_login, sec_hasp_api_login); + util_patch_function((uintptr_t) func_api_logout, sec_hasp_api_logout); + util_patch_function((uintptr_t) func_api_decrypt, sec_hasp_api_decrypt); + util_patch_function((uintptr_t) func_api_getid, sec_hasp_api_getid); + + sec_hasp_init(key_data, len); + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/hasp.h b/src/main/hook/patch/hasp.h new file mode 100644 index 0000000..7c5c5f4 --- /dev/null +++ b/src/main/hook/patch/hasp.h @@ -0,0 +1,15 @@ +/** + * Patch module to install the dongle emulator for a hasp dongle + */ +#ifndef PATCH_HASP_H +#define PATCH_HASP_H + +/** + * Initialize the patch module + * + * @param key_data Pointer to loaded key data (key table) + * @param len Length of the buffer + */ +void patch_hasp_init(const uint8_t* key_data, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/hasp2.c b/src/main/hook/patch/hasp2.c new file mode 100644 index 0000000..81e8e81 --- /dev/null +++ b/src/main/hook/patch/hasp2.c @@ -0,0 +1,40 @@ +#define LOG_MODULE "patch-hasp" + +#include + +#include "sec/hasp/server.h" + +#include "util/log.h" + +static pthread_t patch_hasp_thread; + +static void* patch_hasp_thread_run(void* args) +{ + log_info("Running hasp server thread"); + + sec_hasp_server_run(); + + log_info("Finished hasp server thread"); + + return NULL; +} + +void patch_hasp_init(const uint8_t* key_data, size_t len) +{ + sec_hasp_server_init(key_data, len); + + /* Run the daemon in another thread */ + pthread_create(&patch_hasp_thread, NULL, + patch_hasp_thread_run, NULL); + + log_info("Initialized"); +} + +void patch_hasp_shutdown(void) +{ + log_info("Shutting down"); + + sec_hasp_server_shutdown(); + + pthread_join(patch_hasp_thread_run, NULL); +} \ No newline at end of file diff --git a/src/main/hook/patch/hasp2.h b/src/main/hook/patch/hasp2.h new file mode 100644 index 0000000..4d0e1d6 --- /dev/null +++ b/src/main/hook/patch/hasp2.h @@ -0,0 +1,22 @@ +/** + * Patch module to install the dongle emulator for a hasp dongle (runs a local server) + */ +#ifndef PATCH_HASP2_H +#define PATCH_HASP2_H + +#include + +/** + * Initialize the patch module and run the hasp server + * + * @param key_data Pointer to loaded key data (attributes and key table) + * @param len Length of the buffer + */ +void patch_hasp_init(const uint8_t* key_data, size_t len); + +/** + * Shutdown and remove the emulator + */ +void patch_hasp_shutdown(void); + +#endif diff --git a/src/main/hook/patch/hdd-check.c b/src/main/hook/patch/hdd-check.c new file mode 100644 index 0000000..7ee692e --- /dev/null +++ b/src/main/hook/patch/hdd-check.c @@ -0,0 +1,257 @@ +#define LOG_MODULE "patch-hdd-check" + +#include +#include +#include +#include + +#include "capnhook/hook/filehook.h" +#include "capnhook/hook/iohook.h" + +#include "util/log.h" + +static const uint8_t* patch_hdd_check_boot_area; + +static int patch_hdd_check_fd = -1; +static FILE* patch_hdd_check_file = NULL; +static size_t patch_hdd_check_boot_area_size; +static size_t patch_hdd_check_boot_area_fp; + +static enum cnh_result patch_hdd_check_filehook(struct cnh_filehook_irp *irp) +{ + enum cnh_result result; + + result = CNH_RESULT_SUCCESS; + + assert(irp); + + if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN && (!patch_hdd_check_file || irp->file != patch_hdd_check_file)) { + return cnh_filehook_invoke_next(irp); + } + + switch (irp->op) { + case CNH_FILEHOOK_IRP_OP_OPEN: + { + if (!strcmp(irp->open_filename, "/dev/hdd")) { + patch_hdd_check_file = cnh_filehook_open_dummy_file_handle(); + irp->file = patch_hdd_check_file; + + log_debug("/dev/hdd fopened: %p", patch_hdd_check_file); + } else { + return cnh_filehook_invoke_next(irp); + } + + break; + } + + case CNH_FILEHOOK_IRP_OP_READ: + { + uint32_t len; + + log_debug("fread, pos 0x%X, bytes %d", patch_hdd_check_boot_area_fp, irp->read.nbytes); + + /* read up to eof, only */ + if (patch_hdd_check_boot_area_fp + irp->read.nbytes < patch_hdd_check_boot_area_size) { + len = irp->read.nbytes; + } else { + len = patch_hdd_check_boot_area_size - patch_hdd_check_boot_area_fp; + } + + memcpy(irp->read.bytes, patch_hdd_check_boot_area + patch_hdd_check_boot_area_fp, len); + + patch_hdd_check_boot_area_fp += len; + irp->read.pos = len; + + break; + } + + case CNH_FILEHOOK_IRP_OP_WRITE: + { + // Apparently, Prime starts writing to /dev/hdd, stub + log_debug("fwrite"); + + break; + } + + case CNH_FILEHOOK_IRP_OP_SEEK: + { + /* TODO this is not verified to work, yet + not needed on older games as it seems (zero for example) + patch seeking for CRED file so we can avoid a huge hdd file */ + if (irp->seek_offset == 0x1F70000) { + log_debug("fseek: fake for hdd (0x1F70000)"); + irp->seek_offset = 1024; + irp->seek_origin = SEEK_SET; + } else if (irp->seek_offset == 0x1F70800) { + log_debug("fseek: fake for hdd (0x1F70800)"); + irp->seek_offset = 1024 + 2048; + irp->seek_origin = SEEK_SET; + } + + switch (irp->seek_origin) { + case SEEK_SET: + patch_hdd_check_boot_area_fp = (size_t) irp->seek_offset; + break; + + case SEEK_CUR: + patch_hdd_check_boot_area_fp += irp->seek_offset; + break; + + case SEEK_END: + patch_hdd_check_boot_area_fp = patch_hdd_check_boot_area_size; + patch_hdd_check_boot_area_fp += irp->seek_offset; + break; + + default: + log_die("Unhandled origin %d", irp->seek_origin); + break; + } + + log_debug("fseek, offset 0x%X, origin %d", irp->seek_offset, irp->seek_origin); + + break; + } + + case CNH_FILEHOOK_IRP_OP_CLOSE: + patch_hdd_check_file = NULL; + + log_debug("/dev/hdd fclosed"); + + /* Actually close dummy handle */ + return cnh_filehook_invoke_next(irp); + + default: + log_warn("Unhandled file op: %d", irp->op); + break; + } + + return result; +} + +static enum cnh_result patch_hdd_check_iohook(struct cnh_iohook_irp *irp) +{ + enum cnh_result result; + + result = CNH_RESULT_SUCCESS; + + assert(irp); + + if ((irp->op != CNH_IOHOOK_IRP_OP_OPEN && irp->fd != patch_hdd_check_fd) && + (irp->op != CNH_IOHOOK_IRP_OP_FDOPEN || irp->fdopen_fd != patch_hdd_check_fd)) { + return cnh_iohook_invoke_next(irp); + } + + switch (irp->op) { + case CNH_IOHOOK_IRP_OP_OPEN: + { + if (!strcmp(irp->open_filename, "/dev/hdd")) { + patch_hdd_check_fd = cnh_iohook_open_dummy_fd(); + irp->fd = patch_hdd_check_fd; + + log_debug("/dev/hdd opened: %d", patch_hdd_check_fd); + } else { + return cnh_iohook_invoke_next(irp); + } + + break; + } + + case CNH_IOHOOK_IRP_OP_FDOPEN: + { + patch_hdd_check_file = cnh_filehook_open_dummy_file_handle(); + irp->fdopen_res = patch_hdd_check_file; + + log_debug("/dev/hdd fdopen: %p", patch_hdd_check_file); + + break; + } + + case CNH_IOHOOK_IRP_OP_READ: + { + uint32_t len; + + log_debug("read, pos 0x%X, bytes %d", patch_hdd_check_boot_area_fp, irp->read.nbytes); + + /* read up to eof, only */ + if (patch_hdd_check_boot_area_fp + irp->read.nbytes < patch_hdd_check_boot_area_size) { + len = irp->read.nbytes; + } else { + len = patch_hdd_check_boot_area_size - patch_hdd_check_boot_area_fp; + } + + memcpy(irp->read.bytes, patch_hdd_check_boot_area + patch_hdd_check_boot_area_fp, len); + + patch_hdd_check_boot_area_fp += len; + irp->read.pos = len; + + break; + } + + case CNH_IOHOOK_IRP_OP_SEEK: + { + log_debug("Seek: offset %d, orig %d", irp->seek_offset, irp->seek_origin); + + switch (irp->seek_origin) { + case SEEK_SET: + patch_hdd_check_boot_area_fp = (size_t) irp->seek_offset; + break; + + case SEEK_CUR: + patch_hdd_check_boot_area_fp += irp->seek_offset; + break; + + case SEEK_END: + patch_hdd_check_boot_area_fp = patch_hdd_check_boot_area_size; + patch_hdd_check_boot_area_fp += irp->seek_offset; + break; + + default: + log_die("Unhandled origin %d", irp->seek_origin); + break; + } + + irp->seek_pos = patch_hdd_check_boot_area_fp; + + break; + } + + case CNH_IOHOOK_IRP_OP_IOCTL: + { + /* hdd information request */ + if (irp->ioctl_req == HDIO_GET_IDENTITY) { + log_info("ioctl: fake ioctl for hdd (0x30D)"); + /* just return empty data instead of real data */ + struct hd_driveid* drid = (struct hd_driveid*) irp->ioctl.bytes; + memset(drid, 0, sizeof(struct hd_driveid)); + irp->ioctl.pos = 0; + } + + break; + } + + case CNH_IOHOOK_IRP_OP_CLOSE: + patch_hdd_check_fd = -1; + + log_debug("/dev/hdd closed"); + + /* Actually close dummy handle */ + return cnh_iohook_invoke_next(irp); + + default: + log_warn("Unhandled io op: %d", irp->op); + break; + } + + return result; +} + +void patch_hdd_check_init(const uint8_t* boot_area_buffer, size_t len) +{ + patch_hdd_check_boot_area = boot_area_buffer; + patch_hdd_check_boot_area_size = len; + + cnh_iohook_push_handler(patch_hdd_check_iohook); + cnh_filehook_push_handler(patch_hdd_check_filehook); + + log_info("Initialized: boot area size %d", patch_hdd_check_boot_area_size); +} \ No newline at end of file diff --git a/src/main/hook/patch/hdd-check.h b/src/main/hook/patch/hdd-check.h new file mode 100644 index 0000000..a2822ab --- /dev/null +++ b/src/main/hook/patch/hdd-check.h @@ -0,0 +1,16 @@ +/** + * Patch the HDD check which (normally) ensures that the game is running on a + * real HDD with valid data written to the boot area + */ +#ifndef PATCH_HDD_CHECK_H +#define PATCH_HDD_CHECK_H + +/** + * Initialize the patch module + * + * @param boot_area_buffer Buffer with boot area sections of a real drive + * @param len Length of buffer provided + */ +void patch_hdd_check_init(const uint8_t* boot_area_buffer, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/hook-mon.c b/src/main/hook/patch/hook-mon.c new file mode 100644 index 0000000..b08a29e --- /dev/null +++ b/src/main/hook/patch/hook-mon.c @@ -0,0 +1,36 @@ +#define LOG_MODULE "patch-hook-mon" + +#include "capnhook/hooklib/filehook-mon.h" +#include "capnhook/hooklib/fileopen-mon.h" +#include "capnhook/hooklib/fshook-mon.h" +#include "capnhook/hooklib/iohook-mon.h" +#include "capnhook/hooklib/usbhook-mon.h" + +#include "util/log.h" + +void patch_hook_mon_init(bool monitor_io, bool monitor_file, bool monitor_fs, bool monitor_usb, bool monitor_open) +{ + if (monitor_open) { + cnh_iohook_push_handler(cnh_fileopen_mon_iohook); + cnh_filehook_push_handler(cnh_fileopen_mon_filehook); + cnh_fshook_push_handler(cnh_fileopen_mon_fshook); + } + + if (monitor_io) { + cnh_iohook_push_handler(cnh_iohook_mon); + } + + if (monitor_file) { + cnh_filehook_push_handler(cnh_filehook_mon); + } + + if (monitor_fs) { + cnh_fshook_push_handler(cnh_fshook_mon); + } + + if (monitor_usb) { + cnh_usbhook_push_handler(cnh_usbhook_mon); + } + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/hook-mon.h b/src/main/hook/patch/hook-mon.h new file mode 100644 index 0000000..4c5a1c6 --- /dev/null +++ b/src/main/hook/patch/hook-mon.h @@ -0,0 +1,20 @@ +/** + * Patch module for monitoring various system calls. For development and debugging purpose. + */ +#ifndef PATCH_HOOK_MON_H +#define PATCH_HOOK_MON_H + +#include + +/** + * Initialize the module + * + * @param monitor_io True to enable monitoring of I/O calls + * @param monitor_file True to enable monitoring of file related calls + * @param monitor_fs True to enable monitoring of file system related calls + * @param monitor_usb True to enable monitoring of usb calls + * @param monitor_open True to enable monitoring of any file/device open calls + */ +void patch_hook_mon_init(bool monitor_io, bool monitor_file, bool monitor_fs, bool monitor_usb, bool monitor_open); + +#endif diff --git a/src/main/hook/patch/main-loop.c b/src/main/hook/patch/main-loop.c new file mode 100644 index 0000000..bdcc7ff --- /dev/null +++ b/src/main/hook/patch/main-loop.c @@ -0,0 +1,235 @@ +#define LOG_MODULE "patch-main-loop" + +#include +#include +#include + +#include "ptapi/io/x11-input-hook.h" + +#include "capnhook/hook/lib.h" +#include "capnhook/hook/sig.h" + +#include "hook/patch/main-loop.h" + +#include "util/log.h" + +typedef int (*XPending_t)(Display* display); +typedef int (*XNextEvent_t)(Display* display, XEvent* event_return); + +typedef const struct ptapi_io_x11_input_hook_handler* (*ptapi_io_x11_input_handler_hook_t)(void); + +static XPending_t _patch_main_loop_real_XPending; +static XNextEvent_t _patch_main_loop_real_XNextEvent; + +static void _patch_main_loop_sigalarm_handler(int signal, __sighandler_t orig_handler); +static bool _patch_main_loop_is_key_repeated(Display* display, XEvent* event); + +static __sighandler_t _patch_main_loop_sigalarm_orig_handler; +static bool _patch_main_loop_disable_built_in_inputs; +static bool _patch_main_loop_disable_mk3_linux_ports_built_in_inputs; +static pthread_mutex_t _patch_main_loop_input_handlers_lock; +static const struct ptapi_io_x11_input_hook_handler** _patch_main_loop_input_handlers; +static size_t _patch_main_loop_num_input_handlers; + +void patch_main_loop_init(bool fix_sigalarm_main_loop, bool disable_built_in_inputs, bool disable_mk3_linux_port_inputs) +{ + _patch_main_loop_disable_built_in_inputs = disable_built_in_inputs; + _patch_main_loop_disable_mk3_linux_ports_built_in_inputs = disable_mk3_linux_port_inputs; + + _patch_main_loop_sigalarm_orig_handler = NULL; + + if (fix_sigalarm_main_loop) { + log_info("Applying sigalarm handler fix"); + cnh_sig_install(SIGALRM, _patch_main_loop_sigalarm_handler); + } + + pthread_mutex_init(&_patch_main_loop_input_handlers_lock, NULL); + + log_info("Initialized, disable built-in inputs %d", disable_built_in_inputs); +} + +void patch_main_loop_add_x11_input_handler(const char* lib_with_handler_impl) +{ + const struct ptapi_io_x11_input_hook_handler** new_array; + size_t new_size; + void* lib_handle; + ptapi_io_x11_input_handler_hook_t input_handler_hook_func; + const struct ptapi_io_x11_input_hook_handler* input_handler_hook; + + if (!lib_with_handler_impl) { + log_die("No library path with input handler implementation specified"); + } + + log_info("Loading library with x11-input-handler api implementation %s", lib_with_handler_impl); + + lib_handle = cnh_lib_load(lib_with_handler_impl); + + input_handler_hook_func = (ptapi_io_x11_input_handler_hook_t) + cnh_lib_get_func_addr_handle(lib_handle, "ptapi_io_x11_input_handler_hook"); + + input_handler_hook = input_handler_hook_func(); + + if (!input_handler_hook) { + log_warn("Library impl %s returned NULL hook handler, skipping", lib_with_handler_impl); + return; + } + + pthread_mutex_lock(&_patch_main_loop_input_handlers_lock); + + new_size = _patch_main_loop_num_input_handlers + 1; + new_array = realloc(_patch_main_loop_input_handlers, new_size * sizeof(struct ptapi_io_input_hook_handler*)); + + if (new_array != NULL) { + _patch_main_loop_input_handlers = new_array; + + _patch_main_loop_input_handlers[_patch_main_loop_num_input_handlers++] = input_handler_hook; + } else { + log_die("Out of memory"); + } + + pthread_mutex_unlock(&_patch_main_loop_input_handlers_lock); + + log_debug("Added input handler %p of lib %s", input_handler_hook, lib_with_handler_impl); +} + +int XPending(Display* display) +{ + if (!_patch_main_loop_real_XPending) { + _patch_main_loop_real_XPending = (XPending_t) cnh_lib_get_func_addr("XPending"); + } + + /* poll the original SIGALRM handler to drive the pump engine */ + if (_patch_main_loop_sigalarm_orig_handler) { + _patch_main_loop_sigalarm_orig_handler(SIGALRM); + } + + return _patch_main_loop_real_XPending(display); +} + +int XNextEvent(Display* display, XEvent* event_return) +{ + int res; + KeySym key; + + if (!_patch_main_loop_real_XNextEvent) { + _patch_main_loop_real_XNextEvent = (XNextEvent_t) cnh_lib_get_func_addr("XNextEvent"); + } + + /* Trap KeyPress and KeyRelease events */ + res = _patch_main_loop_real_XNextEvent(display, event_return); + + switch (event_return->type) { + case KeyPress: + { + /* Mask out the modifier states X11 sets and read again */ + event_return->xkey.state &= ~ShiftMask; + event_return->xkey.state &= ~LockMask; + + XLookupString(&event_return->xkey, 0, 0, &key, 0); + + /* Swallow some keys to disable them */ + if (_patch_main_loop_disable_built_in_inputs) { + if (key == XK_F1 || key == XK_F2 || key == XK_F3) { + event_return->type = GenericEvent; + } + } + + /* Swallow all built-in inputs of the MK3 ports */ + if (_patch_main_loop_disable_mk3_linux_ports_built_in_inputs) { + if (key == XK_g || key == XK_h || key == XK_j || key == XK_k || key == XK_l || + key == XK_q || key == XK_e || key == XK_s || key == XK_z || key == XK_c || + key == XK_KP_7 || key == XK_KP_9 || key == XK_KP_5 || key == XK_KP_1 || key == XK_KP_3) { + event_return->type = GenericEvent; + } + } + + /* Dispatch to external input handlers */ + if (_patch_main_loop_num_input_handlers > 0) { + pthread_mutex_lock(&_patch_main_loop_input_handlers_lock); + + for (size_t i = 0; i < _patch_main_loop_num_input_handlers; i++) { + if (_patch_main_loop_input_handlers[i]->dispatch_key_press) { + _patch_main_loop_input_handlers[i]->dispatch_key_press(key); + } + } + + pthread_mutex_unlock(&_patch_main_loop_input_handlers_lock); + } + + break; + } + + case KeyRelease: + { + /* Mask out the modifier states X11 sets and read again */ + event_return->xkey.state &= ~ShiftMask; + event_return->xkey.state &= ~LockMask; + + XLookupString(&event_return->xkey, 0, 0, &key, 0); + + /* Swallow some keys to disable them */ + if (_patch_main_loop_disable_built_in_inputs) { + if (key == XK_F1 || key == XK_F2 || key == XK_F3) { + event_return->type = GenericEvent; + } + } + + /* Swallow all built-in inputs of the MK3 ports */ + if (_patch_main_loop_disable_mk3_linux_ports_built_in_inputs) { + if (key == XK_g || key == XK_h || key == XK_j || key == XK_k || key == XK_l || + key == XK_q || key == XK_e || key == XK_s || key == XK_z || key == XK_c || + key == XK_KP_7 || key == XK_KP_9 || key == XK_KP_5 || key == XK_KP_1 || key == XK_KP_3) { + event_return->type = GenericEvent; + } + } + + /* Dispatch to external input handlers */ + if (_patch_main_loop_num_input_handlers > 0) { + /* Dispatch release if key is not repeated */ + if (!_patch_main_loop_is_key_repeated(display, event_return)) { + pthread_mutex_lock(&_patch_main_loop_input_handlers_lock); + + for (size_t i = 0; i < _patch_main_loop_num_input_handlers; i++) { + if (_patch_main_loop_input_handlers[i]->dispatch_key_release) { + _patch_main_loop_input_handlers[i]->dispatch_key_release(key); + } + } + + pthread_mutex_unlock(&_patch_main_loop_input_handlers_lock); + } + } + + break; + } + + default: + break; + } + + return res; +} + +static void _patch_main_loop_sigalarm_handler(int signal, __sighandler_t orig_handler) +{ + /* do nothing on sigalrm */ + _patch_main_loop_sigalarm_orig_handler = orig_handler; +} + +static bool _patch_main_loop_is_key_repeated(Display* display, XEvent* event) +{ + /* When a key is repeated, there will be two events: released, followed by another immediate pressed. + So check to see if another pressed is present */ + if (!_patch_main_loop_real_XPending(display)) { + return false; + } + + XEvent e; + XPeekEvent(display, &e); + + if (e.type == KeyPress && e.xkey.keycode == event->xkey.keycode && (e.xkey.time - event->xkey.time) < 2) { + _patch_main_loop_real_XNextEvent(display, &e); + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/main/hook/patch/main-loop.h b/src/main/hook/patch/main-loop.h new file mode 100644 index 0000000..83f9e2d --- /dev/null +++ b/src/main/hook/patch/main-loop.h @@ -0,0 +1,59 @@ +/** + * This module fixes different things on the main loop of the game. + * + * First, it hooks into the X11 event loop to grab and dispatch inputs for + * further IO hooks or input emulation. + * + * The second thing is quite stupid and fixes crashing on newer kernels + * because AM used sigalarm to drive certain parts of their engine. + * This only applies to one generation of the game engine which includes the + * releases: Exceed, Exceed2, Zero and NX + * + * Detailed analysis: + * + * AM decided to poll the CPIU::Update function which contains updating + * the render state as well as decoding sound data, using the signal SIGALRM. + * This signal is triggered periodically and drives the whole engine. + * But, starting kernel versions newer than 2.4.27 something changed which + * causes the game to deadlock an _ALL_ kernels newer than said version. Trying + * to fix the deadlock issue makes things even worse and only introduces + * segfaults with random crashes. So this is not the right approach. + * Starting NX2 AM used a newer kernel and apparently got the same problem + * with their OS. They fixed it by moving music_send_data from CPIU::Update() + * to the IO thread. Splitting CPIU::Update that way, this seems to work fine. + * However, trying this with the old games lacking the patch, we are getting + * segfaults (again). + * It looks like there is an issue with firing and/or handling of SIGALRM in + * another thread than the main thread (don't ask me why). This leads to the + * solution of the problem: moving the call of CPIU::Update()to the main + * thread which is only pumping XEvents (i.e. nothing). + * So let's give the main thread some work: Detouring XPending and calling + * the registered signal handler for SIGALRM of the game in there and muting + * signal handling for SIGALRM. This solves all deadlocking and segfault + * issues which are related to audio playback in combination with decoding a + * video. + */ +#ifndef PATCH_MAIN_LOOP_H +#define PATCH_MAIN_LOOP_H + +#include +#include + +/** + * Initialize the patch module + * + * @param fix_sigalarm_main_loop True to enable the sigalarm thread fix (doc see above) + * @param disable_built_in_inputs Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3) + * @param disable_mk3_linux_port_inputs Disable the built-in keyboard inputs on the MK3 linux ports + */ +void patch_main_loop_init(bool fix_sigalarm_main_loop, bool disable_built_in_inputs, + bool disable_mk3_linux_port_inputs); + +/** + * Register a handler (multiple possible) to receive X11 input events, e.g. key pressed, released + * + * @param lib_with_handler_impl Path to a library implementing the x11-input-handler API + */ +void patch_main_loop_add_x11_input_handler(const char* lib_with_handler_impl); + +#endif diff --git a/src/main/hook/patch/microdog34.c b/src/main/hook/patch/microdog34.c new file mode 100644 index 0000000..110734c --- /dev/null +++ b/src/main/hook/patch/microdog34.c @@ -0,0 +1,72 @@ +#define LOG_MODULE "patch-microdog34" + +#define USBDOG_ENDPOINT "/dev/usbdog" + +#include +#include +#include + +#include "capnhook/hook/iohook.h" + +#include "sec/microdog34/microdog34.h" + +#include "util/log.h" + +static int _patch_microdog34_fd = -1; + +enum cnh_result patch_microdog34_iohook(struct cnh_iohook_irp *irp); + +void patch_microdog34_init(const uint8_t* key_data, size_t len) +{ + sec_microdog34_init(key_data, len); + + cnh_iohook_push_handler(patch_microdog34_iohook); + log_info("Initialized"); +} + +enum cnh_result patch_microdog34_iohook(struct cnh_iohook_irp *irp) +{ + if (irp->op != CNH_IOHOOK_IRP_OP_OPEN && irp->fd != _patch_microdog34_fd) { + return cnh_iohook_invoke_next(irp); + } + + switch (irp->op) { + case CNH_IOHOOK_IRP_OP_OPEN: + { + if (!strcmp(irp->open_filename, USBDOG_ENDPOINT)) { + _patch_microdog34_fd = cnh_iohook_open_dummy_fd(); + irp->fd = _patch_microdog34_fd; + return CNH_RESULT_SUCCESS; + } + + return cnh_iohook_invoke_next(irp); + } + + case CNH_IOHOOK_IRP_OP_CLOSE: + { + assert(irp->fd); + + cnh_iohook_close_dummy_fd(irp->fd); + _patch_microdog34_fd = -1; + + return CNH_RESULT_SUCCESS; + } + + case CNH_IOHOOK_IRP_OP_READ: + { + /* Just stub and return success */ + return CNH_RESULT_SUCCESS; + } + + case CNH_IOHOOK_IRP_OP_IOCTL: + { + irp->ioctl.pos = (size_t) sec_microdog34_process(irp->ioctl_req, irp->ioctl.bytes); + + return CNH_RESULT_SUCCESS; + } + + default: + log_die("Unhandled operation: %d", irp->op); + return CNH_RESULT_OTHER_ERROR; + } +} \ No newline at end of file diff --git a/src/main/hook/patch/microdog34.h b/src/main/hook/patch/microdog34.h new file mode 100644 index 0000000..9ec63c4 --- /dev/null +++ b/src/main/hook/patch/microdog34.h @@ -0,0 +1,15 @@ +/** + * Patch module to install the dongle emulator for a microdog 3.4 + */ +#ifndef PATCH_MICRODOG34_H +#define PATCH_MICRODOG34_H + +/** + * Initialize the patch module + * + * @param key_data Pointer to loaded key data (dog attributes, key table) + * @param len Length of the buffer + */ +void patch_microdog34_init(const uint8_t* key_data, size_t len); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/microdog40.c b/src/main/hook/patch/microdog40.c new file mode 100644 index 0000000..0f53168 --- /dev/null +++ b/src/main/hook/patch/microdog40.c @@ -0,0 +1,40 @@ +#define LOG_MODULE "patch-microdog40" + +#include + +#include "sec/microdog40/microdog40d.h" + +#include "util/log.h" + +static pthread_t patch_microdog40_thread; + +static void* patch_microdog40_thread_run(void* args) +{ + log_info("Running daemon thread"); + + sec_microdog40d_run(); + + log_info("Finished daemon thread"); + + return NULL; +} + +void patch_microdog40_init(const uint8_t* key_data, size_t len) +{ + sec_microdog40d_init(key_data, len); + + /* Run the daemon in another thread */ + pthread_create(&patch_microdog40_thread, NULL, + patch_microdog40_thread_run, NULL); + + log_info("Initialized"); +} + +void patch_microdog40_shutdown(void) +{ + log_info("Shutting down"); + + sec_microdog40d_shutdown(); + + pthread_join(patch_microdog40_thread, NULL); +} \ No newline at end of file diff --git a/src/main/hook/patch/microdog40.h b/src/main/hook/patch/microdog40.h new file mode 100644 index 0000000..4cef422 --- /dev/null +++ b/src/main/hook/patch/microdog40.h @@ -0,0 +1,20 @@ +/** + * Patch module to install the dongle emulator for a microdog 4.0 + */ +#ifndef PATCH_MICRODOG34_H +#define PATCH_MICRODOG34_H + +/** + * Initialize the patch module + * + * @param key_data Pointer to loaded key data (dog attributes, key table) + * @param len Length of the buffer + */ +void patch_microdog40_init(const uint8_t* key_data, size_t len); + +/** + * Shutdown and remove the emulator + */ +void patch_microdog40_shutdown(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/mounts.c b/src/main/hook/patch/mounts.c new file mode 100644 index 0000000..cd8f8c0 --- /dev/null +++ b/src/main/hook/patch/mounts.c @@ -0,0 +1,105 @@ +#define LOG_MODULE "patch-mounts" + +#include +#include +#include +#include + +#include "capnhook/hook/filehook.h" + +#include "util/log.h" + +/* looks odd but hddd is intentional */ +static const char* patch_mount_proc_mount_file = "/dev/hddd /mnt/hd"; + +static FILE* patch_mount_file = NULL; +static bool patch_mount_eof; + +static enum cnh_result patch_hdd_check_filehook(struct cnh_filehook_irp *irp) +{ + enum cnh_result result; + + result = CNH_RESULT_SUCCESS; + + assert(irp); + + if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN && irp->file != patch_mount_file) { + return cnh_filehook_invoke_next(irp); + } + + switch (irp->op) { + case CNH_FILEHOOK_IRP_OP_OPEN: + { + if (!strcmp(irp->open_filename, "/proc/mounts")) { + patch_mount_file = cnh_filehook_open_dummy_file_handle(); + irp->file = patch_mount_file; + + patch_mount_eof = false; + + log_debug("/proc/mounts opened: %p", irp->file); + } else { + return cnh_filehook_invoke_next(irp); + } + + break; + } + + case CNH_FILEHOOK_IRP_OP_FGETS: + { + size_t size; + + log_info("/proc/mounts fake read"); + + size = strlen(patch_mount_proc_mount_file) + 1; + + /* +1: copy the null terminator, too */ + memcpy(&irp->read.bytes[irp->read.pos], patch_mount_proc_mount_file, size); + irp->read.pos += size; + + patch_mount_eof = true; + + break; + } + + case CNH_FILEHOOK_IRP_OP_SEEK: + { + /* Ignore seeking */ + + break; + } + + case CNH_FILEHOOK_IRP_OP_TELL: + { + /* Return size */ + irp->tell_offset = strlen(patch_mount_proc_mount_file) + 1; + + break; + } + + case CNH_FILEHOOK_IRP_OP_EOF: + { + irp->eof = patch_mount_eof; + break; + } + + case CNH_FILEHOOK_IRP_OP_CLOSE: + { + patch_mount_file = NULL; + + /* Actually close dummy handle */ + return cnh_filehook_invoke_next(irp); + } + + default: + log_warn("Unhandled file op: %d", irp->op); + break; + } + + return result; +} + +void patch_mounts_init(void) +{ + cnh_filehook_push_handler(patch_hdd_check_filehook); + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/mounts.h b/src/main/hook/patch/mounts.h new file mode 100644 index 0000000..6651c7c --- /dev/null +++ b/src/main/hook/patch/mounts.h @@ -0,0 +1,13 @@ +/** + * Starting NX2, the game is checking /proc/mounts for the entry + * "/dev/hddd /mnt/hd" -> hook and inject that + */ +#ifndef PATCH_MOUNTS_H +#define PATCH_MOUNTS_H + +/** + * Initialize the patch module + */ +void patch_mounts_init(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/net-profile.c b/src/main/hook/patch/net-profile.c new file mode 100644 index 0000000..7a9b038 --- /dev/null +++ b/src/main/hook/patch/net-profile.c @@ -0,0 +1,594 @@ +#define LOG_MODULE "patch-net-profile" + +#include +#include + +#include "asset/nx2/lib/usb-rank.h" +#include "asset/nx2/lib/usb-save.h" + +#include "capnhook/hook/filehook.h" + +#include "pumpnet/lib/profile-token.h" +#include "pumpnet/lib/pumpnet.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/time.h" + +#define PUMPNET_MAX_NUM_PLAYERS 2 +#define PUMPNET_PROFILE_FILE_NAME "pumpnet.bin" +#define PUMPNET_TRY_CONNECT_TIMEOUT_MS 2000 + +struct profile_virtual_mnt_point_file_info { + int player; + enum pumpnet_lib_file_type file_type; + const char* file_path; + size_t file_size; +}; + +struct profile_virtual_mnt_point_info { + struct { + struct profile_virtual_mnt_point_file_info file_info[PUMPNET_LIB_FILE_TYPE_COUNT]; + } player[PUMPNET_MAX_NUM_PLAYERS]; +}; + +struct profile_virtual_file { + uint64_t player_ref_id; + FILE* handle; + const struct profile_virtual_mnt_point_file_info* file_info; + size_t buffer_pos; + uint8_t* buffer; + bool is_written; +}; + +struct profile_virtual_mnt_point { + struct profile_virtual_file files[PUMPNET_LIB_FILE_TYPE_COUNT]; +}; + +static enum cnh_result _patch_net_profile_filehook(struct cnh_filehook_irp* irp); +static enum cnh_result _patch_net_profile_fopen(struct cnh_filehook_irp* irp); +static enum cnh_result _patch_net_profile_fclose(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file); +static enum cnh_result _patch_net_profile_fread(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file); +static enum cnh_result _patch_net_profile_fwrite(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file); +static enum cnh_result _patch_net_profile_fseek(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file); +static enum cnh_result _patch_net_profile_ftell(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file); +static enum cnh_result _patch_net_profile_feof(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file); + +static struct profile_virtual_file* _patch_net_profile_get_virtual_mnt_point(FILE* handle); +static bool _patch_net_profile_determine_is_profile_and_player_and_file_type( + struct cnh_filehook_irp* irp, + uint8_t* player, + enum pumpnet_lib_file_type* file_type); +static enum cnh_result _patch_net_profile_open_pumpnet_profile_file( + struct cnh_filehook_irp* irp, + uint8_t player, + enum pumpnet_lib_file_type file_type); +static bool _patch_net_profile_close_pumpnet_profile_file(struct profile_virtual_file* virtual_file); + +static const struct profile_virtual_mnt_point_info _patch_net_profile_virtual_mnt_point_infos[] = { + { + .player = { + { + .file_info = { + { + .player = 0, + .file_type = PUMPNET_LIB_FILE_TYPE_SAVE, + .file_path = "/mnt/0/nx2save.bin", + .file_size = ASSET_NX2_USB_SAVE_SIZE + }, + { + .player = 0, + .file_type = PUMPNET_LIB_FILE_TYPE_RANK, + .file_path = "/mnt/0/nx2rank.bin", + .file_size = ASSET_NX2_USB_RANK_SIZE + } + } + }, + { + .file_info = { + { + .player = 1, + .file_type = PUMPNET_LIB_FILE_TYPE_SAVE, + .file_path = "/mnt/1/nx2save.bin", + .file_size = ASSET_NX2_USB_SAVE_SIZE + }, + { + .player = 1, + .file_type = PUMPNET_LIB_FILE_TYPE_RANK, + .file_path = "/mnt/1/nx2rank.bin", + .file_size = ASSET_NX2_USB_RANK_SIZE + } + } + }, + } + } +}; + +static const struct profile_virtual_mnt_point_info* _patch_net_profile_file_info_ref; +static struct profile_virtual_mnt_point _patch_net_profile_virtual_mnt_points[PUMPNET_MAX_NUM_PLAYERS]; +static pthread_mutex_t _patch_net_profile_mutex; + +static enum cnh_result _patch_net_profile_filehook(struct cnh_filehook_irp* irp) +{ + if (irp->op == CNH_FILEHOOK_IRP_OP_OPEN) { + return _patch_net_profile_fopen(irp); + } else { + struct profile_virtual_file* virt_file = _patch_net_profile_get_virtual_mnt_point(irp->file); + + if (virt_file != NULL) { + switch (irp->op) { + case CNH_FILEHOOK_IRP_OP_CLOSE: + return _patch_net_profile_fclose(irp, virt_file); + + case CNH_FILEHOOK_IRP_OP_READ: + return _patch_net_profile_fread(irp, virt_file); + + case CNH_FILEHOOK_IRP_OP_WRITE: + return _patch_net_profile_fwrite(irp, virt_file); + + case CNH_FILEHOOK_IRP_OP_FGETS: + log_die("Unsupported operation"); + return CNH_RESULT_OTHER_ERROR; + + case CNH_FILEHOOK_IRP_OP_SEEK: + return _patch_net_profile_fseek(irp, virt_file); + + case CNH_FILEHOOK_IRP_OP_TELL: + return _patch_net_profile_ftell(irp, virt_file); + + case CNH_FILEHOOK_IRP_OP_EOF: + return _patch_net_profile_feof(irp, virt_file); + + default: + log_die("Unhandled switch case"); + return CNH_RESULT_OTHER_ERROR; + } + } else { + return cnh_filehook_invoke_next(irp); + } + } +} + +static enum cnh_result _patch_net_profile_fopen(struct cnh_filehook_irp* irp) +{ + uint8_t player; + enum pumpnet_lib_file_type file_type; + player = -1; + file_type = PUMPNET_LIB_FILE_TYPE_COUNT; + + // not a profile file, pass + if (!_patch_net_profile_determine_is_profile_and_player_and_file_type(irp, &player, &file_type)) { + return cnh_filehook_invoke_next(irp); + } + + return _patch_net_profile_open_pumpnet_profile_file(irp, player, file_type); +} + +static enum cnh_result _patch_net_profile_fclose(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file) +{ + log_debug("fclose %s", virtual_file->file_info->file_path); + + return _patch_net_profile_close_pumpnet_profile_file(virtual_file) ? CNH_RESULT_SUCCESS : CNH_RESULT_OTHER_ERROR; +} + +static enum cnh_result _patch_net_profile_fread(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file) +{ + uint32_t len; + + pthread_mutex_lock(&_patch_net_profile_mutex); + + log_debug("fread %s, pos 0x%X, bytes %d", virtual_file->file_info->file_path, virtual_file->buffer_pos, + irp->read.nbytes); + + /* read up to eof, only */ + if (virtual_file->buffer_pos + irp->read.nbytes < virtual_file->file_info->file_size) { + len = irp->read.nbytes; + } else { + len = virtual_file->file_info->file_size - virtual_file->buffer_pos; + } + + memcpy(&irp->read.bytes[irp->read.pos], virtual_file->buffer + virtual_file->buffer_pos, len); + + virtual_file->buffer_pos += len; + irp->read.pos += len; + + pthread_mutex_unlock(&_patch_net_profile_mutex); + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_net_profile_fwrite(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file) +{ + uint32_t len; + + pthread_mutex_lock(&_patch_net_profile_mutex); + + log_debug("fwrite %s, pos 0x%X, bytes %d", virtual_file->file_info->file_path, virtual_file->buffer_pos, + irp->read.nbytes); + + /* write up to eof, only */ + if (virtual_file->buffer_pos + irp->write.nbytes < virtual_file->file_info->file_size) { + len = irp->write.nbytes; + } else { + len = virtual_file->file_info->file_size - virtual_file->buffer_pos; + } + + memcpy(virtual_file->buffer + virtual_file->buffer_pos, irp->write.bytes, len); + + virtual_file->buffer_pos += len; + virtual_file->is_written = true; + irp->write.pos += len; + + pthread_mutex_unlock(&_patch_net_profile_mutex); + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_net_profile_fseek(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file) +{ + pthread_mutex_lock(&_patch_net_profile_mutex); + + switch (irp->seek_origin) { + case SEEK_SET: + virtual_file->buffer_pos = (size_t) irp->seek_offset; + break; + + case SEEK_CUR: + virtual_file->buffer_pos += (size_t) irp->seek_offset; + break; + + case SEEK_END: + virtual_file->buffer_pos = virtual_file->file_info->file_size; + virtual_file->buffer_pos += (size_t) irp->seek_offset; + break; + + default: + log_die("Unhandled origin %d", irp->seek_origin); + break; + } + + log_debug("fseek %s, offset 0x%X, origin %d", virtual_file->file_info->file_path, irp->seek_offset, + irp->seek_origin); + + pthread_mutex_unlock(&_patch_net_profile_mutex); + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_net_profile_ftell(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file) +{ + pthread_mutex_lock(&_patch_net_profile_mutex); + + irp->tell_offset = virtual_file->buffer_pos; + + pthread_mutex_unlock(&_patch_net_profile_mutex); + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_net_profile_feof(struct cnh_filehook_irp* irp, + struct profile_virtual_file* virtual_file) +{ + pthread_mutex_lock(&_patch_net_profile_mutex); + + irp->eof = virtual_file->buffer_pos == virtual_file->file_info->file_size; + + pthread_mutex_unlock(&_patch_net_profile_mutex); + + return CNH_RESULT_SUCCESS; +} + +// ===================================================================================================================== + +static struct profile_virtual_file* _patch_net_profile_get_virtual_mnt_point(FILE* handle) +{ + for (int i = 0; i < PUMPNET_MAX_NUM_PLAYERS; i++) { + for (int j = 0; j < PUMPNET_LIB_FILE_TYPE_COUNT; j++) { + if (_patch_net_profile_virtual_mnt_points->files[j].handle == handle) { + return &_patch_net_profile_virtual_mnt_points->files[j]; + } + } + } + + return NULL; +} + +static bool _patch_net_profile_determine_is_profile_and_player_and_file_type( + struct cnh_filehook_irp* irp, + uint8_t* player, + enum pumpnet_lib_file_type* file_type) +{ + log_assert(irp); + log_assert(player); + log_assert(file_type); + + // compare to static string to avoid slowing everything down + for (int i = 0; i < PUMPNET_MAX_NUM_PLAYERS; i++) { + for (int j = 0; j < PUMPNET_LIB_FILE_TYPE_COUNT; j++) { + if (!strcmp(irp->open_filename, + _patch_net_profile_virtual_mnt_point_infos->player[i].file_info[j].file_path)) { + *player = i; + *file_type = (enum pumpnet_lib_file_type) j; + return true; + } + } + } + + return false; +} + +static struct profile_virtual_file* _patch_net_profile_setup_virtual_file( + uint8_t player, + enum pumpnet_lib_file_type file_type, + uint64_t player_ref_id) +{ + struct profile_virtual_file* virtual_file; + + log_debug("Setup virtual file, player %d, file_type %d", player, file_type); + + virtual_file = &_patch_net_profile_virtual_mnt_points[player].files[file_type]; + + // sanity checks + log_assert(!virtual_file->handle); + log_assert(!virtual_file->file_info); + log_assert(!virtual_file->buffer); + + virtual_file->player_ref_id = player_ref_id; + virtual_file->handle = cnh_filehook_open_dummy_file_handle(); + virtual_file->file_info = &_patch_net_profile_virtual_mnt_point_infos->player[player].file_info[file_type]; + virtual_file->buffer_pos = 0; + virtual_file->is_written = false; + + virtual_file->buffer = (uint8_t*) malloc(virtual_file->file_info->file_size); + + log_debug("Setup virtual file done, size %d", virtual_file->file_info->file_size); + + return virtual_file; +} + +static void _patch_net_profile_destroy_virtual_file(uint8_t player, enum pumpnet_lib_file_type file_type) +{ + struct profile_virtual_file* virtual_file; + + log_debug("Destroying virtual file, player %d, file_type %d", player, file_type); + + virtual_file = &_patch_net_profile_virtual_mnt_points[player].files[file_type]; + + // sanity checks + log_assert(virtual_file->handle); + log_assert(virtual_file->file_info); + log_assert(virtual_file->buffer); + + cnh_filehook_close_dummy_file_handle(virtual_file->handle); + virtual_file->handle = NULL; + virtual_file->file_info = NULL; + free(virtual_file->buffer); + virtual_file->buffer = NULL; +} + +static bool _patch_net_profile_get_token(int player, uint64_t* player_ref_id) +{ + log_assert(player_ref_id); + log_assert(player >= 0); + log_assert(player <= 1); + + char path_profile_token[128]; + + *player_ref_id = 0; + + sprintf(path_profile_token, "/mnt/%d/%s", player, PUMPNET_PROFILE_FILE_NAME); + + if (util_fs_path_exists(path_profile_token)) { + log_info("Found pumpnet profile file %s", path_profile_token); + + if (pumpnet_lib_profile_token_load(path_profile_token, player_ref_id)) { + return true; + } else { + log_error("Loading pumpnet profile token %s failed, fallback to local profiles", path_profile_token); + } + } else { + log_info("No pumpnet profile found, fallback to local profile"); + } + + return false; +} + +static bool _patch_net_profile_download_profile_file(struct profile_virtual_file* virtual_file) +{ + int player; + enum pumpnet_lib_file_type file_type; + uint64_t player_ref_id; + + player = virtual_file->file_info->player; + file_type = virtual_file->file_info->file_type; + player_ref_id = virtual_file->player_ref_id; + + log_info("Profile file player %d, file_type %d, refId %llX downloading from server...", + player, + file_type, + player_ref_id); + + if (!pumpnet_lib_get(file_type, player_ref_id, virtual_file->buffer, virtual_file->file_info->file_size)) { + log_error("Downloading file player %d, file_type %d, refId %llX failed", + player, + file_type, + player_ref_id); + return false; + } + + log_info("Downloading file player %d, file_type %d, refId %llX successful", + player, + file_type, + player_ref_id); + return true; +} + +static enum cnh_result _patch_net_profile_open_pumpnet_profile_file( + struct cnh_filehook_irp* irp, + uint8_t player, + enum pumpnet_lib_file_type file_type) +{ + uint64_t player_ref_id; + + log_debug("open %s %d %d", irp->open_filename, player, file_type); + + // check if pumpnet.bin token is available and server reachable + // download all profile files on the first file request and hook all following files to virtual endpoints of + // the already downloaded and buffered data + if (_patch_net_profile_get_token(player, &player_ref_id)) { + struct profile_virtual_file* virtual_file = + _patch_net_profile_setup_virtual_file(player, file_type, player_ref_id); + + if (_patch_net_profile_download_profile_file(virtual_file)) { + irp->file = virtual_file->handle; + pthread_mutex_unlock(&_patch_net_profile_mutex); + + return CNH_RESULT_SUCCESS; + } else { + _patch_net_profile_destroy_virtual_file(player, file_type); + + pthread_mutex_unlock(&_patch_net_profile_mutex); + + return CNH_RESULT_NO_SUCH_FILE_OR_DIR; + } + } + + return cnh_filehook_invoke_next(irp); +} + +static bool _patch_net_profile_upload_profile_file(struct profile_virtual_file* virtual_file) +{ + int player; + enum pumpnet_lib_file_type file_type; + uint64_t player_ref_id; + + player = virtual_file->file_info->player; + file_type = virtual_file->file_info->file_type; + player_ref_id = virtual_file->player_ref_id; + + log_info("Profile file player %d, file_type %d, refId %llX uploading to server...", + player, + file_type, + player_ref_id); + + if (!pumpnet_lib_put(file_type, player_ref_id, virtual_file->buffer, virtual_file->file_info->file_size)) { + log_error("Uploading file player %d, file_type %d, refId %llX failed", + player, + file_type, + player_ref_id); + return false; + } + + log_info("Uploading file player %d, file_type %d, refId %llX successful", + player, + file_type, + player_ref_id); + return true; +} + +static bool _patch_net_profile_close_pumpnet_profile_file(struct profile_virtual_file* virtual_file) +{ + bool res = true; + + pthread_mutex_lock(&_patch_net_profile_mutex); + + if (virtual_file->is_written) { + res = _patch_net_profile_upload_profile_file(virtual_file); + } + + _patch_net_profile_destroy_virtual_file(virtual_file->file_info->player, virtual_file->file_info->file_type); + + pthread_mutex_unlock(&_patch_net_profile_mutex); + + return res; +} + +// ===================================================================================================================== + +void patch_net_profile_init( + enum asset_game_version game, + const char* pumpnet_server_addr, + uint64_t machine_id, + const char* cert_dir_path, + bool verbose_debug_log) +{ + uint8_t idx; + + /* use a switch instead of offset'ing the enum for additional safety and block unsupported games */ + switch (game) { + case ASSET_GAME_VERSION_NX2: + idx = 0; + break; + +// case PUMPNET_LIB_GAME_NXA: +// idx = 1; +// break; +// +// case PUMPNET_LIB_GAME_FST: +// idx = 2; +// break; +// +// case PUMPNET_LIB_GAME_FEX: +// idx = 3; +// break; +// +// case PUMPNET_LIB_GAME_F2: +// idx = 4; +// break; + case ASSET_GAME_VERSION_NXA: + case ASSET_GAME_VERSION_FIESTA: + case ASSET_GAME_VERSION_FIESTA_EX: + case ASSET_GAME_VERSION_FIESTA_2: + + default: + log_error("Unsupported game version %d specified, disabled network service", game); + return; + } + + _patch_net_profile_file_info_ref = &_patch_net_profile_virtual_mnt_point_infos[idx]; + + size_t max_data_size = 0; + + // determine max file size for network buffers, file sizes identical on for all players. + for (int i = 0; i < PUMPNET_LIB_FILE_TYPE_COUNT; i++) { + max_data_size += _patch_net_profile_file_info_ref->player[0].file_info[i].file_size; + } + + // set buffers to NULL to detect illegal states + for (int i = 0; i < PUMPNET_LIB_FILE_TYPE_COUNT; i++) { + for (int j = 0; j < PUMPNET_MAX_NUM_PLAYERS; j++) { + _patch_net_profile_virtual_mnt_points[j].files[i].player_ref_id = 0; + _patch_net_profile_virtual_mnt_points[j].files[i].handle = NULL; + _patch_net_profile_virtual_mnt_points[j].files[i].file_info = NULL; + _patch_net_profile_virtual_mnt_points[j].files[i].buffer = NULL; + } + } + + pumpnet_lib_init(game, pumpnet_server_addr, machine_id, cert_dir_path, verbose_debug_log); + + cnh_filehook_push_handler(_patch_net_profile_filehook); + + pthread_mutex_init(&_patch_net_profile_mutex, NULL); + + log_info("Initialized: game %d, server %s, machine id %llX", game, pumpnet_server_addr, machine_id); +} + +void patch_net_profile_shutdown() +{ + pumpnet_lib_shutdown(); + + pthread_mutex_destroy(&_patch_net_profile_mutex); + + log_info("Shut down"); +} \ No newline at end of file diff --git a/src/main/hook/patch/net-profile.h b/src/main/hook/patch/net-profile.h new file mode 100644 index 0000000..2d5e254 --- /dev/null +++ b/src/main/hook/patch/net-profile.h @@ -0,0 +1,31 @@ +/** + * Patch module to redirect profile saving and loading to a pumpnet server (NX2 to Fiesta 2). + */ +#ifndef HOOK_PATCH_NET_PROFILE_H +#define HOOK_PATCH_NET_PROFILE_H + +#include "pumpnet/lib/pumpnet.h" + +/** + * Initialize the module. + * + * @param game Game version to run this module on + * @param pumpnet_server_addr Server address of pumpnet to connect to + * @param machine_id Machine ID to use for authentication + * @param cert_dir_path Path to a directory containing the client cert, client key and CA cert bundle to enable https + * communication + * @param verbose_debug_log Enable verbose debug log output, e.g. network backend logging/traffic. + */ +void patch_net_profile_init( + enum asset_game_version game, + const char* pumpnet_server_addr, + uint64_t machine_id, + const char* cert_dir_path, + bool verbose_debug_log); + +/** + * Shut down the module. + */ +void patch_net_profile_shutdown(); + +#endif diff --git a/src/main/hook/patch/network.c b/src/main/hook/patch/network.c new file mode 100644 index 0000000..aea31dc --- /dev/null +++ b/src/main/hook/patch/network.c @@ -0,0 +1,96 @@ +#define LOG_MODULE "patch-network" + +#include +#include + +#include "capnhook/hook/lib.h" + +#include "util/list.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +struct patch_network_redirect_elem { + struct util_list_node head; + char* orig_ipv4; + char* new_ipv4; + uint16_t new_port; +}; + +typedef int (*connect_t) (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len); + +static connect_t patch_network_real_connect; + +static bool patch_network_initialized; +static struct util_list patch_network_redirect_list; + +int connect(int fd, __CONST_SOCKADDR_ARG addr, socklen_t len) +{ + if (!patch_network_real_connect) { + patch_network_real_connect = (connect_t) cnh_lib_get_func_addr("connect"); + } + + if (patch_network_initialized) { + if (addr.__sockaddr__->sa_family == AF_INET) { + log_debug("connect ipv4 %d.%d.%d.%d:%d", + addr.__sockaddr_in__->sin_addr.s_addr & 0xFF, + addr.__sockaddr_in__->sin_addr.s_addr >> 8 & 0xFF, + addr.__sockaddr_in__->sin_addr.s_addr >> 16 & 0xFF, + addr.__sockaddr_in__->sin_addr.s_addr >> 24 & 0xFF, + addr.__sockaddr_in__->sin_port); + + struct util_list_node* pos; + struct patch_network_redirect_elem* elem; + struct sockaddr_in tmp; + + for (pos = patch_network_redirect_list.head; pos != NULL; pos = pos->next) { + elem = containerof(pos, struct patch_network_redirect_elem, head); + + inet_aton(elem->orig_ipv4, &tmp.sin_addr); + + if (addr.__sockaddr_in__->sin_addr.s_addr == tmp.sin_addr.s_addr) { + char* tmp_old = inet_ntoa(addr.__sockaddr_in__->sin_addr); + log_info("Redirect %s:%d -> %s:%d", tmp_old, + ((struct sockaddr_in*) addr.__sockaddr_in__)->sin_port, elem->new_ipv4, + elem->new_port != 0xFFFF ? elem->new_port : + ((struct sockaddr_in*) addr.__sockaddr_in__)->sin_port); + + inet_aton(elem->new_ipv4, &((struct sockaddr_in*) addr.__sockaddr_in__)->sin_addr); + + if (elem->new_port != 0xFFFF) { + ((struct sockaddr_in*) addr.__sockaddr_in__)->sin_port = htons(elem->new_port); + } + } + } + } else { + log_debug("connect, addr family: %d", addr.__sockaddr__->sa_family); + } + } + + return patch_network_real_connect(fd, addr, len); +} + +void patch_network_init() +{ + util_list_init(&patch_network_redirect_list); + patch_network_initialized = true; + log_info("Initialized"); +} + +void patch_network_redirect_server_address(const char* orig_ipv4, const char* new_ipv4, uint16_t new_port) +{ + struct patch_network_redirect_elem* elem; + + elem = util_xmalloc(sizeof(struct patch_network_redirect_elem)); + elem->orig_ipv4 = util_str_dup(orig_ipv4); + elem->new_ipv4 = util_str_dup(new_ipv4); + elem->new_port = new_port; + + util_list_append(&patch_network_redirect_list, &elem->head); + + if (new_port != 0xFFFF) { + log_info("Add network redirect: %s -> %s:%d", orig_ipv4, new_ipv4, new_port); + } else { + log_info("Add network redirect: %s -> %s", orig_ipv4, new_ipv4); + } +} diff --git a/src/main/hook/patch/network.h b/src/main/hook/patch/network.h new file mode 100644 index 0000000..3548ff9 --- /dev/null +++ b/src/main/hook/patch/network.h @@ -0,0 +1,23 @@ +/** + * Redirect network traffic for game server (Prime and up). + */ +#ifndef PATCH_NETWORK_H +#define PATCH_NETWORK_H + +#include + +/** + * Initialize patch module + */ +void patch_network_init(); + +/** + * Redirect a server address + * + * @param orig_ipv4 IPV4 address of the original server + * @param new_ipv4 IPV4 address of the new server to redirect to + * @param new_port Port of server to redirect to (-1 to keep old port) + */ +void patch_network_redirect_server_address(const char* orig_ipv4, const char* new_ipv4, uint16_t new_port); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/piubtn.c b/src/main/hook/patch/piubtn.c new file mode 100644 index 0000000..3a8526b --- /dev/null +++ b/src/main/hook/patch/piubtn.c @@ -0,0 +1,292 @@ +#define LOG_MODULE "patch-piubtn" + +#include +#include +#include +#include + +#include "capnhook/hook/lib.h" +#include "capnhook/hooklib/usb-emu.h" + +#include "io/piubtn/defs.h" + +#include "ptapi/io/piubtn.h" + +#include "util/log.h" + +static bool _patch_piubtn_enumerate(bool real_exists); +static enum cnh_result _patch_piubtn_open(void); +static enum cnh_result _patch_piubtn_reset(void); +static enum cnh_result _patch_piubtn_control_msg(int request_type, int request, int value, int index, + struct cnh_iobuf* buffer, int timeout); +static void _patch_piubtn_close(void); + +static enum cnh_result _patch_piubtn_process_inputs(struct cnh_iobuf* buffer); +static enum cnh_result _patch_piubtn_process_outputs(struct cnh_iobuf* buffer); + +static ptapi_io_piubtn_ident_t _patch_piubtn_ptapi_io_piubtn_ident; +static ptapi_io_piubtn_open_t _patch_piubtn_ptapi_io_piubtn_open; +static ptapi_io_piubtn_close_t _patch_piubtn_ptapi_io_piubtn_close; +static ptapi_io_piubtn_recv_t _patch_piubtn_ptapi_io_piubtn_recv; +static ptapi_io_piubtn_send_t _patch_piubtn_ptapi_io_piubtn_send; +static ptapi_io_piubtn_get_input_t _patch_piubtn_ptapi_io_piubtn_get_input; +static ptapi_io_piubtn_set_output_t _patch_piubtn_ptapi_io_piubtn_set_output; + +static const struct cnh_usb_emu_virtdev_ep _patch_piubtn_virtdev = { + .pid = PIUBTN_DRV_PID, + .vid = PIUBTN_DRV_VID, + .enumerate = _patch_piubtn_enumerate, + .open = _patch_piubtn_open, + .reset = _patch_piubtn_reset, + .control_msg = _patch_piubtn_control_msg, + .close = _patch_piubtn_close, +}; + +static void* _patch_piubtn_api_lib_handle; + +void patch_piubtn_init(const char* piubtn_lib_path) +{ + if (!piubtn_lib_path) { + log_die("No piubtn emulation library path specified"); + } + + /* Load piubtn library funcs */ + log_info("Loading piubtn api implementation %s", piubtn_lib_path); + + _patch_piubtn_api_lib_handle = cnh_lib_load(piubtn_lib_path); + + _patch_piubtn_ptapi_io_piubtn_ident = (ptapi_io_piubtn_ident_t) + cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_ident"); + + _patch_piubtn_ptapi_io_piubtn_open = (ptapi_io_piubtn_open_t) + cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_open"); + _patch_piubtn_ptapi_io_piubtn_close = (ptapi_io_piubtn_close_t) + cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_close"); + _patch_piubtn_ptapi_io_piubtn_recv = (ptapi_io_piubtn_recv_t) + cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_recv"); + _patch_piubtn_ptapi_io_piubtn_send = (ptapi_io_piubtn_send_t) + cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_send"); + _patch_piubtn_ptapi_io_piubtn_get_input = (ptapi_io_piubtn_get_input_t) + cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_get_input"); + _patch_piubtn_ptapi_io_piubtn_set_output = (ptapi_io_piubtn_set_output_t) + cnh_lib_get_func_addr_handle(_patch_piubtn_api_lib_handle, "ptapi_io_piubtn_set_output"); + + cnh_usb_emu_add_virtdevep(&_patch_piubtn_virtdev); + + log_info("Initialized"); +} + +void patch_piubtn_shutdown(void) +{ + +} + +static bool _patch_piubtn_enumerate(bool real_exists) +{ + if (real_exists) { + log_info("Real PIUBTN exists but emulation enabled, blocking real"); + } else { + log_info("Enumerating PIUBTN emulation"); + } + + return true; +} + +static enum cnh_result _patch_piubtn_open(void) +{ + log_info("Opening PIUBTN: %s", _patch_piubtn_ptapi_io_piubtn_ident()); + + if (!_patch_piubtn_ptapi_io_piubtn_open()) { + log_error("Opening api piubtn %s failed", _patch_piubtn_ptapi_io_piubtn_ident()); + return CNH_RESULT_OTHER_ERROR; + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_piubtn_reset(void) +{ + log_info("Resetting PIUBTN: %s", _patch_piubtn_ptapi_io_piubtn_ident()); + + _patch_piubtn_ptapi_io_piubtn_close(); + + if (!_patch_piubtn_ptapi_io_piubtn_open()) { + log_error("Resetting api piubtn %s failed", _patch_piubtn_ptapi_io_piubtn_ident()); + return CNH_RESULT_OTHER_ERROR; + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_piubtn_control_msg(int request_type, int request, int value, int index, + struct cnh_iobuf* buffer, int timeout) +{ + if (request_type == PIUBTN_DRV_USB_CTRL_TYPE_IN && request == PIUBTN_DRV_USB_CTRL_REQUEST) { + if (buffer->nbytes != PIUBTN_DRV_BUFFER_SIZE) { + log_error("Invalid buffer size for ctrl in: %d", buffer->nbytes); + return CNH_RESULT_INVALID_PARAMETER; + } + + return _patch_piubtn_process_inputs(buffer); + } else if (request_type == PIUBTN_DRV_USB_CTRL_TYPE_OUT && request == PIUBTN_DRV_USB_CTRL_REQUEST) { + if (buffer->nbytes != PIUBTN_DRV_BUFFER_SIZE) { + log_error("Invalid buffer size for ctrl out: %d", buffer->nbytes); + return CNH_RESULT_INVALID_PARAMETER; + } + + return _patch_piubtn_process_outputs(buffer); + } else { + log_error("Invalid usb control request to PIUBTN: 0x%02X", request); + return CNH_RESULT_INVALID_PARAMETER; + } +} + +static void _patch_piubtn_close(void) +{ + log_info("Closing PIUBTN: %s", _patch_piubtn_ptapi_io_piubtn_ident()); + + _patch_piubtn_ptapi_io_piubtn_close(); +} + +static enum cnh_result _patch_piubtn_process_inputs(struct cnh_iobuf* buffer) +{ + struct ptapi_io_piubtn_inputs p1; + struct ptapi_io_piubtn_inputs p2; + + if (!_patch_piubtn_ptapi_io_piubtn_recv()) { + log_error("Receiving inputs on api piubtn %s failed", _patch_piubtn_ptapi_io_piubtn_ident()); + return CNH_RESULT_OTHER_ERROR; + } + + memset(&p1, 0, sizeof(p1)); + memset(&p2, 0, sizeof(p2)); + + _patch_piubtn_ptapi_io_piubtn_get_input(0, &p1); + _patch_piubtn_ptapi_io_piubtn_get_input(1, &p2); + + memset(buffer->bytes, 0, PIUBTN_DRV_BUFFER_SIZE); + + /* + byte 0: + bit 0: P1 back (red) + bit 1: P1 left + bit 2: P1 right + bit 3: P1 start (green) + bit 4: P2 back (red) + bit 5: P2 left + bit 6: P2 right + bit 7: P2 start (green) + */ + + /* Player 1 */ + if (p1.back) { + buffer->bytes[0] |= (1 << 0); + } + + if (p1.left) { + buffer->bytes[0] |= (1 << 1); + } + + if (p1.right) { + buffer->bytes[0] |= (1 << 2); + } + + if (p1.start) { + buffer->bytes[0] |= (1 << 3); + } + + /* Player 2 */ + if (p2.back) { + buffer->bytes[0] |= (1 << 4); + } + + if (p2.left) { + buffer->bytes[0] |= (1 << 5); + } + + if (p2.right) { + buffer->bytes[0] |= (1 << 6); + } + + if (p2.start) { + buffer->bytes[0] |= (1 << 7); + } + + /* xor inputs because pullup active */ + for (int i = 0; i < PIUBTN_DRV_BUFFER_SIZE; i++) { + buffer->bytes[i] ^= 0xFF; + } + + buffer->pos = PIUBTN_DRV_BUFFER_SIZE; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_piubtn_process_outputs(struct cnh_iobuf* buffer) +{ + struct ptapi_io_piubtn_outputs p1; + struct ptapi_io_piubtn_outputs p2; + + memset(&p1, 0, sizeof(p1)); + memset(&p2, 0, sizeof(p2)); + + /* + byte 0: + bit 0: p1 start (green) + bit 1: p1 right + bit 2: p1 left + bit 3: p1 back (red) + bit 4: p2 start (green) + bit 5: p2 right + bit 6: p2 left + bit 7: p2 back (red) + */ + + /* Player 1 */ + + if (buffer->bytes[0] & (1 << 0)) { + p1.start = true; + } + + if (buffer->bytes[0] & (1 << 1)) { + p1.back = true; + } + + if (buffer->bytes[0] & (1 << 2)) { + p1.left = true; + } + + if (buffer->bytes[0] & (1 << 3)) { + p1.right = true; + } + + /* Player 2 */ + + if (buffer->bytes[0] & (1 << 4)) { + p2.start = true; + } + + if (buffer->bytes[0] & (1 << 5)) { + p2.right = true; + } + + if (buffer->bytes[0] & (1 << 6)) { + p2.left = true; + } + + if (buffer->bytes[0] & (1 << 7)) { + p2.back = true; + } + + _patch_piubtn_ptapi_io_piubtn_set_output(0, &p1); + _patch_piubtn_ptapi_io_piubtn_set_output(1, &p2); + + if (!_patch_piubtn_ptapi_io_piubtn_send()) { + log_error("Sending outputs on api piubtn %s failed", _patch_piubtn_ptapi_io_piubtn_ident()); + return CNH_RESULT_OTHER_ERROR; + } + + buffer->pos = PIUBTN_DRV_BUFFER_SIZE; + + return CNH_RESULT_SUCCESS; +} \ No newline at end of file diff --git a/src/main/hook/patch/piubtn.h b/src/main/hook/patch/piubtn.h new file mode 100644 index 0000000..31b0b56 --- /dev/null +++ b/src/main/hook/patch/piubtn.h @@ -0,0 +1,32 @@ +/** + * Patch libusb-0.1 calls to intercept with piubtn (pump pro buttons) + * communication and hook our API with emulation code + */ +#ifndef PATCH_PIUBTN_H +#define PATCH_PIUBTN_H + +#include "ptapi/io/x11-input-hook.h" + +#include "util/options.h" + +/** + * Initialize the patch module + * + * @param piubtn_lib_path Path to a library implementing the piubtn API + * @param real_passthrough True to ignore the library and disable any emulation + */ +void patch_piubtn_init(const char* piubtn_lib_path); + +/** + * Get the input hook handler provided by the piubtn library. + * + * @return Input hook handler for main-loop patch module or NULL if none is used. + */ +const struct pumptools_input_hook_handler* patch_piubtn_get_input_hook_handler(void); + +/** + * Shut down the patch module + */ +void patch_piubtn_shutdown(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/piuio-exit.c b/src/main/hook/patch/piuio-exit.c new file mode 100644 index 0000000..1ddd8eb --- /dev/null +++ b/src/main/hook/patch/piuio-exit.c @@ -0,0 +1,87 @@ + +#include "capnhook/hook/usbhook.h" + +#include "io/piuio/defs.h" + +#include "util/log.h" + +static enum cnh_result patch_piuio_exit_usbhook(struct cnh_usbhook_irp *irp); + +static usb_dev_handle* _patch_piuio_exit_usb_handle; + +void patch_piuio_exit_init() +{ + cnh_usbhook_push_handler(patch_piuio_exit_usbhook); + log_info("Initialized"); +} + +static enum cnh_result patch_piuio_exit_usbhook(struct cnh_usbhook_irp *irp) +{ + enum cnh_result result; + + if (irp->op != CNH_USBHOOK_IRP_OP_OPEN && _patch_piuio_exit_usb_handle != irp->handle) { + return cnh_usbhook_invoke_next(irp); + } + + switch (irp->op) { + case CNH_USBHOOK_IRP_OP_OPEN: + { + if (_patch_piuio_exit_usb_handle) { + log_warn("Already detected PIUIO previously, ignore re-detection?"); + result = cnh_usbhook_invoke_next(irp); + break; + } + + if (irp->open_usb_dev->descriptor.idVendor == PIUIO_DRV_VID && + irp->open_usb_dev->descriptor.idProduct == PIUIO_DRV_PID) { + log_info("Detected PIUIO"); + + result = cnh_usbhook_invoke_next(irp); + + if (result == CNH_RESULT_SUCCESS) { + _patch_piuio_exit_usb_handle = irp->handle; + } + } else { + result = cnh_usbhook_invoke_next(irp); + } + + break; + } + + case CNH_USBHOOK_IRP_OP_CTRL_MSG: + { + result = cnh_usbhook_invoke_next(irp); + + /* Post process inputs */ + if (irp->ctrl_req_type == PIUIO_DRV_USB_CTRL_TYPE_IN && irp->ctrl_req == PIUIO_DRV_USB_CTRL_REQUEST) { + if (irp->ctrl_buffer.nbytes != PIUIO_DRV_BUFFER_SIZE) { + log_error("Invalid buffer size for ctrl in: %d", irp->ctrl_buffer.nbytes); + } else { + /* Service + Test button pressed */ + /* Invert data -> pull up active */ + if (((~irp->ctrl_buffer.bytes[1]) & (1 << 1)) && ((~irp->ctrl_buffer.bytes[1]) & (1 << 6))) { + log_info("Exit on service + test enabled and hit, bye"); + exit(0); + } + } + } + + break; + } + + case CNH_USBHOOK_IRP_OP_CLOSE: + { + _patch_piuio_exit_usb_handle = NULL; + + result = cnh_usbhook_invoke_next(irp); + + break; + } + + default: + result = cnh_usbhook_invoke_next(irp); + break; + } + + return result; +} \ No newline at end of file diff --git a/src/main/hook/patch/piuio-exit.h b/src/main/hook/patch/piuio-exit.h new file mode 100644 index 0000000..74f40bb --- /dev/null +++ b/src/main/hook/patch/piuio-exit.h @@ -0,0 +1,12 @@ +/** + * Patch module to allow exiting the game using the PIUIO, e.g. pressing service + test. + */ +#ifndef PATCH_PIUIO_EXIT_H +#define PATCH_PIUIO_EXIT_H + +/** + * Enable the module + */ +void patch_piuio_exit_init(); + +#endif diff --git a/src/main/hook/patch/piuio.c b/src/main/hook/patch/piuio.c new file mode 100644 index 0000000..29bcbdb --- /dev/null +++ b/src/main/hook/patch/piuio.c @@ -0,0 +1,572 @@ +#define LOG_MODULE "patch-piuio" + +#include +#include +#include +#include + +#include "capnhook/hooklib/usb-emu.h" + +#include "io/piuio/defs.h" + +#include "ptapi/io/piuio.h" +#include "ptapi/io/piuio/util/lib.h" + +#include "util/log.h" + +static bool _patch_piuio_enumerate(bool real_exists); +static enum cnh_result _patch_piuio_open(void); +static enum cnh_result _patch_piuio_reset(void); +static enum cnh_result _patch_piuio_control_msg(int request_type, int request, int value, int index, + struct cnh_iobuf* buffer, int timeout); +static void _patch_piuio_close(void); + +static enum cnh_result _patch_piuio_process_inputs(struct cnh_iobuf* buffer); +static enum cnh_result _patch_piuio_process_outputs(struct cnh_iobuf* buffer); +static enum cnh_result _patch_piuio_process_itg_kernel_module_hack(struct cnh_iobuf* buffer); + +static const struct cnh_usb_emu_virtdev_ep _patch_piuio_virtdev = { + .pid = PIUIO_DRV_PID, + .vid = PIUIO_DRV_VID, + .enumerate = _patch_piuio_enumerate, + .open = _patch_piuio_open, + .reset = _patch_piuio_reset, + .control_msg = _patch_piuio_control_msg, + .close = _patch_piuio_close, +}; + +static struct ptapi_io_piuio_api _patch_piuio_api; +static enum ptapi_io_piuio_sensor_group _patch_piuio_sensor_group; + +void patch_piuio_init(const char* piuio_lib_path) +{ + if (!piuio_lib_path) { + log_die("No piuio emulation library path specified"); + } + + /* Load piuio library funcs */ + log_info("Loading piuio api implementation %s", piuio_lib_path); + + if (!ptapi_io_piuio_util_lib_load(piuio_lib_path, &_patch_piuio_api)) { + log_error("Loading PIUIO API from %s failed", piuio_lib_path); + return; + } + + cnh_usb_emu_add_virtdevep(&_patch_piuio_virtdev); + + log_info("Initialized"); +} + +void patch_piuio_shutdown(void) +{ + +} + +static bool _patch_piuio_enumerate(bool real_exists) +{ + if (real_exists) { + log_info("Real PIUIO exists but emulation enabled, blocking real"); + } else { + log_info("Enumerating PIUIO emulation"); + } + + return true; +} + +static enum cnh_result _patch_piuio_open(void) +{ + log_info("Opening PIUIO: %s", _patch_piuio_api.ident()); + + if (!_patch_piuio_api.open()) { + log_error("Opening api piuio %s failed", _patch_piuio_api.ident()); + return CNH_RESULT_OTHER_ERROR; + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_piuio_reset(void) +{ + log_info("Resetting PIUIO: %s", _patch_piuio_api.ident()); + + _patch_piuio_api.close(); + + if (!_patch_piuio_api.open()) { + log_error("Resetting api piuio %s failed", _patch_piuio_api.ident()); + return CNH_RESULT_OTHER_ERROR; + } + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_piuio_control_msg(int request_type, int request, int value, int index, + struct cnh_iobuf* buffer, int timeout) +{ + if (request_type == PIUIO_DRV_USB_CTRL_TYPE_IN && request == PIUIO_DRV_USB_CTRL_REQUEST) { + if (buffer->nbytes != PIUIO_DRV_BUFFER_SIZE) { + log_error("Invalid buffer size for ctrl in: %d", buffer->nbytes); + return CNH_RESULT_INVALID_PARAMETER; + } + + return _patch_piuio_process_inputs(buffer); + } else if (request_type == PIUIO_DRV_USB_CTRL_TYPE_OUT && request == PIUIO_DRV_USB_CTRL_REQUEST) { + if (buffer->nbytes != PIUIO_DRV_BUFFER_SIZE) { + log_error("Invalid buffer size for ctrl out: %d", buffer->nbytes); + return CNH_RESULT_INVALID_PARAMETER; + } + + return _patch_piuio_process_outputs(buffer); + } else if (request_type == 0 && request == 0) { + /* ITG 2/PIU Pro kernel hack */ + if (buffer->nbytes != PIUIO_DRV_BUFFER_SIZE * 4) { + log_error("Invalid buffer size for kernel hack: %d", buffer->nbytes); + return CNH_RESULT_INVALID_PARAMETER; + } + + return _patch_piuio_process_itg_kernel_module_hack(buffer); + } else { + log_error("Invalid usb control request to PIUIO: 0x%02X", request); + return CNH_RESULT_INVALID_PARAMETER; + } +} + +static void _patch_piuio_close(void) +{ + log_info("Closing PIUIO: %s", _patch_piuio_api.ident()); + + _patch_piuio_api.close(); +} + +static enum cnh_result _patch_piuio_process_inputs(struct cnh_iobuf* buffer) +{ + struct ptapi_io_piuio_pad_inputs p1_pad_in; + struct ptapi_io_piuio_pad_inputs p2_pad_in; + struct ptapi_io_piuio_sys_inputs sys_in; + + if (!_patch_piuio_api.recv()) { + log_error("Receiving inputs on api piuio %s failed", _patch_piuio_api.ident()); + return CNH_RESULT_OTHER_ERROR; + } + + memset(&p1_pad_in, 0, sizeof(struct ptapi_io_piuio_pad_inputs)); + memset(&p2_pad_in, 0, sizeof(struct ptapi_io_piuio_pad_inputs)); + memset(&sys_in, 0, sizeof(struct ptapi_io_piuio_sys_inputs)); + + _patch_piuio_api.get_input_pad(0, _patch_piuio_sensor_group, &p1_pad_in); + _patch_piuio_api.get_input_pad(1, _patch_piuio_sensor_group, &p2_pad_in); + + _patch_piuio_api.get_input_sys(&sys_in); + + /* + byte 0: + bit 0: sensor p1: LU + bit 1: sensor p1: RU + bit 2: sensor p1: CN + bit 3: sensor p1: LD + bit 4: sensor p1: RD + bit 5: + bit 6: + bit 7: + + byte 1: + bit 0: + bit 1: test + bit 2: coin 1 + bit 3: + bit 4: + bit 5: + bit 6: service + bit 7: clear + + byte 2: + bit 0: sensor p2: LU + bit 1: sensor p2: RU + bit 2: sensor p2: CN + bit 3: sensor p2: LD + bit 4: sensor p2: RD + bit 5: + bit 6: + bit 7: + + byte 3: + bit 0: + bit 1: + bit 2: coin 2 + bit 3: + bit 4: + bit 5: + bit 6: + bit 7: + + bytes 4 - 7 dummy + */ + + /* Player 1 */ + buffer->bytes[0] = 0; + + if (p1_pad_in.lu) { + buffer->bytes[0] |= (1 << 0); + } + + if (p1_pad_in.ru) { + buffer->bytes[0] |= (1 << 1); + } + + if (p1_pad_in.cn) { + buffer->bytes[0] |= (1 << 2); + } + + if (p1_pad_in.ld) { + buffer->bytes[0] |= (1 << 3); + } + + if (p1_pad_in.rd) { + buffer->bytes[0] |= (1 << 4); + } + + buffer->bytes[0] ^= 0xFF; + + buffer->bytes[2] = 0; + + /* Player 2 */ + if (p2_pad_in.lu) { + buffer->bytes[2] |= (1 << 0); + } + + if (p2_pad_in.ru) { + buffer->bytes[2] |= (1 << 1); + } + + if (p2_pad_in.cn) { + buffer->bytes[2] |= (1 << 2); + } + + if (p2_pad_in.ld) { + buffer->bytes[2] |= (1 << 3); + } + + if (p2_pad_in.rd) { + buffer->bytes[2] |= (1 << 4); + } + + buffer->bytes[2] ^= 0xFF; + + buffer->bytes[1] = 0; + + /* Sys */ + if (sys_in.test) { + buffer->bytes[1] |= (1 << 1); + } + + if (sys_in.service) { + buffer->bytes[1] |= (1 << 6); + } + + if (sys_in.clear) { + buffer->bytes[1] |= (1 << 7); + } + + if (sys_in.coin) { + buffer->bytes[1] |= (1 << 2); + } + + buffer->bytes[1] ^= 0xFF; + + /* Apparently, "touching" byte 3 as a whole causes some random and weird input triggering + which results in the service menu popping up. Don't clear the whole byte, touch coin2 only to avoid this */ + if (!sys_in.coin2) { + buffer->bytes[3] |= (1 << 2); + } else { + buffer->bytes[3] &= ~(1 << 2); + } + + buffer->pos = PIUIO_DRV_BUFFER_SIZE; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_piuio_process_outputs(struct cnh_iobuf* buffer) +{ + struct ptapi_io_piuio_pad_outputs p1_pad_out; + struct ptapi_io_piuio_pad_outputs p2_pad_out; + struct ptapi_io_piuio_cab_outputs cab_out; + + memset(&p1_pad_out, 0, sizeof(struct ptapi_io_piuio_pad_outputs)); + memset(&p2_pad_out, 0, sizeof(struct ptapi_io_piuio_pad_outputs)); + memset(&cab_out, 0, sizeof(struct ptapi_io_piuio_cab_outputs)); + + /* + byte 0: + bit 0: sensor id bit 0 select sensor to be read for next input request: + bit 1: sensor id bit 1 00 = sens1, 01 = sens2, 10 = sens3, 11 = sens4 + bit 2: pad light p1: LU + bit 3: pad light p1: RU + bit 4: pad light p1: CN + bit 5: pad light p1: LD + bit 6: pad light p1: RD + bit 7: + + byte 1: + bit 0: + bit 1: + bit 2: light neons + bit 3: coin counter 2 (front usb ports enable) + bit 4: + bit 5: + bit 6: + bit 7: + + byte 2: + bit 0: + bit 1: + bit 2: pad light p2: LU + bit 3: pad light p2: RU + bit 4: pad light p2: CN + bit 5: pad light p2: LD + bit 6: pad light p2: RD + bit 7: halogen R2 + + byte 3: + bit 0: halogen R1 + bit 1: halogen L2 + bit 2: halogen L1 + bit 3: + bit 4: coin counter 1 + bit 5: + bit 6: + bit 7: + + bytes 4 - 7 dummy + */ + + _patch_piuio_sensor_group = (enum ptapi_io_piuio_sensor_group) (buffer->bytes[0] & 0x03); + + /* Player 1 */ + + if (buffer->bytes[0] & (1 << 2)) { + p1_pad_out.lu = true; + } + + if (buffer->bytes[0] & (1 << 3)) { + p1_pad_out.ru = true; + } + + if (buffer->bytes[0] & (1 << 4)) { + p1_pad_out.cn = true; + } + + if (buffer->bytes[0] & (1 << 5)) { + p1_pad_out.ld = true; + } + + if (buffer->bytes[0] & (1 << 6)) { + p1_pad_out.rd = true; + } + + /* Player 2 */ + + if (buffer->bytes[2] & (1 << 2)) { + p2_pad_out.lu = true; + } + + if (buffer->bytes[2] & (1 << 3)) { + p2_pad_out.ru = true; + } + + if (buffer->bytes[2] & (1 << 4)) { + p2_pad_out.cn = true; + } + + if (buffer->bytes[2] & (1 << 5)) { + p2_pad_out.ld = true; + } + + if (buffer->bytes[2] & (1 << 6)) { + p2_pad_out.rd = true; + } + + /* Cabinet */ + + if (buffer->bytes[1] & (1 << 2)) { + cab_out.bass = true; + } + + if (buffer->bytes[2] & (1 << 7)) { + cab_out.halo_r2 = true; + } + + if (buffer->bytes[3] & (1 << 0)) { + cab_out.halo_r1 = true; + } + + if (buffer->bytes[3] & (1 << 1)) { + cab_out.halo_l2 = true; + } + + if (buffer->bytes[3] & (1 << 2)) { + cab_out.halo_l1 = true; + } + + _patch_piuio_api.set_output_pad(0, &p1_pad_out); + _patch_piuio_api.set_output_pad(1, &p2_pad_out); + _patch_piuio_api.set_output_cab(&cab_out); + + if (!_patch_piuio_api.send()) { + log_error("Sending outputs on api piuio %s failed", _patch_piuio_api.ident()); + return CNH_RESULT_OTHER_ERROR; + } + + buffer->pos = PIUIO_DRV_BUFFER_SIZE; + + return CNH_RESULT_SUCCESS; +} + +static enum cnh_result _patch_piuio_process_itg_kernel_module_hack(struct cnh_iobuf* buffer) +{ + /* Instead of calling four times, one call for each sensor set, a special + kernel module is loaded which traps the following call: + usb_control_msg(dev_handle, 0, 0, 0, 0, buffer, 32, 10011); + All four sensor sets are polled in a single context switch and returned + as one big buffer instead of four smalle ones (8 bytes each). + Pump Pro relies on this, so we have to support it here */ + + struct ptapi_io_piuio_pad_inputs p1_pad_in[4]; + struct ptapi_io_piuio_pad_inputs p2_pad_in[4]; + struct ptapi_io_piuio_sys_inputs sys_in; + + /* The output duplicated 4 times (4 * 8) in the buffer. Write it once */ + _patch_piuio_process_outputs(buffer); + + if (!_patch_piuio_api.recv()) { + log_error("Receiving inputs on api piuio %s failed", _patch_piuio_api.ident()); + return CNH_RESULT_OTHER_ERROR; + } + + memset(buffer->bytes, 0, PIUIO_DRV_BUFFER_SIZE * 4); + + memset(&sys_in, 0, sizeof(struct ptapi_io_piuio_sys_inputs)); + + for (uint8_t i = 0; i < 4; i++) { + memset(&p1_pad_in[i], 0, sizeof(struct ptapi_io_piuio_pad_inputs)); + memset(&p2_pad_in[i], 0, sizeof(struct ptapi_io_piuio_pad_inputs)); + + _patch_piuio_api.get_input_pad(0, (enum ptapi_io_piuio_sensor_group) i, &p1_pad_in[i]); + _patch_piuio_api.get_input_pad(1, (enum ptapi_io_piuio_sensor_group) i, &p2_pad_in[i]); + } + + _patch_piuio_api.get_input_sys(&sys_in); + + /* + byte 0: + bit 0: sensor p1: LU + bit 1: sensor p1: RU + bit 2: sensor p1: CN + bit 3: sensor p1: LD + bit 4: sensor p1: RD + bit 5: + bit 6: + bit 7: + + byte 1: + bit 0: + bit 1: test + bit 2: coin 1 + bit 3: + bit 4: + bit 5: + bit 6: service + bit 7: clear + + byte 2: + bit 0: sensor p2: LU + bit 1: sensor p2: RU + bit 2: sensor p2: CN + bit 3: sensor p2: LD + bit 4: sensor p2: RD + bit 5: + bit 6: + bit 7: + + byte 3: + bit 0: + bit 1: + bit 2: coin 2 + bit 3: + bit 4: + bit 5: + bit 6: + bit 7: + + bytes 4 - 7 dummy + */ + + for (uint8_t i = 0; i < 4; i++) { + /* Player 1 */ + if (p1_pad_in[i].lu) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 0); + } + + if (p1_pad_in[i].ru) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 1); + } + + if (p1_pad_in[i].cn) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 2); + } + + if (p1_pad_in[i].ld) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 3); + } + + if (p1_pad_in[i].rd) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 0] |= (1 << 4); + } + + /* Player 2 */ + if (p2_pad_in[i].lu) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 0); + } + + if (p2_pad_in[i].ru) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 1); + } + + if (p2_pad_in[i].cn) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 2); + } + + if (p2_pad_in[i].ld) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 3); + } + + if (p2_pad_in[i].rd) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 2] |= (1 << 4); + } + + /* Sys */ + if (sys_in.test) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 1] |= (1 << 1); + } + + if (sys_in.service) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 1] |= (1 << 6); + } + + if (sys_in.clear) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 1] |= (1 << 7); + } + + if (sys_in.coin) { + buffer->bytes[PIUIO_DRV_BUFFER_SIZE * i + 1] |= (1 << 2); + } + } + + /* xor inputs because pullup active */ + for (int i = 0; i < PIUIO_DRV_BUFFER_SIZE * 4; i++) { + buffer->bytes[i] ^= 0xFF; + } + + buffer->nbytes = PIUIO_DRV_BUFFER_SIZE * 4; + + return CNH_RESULT_SUCCESS; +} \ No newline at end of file diff --git a/src/main/hook/patch/piuio.h b/src/main/hook/patch/piuio.h new file mode 100644 index 0000000..c2c2a3e --- /dev/null +++ b/src/main/hook/patch/piuio.h @@ -0,0 +1,22 @@ +/** + * Patch libusb-0.1 calls to intercept with piuio communication and hook + * our API with emulation code + */ +#ifndef PATCH_PIUIO_H +#define PATCH_PIUIO_H + +#include "util/options.h" + +/** + * Initialize the patch module + * + * @param piuio_lib_path Path to a library implementing the piuio API + */ +void patch_piuio_init(const char* piuio_lib_path); + +/** + * Shut down the patch module + */ +void patch_piuio_shutdown(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/ram-wipe.c b/src/main/hook/patch/ram-wipe.c new file mode 100644 index 0000000..db97aae --- /dev/null +++ b/src/main/hook/patch/ram-wipe.c @@ -0,0 +1,25 @@ +#define LOG_MODULE "patch-ram-wipe" + +#include + +#include "capnhook/hook/filehook.h" + +#include "util/log.h" + +static enum cnh_result _patch_ram_wipe_filehook(struct cnh_filehook_irp *irp); + +void patch_ram_wipe_init(void) +{ + cnh_filehook_push_handler(_patch_ram_wipe_filehook); + log_info("Initialized"); +} + +static enum cnh_result _patch_ram_wipe_filehook(struct cnh_filehook_irp *irp) +{ + if (irp->op == CNH_FILEHOOK_IRP_OP_OPEN && !strcmp(irp->open_filename, "/dev/ram0")) { + log_info("Killing ram wipe"); + return CNH_RESULT_NO_SUCH_FILE_OR_DIR; + } else { + return cnh_filehook_invoke_next(irp); + } +} \ No newline at end of file diff --git a/src/main/hook/patch/ram-wipe.h b/src/main/hook/patch/ram-wipe.h new file mode 100644 index 0000000..a052495 --- /dev/null +++ b/src/main/hook/patch/ram-wipe.h @@ -0,0 +1,13 @@ +/** + * Starting with Prime the game executes a ram wipe. We don't want that + * to happen. + */ +#ifndef PATCH_RAM_WIPE_H +#define PATCH_RAM_WIPE_H + +/** + * Initialize patch module + */ +void patch_ram_wipe_init(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/redir.c b/src/main/hook/patch/redir.c new file mode 100644 index 0000000..148acf4 --- /dev/null +++ b/src/main/hook/patch/redir.c @@ -0,0 +1,14 @@ +#define LOG_MODULE "patch-redir" + +#include "capnhook/hooklib/redir.h" + +#include "util/log.h" + +void patch_redir_init() +{ + cnh_fshook_push_handler(cnh_redir_fshook); + cnh_filehook_push_handler(cnh_redir_filehook); + cnh_iohook_push_handler(cnh_redir_iohook); + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/redir.h b/src/main/hook/patch/redir.h new file mode 100644 index 0000000..fa32093 --- /dev/null +++ b/src/main/hook/patch/redir.h @@ -0,0 +1,12 @@ +/** + * Module to enable the redirect hook module to redirect file/device open paths. + */ +#ifndef PATCH_REDIR_H +#define PATCH_REDIR_H + +/** + * Enable module + */ +void patch_redir_init(); + +#endif //PATCH_REDIR_H diff --git a/src/main/hook/patch/sigsegv.c b/src/main/hook/patch/sigsegv.c new file mode 100644 index 0000000..aed619c --- /dev/null +++ b/src/main/hook/patch/sigsegv.c @@ -0,0 +1,53 @@ +#define LOG_MODULE "patch-sigsegv" + +#include +#include +#include +#include + +#include "capnhook/hook/sig.h" + +#include "util/log.h" + +#include "sigsegv.h" + +static bool patch_sigsevc_halt_on_segv; + +static void patch_sigsegv_handler(int signal, __sighandler_t orig_handler) +{ + log_error("==================="); + log_error("!!!!! SIGSEGV !!!!!"); + + void* array[128]; + size_t size; + char** strings; + + size = (size_t) backtrace(array, sizeof(array)); + strings = backtrace_symbols(array, size); + + log_error("Backtrace (%d frames):", size); + + for (int i = 0; i < size; i++) { + log_error("%s", strings[i]); + } + + free(strings); + + if (patch_sigsevc_halt_on_segv) { + log_error("Halting for debugger..."); + + while (true) { + sleep(1); + } + } + + exit(-1); +} + +void patch_sigsegv_init(bool halt_on_segv) +{ + patch_sigsevc_halt_on_segv = halt_on_segv; + + cnh_sig_install(SIGSEGV, patch_sigsegv_handler); + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/sigsegv.h b/src/main/hook/patch/sigsegv.h new file mode 100644 index 0000000..8bd5230 --- /dev/null +++ b/src/main/hook/patch/sigsegv.h @@ -0,0 +1,16 @@ +/** + * Install a proper signal handler to enhance error output on crashes + */ +#ifndef PATCH_SIGSEGV_H +#define PATCH_SIGSEGV_H + +#include + +/** + * Initialize the patch module + * + * @param halt_on_segv Halt on segv to attach a debugger to the application + */ +void patch_sigsegv_init(bool halt_on_segv); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/sound.c b/src/main/hook/patch/sound.c new file mode 100644 index 0000000..a2bf707 --- /dev/null +++ b/src/main/hook/patch/sound.c @@ -0,0 +1,66 @@ +#define LOG_MODULE "patch-sound" + +#include +#include + #include + +#include "capnhook/hook/lib.h" + +#include "util/log.h" + +typedef int (*snd_pcm_open_t)(snd_pcm_t** pcmp, const char* name, + snd_pcm_stream_t stream, int mode); + +static snd_pcm_open_t patch_sound_real_snd_pcm_open; + +static const char* patch_sound_dev_name; + +int snd_pcm_open(snd_pcm_t** pcmp, const char* name, snd_pcm_stream_t stream, int mode) +{ + int ret = 0; + int retry_count = 5; + + log_debug("snd_pcm_open: %s, stream %d, mode %d", name, stream, mode); + + if (!patch_sound_real_snd_pcm_open) { + patch_sound_real_snd_pcm_open = (snd_pcm_open_t) cnh_lib_get_func_addr("snd_pcm_open"); + } + + if (patch_sound_dev_name) { + log_info("Opening sound device %s instead of %s", patch_sound_dev_name, name); + + name = patch_sound_dev_name; + } + + /* + Notes about exceed and how it fucked up using alsa: + - Opening the same (hw:0) device multiple times without closing + - Opening and closing the device on each effect play + - No proper error handling on snd_pcm_open return + */ + + /* Using dmix instead of hw:0 on exceed, snd_pcm_open sometimes returns + with a bad fd but after another retry, everything's fine */ + while (retry_count > 0) { + ret = patch_sound_real_snd_pcm_open(pcmp, name, stream, mode); + + if (ret < 0) { + retry_count--; + } else { + break; + } + } + + if (ret < 0) { + log_error("snd_pcm_open failed: %s", snd_strerror(ret)); + } + + return ret; +} + +void patch_sound_init(const char* dev_name) +{ + patch_sound_dev_name = dev_name; + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/sound.h b/src/main/hook/patch/sound.h new file mode 100644 index 0000000..77a98ec --- /dev/null +++ b/src/main/hook/patch/sound.h @@ -0,0 +1,15 @@ +/** + * Patch the sound device to open for sound output. Allows us to use a + * different device than hw:0 (e.g. HDMI sound output) + */ +#ifndef PATCH_SOUND_H +#define PATCH_SOUND_H + +/** + * Initialize the patch module + * + * @param dev_name Sound device name to open instead of whatever pump decides + */ +void patch_sound_init(const char* dev_name); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/usb-emu.c b/src/main/hook/patch/usb-emu.c new file mode 100644 index 0000000..9d9e54c --- /dev/null +++ b/src/main/hook/patch/usb-emu.c @@ -0,0 +1,12 @@ +#define LOG_MODULE "patch-usb-emu" + +#include "capnhook/hooklib/usb-emu.h" + +#include "util/log.h" + +void patch_usb_emu_init() +{ + cnh_usbhook_push_handler(&cnh_usb_emu); + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/usb-emu.h b/src/main/hook/patch/usb-emu.h new file mode 100644 index 0000000..9560a97 --- /dev/null +++ b/src/main/hook/patch/usb-emu.h @@ -0,0 +1,12 @@ +/** + * Initialize the hook module for usb device emulation. + */ +#ifndef PATCH_USB_EMU_H +#define PATCH_USB_EMU_H + +/** + * Initialize module. + */ +void patch_usb_emu_init(); + +#endif diff --git a/src/main/hook/patch/usb-init-fix.c b/src/main/hook/patch/usb-init-fix.c new file mode 100644 index 0000000..12a45b7 --- /dev/null +++ b/src/main/hook/patch/usb-init-fix.c @@ -0,0 +1,12 @@ +#define LOG_MODULE "patch-usb-init-fix" + +#include "capnhook/hooklib/usb-init-fix.h" + +#include "util/log.h" + +void patch_usb_init_fix_init() +{ + cnh_usbhook_push_handler(&cnh_usb_init_fix); + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/usb-init-fix.h b/src/main/hook/patch/usb-init-fix.h new file mode 100644 index 0000000..b0a0381 --- /dev/null +++ b/src/main/hook/patch/usb-init-fix.h @@ -0,0 +1,12 @@ +/** + * Initialize the hook module for fixing multiple usb_init calls. + */ +#ifndef PATCH_USB_INIT_FIX_H +#define PATCH_USB_INIT_FIX_H + +/** + * Initialize module. + */ +void patch_usb_init_fix_init(); + +#endif diff --git a/src/main/hook/patch/usb-mnt.c b/src/main/hook/patch/usb-mnt.c new file mode 100644 index 0000000..98f69ad --- /dev/null +++ b/src/main/hook/patch/usb-mnt.c @@ -0,0 +1,29 @@ +#define LOG_MODULE "patch-usb-mnt" + +#include "util/fs.h" +#include "util/log.h" + +#include "usb-mnt.h" + +static const char* patch_usb_mnt_player[2] = {"/mnt/0", "/mnt/1"}; +// Invalid files are used to detect if mounting the usb stick was successful +static const char* patch_usb_mnt_player_invalid[2] = {"/mnt/0/invalid", "/mnt/1/invalid"}; + +void patch_usb_mnt_init(void) +{ + for (uint8_t i = 0; i < 2; i++) { + if (!util_fs_path_exists(patch_usb_mnt_player[i])) { + log_info("%s does not exist, creating", patch_usb_mnt_player[i]); + + if (!util_fs_mkdir(patch_usb_mnt_player[i])) { + log_error("Creating directory %s failed", patch_usb_mnt_player[i]); + } + + if (!util_fs_mkfile(patch_usb_mnt_player_invalid[i])) { + log_error("Creating invalid indicator file %s failed", patch_usb_mnt_player_invalid[i]); + } + } + } + + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/usb-mnt.h b/src/main/hook/patch/usb-mnt.h new file mode 100644 index 0000000..ce4c750 --- /dev/null +++ b/src/main/hook/patch/usb-mnt.h @@ -0,0 +1,13 @@ +/** + * Ensures that the mount points /mnt/0 and /mnt/1 exist to allow mounting + * of connected usb sticks with profile data + */ +#ifndef PATCH_USB_MNT_H +#define PATCH_USB_MNT_H + +/** + * Initialize the patch module + */ +void patch_usb_mnt_init(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/usb-unlock.c b/src/main/hook/patch/usb-unlock.c new file mode 100644 index 0000000..17581b3 --- /dev/null +++ b/src/main/hook/patch/usb-unlock.c @@ -0,0 +1,73 @@ +#define LOG_MODULE "patch-usb-unlock" + +#include +#include +#include + +#include "capnhook/hook/lib.h" + +#include "util/log.h" + +#include "usb-unlock.h" + +#define SERIAL_UNLOCK_TOKEN "PUMPToken\0" + +typedef int (*sscanf_t)(const char *str, const char *format, ...); +typedef int (*strcasecmp_t)(const char *s1, const char *s2); + +static sscanf_t patch_usb_unlock_real_sscanf; +static strcasecmp_t patch_usb_unlock_real_strcasecmp; + +int sscanf(const char *str, const char *format, ...) +{ + if (!patch_usb_unlock_real_sscanf) { + patch_usb_unlock_real_sscanf = (sscanf_t) cnh_lib_get_func_addr("sscanf"); + } + + va_list ap; + va_start(ap, format); + + if (!strcmp(format, "P: Vendor=%4x ProdID=%4x")) { + log_debug("Spoofing VID 0x058f for usb stick"); + + /* unpack argument list, we know we got 2 arguments here */ + /* VID */ + *va_arg(ap, int*) = 0x058f; + /* PID */ + *va_arg(ap, int*) = 0; + + va_end(ap); + return 2; + } + + if (!strcmp(format, "S: SerialNumber=%s")) { + log_debug("Spoofing serial number of usb stick to kill serial number locking of profile in a moment..."); + memcpy(va_arg(ap, char*), SERIAL_UNLOCK_TOKEN, strlen(SERIAL_UNLOCK_TOKEN) + 1); + + va_end(ap); + return 1; + } + + int ret = vsscanf(str, format, ap); + va_end(ap); + return ret; +} + +int strcasecmp(const char *s1, const char *s2) +{ + if (!patch_usb_unlock_real_strcasecmp) { + patch_usb_unlock_real_strcasecmp = (strcasecmp_t) cnh_lib_get_func_addr("strcasecmp"); + } + + if (!strcmp(s2, SERIAL_UNLOCK_TOKEN)) { + log_debug("Caught serial unlock token...unlocking profile"); + return 0; + } + + return patch_usb_unlock_real_strcasecmp(s1, s2); +} + +void patch_usb_unlock_init(void) +{ + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/usb-unlock.h b/src/main/hook/patch/usb-unlock.h new file mode 100644 index 0000000..6a9afbd --- /dev/null +++ b/src/main/hook/patch/usb-unlock.h @@ -0,0 +1,13 @@ +/** + * Remove the vendor id lock check for usb sticks limiting nx2 and nxa to + * a specific brand of usb sticks to save profile data to + */ +#ifndef PATCH_USB_UNLOCK_H +#define PATCH_USB_UNLOCK_H + +/** + * Initialize the patch module + */ +void patch_usb_unlock_init(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/usbfs.c b/src/main/hook/patch/usbfs.c new file mode 100644 index 0000000..899d297 --- /dev/null +++ b/src/main/hook/patch/usbfs.c @@ -0,0 +1,130 @@ +#define LOG_MODULE "patch-usbfs" + +#include +#include +#include +#include + +#include "capnhook/hook/lib.h" + +#include "util/fs.h" +#include "util/log.h" + +typedef int (*vsprintf_t)(char* str, const char* format, va_list ap); +typedef int (*sprintf_t)(char *str, const char *format, ...); + +static vsprintf_t patch_usbfs_vsprintf_orig; +static sprintf_t patch_usbfs_sprintf_orig; + +static const char* patch_usbfs_usb_str = "/sys/bus/usb/devices/"; + +int sprintf(char *str, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + int ret = vsprintf(str, format, ap); + va_end(ap); + return ret; +} + +int __sprintf_chk(char *dest, int flags, size_t dest_len_from_compiler, const char *format, ...) +{ + int ret; + va_list ap; + va_start(ap, format); + + /* fuck...why did you guys have to switch from sprintf to sprintf_chk? + everything was perfectly fine... and you give a shit about "safe" code, + which doesn't make this mess better... */ + ret = vsprintf(dest, format, ap); + + va_end(ap); + return ret; +} + +int vsprintf(char* str, const char* format, va_list ap) +{ + if (!patch_usbfs_vsprintf_orig) { + patch_usbfs_vsprintf_orig = (vsprintf_t) cnh_lib_get_func_addr("vsprintf"); + } + + if (!patch_usbfs_sprintf_orig) { + patch_usbfs_sprintf_orig = (sprintf_t) cnh_lib_get_func_addr("sprintf"); + } + + /* This path does not work on newer kernels (not sure which version it + started but everything > 3.18 doesn't work) */ + + // nx2/nxa path: + // /sys/bus/usb/devices/%d-%d/%d-%d:1.0/host*/target*:0:0/*:0:0:0/block:sd* + // fiesta and newer: + // /sys/bus/usb/devices/%d-%d:1.0/host*/target*:0:0/*:0:0:0/block/sd* + + if (!strncmp(patch_usbfs_usb_str, format, strlen(patch_usbfs_usb_str))) { + /* unpack argument list, we know we got 4 arguments here + the second pair is a dupe of the first one */ + uint8_t bus = (uint8_t) va_arg(ap, int); + uint8_t port = (uint8_t) va_arg(ap, int); + + // Removed because it spams the log. Put it back when debugging usb profile mounting related issues + // log_debug("Patching lookup of '%s' device node for bus %d, port %d.", format, bus, port); + + /* note: ports start with 1 (not 0) in sysfs */ + + /* because the linux kernel likes to change things here often, we have to consider different paths. + for each path we encountered so far (add more if missing from different kernel versions) just + check if the path exists and use it */ + + int counter = 0; + + while (counter <= 2) { + switch (counter) { + case 0: + patch_usbfs_sprintf_orig(str, "/sys/bus/usb/devices/%d-1.%d:1.0/host*/" + "target*:0:0/*:0:0:0/block/sd*", bus, port); + break; + + case 1: + patch_usbfs_sprintf_orig(str, "/sys/bus/usb/devices/%d-%d:1.0/host*/" + "target*:0:0/*:0:0:0/block/sd*", bus, port); + break; + + case 2: + // Discovered that the string needs to be changed on 5.5.6. Probably changed slightly before that + // version + patch_usbfs_sprintf_orig(str, "/sys/bus/usb/devices/%d-%d/%d-%d:1.0/host*/" + "target*:0:0/*:0:0:0/block/sd*", bus, port, bus, port); + break; + + default: + log_error("Could not find path in sys-fs to lookup usb drive block device. Likely, you have a " + "kernel version that uses a (slightly) different path. This (new) path must be added in " + "order to get usb drives showing up in the game."); + break; + } + + glob_t gl; + + // Pump does the same after returning from this hooked call. Because there are different file system + // layouts depending on the kernel version, try the above list using the same method with glob + if (!glob(str, 4, NULL, &gl)) { + globfree(&gl); + break; + } + + globfree(&gl); + + counter++; + } + + /* original argument count */ + return 4; + } + + return patch_usbfs_vsprintf_orig(str, format, ap); +} + +void patch_usbfs_init() +{ + log_info("Initialized"); +} \ No newline at end of file diff --git a/src/main/hook/patch/usbfs.h b/src/main/hook/patch/usbfs.h new file mode 100644 index 0000000..0730b62 --- /dev/null +++ b/src/main/hook/patch/usbfs.h @@ -0,0 +1,14 @@ +/** + * The kernels used by nx2 and nxa support usbfs. Newer kernels + * don't have this module anymore. This module takes care of fixing any + * issues not having the module. + */ +#ifndef PATCH_USBFS_H +#define PATCH_USBFS_H + +/** + * Initialize the patch module + */ +void patch_usbfs_init(); + +#endif \ No newline at end of file diff --git a/src/main/hook/patch/x11-event-loop.c b/src/main/hook/patch/x11-event-loop.c new file mode 100644 index 0000000..8ac4174 --- /dev/null +++ b/src/main/hook/patch/x11-event-loop.c @@ -0,0 +1,269 @@ +#define LOG_MODULE "patch-x11-event-loop" + +#include +#include +#include +#include + +#include "capnhook/hook/lib.h" + +#include "ptapi/io/x11-input-hook.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/time.h" + +struct patch_x11_event_loop_ctx { + Display* display; + Window window; + pthread_t thread; + atomic_int run; +}; + +typedef Window (*XCreateWindow_t)(Display *display, Window parent, int x, int y, unsigned int width, + unsigned int height, unsigned int border_width, int depth, unsigned int _class, Visual *visual, + unsigned long valuemask, XSetWindowAttributes *attributes); +typedef Display* (*XOpenDisplay_t)(const char *display_name); +typedef int (*XDestroyWindow_t)(Display *display, Window w); + +static XCreateWindow_t patch_x11_event_loop_real_XCreateWindow; +static XOpenDisplay_t patch_x11_event_loop_real_XOpenDisplay; +static XDestroyWindow_t patch_x11_event_loop_real_XDestroyWindow; + +static bool patch_x11_event_loop_enabled; +static struct patch_x11_event_loop_ctx* patch_x11_event_loop_ctx; + +static pthread_mutex_t patch_x11_event_loop_input_handlers_lock; +static const struct ptapi_io_x11_input_hook_handler** patch_x11_event_loop_input_handlers; +static size_t patch_x11_event_loop_num_input_handlers; + +static bool patch_x11_event_loop_is_key_repeated(Display* display, XEvent* event) +{ + /* When a key is repeated, there will be two events: released, followed by another immediate pressed. + So check to see if another pressed is present */ + if (!XPending(display)) { + return false; + } + + XEvent e; + XPeekEvent(display, &e); + + if (e.type == KeyPress && e.xkey.keycode == event->xkey.keycode && (e.xkey.time - event->xkey.time) < 2) { + XNextEvent(display, &e); + return true; + } + + return false; +} + +static void* patch_x11_event_loop_run(void* args) +{ + log_info("Running X11 event loop thread"); + + struct patch_x11_event_loop_ctx* ctx = (struct patch_x11_event_loop_ctx*) args; + + if (XSelectInput(ctx->display, ctx->window, KeyPressMask | KeyReleaseMask) == BadWindow) { + log_error("XSelectInput failed"); + return NULL; + } + + XGrabKeyboard(ctx->display, ctx->window, 1, GrabModeAsync, GrabModeAsync, CurrentTime); + + while (atomic_load(&ctx->run) > 0) { + while (atomic_load(&ctx->run) > 0 && XPending(ctx->display) > 0) { + XEvent event; + KeySym key; + + if (XNextEvent(ctx->display, &event) != Success) { + log_error("XNextEvent"); + continue; + } + + switch (event.type) { + case KeyPress: + { + /* Mask out the modifier states X11 sets and read again */ + event.xkey.state &= ~ShiftMask; + event.xkey.state &= ~LockMask; + + XLookupString(&event.xkey, 0, 0, &key, 0); + + /* Dispatch to external input handlers */ + if (patch_x11_event_loop_num_input_handlers > 0) { + pthread_mutex_lock(&patch_x11_event_loop_input_handlers_lock); + + for (size_t i = 0; i < patch_x11_event_loop_num_input_handlers; i++) { + if (patch_x11_event_loop_input_handlers[i]->dispatch_key_press) { + patch_x11_event_loop_input_handlers[i]->dispatch_key_press(key); + } + } + + pthread_mutex_unlock(&patch_x11_event_loop_input_handlers_lock); + } + + break; + } + + case KeyRelease: + { + /* Mask out the modifier states X11 sets and read again */ + event.xkey.state &= ~ShiftMask; + event.xkey.state &= ~LockMask; + + XLookupString(&event.xkey, 0, 0, &key, 0); + + /* Dispatch to external input handlers */ + if (patch_x11_event_loop_num_input_handlers > 0) { + /* Dispatch release if key is not repeated */ + if (!patch_x11_event_loop_is_key_repeated(ctx->display, &event)) { + pthread_mutex_lock(&patch_x11_event_loop_input_handlers_lock); + + for (size_t i = 0; i < patch_x11_event_loop_num_input_handlers; i++) { + if (patch_x11_event_loop_input_handlers[i]->dispatch_key_release) { + patch_x11_event_loop_input_handlers[i]->dispatch_key_release(key); + } + } + + pthread_mutex_unlock(&patch_x11_event_loop_input_handlers_lock); + } + } + + break; + } + + default: + break; + } + } + + // Avoid CPU banging + util_time_sleep_ms(1); + } + + log_info("Finished X11 event loop thread"); + + return NULL; +} + +Display *XOpenDisplay(const char *display_name) +{ + if (!patch_x11_event_loop_real_XOpenDisplay) { + patch_x11_event_loop_real_XOpenDisplay = (XOpenDisplay_t) cnh_lib_get_func_addr("XOpenDisplay"); + } + + log_debug("XOpenDisplay: %s", display_name); + + // This function needs to be called before any other X11 function is called. + // Required to pump events in separate thread + if (patch_x11_event_loop_enabled && XInitThreads() != Success) { + log_error("XInitThreads failed"); + } + + Display* res = patch_x11_event_loop_real_XOpenDisplay(display_name); + + if (!res) { + log_error("XOpenDisplay returned NULL. This might indicate that your environment is not properly setup. Check " + " that you have GPU drivers installed and configured properly and that OpenGL hardware acceleration is " + " working (use the command line tool \"glxinfo\")."); + } + + return res; +} + +Window XCreateWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, + unsigned int border_width, int depth, unsigned int _class, Visual *visual, unsigned long valuemask, + XSetWindowAttributes *attributes) +{ + log_debug("XCreateWindow"); + + if (!patch_x11_event_loop_real_XCreateWindow) { + patch_x11_event_loop_real_XCreateWindow = (XCreateWindow_t) cnh_lib_get_func_addr("XCreateWindow"); + } + + Window w = patch_x11_event_loop_real_XCreateWindow(display, parent, x, y, width, height, border_width, depth, + _class, visual, valuemask, attributes); + + if (patch_x11_event_loop_enabled && !patch_x11_event_loop_ctx) { + patch_x11_event_loop_ctx = util_xmalloc(sizeof(struct patch_x11_event_loop_ctx)); + + patch_x11_event_loop_ctx->display = display; + patch_x11_event_loop_ctx->window = w; + patch_x11_event_loop_ctx->run = ATOMIC_VAR_INIT(1); + + pthread_create(&patch_x11_event_loop_ctx->thread, NULL, patch_x11_event_loop_run, patch_x11_event_loop_ctx); + } + + return w; +} + +int XDestroyWindow(Display *display, Window w) +{ + log_debug("XDestroyWindow"); + + if (!patch_x11_event_loop_real_XDestroyWindow) { + patch_x11_event_loop_real_XDestroyWindow = (XDestroyWindow_t) cnh_lib_get_func_addr("XDestroyWindow"); + } + + if (patch_x11_event_loop_enabled && patch_x11_event_loop_ctx) { + log_debug("Stopping X11 event loop thread..."); + + atomic_store(&patch_x11_event_loop_ctx->run, 0); + pthread_join(patch_x11_event_loop_ctx->thread, NULL); + free(patch_x11_event_loop_ctx); + patch_x11_event_loop_ctx = NULL; + + log_debug("Stopped X11 event loop thread"); + } + + return patch_x11_event_loop_real_XDestroyWindow(display, w); +} + +void patch_x11_event_loop_init() +{ + patch_x11_event_loop_enabled = true; + log_info("Initialized"); +} + +void patch_x11_event_loop_add_input_handler(const char* lib_with_handler_impl) +{ + const struct ptapi_io_x11_input_hook_handler** new_array; + size_t new_size; + void* lib_handle; + ptapi_io_x11_input_handler_hook_t input_handler_hook_func; + const struct ptapi_io_x11_input_hook_handler* input_handler_hook; + + if (!lib_with_handler_impl) { + log_die("No library path with input handler implementation specified"); + } + + log_info("Loading library with x11-input-handler api implementation %s", lib_with_handler_impl); + + lib_handle = cnh_lib_load(lib_with_handler_impl); + + input_handler_hook_func = (ptapi_io_x11_input_handler_hook_t) + cnh_lib_get_func_addr_handle(lib_handle, "ptapi_io_x11_input_handler_hook"); + + input_handler_hook = input_handler_hook_func(); + + if (!input_handler_hook) { + log_warn("Library impl %s returned NULL hook handler, skipping", lib_with_handler_impl); + return; + } + + pthread_mutex_lock(&patch_x11_event_loop_input_handlers_lock); + + new_size = patch_x11_event_loop_num_input_handlers + 1; + new_array = realloc(patch_x11_event_loop_input_handlers, new_size * sizeof(struct ptapi_io_input_hook_handler*)); + + if (new_array != NULL) { + patch_x11_event_loop_input_handlers = new_array; + + patch_x11_event_loop_input_handlers[patch_x11_event_loop_num_input_handlers++] = input_handler_hook; + } else { + log_die("Out of memory"); + } + + pthread_mutex_unlock(&patch_x11_event_loop_input_handlers_lock); + + log_debug("Added input handler %p of lib %s", input_handler_hook, lib_with_handler_impl); +} \ No newline at end of file diff --git a/src/main/hook/patch/x11-event-loop.h b/src/main/hook/patch/x11-event-loop.h new file mode 100644 index 0000000..282ca17 --- /dev/null +++ b/src/main/hook/patch/x11-event-loop.h @@ -0,0 +1,18 @@ +#ifndef PROHOOK_X11_EVENT_LOOP_H +#define PROHOOK_X11_EVENT_LOOP_H + +/** + * Initialize this module which re-implements a X11 event loop processing keyboard button presses and releases. + * This is required for Pro 1/2 because the game has all X11 input handling removed. Therefore, we cannot use any + * ptapi io implementations using X11, e.g. keyboard inputs. + */ +void patch_x11_event_loop_init(); + +/** + * Register a handler (multiple possible) to receive X11 input events, e.g. key pressed, released + * + * @param lib_with_handler_impl Path to a library implementing the x11-input-handler API + */ +void patch_x11_event_loop_add_input_handler(const char* lib_with_handler_impl); + +#endif diff --git a/src/main/hook/pri/main.c b/src/main/hook/pri/main.c new file mode 100644 index 0000000..0d53da3 --- /dev/null +++ b/src/main/hook/pri/main.c @@ -0,0 +1,294 @@ +/** + * Hook library for Pump It Up: Prime + */ +#define LOG_MODULE "prihook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/amixer-block.h" +#include "hook/patch/blacklist-url.h" +#include "hook/patch/block-keyboard-grab.h" +#include "hook/patch/gfx.h" +#include "hook/patch/hasp.h" +#include "hook/patch/hdd-check.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/mounts.h" +#include "hook/patch/network.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/ram-wipe.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" +#include "hook/patch/usb-mnt.h" +#include "hook/patch/usbfs.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/net.h" +#include "util/sys-info.h" + +#include "options.h" +#include "usb-updates.h" + +/* Compiled binary data from data folder. Symbols are defined by compiler */ +extern const uint8_t _binary_prihook_hdd_raw_start[]; +extern const uint8_t _binary_prihook_hdd_raw_end[]; +extern const uint8_t _binary_prihook_hasp_key_start[]; +extern const uint8_t _binary_prihook_hasp_key_end[]; + +static void prihook_bootstrapping(int argc, char** argv, struct prihook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!prihook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + hook_core_piu_utils_fix_locale(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void prihook_patch_fs_init(struct prihook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void prihook_fs_redir_settings(struct prihook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void prihook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); + patch_usbfs_init(); +} + +static void prihook_fs_redirs_init(struct prihook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + prihook_fs_redir_settings(options); + + prihook_fs_redir_usb(); +} + +static void prihook_patch_fs_mounting_init() +{ + patch_mounts_init(); + + patch_usb_mnt_init(); +} + +static void prihook_patch_gfx_init(struct prihook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void prihook_patch_main_loop_init(struct prihook_options* options) +{ + log_assert(options); + + patch_main_loop_init(false, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } + + patch_block_keyboard_grab_init(); +} + +static void prihook_patch_sound_init(struct prihook_options* options) +{ + log_assert(options); + + patch_sound_init(options->patch.sound.device); + patch_axmier_block_init(); +} + +static void prihook_patch_sigsegv_init(struct prihook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void prihook_patch_dongle_init() +{ + patch_hasp_init((const uint8_t*) _binary_prihook_hasp_key_start, + ((uintptr_t) &_binary_prihook_hasp_key_end - (uintptr_t) &_binary_prihook_hasp_key_start)); +} + +static void prihook_patch_hdd_check_init() +{ + patch_hdd_check_init((const uint8_t*) _binary_prihook_hdd_raw_start, + ((uintptr_t) &_binary_prihook_hdd_raw_end - (uintptr_t) &_binary_prihook_hdd_raw_start)); +} + +static void prihook_patch_ram_wipe_init() +{ + patch_ram_wipe_init(); +} + +static void prihook_patch_piuio_init(struct prihook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +static void prihook_patch_net_init(struct prihook_options* options) +{ + patch_blacklist_url_init(); + /* update server */ + patch_blacklist_url_add("http://update_kr"); + /* game server */ + patch_blacklist_url_add("http://www2.piugame.com"); + + patch_network_init(); + + if (options->patch.net.server) { + char* address; + uint16_t port; + + if (!util_net_split_ipv4_str(options->patch.net.server, &address, &port)) { + log_warn("Invalid format for option network address specified, ignored"); + } else { + patch_network_redirect_server_address("115.68.108.183", address, (uint16_t) port); + free(address); + } + } else { + // Redirect to avoid phoning home if no server address is set + patch_network_redirect_server_address("115.68.108.183", "127.0.0.1", 12345); + } + + // Idk what this address is...redirect to something save for now + patch_network_redirect_server_address("45.76.222.114", "127.0.0.1", 12345); +} + +static void prihook_patch_usb_init() +{ + prihook_usb_updates_init(); +} + +void prihook_constructor(void) +{ + /* Nothing here */ +} + +void prihook_destructor(void) +{ + /* Nothing here */ +} + +void prihook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct prihook_options options; + + prihook_bootstrapping(argc, argv, &options, &game_data_path); + prihook_patch_fs_init(&options); + prihook_fs_redirs_init(&options, game_data_path); + prihook_patch_fs_mounting_init(); + prihook_patch_gfx_init(&options); + prihook_patch_main_loop_init(&options); + prihook_patch_sound_init(&options); + prihook_patch_sigsegv_init(&options); + prihook_patch_dongle_init(); + prihook_patch_hdd_check_init(); + prihook_patch_ram_wipe_init(); + prihook_patch_piuio_init(&options); + prihook_patch_net_init(&options); + prihook_patch_usb_init(); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void prihook_trap_after_main(void) +{ + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(prihook_constructor); +LIB_MAIN_DESTRUCTOR(prihook_destructor); +LIB_MAIN_TRAP_MAIN(prihook_trap_before_main, prihook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/pri/options.c b/src/main/hook/pri/options.c new file mode 100644 index 0000000..e78db1d --- /dev/null +++ b/src/main/hook/pri/options.c @@ -0,0 +1,183 @@ +#include "hook/pri/options.h" + +#include "util/options.h" + +#define PRIHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define PRIHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define PRIHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define PRIHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define PRIHOOK_OPTIONS_STR_PATCH_NET_SERVER "patch.net.server" +#define PRIHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define PRIHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define PRIHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define PRIHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define PRIHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define PRIHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +const struct util_options_def prihook_options_def[] = { + { + .name = PRIHOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (PRI) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_NET_SERVER, + .description = "Address and port of pumpnet server (e.g. 127.0.0.1:1234)", + .param = 'n', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = PRIHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +const struct util_options_defs prihook_options_defs = { + .usage_header = + "Pumptools prihook for Pump It Up: Prime, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./prihook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = prihook_options_def, + .ndefs = lengthof(prihook_options_def) +}; + +bool prihook_options_init(int argc, char** argv, struct prihook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&prihook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.settings = util_options_get_str(options_opt, PRIHOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, PRIHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, PRIHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + PRIHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, PRIHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.net.server = util_options_get_str(options_opt, PRIHOOK_OPTIONS_STR_PATCH_NET_SERVER); + options->patch.piuio.api_lib = util_options_get_str(options_opt, PRIHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, PRIHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, PRIHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, PRIHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, PRIHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, PRIHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/pri/options.h b/src/main/hook/pri/options.h new file mode 100644 index 0000000..e4ba4ed --- /dev/null +++ b/src/main/hook/pri/options.h @@ -0,0 +1,58 @@ +#ifndef PRIHOOK_OPTIONS_H +#define PRIHOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct prihook_options { + struct game { + const char* settings; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct net { + const char* server; + } net; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool prihook_options_init(int argc, char** argv, struct prihook_options* options); + +#endif diff --git a/src/main/hook/pri/prihook.hasp.key b/src/main/hook/pri/prihook.hasp.key new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/pri/prihook.hasp.key @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/pri/prihook.hdd.raw b/src/main/hook/pri/prihook.hdd.raw new file mode 100644 index 0000000..77b1c43 --- /dev/null +++ b/src/main/hook/pri/prihook.hdd.raw @@ -0,0 +1 @@ +DUMMY NOT RELEASED YET \ No newline at end of file diff --git a/src/main/hook/pri/usb-updates.c b/src/main/hook/pri/usb-updates.c new file mode 100644 index 0000000..876a7da --- /dev/null +++ b/src/main/hook/pri/usb-updates.c @@ -0,0 +1,27 @@ +#define LOG_MODULE "prihook-usb-updates" + +#include + +#include "capnhook/hook/filehook.h" + +#include "util/log.h" + +static enum cnh_result prihook_usb_updates_filehook(struct cnh_filehook_irp *irp) +{ + if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN) { + return cnh_filehook_invoke_next(irp); + } + + if (!strcmp(irp->open_filename, "/mnt/0/prime_") || !strcmp(irp->open_filename, "/mnt/1/prime_")) { + log_info("Blocking usb update %s", irp->open_filename); + + return CNH_RESULT_NO_SUCH_FILE_OR_DIR; + } + + return cnh_filehook_invoke_next(irp); +} + +void prihook_usb_updates_init(void) +{ + cnh_filehook_push_handler(prihook_usb_updates_filehook); +} \ No newline at end of file diff --git a/src/main/hook/pri/usb-updates.h b/src/main/hook/pri/usb-updates.h new file mode 100644 index 0000000..817a467 --- /dev/null +++ b/src/main/hook/pri/usb-updates.h @@ -0,0 +1,12 @@ +/** + * Block updates from usb flash drives on Prime + */ +#ifndef PRIHOOK_USB_UPDATES_H +#define PATCH_USB_UPDATES_H + +/** + * Initialize patch module + */ +void prihook_usb_updates_init(void); + +#endif \ No newline at end of file diff --git a/src/main/hook/pro/main.c b/src/main/hook/pro/main.c new file mode 100644 index 0000000..aed7037 --- /dev/null +++ b/src/main/hook/pro/main.c @@ -0,0 +1,218 @@ +/** + * Hook library for Pump It Up: Pro + */ +#define LOG_MODULE "prohook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/hook-mon.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" +#include "hook/patch/x11-event-loop.h" + +#include "hook/propatch/usb-fix.h" + +#include "hook/patch/piubtn.h" +#include "hook/patch/piuio.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/log.h" +#include "util/sys-info.h" + +#include "options.h" + +static void prohook_bootstrapping(int argc, char** argv, struct prohook_options* options) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!prohook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing"); +} + +static void prohook_patch_fs_init(struct prohook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void prohook_fs_redir_settings(struct prohook_options* options) +{ + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/data", abs_path); + free(abs_path); + } else { + log_warn("No settings (data) path specified"); + } +} + +static void prohook_fs_redir_data(struct prohook_options* options) +{ + if (options->game.data) { + char* abs_path = util_fs_get_abs_path(options->game.data); + + cnh_redir_add("/game", abs_path); + free(abs_path); + } else { + log_warn("No data (game) path specified"); + } +} + +static void prohook_fs_redirs_init(struct prohook_options* options) +{ + log_assert(options); + + prohook_fs_redir_settings(options); + prohook_fs_redir_data(options); +} + +static void prohook_patch_sigsegv_init(struct prohook_options* options) +{ + log_assert(options); + + /* seems like pump pro doesn't like it when injecting our own signal + handler. game crashes instantly when adding it. */ + /* + patch_sigsegv_init(); + */ +} + +static void prohook_patch_x11_event_loop_init(struct prohook_options* options) +{ + patch_x11_event_loop_init(); + + // Register up to two X11 input handlers for piuio and piubtn + + if (options->patch.x11_event_loop.api_lib) { + char* abs_path_lib = util_fs_get_abs_path(options->patch.x11_event_loop.api_lib); + + patch_x11_event_loop_add_input_handler(abs_path_lib); + free(abs_path_lib); + } + + if (options->patch.x11_event_loop.api_lib2) { + char* abs_path_lib = util_fs_get_abs_path(options->patch.x11_event_loop.api_lib2); + + patch_x11_event_loop_add_input_handler(abs_path_lib); + free(abs_path_lib); + } +} + +static void prohook_patch_usbprofiles(struct prohook_options* options) +{ + propatch_usb_fix_init( + options->patch.usb_profile.device_nodes, + options->patch.usb_profile.p1_bus_port, + options->patch.usb_profile.p2_bus_port); +} + +static void prohook_patch_piuio_init(struct prohook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +static void prohook_patch_piubtn_init(struct prohook_options* options) +{ + if (options->patch.piubtn.api_lib) { + char* abs_path_btnlib = util_fs_get_abs_path(options->patch.piubtn.api_lib); + + patch_piubtn_init(abs_path_btnlib); + free(abs_path_btnlib); + } +} + +void prohook_constructor(void) +{ + /* Nothing here */ +} + +void prohook_destructor(void) +{ + /* Nothing here */ +} + +void prohook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + struct prohook_options options; + + prohook_bootstrapping(argc, argv, &options); + prohook_patch_fs_init(&options); + prohook_fs_redirs_init(&options); + prohook_patch_sigsegv_init(&options); + prohook_patch_x11_event_loop_init(&options); + prohook_patch_usbprofiles(&options); + prohook_patch_piuio_init(&options); + prohook_patch_piubtn_init(&options); + + log_info("Hooking finished"); +} + +void prohook_trap_after_main(void) +{ + propatch_usb_fix_shutdown(); + patch_piubtn_shutdown(); + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(prohook_constructor); +LIB_MAIN_DESTRUCTOR(prohook_destructor); +LIB_MAIN_TRAP_MAIN(prohook_trap_before_main, prohook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/pro/options.c b/src/main/hook/pro/options.c new file mode 100644 index 0000000..9aadcc5 --- /dev/null +++ b/src/main/hook/pro/options.c @@ -0,0 +1,197 @@ +#include "hook/pro/options.h" + +#include "util/options.h" + +#define PROHOOK_OPTIONS_STR_GAME_DATA "game.data" +#define PROHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define PROHOOK_OPTIONS_STR_PATCH_PIUBTN_EMU_LIB "patch.piubtn.emu_lib" +#define PROHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define PROHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define PROHOOK_OPTIONS_STR_PATCH_USB_PROFILE_P1_BUS_PORT "patch.usb_profile.p1.bus_port" +#define PROHOOK_OPTIONS_STR_PATCH_USB_PROFILE_P2_BUS_PORT "patch.usb_profile.p2.bus_port" +#define PROHOOK_OPTIONS_STR_PATCH_USB_PROFILE_DEV_NODES "patch.usb_profile.dev_nodes" +#define PROHOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER "patch_x11_event_loop.input_handler_lib" +#define PROHOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER2 "patch_x11_event_loop.input_handler_lib2" +#define PROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define PROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +static const struct util_options_def prohook_options_def[] = { + { + .name = PROHOOK_OPTIONS_STR_GAME_DATA, + .description = "Path to game data (game) folder", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./game", + }, + { + .name = PROHOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (data) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_PIUBTN_EMU_LIB, + .description = "Path to library implementing the piubtn api for piubtn emulation", + .param = 'b', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_USB_PROFILE_P1_BUS_PORT, + .description = "Bus and port (format: X-X, e.g. 1-2) of the USB slot to assign to Player 1 when a USB thumb " + "drive is plugged in", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_USB_PROFILE_P2_BUS_PORT, + .description = "Bus and port (format: X-X, e.g. 1-2) of the USB slot to assign to Player 2 when a USB thumb " + "drive is plugged in", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_USB_PROFILE_DEV_NODES, + .description = "Device nodes of plugged in USB thumb drives to consider for mounting for usb profiles, format: " + "sdX,sdY e.g. sde,sdf", + .param = 't', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER2, + .description = "Path to a second library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'r', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = PROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +static const struct util_options_defs prohook_options_defs = { + .usage_header = + "Pumptools prohook for Pump It Up: Pro, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./prohook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = prohook_options_def, + .ndefs = lengthof(prohook_options_def) +}; + +bool prohook_options_init(int argc, char** argv, struct prohook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&prohook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.data = util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_GAME_DATA); + options->game.settings = util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.hook_mon.file = util_options_get_bool(options_opt, PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, PROHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.piubtn.api_lib = util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_PATCH_PIUBTN_EMU_LIB); + options->patch.piuio.api_lib = util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, PROHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.usb_profile.device_nodes = + util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_PATCH_USB_PROFILE_DEV_NODES); + options->patch.usb_profile.p1_bus_port = + util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_PATCH_USB_PROFILE_P1_BUS_PORT); + options->patch.usb_profile.p2_bus_port = + util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_PATCH_USB_PROFILE_P2_BUS_PORT); + options->patch.x11_event_loop.api_lib = + util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER); + options->patch.x11_event_loop.api_lib2 = + util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER2); + options->log.file = util_options_get_str(options_opt, PROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, PROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/pro/options.h b/src/main/hook/pro/options.h new file mode 100644 index 0000000..8cd6010 --- /dev/null +++ b/src/main/hook/pro/options.h @@ -0,0 +1,53 @@ +#ifndef PROHOOK_OPTIONS_H +#define PROHOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct prohook_options { + struct game { + const char* data; + const char* settings; + } game; + + struct patch { + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct piubtn { + const char* api_lib; + } piubtn; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct usb_profile { + const char* device_nodes; + const char* p1_bus_port; + const char* p2_bus_port; + } usb_profile; + + struct x11_event_loop { + const char* api_lib; + const char* api_lib2; + } x11_event_loop; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool prohook_options_init(int argc, char** argv, struct prohook_options* options); + +#endif diff --git a/src/main/hook/pro2/main.c b/src/main/hook/pro2/main.c new file mode 100644 index 0000000..ba6af5b --- /dev/null +++ b/src/main/hook/pro2/main.c @@ -0,0 +1,230 @@ +/** + * Hook library for Pump It Up: Pro 2 + */ +#define LOG_MODULE "pro2hook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/hook-mon.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" +#include "hook/patch/x11-event-loop.h" + +#include "hook/patch/piubtn.h" +#include "hook/patch/piuio.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/log.h" +#include "util/patch.h" +#include "util/sys-info.h" + +#include "options.h" + +/** + * Copy/paste from binary: + * int PatchOriginalDlopen() + * { + * int result; // eax@1 + * + * result = mprotect((void *)((unsigned int)dlopen & 0xFFFFF000), 0x2000u, 7) + 1; + * if ( !result ) + * { + * sub_836EF70("../../src/pump/SecureBinary.cpp", 127, (int)"Assertion 'ret != -1' failed"); + * sub_821B1B0("Assertion 'ret != -1' failed"); + * } + * if ( *(_BYTE *)dlopen != -1 ) + * { + * sub_836EF70("../../src/pump/SecureBinary.cpp", 130, (int)"Assertion 'pDlopen[0] == 0xFF' failed"); + * sub_821B1B0("Assertion 'pDlopen[0] == 0xFF' failed"); + * } + * *((_BYTE *)dlopen + 5) = 0x90u; + * *(_BYTE *)dlopen = 0xE9u; + * dword_865C03C = *(_DWORD *)dlopen; + * word_865C040 = *((_WORD *)dlopen + 2); + * *(_DWORD *)((char *)dlopen + 1) = (char *)sub_8383C90 + 0xF7FB232B; + * return result; + * } + */ +static void pro2hook_patch_secure_binary_dlopen_switcheroo() +{ + uint8_t buffer[4] = {0xC3, 0x90, 0x90, 0x90}; + + log_info("Patching dlopen switcheroo"); + + /* This address will only work with the latest revision executable (iirc + that was R5?) */ + util_patch_write_memory(0x8383BC0, buffer, 4); +} + +static void pro2hook_bootstrapping(int argc, char** argv, struct pro2hook_options* options) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!pro2hook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing"); +} + +static void pro2hook_patch_fs_init(struct pro2hook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void pro2hook_fs_redir_data(struct pro2hook_options* options) +{ + log_assert(options); + + if (options->game.data) { + char* abs_path = util_fs_get_abs_path(options->game.data); + + cnh_redir_add("/pro2", abs_path); + free(abs_path); + } else { + log_warn("No data (game) path specified"); + } +} + +static void pro2hook_fs_redirs_init(struct pro2hook_options* options) +{ + log_assert(options); + + pro2hook_fs_redir_data(options); +} + +static void pro2hook_patch_sigsegv_init(struct pro2hook_options* options) +{ + log_assert(options); + + /* seems like pump pro doesn't like it when injecting our own signal + handler. game crashes instantly when adding it. */ + /* + patch_sigsegv_init(); + */ +} + +static void pro2hook_patch_x11_event_loop_init(struct pro2hook_options* options) +{ + patch_x11_event_loop_init(); + + // Register up to two X11 input handlers for piuio and piubtn + + if (options->patch.x11_event_loop.api_lib) { + char* abs_path_lib = util_fs_get_abs_path(options->patch.x11_event_loop.api_lib); + + patch_x11_event_loop_add_input_handler(abs_path_lib); + free(abs_path_lib); + } + + if (options->patch.x11_event_loop.api_lib2) { + char* abs_path_lib = util_fs_get_abs_path(options->patch.x11_event_loop.api_lib2); + + patch_x11_event_loop_add_input_handler(abs_path_lib); + free(abs_path_lib); + } +} + +static void pro2hook_patch_piuio_init(struct pro2hook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +static void pro2hook_patch_piubtn_init(struct pro2hook_options* options) +{ + if (options->patch.piubtn.api_lib) { + char* abs_path_btnlib = util_fs_get_abs_path(options->patch.piubtn.api_lib); + + patch_piubtn_init(abs_path_btnlib); + free(abs_path_btnlib); + } +} + +static void pro2hook_patch_secure_binary() +{ + pro2hook_patch_secure_binary_dlopen_switcheroo(); +} + +void pro2hook_constructor(void) +{ + /* Nothing here */ +} + +void pro2hook_destructor(void) +{ + /* Nothing here */ +} + +void pro2hook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + struct pro2hook_options options; + + pro2hook_bootstrapping(argc, argv, &options); + pro2hook_patch_fs_init(&options); + pro2hook_fs_redirs_init(&options); + pro2hook_patch_sigsegv_init(&options); + pro2hook_patch_x11_event_loop_init(&options); + pro2hook_patch_piuio_init(&options); + pro2hook_patch_piubtn_init(&options); + pro2hook_patch_secure_binary(); + + log_info("Hooking finished"); +} + +void pro2hook_trap_after_main(void) +{ + patch_piubtn_shutdown(); + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(pro2hook_constructor); +LIB_MAIN_DESTRUCTOR(pro2hook_destructor); +LIB_MAIN_TRAP_MAIN(pro2hook_trap_before_main, pro2hook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/pro2/options.c b/src/main/hook/pro2/options.c new file mode 100644 index 0000000..1dac0c9 --- /dev/null +++ b/src/main/hook/pro2/options.c @@ -0,0 +1,155 @@ +#include "hook/pro2/options.h" + +#include "util/options.h" + +#define PRO2HOOK_OPTIONS_STR_GAME_DATA "game.data" +#define PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define PRO2HOOK_OPTIONS_STR_PATCH_PIUBTN_EMU_LIB "patch.piubtn.emu_lib" +#define PRO2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define PRO2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define PRO2HOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER "patch_x11_event_loop.input_handler_lib" +#define PRO2HOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER2 "patch_x11_event_loop.input_handler_lib2" +#define PRO2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define PRO2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +const struct util_options_def pro2hook_options_def[] = { + { + .name = PRO2HOOK_OPTIONS_STR_GAME_DATA, + .description = "Path to game data (game) folder", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./game", + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_PIUBTN_EMU_LIB, + .description = "Path to library implementing the piubtn api for piubtn emulation", + .param = 'b', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER2, + .description = "Path to a second library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'r', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = PRO2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +const struct util_options_defs pro2hook_options_defs = { + .usage_header = + "Pumptools prohook for Pump It Up: Pro 2, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./prohook2.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = pro2hook_options_def, + .ndefs = lengthof(pro2hook_options_def) +}; + +bool pro2hook_options_init(int argc, char** argv, struct pro2hook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&pro2hook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.data = util_options_get_str(options_opt, PRO2HOOK_OPTIONS_STR_GAME_DATA); + options->patch.hook_mon.file = util_options_get_bool(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.piubtn.api_lib = util_options_get_str(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_PIUBTN_EMU_LIB); + options->patch.piuio.api_lib = util_options_get_str(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.x11_event_loop.api_lib = + util_options_get_str(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER); + options->patch.x11_event_loop.api_lib2 = + util_options_get_str(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_X11_EVENT_LOOP_INPUT_HANDLER2); + options->log.file = util_options_get_str(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, PRO2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/pro2/options.h b/src/main/hook/pro2/options.h new file mode 100644 index 0000000..19600df --- /dev/null +++ b/src/main/hook/pro2/options.h @@ -0,0 +1,46 @@ +#ifndef PRO2HOOK_OPTIONS_H +#define PRO2HOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct pro2hook_options { + struct game { + const char* data; + } game; + + struct patch { + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct piubtn { + const char* api_lib; + } piubtn; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct x11_event_loop { + const char* api_lib; + const char* api_lib2; + } x11_event_loop; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool pro2hook_options_init(int argc, char** argv, struct pro2hook_options* options); + +#endif diff --git a/src/main/hook/propatch/usb-fix.c b/src/main/hook/propatch/usb-fix.c new file mode 100644 index 0000000..bd3021a --- /dev/null +++ b/src/main/hook/propatch/usb-fix.c @@ -0,0 +1,177 @@ +#define LOG_MODULE "usb-fix" + +#include + +#include "capnhook/hook/lib.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +typedef ssize_t (*readlink_t) (const char * path, char * buf, size_t len); +static readlink_t pro_usb_fix_real_readlink; + +static const int16_t propatch_usb_fix_usb_static_ini_usb_bus[2] = {1, 1}; +static const int16_t propatch_usb_fix_usb_static_ini_usb_port[2] = {2, 1}; + +static char propatch_usb_fix_device_thumb_drive_p1[128]; +static char propatch_usb_fix_device_thumb_drive_p2[128]; + +static int propatch_usb_fix_usb_bus[2]; +static int propatch_usb_fix_usb_port[2]; + +static bool propatch_usb_fix_enabled; + +// Non static for testing +ssize_t propatch_usb_fix_readlink(const char* path, char* buf, size_t len) +{ + if (!pro_usb_fix_real_readlink) { + pro_usb_fix_real_readlink = (readlink_t) cnh_lib_get_func_addr("readlink"); + } + + // pro is scanning the /sys/block directory where all usb thumb drives show up once connected + // The game iterates the directory and resolves the /sys/block/sdX/device symlink + // On current kernels, e.g. 5.6.14, it might look like this + // ../devices/pci0000:00/0000:00:14.0/usb2/2-2/2-2:1.0/host7/target7:0:0/7:0:0:0/block/sdd + // However, when pro was developed, the symlink looked entirely different. + // In order to allow the game to detect and mount usb thumb drives on newer kernels, the path + // needs to be altered to match what the game is expecting which might be similar to this: + // ../devices/pci0000:00/0000:00:14.0/usb2/2-2/2-2:1.0 + // The game tokenizes this path by / and picks the second to last item, which is '2-2' here. + // This item gets further split into bus (2) and port (2). If that is successful, the game treats the + // detected device as valid allowing it to mount if the bus and port match what has been configured in + // the Static.ini file + if (propatch_usb_fix_enabled && util_str_starts_with(path, "/sys/block/") && util_str_ends_with(path, "/device")) { + log_debug("Trap %s", path); + + // Remove the trailing "/device" part which is no longer valid on current kernels + // /sys/block/sdc/device -> /sys/block/sdc + char* tmp = util_str_dup(path); + size_t len_tmp = strlen(tmp); + tmp[len_tmp - 7] = '\0'; + + memset(buf, 0, len); + + ssize_t res = pro_usb_fix_real_readlink(tmp, buf, len); + + util_xfree((void**) &tmp); + + if (strstr(buf, propatch_usb_fix_device_thumb_drive_p1) || strstr(buf, propatch_usb_fix_device_thumb_drive_p2)) { + log_debug("Detected assigned flash drive plugged in"); + + size_t count; + char** toks; + + toks = util_str_split(buf, "/", &count); + + // some sanity checks to ensure the format isn't different + if ( count != 12 || + strcmp(toks[0], "..") != 0 || + strcmp(toks[1], "devices") != 0 || + strcmp(toks[10], "block") != 0) { + log_error( + "/sys/block device readlink format changed, usb thumb drive profiles will likely not work!: %s", + buf); + util_str_free_split(toks, count); + return 0; + } + + int bus; + int port; + const char* device = toks[11]; + + if (sscanf(toks[5], "%d-%d", &bus, &port) != 2) { + log_error("Parsing bus-port of %s failed", buf); + util_str_free_split(toks, count); + return 0; + } + + log_debug("Detected flash drive on bus %d, port %d, device %s", bus, port, device); + + for (int i = 0; i < 2; i++) { + if (propatch_usb_fix_usb_bus[i] == bus && propatch_usb_fix_usb_port[i] == port) { + // The usb manager only cares about reading the second to last token in the correct format + // Copy-paste everything else + sprintf( + buf, + "%s/%s/%s/%s/%s/%d-%d/%d-%d:1.0", + toks[0], + toks[1], + toks[2], + toks[3], + toks[4], + propatch_usb_fix_usb_static_ini_usb_bus[i], + propatch_usb_fix_usb_static_ini_usb_port[i], + propatch_usb_fix_usb_static_ini_usb_bus[i], + propatch_usb_fix_usb_static_ini_usb_port[i]); + + log_debug("Player %d usb thumb drive at path %s", i, buf); + + util_str_free_split(toks, count); + + return strlen(buf); + } + } + + util_str_free_split(toks, count); + } + + return res; + } + + return pro_usb_fix_real_readlink(path, buf, len); +} + +ssize_t readlink(const char* path, char* buf, size_t len) +{ + return propatch_usb_fix_readlink(path, buf, len); +} + +void propatch_usb_fix_init(const char* device_nodes, const char* p1_bus_port, const char* p2_bus_port) +{ + size_t count; + char** toks; + + if (!device_nodes || !p1_bus_port || !p2_bus_port) { + log_warn("Disabled, missing one or multiple option properties"); + return; + } + + toks = util_str_split(device_nodes, ",", &count); + + if (count != 2) { + log_error("Invalid format for device nodes: %s", device_nodes); + util_str_free_split(toks, count); + return; + } + + strcpy(propatch_usb_fix_device_thumb_drive_p1, toks[0]); + strcpy(propatch_usb_fix_device_thumb_drive_p2, toks[1]); + + util_str_free_split(toks, count); + + if (sscanf(p1_bus_port, "%d-%d", &propatch_usb_fix_usb_bus[0], &propatch_usb_fix_usb_port[0]) != 2) { + log_error("Invalid format for p1 bus-port: %s", p1_bus_port); + return; + } + + if (sscanf(p2_bus_port, "%d-%d", &propatch_usb_fix_usb_bus[1], &propatch_usb_fix_usb_port[1]) != 2) { + log_error("Invalid format for p2 bus-port: %s", p2_bus_port); + return; + } + + log_info("Initialized: device nodes %s,%s, p1 %d-%d, p2 %d-%d", + propatch_usb_fix_device_thumb_drive_p1, + propatch_usb_fix_device_thumb_drive_p2, + propatch_usb_fix_usb_bus[0], + propatch_usb_fix_usb_port[0], + propatch_usb_fix_usb_bus[1], + propatch_usb_fix_usb_port[1]); + + propatch_usb_fix_enabled = true; +} + +void propatch_usb_fix_shutdown() +{ + +} \ No newline at end of file diff --git a/src/main/hook/propatch/usb-fix.h b/src/main/hook/propatch/usb-fix.h new file mode 100644 index 0000000..0246d83 --- /dev/null +++ b/src/main/hook/propatch/usb-fix.h @@ -0,0 +1,5 @@ +#pragma once + +void propatch_usb_fix_init(const char* device_nodes, const char* p1_bus_port, const char* p2_bus_port); + +void propatch_usb_fix_shutdown(); \ No newline at end of file diff --git a/src/main/hook/x2/main.c b/src/main/hook/x2/main.c new file mode 100644 index 0000000..00b4e3f --- /dev/null +++ b/src/main/hook/x2/main.c @@ -0,0 +1,250 @@ +/** + * Hook library for Pump It Up: Exceed 2 + */ +#define LOG_MODULE "x2hook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/gfx.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/microdog34.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/patch.h" +#include "util/str.h" +#include "util/sys-info.h" + +#include "options.h" +#include "unlock.h" + +/* Compiled binary data from data folder. Symbols are defined by compiler */ +extern const uint8_t _binary_x2hook_dog_key_start[]; +extern const uint8_t _binary_x2hook_dog_key_end[]; + +static void x2hook_bootstrapping(int argc, char** argv, struct x2hook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!x2hook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void x2hook_patch_fs_init(struct x2hook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void x2hook_fs_redir_settings(struct x2hook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void x2hook_fs_redir_script(struct x2hook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + path_tmp = util_str_merge(game_data_path, "/SCRIPT"); + cnh_redir_add("/SCRIPT", path_tmp); + free(path_tmp); +} + +static void x2hook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); +} + +static void x2hook_fs_redirs_init(struct x2hook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + /* Expecting all assets in the piu game (asset) folder. redirects for files + that are not in there by default for reasons */ + x2hook_fs_redir_settings(options); + x2hook_fs_redir_script(options, game_data_path); + + x2hook_fs_redir_usb(); +} + +static void x2hook_patch_gfx_init(struct x2hook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void x2hook_patch_game_init(struct x2hook_options* options) +{ + log_assert(options); + + if (options->game.force_unlock) { + hook_x2_unlock_init(); + } + + /* return from StopAllEffects to avoid killing the sound thread */ + log_warn("Applying memory patch only valid for version 102"); + util_patch_write_memory_byte(0x08074CE0, 0xC3); +} + +static void x2hook_patch_main_loop_init(struct x2hook_options* options) +{ + log_assert(options); + + patch_main_loop_init(true, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void x2hook_patch_sound_init(struct x2hook_options* options) +{ + log_assert(options); + + patch_sound_init(options->patch.sound.device); +} + +static void x2hook_patch_sigsegv_init(struct x2hook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void x2hook_patch_dongle_init() +{ + patch_microdog34_init((const uint8_t*) _binary_x2hook_dog_key_start, + ((uintptr_t) &_binary_x2hook_dog_key_end - (uintptr_t) &_binary_x2hook_dog_key_start)); +} + +static void x2hook_patch_piuio_init(struct x2hook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +void x2hook_constructor(void) +{ + /* Nothing here */ +} + +void x2hook_destructor(void) +{ + /* Nothing here */ +} + +void x2hook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct x2hook_options options; + + x2hook_bootstrapping(argc, argv, &options, &game_data_path); + x2hook_patch_fs_init(&options); + x2hook_fs_redirs_init(&options, game_data_path); + x2hook_patch_gfx_init(&options); + x2hook_patch_game_init(&options); + x2hook_patch_main_loop_init(&options); + x2hook_patch_sound_init(&options); + x2hook_patch_sigsegv_init(&options); + x2hook_patch_dongle_init(); + x2hook_patch_piuio_init(&options); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void x2hook_trap_after_main(void) +{ + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(x2hook_constructor); +LIB_MAIN_DESTRUCTOR(x2hook_destructor); +LIB_MAIN_TRAP_MAIN(x2hook_trap_before_main, x2hook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/x2/ogl-fix.c b/src/main/hook/x2/ogl-fix.c new file mode 100644 index 0000000..067909e --- /dev/null +++ b/src/main/hook/x2/ogl-fix.c @@ -0,0 +1,38 @@ +/** + * Module to fix weird OpenGL error that only appeared on Exceed 2 thus far. + * + * If OpenGL is not forced into single threaded mode, the backend will run in + * multi-threaded mode for some reason and crash in __glDispatchCheckMultithreaded + * when the game calls glXChooseVisual in its GFX init function. + * + * The fix is quite simple (luckily) as this seems that Exceed 2 is not the only + * game that requires the environment variable __GL_SINGLETHREADED to be set. + */ + +#include +#include + +#include "capnhook/hook/lib.h" + +typedef char* (*getenv_t) (const char* name); + +static getenv_t _x2hook_ogl_fix_real_getenv; + +char* getenv(const char* name) +{ + if (!_x2hook_ogl_fix_real_getenv) { + _x2hook_ogl_fix_real_getenv = (getenv_t) cnh_lib_get_func_addr("getenv"); + } + + char* ret = _x2hook_ogl_fix_real_getenv(name); + + if (!strcmp(name, "__GL_SINGLETHREADED")) { + if (ret == NULL || strcmp(name, "1") != 0) { + printf("!!! x2hook: __GL_SINGLETHREADED not set or enabled, force patching OpenGL to single threaded to " + "avoid crash !!!\n"); + return "1"; + } + } + + return ret; +} \ No newline at end of file diff --git a/src/main/hook/x2/options.c b/src/main/hook/x2/options.c new file mode 100644 index 0000000..3816488 --- /dev/null +++ b/src/main/hook/x2/options.c @@ -0,0 +1,183 @@ +#include "hook/x2/options.h" + +#include "util/options.h" + +#define X2HOOK_OPTIONS_STR_GAME_FORCE_UNLOCK "game.force_unlock" +#define X2HOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define X2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define X2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define X2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define X2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define X2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define X2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define X2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define X2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define X2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +static const struct util_options_def x2hook_options_def[] = { + { + .name = X2HOOK_OPTIONS_STR_GAME_FORCE_UNLOCK, + .description = "Force unlock the game. Modifies the current and any new PIUEXCEED.INI settings file permanently", + .param = 'z', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = X2HOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = X2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +static const struct util_options_defs x2hook_options_defs = { + .usage_header = + "Pumptools x2hook for Pump It Up: Exceed 2, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./x2hook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = x2hook_options_def, + .ndefs = lengthof(x2hook_options_def) +}; + +bool x2hook_options_init(int argc, char** argv, struct x2hook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&x2hook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.force_unlock = util_options_get_bool(options_opt, X2HOOK_OPTIONS_STR_GAME_FORCE_UNLOCK); + options->game.settings = util_options_get_str(options_opt, X2HOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, X2HOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, X2HOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + X2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, X2HOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.piuio.api_lib = util_options_get_str(options_opt, X2HOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, X2HOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, X2HOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, X2HOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, X2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, X2HOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/x2/options.h b/src/main/hook/x2/options.h new file mode 100644 index 0000000..21f963d --- /dev/null +++ b/src/main/hook/x2/options.h @@ -0,0 +1,55 @@ +#ifndef X2HOOK_OPTIONS_H +#define X2HOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct x2hook_options { + struct game { + bool force_unlock; + const char* settings; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool x2hook_options_init(int argc, char** argv, struct x2hook_options* options); + +#endif diff --git a/src/main/hook/x2/unlock.c b/src/main/hook/x2/unlock.c new file mode 100644 index 0000000..459cf44 --- /dev/null +++ b/src/main/hook/x2/unlock.c @@ -0,0 +1,81 @@ +#define LOG_MODULE "hook-x2-unlock" + +#include +#include + +#include "asset/x2/lib/settings.h" + +#include "capnhook/hook/filehook.h" + +#include "util/log.h" +#include "util/str.h" + +static FILE* _hook_x2_unlock_settings_file = NULL; + +static enum cnh_result _hook_x2_unlock_filehook(struct cnh_filehook_irp *irp); + +void hook_x2_unlock_init(void) +{ + cnh_filehook_push_handler(_hook_x2_unlock_filehook); + + log_info("Initialized"); +} + +static enum cnh_result _hook_x2_unlock_filehook(struct cnh_filehook_irp *irp) +{ + enum cnh_result result; + + assert(irp); + + if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN && irp->file != _hook_x2_unlock_settings_file) { + return cnh_filehook_invoke_next(irp); + } + + switch (irp->op) { + case CNH_FILEHOOK_IRP_OP_OPEN: + { + /* Let it open the real file */ + result = cnh_filehook_invoke_next(irp); + + if (util_str_ends_with(irp->open_filename, ASSET_X2_SETTINGS_FILE_NAME)) { + if (result == CNH_RESULT_SUCCESS) { + log_debug("Intercepting settings file open"); + _hook_x2_unlock_settings_file = irp->file; + } + } + + break; + } + + case CNH_FILEHOOK_IRP_OP_READ: + { + /* Let it read the real file */ + result = cnh_filehook_invoke_next(irp); + + if (result == CNH_RESULT_SUCCESS) { + struct asset_x2_settings* settings = (struct asset_x2_settings*) irp->read.bytes; + + asset_x2_settings_unlock_all(settings); + + log_debug("Applied unlock patch to loaded settings (buffer size %d)", irp->read.pos); + } + + break; + } + + case CNH_FILEHOOK_IRP_OP_CLOSE: + { + _hook_x2_unlock_settings_file = NULL; + + result = cnh_filehook_invoke_next(irp); + + break; + } + + default: + result = cnh_filehook_invoke_next(irp); + break; + } + + return result; +} \ No newline at end of file diff --git a/src/main/hook/x2/unlock.h b/src/main/hook/x2/unlock.h new file mode 100644 index 0000000..4105a0a --- /dev/null +++ b/src/main/hook/x2/unlock.h @@ -0,0 +1,12 @@ +/** + * Patch module to force unlock all permanently unlockable songs and additional difficulties in the game. + */ +#ifndef HOOK_X2_UNLOCK_H +#define HOOK_X2_UNLOCK_H + +/** + * Initialize the patch module force unlocking all locked game content. + */ +void hook_x2_unlock_init(void); + +#endif diff --git a/src/main/hook/x2/x2hook.dog.key b/src/main/hook/x2/x2hook.dog.key new file mode 100755 index 0000000..2534418 Binary files /dev/null and b/src/main/hook/x2/x2hook.dog.key differ diff --git a/src/main/hook/zero/main.c b/src/main/hook/zero/main.c new file mode 100644 index 0000000..3ecbc00 --- /dev/null +++ b/src/main/hook/zero/main.c @@ -0,0 +1,255 @@ +/** + * Hook library for Pump It Up: Zero + */ +#define LOG_MODULE "zerohook" + +#include + +#include "capnhook/hook/lib-main.h" +#include "capnhook/hooklib/redir.h" + +#include "hook/core/piu-utils.h" + +#include "hook/patch/gfx.h" +#include "hook/patch/hdd-check.h" +#include "hook/patch/hook-mon.h" +#include "hook/patch/main-loop.h" +#include "hook/patch/microdog34.h" +#include "hook/patch/piuio.h" +#include "hook/patch/piuio-exit.h" +#include "hook/patch/redir.h" +#include "hook/patch/sigsegv.h" +#include "hook/patch/sound.h" +#include "hook/patch/usb-emu.h" +#include "hook/patch/usb-init-fix.h" + +#include "util/fs.h" +#include "util/glibc.h" +#include "util/str.h" +#include "util/sys-info.h" + +#include "options.h" +#include "unlock.h" + +/* Compiled binary data from data folder. Symbols are defined by compiler */ +extern const uint8_t _binary_zerohook_hdd_raw_start[]; +extern const uint8_t _binary_zerohook_hdd_raw_end[]; +extern const uint8_t _binary_zerohook_dog_key_start[]; +extern const uint8_t _binary_zerohook_dog_key_end[]; + +static void zerohook_bootstrapping(int argc, char** argv, struct zerohook_options* options, char** game_data_path) +{ + log_assert(argc >= 0); + log_assert(argv); + log_assert(options); + log_assert(game_data_path); + + if (hook_core_piu_block_non_piu_process_recursion(argc, argv)) { + return; + } + + hook_core_piu_log_init(argc, argv); + + if (!zerohook_options_init(argc, argv, options)) { + exit(0); + } + + hook_core_piu_log_info(argc, argv); + + *game_data_path = hook_core_piu_utils_get_and_verify_game_data_path(argc, argv); + + hook_core_piu_utils_verify_root_user(); + + log_info("Initializing, piu game data path: %s", *game_data_path); +} + +static void zerohook_patch_fs_init(struct zerohook_options* options) +{ + log_assert(options); + + patch_hook_mon_init( + options->patch.hook_mon.io, + options->patch.hook_mon.file, + options->patch.hook_mon.fs, + options->patch.hook_mon.usb, + options->patch.hook_mon.open); + + patch_redir_init(); +} + +static void zerohook_fs_redir_settings(struct zerohook_options* options) +{ + log_assert(options); + + if (options->game.settings) { + if (!util_fs_path_exists(options->game.settings)) { + log_info("Settings path %s does not exist, creating", options->game.settings); + + if (!util_fs_mkdir(options->game.settings)) { + log_error("Creating directory %s failed", options->game.settings); + } + } + + char* abs_path = util_fs_get_abs_path(options->game.settings); + + cnh_redir_add("/SETTINGS", abs_path); + free(abs_path); + } else { + log_warn("No settings path specified"); + } +} + +static void zerohook_fs_redir_script(struct zerohook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + char* path_tmp; + + path_tmp = util_str_merge(game_data_path, "/SCRIPT"); + cnh_redir_add("/SCRIPT", path_tmp); + free(path_tmp); +} + +static void zerohook_fs_redir_usb() +{ + /* Needed on newer kernels */ + cnh_redir_add("/proc/bus/usb/devices", "/sys/kernel/debug/usb/devices"); +} + +static void zerohook_fs_redirs_init(struct zerohook_options* options, const char* game_data_path) +{ + log_assert(options); + log_assert(game_data_path); + + /* Expecting all assets in the piu game (asset) folder. redirects for files + that are not in there by default for reasons */ + zerohook_fs_redir_settings(options); + zerohook_fs_redir_script(options, game_data_path); + + zerohook_fs_redir_usb(); +} + +static void zerohook_patch_gfx_init(struct zerohook_options* options) +{ + log_assert(options); + + patch_gfx_init(); + + if (options->patch.gfx.windowed) { + patch_gfx_force_window_mode(); + } +} + +static void zerohook_patch_game_init(struct zerohook_options* options) +{ + log_assert(options); + + if (options->game.force_unlock) { + hook_zero_unlock_init(); + } +} + +static void zerohook_patch_main_loop_init(struct zerohook_options* options) +{ + log_assert(options); + + patch_main_loop_init(true, options->patch.main_loop.disable_built_in_inputs, false); + + if (options->patch.main_loop.x11_input_handler_api_lib) { + char* abs_path_x11_input_hook_lib = util_fs_get_abs_path(options->patch.main_loop.x11_input_handler_api_lib); + + patch_main_loop_add_x11_input_handler(abs_path_x11_input_hook_lib); + free(abs_path_x11_input_hook_lib); + } +} + +static void zerohook_patch_sound_init(struct zerohook_options* options) +{ + log_assert(options); + + patch_sound_init(options->patch.sound.device); +} + +static void zerohook_patch_sigsegv_init(struct zerohook_options* options) +{ + log_assert(options); + + patch_sigsegv_init(options->patch.sigsegv.halt_on_segv); +} + +static void zerohook_patch_dongle_init() +{ + patch_microdog34_init((const uint8_t*) _binary_zerohook_dog_key_start, + ((uintptr_t) &_binary_zerohook_dog_key_end - (uintptr_t) &_binary_zerohook_dog_key_start)); +} + +static void zerohook_patch_hdd_check_init() +{ + patch_hdd_check_init((const uint8_t*) _binary_zerohook_hdd_raw_start, + ((uintptr_t) &_binary_zerohook_hdd_raw_end - (uintptr_t) &_binary_zerohook_hdd_raw_start)); +} + +static void zerohook_patch_piuio_init(struct zerohook_options* options) +{ + log_assert(options); + + /* Hook before IO emulation */ + if (options->patch.piuio.exit_test_serv) { + patch_piuio_exit_init(); + } + + patch_usb_init_fix_init(); + patch_usb_emu_init(); + + if (options->patch.piuio.api_lib) { + char* abs_path_iolib = util_fs_get_abs_path(options->patch.piuio.api_lib); + + patch_piuio_init(abs_path_iolib); + free(abs_path_iolib); + } +} + +void zerohook_constructor(void) +{ + /* Nothing here */ +} + +void zerohook_destructor(void) +{ + /* Nothing here */ +} + +void zerohook_trap_before_main(int argc, char** argv) +{ + log_assert(argc >= 0); + log_assert(argv); + + char* game_data_path; + struct zerohook_options options; + + zerohook_bootstrapping(argc, argv, &options, &game_data_path); + zerohook_patch_fs_init(&options); + zerohook_fs_redirs_init(&options, game_data_path); + zerohook_patch_gfx_init(&options); + zerohook_patch_game_init(&options); + zerohook_patch_main_loop_init(&options); + zerohook_patch_sound_init(&options); + zerohook_patch_sigsegv_init(&options); + zerohook_patch_dongle_init(); + zerohook_patch_hdd_check_init(); + zerohook_patch_piuio_init(&options); + + free(game_data_path); + + log_info("Hooking finished"); +} + +void zerohook_trap_after_main(void) +{ + patch_piuio_shutdown(); +} + +LIB_MAIN_CONSTRUCTOR(zerohook_constructor); +LIB_MAIN_DESTRUCTOR(zerohook_destructor); +LIB_MAIN_TRAP_MAIN(zerohook_trap_before_main, zerohook_trap_after_main); \ No newline at end of file diff --git a/src/main/hook/zero/options.c b/src/main/hook/zero/options.c new file mode 100644 index 0000000..74166bc --- /dev/null +++ b/src/main/hook/zero/options.c @@ -0,0 +1,183 @@ +#include "hook/zero/options.h" + +#include "util/options.h" + +#define ZEROHOOK_OPTIONS_STR_GAME_FORCE_UNLOCK "game.force_unlock" +#define ZEROHOOK_OPTIONS_STR_GAME_SETTINGS "game.settings" +#define ZEROHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED "patch.gfx.windowed" +#define ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE "patch.hook_mon.file" +#define ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS "patch.hook_mon.fs" +#define ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO "patch.hook_mon.io" +#define ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN "patch.hook_mon.open" +#define ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB "patch.hook_mon.usb" +#define ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS "patch.hook_main_loop.disable_built_in_inputs" +#define ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER "patch_hook_main_loop.x11_input_handler" +#define ZEROHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB "patch.piuio.emu_lib" +#define ZEROHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV "patch.piuio_exit.test_serv" +#define ZEROHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE "patch.sound.device" +#define ZEROHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV "patch.sigsegv.halt_on_segv" +#define ZEROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE "util.log.file" +#define ZEROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL "util.log.level" + +static const struct util_options_def zerohook_options_def[] = { + { + .name = ZEROHOOK_OPTIONS_STR_GAME_FORCE_UNLOCK, + .description = "Force unlock the game. Modifies the current and any new PIUZERO.INI settings file permanently", + .param = 'z', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_GAME_SETTINGS, + .description = "Path to game settings (SETTINGS) folder", + .param = 's', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "./save", + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED, + .description = "Force game into window mode", + .param = 'w', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE, + .description = "Enable file call monitoring", + .param = 'f', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS, + .description = "Enable file system related call monitoring", + .param = 'g', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO, + .description = "Enable IO syscall monitoring", + .param = 'i', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN, + .description = "Enable any file/IO open call monitoring", + .param = 'j', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB, + .description = "Enable libusb call monitoring", + .param = 'u', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS, + .description = "Disable the built in keyboard inputs for test (F1), service (F2) and clear (F3)", + .param = 'k', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER, + .description = "Path to a library implementing the x11-input-handler api to capture X11 keyboard inputs", + .param = 'q', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB, + .description = "Path to library implementing the piuio api for piuio emulation", + .param = 'p', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = NULL, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV, + .description = "Enable game exit on Test + Service", + .param = 'e', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.b = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE, + .description = "Select the sound device to open on snd_pcm_open", + .param = 'a', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "dmix", + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV, + .description = "Halt on sigsegv to attach a debugger to the process", + .param = 'd', + .type = UTIL_OPTIONS_TYPE_BOOL, + .default_value.str = false, + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE, + .description = "Print the log output to the specified file", + .param = 'o', + .type = UTIL_OPTIONS_TYPE_STR, + .default_value.str = "pumptools.log", + }, + { + .name = ZEROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL, + .description = "Set the log level (0-4)", + .param = 'l', + .type = UTIL_OPTIONS_TYPE_INT, + .default_value.i = LOG_LEVEL_DEBUG, + }, +}; + +static const struct util_options_defs zerohook_options_defs = { + .usage_header = + "Pumptools zerohook for Pump It Up: Zero, build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) "\n" + "Usage: LD_PRELOAD=./zerohook.so [game exec] [game data path] ", + .usage_param = 'h', + .defs = zerohook_options_def, + .ndefs = lengthof(zerohook_options_def) +}; + +bool zerohook_options_init(int argc, char** argv, struct zerohook_options* options) +{ + log_assert(argv); + log_assert(options); + + struct util_options_opts* options_opt; + + util_options_init(argc, argv); + options_opt = util_options_get(&zerohook_options_defs); + + if (!options_opt) { + return false; + } + + options->game.force_unlock = util_options_get_bool(options_opt, ZEROHOOK_OPTIONS_STR_GAME_FORCE_UNLOCK); + options->game.settings = util_options_get_str(options_opt, ZEROHOOK_OPTIONS_STR_GAME_SETTINGS); + options->patch.gfx.windowed = util_options_get_bool(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_GFX_WINDOWED); + options->patch.hook_mon.file = util_options_get_bool(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FILE); + options->patch.hook_mon.fs = util_options_get_bool(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_FS); + options->patch.hook_mon.io = util_options_get_bool(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_IO); + options->patch.hook_mon.open = util_options_get_bool(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_OPEN); + options->patch.hook_mon.usb = util_options_get_bool(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MON_USB); + options->patch.main_loop.disable_built_in_inputs = util_options_get_bool(options_opt, + ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_DISABLE_BUILT_IN_INPUTS); + options->patch.main_loop.x11_input_handler_api_lib = + util_options_get_str(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_HOOK_MAIN_LOOP_X11_INPUT_HANDLER); + options->patch.piuio.api_lib = util_options_get_str(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_PIUIO_EMU_LIB); + options->patch.piuio.exit_test_serv = + util_options_get_bool(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_PIUIO_EXIT_TEST_SERV); + options->patch.sound.device = util_options_get_str(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_SOUND_DEVICE); + options->patch.sigsegv.halt_on_segv = + util_options_get_bool(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_SIGSEGV_HALT_ON_SEGV); + options->log.file = util_options_get_str(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_FILE); + options->log.level = + (enum util_log_level) util_options_get_int(options_opt, ZEROHOOK_OPTIONS_STR_PATCH_UTIL_LOG_LEVEL); + + return true; +} \ No newline at end of file diff --git a/src/main/hook/zero/options.h b/src/main/hook/zero/options.h new file mode 100644 index 0000000..a5e4f00 --- /dev/null +++ b/src/main/hook/zero/options.h @@ -0,0 +1,55 @@ +#ifndef ZEROHOOK_OPTIONS_H +#define ZEROHOOK_OPTIONS_H + +#include +#include + +#include "util/log.h" + +struct zerohook_options { + struct game { + bool force_unlock; + const char* settings; + } game; + + struct patch { + struct gfx { + bool windowed; + } gfx; + + struct hook_mon { + bool file; + bool fs; + bool io; + bool open; + bool usb; + } hook_mon; + + struct main_loop { + bool disable_built_in_inputs; + const char* x11_input_handler_api_lib; + } main_loop; + + struct piuio { + const char* api_lib; + bool exit_test_serv; + } piuio; + + struct sound { + const char* device; + } sound; + + struct sigsegv { + bool halt_on_segv; + } sigsegv; + } patch; + + struct log { + const char* file; + enum util_log_level level; + } log; +}; + +bool zerohook_options_init(int argc, char** argv, struct zerohook_options* options); + +#endif diff --git a/src/main/hook/zero/unlock.c b/src/main/hook/zero/unlock.c new file mode 100644 index 0000000..bb088c7 --- /dev/null +++ b/src/main/hook/zero/unlock.c @@ -0,0 +1,81 @@ +#define LOG_MODULE "hook-zero-unlock" + +#include +#include + +#include "asset/zero/lib/settings.h" + +#include "capnhook/hook/filehook.h" + +#include "util/log.h" +#include "util/str.h" + +static FILE* _hook_zerounlock_settings_file = NULL; + +static enum cnh_result _hook_zerounlock_filehook(struct cnh_filehook_irp *irp); + +void hook_zero_unlock_init(void) +{ + cnh_filehook_push_handler(_hook_zerounlock_filehook); + + log_info("Initialized"); +} + +static enum cnh_result _hook_zerounlock_filehook(struct cnh_filehook_irp *irp) +{ + enum cnh_result result; + + assert(irp); + + if (irp->op != CNH_FILEHOOK_IRP_OP_OPEN && irp->file != _hook_zerounlock_settings_file) { + return cnh_filehook_invoke_next(irp); + } + + switch (irp->op) { + case CNH_FILEHOOK_IRP_OP_OPEN: + { + /* Let it open the real file */ + result = cnh_filehook_invoke_next(irp); + + if (util_str_ends_with(irp->open_filename, ASSET_ZERO_SETTINGS_FILE_NAME)) { + if (result == CNH_RESULT_SUCCESS) { + log_debug("Intercepting settings file open"); + _hook_zerounlock_settings_file = irp->file; + } + } + + break; + } + + case CNH_FILEHOOK_IRP_OP_READ: + { + /* Let it read the real file */ + result = cnh_filehook_invoke_next(irp); + + if (result == CNH_RESULT_SUCCESS) { + struct asset_zero_settings* settings = (struct asset_zero_settings*) irp->read.bytes; + + asset_zero_settings_unlock_all(settings); + + log_debug("Applied unlock patch to loaded settings (buffer size %d)", irp->read.pos); + } + + break; + } + + case CNH_FILEHOOK_IRP_OP_CLOSE: + { + _hook_zerounlock_settings_file = NULL; + + result = cnh_filehook_invoke_next(irp); + + break; + } + + default: + result = cnh_filehook_invoke_next(irp); + break; + } + + return result; +} \ No newline at end of file diff --git a/src/main/hook/zero/unlock.h b/src/main/hook/zero/unlock.h new file mode 100644 index 0000000..a41c593 --- /dev/null +++ b/src/main/hook/zero/unlock.h @@ -0,0 +1,12 @@ +/** + * Patch module to force unlock all songs and missions of the game. + */ +#ifndef HOOK_ZERO_UNLOCK_H +#define HOOK_ZERO_UNLOCK_H + +/** + * Initialize the patch module force unlocking all locked game content. + */ +void hook_zero_unlock_init(void); + +#endif diff --git a/src/main/hook/zero/zerohook.dog.key b/src/main/hook/zero/zerohook.dog.key new file mode 100755 index 0000000..42eb75f Binary files /dev/null and b/src/main/hook/zero/zerohook.dog.key differ diff --git a/src/main/hook/zero/zerohook.hdd.raw b/src/main/hook/zero/zerohook.hdd.raw new file mode 100755 index 0000000..7185727 Binary files /dev/null and b/src/main/hook/zero/zerohook.hdd.raw differ diff --git a/src/main/io/piubtn/defs.h b/src/main/io/piubtn/defs.h new file mode 100644 index 0000000..73bd7c8 --- /dev/null +++ b/src/main/io/piubtn/defs.h @@ -0,0 +1,17 @@ +#ifndef PIUBTN_DRV_DEFS_H +#define PIUBTN_DRV_DEFS_H + +#define PIUBTN_DRV_VID 0x0D2F +#define PIUBTN_DRV_PID 0x1010 + +#define PIUBTN_DRV_CONFIG 0x01 +#define PIUBTN_DRV_IFACE 0x00 + +#define PIUBTN_DRV_USB_CTRL_TYPE_IN 0xC0 +#define PIUBTN_DRV_USB_CTRL_TYPE_OUT 0x40 +#define PIUBTN_DRV_USB_CTRL_REQUEST 0xAE +#define PIUBTN_DRV_USB_REQ_TIMEOUT 10000 + +#define PIUBTN_DRV_BUFFER_SIZE 8 + +#endif \ No newline at end of file diff --git a/src/main/io/piubtn/device.c b/src/main/io/piubtn/device.c new file mode 100644 index 0000000..e0c8e95 --- /dev/null +++ b/src/main/io/piubtn/device.c @@ -0,0 +1,84 @@ +#define LOG_MODULE "piubtn-drv-device" + +#include "io/util/usb_.h" + +#include "util/log.h" + +#include "defs.h" +#include "device.h" + +static void* piubtn_drv_device_handle; + +bool piubtn_drv_device_open(void) +{ + if (piubtn_drv_device_handle) { + log_warn("Device already opened"); + return true; + } + + piubtn_drv_device_handle = io_usb_open(PIUBTN_DRV_VID, PIUBTN_DRV_PID, + PIUBTN_DRV_CONFIG, PIUBTN_DRV_IFACE); + + return piubtn_drv_device_handle; +} + +bool piubtn_drv_device_read(uint8_t* buffer, uint8_t len) +{ + int32_t res; + + if (!piubtn_drv_device_handle) { + log_error("Device not opened"); + return false; + } + + if (len < PIUBTN_DRV_BUFFER_SIZE) { + log_error("Read failed, buffer (%d) too small", len); + return false; + } + + res = io_usb_control_transfer(piubtn_drv_device_handle, + PIUBTN_DRV_USB_CTRL_TYPE_IN, PIUBTN_DRV_USB_CTRL_REQUEST, 0, 0, buffer, + PIUBTN_DRV_BUFFER_SIZE, PIUBTN_DRV_USB_REQ_TIMEOUT); + + if (res != PIUBTN_DRV_BUFFER_SIZE) { + log_error("Read failed: %d", res); + return false; + } + + return true; +} + +bool piubtn_drv_device_write(uint8_t* buffer, uint8_t len) +{ + int32_t res; + + if (!piubtn_drv_device_handle) { + log_error("Device not opened"); + return false; + } + + if (len < PIUBTN_DRV_BUFFER_SIZE) { + log_error("Write failed, buffer (%d) too small", len); + return false; + } + + res = io_usb_control_transfer(piubtn_drv_device_handle, + PIUBTN_DRV_USB_CTRL_TYPE_OUT, PIUBTN_DRV_USB_CTRL_REQUEST, 0, 0, buffer, + PIUBTN_DRV_BUFFER_SIZE, PIUBTN_DRV_USB_REQ_TIMEOUT); + + if (res != PIUBTN_DRV_BUFFER_SIZE) { + log_error("Write failed: %d", res); + return false; + } + + return true; +} + +void piubtn_drv_device_close(void) +{ + if (!piubtn_drv_device_handle) { + log_error("Device not opened"); + } + + io_usb_close(piubtn_drv_device_handle); +} \ No newline at end of file diff --git a/src/main/io/piubtn/device.h b/src/main/io/piubtn/device.h new file mode 100644 index 0000000..e2bc097 --- /dev/null +++ b/src/main/io/piubtn/device.h @@ -0,0 +1,41 @@ +/** + * This module serves as a low level device implementation for a usb piubtn + * io. It uses libusb-1.0 to communicate with the usb device. + */ +#ifndef PIUBTN_DRV_DEVICE_H +#define PIUBTN_DRV_DEVICE_H + +#include +#include + +/** + * Open a connected usb piubtn device + * + * @return True if opening a piubtn device was successful, false on failure + */ +bool piubtn_drv_device_open(void); + +/** + * Read raw data from the piubtn device + * + * @param buffer Allocated buffer to read data from the device into + * @param len Number of bytes to read + * @return True if reading data to device was successful, false otherwise + */ +bool piubtn_drv_device_read(uint8_t* buffer, uint8_t len); + +/** + * Write raw data to the piubtn device + * + * @param buffer Buffer with data to write + * @param len Number of bytes to write + * @return True if writing data to device was successful, false otherwise + */ +bool piubtn_drv_device_write(uint8_t* buffer, uint8_t len); + +/** + * Close the opened piubtn device + */ +void piubtn_drv_device_close(void); + +#endif \ No newline at end of file diff --git a/src/main/io/piuio/defs.h b/src/main/io/piuio/defs.h new file mode 100644 index 0000000..dd3641a --- /dev/null +++ b/src/main/io/piuio/defs.h @@ -0,0 +1,17 @@ +#ifndef PIUIO_DRV_DEFS_H +#define PIUIO_DRV_DEFS_H + +#define PIUIO_DRV_VID 0x0547 +#define PIUIO_DRV_PID 0x1002 + +#define PIUIO_DRV_CONFIG 0x01 +#define PIUIO_DRV_IFACE 0x00 + +#define PIUIO_DRV_USB_CTRL_TYPE_IN 0xC0 +#define PIUIO_DRV_USB_CTRL_TYPE_OUT 0x40 +#define PIUIO_DRV_USB_CTRL_REQUEST 0xAE +#define PIUIO_DRV_USB_REQ_TIMEOUT 10000 + +#define PIUIO_DRV_BUFFER_SIZE 8 + +#endif \ No newline at end of file diff --git a/src/main/io/piuio/device.c b/src/main/io/piuio/device.c new file mode 100644 index 0000000..b1db8b5 --- /dev/null +++ b/src/main/io/piuio/device.c @@ -0,0 +1,84 @@ +#define LOG_MODULE "piuio-drv-device" + +#include "io/util/usb_.h" + +#include "util/log.h" + +#include "defs.h" +#include "device.h" + +static void* piuio_drv_device_handle; + +bool piuio_drv_device_open(void) +{ + if (piuio_drv_device_handle) { + log_warn("Device already opened"); + return true; + } + + piuio_drv_device_handle = io_usb_open(PIUIO_DRV_VID, PIUIO_DRV_PID, + PIUIO_DRV_CONFIG, PIUIO_DRV_IFACE); + + return piuio_drv_device_handle; +} + +bool piuio_drv_device_read(uint8_t* buffer, uint8_t len) +{ + int32_t res; + + if (!piuio_drv_device_handle) { + log_error("Device not opened"); + return false; + } + + if (len < PIUIO_DRV_BUFFER_SIZE) { + log_error("Read failed, buffer (%d) too small", len); + return false; + } + + res = io_usb_control_transfer(piuio_drv_device_handle, + PIUIO_DRV_USB_CTRL_TYPE_IN, PIUIO_DRV_USB_CTRL_REQUEST, 0, 0, buffer, + PIUIO_DRV_BUFFER_SIZE, PIUIO_DRV_USB_REQ_TIMEOUT); + + if (res != PIUIO_DRV_BUFFER_SIZE) { + log_error("Read failed: %d", res); + return false; + } + + return true; +} + +bool piuio_drv_device_write(uint8_t* buffer, uint8_t len) +{ + int32_t res; + + if (!piuio_drv_device_handle) { + log_error("Device not opened"); + return false; + } + + if (len < PIUIO_DRV_BUFFER_SIZE) { + log_error("Write failed, buffer (%d) too small", len); + return false; + } + + res = io_usb_control_transfer(piuio_drv_device_handle, + PIUIO_DRV_USB_CTRL_TYPE_OUT, PIUIO_DRV_USB_CTRL_REQUEST, 0, 0, buffer, + PIUIO_DRV_BUFFER_SIZE, PIUIO_DRV_USB_REQ_TIMEOUT); + + if (res != PIUIO_DRV_BUFFER_SIZE) { + log_error("Write failed: %d", res); + return false; + } + + return true; +} + +void piuio_drv_device_close(void) +{ + if (!piuio_drv_device_handle) { + log_error("Device not opened"); + } + + io_usb_close(piuio_drv_device_handle); +} \ No newline at end of file diff --git a/src/main/io/piuio/device.h b/src/main/io/piuio/device.h new file mode 100644 index 0000000..82d7400 --- /dev/null +++ b/src/main/io/piuio/device.h @@ -0,0 +1,41 @@ +/** + * This module serves as a low level device implementation for a usb piuio + * card. It uses libusb-1.0 to communicate with the usb device. + */ +#ifndef PIUIO_DRV_DEVICE_H +#define PIUIO_DRV_DEVICE_H + +#include +#include + +/** + * Open a connected usb piuio device + * + * @return True if opening a piuio device was successful, false on failure + */ +bool piuio_drv_device_open(void); + +/** + * Read raw data from the piuio device + * + * @param buffer Allocated buffer to read data from the device into + * @param len Number of bytes to read + * @return True if reading data to device was successful, false otherwise + */ +bool piuio_drv_device_read(uint8_t* buffer, uint8_t len); + +/** + * Write raw data to the piuio device + * + * @param buffer Buffer with data to write + * @param len Number of bytes to write + * @return True if writing data to device was successful, false otherwise + */ +bool piuio_drv_device_write(uint8_t* buffer, uint8_t len); + +/** + * Close the opened piuio device + */ +void piuio_drv_device_close(void); + +#endif \ No newline at end of file diff --git a/src/main/io/util/joystick-util.c b/src/main/io/util/joystick-util.c new file mode 100644 index 0000000..74be37b --- /dev/null +++ b/src/main/io/util/joystick-util.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +#include +#include + +#include "io/util/joystick-util.h" + +#include "util/fs.h" +#include "util/log.h" + +size_t io_util_joystick_util_scan(struct io_util_joystick_util_device_info* devices, size_t len) +{ + log_assert(devices); + + char dev_path[PATH_MAX]; + int handle; + size_t num_joysticks; + + num_joysticks = 0; + + for (size_t i = 0; i < len; i++) { + sprintf(dev_path, "/dev/input/js%d", i); + + memset(&devices[num_joysticks], 0, sizeof(struct io_util_joystick_util_device_info)); + + if (util_fs_path_exists(dev_path)) { + // temporarily have to open the device + handle = open(dev_path, O_RDONLY | O_NONBLOCK); + + if (handle == -1) { + log_error("Opening joystick device %s failed: %s", dev_path, strerror(errno)); + continue; + } + + strcpy(devices[num_joysticks].dev_path, dev_path); + + ioctl(handle, JSIOCGAXES, &(devices[num_joysticks].num_axes)); + ioctl(handle, JSIOCGBUTTONS, &(devices[num_joysticks].num_buttons)); + ioctl(handle, JSIOCGNAME(JOYSTICK_NAME_LEN), devices[num_joysticks].name); + + close(handle); + + num_joysticks++; + } + } + + return num_joysticks; +} \ No newline at end of file diff --git a/src/main/io/util/joystick-util.h b/src/main/io/util/joystick-util.h new file mode 100644 index 0000000..f9becfd --- /dev/null +++ b/src/main/io/util/joystick-util.h @@ -0,0 +1,20 @@ +#ifndef IO_UTIL_JOYSTICK_UTIL_H +#define IO_UTIL_JOYSTICK_UTIL_H + +#include +#include +#include +#include + +#define JOYSTICK_NAME_LEN 128 + +struct io_util_joystick_util_device_info { + char dev_path[PATH_MAX]; + uint16_t num_buttons; + uint16_t num_axes; + char name[JOYSTICK_NAME_LEN]; +}; + +size_t io_util_joystick_util_scan(struct io_util_joystick_util_device_info* devices, size_t len); + +#endif diff --git a/src/main/io/util/joystick.c b/src/main/io/util/joystick.c new file mode 100644 index 0000000..3dee804 --- /dev/null +++ b/src/main/io/util/joystick.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +#include +#include + +#include "io/util/joystick.h" + +#include "util/log.h" +#include "util/mem.h" + +#define MAX_NUM_EVENTS 64 + +bool io_util_joystick_open(const char* dev_path, struct io_util_joystick** ctx) +{ + log_assert(dev_path); + log_assert(ctx); + + int handle; + + handle = open(dev_path, O_RDONLY | O_NONBLOCK); + + if (handle == -1) { + log_error("Opening joystick device %s failed: %s", dev_path, strerror(errno)); + return false; + } + + *ctx = util_xmalloc(sizeof(struct io_util_joystick)); + + strcpy((*ctx)->dev_path, dev_path); + (*ctx)->handle = handle; + + // Init values to avoid having ioctl return garbage + (*ctx)->num_buttons = 0; + (*ctx)->num_axes = 0; + memset((*ctx)->name, 0, sizeof(char) * JOYSTICK_NAME_LEN); + + // grab data + ioctl(handle, JSIOCGAXES, &((*ctx)->num_axes)); + ioctl(handle, JSIOCGBUTTONS, &((*ctx)->num_buttons)); + ioctl(handle, JSIOCGNAME(JOYSTICK_NAME_LEN), (*ctx)->name); + + (*ctx)->button_state = util_xmalloc(sizeof(uint8_t) * (*ctx)->num_buttons); + + log_debug("Opened joystick device %s, handle %p, num buttons %d, num axes %d, name %s", (*ctx)->dev_path, + (*ctx)->handle, (*ctx)->num_buttons, (*ctx)->num_axes, (*ctx)->name); + + return true; +} + +bool io_util_joystick_close(struct io_util_joystick* ctx) +{ + log_assert(ctx); + + bool res; + + log_debug("Closing joystick device %s, handle %p", ctx->dev_path, ctx->handle); + + if (close(ctx->handle) != 0) { + log_error("Closing joystick device %s failed: %s", ctx->dev_path, strerror(errno)); + res = false; + } else { + res = true; + } + + free(ctx->button_state); + free(ctx); + + return res; +} + +bool io_util_joystick_update(struct io_util_joystick* ctx) +{ + log_assert(ctx); + + struct js_event jse[MAX_NUM_EVENTS]; + + int bytes = read(ctx->handle, jse, sizeof(struct js_event) * MAX_NUM_EVENTS); + + if (bytes < 0) { + switch (errno) { + // try again on next poll + case EAGAIN: + return true; + default: + log_error("Reading from joystick device failed: %s", ctx->dev_path, strerror(errno)); + return false; + } + } + + // determine how many whole events we read + bytes /= sizeof(struct js_event); + + // now iterate all events + for (int i = 0; i < bytes; i++) { + switch (jse[i].type & ~JS_EVENT_INIT) { + case JS_EVENT_BUTTON: + ctx->button_state[jse[i].number] = (bool) jse[i].value; + break; + + default: + break; + } + } + + + return true; +} + +bool io_util_joystick_is_button_pressed(struct io_util_joystick* ctx, uint16_t button) +{ + log_assert(ctx); + + if (button >= ctx->num_buttons) { + return false; + } + + return ctx->button_state[button]; +} \ No newline at end of file diff --git a/src/main/io/util/joystick.h b/src/main/io/util/joystick.h new file mode 100644 index 0000000..88e3c3d --- /dev/null +++ b/src/main/io/util/joystick.h @@ -0,0 +1,27 @@ +#ifndef IO_UTIL_JOYSTICK_H +#define IO_UTIL_JOYSTICK_H + +#include +#include +#include + +#define JOYSTICK_NAME_LEN 128 + +struct io_util_joystick { + char dev_path[PATH_MAX]; + int handle; + uint16_t num_buttons; + uint16_t num_axes; + char name[JOYSTICK_NAME_LEN]; + bool* button_state; +}; + +bool io_util_joystick_open(const char* dev_path, struct io_util_joystick** ctx); + +bool io_util_joystick_close(struct io_util_joystick* ctx); + +bool io_util_joystick_update(struct io_util_joystick* ctx); + +bool io_util_joystick_is_button_pressed(struct io_util_joystick* ctx, uint16_t button); + +#endif diff --git a/src/main/io/util/usb.c b/src/main/io/util/usb.c new file mode 100644 index 0000000..3acf0fc --- /dev/null +++ b/src/main/io/util/usb.c @@ -0,0 +1,171 @@ +#define LOG_MODULE "io-usb" + +#include +#include + +#include "util/log.h" +#include "util/mem.h" + +struct usb_io_ctx { + struct libusb_context* ctx; + struct libusb_device_handle* dev; +}; + +static struct libusb_device_handle* io_usb_get_device_handle( + struct libusb_context* ctx, uint16_t vid, uint16_t pid) +{ + int ret; + struct libusb_device** dev_list; + struct libusb_device* device; + struct libusb_device_handle* handle; + + dev_list = NULL; + device = NULL; + + ssize_t num_dev = libusb_get_device_list(ctx, &dev_list); + + /* find device */ + for (ssize_t i = 0; i < num_dev; i++) { + struct libusb_device_descriptor dev_desc_tmp; + struct libusb_device* dev_tmp; + + dev_tmp = dev_list[i]; + + libusb_get_device_descriptor(dev_tmp, &dev_desc_tmp); + + /* for handling multiple devices with the same pid and vid */ + /* + device_id_tmp = ((libusb_get_bus_number(dev_tmp) << 8) | + libusb_get_port_number(dev_tmp)); + */ + + if (dev_desc_tmp.idVendor == vid && dev_desc_tmp.idProduct == pid) { + device = dev_tmp; + break; + } + } + + /* not found */ + if (device == NULL) { + libusb_free_device_list(dev_list, 1); + log_error("Could not find device %04X:%04X", vid, pid); + return NULL; + } + + ret = libusb_open(device, &handle); + if (ret != LIBUSB_SUCCESS) { + libusb_free_device_list(dev_list, 1); + + log_error("Could not open device %04X:%04X: %s", vid, pid, + libusb_error_name(ret)); + return NULL; + } + + libusb_free_device_list(dev_list, 1); + + return handle; +} + +void* io_usb_open(uint16_t vid, uint16_t pid, uint16_t config, uint16_t iface) +{ + int ret; + struct libusb_context* ctx; + struct libusb_device_handle* dev; + struct usb_io_ctx* handle; + + log_debug("Opening %04X:%04X, config %d, iface %d", vid, pid, config, + iface); + + ret = libusb_init(&ctx); + + if (ret != LIBUSB_SUCCESS) { + log_error("(%04X:%04X) Initializing libusb context failed: %s", + vid, pid, libusb_error_name(ret)); + return NULL; + } + + /* dont print any error messages to stderr, change this for debugging */ + // libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_NONE); + + dev = io_usb_get_device_handle(ctx, vid, pid); + + if (!dev) { + libusb_exit(ctx); + return NULL; + } + + ret = libusb_set_configuration(dev, config); + + if (ret != LIBUSB_SUCCESS) { + libusb_close(dev); + libusb_exit(ctx); + + log_error("(%04X:%04X) Setting configuration failed", vid, pid, + libusb_error_name(ret)); + return NULL; + } + + /* check if the device is attached to the kernel, detach it first then */ + if (libusb_kernel_driver_active(dev, iface) == 1) { + ret = libusb_detach_kernel_driver(dev, iface); + if (ret != LIBUSB_SUCCESS) { + libusb_close(dev); + libusb_exit(ctx); + + log_error( + "(%04X:%04X) Detaching interface %d from kernel failed: %s", + vid, pid, iface, libusb_error_name(ret)); + return NULL; + } + } + + ret = libusb_claim_interface(dev, iface); + + if (ret != LIBUSB_SUCCESS) { + libusb_close(dev); + libusb_exit(ctx); + + log_error("(%04X:%04X) Claiming interface %d failed: %s", vid, pid, + iface, libusb_error_name(ret)); + return NULL; + } + + handle = (struct usb_io_ctx*) util_xmalloc(sizeof(struct usb_io_ctx)); + + handle->ctx = ctx; + handle->dev = dev; + + log_debug("Opened %04X:%04X, handle %p", vid, pid, handle); + + return handle; +} + +int32_t io_usb_control_transfer(void* handle, uint8_t request_type, + uint8_t request, uint16_t value, uint16_t index, uint8_t* data, + uint16_t len, uint32_t timeout) +{ + struct usb_io_ctx* dev = (struct usb_io_ctx*) handle; + int32_t ret; + + ret = libusb_control_transfer(dev->dev, request_type, request, value, + index, data, len, timeout); + + if (ret != len) { + log_error("Control transfer, handle %p failed: %s", handle, + libusb_error_name(ret)); + } + + return ret; +} + +void io_usb_close(void* handle) +{ + struct usb_io_ctx* dev = (struct usb_io_ctx*) handle; + + log_debug("Closing handle %p", handle); + + libusb_close(dev->dev); + libusb_exit(dev->ctx); + + free(dev); +} diff --git a/src/main/io/util/usb_.h b/src/main/io/util/usb_.h new file mode 100644 index 0000000..ab032cf --- /dev/null +++ b/src/main/io/util/usb_.h @@ -0,0 +1,46 @@ +/** + * Wrapper module for libusb-1.0 to make it a little more convenient to use + * usb devices + */ +#ifndef IO_USB_H +#define IO_USB_H + +#include + +/** + * Open a usb device + * + * @param vid Vendor ID of the device + * @param pid Product ID of the device + * @param config Configuration to set + * @param iface Interface to claim + * @return On success, a handle to the opened device, NULL on failure + */ +void* io_usb_open(uint16_t vid, uint16_t pid, uint16_t config, uint16_t iface); + +/** + * Execute a control transfer + * + * @param handle Handle of an opened usb device + * @param request_type Request type + * @param request Request + * @param value Value + * @param index Index + * @param data Pointer to an allocated buffer (depending on the vendor + * specific request type, this can be a read or write buffer) + * @param len Length of the data to write/read + * @param timeout Timeout for the operation in ms + * @return Number of bytes read/written or -1 on error + */ +int32_t io_usb_control_transfer(void* handle, uint8_t request_type, + uint8_t request, uint16_t value, uint16_t index, uint8_t* data, + uint16_t len, uint32_t timeout); + +/** + * Close a opened usb device + * + * @param handle Handle of the opened device + */ +void io_usb_close(void* handle); + +#endif \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/joystick-conf/main.c b/src/main/ptapi/io/piubtn/joystick-conf/main.c new file mode 100644 index 0000000..0e381c3 --- /dev/null +++ b/src/main/ptapi/io/piubtn/joystick-conf/main.c @@ -0,0 +1,255 @@ +#include +#include + +#include "io/util/joystick.h" +#include "io/util/joystick-util.h" + +#include "ptapi/io/piubtn/joystick-util/joystick-conf.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/time.h" + +#define MAX_JOYSTICKS 16 +#define CONFIG_FILENAME "piubtn-joystick-conf.bin" +#define SKIP_INPUT 0xFFFF + +int kbhit() +{ + struct timeval tv = { 0L, 0L }; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(0, &fds); + + return select(1, &fds, NULL, NULL, &tv); +} + +int getch() +{ + int r; + unsigned char c; + + if ((r = read(0, &c, sizeof(c))) < 0) { + return r; + } else { + return c; + } +} + +static uint16_t joystick_get_next_input(struct io_util_joystick* joystick) +{ + bool pressed; + uint16_t button; + + pressed = false; + button = 0; + + while (true) { + if (!io_util_joystick_update(joystick)) { + printf("Error updating joystick failed.\n"); + exit(-1); + } + + // Allow skip-able input assignment via enter key on keyboard + if (kbhit() > 0) { + if (getch() == '\n') { + return SKIP_INPUT; + } + } + + if (!pressed) { + for (uint16_t j = 0; j < joystick->num_buttons; j++) { + if (io_util_joystick_is_button_pressed(joystick, j)) { + button = j; + pressed = true; + break; + } + } + } else { + // Wait for release of pressed input -> debouncing + if (!io_util_joystick_is_button_pressed(joystick, button)) { + break; + } + } + + util_time_sleep_ms(10); + } + + return button; +} + +static void menu_joystick_test() +{ + struct io_util_joystick_util_device_info dev_info[MAX_JOYSTICKS]; + size_t connected_joysticks; + + connected_joysticks = io_util_joystick_util_scan(dev_info, MAX_JOYSTICKS); + + printf("Connected joysticks: %d\n", connected_joysticks); + + for (size_t i = 0; i < connected_joysticks; i++) { + printf("%d: %s -> %s, buttons %d, axes %d\n", + i, dev_info[i].name, dev_info[i].dev_path, dev_info[i].num_buttons, dev_info[i].num_axes); + } + + size_t selected_device_idx; + + printf("Select device: "); + scanf("%d", &selected_device_idx); + + if (selected_device_idx > connected_joysticks) { + printf("Invalid device id %d, out of range\n", selected_device_idx); + return; + } + + struct io_util_joystick* joystick; + + if (!io_util_joystick_open(dev_info[selected_device_idx].dev_path, &joystick)) { + printf("Error opening device %s -> %s", dev_info[selected_device_idx].name, + dev_info[selected_device_idx].dev_path); + return; + } + + printf("Pressed + released inputs:\n"); + + while (true) { + printf("%d\n", joystick_get_next_input(joystick)); + } + + io_util_joystick_close(joystick); +} + +static void menu_joystick_map() +{ + struct ptapi_io_piubtn_joystick_util_conf* config; + struct ptapi_io_piubtn_joystick_util_conf_entry config_entry; + + struct io_util_joystick_util_device_info dev_info[MAX_JOYSTICKS]; + size_t connected_joysticks; + struct io_util_joystick* joystick; + + connected_joysticks = io_util_joystick_util_scan(dev_info, MAX_JOYSTICKS); + + printf("Connected joysticks: %d\n", connected_joysticks); + + for (size_t i = 0; i < connected_joysticks; i++) { + printf("%d: %s -> %s, buttons %d, axes %d\n", + i, dev_info[i].name, dev_info[i].dev_path, dev_info[i].num_buttons, dev_info[i].num_axes); + } + + size_t selected_device_idx; + + printf("Select device: "); + scanf("%d", &selected_device_idx); + + if (selected_device_idx > connected_joysticks) { + printf("Invalid device id %d, out of range\n", selected_device_idx); + return; + } + + if (!ptapi_io_piubtn_joystick_util_conf_read_from_file(&config, CONFIG_FILENAME)) { + printf("Reading existing config file failed, creating new\n"); + + ptapi_io_piubtn_joystick_util_conf_new(&config); + + memset(&config_entry, 0, sizeof(struct ptapi_io_piubtn_joystick_util_conf_entry)); + + strcpy(config_entry.dev_path, dev_info[selected_device_idx].dev_path); + strcpy(config_entry.name, dev_info[selected_device_idx].name); + } else { + if (!ptapi_io_piubtn_joystick_util_conf_find_entry(config, dev_info[selected_device_idx].name, + dev_info[selected_device_idx].dev_path, &config_entry)) { + printf("Could not find existing mapping entry in config for joystick, creating new one\n"); + + memset(&config_entry, 0, sizeof(struct ptapi_io_piubtn_joystick_util_conf_entry)); + + strcpy(config_entry.dev_path, dev_info[selected_device_idx].dev_path); + strcpy(config_entry.name, dev_info[selected_device_idx].name); + } else { + printf("Found existing mapping entry in config for joystick, overwriting\n"); + } + } + + printf("Configuring device %s -> %s\n", dev_info[selected_device_idx].name, dev_info[selected_device_idx].dev_path); + + if (!io_util_joystick_open(dev_info[selected_device_idx].dev_path, &joystick)) { + printf("Error opening device %s -> %s", dev_info[selected_device_idx].name, + dev_info[selected_device_idx].dev_path); + return; + } + + for (uint32_t i = PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P1_LEFT; + i < PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_COUNT; + i++) { + printf("Press button to assign to \"%s\" or Enter/Return to skip: ", ptapi_io_piubtn_joystick_util_conf_input_str[i]); + fflush(stdout); + + config_entry.button_map[i] = joystick_get_next_input(joystick); + + if (config_entry.button_map[i] == SKIP_INPUT) { + printf("SKIP\n"); + } else { + printf("%d\n", config_entry.button_map[i]); + } + } + + io_util_joystick_close(joystick); + + printf("Configuration of device %s -> %s done\n", dev_info[selected_device_idx].name, + dev_info[selected_device_idx].dev_path); + + if (!ptapi_io_piubtn_joystick_util_conf_set_entry(config, &config_entry)) { + ptapi_io_piubtn_joystick_util_conf_append_entry(&config, &config_entry); + } + + ptapi_io_piubtn_joystick_util_conf_write_to_file(config, CONFIG_FILENAME); + + free(config); + + printf("Configuration file saved: %s\n", CONFIG_FILENAME); +} + +int main(int argc, char** argv) +{ + bool main_loop; + + util_log_set_file("joystick-conf.log", false); + util_log_set_level(LOG_LEVEL_ERROR); + + printf("ptapi PIUBTN joystick button mapping config tool\n"); + + main_loop = true; + + while (main_loop) { + uint32_t item; + + printf("Main menu, please select:\n"); + printf("1 > Joystick input test\n"); + printf("2 > Configure joystick mappings\n"); + printf("3 > Exit\n"); + printf("Select number: "); + + scanf("%d", &item); + + switch (item) { + case 1: + menu_joystick_test(); + break; + + case 2: + menu_joystick_map(); + break; + + case 3: + main_loop = false; + break; + + default: + printf("Invalid item chosen: %d\n", item); + break; + } + } + + return 0; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/joystick-util/joystick-conf.c b/src/main/ptapi/io/piubtn/joystick-util/joystick-conf.c new file mode 100644 index 0000000..4242c8a --- /dev/null +++ b/src/main/ptapi/io/piubtn/joystick-util/joystick-conf.c @@ -0,0 +1,99 @@ +#include "ptapi/io/piubtn/joystick-util/joystick-conf.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/mem.h" + +const char* ptapi_io_piubtn_joystick_util_conf_input_str[PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_COUNT] = { + "invalid", + "P1 Left", + "P1 Right", + "P1 Start", + "P1 Back", + "P2 Left", + "P2 Right", + "P2 Start", + "P2 Back", +}; + +void ptapi_io_piubtn_joystick_util_conf_new(struct ptapi_io_piubtn_joystick_util_conf** conf) +{ + log_assert(conf); + + *conf = util_xmalloc(sizeof(struct ptapi_io_piubtn_joystick_util_conf)); + (*conf)->num_entries = 0; +} + +bool ptapi_io_piubtn_joystick_util_conf_read_from_file(struct ptapi_io_piubtn_joystick_util_conf** conf, const char* path) +{ + log_assert(path); + log_assert(conf); + + void* buffer; + size_t size; + + if (!util_file_load(path, &buffer, &size, false)) { + return false; + } + + *conf = (struct ptapi_io_piubtn_joystick_util_conf*) buffer; + + return true; +} + +bool ptapi_io_piubtn_joystick_util_conf_write_to_file(struct ptapi_io_piubtn_joystick_util_conf* conf, const char* path) +{ + log_assert(path); + log_assert(conf); + + return util_file_save(path, conf, sizeof(struct ptapi_io_piubtn_joystick_util_conf) + + sizeof(struct ptapi_io_piubtn_joystick_util_conf_entry) * conf->num_entries); +} + +bool ptapi_io_piubtn_joystick_util_conf_set_entry(struct ptapi_io_piubtn_joystick_util_conf* conf, + struct ptapi_io_piubtn_joystick_util_conf_entry* entry) +{ + log_assert(conf); + log_assert(entry); + + for (size_t i = 0; i < conf->num_entries; i++) { + if (!strcmp(conf->entries[i].name, entry->name) && !strcmp(conf->entries[i].dev_path, entry->dev_path)) { + memcpy(&conf->entries[i], entry, sizeof(struct ptapi_io_piubtn_joystick_util_conf_entry)); + return true; + } + } + + return false; +} + +void ptapi_io_piubtn_joystick_util_conf_append_entry(struct ptapi_io_piubtn_joystick_util_conf** conf, + struct ptapi_io_piubtn_joystick_util_conf_entry* entry) +{ + log_assert(conf); + log_assert(entry); + + (*conf)->num_entries++; + + *conf = util_xrealloc(*conf, sizeof(struct ptapi_io_piubtn_joystick_util_conf*) + + sizeof(struct ptapi_io_piubtn_joystick_util_conf_entry) * (*conf)->num_entries); + + memcpy(&(*conf)->entries[(*conf)->num_entries - 1], entry, sizeof(struct ptapi_io_piubtn_joystick_util_conf_entry)); +}; + +bool ptapi_io_piubtn_joystick_util_conf_find_entry(struct ptapi_io_piubtn_joystick_util_conf* conf, const char* name, + const char* dev_path, struct ptapi_io_piubtn_joystick_util_conf_entry* entry) +{ + log_assert(name); + log_assert(dev_path); + log_assert(entry); + + for (size_t i = 0; i < conf->num_entries; i++) { + if (!strcmp(conf->entries[i].name, name) && !strcmp(conf->entries[i].dev_path, dev_path)) { + memcpy(entry, &conf->entries[i], sizeof(struct ptapi_io_piubtn_joystick_util_conf_entry)); + + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/joystick-util/joystick-conf.h b/src/main/ptapi/io/piubtn/joystick-util/joystick-conf.h new file mode 100644 index 0000000..d27dbc7 --- /dev/null +++ b/src/main/ptapi/io/piubtn/joystick-util/joystick-conf.h @@ -0,0 +1,50 @@ +#ifndef PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_H +#define PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_H + +#include + +#include "io/util/joystick.h" + +enum PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT { + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_INVALID = 0, + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P1_LEFT = 1, + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P1_RIGHT = 2, + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P1_START = 3, + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P1_BACK = 4, + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P2_LEFT = 5, + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P2_RIGHT = 6, + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P2_START = 7, + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P2_BACK = 8, + PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_COUNT = 9, +}; + +extern const char* ptapi_io_piubtn_joystick_util_conf_input_str[PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_COUNT]; + +struct ptapi_io_piubtn_joystick_util_conf_entry { + char name[JOYSTICK_NAME_LEN]; + char dev_path[PATH_MAX]; + + uint16_t button_map[PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_COUNT]; +}; + +struct ptapi_io_piubtn_joystick_util_conf { + size_t num_entries; + struct ptapi_io_piubtn_joystick_util_conf_entry entries[]; +}; + +void ptapi_io_piubtn_joystick_util_conf_new(struct ptapi_io_piubtn_joystick_util_conf** conf); + +bool ptapi_io_piubtn_joystick_util_conf_read_from_file(struct ptapi_io_piubtn_joystick_util_conf** conf, const char* path); + +bool ptapi_io_piubtn_joystick_util_conf_write_to_file(struct ptapi_io_piubtn_joystick_util_conf* conf, const char* path); + +bool ptapi_io_piubtn_joystick_util_conf_set_entry(struct ptapi_io_piubtn_joystick_util_conf* conf, + struct ptapi_io_piubtn_joystick_util_conf_entry* entry); + +void ptapi_io_piubtn_joystick_util_conf_append_entry(struct ptapi_io_piubtn_joystick_util_conf** conf, + struct ptapi_io_piubtn_joystick_util_conf_entry* entry); + +bool ptapi_io_piubtn_joystick_util_conf_find_entry(struct ptapi_io_piubtn_joystick_util_conf* conf, const char* name, + const char* dev_path, struct ptapi_io_piubtn_joystick_util_conf_entry* entry); + +#endif diff --git a/src/main/ptapi/io/piubtn/joystick/joystick.c b/src/main/ptapi/io/piubtn/joystick/joystick.c new file mode 100644 index 0000000..12d09d3 --- /dev/null +++ b/src/main/ptapi/io/piubtn/joystick/joystick.c @@ -0,0 +1,174 @@ +/** + * Implementation of the piubtn API to use the game with joysticks/gamepads. + */ +#define LOG_MODULE "ptapi-io-piubtn-joystick" + +#include "io/util/joystick.h" +#include "io/util/joystick-util.h" + +#include "ptapi/io/piubtn.h" + +#include "ptapi/io/piubtn/joystick-util/joystick-conf.h" + +#include "util/log.h" +#include "util/proc.h" +#include "util/str.h" + +#define MAX_NUM_JOYSTICKS 16 +#define CONFIG_FILENAME "/piubtn-joystick-conf.bin" + +struct ptapi_io_piubtn_joystick_with_conf { + struct ptapi_io_piubtn_joystick_util_conf_entry* conf; + struct io_util_joystick* joystick; +}; + +static struct ptapi_io_piubtn_joystick_util_conf* ptapi_io_piubtn_joystick_util_conf; +static struct ptapi_io_piubtn_joystick_with_conf ptapi_io_piubtn_joysticks_with_conf[MAX_NUM_JOYSTICKS]; +static size_t ptapi_io_piubtn_num_active_joysticks; + +static bool ptapi_io_piubtn_merge_inputs(enum PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT input) +{ + bool pressed; + + pressed = false; + + for (size_t i = 0; i < ptapi_io_piubtn_num_active_joysticks; i++) { + if (ptapi_io_piubtn_joysticks_with_conf[i].joystick) { + pressed = pressed || + io_util_joystick_is_button_pressed( + ptapi_io_piubtn_joysticks_with_conf[i].joystick, + ptapi_io_piubtn_joysticks_with_conf[i].conf->button_map[input]); + } + } + + return pressed; +} + +const char* ptapi_io_piubtn_ident(void) +{ + return "joystick"; +} + +bool ptapi_io_piubtn_open() +{ + char path[PATH_MAX]; + char* config_path; + struct io_util_joystick_util_device_info device_info[MAX_NUM_JOYSTICKS]; + size_t devices_connected; + + // The game changes the working directory to the 'game' sub-folder. Therefore, ./my-config does not work here. + if (!util_proc_get_folder_path_executable_no_ld_linux(path, sizeof(path))) { + log_error("Getting executable folder path failed."); + return false; + } + + config_path = util_str_merge(path, CONFIG_FILENAME); + + if (!ptapi_io_piubtn_joystick_util_conf_read_from_file(&ptapi_io_piubtn_joystick_util_conf, config_path)) { + log_error("Loading joystick config file %s failed.", CONFIG_FILENAME); + free(config_path); + return false; + } + + free(config_path); + + devices_connected = io_util_joystick_util_scan(device_info, MAX_NUM_JOYSTICKS); + + log_debug("Found connected joysticks: %d", devices_connected); + + ptapi_io_piubtn_num_active_joysticks = 0; + + memset(ptapi_io_piubtn_joysticks_with_conf, 0, sizeof(struct ptapi_io_piubtn_joystick_with_conf) * MAX_NUM_JOYSTICKS); + + for (size_t i = 0; i < devices_connected; i++) { + log_debug("%d: %s -> %s, buttons %d, axes %d\n", + i, device_info[i].name, device_info[i].dev_path, device_info[i].num_buttons, device_info[i].num_axes); + + // Find a matching configuration + for (size_t j = 0; j < ptapi_io_piubtn_joystick_util_conf->num_entries; j++) { + + if (!strcmp(ptapi_io_piubtn_joystick_util_conf->entries[j].name, device_info[i].name) && + !strcmp(ptapi_io_piubtn_joystick_util_conf->entries[j].dev_path, device_info[i].dev_path)) { + log_debug("Found matching configuration for %s -> %s", device_info[i].name, device_info[i].dev_path); + + ptapi_io_piubtn_joysticks_with_conf[ptapi_io_piubtn_num_active_joysticks].conf = + &ptapi_io_piubtn_joystick_util_conf->entries[j]; + ptapi_io_piubtn_num_active_joysticks++; + } + } + } + + // Open all joysticks with a matching configuration + + log_debug("Opening %d joysticks...", ptapi_io_piubtn_num_active_joysticks); + + for (size_t i = 0; i < ptapi_io_piubtn_num_active_joysticks; i++) { + + if (!io_util_joystick_open(ptapi_io_piubtn_joysticks_with_conf[i].conf->dev_path, + &ptapi_io_piubtn_joysticks_with_conf[i].joystick)) { + log_warn("Could not open joystick %s -> %s, ignoring", ptapi_io_piubtn_joysticks_with_conf[i].conf->name, + ptapi_io_piubtn_joysticks_with_conf[i].conf->dev_path); + } else { + log_info("Opened joystick %s -> %s", ptapi_io_piubtn_joysticks_with_conf[i].conf->name, + ptapi_io_piubtn_joysticks_with_conf[i].conf->dev_path); + } + } + + return true; +} + +void ptapi_io_piubtn_close(void) +{ + free(ptapi_io_piubtn_joystick_util_conf); + + for (size_t i = 0; i < ptapi_io_piubtn_num_active_joysticks; i++) { + if (ptapi_io_piubtn_joysticks_with_conf[i].joystick) { + io_util_joystick_close(ptapi_io_piubtn_joysticks_with_conf[i].joystick); + } + } +} + +bool ptapi_io_piubtn_recv(void) +{ + bool success; + + success = true; + + for (size_t i = 0; i < ptapi_io_piubtn_num_active_joysticks; i++) { + if (ptapi_io_piubtn_joysticks_with_conf[i].joystick) { + success = io_util_joystick_update(ptapi_io_piubtn_joysticks_with_conf[i].joystick); + + if (!success) { + log_error("Updating joystick %s -> %s failed", ptapi_io_piubtn_joysticks_with_conf[i].conf->name, + ptapi_io_piubtn_joysticks_with_conf[i].conf->dev_path); + } + } + } + + return success; +} + +bool ptapi_io_piubtn_send(void) +{ + return true; +} + +void ptapi_io_piubtn_get_input(uint8_t player, struct ptapi_io_piubtn_inputs* inputs) +{ + if (player == 0) { + inputs->start = ptapi_io_piubtn_merge_inputs(PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P1_START); + inputs->back = ptapi_io_piubtn_merge_inputs(PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P1_BACK); + inputs->left = ptapi_io_piubtn_merge_inputs(PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P1_LEFT); + inputs->right = ptapi_io_piubtn_merge_inputs(PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P1_RIGHT); + } else { + inputs->start = ptapi_io_piubtn_merge_inputs(PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P2_START); + inputs->back = ptapi_io_piubtn_merge_inputs(PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P2_BACK); + inputs->left = ptapi_io_piubtn_merge_inputs(PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P2_LEFT); + inputs->right = ptapi_io_piubtn_merge_inputs(PTAPI_IO_PIUBTN_JOYSTICK_UTIL_CONF_INPUT_P2_RIGHT); + } +} + +void ptapi_io_piubtn_set_output(uint8_t player, const struct ptapi_io_piubtn_outputs* outputs) +{ + /* Not supported */ +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/keyboard-conf/main.c b/src/main/ptapi/io/piubtn/keyboard-conf/main.c new file mode 100644 index 0000000..5daeb98 --- /dev/null +++ b/src/main/ptapi/io/piubtn/keyboard-conf/main.c @@ -0,0 +1,142 @@ +#include + +#include +#include + +#include "ptapi/io/piubtn/keyboard-util/keyboard-conf.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/time.h" + +#define CONFIG_FILENAME "piubtn-keyboard-conf.bin" + +static Display* display; +static Window window; + +static uint32_t keyboard_get_next_input() +{ + XEvent event; + KeySym key; + + key = 0; + + while (true) { + XNextEvent(display, &event); + + if (event.type == KeyPress) { + XLookupString(&event.xkey, 0, 0, &key, 0); + } else if (event.type == KeyRelease) { + break; + } + } + + return key; +} + +static void menu_keyboard_test() +{ + printf("Pressed + released inputs:\n"); + + while (true) { + printf("%d\n", keyboard_get_next_input()); + } +} + +static void menu_keyboard_map() +{ + struct ptapi_io_piubtn_keyboard_util_conf* config; + + if (!ptapi_io_piubtn_keyboard_util_conf_read_from_file(&config, CONFIG_FILENAME)) { + printf("Reading existing config file failed, creating new\n"); + + ptapi_io_piubtn_keyboard_util_conf_new(&config); + } else { + printf("Found existing mapping entry in config for keyboard, overwriting\n"); + } + + printf("A new window with no contents/white background is opened. Ensure to have the application focus on that " + "window to allow input capturing. Otherwise, the tool cannot capture any keyboard inputs!\n"); + + // Delay a moment to avoid picking up any previous keyboard inputs + util_time_sleep_ms(500); + + for (uint32_t i = PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P1_LEFT; + i < PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_COUNT; + i++) { + printf("Press button to assign to \"%s\": ", ptapi_io_piubtn_keyboard_util_conf_input_str[i]); + fflush(stdout); + + config->button_map[i] = keyboard_get_next_input(); + + printf("%d\n", config->button_map[i]); + } + + ptapi_io_piubtn_keyboard_util_conf_write_to_file(config, CONFIG_FILENAME); + + free(config); + + printf("Configuration file saved: %s\n", CONFIG_FILENAME); +} + +int main(int argc, char** argv) +{ + bool main_loop; + int screen; + + util_log_set_file("keyboard-conf.log", false); + util_log_set_level(LOG_LEVEL_ERROR); + + display = XOpenDisplay(NULL); + + if (!display) { + printf("Opening display failed\n"); + return -1; + } + + screen = DefaultScreen(display); + + window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 200, 200, 1, + BlackPixel(display, screen), WhitePixel(display, screen)); + + XSelectInput(display, window, KeyPressMask | KeyReleaseMask); + XMapWindow(display, window); + + printf("ptapi PIUBTN keyboard button mapping config tool\n"); + + main_loop = true; + + while (main_loop) { + uint32_t item; + + printf("Main menu, please select:\n"); + printf("1 > Keyboard input test\n"); + printf("2 > Configure keyboard mappings\n"); + printf("3 > Exit\n"); + printf("Select number: "); + + scanf("%d", &item); + + switch (item) { + case 1: + menu_keyboard_test(); + break; + + case 2: + menu_keyboard_map(); + break; + + case 3: + main_loop = false; + break; + + default: + printf("Invalid item chosen: %d\n", item); + break; + } + } + + XCloseDisplay(display); + + return 0; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/keyboard-util/keyboard-conf.c b/src/main/ptapi/io/piubtn/keyboard-util/keyboard-conf.c new file mode 100644 index 0000000..a67bcf5 --- /dev/null +++ b/src/main/ptapi/io/piubtn/keyboard-util/keyboard-conf.c @@ -0,0 +1,50 @@ +#include "ptapi/io/piubtn/keyboard-util/keyboard-conf.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/mem.h" + +const char* ptapi_io_piubtn_keyboard_util_conf_input_str[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_COUNT] = { + "invalid", + "P1 Left", + "P1 Right", + "P1 Start", + "P1 Back", + "P2 Left", + "P2 Right", + "P2 Start", + "P2 Back", +}; + +void ptapi_io_piubtn_keyboard_util_conf_new(struct ptapi_io_piubtn_keyboard_util_conf** conf) +{ + log_assert(conf); + + *conf = util_xmalloc(sizeof(struct ptapi_io_piubtn_keyboard_util_conf)); + memset(*conf, 0, sizeof(struct ptapi_io_piubtn_keyboard_util_conf)); +} + +bool ptapi_io_piubtn_keyboard_util_conf_read_from_file(struct ptapi_io_piubtn_keyboard_util_conf** conf, const char* path) +{ + log_assert(path); + log_assert(conf); + + void* buffer; + size_t size; + + if (!util_file_load(path, &buffer, &size, false)) { + return false; + } + + *conf = (struct ptapi_io_piubtn_keyboard_util_conf*) buffer; + + return true; +} + +bool ptapi_io_piubtn_keyboard_util_conf_write_to_file(struct ptapi_io_piubtn_keyboard_util_conf* conf, const char* path) +{ + log_assert(path); + log_assert(conf); + + return util_file_save(path, conf, sizeof(struct ptapi_io_piubtn_keyboard_util_conf)); +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/keyboard-util/keyboard-conf.h b/src/main/ptapi/io/piubtn/keyboard-util/keyboard-conf.h new file mode 100644 index 0000000..65ab31c --- /dev/null +++ b/src/main/ptapi/io/piubtn/keyboard-util/keyboard-conf.h @@ -0,0 +1,33 @@ +#ifndef PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_H +#define PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_H + +#include + +#include "io/util/joystick.h" + +enum PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT { + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_INVALID = 0, + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P1_LEFT = 1, + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P1_RIGHT = 2, + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P1_START = 3, + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P1_BACK = 4, + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P2_LEFT = 5, + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P2_RIGHT = 6, + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P2_START = 7, + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P2_BACK = 8, + PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_COUNT = 9, +}; + +extern const char* ptapi_io_piubtn_keyboard_util_conf_input_str[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_COUNT]; + +struct ptapi_io_piubtn_keyboard_util_conf { + uint32_t button_map[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_COUNT]; +}; + +void ptapi_io_piubtn_keyboard_util_conf_new(struct ptapi_io_piubtn_keyboard_util_conf** conf); + +bool ptapi_io_piubtn_keyboard_util_conf_read_from_file(struct ptapi_io_piubtn_keyboard_util_conf** conf, const char* path); + +bool ptapi_io_piubtn_keyboard_util_conf_write_to_file(struct ptapi_io_piubtn_keyboard_util_conf* conf, const char* path); + +#endif diff --git a/src/main/ptapi/io/piubtn/keyboard/keyboard.c b/src/main/ptapi/io/piubtn/keyboard/keyboard.c new file mode 100644 index 0000000..5a6a4c4 --- /dev/null +++ b/src/main/ptapi/io/piubtn/keyboard/keyboard.c @@ -0,0 +1,126 @@ +/** + * Implementation of the piubtn API. This implements keyboard wrapper to play + * (actually mainly test) any piu games supporting the piubtn IO + */ +#define LOG_MODULE "ptapi-io-piubtn-keyboard" + +#include +#include + +#include "ptapi/io/piubtn.h" +#include "ptapi/io/x11-input-hook.h" + +#include "ptapi/io/piubtn/keyboard-util/keyboard-conf.h" + +#include "util/log.h" +#include "util/proc.h" +#include "util/str.h" + +#define CONFIG_FILENAME "/piubtn-keyboard-conf.bin" +#define KEY_MAP_SIZE 0xFFFF + +struct ptapi_io_piubtn_keyboard_util_conf* ptapi_io_piubtn_keyboard_conf; + +static bool ptapi_io_piubtn_keyboard_buffer[KEY_MAP_SIZE]; + +static void _piubtn_emu_keyboard_dispatch_key_press(KeySym key); +static void _piubtn_emu_keyboard_dispatch_key_release(KeySym key); + +static const struct ptapi_io_x11_input_hook_handler _piubtn_emu_keyboard_input_hook_handler = { + .dispatch_key_press = _piubtn_emu_keyboard_dispatch_key_press, + .dispatch_key_release = _piubtn_emu_keyboard_dispatch_key_release, +}; + +const char* ptapi_io_piubtn_ident(void) +{ + return "keyboard"; +} + +const struct ptapi_io_x11_input_hook_handler* ptapi_io_x11_input_handler_hook(void) +{ + return &_piubtn_emu_keyboard_input_hook_handler; +} + +bool ptapi_io_piubtn_open() +{ + char path[PATH_MAX]; + char* config_path; + + // The game changes the working directory to the 'game' sub-folder. Therefore, ./my-config does not work here. + if (!util_proc_get_folder_path_executable_no_ld_linux(path, sizeof(path))) { + log_error("Getting executable folder path failed."); + return false; + } + + config_path = util_str_merge(path, CONFIG_FILENAME); + + if (!ptapi_io_piubtn_keyboard_util_conf_read_from_file(&ptapi_io_piubtn_keyboard_conf, config_path)) { + log_error("Loading keyboard config file %s failed.", config_path); + free(config_path); + return false; + } + + free(config_path); + + memset(ptapi_io_piubtn_keyboard_buffer, 0, sizeof(bool) * KEY_MAP_SIZE); + + return true; +} + +void ptapi_io_piubtn_close(void) +{ + free(ptapi_io_piubtn_keyboard_conf); +} + +bool ptapi_io_piubtn_recv(void) +{ + /* Nothing to poll */ + return true; +} + +bool ptapi_io_piubtn_send(void) +{ + return true; +} + +void ptapi_io_piubtn_get_input(uint8_t player, struct ptapi_io_piubtn_inputs* inputs) +{ + if (player == 0) { + inputs->left = ptapi_io_piubtn_keyboard_buffer[ + ptapi_io_piubtn_keyboard_conf->button_map[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P1_LEFT]]; + inputs->right = ptapi_io_piubtn_keyboard_buffer[ + ptapi_io_piubtn_keyboard_conf->button_map[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P1_RIGHT]]; + inputs->start = ptapi_io_piubtn_keyboard_buffer[ + ptapi_io_piubtn_keyboard_conf->button_map[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P1_START]]; + inputs->back = ptapi_io_piubtn_keyboard_buffer[ + ptapi_io_piubtn_keyboard_conf->button_map[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P1_BACK]]; + } else { + inputs->left = ptapi_io_piubtn_keyboard_buffer[ + ptapi_io_piubtn_keyboard_conf->button_map[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P2_LEFT]]; + inputs->right = ptapi_io_piubtn_keyboard_buffer[ + ptapi_io_piubtn_keyboard_conf->button_map[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P2_RIGHT]]; + inputs->start = ptapi_io_piubtn_keyboard_buffer[ + ptapi_io_piubtn_keyboard_conf->button_map[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P2_START]]; + inputs->back = ptapi_io_piubtn_keyboard_buffer[ + ptapi_io_piubtn_keyboard_conf->button_map[PTAPI_IO_PIUBTN_KEYBOARD_UTIL_CONF_INPUT_P2_BACK]]; + } +} + +void ptapi_io_piubtn_set_output(uint8_t player, const struct ptapi_io_piubtn_outputs* outputs) +{ + /* Not supported */ +} + +static void _piubtn_emu_keyboard_dispatch_key_press(KeySym key) +{ + if (key > 0 && key < KEY_MAP_SIZE) { + ptapi_io_piubtn_keyboard_buffer[key] = true; + } +} + +static void _piubtn_emu_keyboard_dispatch_key_release(KeySym key) +{ + if (key > 0 && key < KEY_MAP_SIZE) { + ptapi_io_piubtn_keyboard_buffer[key] = false; + } +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/null/null.c b/src/main/ptapi/io/piubtn/null/null.c new file mode 100644 index 0000000..b0f6e37 --- /dev/null +++ b/src/main/ptapi/io/piubtn/null/null.c @@ -0,0 +1,42 @@ +/** + * Implementation of the piubtn API. Null driver for testing and starting piu without a real io + */ +#define LOG_MODULE "ptapi-io-piubtn-null" + +#include "ptapi/io/piubtn.h" + +const char* ptapi_io_piubtn_ident(void) +{ + return "null"; +} + +bool ptapi_io_piubtn_open(void) +{ + return true; +} + + +void ptapi_io_piubtn_close(void) +{ + +} + +bool ptapi_io_piubtn_recv(void) +{ + return true; +} + +bool ptapi_io_piubtn_send(void) +{ + return true; +} + +void ptapi_io_piubtn_get_input(uint8_t player, struct ptapi_io_piubtn_inputs* inputs) +{ + +} + +void ptapi_io_piubtn_set_output(uint8_t player, const struct ptapi_io_piubtn_outputs* outputs) +{ + +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/real/piubtn.c b/src/main/ptapi/io/piubtn/real/piubtn.c new file mode 100644 index 0000000..56afe59 --- /dev/null +++ b/src/main/ptapi/io/piubtn/real/piubtn.c @@ -0,0 +1,135 @@ +/** + * Implementation of the piubtn API. This implements a piubtn usb driver to + * communicate with real piubtn hardware and also shares a common interface with + * other "piubtn devices" (e.g. keyboard, custom IOs) + */ +#define LOG_MODULE "ptapi-io-piubtn-real" + +#include +#include + +#include "io/piubtn/defs.h" +#include "io/piubtn/device.h" + +#include "ptapi/io/piubtn.h" + +static uint8_t piubtn_drv_piubtn_in_buffer[PIUBTN_DRV_BUFFER_SIZE]; +static uint8_t piubtn_drv_piubtn_out_buffer[PIUBTN_DRV_BUFFER_SIZE]; + +static struct ptapi_io_piubtn_inputs piubtn_drv_piubtn_in[2]; +static struct ptapi_io_piubtn_outputs piubtn_drv_piubtn_out[2]; + +static void piubtn_drv_piubtn_copy_outputs() +{ + memset(piubtn_drv_piubtn_out_buffer, 0, + sizeof(piubtn_drv_piubtn_out_buffer)); + + /* Player 1 */ + + if (piubtn_drv_piubtn_out[0].start) { + piubtn_drv_piubtn_out_buffer[0] |= (1 << 0); + } + + if (piubtn_drv_piubtn_out[0].right) { + piubtn_drv_piubtn_out_buffer[0] |= (1 << 1); + } + + if (piubtn_drv_piubtn_out[0].left) { + piubtn_drv_piubtn_out_buffer[0] |= (1 << 2); + } + + if (piubtn_drv_piubtn_out[0].back) { + piubtn_drv_piubtn_out_buffer[0] |= (1 << 3); + } + + /* Player 2 */ + + if (piubtn_drv_piubtn_out[1].start) { + piubtn_drv_piubtn_out_buffer[0] |= (1 << 4); + } + + if (piubtn_drv_piubtn_out[1].right) { + piubtn_drv_piubtn_out_buffer[0] |= (1 << 5); + } + + if (piubtn_drv_piubtn_out[1].left) { + piubtn_drv_piubtn_out_buffer[0] |= (1 << 6); + } + + if (piubtn_drv_piubtn_out[1].back) { + piubtn_drv_piubtn_out_buffer[0] |= (1 << 7); + } +} + +static void piubtn_drv_piubtn_copy_inputs() +{ + /* Player 1 */ + piubtn_drv_piubtn_in[0].back = piubtn_drv_piubtn_in_buffer[0] & (1 << 0); + piubtn_drv_piubtn_in[0].left = piubtn_drv_piubtn_in_buffer[0] & (1 << 1); + piubtn_drv_piubtn_in[0].right = piubtn_drv_piubtn_in_buffer[0] & (1 << 2); + piubtn_drv_piubtn_in[0].start = piubtn_drv_piubtn_in_buffer[0] & (1 << 3); + + /* Player 2 */ + piubtn_drv_piubtn_in[1].back = piubtn_drv_piubtn_in_buffer[0] & (1 << 4); + piubtn_drv_piubtn_in[1].left = piubtn_drv_piubtn_in_buffer[0] & (1 << 5); + piubtn_drv_piubtn_in[1].right = piubtn_drv_piubtn_in_buffer[0] & (1 << 6); + piubtn_drv_piubtn_in[1].start = piubtn_drv_piubtn_in_buffer[0] & (1 << 7); +} + +const char* ptapi_io_piubtn_ident(void) +{ + return "piubtn"; +} + +bool ptapi_io_piubtn_open(void) +{ + return piubtn_drv_device_open(); +} + +void ptapi_io_piubtn_close(void) +{ + piubtn_drv_device_close(); +} + +bool ptapi_io_piubtn_recv(void) +{ + if (!piubtn_drv_device_read(piubtn_drv_piubtn_in_buffer, + sizeof(piubtn_drv_piubtn_in_buffer))) { + return false; + } + + /* Invert pull ups */ + for (uint8_t j = 0; j < 4; j++) { + piubtn_drv_piubtn_in_buffer[j] ^= 0xFF; + } + + piubtn_drv_piubtn_copy_inputs(); + + return true; +} + +bool ptapi_io_piubtn_send(void) +{ + piubtn_drv_piubtn_copy_outputs(); + + if (!piubtn_drv_device_write(piubtn_drv_piubtn_out_buffer, + sizeof(piubtn_drv_piubtn_out_buffer))) { + return false; + } + + return true; +} + +void ptapi_io_piubtn_get_input(uint8_t player, + struct ptapi_io_piubtn_inputs* inputs) +{ + memcpy(inputs, &piubtn_drv_piubtn_in[player], + sizeof(struct ptapi_io_piubtn_inputs)); +} + +void ptapi_io_piubtn_set_output(uint8_t player, + const struct ptapi_io_piubtn_outputs* outputs) +{ + memcpy(&piubtn_drv_piubtn_out[player], outputs, + sizeof(struct ptapi_io_piubtn_outputs)); +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/test/main.c b/src/main/ptapi/io/piubtn/test/main.c new file mode 100644 index 0000000..d3fe9f0 --- /dev/null +++ b/src/main/ptapi/io/piubtn/test/main.c @@ -0,0 +1,103 @@ +/** + * Tool to test implementations of the piubtn API + */ +#include +#include +#include +#include + +#include "ptapi/io/piubtn.h" +#include "ptapi/io/piubtn/util/lib.h" + +#include "util/log.h" + +static struct ptapi_io_piubtn_api api; + +int main(int argc, char** argv) +{ + struct ptapi_io_piubtn_inputs inputs[2]; + struct ptapi_io_piubtn_outputs outputs[2]; + + util_log_set_file("piubtn-test.log", false); + util_log_set_level(LOG_LEVEL_ERROR); + + if (argc < 2) { + printf( + "Usage: %s [debug]\n", + argv[0]); + return -1; + } + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "debug")) { + util_log_set_level(LOG_LEVEL_DEBUG); + break; + } + } + + if (!ptapi_io_piubtn_util_lib_load(argv[1], &api)) { + printf("Loading piubtn lib %s failed\n", argv[1]); + return -2; + } + + if (!api.open()) { + return -3; + } + + for (uint8_t i = 0; i < 2; i++) { + memset(&inputs, 0, sizeof(inputs)); + memset(&outputs, 0, sizeof(outputs)); + } + + printf("Enter main loop by pressing any key (on the keyboard)\n"); + getchar(); + + while (true) { + if (!api.recv()) { + return -4; + } + + for (uint8_t i = 0; i < 2; i++) { + api.get_input(i, &inputs[i]); + } + + if (inputs[0].start && inputs[1].start) { + break; + } + + system("clear"); + + printf("Press p1 start + p2 start to exit\n"); + printf( + "|-------------------|\n" + "| %d %d |\n" + "|%d %d %d %d|\n" + "| %d %d |\n" + "|-------------------|\n", + inputs[0].back, inputs[1].back, + inputs[0].left, inputs[0].right, inputs[1].left, inputs[1].right, + inputs[0].start, inputs[1].start); + + for (int i = 0; i < 2; i++) { + memset(&outputs[i], 0, sizeof(outputs[i])); + + inputs[i].start = outputs[i].start; + inputs[i].back = outputs[i].back; + inputs[i].left = outputs[i].left; + inputs[i].right = outputs[i].right; + + api.set_output(i, &outputs[i]); + } + + if (!api.send()) { + return -5; + } + + /* Avoid hardware banging */ + usleep(1000); + } + + api.close(); + + return 0; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/util/lib.c b/src/main/ptapi/io/piubtn/util/lib.c new file mode 100644 index 0000000..94934a3 --- /dev/null +++ b/src/main/ptapi/io/piubtn/util/lib.c @@ -0,0 +1,34 @@ +#include + +#include "capnhook/hook/lib.h" + +#include "ptapi/io/piubtn/util/lib.h" + +bool ptapi_io_piubtn_util_lib_load(const char* path, struct ptapi_io_piubtn_api* api) +{ + void* handle; + + handle = cnh_lib_load(path); + + if (handle == NULL) { + return false; + } + + api->ident = (ptapi_io_piubtn_ident_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piubtn_ident"); + + api->open = (ptapi_io_piubtn_open_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piubtn_open"); + api->close = (ptapi_io_piubtn_close_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piubtn_close"); + api->recv = (ptapi_io_piubtn_recv_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piubtn_recv"); + api->send = (ptapi_io_piubtn_send_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piubtn_send"); + api->get_input = (ptapi_io_piubtn_get_input_t) + cnh_lib_get_func_addr_handle(handle, "ptapi_io_piubtn_get_input"); + api->set_output = (ptapi_io_piubtn_set_output_t) + cnh_lib_get_func_addr_handle(handle, "ptapi_io_piubtn_set_output"); + + return true; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piubtn/util/lib.h b/src/main/ptapi/io/piubtn/util/lib.h new file mode 100644 index 0000000..c8adcbc --- /dev/null +++ b/src/main/ptapi/io/piubtn/util/lib.h @@ -0,0 +1,8 @@ +#ifndef PTAPI_IO_PIUBTN_UTIL_LIB_H +#define PTAPI_IO_PIUBTN_UTIL_LIB_H + +#include "ptapi/io/piubtn.h" + +bool ptapi_io_piubtn_util_lib_load(const char* path, struct ptapi_io_piubtn_api* api); + +#endif diff --git a/src/main/ptapi/io/piuio/joystick-conf/main.c b/src/main/ptapi/io/piuio/joystick-conf/main.c new file mode 100644 index 0000000..6e3dc66 --- /dev/null +++ b/src/main/ptapi/io/piuio/joystick-conf/main.c @@ -0,0 +1,254 @@ +#include +#include + +#include "io/util/joystick.h" +#include "io/util/joystick-util.h" + +#include "ptapi/io/piuio/joystick-util/joystick-conf.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/time.h" + +#define MAX_JOYSTICKS 16 +#define CONFIG_FILENAME "piuio-joystick-conf.bin" +#define SKIP_INPUT 0xFFFF + +int kbhit() +{ + struct timeval tv = { 0L, 0L }; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(0, &fds); + + return select(1, &fds, NULL, NULL, &tv); +} + +int getch() +{ + int r; + unsigned char c; + + if ((r = read(0, &c, sizeof(c))) < 0) { + return r; + } else { + return c; + } +} + +static uint16_t joystick_get_next_input(struct io_util_joystick* joystick) +{ + bool pressed; + uint16_t button; + + pressed = false; + button = 0; + + while (true) { + if (!io_util_joystick_update(joystick)) { + printf("Error updating joystick failed.\n"); + exit(-1); + } + + // Allow skip-able input assignment via enter key on keyboard + if (kbhit() > 0) { + if (getch() == '\n') { + return SKIP_INPUT; + } + } + + if (!pressed) { + for (uint16_t j = 0; j < joystick->num_buttons; j++) { + if (io_util_joystick_is_button_pressed(joystick, j)) { + button = j; + pressed = true; + break; + } + } + } else { + // Wait for release of pressed input -> debouncing + if (!io_util_joystick_is_button_pressed(joystick, button)) { + break; + } + } + + util_time_sleep_ms(10); + } + + return button; +} + +static void menu_joystick_test() +{ + struct io_util_joystick_util_device_info dev_info[MAX_JOYSTICKS]; + size_t connected_joysticks; + + connected_joysticks = io_util_joystick_util_scan(dev_info, MAX_JOYSTICKS); + + printf("Connected joysticks: %d\n", connected_joysticks); + + for (size_t i = 0; i < connected_joysticks; i++) { + printf("%d: %s -> %s, buttons %d, axes %d\n", + i, dev_info[i].name, dev_info[i].dev_path, dev_info[i].num_buttons, dev_info[i].num_axes); + } + + size_t selected_device_idx; + + printf("Select device: "); + scanf("%d", &selected_device_idx); + + if (selected_device_idx > connected_joysticks) { + printf("Invalid device id %d, out of range\n", selected_device_idx); + return; + } + + struct io_util_joystick* joystick; + + if (!io_util_joystick_open(dev_info[selected_device_idx].dev_path, &joystick)) { + printf("Error opening device %s -> %s", dev_info[selected_device_idx].name, + dev_info[selected_device_idx].dev_path); + return; + } + + printf("Pressed + released inputs:\n"); + + while (true) { + printf("%d\n", joystick_get_next_input(joystick)); + } + + io_util_joystick_close(joystick); +} + +static void menu_joystick_map() +{ + struct ptapi_io_piuio_joystick_util_conf* config; + struct ptapi_io_piuio_joystick_util_conf_entry config_entry; + + struct io_util_joystick_util_device_info dev_info[MAX_JOYSTICKS]; + size_t connected_joysticks; + struct io_util_joystick* joystick; + + connected_joysticks = io_util_joystick_util_scan(dev_info, MAX_JOYSTICKS); + + printf("Connected joysticks: %d\n", connected_joysticks); + + for (size_t i = 0; i < connected_joysticks; i++) { + printf("%d: %s -> %s, buttons %d, axes %d\n", + i, dev_info[i].name, dev_info[i].dev_path, dev_info[i].num_buttons, dev_info[i].num_axes); + } + + size_t selected_device_idx; + + printf("Select device: "); + scanf("%d", &selected_device_idx); + + if (selected_device_idx > connected_joysticks) { + printf("Invalid device id %d, out of range\n", selected_device_idx); + return; + } + + if (!ptapi_io_piuio_joystick_util_conf_read_from_file(&config, CONFIG_FILENAME)) { + printf("Reading existing config file failed, creating new\n"); + + ptapi_io_piuio_joystick_util_conf_new(&config); + + memset(&config_entry, 0, sizeof(struct ptapi_io_piuio_joystick_util_conf_entry)); + + strcpy(config_entry.dev_path, dev_info[selected_device_idx].dev_path); + strcpy(config_entry.name, dev_info[selected_device_idx].name); + } else { + if (!ptapi_io_piuio_joystick_util_conf_find_entry(config, dev_info[selected_device_idx].name, + dev_info[selected_device_idx].dev_path, &config_entry)) { + printf("Could not find existing mapping entry in config for joystick, creating new one\n"); + + memset(&config_entry, 0, sizeof(struct ptapi_io_piuio_joystick_util_conf_entry)); + + strcpy(config_entry.dev_path, dev_info[selected_device_idx].dev_path); + strcpy(config_entry.name, dev_info[selected_device_idx].name); + } else { + printf("Found existing mapping entry in config for joystick, overwriting\n"); + } + } + + printf("Configuring device %s -> %s\n", dev_info[selected_device_idx].name, dev_info[selected_device_idx].dev_path); + + if (!io_util_joystick_open(dev_info[selected_device_idx].dev_path, &joystick)) { + printf("Error opening device %s -> %s", dev_info[selected_device_idx].name, + dev_info[selected_device_idx].dev_path); + return; + } + + for (uint32_t i = PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_LU; i < PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_COUNT; + i++) { + printf("Press button to assign to \"%s\" or Enter/Return to skip: ", ptapi_io_piuio_joystick_util_conf_input_str[i]); + fflush(stdout); + + config_entry.button_map[i] = joystick_get_next_input(joystick); + + if (config_entry.button_map[i] == SKIP_INPUT) { + printf("SKIP\n"); + } else { + printf("%d\n", config_entry.button_map[i]); + } + } + + io_util_joystick_close(joystick); + + printf("Configuration of device %s -> %s done\n", dev_info[selected_device_idx].name, + dev_info[selected_device_idx].dev_path); + + if (!ptapi_io_piuio_joystick_util_conf_set_entry(config, &config_entry)) { + ptapi_io_piuio_joystick_util_conf_append_entry(&config, &config_entry); + } + + ptapi_io_piuio_joystick_util_conf_write_to_file(config, CONFIG_FILENAME); + + free(config); + + printf("Configuration file saved: %s\n", CONFIG_FILENAME); +} + +int main(int argc, char** argv) +{ + bool main_loop; + + util_log_set_file("joystick-conf.log", false); + util_log_set_level(LOG_LEVEL_ERROR); + + printf("ptapi PIUIO joystick button mapping config tool\n"); + + main_loop = true; + + while (main_loop) { + uint32_t item; + + printf("Main menu, please select:\n"); + printf("1 > Joystick input test\n"); + printf("2 > Configure joystick mappings\n"); + printf("3 > Exit\n"); + printf("Select number: "); + + scanf("%d", &item); + + switch (item) { + case 1: + menu_joystick_test(); + break; + + case 2: + menu_joystick_map(); + break; + + case 3: + main_loop = false; + break; + + default: + printf("Invalid item chosen: %d\n", item); + break; + } + } + + return 0; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/joystick-util/joystick-conf.c b/src/main/ptapi/io/piuio/joystick-util/joystick-conf.c new file mode 100644 index 0000000..9216923 --- /dev/null +++ b/src/main/ptapi/io/piuio/joystick-util/joystick-conf.c @@ -0,0 +1,106 @@ +#include "ptapi/io/piuio/joystick-util/joystick-conf.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/mem.h" + +const char* ptapi_io_piuio_joystick_util_conf_input_str[PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_COUNT] = { + "invalid", + "P1 Left Up", + "P1 Right Up", + "P1 Center", + "P1 Left Down", + "P1 Right Down", + "P2 Left Up", + "P2 Right Up", + "P2 Center", + "P2 Left Down", + "P2 Right Down", + "Service", + "Test", + "Coin Clear", + "Coin", + "Coin 2" +}; + +void ptapi_io_piuio_joystick_util_conf_new(struct ptapi_io_piuio_joystick_util_conf** conf) +{ + log_assert(conf); + + *conf = util_xmalloc(sizeof(struct ptapi_io_piuio_joystick_util_conf)); + (*conf)->num_entries = 0; +} + +bool ptapi_io_piuio_joystick_util_conf_read_from_file(struct ptapi_io_piuio_joystick_util_conf** conf, const char* path) +{ + log_assert(path); + log_assert(conf); + + void* buffer; + size_t size; + + if (!util_file_load(path, &buffer, &size, false)) { + return false; + } + + *conf = (struct ptapi_io_piuio_joystick_util_conf*) buffer; + + return true; +} + +bool ptapi_io_piuio_joystick_util_conf_write_to_file(struct ptapi_io_piuio_joystick_util_conf* conf, const char* path) +{ + log_assert(path); + log_assert(conf); + + return util_file_save(path, conf, sizeof(struct ptapi_io_piuio_joystick_util_conf) + + sizeof(struct ptapi_io_piuio_joystick_util_conf_entry) * conf->num_entries); +} + +bool ptapi_io_piuio_joystick_util_conf_set_entry(struct ptapi_io_piuio_joystick_util_conf* conf, + struct ptapi_io_piuio_joystick_util_conf_entry* entry) +{ + log_assert(conf); + log_assert(entry); + + for (size_t i = 0; i < conf->num_entries; i++) { + if (!strcmp(conf->entries[i].name, entry->name) && !strcmp(conf->entries[i].dev_path, entry->dev_path)) { + memcpy(&conf->entries[i], entry, sizeof(struct ptapi_io_piuio_joystick_util_conf_entry)); + return true; + } + } + + return false; +} + +void ptapi_io_piuio_joystick_util_conf_append_entry(struct ptapi_io_piuio_joystick_util_conf** conf, + struct ptapi_io_piuio_joystick_util_conf_entry* entry) +{ + log_assert(conf); + log_assert(entry); + + (*conf)->num_entries++; + + *conf = util_xrealloc(*conf, sizeof(struct ptapi_io_piuio_joystick_util_conf*) + + sizeof(struct ptapi_io_piuio_joystick_util_conf_entry) * (*conf)->num_entries); + + memcpy(&(*conf)->entries[(*conf)->num_entries - 1], entry, sizeof(struct ptapi_io_piuio_joystick_util_conf_entry)); +}; + +bool ptapi_io_piuio_joystick_util_conf_find_entry(struct ptapi_io_piuio_joystick_util_conf* conf, const char* name, + const char* dev_path, struct ptapi_io_piuio_joystick_util_conf_entry* entry) +{ + log_assert(name); + log_assert(dev_path); + log_assert(entry); + + for (size_t i = 0; i < conf->num_entries; i++) { + if (!strcmp(conf->entries[i].name, name) && !strcmp(conf->entries[i].dev_path, dev_path)) { + memcpy(entry, &conf->entries[i], sizeof(struct ptapi_io_piuio_joystick_util_conf_entry)); + + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/joystick-util/joystick-conf.h b/src/main/ptapi/io/piuio/joystick-util/joystick-conf.h new file mode 100644 index 0000000..094d768 --- /dev/null +++ b/src/main/ptapi/io/piuio/joystick-util/joystick-conf.h @@ -0,0 +1,57 @@ +#ifndef PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_H +#define PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_H + +#include + +#include "io/util/joystick.h" + +enum PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT { + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_INVALID = 0, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_LU = 1, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_RU = 2, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_CN = 3, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_LD = 4, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_RD = 5, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_LU = 6, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_RU = 7, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_CN = 8, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_LD = 9, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_RD = 10, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_SERVICE = 11, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_TEST = 12, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_CLEAR = 13, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_COIN = 14, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_COIN2 = 15, + PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_COUNT = 16, +}; + +extern const char* ptapi_io_piuio_joystick_util_conf_input_str[PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_COUNT]; + +struct ptapi_io_piuio_joystick_util_conf_entry { + char name[JOYSTICK_NAME_LEN]; + char dev_path[PATH_MAX]; + + uint16_t button_map[PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_COUNT]; +}; + +struct ptapi_io_piuio_joystick_util_conf { + size_t num_entries; + struct ptapi_io_piuio_joystick_util_conf_entry entries[]; +}; + +void ptapi_io_piuio_joystick_util_conf_new(struct ptapi_io_piuio_joystick_util_conf** conf); + +bool ptapi_io_piuio_joystick_util_conf_read_from_file(struct ptapi_io_piuio_joystick_util_conf** conf, const char* path); + +bool ptapi_io_piuio_joystick_util_conf_write_to_file(struct ptapi_io_piuio_joystick_util_conf* conf, const char* path); + +bool ptapi_io_piuio_joystick_util_conf_set_entry(struct ptapi_io_piuio_joystick_util_conf* conf, + struct ptapi_io_piuio_joystick_util_conf_entry* entry); + +void ptapi_io_piuio_joystick_util_conf_append_entry(struct ptapi_io_piuio_joystick_util_conf** conf, + struct ptapi_io_piuio_joystick_util_conf_entry* entry); + +bool ptapi_io_piuio_joystick_util_conf_find_entry(struct ptapi_io_piuio_joystick_util_conf* conf, const char* name, + const char* dev_path, struct ptapi_io_piuio_joystick_util_conf_entry* entry); + +#endif diff --git a/src/main/ptapi/io/piuio/joystick/joystick.c b/src/main/ptapi/io/piuio/joystick/joystick.c new file mode 100644 index 0000000..5ad3663 --- /dev/null +++ b/src/main/ptapi/io/piuio/joystick/joystick.c @@ -0,0 +1,195 @@ +/** + * Implementation of the piuio API to use the game with joysticks/gamepads. + */ +#define LOG_MODULE "ptapi-io-piuio-joystick" + +#include "io/util/joystick.h" +#include "io/util/joystick-util.h" + +#include "ptapi/io/piuio.h" + +#include "ptapi/io/piuio/joystick-util/joystick-conf.h" + +#include "util/log.h" +#include "util/proc.h" +#include "util/str.h" + +#define MAX_NUM_JOYSTICKS 16 +#define CONFIG_FILENAME "/piuio-joystick-conf.bin" + +struct ptapi_io_piuio_joystick_with_conf { + struct ptapi_io_piuio_joystick_util_conf_entry* conf; + struct io_util_joystick* joystick; +}; + +static struct ptapi_io_piuio_joystick_util_conf* ptapi_io_piuio_joystick_util_conf; +static struct ptapi_io_piuio_joystick_with_conf ptapi_io_piuio_joysticks_with_conf[MAX_NUM_JOYSTICKS]; +static size_t ptapi_io_piuio_num_active_joysticks; + +static bool ptapi_io_piuio_merge_inputs(enum PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT input) +{ + bool pressed; + + pressed = false; + + for (size_t i = 0; i < ptapi_io_piuio_num_active_joysticks; i++) { + if (ptapi_io_piuio_joysticks_with_conf[i].joystick) { + pressed = pressed || + io_util_joystick_is_button_pressed( + ptapi_io_piuio_joysticks_with_conf[i].joystick, + ptapi_io_piuio_joysticks_with_conf[i].conf->button_map[input]); + } + } + + return pressed; +} + +const char* ptapi_io_piuio_ident(void) +{ + return "joystick"; +} + +bool ptapi_io_piuio_open() +{ + char path[PATH_MAX]; + char* config_path; + struct io_util_joystick_util_device_info device_info[MAX_NUM_JOYSTICKS]; + size_t devices_connected; + + // The game changes the working directory to the 'game' sub-folder. Therefore, ./my-config does not work here. + if (!util_proc_get_folder_path_executable_no_ld_linux(path, sizeof(path))) { + log_error("Getting executable folder path failed."); + return false; + } + + config_path = util_str_merge(path, CONFIG_FILENAME); + + log_info("Loading configuration: ", config_path); + + if (!ptapi_io_piuio_joystick_util_conf_read_from_file(&ptapi_io_piuio_joystick_util_conf, config_path)) { + log_error("Loading joystick config file %s failed.", CONFIG_FILENAME); + free(config_path); + return false; + } + + free(config_path); + + devices_connected = io_util_joystick_util_scan(device_info, MAX_NUM_JOYSTICKS); + + log_debug("Found connected joysticks: %d", devices_connected); + + ptapi_io_piuio_num_active_joysticks = 0; + + memset(ptapi_io_piuio_joysticks_with_conf, 0, sizeof(struct ptapi_io_piuio_joystick_with_conf) * MAX_NUM_JOYSTICKS); + + for (size_t i = 0; i < devices_connected; i++) { + log_debug("%d: %s -> %s, buttons %d, axes %d\n", + i, device_info[i].name, device_info[i].dev_path, device_info[i].num_buttons, device_info[i].num_axes); + + // Find a matching configuration + for (size_t j = 0; j < ptapi_io_piuio_joystick_util_conf->num_entries; j++) { + + if (!strcmp(ptapi_io_piuio_joystick_util_conf->entries[j].name, device_info[i].name) && + !strcmp(ptapi_io_piuio_joystick_util_conf->entries[j].dev_path, device_info[i].dev_path)) { + log_debug("Found matching configuration for %s -> %s", device_info[i].name, device_info[i].dev_path); + + ptapi_io_piuio_joysticks_with_conf[ptapi_io_piuio_num_active_joysticks].conf = + &ptapi_io_piuio_joystick_util_conf->entries[j]; + ptapi_io_piuio_num_active_joysticks++; + } + } + } + + // Open all joysticks with a matching configuration + + log_debug("Opening %d joysticks...", ptapi_io_piuio_num_active_joysticks); + + for (size_t i = 0; i < ptapi_io_piuio_num_active_joysticks; i++) { + + if (!io_util_joystick_open(ptapi_io_piuio_joysticks_with_conf[i].conf->dev_path, + &ptapi_io_piuio_joysticks_with_conf[i].joystick)) { + log_warn("Could not open joystick %s -> %s, ignoring", ptapi_io_piuio_joysticks_with_conf[i].conf->name, + ptapi_io_piuio_joysticks_with_conf[i].conf->dev_path); + } else { + log_info("Opened joystick %s -> %s", ptapi_io_piuio_joysticks_with_conf[i].conf->name, + ptapi_io_piuio_joysticks_with_conf[i].conf->dev_path); + } + } + + return true; +} + +void ptapi_io_piuio_close(void) +{ + free(ptapi_io_piuio_joystick_util_conf); + + for (size_t i = 0; i < ptapi_io_piuio_num_active_joysticks; i++) { + if (ptapi_io_piuio_joysticks_with_conf[i].joystick) { + io_util_joystick_close(ptapi_io_piuio_joysticks_with_conf[i].joystick); + } + } +} + +bool ptapi_io_piuio_recv(void) +{ + bool success; + + success = true; + + for (size_t i = 0; i < ptapi_io_piuio_num_active_joysticks; i++) { + if (ptapi_io_piuio_joysticks_with_conf[i].joystick) { + success = io_util_joystick_update(ptapi_io_piuio_joysticks_with_conf[i].joystick); + + if (!success) { + log_error("Updating joystick %s -> %s failed", ptapi_io_piuio_joysticks_with_conf[i].conf->name, + ptapi_io_piuio_joysticks_with_conf[i].conf->dev_path); + } + } + } + + return success; +} + +bool ptapi_io_piuio_send(void) +{ + /* Not supported */ + + return true; +} + +void ptapi_io_piuio_get_input_pad(uint8_t player, enum ptapi_io_piuio_sensor_group sensor_group, + struct ptapi_io_piuio_pad_inputs* inputs) +{ + if (player == 0) { + inputs->lu = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_LU); + inputs->ru = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_RU); + inputs->cn = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_CN); + inputs->ld = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_LD); + inputs->rd = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P1_RD); + } else { + inputs->lu = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_LU); + inputs->ru = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_RU); + inputs->cn = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_CN); + inputs->ld = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_LD); + inputs->rd = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_P2_RD); + } +} + +void ptapi_io_piuio_get_input_sys(struct ptapi_io_piuio_sys_inputs* inputs) +{ + inputs->test = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_TEST); + inputs->service = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_SERVICE); + inputs->clear = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_CLEAR); + inputs->coin = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_COIN); + inputs->coin2 = ptapi_io_piuio_merge_inputs(PTAPI_IO_PIUIO_JOYSTICK_UTIL_CONF_INPUT_COIN2); +} + +void ptapi_io_piuio_set_output_pad(uint8_t player, const struct ptapi_io_piuio_pad_outputs* outputs) +{ + /* Not supported */ +} + +void ptapi_io_piuio_set_output_cab(const struct ptapi_io_piuio_cab_outputs* outputs) +{ + /* Not supported */ +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/keyboard-conf/main.c b/src/main/ptapi/io/piuio/keyboard-conf/main.c new file mode 100644 index 0000000..34169df --- /dev/null +++ b/src/main/ptapi/io/piuio/keyboard-conf/main.c @@ -0,0 +1,147 @@ +#include + +#include +#include + +#include "ptapi/io/piuio/keyboard-util/keyboard-conf.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/time.h" + +#define CONFIG_FILENAME "piuio-keyboard-conf.bin" + +static Display* display; +static Window window; + +static uint32_t keyboard_get_next_input() +{ + XEvent event; + KeySym key; + + key = 0; + + while (true) { + XNextEvent(display, &event); + + if (event.type == KeyPress) { + XLookupString(&event.xkey, 0, 0, &key, 0); + } else if (event.type == KeyRelease) { + break; + } + } + + return key; +} + +static void menu_keyboard_test() +{ + printf("Pressed + released inputs:\n"); + + while (true) { + printf("%d\n", keyboard_get_next_input()); + } +} + +static void menu_keyboard_map() +{ + struct ptapi_io_piuio_keyboard_util_conf* config; + + if (!ptapi_io_piuio_keyboard_util_conf_read_from_file(&config, CONFIG_FILENAME)) { + printf("Reading existing config file failed, creating new\n"); + + ptapi_io_piuio_keyboard_util_conf_new(&config); + } else { + printf("Found existing mapping entry in config for keyboard, overwriting\n"); + } + + printf("A new window with no contents/white background is opened. Ensure to have the application focus on that " + "window to allow input capturing. Otherwise, the tool cannot capture any keyboard inputs!\n"); + + // Delay a moment to avoid picking up any previous keyboard inputs + util_time_sleep_ms(500); + + for (uint32_t i = PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_LU; i < PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_COUNT; + i++) { + printf("Press button to assign to \"%s\" or 'Enter/Return' to skip: ", + ptapi_io_piuio_keyboard_util_conf_input_str[i]); + fflush(stdout); + + config->button_map[i] = keyboard_get_next_input(); + + if (config->button_map[i] == XK_Return) { + config->button_map[i] = 0; + printf("SKIP\n"); + } else { + printf("%d\n", config->button_map[i]); + } + } + + ptapi_io_piuio_keyboard_util_conf_write_to_file(config, CONFIG_FILENAME); + + free(config); + + printf("Configuration file saved: %s\n", CONFIG_FILENAME); +} + +int main(int argc, char** argv) +{ + bool main_loop; + int screen; + + util_log_set_file("keyboard-conf.log", false); + util_log_set_level(LOG_LEVEL_ERROR); + + display = XOpenDisplay(NULL); + + if (!display) { + printf("Opening display failed\n"); + return -1; + } + + screen = DefaultScreen(display); + + window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 200, 200, 1, + BlackPixel(display, screen), WhitePixel(display, screen)); + + XSelectInput(display, window, KeyPressMask | KeyReleaseMask); + XMapWindow(display, window); + + printf("ptapi PIUIO keyboard button mapping config tool\n"); + + main_loop = true; + + while (main_loop) { + uint32_t item; + + printf("Main menu, please select:\n"); + printf("1 > Keyboard input test\n"); + printf("2 > Configure keyboard mappings\n"); + printf("3 > Exit\n"); + printf("Select number: "); + + scanf("%d", &item); + + switch (item) { + case 1: + menu_keyboard_test(); + break; + + case 2: + menu_keyboard_map(); + break; + + case 3: + main_loop = false; + break; + + default: + printf("Invalid item chosen: %d\n", item); + break; + } + } + + XCloseDisplay(display); + + return 0; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/keyboard-util/keyboard-conf.c b/src/main/ptapi/io/piuio/keyboard-util/keyboard-conf.c new file mode 100644 index 0000000..604936e --- /dev/null +++ b/src/main/ptapi/io/piuio/keyboard-util/keyboard-conf.c @@ -0,0 +1,57 @@ +#include "ptapi/io/piuio/keyboard-util/keyboard-conf.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/mem.h" + +const char* ptapi_io_piuio_keyboard_util_conf_input_str[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_COUNT] = { + "invalid", + "P1 Left Up", + "P1 Right Up", + "P1 Center", + "P1 Left Down", + "P1 Right Down", + "P2 Left Up", + "P2 Right Up", + "P2 Center", + "P2 Left Down", + "P2 Right Down", + "Service", + "Test", + "Coin Clear", + "Coin", + "Coin 2" +}; + +void ptapi_io_piuio_keyboard_util_conf_new(struct ptapi_io_piuio_keyboard_util_conf** conf) +{ + log_assert(conf); + + *conf = util_xmalloc(sizeof(struct ptapi_io_piuio_keyboard_util_conf)); + memset(*conf, 0, sizeof(struct ptapi_io_piuio_keyboard_util_conf)); +} + +bool ptapi_io_piuio_keyboard_util_conf_read_from_file(struct ptapi_io_piuio_keyboard_util_conf** conf, const char* path) +{ + log_assert(path); + log_assert(conf); + + void* buffer; + size_t size; + + if (!util_file_load(path, &buffer, &size, false)) { + return false; + } + + *conf = (struct ptapi_io_piuio_keyboard_util_conf*) buffer; + + return true; +} + +bool ptapi_io_piuio_keyboard_util_conf_write_to_file(struct ptapi_io_piuio_keyboard_util_conf* conf, const char* path) +{ + log_assert(path); + log_assert(conf); + + return util_file_save(path, conf, sizeof(struct ptapi_io_piuio_keyboard_util_conf)); +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/keyboard-util/keyboard-conf.h b/src/main/ptapi/io/piuio/keyboard-util/keyboard-conf.h new file mode 100644 index 0000000..79f63fb --- /dev/null +++ b/src/main/ptapi/io/piuio/keyboard-util/keyboard-conf.h @@ -0,0 +1,40 @@ +#ifndef PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_H +#define PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_H + +#include + +#include "io/util/joystick.h" + +enum PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT { + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_INVALID = 0, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_LU = 1, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_RU = 2, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_CN = 3, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_LD = 4, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_RD = 5, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_LU = 6, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_RU = 7, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_CN = 8, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_LD = 9, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_RD = 10, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_SERVICE = 11, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_TEST = 12, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_CLEAR = 13, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_COIN = 14, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_COIN2 = 15, + PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_COUNT = 16, +}; + +extern const char* ptapi_io_piuio_keyboard_util_conf_input_str[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_COUNT]; + +struct ptapi_io_piuio_keyboard_util_conf { + uint32_t button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_COUNT]; +}; + +void ptapi_io_piuio_keyboard_util_conf_new(struct ptapi_io_piuio_keyboard_util_conf** conf); + +bool ptapi_io_piuio_keyboard_util_conf_read_from_file(struct ptapi_io_piuio_keyboard_util_conf** conf, const char* path); + +bool ptapi_io_piuio_keyboard_util_conf_write_to_file(struct ptapi_io_piuio_keyboard_util_conf* conf, const char* path); + +#endif diff --git a/src/main/ptapi/io/piuio/keyboard/keyboard.c b/src/main/ptapi/io/piuio/keyboard/keyboard.c new file mode 100644 index 0000000..0adf510 --- /dev/null +++ b/src/main/ptapi/io/piuio/keyboard/keyboard.c @@ -0,0 +1,151 @@ +/** + * Implementation of the piuio API. This implements keyboard wrapper to play + * (actually mainly test) any piu games supporting a (mk6) usb piuio + */ +#define LOG_MODULE "ptapi-io-piuio-keyboard" + +#include + +#include "ptapi/io/piuio.h" +#include "ptapi/io/x11-input-hook.h" + +#include "ptapi/io/piuio/keyboard-util/keyboard-conf.h" + +#include "util/log.h" +#include "util/proc.h" +#include "util/str.h" + +#define CONFIG_FILENAME "/piuio-keyboard-conf.bin" +#define KEY_MAP_SIZE 0xFFFF + +struct ptapi_io_piuio_keyboard_util_conf* ptapi_io_piuio_keyboard_conf; + +static bool ptapi_io_piuio_keyboard_buffer[KEY_MAP_SIZE]; + +static void _piuio_emu_keyboard_dispatch_key_press(KeySym key); +static void _piuio_emu_keyboard_dispatch_key_release(KeySym key); + +static const struct ptapi_io_x11_input_hook_handler _piuio_emu_keyboard_input_hook_handler = { + .dispatch_key_press = _piuio_emu_keyboard_dispatch_key_press, + .dispatch_key_release = _piuio_emu_keyboard_dispatch_key_release, +}; + +const char* ptapi_io_piuio_ident(void) +{ + return "keyboard"; +} + +const struct ptapi_io_x11_input_hook_handler* ptapi_io_x11_input_handler_hook(void) +{ + return &_piuio_emu_keyboard_input_hook_handler; +} + +bool ptapi_io_piuio_open() +{ + char path[PATH_MAX]; + char* config_path; + + // The game changes the working directory to the 'game' sub-folder. Therefore, ./my-config does not work here. + if (!util_proc_get_folder_path_executable_no_ld_linux(path, sizeof(path))) { + log_error("Getting executable folder path failed."); + return false; + } + + config_path = util_str_merge(path, CONFIG_FILENAME); + + log_info("Loading configuration: ", config_path); + + if (!ptapi_io_piuio_keyboard_util_conf_read_from_file(&ptapi_io_piuio_keyboard_conf, config_path)) { + log_error("Loading keyboard config file %s failed.", config_path); + free(config_path); + return false; + } + + free(config_path); + + memset(ptapi_io_piuio_keyboard_buffer, 0, sizeof(bool) * KEY_MAP_SIZE); + + return true; +} + +void ptapi_io_piuio_close(void) +{ + free(ptapi_io_piuio_keyboard_conf); +} + +bool ptapi_io_piuio_recv(void) +{ + /* Nothing to poll */ + return true; +} + +bool ptapi_io_piuio_send(void) +{ + return true; +} + +void ptapi_io_piuio_get_input_pad(uint8_t player, enum ptapi_io_piuio_sensor_group sensor_group, + struct ptapi_io_piuio_pad_inputs* inputs) +{ + if (player == 0) { + inputs->lu = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_LU]]; + inputs->ru = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_RU]]; + inputs->cn = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_CN]]; + inputs->ld = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_LD]]; + inputs->rd = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P1_RD]]; + } else { + inputs->lu = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_LU]]; + inputs->ru = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_RU]]; + inputs->cn = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_CN]]; + inputs->ld = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_LD]]; + inputs->rd = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_P2_RD]]; + } +} + +void ptapi_io_piuio_get_input_sys(struct ptapi_io_piuio_sys_inputs* inputs) +{ + inputs->test = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_TEST]]; + inputs->service = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_SERVICE]]; + inputs->clear = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_CLEAR]]; + inputs->coin = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_COIN]]; + inputs->coin2 = ptapi_io_piuio_keyboard_buffer[ + ptapi_io_piuio_keyboard_conf->button_map[PTAPI_IO_PIUIO_KEYBOARD_UTIL_CONF_INPUT_COIN2]]; +} + +void ptapi_io_piuio_set_output_pad(uint8_t player, const struct ptapi_io_piuio_pad_outputs* outputs) +{ + /* Not supported */ +} + +void ptapi_io_piuio_set_output_cab(const struct ptapi_io_piuio_cab_outputs* outputs) +{ + /* Not supported */ +} + +static void _piuio_emu_keyboard_dispatch_key_press(KeySym key) +{ + if (key > 0 && key < KEY_MAP_SIZE) { + ptapi_io_piuio_keyboard_buffer[key] = true; + } +} + +static void _piuio_emu_keyboard_dispatch_key_release(KeySym key) +{ + if (key > 0 && key < KEY_MAP_SIZE) { + ptapi_io_piuio_keyboard_buffer[key] = false; + } +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/null/null.c b/src/main/ptapi/io/piuio/null/null.c new file mode 100644 index 0000000..7669052 --- /dev/null +++ b/src/main/ptapi/io/piuio/null/null.c @@ -0,0 +1,55 @@ +/** + * Implementation of the piuio API. Null driver for testing and starting piu without a real io + */ +#define LOG_MODULE "ptapi-io-piuio-null" + +#include "ptapi/io/piuio.h" + +const char* ptapi_io_piuio_ident(void) +{ + return "null"; +} + +bool ptapi_io_piuio_open(void) +{ + return true; +} + +void ptapi_io_piuio_close(void) +{ + +} + +bool ptapi_io_piuio_recv(void) +{ + return true; +} + +bool ptapi_io_piuio_send(void) +{ + return true; +} + +void ptapi_io_piuio_get_input_pad(uint8_t player, + enum ptapi_io_piuio_sensor_group sensor_group, + struct ptapi_io_piuio_pad_inputs* inputs) +{ + +} + +void ptapi_io_piuio_get_input_sys(struct ptapi_io_piuio_sys_inputs* inputs) +{ + +} + +void ptapi_io_piuio_set_output_pad(uint8_t player, + const struct ptapi_io_piuio_pad_outputs* outputs) +{ + +} + +void ptapi_io_piuio_set_output_cab( + const struct ptapi_io_piuio_cab_outputs* outputs) +{ + /* Not supported */ +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/real/piuio.c b/src/main/ptapi/io/piuio/real/piuio.c new file mode 100644 index 0000000..d3ed607 --- /dev/null +++ b/src/main/ptapi/io/piuio/real/piuio.c @@ -0,0 +1,187 @@ +/** + * Implementation of the piuio API. This implements a piuio usb driver to + * communicate with real piuio hardware and also shares a common interface with + * other "piuio devices" (e.g. keyboard, custom IOs) + */ +#define LOG_MODULE "ptapi-io-piuio-null" + +#include +#include + +#include "io/piuio/defs.h" +#include "io/piuio/device.h" + +#include "ptapi/io/piuio.h" + +static uint8_t piuio_drv_piuio_in_buffer[PIUIO_DRV_BUFFER_SIZE]; +static uint8_t piuio_drv_piuio_out_buffer[PIUIO_DRV_BUFFER_SIZE]; + +static struct ptapi_io_piuio_pad_inputs + piuio_drv_piuio_pad_in[2][PTAPI_IO_PIUIO_SENSOR_GROUP_NUM]; +static struct ptapi_io_piuio_sys_inputs piuio_drv_piuio_sys; +static struct ptapi_io_piuio_pad_outputs piuio_drv_piuio_pad_out[2]; +static struct ptapi_io_piuio_cab_outputs piuio_drv_piuio_cab_out; + +static void piuio_drv_piuio_copy_outputs(uint8_t sensor_group) +{ + memset(piuio_drv_piuio_out_buffer, 0, sizeof(piuio_drv_piuio_out_buffer)); + + /* 00 = sensor 1 (right), 01 = sensor 2 (left), 10 = sensor 3 (down), + 11 = sensor 4 (up) */ + /* Pad 1 sensor group selector */ + piuio_drv_piuio_out_buffer[0] |= sensor_group; + /* Pad 2 sensor group selector */ + piuio_drv_piuio_out_buffer[2] |= sensor_group; + + /* Set pad lights */ + for (uint8_t i = 0; i < 2; i++) { + if (piuio_drv_piuio_pad_out[i].lu) { + piuio_drv_piuio_out_buffer[i * 2] |= (1 << 2); + } + + if (piuio_drv_piuio_pad_out[i].ru) { + piuio_drv_piuio_out_buffer[i * 2] |= (1 << 3); + } + + if (piuio_drv_piuio_pad_out[i].cn) { + piuio_drv_piuio_out_buffer[i * 2] |= (1 << 4); + } + + if (piuio_drv_piuio_pad_out[i].ld) { + piuio_drv_piuio_out_buffer[i * 2] |= (1 << 5); + } + + if (piuio_drv_piuio_pad_out[i].rd) { + piuio_drv_piuio_out_buffer[i * 2] |= (1 << 6); + } + } + + /* Neons/Bass */ + if (piuio_drv_piuio_cab_out.bass) { + piuio_drv_piuio_out_buffer[1] |= (1 << 2); + } + + /* Halogens */ + if (piuio_drv_piuio_cab_out.halo_r2) { + piuio_drv_piuio_out_buffer[2] |= (1 << 7); + } + + if (piuio_drv_piuio_cab_out.halo_r1) { + piuio_drv_piuio_out_buffer[3] |= (1 << 0); + } + + if (piuio_drv_piuio_cab_out.halo_l2) { + piuio_drv_piuio_out_buffer[3] |= (1 << 1); + } + + if (piuio_drv_piuio_cab_out.halo_l1) { + piuio_drv_piuio_out_buffer[3] |= (1 << 2); + } + + // // set coin counter 1 + // piuio_drv_piuio_out_buffer[3] |= ((m_outputStates->m_counter & 0x01) << 4); + + // // set coin counter 2 (front usb enable) + // piuio_drv_piuio_out_buffer[1] |= ((m_outputStates->m_counter & 0x02) << 3); +} + +static void piuio_drv_piuio_copy_inputs(uint8_t sensor_group) +{ + /* Pad */ + for (uint8_t i = 0; i < 2; i++) { + piuio_drv_piuio_pad_in[i][sensor_group].lu = + piuio_drv_piuio_in_buffer[i * 2] & (1 << 0); + piuio_drv_piuio_pad_in[i][sensor_group].ru = + piuio_drv_piuio_in_buffer[i * 2] & (1 << 1); + piuio_drv_piuio_pad_in[i][sensor_group].cn = + piuio_drv_piuio_in_buffer[i * 2] & (1 << 2); + piuio_drv_piuio_pad_in[i][sensor_group].ld = + piuio_drv_piuio_in_buffer[i * 2] & (1 << 3); + piuio_drv_piuio_pad_in[i][sensor_group].rd = + piuio_drv_piuio_in_buffer[i * 2] & (1 << 4); + } + + /* Sys */ + piuio_drv_piuio_sys.test = piuio_drv_piuio_in_buffer[1] & (1 << 1); + piuio_drv_piuio_sys.service = piuio_drv_piuio_in_buffer[1] & (1 << 6); + piuio_drv_piuio_sys.clear = piuio_drv_piuio_in_buffer[1] & (1 << 7); + piuio_drv_piuio_sys.coin = piuio_drv_piuio_in_buffer[1] & (1 << 2); + + memset(piuio_drv_piuio_in_buffer, 0, sizeof(piuio_drv_piuio_in_buffer)); +} + +const char* ptapi_io_piuio_ident(void) +{ + return "piuio"; +} + +bool ptapi_io_piuio_open(void) +{ + return piuio_drv_device_open(); +} + +void ptapi_io_piuio_close(void) +{ + piuio_drv_device_close(); +} + +bool ptapi_io_piuio_recv(void) +{ + /* cycle all four sensor groups */ + for (uint8_t i = 0; i < PTAPI_IO_PIUIO_SENSOR_GROUP_NUM; i++) { + piuio_drv_piuio_copy_outputs(i); + + if (!piuio_drv_device_write(piuio_drv_piuio_out_buffer, + sizeof(piuio_drv_piuio_out_buffer))) { + return false; + } + + if (!piuio_drv_device_read(piuio_drv_piuio_in_buffer, + sizeof(piuio_drv_piuio_in_buffer))) { + return false; + } + + /* Invert pull ups */ + for (uint8_t j = 0; j < 4; j++) { + piuio_drv_piuio_in_buffer[j] ^= 0xFF; + } + + piuio_drv_piuio_copy_inputs(i); + } + + return true; +} + +bool ptapi_io_piuio_send(void) +{ + /* Already done in recv */ + return true; +} + +void ptapi_io_piuio_get_input_pad(uint8_t player, + enum ptapi_io_piuio_sensor_group sensor_group, + struct ptapi_io_piuio_pad_inputs* inputs) +{ + memcpy(inputs, &piuio_drv_piuio_pad_in[player][sensor_group], + sizeof(struct ptapi_io_piuio_pad_inputs)); +} + +void ptapi_io_piuio_get_input_sys(struct ptapi_io_piuio_sys_inputs* inputs) +{ + memcpy(inputs, &piuio_drv_piuio_sys, + sizeof(struct ptapi_io_piuio_sys_inputs)); +} + +void ptapi_io_piuio_set_output_pad(uint8_t player, + const struct ptapi_io_piuio_pad_outputs* outputs) +{ + memcpy(&piuio_drv_piuio_pad_out[player], outputs, + sizeof(struct ptapi_io_piuio_pad_outputs)); +} + +void ptapi_io_piuio_set_output_cab( + const struct ptapi_io_piuio_cab_outputs* outputs) +{ + memcpy(&piuio_drv_piuio_cab_out, outputs, + sizeof(struct ptapi_io_piuio_cab_outputs)); +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/test/main.c b/src/main/ptapi/io/piuio/test/main.c new file mode 100644 index 0000000..437d681 --- /dev/null +++ b/src/main/ptapi/io/piuio/test/main.c @@ -0,0 +1,181 @@ +/** + * Tool to test implementations of the piuio API + */ +#include +#include +#include +#include + +#include "ptapi/io/piuio.h" +#include "ptapi/io/piuio/util/lib.h" + +#include "util/log.h" + +static struct ptapi_io_piuio_api api; + +int main(int argc, char** argv) +{ + struct ptapi_io_piuio_pad_inputs + pad_in[2][PTAPI_IO_PIUIO_SENSOR_GROUP_NUM]; + struct ptapi_io_piuio_sys_inputs sys_in; + struct ptapi_io_piuio_pad_outputs pad_out[2]; + struct ptapi_io_piuio_cab_outputs cab_out; + + util_log_set_file("piuio-test.log", false); + util_log_set_level(LOG_LEVEL_ERROR); + + if (argc < 2) { + printf( + "Usage: %s [debug]\n", + argv[0]); + return -1; + } + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "debug")) { + util_log_set_level(LOG_LEVEL_DEBUG); + break; + } + } + + if (!ptapi_io_piuio_util_lib_load(argv[1], &api)) { + printf("Loading piuio lib %s failed\n", argv[1]); + return -2; + } + + if (!api.open()) { + return -3; + } + + memset(&pad_in, 0, sizeof(pad_in)); + memset(&sys_in, 0, sizeof(sys_in)); + memset(&pad_out, 0, sizeof(pad_out)); + memset(&cab_out, 0, sizeof(cab_out)); + + printf("Enter main loop by pressing any key (on the keyboard)\n"); + getchar(); + + while (true) { + if (!api.recv()) { + return -4; + } + + for (uint8_t i = 0; i < 2; i++) { + for (uint8_t j = 0; j < PTAPI_IO_PIUIO_SENSOR_GROUP_NUM; j++) { + api.get_input_pad(i, j, &pad_in[i][j]); + } + } + + api.get_input_sys(&sys_in); + + if (sys_in.test && sys_in.service) { + break; + } + + system("clear"); + + printf("Press test + service to exit\n"); + printf( + "| Test %d Service %d Clear %d Coin %d |\n" + "|-------------------||-------------------|\n" + "| %d %d || %d %d |\n" + "|%d %d %d %d||%d %d %d %d|\n" + "| %d %d || %d %d |\n" + "| %d || %d |\n" + "| %d %d || %d %d |\n" + "| %d || %d |\n" + "| %d %d || %d %d |\n" + "|%d %d %d %d||%d %d %d %d|\n" + "| %d %d || %d %d |\n" + "|-------------------||-------------------|\n", + sys_in.test, sys_in.service, sys_in.clear, sys_in.coin, + + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].lu, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].ru, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].lu, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].ru, + + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].lu, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].lu, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].ru, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].ru, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].lu, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].lu, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].ru, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].ru, + + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].lu, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].ru, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].lu, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].ru, + + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].cn, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].cn, + + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].cn, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].cn, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].cn, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].cn, + + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].cn, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].cn, + + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].ld, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].rd, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].ld, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_UP].rd, + + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].ld, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].ld, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].rd, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].rd, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].ld, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].ld, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_LEFT].rd, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_RIGHT].rd, + + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].ld, + pad_in[0][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].rd, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].ld, + pad_in[1][PTAPI_IO_PIUIO_SENSOR_GROUP_DOWN].rd); + + memset(&pad_out, 0, sizeof(pad_out)); + + for (int i = 0; i < 2; i++) { + for (int j = 0; j < PTAPI_IO_PIUIO_SENSOR_GROUP_NUM; j++) { + if (pad_in[i][j].lu) { + pad_out[i].lu = true; + } + + if (pad_in[i][j].ru) { + pad_out[i].ru = true; + } + + if (pad_in[i][j].cn) { + pad_out[i].cn = true; + } + + if (pad_in[i][j].ld) { + pad_out[i].ld = true; + } + + if (pad_in[i][j].rd) { + pad_out[i].rd = true; + } + } + + api.set_output_pad(i, &pad_out[i]); + } + + if (!api.send()) { + return -5; + } + + /* Avoid hardware banging */ + usleep(1000); + } + + api.close(); + + return 0; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/util/lib.c b/src/main/ptapi/io/piuio/util/lib.c new file mode 100644 index 0000000..7d15d7b --- /dev/null +++ b/src/main/ptapi/io/piuio/util/lib.c @@ -0,0 +1,38 @@ +#include + +#include "capnhook/hook/lib.h" + +#include "ptapi/io/piuio/util/lib.h" + +bool ptapi_io_piuio_util_lib_load(const char* path, struct ptapi_io_piuio_api* api) +{ + void* handle; + + handle = cnh_lib_load(path); + + if (handle == NULL) { + return false; + } + + api->ident = (ptapi_io_piuio_ident_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piuio_ident"); + + api->open = (ptapi_io_piuio_open_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piuio_open"); + api->close = (ptapi_io_piuio_close_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piuio_close"); + api->recv = (ptapi_io_piuio_recv_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piuio_recv"); + api->send = (ptapi_io_piuio_send_t) cnh_lib_get_func_addr_handle( + handle, "ptapi_io_piuio_send"); + api->get_input_pad = (ptapi_io_piuio_get_input_pad_t) + cnh_lib_get_func_addr_handle(handle, "ptapi_io_piuio_get_input_pad"); + api->get_input_sys = (ptapi_io_piuio_get_input_sys_t) + cnh_lib_get_func_addr_handle(handle, "ptapi_io_piuio_get_input_sys"); + api->set_output_pad = (ptapi_io_piuio_set_output_pad_t) + cnh_lib_get_func_addr_handle(handle, "ptapi_io_piuio_set_output_pad"); + api->set_output_cab = (ptapi_io_piuio_set_output_cab_t) + cnh_lib_get_func_addr_handle(handle, "ptapi_io_piuio_set_output_cab"); + + return true; +} \ No newline at end of file diff --git a/src/main/ptapi/io/piuio/util/lib.h b/src/main/ptapi/io/piuio/util/lib.h new file mode 100644 index 0000000..e564b1a --- /dev/null +++ b/src/main/ptapi/io/piuio/util/lib.h @@ -0,0 +1,8 @@ +#ifndef PTAPI_IO_PIUIO_UTIL_LIB_H +#define PTAPI_IO_PIUIO_UTIL_LIB_H + +#include "ptapi/io/piuio.h" + +bool ptapi_io_piuio_util_lib_load(const char* path, struct ptapi_io_piuio_api* api); + +#endif diff --git a/src/main/pumpnet/client/main.c b/src/main/pumpnet/client/main.c new file mode 100644 index 0000000..143fe6d --- /dev/null +++ b/src/main/pumpnet/client/main.c @@ -0,0 +1,115 @@ +#define LOG_MODULE "pumpnet-client" + +#include +#include + +#include "asset/nx2/lib/usb-rank.h" +#include "asset/nx2/lib/usb-save.h" + +#include "pumpnet/lib/pumpnet.h" + +#include "util/fs.h" +#include "util/log.h" +#include "util/mem.h" + +static void _print_usage_and_exit(const char* program_name) +{ + log_info("Usage: %s " + "", program_name); + exit(EXIT_FAILURE); +} + +static size_t _get_size_save_file(enum asset_game_version game_version) +{ + switch (game_version) { + case ASSET_GAME_VERSION_NX2: + return ASSET_NX2_USB_SAVE_SIZE; + + default: + log_die_illegal_state(); + return 0; + } +} + +static size_t _get_size_rank_file(enum asset_game_version game_version) +{ + switch (game_version) { + case ASSET_GAME_VERSION_NX2: + return ASSET_NX2_USB_RANK_SIZE; + + default: + log_die_illegal_state(); + return 0; + } +} + +static size_t _get_size_usb_file(enum asset_game_version game_version, enum pumpnet_lib_file_type file_type) +{ + switch (file_type) { + case PUMPNET_LIB_FILE_TYPE_SAVE: + return _get_size_save_file(game_version); + + case PUMPNET_LIB_FILE_TYPE_RANK: + return _get_size_rank_file(game_version); + + default: + log_die_illegal_state(); + return 0; + } +} + +int main(int argc, char** argv) +{ + util_log_set_level(LOG_LEVEL_DEBUG); + + if (argc < 8) { + _print_usage_and_exit(argv[0]); + } + + const char* server_addr = argv[1]; + const char* cmd = argv[2]; + enum asset_game_version game_version = strtol(argv[3], NULL, 10); + enum pumpnet_lib_file_type file_type = strtol(argv[4], NULL, 10); + uint64_t machine_id = strtol(argv[5], NULL, 16); + uint64_t player_ref_id = strtol(argv[6], NULL, 16); + const char* file_path = argv[7]; + + bool success; + + pumpnet_lib_init(game_version, server_addr, machine_id, NULL, true); + + if (!strcmp(cmd, "put")) { + size_t size; + void* buffer; + + if (!util_file_load(file_path, &buffer, &size, false)) { + log_error("Loading data from %s failed", file_path); + success = false; + } else { + success = pumpnet_lib_put(file_type, player_ref_id, buffer, sizeof(buffer)); + } + } else if (!strcmp(cmd, "get")) { + size_t buffer_size = _get_size_usb_file(game_version, file_type); + uint8_t* buffer = malloc(buffer_size); + + success = pumpnet_lib_get(file_type, player_ref_id, buffer, sizeof(buffer)); + + if (success) { + if (!util_file_save(file_path, buffer, buffer_size)) { + log_error("Saving data to %s, size %d failed", file_path, buffer_size); + success = false; + } + } + } else { + log_error("Invalid command %s", cmd); + success = false; + } + + pumpnet_lib_shutdown(); + + if (success) { + exit(EXIT_SUCCESS); + } else { + exit(EXIT_FAILURE); + } +} \ No newline at end of file diff --git a/src/main/pumpnet/lib/http.c b/src/main/pumpnet/lib/http.c new file mode 100644 index 0000000..91a4df5 --- /dev/null +++ b/src/main/pumpnet/lib/http.c @@ -0,0 +1,297 @@ +#define LOG_MODULE "http" + +#include +#include + +#include "util/base64.h" +#include "util/fs.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +#define MEDIA_TYPE "text/plain" +#define RECV_BUFFER_SIZE 1024 * 512 +#define CLIENT_CRT "/client-crt.pem" +#define CLIENT_KEY "/client-key.pem" +#define CA_BUNDLE_CRT "/ca-bundle-crt.pem" + +struct pumpnet_lib_http_buffer { + void* data; + size_t size; + size_t pos; +}; + +static size_t _pumpnet_lib_http_curl_cb_write_data(void* ptr, size_t size, size_t nmemb, void* ctx) +{ + struct pumpnet_lib_http_buffer* buffer; + + buffer = (struct pumpnet_lib_http_buffer*) ctx; + + if (buffer->pos == buffer->size) { + return 0; + } + + if (buffer->pos + size * nmemb >= buffer->size) { + log_error("Buffer overflow, pos %d, size %d | size %d, nmemb %d", + buffer->pos, + buffer->size, + size, + nmemb); + return 0; + } + + memcpy(buffer->data + buffer->pos, ptr, size * nmemb); + buffer->pos += size * nmemb; + + return size * nmemb; +} + +static size_t _pumpnet_lib_http_curl_cb_read_data(void* ptr, size_t size, size_t nmemb, void* ctx) +{ + struct pumpnet_lib_http_buffer* buffer; + size_t max_size; + size_t read; + + max_size = size * nmemb; + + buffer = (struct pumpnet_lib_http_buffer*) ctx; + + if (buffer->size == buffer->pos) { + return 0; + } + + if (buffer->size - buffer->pos < max_size) { + read = buffer->size - buffer->pos; + } else { + read = max_size; + } + + memcpy(ptr, buffer->data + buffer->pos, read); + + buffer->pos += read; + + return read; +} + +int _pumpnet_libcurl_debug_callback( + CURL *handle, + curl_infotype type, + char *data, + size_t size, + void *userptr) +{ + char* null_term_buffer; + + switch (type) { + case CURLINFO_TEXT: + log_debug("[CURL]: %s", data); + break; + + case CURLINFO_HEADER_IN: + case CURLINFO_HEADER_OUT: + null_term_buffer = util_xmalloc(size + 1); + memcpy(null_term_buffer, data, size); + null_term_buffer[size] = '\0'; + + log_debug("[CURL]: %d (%p), len %ld:\n%s", type, handle, size, data); + + free(null_term_buffer); + break; + case CURLINFO_DATA_IN: + case CURLINFO_DATA_OUT: + case CURLINFO_SSL_DATA_OUT: + case CURLINFO_SSL_DATA_IN: + log_debug("[CURL]: %d (%p), len %ld", type, handle, size); + break; + default: + log_die_illegal_state(); + } + + return 0; +} + +static char* pumpnet_lib_http_client_crt_path; +static char* pumpnet_lib_http_client_key_path; +static char* pumpnet_lib_http_ca_bundle_crt_path; +static bool pumpnet_lib_http_verbose_debug_log; + +void pumpnet_lib_http_init(const char* cert_dir_path, bool verbose_debug_log) +{ + if (cert_dir_path) { + char* absolute_path = util_fs_get_abs_path(cert_dir_path); + + pumpnet_lib_http_client_crt_path = util_str_merge(absolute_path, CLIENT_CRT); + pumpnet_lib_http_client_key_path = util_str_merge(absolute_path, CLIENT_KEY); + pumpnet_lib_http_ca_bundle_crt_path = util_str_merge(absolute_path, CA_BUNDLE_CRT); + + free(absolute_path); + } + + pumpnet_lib_http_verbose_debug_log = verbose_debug_log; + + log_info("Initialized, client crt %s, client key %s, ca bundle crt %s, verbose debug log %d", + pumpnet_lib_http_client_crt_path, + pumpnet_lib_http_client_key_path, + pumpnet_lib_http_ca_bundle_crt_path, + verbose_debug_log); +} + +void pumpnet_lib_http_shutdown() +{ + if (pumpnet_lib_http_client_crt_path) { + free(pumpnet_lib_http_client_crt_path); + } + + if (pumpnet_lib_http_client_key_path) { + free(pumpnet_lib_http_client_key_path); + } + + if (pumpnet_lib_http_ca_bundle_crt_path) { + free(pumpnet_lib_http_ca_bundle_crt_path); + } +} + +bool pumpnet_lib_http_get_put( + uint64_t trace_id, + const char* address, + void* send_data, + size_t send_size, + void* recv_data, + size_t recv_size, + uint32_t* http_code, + bool is_post) +{ + log_assert(address); + log_assert(send_data); + log_assert(recv_data); + log_assert(http_code); + + CURL* curl_handle; + CURLcode res; + struct pumpnet_lib_http_buffer send_buffer; + struct pumpnet_lib_http_buffer recv_buffer; + struct curl_slist* host = NULL; + + *http_code = 0; + + curl_handle = curl_easy_init(); + + if (!curl_handle) { + log_error("[%llX] Initializing curl backend failed", trace_id); + return false; + } + + send_buffer.data = util_base64_encode(send_data, send_size, &send_buffer.size); + send_buffer.pos = 0; + + recv_buffer.data = util_xmalloc(RECV_BUFFER_SIZE); + recv_buffer.size = RECV_BUFFER_SIZE; + recv_buffer.pos = 0; + + if (is_post) { + curl_easy_setopt(curl_handle, CURLOPT_POST, 1L); + } else { + curl_easy_setopt(curl_handle, CURLOPT_POST, 1L); + curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "GET"); + } + + if (pumpnet_lib_http_verbose_debug_log) { + curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1); + curl_easy_setopt(curl_handle, CURLOPT_DEBUGFUNCTION, _pumpnet_libcurl_debug_callback); + } + + if (pumpnet_lib_http_client_crt_path) { + host = curl_slist_append(NULL, "pumpnet:443:185.41.243.94"); + curl_easy_setopt(curl_handle, CURLOPT_RESOLVE, host); + curl_easy_setopt(curl_handle, CURLOPT_SSLCERT, pumpnet_lib_http_client_crt_path); + curl_easy_setopt(curl_handle, CURLOPT_SSLKEY, pumpnet_lib_http_client_key_path); + curl_easy_setopt(curl_handle, CURLOPT_SSLKEYTYPE, "PEM"); + curl_easy_setopt(curl_handle, CURLOPT_CAINFO, pumpnet_lib_http_ca_bundle_crt_path); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1); + } + + // It seems like some libcurl revisions cut off the content after processing it using the provided + // CURLOPT_READFUNCTION if the size is not force set like this + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, send_buffer.size); + + curl_easy_setopt(curl_handle, CURLOPT_URL, address); + curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, _pumpnet_lib_http_curl_cb_read_data); + curl_easy_setopt(curl_handle, CURLOPT_READDATA, &send_buffer); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, _pumpnet_lib_http_curl_cb_write_data); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &recv_buffer); + + struct curl_slist *hs=NULL; + hs = curl_slist_append(hs, "Content-Type: " MEDIA_TYPE); + curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hs); + + log_debug("[%llX][%s] %s %d %d", trace_id, address, is_post ? "POST" : "GET", send_size, recv_size); + + res = curl_easy_perform(curl_handle); + + curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, http_code); + + log_debug("[%llX][%s] %s: %d (%d %d)", + trace_id, + address, + is_post ? "POST" : "GET", + *http_code, + recv_buffer.pos, + send_buffer.pos); + + if (host) { + curl_slist_free_all(host); + } + + curl_slist_free_all(hs); + + curl_easy_cleanup(curl_handle); + + if (res != CURLE_OK) { + log_error("[%llX][%s][%d] Performing curl request failed: %s", + trace_id, + address, + is_post, + curl_easy_strerror(res)); + + free(send_buffer.data); + free(recv_buffer.data); + return false; + } + + if (send_buffer.pos != send_buffer.size) { + log_error("[%llX][%s][%d] Invalid send data size: %d != %d", + trace_id, + address, + is_post, + send_buffer.pos, + send_buffer.size); + + free(send_buffer.data); + free(recv_buffer.data); + return false; + } + + free(send_buffer.data); + + size_t recv_size_decoded; + uint8_t* tmp_recv_decoded = util_base64_decode(recv_buffer.data, recv_buffer.pos, &recv_size_decoded); + + free(recv_buffer.data); + + if (recv_size_decoded != recv_size) { + log_error("[%llX][%s][%d] Invalid recv data size: %d != %d", + trace_id, + address, + is_post, + recv_size_decoded, + recv_size); + + free(tmp_recv_decoded); + return false; + } + + memcpy(recv_data, tmp_recv_decoded, recv_size); + free(tmp_recv_decoded); + + return true; +} \ No newline at end of file diff --git a/src/main/pumpnet/lib/http.h b/src/main/pumpnet/lib/http.h new file mode 100644 index 0000000..312fb8e --- /dev/null +++ b/src/main/pumpnet/lib/http.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +#define HTTP_CODE_OK 200 +#define HTTP_CODE_PRECONDITION_FAILED 412 + +void pumpnet_lib_http_init(const char* cert_dir_path, bool verbose_debug_log); + +void pumpnet_lib_http_shutdown(); + +bool pumpnet_lib_http_get_put( + uint64_t trace_id, + const char* address, + void* send_data, + size_t send_size, + void* recv_data, + size_t recv_size, + uint32_t* http_code, + bool is_post); \ No newline at end of file diff --git a/src/main/pumpnet/lib/profile-token.c b/src/main/pumpnet/lib/profile-token.c new file mode 100644 index 0000000..5ac22a9 --- /dev/null +++ b/src/main/pumpnet/lib/profile-token.c @@ -0,0 +1,44 @@ +#define LOG_MODULE "pumpnet-lib-profile-token" + +#include +#include +#include + +#include "pumpnet/lib/profile-token.h" + +#include "util/log.h" + +bool pumpnet_lib_profile_token_load(const char* path, uint64_t* profile_id) +{ + FILE* file = fopen(path, "rb"); + ssize_t size; + + if (!file) { + *profile_id = PUMPNET_LIB_PROFILE_TOKEN_INVALID; + return false; + } + + // TODO file needs to be encrypted + + fseek(file, 0, SEEK_END); + size = ftell(file); + + if (size != 8) { + fclose(file); + *profile_id = PUMPNET_LIB_PROFILE_TOKEN_INVALID; + log_error("Invalid file size (%d) for pumpnet profile %s", size, path); + return false; + } + + fseek(file, 0, SEEK_SET); + + if (fread(profile_id, 8, 1, file) != 1) { + fclose(file); + *profile_id = PUMPNET_LIB_PROFILE_TOKEN_INVALID; + log_error("Reading pumpnet profile %s failed: %s", path, strerror(errno)); + return false; + } + + fclose(file); + return true; +} \ No newline at end of file diff --git a/src/main/pumpnet/lib/profile-token.h b/src/main/pumpnet/lib/profile-token.h new file mode 100644 index 0000000..6776d19 --- /dev/null +++ b/src/main/pumpnet/lib/profile-token.h @@ -0,0 +1,15 @@ +// +// Created by on 9/16/18. +// + +#ifndef PUMPNET_LIB_PROFILE_TOKEN_H +#define PUMPNET_LIB_PROFILE_TOKEN_H + +#include +#include + +#define PUMPNET_LIB_PROFILE_TOKEN_INVALID 0xFFFFFFFFFFFFFFFF + +bool pumpnet_lib_profile_token_load(const char* path, uint64_t* profile_id); + +#endif diff --git a/src/main/pumpnet/lib/protocol.h b/src/main/pumpnet/lib/protocol.h new file mode 100644 index 0000000..a3743ca --- /dev/null +++ b/src/main/pumpnet/lib/protocol.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "pumpnet/lib/pumpnet.h" + +#define USBPROFILE_ENDPOINT "/usbprofile/v1" + +struct pumpnet_lib_get_save_req { + uint64_t trace_id; + uint64_t machine_id; + uint64_t player_ref_id; +} __attribute__((__packed__)); + +struct pumpnet_lib_get_save_resp { + size_t size; + uint8_t data[]; +} __attribute__((__packed__)); + +struct pumpnet_lib_get_rank_req { + uint64_t trace_id; + uint64_t machine_id; + uint64_t player_ref_id; +} __attribute__((__packed__)); + +struct pumpnet_lib_get_rank_resp { + size_t size; + uint8_t data[]; +} __attribute__((__packed__)); + +struct pumpnet_lib_put_save_req { + uint64_t trace_id; + uint64_t machine_id; + uint64_t player_ref_id; + size_t size; + uint8_t data[]; +} __attribute__((__packed__)); + +struct pumpnet_lib_put_save_resp { + +} __attribute__((__packed__)); + +struct pumpnet_lib_put_rank_req { + uint64_t trace_id; + uint64_t machine_id; + uint64_t player_ref_id; + size_t size; + uint8_t data[]; +} __attribute__((__packed__)); + +struct pumpnet_lib_put_rank_resp { + +} __attribute__((__packed__)); \ No newline at end of file diff --git a/src/main/pumpnet/lib/pumpnet.c b/src/main/pumpnet/lib/pumpnet.c new file mode 100644 index 0000000..3ec46f7 --- /dev/null +++ b/src/main/pumpnet/lib/pumpnet.c @@ -0,0 +1,343 @@ +#define LOG_MODULE "pumpnet" + +#include + +#include "pumpnet/lib/http.h" +#include "pumpnet/lib/protocol.h" +#include "pumpnet/lib/pumpnet.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/rand.h" +#include "util/str.h" +#include "util/time.h" + +#define RETRY_COUNT_ON_ERROR 10 +#define RETRY_TIME_BACKOFF_MS 2000 + +static enum asset_game_version pumpnet_lib_game; +static char* pumpnet_lib_server_addr; +static uint64_t pumpnet_lib_machine_id; + +static char* pumpnet_lib_server_endpoint_save; +static char* pumpnet_lib_server_endpoint_rank; + +static char* _pumpnet_lib_get_endpoint_save(enum asset_game_version game, const char* server_addr) +{ + switch (game) { + case ASSET_GAME_VERSION_NX2: + return util_str_merge(server_addr, USBPROFILE_ENDPOINT "/nx2/save"); + + default: + log_die("Unsupported game version %d", game); + return NULL; + } +} + +static char* _pumpnet_lib_get_endpoint_rank(enum asset_game_version game, const char* server_addr) +{ + switch (game) { + case ASSET_GAME_VERSION_NX2: + return util_str_merge(server_addr, USBPROFILE_ENDPOINT "/nx2/rank"); + + default: + log_die("Unsupported game version %d", game); + return NULL; + } +} + +static uint64_t _pumpnet_lib_generate_tracing_id() +{ + return util_rand_gen_64(); +} + +static bool _pumpnet_lib_get_put( + uint64_t trace_id, + const char* endpoint, + void* req_buffer, + size_t req_size, + void* resp_buffer, + size_t resp_size, + bool is_post) +{ + uint32_t http_code; + bool success; + + http_code = 0; + success = false; + + for (int i = 0; i < RETRY_COUNT_ON_ERROR; i++) { + success = pumpnet_lib_http_get_put( + trace_id, + endpoint, + req_buffer, + req_size, + resp_buffer, + resp_size, + &http_code, + is_post); + + if (http_code == 0 || http_code == HTTP_CODE_PRECONDITION_FAILED) { + log_warn("[%llX][%s][%s] Failed, http code %d, retrying (%d)...", + trace_id, + is_post ? "post" : "get", + endpoint, + http_code, + i); + + util_time_sleep_ms(RETRY_TIME_BACKOFF_MS); + } else { + break; + } + } + + if (!success) { + log_error("[%llX][%s][%s] Failed", trace_id, is_post ? "post" : "get", endpoint); + + return false; + } else if (http_code != 200) { + log_error("[%llX][%s][%s] Get non successful: %d", trace_id, is_post ? "post" : "get", endpoint, http_code); + return false; + } + + return true; +} + +static bool _pumpnet_lib_get_rank(uint64_t player_ref_id, void* buffer, size_t size) +{ + log_assert(buffer); + + struct pumpnet_lib_get_rank_req req; + struct pumpnet_lib_get_rank_resp* resp; + size_t resp_size; + uint64_t trace_id; + + trace_id = _pumpnet_lib_generate_tracing_id(); + + req.trace_id = trace_id; + req.machine_id = pumpnet_lib_machine_id; + req.player_ref_id = player_ref_id; + + resp_size = sizeof(struct pumpnet_lib_get_rank_resp) + size; + resp = util_xmalloc(resp_size); + + log_info("[%llX][%llX] Get rank", trace_id, player_ref_id); + + if (!_pumpnet_lib_get_put( + trace_id, + pumpnet_lib_server_endpoint_rank, + &req, + sizeof(req), + resp, + resp_size, + false)) { + free(resp); + return false; + } + + if (resp->size > size) { + log_error("[%llX] Rank resp size greater than buffer size: %d > %d", + trace_id, + resp->size, + size); + free(resp); + return false; + } + + memcpy(buffer, resp->data, resp->size); + + free(resp); + + return true; +} + +static bool _pumpnet_lib_get_save(uint64_t player_ref_id, void* buffer, size_t size) +{ + log_assert(buffer); + + struct pumpnet_lib_get_save_req req; + struct pumpnet_lib_get_save_resp* resp; + size_t resp_size; + uint64_t trace_id; + + trace_id = _pumpnet_lib_generate_tracing_id(); + + req.trace_id = trace_id; + req.machine_id = pumpnet_lib_machine_id; + req.player_ref_id = player_ref_id; + + resp_size = sizeof(struct pumpnet_lib_get_save_resp) + size; + resp = util_xmalloc(resp_size); + + log_info("[%llX][%llX] Get save", trace_id, player_ref_id); + + if (!_pumpnet_lib_get_put( + trace_id, + pumpnet_lib_server_endpoint_save, + &req, + sizeof(req), + resp, + resp_size, + false)) { + free(resp); + return false; + } + + if (resp->size > size) { + log_error("[%llX] Save resp size greater than buffer size: %d > %d", + trace_id, + resp->size, + size); + free(resp); + return false; + } + + memcpy(buffer, resp->data, resp->size); + + free(resp); + + return true; +} + +static bool _pumpnet_lib_put_rank(uint64_t player_ref_id, const void* buffer, size_t size) +{ + log_assert(buffer); + + struct pumpnet_lib_put_rank_req* req; + struct pumpnet_lib_put_rank_resp resp; + size_t req_size; + uint64_t trace_id; + + trace_id = _pumpnet_lib_generate_tracing_id(); + + req_size = sizeof(struct pumpnet_lib_put_rank_req) + size; + req = util_xmalloc(req_size); + + req->trace_id = trace_id; + req->machine_id = pumpnet_lib_machine_id; + req->player_ref_id = player_ref_id; + req->size = size; + memcpy(req->data, buffer, size); + + log_info("[%llX][%llX] Put rank", trace_id, player_ref_id); + + if (!_pumpnet_lib_get_put( + trace_id, + pumpnet_lib_server_endpoint_rank, + req, + req_size, + &resp, + sizeof(resp), + true)) { + free(req); + return false; + } + + free(req); + + return true; +} + +static bool _pumpnet_lib_put_save(uint64_t player_ref_id, const void* buffer, size_t size) +{ + log_assert(buffer); + + struct pumpnet_lib_put_save_req* req; + struct pumpnet_lib_put_save_resp resp; + size_t req_size; + uint64_t trace_id; + + trace_id = _pumpnet_lib_generate_tracing_id(); + + req_size = sizeof(struct pumpnet_lib_put_save_req) + size; + req = util_xmalloc(req_size); + + req->trace_id = trace_id; + req->machine_id = pumpnet_lib_machine_id; + req->player_ref_id = player_ref_id; + req->size = size; + memcpy(req->data, buffer, size); + + log_info("[%llX][%llX] Put save", trace_id, player_ref_id); + + if (!_pumpnet_lib_get_put( + trace_id, + pumpnet_lib_server_endpoint_save, + req, + req_size, + &resp, + sizeof(resp), + true)) { + free(req); + return false; + } + + free(req); + + return true; +} + +void pumpnet_lib_init( + enum asset_game_version game, + const char* server_addr, + uint64_t machine_id, + const char* cert_dir_path, + bool verbose_debug_log) +{ + pumpnet_lib_http_init(cert_dir_path, verbose_debug_log); + + pumpnet_lib_game = game; + pumpnet_lib_server_addr = util_str_dup(server_addr); + pumpnet_lib_machine_id = machine_id; + + pumpnet_lib_server_endpoint_save = _pumpnet_lib_get_endpoint_save(game, server_addr); + pumpnet_lib_server_endpoint_rank = _pumpnet_lib_get_endpoint_rank(game, server_addr); + + log_info("Initialized pumpnet for game %d, serveraddr %s, main endpoint version %s", + pumpnet_lib_game, + pumpnet_lib_server_addr, + USBPROFILE_ENDPOINT); +} + +void pumpnet_lib_shutdown() +{ + free(pumpnet_lib_server_endpoint_save); + free(pumpnet_lib_server_endpoint_rank); + + pumpnet_lib_http_shutdown(); + + log_info("Shut down"); +} + +bool pumpnet_lib_get(enum pumpnet_lib_file_type file_type, uint64_t player_ref_id, void* buffer, size_t size) +{ + switch (file_type) { + case PUMPNET_LIB_FILE_TYPE_SAVE: + return _pumpnet_lib_get_save(player_ref_id, buffer, size); + + case PUMPNET_LIB_FILE_TYPE_RANK: + return _pumpnet_lib_get_rank(player_ref_id, buffer, size); + + case PUMPNET_LIB_FILE_TYPE_COUNT: + default: + log_die_illegal_state(); + return false; + } +} + +bool pumpnet_lib_put(enum pumpnet_lib_file_type file_type, uint64_t player_ref_id, const void* buffer, size_t size) +{ + switch (file_type) { + case PUMPNET_LIB_FILE_TYPE_SAVE: + return _pumpnet_lib_put_save(player_ref_id, buffer, size); + + case PUMPNET_LIB_FILE_TYPE_RANK: + return _pumpnet_lib_put_rank(player_ref_id, buffer, size); + + case PUMPNET_LIB_FILE_TYPE_COUNT: + default: + log_die_illegal_state(); + return false; + } +} + diff --git a/src/main/pumpnet/lib/pumpnet.h b/src/main/pumpnet/lib/pumpnet.h new file mode 100644 index 0000000..1494e21 --- /dev/null +++ b/src/main/pumpnet/lib/pumpnet.h @@ -0,0 +1,33 @@ +// +// Created by on 9/16/18. +// + +#ifndef PUMPNET_LIB_PUMPNET_H +#define PUMPNET_LIB_PUMPNET_H + +#include +#include +#include + +#include "asset/game-version.h" + +enum pumpnet_lib_file_type { + PUMPNET_LIB_FILE_TYPE_SAVE = 0, + PUMPNET_LIB_FILE_TYPE_RANK = 1, + PUMPNET_LIB_FILE_TYPE_COUNT = 2, +}; + +void pumpnet_lib_init( + enum asset_game_version game, + const char* server_addr, + uint64_t machine_id, + const char* cert_dir_path, + bool verbose_debug_log); + +void pumpnet_lib_shutdown(); + +bool pumpnet_lib_get(enum pumpnet_lib_file_type file_type, uint64_t player_ref_id, void* buffer, size_t size); + +bool pumpnet_lib_put(enum pumpnet_lib_file_type file_type, uint64_t player_ref_id, const void* buffer, size_t size); + +#endif diff --git a/src/main/sec/hasp/lib/asn1.c b/src/main/sec/hasp/lib/asn1.c new file mode 100644 index 0000000..8d0f4f5 --- /dev/null +++ b/src/main/sec/hasp/lib/asn1.c @@ -0,0 +1,518 @@ +#include +#include + +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +#include "asn1.h" + +static uint32_t sec_hasp_asn1_decode_int(const uint8_t* buffer, size_t* offset, uint32_t blen); +static uint8_t* sec_hasp_asn1_decode_data(const uint8_t* buffer, size_t* offset, uint32_t blen); +static char* sec_hasp_asn1_decode_str(const uint8_t* buffer, size_t* offset, uint32_t blen); +static uint32_t sec_hasp_asn1_decode_length(const uint8_t* buffer, size_t* offset); +static uint16_t sec_hasp_asn1_decode_tag(const uint8_t* buffer, size_t* offset); +static void sec_hasp_asn1_encode_int(uint32_t val, uint8_t* buffer, size_t* offset); +static void sec_hasp_asn1_encode_data(const uint8_t* data, size_t len, uint8_t* buffer, size_t* offset); +static void sec_hasp_asn1_encode_str(const char* str, uint8_t* buffer, size_t* offset); +static void sec_hasp_asn1_encode_length(uint32_t len, uint8_t* buffer, size_t* offset); +static void sec_hasp_asn1_encode_tag(uint16_t tag, uint8_t* buffer, size_t* offset); + +struct sec_hasp_asn1_store* sec_hasp_asn1_object_store_alloc(uint16_t oid, uint32_t size) +{ + struct sec_hasp_asn1_store* tmp = malloc(sizeof(struct sec_hasp_asn1_store)); + + tmp->objects = malloc(sizeof(struct sec_hasp_asn1_object) * size); + tmp->capacity = size; + tmp->oid = oid; + tmp->pos = 0; + + memset(tmp->objects, 0, sizeof(struct sec_hasp_asn1_object) * size); + + return tmp; +} + +void sec_hasp_asn1_store_free(struct sec_hasp_asn1_store* store) +{ + for (size_t i = 0; i < store->pos; i++) { + if (store->objects[i].type == SEC_HASP_ASN1_VT_STR) { + free(store->objects[i].str); + } else if (store->objects[i].type == SEC_HASP_ASN1_VT_DATA) { + free(store->objects[i].data); + } + } + + free(store); +} + +void sec_hasp_asn1_object_add_int(struct sec_hasp_asn1_store* store, uint16_t tag, uint32_t value) +{ + if (store->pos == store->capacity) { + return; + } + + store->objects[store->pos].tag = tag; + store->objects[store->pos].type = SEC_HASP_ASN1_VT_INT; + + /* Determine length, considers padding for encoding later as well */ + // TODO not sure if this is correct, ask mario + if (value <= 0xFF) { + store->objects[store->pos].blen = 1; + } else if (value <= 0xFFFF) { + store->objects[store->pos].blen = 2; + } else if (value <= 0xFFFFFF) { + store->objects[store->pos].blen = 3; + } else if (value <= 0x7FFFFFFF) { + store->objects[store->pos].blen = 4; + } else { + store->objects[store->pos].blen = 5; + } + + store->objects[store->pos].int_val = value; + store->pos++; +} + +void sec_hasp_asn1_object_add_str(struct sec_hasp_asn1_store* store, uint16_t tag, const char* str) +{ + if (store->pos == store->capacity) { + return; + } + + store->objects[store->pos].tag = tag; + store->objects[store->pos].type = SEC_HASP_ASN1_VT_STR; + + /* Store len + null terminator */ + store->objects[store->pos].blen = strlen(str) + 1; + store->objects[store->pos].str = util_str_dup(str); + store->pos++; +} + +void sec_hasp_asn1_object_add_time(struct sec_hasp_asn1_store* store, uint16_t tag, uint32_t time) +{ + if (store->pos == store->capacity) { + return; + } + + store->objects[store->pos].tag = tag; + store->objects[store->pos].type = SEC_HASP_ASN1_VT_TIME; + + store->objects[store->pos].blen = 5; + store->objects[store->pos].time = time; + store->pos++; +} + +void sec_hasp_asn1_object_add_data(struct sec_hasp_asn1_store* store, uint16_t tag, const uint8_t* data, + size_t len) +{ + if (store->pos == store->capacity) { + return; + } + + store->objects[store->pos].tag = tag; + store->objects[store->pos].type = SEC_HASP_ASN1_VT_DATA; + + /* Store len + null terminator */ + store->objects[store->pos].blen = len; + store->objects[store->pos].data = util_xmalloc(len); + memcpy(store->objects[store->pos].data, data, len); + + store->pos++; +} + +void sec_hasp_asn1_object_set_int(struct sec_hasp_asn1_store* store, uint16_t tag) +{ + store->objects[store->pos].tag = tag; + store->objects[store->pos].type = SEC_HASP_ASN1_VT_INT; + store->pos++; +} + +void sec_hasp_asn1_object_set_str(struct sec_hasp_asn1_store* store, uint16_t tag) +{ + store->objects[store->pos].tag = tag; + store->objects[store->pos].type = SEC_HASP_ASN1_VT_STR; + store->pos++; +} + +void sec_hasp_asn1_object_set_time(struct sec_hasp_asn1_store* store, uint16_t tag) +{ + store->objects[store->pos].tag = tag; + store->objects[store->pos].type = SEC_HASP_ASN1_VT_TIME; + store->pos++; +} + +void sec_hasp_asn1_object_set_data(struct sec_hasp_asn1_store* store, uint16_t tag) +{ + store->objects[store->pos].tag = tag; + store->objects[store->pos].type = SEC_HASP_ASN1_VT_DATA; + store->pos++; +} + +bool sec_hasp_asn1_object_get_int(struct sec_hasp_asn1_store* store, uint16_t tag, uint32_t* ret_value) +{ + for (size_t i = 0; i < store->pos; i++) { + if (store->objects[i].tag == tag) { + if (store->objects[i].type != SEC_HASP_ASN1_VT_INT) { + log_error("Type of tag %X is not int", tag); + return false; + } else { + *ret_value = store->objects[i].int_val; + return true; + } + } + } + + return false; +} + +bool sec_hasp_asn1_object_get_str(struct sec_hasp_asn1_store* store, uint16_t tag, char** ret_value) +{ + for (size_t i = 0; i < store->pos; i++) { + if (store->objects[i].tag == tag) { + if (store->objects[i].type != SEC_HASP_ASN1_VT_STR) { + log_error("Type of tag %X is not str", tag); + return false; + } else { + *ret_value = util_str_dup(store->objects[i].str); + return true; + } + } + } + + return false; +} + +bool sec_hasp_asn1_object_get_time(struct sec_hasp_asn1_store* store, uint16_t tag, uint32_t* ret_value) +{ + for (size_t i = 0; i < store->pos; i++) { + if (store->objects[i].tag == tag) { + if (store->objects[i].type != SEC_HASP_ASN1_VT_TIME) { + log_error("Type of tag %X is not time", tag); + return false; + } else { + *ret_value = store->objects[i].int_val; + return true; + } + } + } + + return false; +} + +bool sec_hasp_asn1_object_get_data(struct sec_hasp_asn1_store* store, uint16_t tag, uint8_t** ret_data, + size_t* ret_len) +{ + for (size_t i = 0; i < store->pos; i++) { + if (store->objects[i].tag == tag) { + if (store->objects[i].type != SEC_HASP_ASN1_VT_DATA) { + log_error("Type of tag %X is not str", tag); + return false; + } else { + *ret_len = store->objects[i].blen; + *ret_data = util_xmalloc(*ret_len); + memcpy(*ret_data, store->objects[i].data, *ret_len); + return true; + } + } + } + + return false; +} + + +size_t sec_hasp_asn1_encode(struct sec_hasp_asn1_store* store, uint8_t* buffer, size_t len) +{ + uint8_t buffer_tmp[32768]; + size_t offset = 0; + + /* we need to know the size of the payload to encode the first length field, encode all objects first */ + for (size_t i = 0; i < store->pos; i++) { + sec_hasp_asn1_encode_tag(store->objects[i].tag, buffer_tmp, &offset); + sec_hasp_asn1_encode_length(store->objects[i].blen, buffer_tmp, &offset); + + switch(store->objects[i].type) { + case SEC_HASP_ASN1_VT_INVALID: + log_error("Invalid object type"); + break; + + case SEC_HASP_ASN1_VT_TIME: + buffer_tmp[offset++] = 0x00; + sec_hasp_asn1_encode_int(store->objects[i].time, buffer_tmp, &offset); + break; + + case SEC_HASP_ASN1_VT_INT: + sec_hasp_asn1_encode_int(store->objects[i].time, buffer_tmp, &offset); + break; + + case SEC_HASP_ASN1_VT_STR: + sec_hasp_asn1_encode_str(store->objects[i].str, buffer_tmp, &offset); + break; + + case SEC_HASP_ASN1_VT_DATA: + sec_hasp_asn1_encode_data(store->objects[i].data, store->objects[i].blen, buffer_tmp, &offset); + break; + + default: + log_error("Unknown object type %d", store->objects[i].type); + break; + } + } + + size_t offset_out = 0; + + sec_hasp_asn1_encode_tag(store->oid, buffer, &offset_out); + sec_hasp_asn1_encode_length(offset, buffer, &offset_out); + + if (offset > len) { + log_error("Target buffer too small: %d < %d", len, offset + offset_out); + } else { + memcpy(&buffer[offset_out], buffer_tmp, offset); + } + + return offset + offset_out; +} + +void sec_hasp_asn1_decode(struct sec_hasp_asn1_store* store, const uint8_t* buffer, size_t len) +{ + size_t offset = 0; + + store->oid = sec_hasp_asn1_decode_tag(buffer, &offset); + sec_hasp_asn1_decode_length(buffer, &offset); + + while (offset < len) { + uint16_t tag = sec_hasp_asn1_decode_tag(buffer, &offset); + uint32_t obj_len = sec_hasp_asn1_decode_length(buffer, &offset); + + if (tag <= 0x7F || tag >= 0x9F) { + log_error("Found invalid object tag: %X", tag); + return; + } + + struct sec_hasp_asn1_object* obj = NULL; + + /* find object that matches the tag and type */ + for (size_t i = 0; i < store->pos; i++) { + if (store->objects[i].tag == tag) { + obj = &store->objects[i]; + break; + } + } + + if (!obj) { + log_error("No object in store that matches tag %X", tag); + return; + } + + obj->blen = obj_len; + + switch (obj->type) { + case SEC_HASP_ASN1_VT_INVALID: + log_error("Invalid object type specified for tag %X", obj->tag); + break; + + case SEC_HASP_ASN1_VT_TIME: + case SEC_HASP_ASN1_VT_INT: + obj->int_val = sec_hasp_asn1_decode_int(buffer, &offset, obj->blen); + break; + + case SEC_HASP_ASN1_VT_STR: + obj->str = sec_hasp_asn1_decode_str(buffer, &offset, obj->blen); + break; + + case SEC_HASP_ASN1_VT_DATA: + obj->data = sec_hasp_asn1_decode_data(buffer, &offset, obj->blen); + break; + + default: + log_error("Unknown object type for tag %X", obj->tag); + break; + } + } +} + +static uint32_t sec_hasp_asn1_decode_int(const uint8_t* buffer, size_t* offset, uint32_t blen) +{ + uint32_t data = 0; + + switch (blen) { + case 1: + data = buffer[(*offset)++]; + break; + + case 2: + data |= buffer[(*offset)++] << 8; + data |= buffer[(*offset)++]; + break; + + case 3: + data |= buffer[(*offset)++] << 16; + data |= buffer[(*offset)++] << 8; + data |= buffer[(*offset)++]; + break; + + case 4: + data |= buffer[(*offset)++] << 24; + data |= buffer[(*offset)++] << 16; + data |= buffer[(*offset)++] << 8; + data |= buffer[(*offset)++]; + break; + + case 5: + /* skip first byte? */ + (*offset)++; + data |= buffer[(*offset)++] << 24; + data |= buffer[(*offset)++] << 16; + data |= buffer[(*offset)++] << 8; + data |= buffer[(*offset)++]; + break; + + default: + // TODO invalid len? + break; + } + + return data; +} + +static uint8_t* sec_hasp_asn1_decode_data(const uint8_t* buffer, size_t* offset, uint32_t blen) +{ + uint8_t* data = util_xmalloc(sizeof(uint8_t) * blen); + + memcpy(data, buffer + *offset, blen); + (*offset) += blen; + + return data; +} + +static char* sec_hasp_asn1_decode_str(const uint8_t* buffer, size_t* offset, uint32_t blen) +{ + /* len already includes null terminator */ + char* str = util_xmalloc(sizeof(char) * blen); + + memcpy(str, buffer + *offset, blen); + (*offset) += blen; + + return str; +} + +static uint32_t sec_hasp_asn1_decode_length(const uint8_t* buffer, size_t* offset) +{ + uint32_t len; + + len = buffer[(*offset)++]; + + if (len == 0x81) { + len = buffer[(*offset)++]; + } else if (len == 0x82) { + /* Big endian */ + len = buffer[(*offset)++] << 8; + len |= buffer[(*offset)++]; + } + + return len; +} + +static uint16_t sec_hasp_asn1_decode_tag(const uint8_t* buffer, size_t* offset) +{ + uint16_t tag; + + tag = buffer[(*offset)++]; + + if (tag == 0x7F) { + /* Big endian */ + tag = tag << 8; + tag |= buffer[(*offset)++]; + } + + return tag; +} + +static void sec_hasp_asn1_encode_int(uint32_t val, uint8_t* buffer, size_t* offset) +{ + uint8_t tmp[8]; + size_t tmp_pos = 0; + + if (val <= 0xFF) { + tmp[tmp_pos++] = (uint8_t) val; + } else if (val <= 0xFFFF) { + /* Big endian */ + tmp[tmp_pos++] = (uint8_t) (val >> 8); + tmp[tmp_pos++] = (uint8_t) val; + } else if (val <= 0xFFFFFF) { + /* Big endian */ + tmp[tmp_pos++] = (uint8_t) (val >> 16); + tmp[tmp_pos++] = (uint8_t) (val >> 8); + tmp[tmp_pos++] = (uint8_t) val; + } else if (val <= 0xFFFFFFFF) { + /* Big endian */ + tmp[tmp_pos++] = (uint8_t) (val >> 24); + tmp[tmp_pos++] = (uint8_t) (val >> 16); + tmp[tmp_pos++] = (uint8_t) (val >> 8); + tmp[tmp_pos++] = (uint8_t) val; + } else if (val <= 0xFFFFFFFFFFFFFFFF) { + // TODO quad words not supported in decode? + } + + /* Deal with padding */ + if (tmp[0] > 0x7F) { + buffer[(*offset)++] = 0x00; + } + + memcpy(buffer + *offset, tmp, tmp_pos); + (*offset) += tmp_pos; +} + +static void sec_hasp_asn1_encode_data(const uint8_t* data, size_t len, uint8_t* buffer, size_t* offset) +{ + memcpy(buffer + *offset, data, len); + (*offset) += len; +} + +static void sec_hasp_asn1_encode_str(const char* str, uint8_t* buffer, size_t* offset) +{ + size_t len = strlen(str); + + memcpy(buffer + *offset, str, len); + (*offset) += len; + + /* Add null terminator */ + buffer[(*offset)++] = 0x00; +} + +static void sec_hasp_asn1_encode_length(uint32_t len, uint8_t* buffer, size_t* offset) +{ + if (len <= 0xF7) { + buffer[(*offset)++] = (uint8_t) len; + } else if (len <= 0xFF) { + buffer[(*offset)++] = 0x81; + buffer[(*offset)++] = (uint8_t) len; + } else if (len <= 0xFFFF) { + buffer[(*offset)++] = 0x82; + /* Big endian */ + buffer[(*offset)++] = (uint8_t) (len >> 8); + buffer[(*offset)++] = (uint8_t) len; + } else if (len <= 0xFFFF) { + buffer[(*offset)++] = 0x82; + /* Big endian */ + buffer[(*offset)++] = (uint8_t) (len >> 16); + buffer[(*offset)++] = (uint8_t) (len >> 8); + buffer[(*offset)++] = (uint8_t) len; + } else if (len <= 0xFFFFFFFF) { + buffer[(*offset)++] = 0x84; + /* Big endian */ + buffer[(*offset)++] = (uint8_t) (len >> 24); + buffer[(*offset)++] = (uint8_t) (len >> 16); + buffer[(*offset)++] = (uint8_t) (len >> 8); + buffer[(*offset)++] = (uint8_t) len; + } +} + +static void sec_hasp_asn1_encode_tag(uint16_t tag, uint8_t* buffer, size_t* offset) +{ + if (tag > 0xFF) { + /* Big endian */ + buffer[(*offset)++] = (uint8_t) (tag >> 8); + buffer[(*offset)++] = (uint8_t) (tag); + } else { + buffer[(*offset)++] = (uint8_t) (tag); + } +} \ No newline at end of file diff --git a/src/main/sec/hasp/lib/asn1.h b/src/main/sec/hasp/lib/asn1.h new file mode 100644 index 0000000..19120b8 --- /dev/null +++ b/src/main/sec/hasp/lib/asn1.h @@ -0,0 +1,71 @@ +#ifndef SEC_HASP_ASN1_H +#define SEC_HASP_ASN1_H + +#include +#include + +#include "util/list.h" + +enum sec_hasp_asn1_value_type { + SEC_HASP_ASN1_VT_INVALID = 0, + SEC_HASP_ASN1_VT_INT = 1, + SEC_HASP_ASN1_VT_STR = 2, + SEC_HASP_ASN1_VT_TIME = 3, + SEC_HASP_ASN1_VT_DATA = 4, +}; + +struct sec_hasp_asn1_object { + uint16_t tag; + uint32_t blen; + enum sec_hasp_asn1_value_type type; + + union { + uint32_t time; + uint32_t int_val; + uint8_t* data; + char* str; + }; +}; + +struct sec_hasp_asn1_store { + uint16_t oid; + size_t capacity; + size_t pos; + struct sec_hasp_asn1_object* objects; +}; + +struct sec_hasp_asn1_store* sec_hasp_asn1_object_store_alloc(uint16_t oid, uint32_t size); + +void sec_hasp_asn1_store_free(struct sec_hasp_asn1_store* store); + +void sec_hasp_asn1_object_add_int(struct sec_hasp_asn1_store* store, uint16_t tag, uint32_t value); + +void sec_hasp_asn1_object_add_str(struct sec_hasp_asn1_store* store, uint16_t tag, const char* str); + +void sec_hasp_asn1_object_add_time(struct sec_hasp_asn1_store* store, uint16_t tag, uint32_t time); + +void sec_hasp_asn1_object_add_data(struct sec_hasp_asn1_store* store, uint16_t tag, const uint8_t* data, + size_t len); + +void sec_hasp_asn1_object_set_int(struct sec_hasp_asn1_store* store, uint16_t tag); + +void sec_hasp_asn1_object_set_str(struct sec_hasp_asn1_store* store, uint16_t tag); + +void sec_hasp_asn1_object_set_time(struct sec_hasp_asn1_store* store, uint16_t tag); + +void sec_hasp_asn1_object_set_data(struct sec_hasp_asn1_store* store, uint16_t tag); + +bool sec_hasp_asn1_object_get_int(struct sec_hasp_asn1_store* store, uint16_t tag, uint32_t* ret_value); + +bool sec_hasp_asn1_object_get_str(struct sec_hasp_asn1_store* store, uint16_t tag, char** ret_value); + +bool sec_hasp_asn1_object_get_time(struct sec_hasp_asn1_store* store, uint16_t tag, uint32_t* ret_value); + +bool sec_hasp_asn1_object_get_data(struct sec_hasp_asn1_store* store, uint16_t tag, uint8_t** ret_data, + size_t* ret_len); + +size_t sec_hasp_asn1_encode(struct sec_hasp_asn1_store* store, uint8_t* buffer, size_t len); + +void sec_hasp_asn1_decode(struct sec_hasp_asn1_store* store, const uint8_t* buffer, size_t len); + +#endif diff --git a/src/main/sec/hasp/lib/const.h b/src/main/sec/hasp/lib/const.h new file mode 100644 index 0000000..8949476 --- /dev/null +++ b/src/main/sec/hasp/lib/const.h @@ -0,0 +1,80 @@ +#ifndef SEC_HASP_CONST_H +#define SEC_HASP_CONST_H + +#define SEC_HAS_CONST_HEADER_SZ 24 + +#define SEC_HAS_CONST_FILEID_RW = 0xfff4 +#define SEC_HAS_CONST_FILEID_RO = 0xfff5 + +#define SEC_HAS_CONST_SCOPE_LM "" +#define SEC_HAS_CONST_SCOPE_HANDLE "" + +#define SEC_HAS_CONST_FORMAT_GETID "" +#define SEC_HAS_CONST_FORMAT_GETSESSION "" +#define SEC_HAS_CONST_FORMAT_GETKEYINFO "" + +#define SEC_HAS_CONST_SPEC_FEATURE_ID "" + +enum sec_hasp_const_status { + SEC_HASP_CONST_STATUS_OK = 0, + SEC_HASP_CONST_STATUS_MEM_RANGE = 1, + SEC_HASP_CONST_STATUS_INV_FILEID = 10, + SEC_HASP_CONST_STATUS_NO_TIME = 12, + SEC_HASP_CONST_STATUS_INV_VCODE = 22, + SEC_HASP_CONST_STATUS_INV_HND = 9, + SEC_HASP_CONST_STATUS_TOO_SHORT = 8, + SEC_HASP_CONST_STATUS_DEVICE_ERR = 43, + SEC_HASP_CONST_STATUS_SCHAN_ERR = 46, + SEC_HASP_CONST_STATUS_BROKEN_SESSION = 0x7F000027 +}; + +enum sec_hasp_const_operation_id { + /* Not Used - Admin or Get LMS Version? */ + SEC_HASP_CONST_OP_ID_ADMIN = 0, + /* Not Used */ + SEC_HASP_CONST_OP_ID_ECHO = 1, + SEC_HASP_CONST_OP_ID_LOGIN = 0x2711, + SEC_HASP_CONST_OP_ID_LOGOUT = 0x2712, + SEC_HASP_CONST_OP_ID_LOGINSCOPE = 0x2713, + SEC_HASP_CONST_OP_ID_GETINFO = 0x2714, + SEC_HASP_CONST_OP_ID_PARSE_SCOPE = 0x2715, + SEC_HASP_CONST_OP_ID_SETUPSCHANNEL = 0x2716, + SEC_HASP_CONST_OP_ID_ENCRYPT = 0x2724, + SEC_HASP_CONST_OP_ID_DECRYPT = 0x2725, + SEC_HASP_CONST_OP_ID_READ = 0x271A, + SEC_HASP_CONST_OP_ID_WRITE = 0x271B, + SEC_HASP_CONST_OP_ID_GETSIZE = 0x271C, + SEC_HASP_CONST_OP_ID_GETRTC = 0x271D, + SEC_HASP_CONST_OP_ID_GETAPIUID = 0x2774, + /* Not Used */ + SEC_HASP_CONST_OP_ID_UPDATE = 0x2775, + /* Not Used */ + SEC_HASP_CONST_OP_ID_DETACH = 0x2779, +}; + +enum sec_hasp_const_operational_object_id { + SEC_HASP_CONST_OID_CLIENTID_REQ = 0x7F34, + SEC_HASP_CONST_OID_CLIENTID_RESP =0x7F35, + SEC_HASP_CONST_OID_LOGIN_REQ = 0x61, + SEC_HASP_CONST_OID_LOGIN_REP = 0x62, + SEC_HASP_CONST_OID_LOGINSCOPE_REQ = 0x63, + SEC_HASP_CONST_OID_LOGINSCOPE_REP = 0x64, + SEC_HASP_CONST_OID_LOGOUT_REQ = 0x65, + SEC_HASP_CONST_OID_LOGOUT_REP = 0x66, + SEC_HASP_CONST_OID_INFO_REQ = 0x67, + SEC_HASP_CONST_OID_INFO_REP = 0x68, + SEC_HASP_CONST_OID_READ_REQ = 0x6A, + SEC_HASP_CONST_OID_READ_REP = 0x6B, + SEC_HASP_CONST_OID_WRITE_REQ = 0x6C, + SEC_HASP_CONST_OID_WRITE_REP = 0x6D, + SEC_HASP_CONST_OID_GETSIZE_REQ = 0x6E, + SEC_HASP_CONST_OID_GETSIZE_REP = 0x6F, + SEC_HASP_CONST_OID_GETRTC_REQ = 0x70, + SEC_HASP_CONST_OID_GETRTC_REP = 0x71, + SEC_HASP_CONST_OID_SCHANNEL_REQ = 0x72, + SEC_HASP_CONST_OID_SCHANNEL_REP = 0x73, + SEC_HASP_CONST_OID_CRYPT_REQ = 0x77, + SEC_HASP_CONST_OID_CRYPT_REP = 0x78, +}; + +#endif diff --git a/src/main/sec/hasp/lib/handler.c b/src/main/sec/hasp/lib/handler.c new file mode 100644 index 0000000..9c471ac --- /dev/null +++ b/src/main/sec/hasp/lib/handler.c @@ -0,0 +1,118 @@ +#define LOG_MODULE "sec-hasp-handler" + +#include + +#include "handler.h" + +#include "util/log.h" + +enum sec_hasp_handler_packet_type { + SEC_HASP_HANDLER_PT_APIUID = 0x2774, + SEC_HASP_HANDLER_PT_LOGIN = 0x2711, + SEC_HASP_HANDLER_PT_LOGOUT = 0x2712, + SEC_HASP_HANDLER_PT_GETINFO = 0x2714, + SEC_HASP_HANDLER_PT_SCHANNEL = 0x2716, + SEC_HASP_HANDLER_PT_ENCRYPT = 0x2724, + SEC_HASP_HANDLER_PT_DECRYPT = 0x2725, + SEC_HASP_HANDLER_PT_READ = 0x271A, + SEC_HASP_HANDLER_PT_WRITE = 0x271B, +}; + +void sec_hasp_handler_init(const uint8_t* key_data, size_t len) +{ + +} + +struct sec_hasp_handler_transaction* sec_hasp_handler_alloc_transaction(size_t max_payload_size) +{ + struct sec_hasp_handler_transaction* trans; + + trans = malloc(sizeof(struct sec_hasp_handler_transaction)); + trans->req_resp_max_size = sizeof(struct sec_hasp_handler_header) + max_payload_size; + trans->req = malloc(trans->req_resp_max_size); + trans->resp = malloc(trans->req_resp_max_size); + + return trans; +} + +void sec_hasp_handler_free_transaction(struct sec_hasp_handler_transaction* transaction) +{ + free(transaction->req); + free(transaction->resp); + free(transaction); +} + +void sec_hasp_handler_do_transaction(struct sec_hasp_handler_transaction* transaction) +{ + log_debug("Req: packet_size %d, head_value 0x%X, transaction_num %d, session_id 0x%X, packet_type 0x%X, " + "unknown 0x%X, tail_value 0x%X", transaction->req->header.packet_size, transaction->req->header.head_value, + transaction->req->header.transaction_num, transaction->req->header.session_id, + transaction->req->header.packet_type, transaction->req->header.tail_value); + + switch (transaction->req->header.packet_type) { + case SEC_HASP_HANDLER_PT_APIUID: + log_debug("SEC_HASP_HANDLER_PT_APIUID"); + break; + + case SEC_HASP_HANDLER_PT_LOGIN: + log_debug("SEC_HASP_HANDLER_PT_LOGIN"); + break; + + case SEC_HASP_HANDLER_PT_LOGOUT: + log_debug("SEC_HASP_HANDLER_PT_LOGOUT"); + break; + + case SEC_HASP_HANDLER_PT_GETINFO: + log_debug("SEC_HASP_HANDLER_PT_GETINFO"); + break; + + case SEC_HASP_HANDLER_PT_SCHANNEL: + log_debug("SEC_HASP_HANDLER_PT_SCHANNEL"); + break; + + case SEC_HASP_HANDLER_PT_ENCRYPT: + log_debug("SEC_HASP_HANDLER_PT_ENCRYPT"); + break; + + case SEC_HASP_HANDLER_PT_DECRYPT: + log_debug("SEC_HASP_HANDLER_PT_DECRYPT"); + break; + + case SEC_HASP_HANDLER_PT_READ: + log_debug("SEC_HASP_HANDLER_PT_READ"); + break; + + case SEC_HASP_HANDLER_PT_WRITE: + log_debug("SEC_HASP_HANDLER_PT_WRITE"); + break; + + default: + log_error("Invalid packet type: %X", transaction->req->header.packet_type); + memset(&transaction->req->header, 0, sizeof(struct sec_hasp_handler_header)); + break; + } +} + +//static void sec_hasp_handler_get_client_id(struct sec_hasp_handler_transaction* transaction) +//{ +// transaction->resp->header.packet_size +// transaction->resp->header.head_value +// transaction->resp->header.transaction_num +// transaction->resp->header.session_id +// transaction->resp->header.packet_type +// transaction->resp->header.tail_value +// +// rp = request_packet.payload_object +// +// client_id = HaspUtils.make_fake_handle_value() +// client_id_response = HaspObject.HO_Client_ID_Response() +// client_id_response.populate(HaspConst.HASP_STATUS_OK,client_id) +// +// self.client_db[client_id] = { +// "hasp_serial":0, +// "sessions":{} +// } +// +// return client_id_response +//} + diff --git a/src/main/sec/hasp/lib/handler.h b/src/main/sec/hasp/lib/handler.h new file mode 100644 index 0000000..27b27fe --- /dev/null +++ b/src/main/sec/hasp/lib/handler.h @@ -0,0 +1,41 @@ +#ifndef SEC_HASP_HANDLER_H +#define SEC_HASP_HANDLER_H + +#include +#include + +struct sec_hasp_handler_header { + uint32_t packet_size; + uint16_t head_value; + uint32_t transaction_num; + uint32_t session_id; + uint16_t unknown; + uint16_t packet_type; + uint32_t tail_value; +} __attribute((__packed__)); + +struct sec_hasp_handler_req { + struct sec_hasp_handler_header header; + uint8_t payload[]; +}; + +struct sec_hasp_handler_resp { + struct sec_hasp_handler_header header; + uint8_t payload[]; +}; + +struct sec_hasp_handler_transaction { + size_t req_resp_max_size; + struct sec_hasp_handler_req* req; + struct sec_hasp_handler_resp* resp; +}; + +void sec_hasp_handler_init(const uint8_t* key_data, size_t len); + +struct sec_hasp_handler_transaction* sec_hasp_handler_alloc_transaction(size_t max_payload_size); + +void sec_hasp_handler_free_transaction(struct sec_hasp_handler_transaction* transaction); + +void sec_hasp_handler_do_transaction(struct sec_hasp_handler_transaction* transaction); + +#endif diff --git a/src/main/sec/hasp/lib/object.c b/src/main/sec/hasp/lib/object.c new file mode 100644 index 0000000..8a175c6 --- /dev/null +++ b/src/main/sec/hasp/lib/object.c @@ -0,0 +1,4 @@ +// +// Created by on 6/2/18. +// + diff --git a/src/main/sec/hasp/lib/object.h b/src/main/sec/hasp/lib/object.h new file mode 100644 index 0000000..9985258 --- /dev/null +++ b/src/main/sec/hasp/lib/object.h @@ -0,0 +1,6 @@ +#ifndef SEC_HASP_OBJECT_H +#define SEC_HASP_OBJECT_H + + + +#endif diff --git a/src/main/sec/hasp/lib/object/client-id-req.c b/src/main/sec/hasp/lib/object/client-id-req.c new file mode 100644 index 0000000..5c731f6 --- /dev/null +++ b/src/main/sec/hasp/lib/object/client-id-req.c @@ -0,0 +1,61 @@ +#include + +#include "sec/hasp/lib/asn1.h" +#include "sec/hasp/lib/const.h" + +#include "util/mem.h" + +#include "client-id-req.h" + +struct sec_hasp_object_client_id_req* sec_hasp_object_client_id_req_alloc() +{ + struct sec_hasp_object_client_id_req* obj = util_xmalloc(sizeof(struct sec_hasp_object_client_id_req)); + + obj->oid = SEC_HASP_CONST_OID_CLIENTID_REQ; + obj->val_80 = 0; + obj->api_version_major = 0; + obj->api_version_minor = 0; + obj->timestamp = 0; + + return obj; +} + +void sec_hasp_object_client_id_req_free(struct sec_hasp_object_client_id_req* obj) +{ + free(obj); +} + +size_t sec_hasp_object_client_id_req_encode(struct sec_hasp_object_client_id_req* obj, uint8_t* buffer, + size_t len) +{ + struct sec_hasp_asn1_store* store = sec_hasp_asn1_object_store_alloc(obj->oid, 4); + + /* populate right before encoding */ + obj->timestamp = (uint32_t) time(NULL); + + sec_hasp_asn1_object_add_int(store, 0x80, obj->val_80); + sec_hasp_asn1_object_add_int(store, 0x81, obj->api_version_major); + sec_hasp_asn1_object_add_int(store, 0x82, obj->api_version_minor); + sec_hasp_asn1_object_add_time(store, 0x84, obj->timestamp); + + return sec_hasp_asn1_encode(store, buffer, len); +} + +void sec_hasp_object_client_id_req_decode(uint8_t* buffer, size_t len, struct sec_hasp_object_client_id_req* obj) +{ + struct sec_hasp_asn1_store* store = sec_hasp_asn1_object_store_alloc(obj->oid, 4); + + sec_hasp_asn1_object_set_int(store, 0x80); + sec_hasp_asn1_object_set_int(store, 0x81); + sec_hasp_asn1_object_set_int(store, 0x82); + sec_hasp_asn1_object_set_time(store, 0x84); + + sec_hasp_asn1_decode(store, buffer, len); + + obj->oid = store->oid; + + sec_hasp_asn1_object_get_int(store, 0x80, &obj->val_80); + sec_hasp_asn1_object_get_int(store, 0x81, &obj->api_version_major); + sec_hasp_asn1_object_get_int(store, 0x82, &obj->api_version_minor); + sec_hasp_asn1_object_get_time(store, 0x84, &obj->timestamp); +} \ No newline at end of file diff --git a/src/main/sec/hasp/lib/object/client-id-req.h b/src/main/sec/hasp/lib/object/client-id-req.h new file mode 100644 index 0000000..11081cb --- /dev/null +++ b/src/main/sec/hasp/lib/object/client-id-req.h @@ -0,0 +1,24 @@ +#ifndef SEC_HASP_OBJECT_CLIENT_ID_REQ_H +#define SEC_HASP_OBJECT_CLIENT_ID_REQ_H + +#include +#include + +struct sec_hasp_object_client_id_req { + uint16_t oid; + uint32_t val_80; + uint32_t api_version_major; + uint32_t api_version_minor; + uint32_t timestamp; +}; + +struct sec_hasp_object_client_id_req* sec_hasp_object_client_id_req_alloc(); + +void sec_hasp_object_client_id_req_free(struct sec_hasp_object_client_id_req* obj); + +size_t sec_hasp_object_client_id_req_encode(struct sec_hasp_object_client_id_req* obj, uint8_t* buffer, + size_t len); + +void sec_hasp_object_client_id_req_decode(uint8_t* buffer, size_t len, struct sec_hasp_object_client_id_req* obj); + +#endif diff --git a/src/main/sec/hasp/lib/object/client-id-resp.c b/src/main/sec/hasp/lib/object/client-id-resp.c new file mode 100644 index 0000000..a8df9de --- /dev/null +++ b/src/main/sec/hasp/lib/object/client-id-resp.c @@ -0,0 +1,48 @@ +#include "sec/hasp/lib/asn1.h" +#include "sec/hasp/lib/const.h" + +#include "util/mem.h" + +#include "client-id-resp.h" + +struct sec_hasp_object_client_id_resp* sec_hasp_object_client_id_resp_alloc() +{ + struct sec_hasp_object_client_id_resp* obj = util_xmalloc(sizeof(struct sec_hasp_object_client_id_resp)); + + obj->oid = SEC_HASP_CONST_OID_CLIENTID_RESP; + obj->status = 0; + obj->client_id = 0; + + return obj; +} + +void sec_hasp_object_client_id_resp_free(struct sec_hasp_object_client_id_resp* obj) +{ + free(obj); +} + +size_t sec_hasp_object_client_id_resp_encode(struct sec_hasp_object_client_id_resp* obj, uint8_t* buffer, + size_t len) +{ + struct sec_hasp_asn1_store* store = sec_hasp_asn1_object_store_alloc(obj->oid, 4); + + sec_hasp_asn1_object_add_int(store, 0x80, obj->status); + sec_hasp_asn1_object_add_int(store, 0x81, obj->client_id); + + return sec_hasp_asn1_encode(store, buffer, len); +} + +void sec_hasp_object_client_id_resp_decode(uint8_t* buffer, size_t len, struct sec_hasp_object_client_id_resp* obj) +{ + struct sec_hasp_asn1_store* store = sec_hasp_asn1_object_store_alloc(obj->oid, 2); + + sec_hasp_asn1_object_set_int(store, 0x80); + sec_hasp_asn1_object_set_int(store, 0x81); + + sec_hasp_asn1_decode(store, buffer, len); + + obj->oid = store->oid; + + sec_hasp_asn1_object_get_int(store, 0x80, &obj->status); + sec_hasp_asn1_object_get_int(store, 0x81, &obj->client_id); +} \ No newline at end of file diff --git a/src/main/sec/hasp/lib/object/client-id-resp.h b/src/main/sec/hasp/lib/object/client-id-resp.h new file mode 100644 index 0000000..e4d51a7 --- /dev/null +++ b/src/main/sec/hasp/lib/object/client-id-resp.h @@ -0,0 +1,22 @@ +#ifndef SEC_HASP_OBJECT_CLIENT_ID_RESP_H +#define SEC_HASP_OBJECT_CLIENT_ID_RESP_H + +#include +#include + +struct sec_hasp_object_client_id_resp { + uint16_t oid; + uint32_t status; + uint32_t client_id; +}; + +struct sec_hasp_object_client_id_resp* sec_hasp_object_client_id_resp_alloc(); + +void sec_hasp_object_client_id_resp_free(struct sec_hasp_object_client_id_resp* obj); + +size_t sec_hasp_object_client_id_resp_encode(struct sec_hasp_object_client_id_resp* obj, uint8_t* buffer, + size_t len); + +void sec_hasp_object_client_id_resp_decode(uint8_t* buffer, size_t len, struct sec_hasp_object_client_id_resp* obj); + +#endif diff --git a/src/main/sec/hasp/lib/object/login-req.c b/src/main/sec/hasp/lib/object/login-req.c new file mode 100644 index 0000000..1f928f3 --- /dev/null +++ b/src/main/sec/hasp/lib/object/login-req.c @@ -0,0 +1,139 @@ +#include + +#include "sec/hasp/main.h" +#include "sec/hasp/const.h" + +#include "util/mem.h" + +#include "login-req.h" + +struct sec_hasp_object_login_req* sec_hasp_object_login_req_init() +{ + struct sec_hasp_object_login_req* obj = util_xmalloc(sizeof(struct sec_hasp_object_login_req)); + + obj->oid = SEC_HASP_CONST_OID_LOGIN_REQ; + obj->pid = 0; + obj->tid = 0; + obj->hasp_uid = 0; + obj->vendor_id = 0; + obj->feature_id = 0; + obj->username = NULL; + obj->machine_name = NULL; + obj->login_type = NULL; + obj->api_version_major = 0; + obj->api_version_minor = 0; + obj->timestamp = 0; + obj->val_8b = 0; + obj->val_8c = 0; + obj->val_8d = 0; + obj->val_8e = 0; + obj->volume_serial = 0; + obj->val_90 = 0; + obj->hasp_handle = 0; + + return obj; +} + +void sec_hasp_object_login_req_free(struct sec_hasp_object_login_req* obj) +{ + if (obj->username) { + free(obj->username); + } + + if (obj->machine_name) { + free(obj->machine_name); + } + + if (obj->login_type) { + free(obj->login_type); + } +} + +size_t sec_hasp_object_login_req_encode(struct sec_hasp_object_login_req* obj, uint8_t* buffer, + size_t len) +{ + struct sec_hasp_asn1_store* store = sec_hasp_asn1_object_store_alloc(obj->oid, 18); + + /* populate right before encoding */ + obj->timestamp = (uint32_t) time(NULL); + + sec_hasp_asn1_object_add_int(store, 0x80, obj->pid); + sec_hasp_asn1_object_add_int(store, 0x81, obj->tid); + sec_hasp_asn1_object_add_int(store, 0x82, obj->hasp_uid); + sec_hasp_asn1_object_add_int(store, 0x83, obj->vendor_id); + sec_hasp_asn1_object_add_int(store, 0x84, obj->feature_id); + + sec_hasp_asn1_object_add_str(store, 0x85, obj->username); + sec_hasp_asn1_object_add_str(store, 0x86, obj->machine_name); + sec_hasp_asn1_object_add_str(store, 0x87, obj->login_type); + + sec_hasp_asn1_object_add_int(store, 0x88, obj->api_version_major); + sec_hasp_asn1_object_add_int(store, 0x89, obj->api_version_minor); + + sec_hasp_asn1_object_add_time(store, 0x8A, obj->timestamp); + + sec_hasp_asn1_object_add_int(store, 0x8B, obj->val_8b); + sec_hasp_asn1_object_add_int(store, 0x8C, obj->val_8c); + sec_hasp_asn1_object_add_int(store, 0x8D, obj->val_8d); + sec_hasp_asn1_object_add_int(store, 0x8E, obj->val_8e); + sec_hasp_asn1_object_add_int(store, 0x8F, obj->volume_serial); + sec_hasp_asn1_object_add_int(store, 0x90, obj->val_90); + sec_hasp_asn1_object_add_int(store, 0x91, obj->hasp_handle); + + return sec_hasp_asn1_encode(store, buffer, len); +} + +void sec_hasp_object_login_req_decode(uint8_t* buffer, size_t len, struct sec_hasp_object_login_req* obj) +{ + struct sec_hasp_asn1_store* store = sec_hasp_asn1_object_store_alloc(obj->oid, 18); + + sec_hasp_asn1_object_set_int(store, 0x80); + sec_hasp_asn1_object_set_int(store, 0x81); + sec_hasp_asn1_object_set_int(store, 0x82); + sec_hasp_asn1_object_set_int(store, 0x83); + sec_hasp_asn1_object_set_int(store, 0x84); + + sec_hasp_asn1_object_set_str(store, 0x85); + sec_hasp_asn1_object_set_str(store, 0x86); + sec_hasp_asn1_object_set_str(store, 0x87); + + sec_hasp_asn1_object_set_int(store, 0x88); + sec_hasp_asn1_object_set_int(store, 0x89); + + sec_hasp_asn1_object_set_time(store, 0x8A); + + sec_hasp_asn1_object_set_int(store, 0x8B); + sec_hasp_asn1_object_set_int(store, 0x8C); + sec_hasp_asn1_object_set_int(store, 0x8D); + sec_hasp_asn1_object_set_int(store, 0x8E); + sec_hasp_asn1_object_set_int(store, 0x8F); + sec_hasp_asn1_object_set_int(store, 0x90); + sec_hasp_asn1_object_set_int(store, 0x91); + + sec_hasp_asn1_decode(store, buffer, len); + + obj->oid = store->oid; + + sec_hasp_asn1_object_get_int(store, 0x80, &obj->pid); + sec_hasp_asn1_object_get_int(store, 0x81, &obj->tid); + sec_hasp_asn1_object_get_int(store, 0x82, &obj->hasp_uid); + sec_hasp_asn1_object_get_int(store, 0x83, &obj->vendor_id); + sec_hasp_asn1_object_get_int(store, 0x84, &obj->feature_id); + + sec_hasp_asn1_object_get_str(store, 0x85, &obj->username); + sec_hasp_asn1_object_get_str(store, 0x86, &obj->machine_name); + sec_hasp_asn1_object_get_str(store, 0x87, &obj->login_type); + + sec_hasp_asn1_object_get_int(store, 0x88, &obj->api_version_major); + sec_hasp_asn1_object_get_int(store, 0x89, &obj->api_version_minor); + + sec_hasp_asn1_object_get_time(store, 0x8A, &obj->timestamp); + + sec_hasp_asn1_object_get_int(store, 0x8B, &obj->val_8b); + sec_hasp_asn1_object_get_int(store, 0x8C, &obj->val_8c); + sec_hasp_asn1_object_get_int(store, 0x8D, &obj->val_8d); + sec_hasp_asn1_object_get_int(store, 0x8E, &obj->val_8e); + sec_hasp_asn1_object_get_int(store, 0x8F, &obj->volume_serial); + sec_hasp_asn1_object_get_int(store, 0x90, &obj->val_90); + sec_hasp_asn1_object_get_int(store, 0x91, &obj->hasp_handle); +} \ No newline at end of file diff --git a/src/main/sec/hasp/lib/object/login-req.h b/src/main/sec/hasp/lib/object/login-req.h new file mode 100644 index 0000000..f9d0cf9 --- /dev/null +++ b/src/main/sec/hasp/lib/object/login-req.h @@ -0,0 +1,38 @@ +#ifndef SEC_HASP_OBJECT_LOGIN_REQ_H +#define SEC_HASP_OBJECT_LOGIN_REQ_H + +#include +#include + +struct sec_hasp_object_login_req { + uint16_t oid; + uint32_t pid; + uint32_t tid; + uint32_t hasp_uid; + uint32_t vendor_id; + uint32_t feature_id; + char* username; + char* machine_name; + char* login_type; + uint32_t api_version_major; + uint32_t api_version_minor; + uint32_t timestamp; + uint32_t val_8b; + uint32_t val_8c; + uint32_t val_8d; + uint32_t val_8e; + uint32_t volume_serial; + uint32_t val_90; + uint32_t hasp_handle; +}; + +struct sec_hasp_object_login_req* sec_hasp_object_login_req_init(); + +void sec_hasp_object_login_req_free(struct sec_hasp_object_login_req* obj); + +size_t sec_hasp_object_login_req_encode(struct sec_hasp_object_login_req* obj, uint8_t* buffer, + size_t len); + +void sec_hasp_object_login_req_decode(uint8_t* buffer, size_t len, struct sec_hasp_object_login_req* obj); + +#endif diff --git a/src/main/sec/hasp/lib/server.c b/src/main/sec/hasp/lib/server.c new file mode 100644 index 0000000..3834571 --- /dev/null +++ b/src/main/sec/hasp/lib/server.c @@ -0,0 +1,162 @@ +#define LOG_MODULE "sec-hasp-server" + +#include +#include +#include +#include +#include +#include + +#include "sec/hasp/lib/handler.h" + +#include "util/log.h" + +#include "server.h" + +#define HASP_PORT 1947 +#define HASP_MAX_BUFFER 16384 + +static bool sec_hasp_server_run_s; +static bool sec_hasp_server_is_running_s; + +void sec_hasp_server_init(const uint8_t* key_data, size_t len) +{ + sec_hasp_handler_init(key_data, len); + sec_hasp_server_run_s = true; + sec_hasp_server_is_running_s = false; +} + +void sec_hasp_server_run() +{ + int fd = -1; + struct sockaddr_in addr; + struct sockaddr_in addr_remote; + socklen_t addr_len_remote; + int fd_remote = -1; + int retval; + struct sec_hasp_handler_transaction* transaction; + ssize_t recv_len; + + log_info("Setting up socket for server"); + + sec_hasp_server_is_running_s = true; + + transaction = sec_hasp_handler_alloc_transaction(HASP_MAX_BUFFER); + + fd = socket(AF_INET, SOCK_STREAM, 0); + + if (fd == -1) { + log_error("Opening socket failed: %s", strerror(errno)); + goto return_cleanup; + } + + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + /* bind to localhost which limits accepting connections to localhost, only */ + addr.sin_addr.s_addr = htonl(0x7F000001); + addr.sin_port = htons(HASP_PORT); + + retval = bind(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)); + + if (retval == -1) { + log_error("Binding socket failed: %s", strerror(errno)); + goto return_cleanup; + } + + log_info("Running server..."); + + while (sec_hasp_server_run_s) { + /* a single connection is fine here */ + retval = listen(fd, 1); + + if (retval == -1) { + log_error("Listening for incoming connections failed: %s", strerror(errno)); + goto return_cleanup; + } + + /* got a connection */ + addr_len_remote = sizeof(addr_remote); + fd_remote = accept(fd, &addr_remote, &addr_len_remote); + + if (fd_remote == -1) { + log_error("Accepting incoming connection failed: %s", strerror(errno)); + goto return_cleanup; + } + + log_debug("Remote %X connected", addr_remote.sin_addr.s_addr); + + /* put the socket to the remote into non blocking mode */ + if (fcntl(fd_remote, F_SETFL, fcntl(fd_remote, F_GETFL) | O_NONBLOCK) < 0) { + log_error("Setting socket to non blocking failed: %s", strerror(errno)); + goto return_cleanup; + } + + while (sec_hasp_server_run_s) { + recv_len = recv(fd_remote, transaction->req, transaction->req_resp_max_size, 0); + + if (recv_len == -1) { + /* no data available, sleep to reduce cpu load and retry */ + if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* 10 ms */ + usleep(1000 * 10); + continue; + } else if (errno == ECONNRESET) { + log_debug("Remote %X disconnected", addr_remote.sin_addr.s_addr); + close(fd_remote); + break; + } else { + log_error("Reading from connection failed: %s", strerror(errno)); + goto return_cleanup; + } + } + + if (recv_len == 0) { + /* remote disconnected */ + log_debug("Remote %X disconnected", addr_remote.sin_addr.s_addr); + close(fd_remote); + break; + } + + log_debug("Received data: %d bytes", recv_len); + + sec_hasp_handler_do_transaction(transaction); + + /* send reply */ + recv_len = send(fd_remote, transaction->resp, transaction->resp->header.packet_size, 0); + + if (recv_len == -1) { + if (errno == ECONNRESET) { + log_debug("Remote %X disconnected", addr_remote.sin_addr.s_addr); + close(fd_remote); + break; + } else { + log_error("Writing to connection failed: %s", strerror(errno)); + goto return_cleanup; + } + } + } + } + +return_cleanup: + if (fd_remote != -1) { + close(fd); + } + + if (fd != -1) { + close(fd); + } + + sec_hasp_handler_free_transaction(transaction); + + sec_hasp_server_is_running_s = false; +} + +bool sec_hasp_server_is_running(void) +{ + return sec_hasp_server_is_running_s; +} + +void sec_hasp_server_shutdown(void) +{ + sec_hasp_server_run_s = false; +} \ No newline at end of file diff --git a/src/main/sec/hasp/lib/server.h b/src/main/sec/hasp/lib/server.h new file mode 100644 index 0000000..f7e9eae --- /dev/null +++ b/src/main/sec/hasp/lib/server.h @@ -0,0 +1,33 @@ +#ifndef SEC_HASP_SERVER_H +#define SEC_HASP_SERVER_H + +#include +#include +#include + +/** + * Initialize the hasp server module. This just initializes the hasp + * dongle emulator module + * + * @param key_data Pointer to loaded key data (attributes, key table) + * @param len Length of the buffer + */ +void sec_hasp_server_init(const uint8_t* key_data, size_t len); + +/** + * Run the server. This call blocks until the server is shut down. + * Recommended to run this in a separate thread. + */ +void sec_hasp_server_run(void); + +/** + * Check if the server is running + */ +bool sec_hasp_server_is_running(void); + +/** + * Signal a shut down to the server + */ +void sec_hasp_server_shutdown(void); + +#endif diff --git a/src/main/sec/hasp/lib/util.c b/src/main/sec/hasp/lib/util.c new file mode 100644 index 0000000..5f5941b --- /dev/null +++ b/src/main/sec/hasp/lib/util.c @@ -0,0 +1,15 @@ +#include + +#include "util/rand.h" + +#include "util.h" + +uint32_t sec_hasp_util_make_fake_handle_value() +{ + return util_rand_gen_32(); +} + +uint32_t sec_hasp_util_get_timestamp() +{ + return (uint32_t) time(NULL); +} \ No newline at end of file diff --git a/src/main/sec/hasp/lib/util.h b/src/main/sec/hasp/lib/util.h new file mode 100644 index 0000000..c01c4ad --- /dev/null +++ b/src/main/sec/hasp/lib/util.h @@ -0,0 +1,10 @@ +#ifndef SEC_HASP_UTIL_H +#define SEC_HASP_UTIL_H + +#include + +uint32_t sec_hasp_util_make_fake_handle_value(); + +uint32_t sec_hasp_util_get_timestamp(); + +#endif diff --git a/src/main/sec/hasp/old/hasp.c b/src/main/sec/hasp/old/hasp.c new file mode 100644 index 0000000..b365255 --- /dev/null +++ b/src/main/sec/hasp/old/hasp.c @@ -0,0 +1,87 @@ +#define LOG_MODULE "sec-hasp" + +#include + +#include "sec/hasp/old/hasp.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +struct sec_hasp_key_table { + uint32_t nkeys; + struct sec_hasp_key* keys; +}; + +/* index 0 (type): 1 = dev, 2 = retail + index 1 (language): 3001 = english */ +/* static const uint32_t sec_hasp_features[] = {2, 3001}; */ +static const uint32_t sec_hasp_key_id = 0xDEADBEEF; +static struct sec_hasp_key_table sec_hasp_keys; + +void sec_hasp_init(const uint8_t* key_data, size_t len) +{ + sec_hasp_keys.nkeys = len / sizeof(struct sec_hasp_key); + sec_hasp_keys.keys = + util_xmalloc(sizeof(struct sec_hasp_key) * sec_hasp_keys.nkeys); + + memcpy(sec_hasp_keys.keys, key_data, + sec_hasp_keys.nkeys * sizeof(struct sec_hasp_key)); + + log_info("Loaded %d keys", sec_hasp_keys.nkeys); +} + +int sec_hasp_api_login(int feature, int vendor_code, int* handle) +{ + *handle = (int) &sec_hasp_key_id; + + log_debug("login, feature %d, vendor_code %d, ret handle 0x%X", feature, + vendor_code, *handle); + + return 0; +} + +int sec_hasp_api_logout(int handle) +{ + if (handle != (int) &sec_hasp_key_id) { + log_error("Logout of unknown handle 0x%X", handle); + } else { + log_debug("Logout: 0x%X", handle); + } + + return 0; +} + +unsigned int sec_hasp_api_getid(void) +{ + return sec_hasp_key_id; +} + +int sec_hasp_api_decrypt(int handle, void* buffer, size_t length) +{ + char* buf; + + if (handle != (int) &sec_hasp_key_id) { + log_error("Unknown handle 0x%X sending decrypt request, ignored"); + return -1; + } + + for (uint32_t i = 0; i < sec_hasp_keys.nkeys; i++) { + if (!memcmp(buffer, sec_hasp_keys.keys[i].req, + sizeof(sec_hasp_keys.keys[i].req))) { + log_debug("Decrypt %u -> %u", + *((uint64_t*) sec_hasp_keys.keys[i].req), + *((uint64_t*) sec_hasp_keys.keys[i].resp)); + memcpy(buffer, sec_hasp_keys.keys[i].resp, + sizeof(sec_hasp_keys.keys[i].resp)); + return 0; + } + } + + buf = util_str_buffer(buffer, length); + + log_warn("Missing key for request %s", buf); + free(buf); + + return 0; +} \ No newline at end of file diff --git a/src/main/sec/hasp/old/hasp.h b/src/main/sec/hasp/old/hasp.h new file mode 100644 index 0000000..582ae0c --- /dev/null +++ b/src/main/sec/hasp/old/hasp.h @@ -0,0 +1,35 @@ +/** + * Hasp emulator (Fiesta 2 and up) + */ +#ifndef SEC_HASP_H +#define SEC_HASP_H + +#include +#include + +/** + * A single entry in the key table + */ +struct sec_hasp_key { + uint8_t req[64]; + uint8_t resp[64]; +} __attribute__((packed)); + +/** + * Initialize the hasp emulation module. The dongle for this + * was used on Prime and up + * + * @param key_data Pointer to loaded key data (key table) + * @param len Length of the buffer + */ +void sec_hasp_init(const uint8_t* key_data, size_t len); + +int sec_hasp_api_login(int feature, int vendor_code, int* handle); + +int sec_hasp_api_logout(int handle); + +unsigned int sec_hasp_api_getid(void); + +int sec_hasp_api_decrypt(int handle, void* buffer, size_t length); + +#endif \ No newline at end of file diff --git a/src/main/sec/hasp/server/main.c b/src/main/sec/hasp/server/main.c new file mode 100644 index 0000000..7e1ddb8 --- /dev/null +++ b/src/main/sec/hasp/server/main.c @@ -0,0 +1,7 @@ +#include "sec/hasp/lib/server.h" + +int main(int argc, char** argv) +{ + sec_hasp_server_init(NULL, 0); + sec_hasp_server_run(); +} \ No newline at end of file diff --git a/src/main/sec/lockchip/lockchip-defs.h b/src/main/sec/lockchip/lockchip-defs.h new file mode 100644 index 0000000..438537f --- /dev/null +++ b/src/main/sec/lockchip/lockchip-defs.h @@ -0,0 +1,64 @@ +#ifndef SEC_LOCKCHIP_DEFS_H +#define SEC_LOCKCHIP_DEFS_H + +#include + +/* List of games using the old type mk3 lockchip board */ +enum sec_lockchip_defs_games { + SEC_LOCK_CHIP_DEFS_GAME_3RD_SE = 0, + SEC_LOCK_CHIP_DEFS_GAME_COLLECTION = 1, + SEC_LOCK_CHIP_DEFS_GAME_PERFECT_COLLECTION = 2, + SEC_LOCK_CHIP_DEFS_GAME_EXTRA_PROTO = 3, + SEC_LOCK_CHIP_DEFS_GAME_PREMIERE = 4, + SEC_LOCK_CHIP_DEFS_GAME_PREX = 5, + SEC_LOCK_CHIP_DEFS_GAME_REBIRTH = 6, + SEC_LOCK_CHIP_DEFS_GAME_PREMIERE_2 = 7, + /* Extra Plus */ + SEC_LOCK_CHIP_DEFS_GAME_PREX_2_BETA = 8, + SEC_LOCK_CHIP_DEFS_GAME_PREX_2 = 9, + SEC_LOCK_CHIP_DEFS_GAME_PREMIERE_3 = 10, + SEC_LOCK_CHIP_DEFS_GAME_PREX_3 = 11, + SEC_LOCK_CHIP_DEFS_GAME_EXTRA = 12, + /* NXA for mk3, funbox... */ + SEC_LOCK_CHIP_DEFS_GAME_BOOTLEGS = 13, + SEC_LOCK_CHIP_DEFS_GAME_EXCEED = 14, + SEC_LOCK_CHIP_DEFS_GAME_COUNT = 15, +}; + +/** List of lockchip transforms of all pump games that used it. + */ +static const uint8_t sec_lockchip_defs_transforms + [SEC_LOCK_CHIP_DEFS_GAME_COUNT][8] = { + /* 3rd SE */ + {0xF0, 0x1C, 0xFE, 0x03, 0x81, 0x40, 0x38, 0xF8}, + /* Collection */ + {0xF0, 0x1C, 0xFE, 0x03, 0x81, 0x40, 0x38, 0xF8}, + /* Perfect Collection */ + {0xF0, 0x1C, 0xFE, 0x03, 0x81, 0x40, 0x38, 0xF8}, + /* Extra Proto */ + {0xF8, 0x82, 0x70, 0x0C, 0xFD, 0xC1, 0x20, 0xE0}, + /* Premiere */ + {0xE0, 0x78, 0x7C, 0xFD, 0xF1, 0xF2, 0xC2, 0xC0}, + /* Prex */ + {0xE0, 0x78, 0x7C, 0xFD, 0xF1, 0xF2, 0xC2, 0xC0}, + /* Rebirth */ + {0xFC, 0xF9, 0x08, 0x10, 0xE2, 0xC2, 0x40, 0x80}, + /* Premiere 2 */ + {0xFC, 0x03, 0xE1, 0x20, 0x38, 0x78, 0x70, 0xF0}, + /* Prex 2 Beta */ + {0xE0, 0x38, 0x78, 0x81, 0x03, 0xFE, 0x0C, 0xF0}, + /* Prex 2 */ + {0xE0, 0x38, 0x78, 0x81, 0x03, 0xFE, 0x0C, 0xF0}, + /* Premiere 3 */ + {0x02, 0xC1, 0xF9, 0x78, 0x7C, 0x1C, 0x10, 0xF0}, + /* Prex 3 */ + {0xF0, 0xE1, 0xF9, 0x78, 0x7C, 0xFE, 0xC2, 0xC0}, + /* Extra */ + {0xF8, 0x82, 0x70, 0x0C, 0xFD, 0xC1, 0x20, 0xE0}, + /* Bootlegs */ + {0x02, 0xC1, 0xF9, 0x78, 0x7C, 0x1C, 0x10, 0xF0}, + /* Exceed */ + {0xF0, 0x78, 0xF9, 0xFD, 0x1C, 0x20, 0xC2, 0x02}, +}; + +#endif \ No newline at end of file diff --git a/src/main/sec/lockchip/lockchip.c b/src/main/sec/lockchip/lockchip.c new file mode 100644 index 0000000..cf8e7f6 --- /dev/null +++ b/src/main/sec/lockchip/lockchip.c @@ -0,0 +1,87 @@ +#define LOG_MODULE "sec-lockchip" + +#include "sec/lockchip/lockchip.h" + +static const uint8_t sec_lockchip_initial_sbox[8] = { + 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x7f +}; + +static const uint8_t* sec_lockchip_transform; +static uint8_t sec_lockchip_state; + +static void sec_lockchip_apply_initial_sbox(void) +{ + uint8_t r = 0; + + for (uint8_t i = 0; i < 8; i++) { + + if (sec_lockchip_state & (1 << i)) { + r ^= sec_lockchip_initial_sbox[i]; + } + } + + sec_lockchip_state = r; +} + +static uint8_t sec_lockchip_comp_sbox_ceof(uint8_t sel, uint8_t bit) +{ + uint8_t r ; + + if (!sel) { + return sec_lockchip_transform[bit]; + } + + r = sec_lockchip_comp_sbox_ceof((sel - 1) & 7, (bit - 1) & 7); + r = (r << 1) | (((r >> 7) ^ (r >> 6)) & 1); + + if (bit != 7) { + return r; + } + + return r ^ sec_lockchip_comp_sbox_ceof(sel, 0); +} + +static void sec_lockchip_apply_bit_sbox(uint8_t bit) +{ + uint8_t r = 0; + + for (uint8_t i = 0; i < 8; i++) { + + if (sec_lockchip_state & (1 << i)) { + r ^= sec_lockchip_comp_sbox_ceof(bit, i); + } + } + + sec_lockchip_state = r; +} + +void sec_lockchip_init(const uint8_t* transform) +{ + sec_lockchip_transform = transform; + sec_lockchip_state = 0; +} + +void sec_lockchip_start(void) +{ + sec_lockchip_state = 0xFC; +} + +uint8_t sec_lockchip_step(uint8_t data) +{ + uint8_t result = 0; + + for (uint8_t bit = 0; bit < 8; bit++) { + + if (bit == 0) { + sec_lockchip_apply_initial_sbox(); + } + + result ^= (((sec_lockchip_state >> bit) & 1) << bit); + + if (((data >> bit) & 1) == 0) { + sec_lockchip_apply_bit_sbox(bit); + } + } + + return result; +} \ No newline at end of file diff --git a/src/main/sec/lockchip/lockchip.h b/src/main/sec/lockchip/lockchip.h new file mode 100644 index 0000000..9034763 --- /dev/null +++ b/src/main/sec/lockchip/lockchip.h @@ -0,0 +1,31 @@ +/** + * Lockchip (MK3 games) emulator + * + * Credits to TheSchaf for the emulation code and transform data + */ +#ifndef SEC_LOCKCHIP_H +#define SEC_LOCKCHIP_H + +#include + +/** + * Initialize (or reset) the lockchip emulation module + * + * @param transform Pointer to a transform to use for the step calls + */ +void sec_lockchip_init(const uint8_t* transform); + +/** + * Start lockchip emulation and set the initial state before calling step + */ +void sec_lockchip_start(void); + +/** + * Execute a step on some data + * + * @param data Input data to transform + * @return Transformed output + */ +uint8_t sec_lockchip_step(uint8_t data); + +#endif \ No newline at end of file diff --git a/src/main/sec/microdog34/microdog34.c b/src/main/sec/microdog34/microdog34.c new file mode 100644 index 0000000..a9902aa --- /dev/null +++ b/src/main/sec/microdog34/microdog34.c @@ -0,0 +1,399 @@ +#define LOG_MODULE "sec-microdog34" + +#include +#include + +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +#define IOCTL_REQUEST_MD_XACT 0x6B00 +#define GOLD_SALT 0x646C6F47 +#define MAGIC 0x484D + +enum sec_microdog34_req_opcode { + SEC_MICRODOG34_OP_DOG_CHECK = 0x01, + SEC_MICRODOG34_OP_READ_DOG = 0x02, + SEC_MICRODOG34_OP_WRITE_DOG = 0x03, + SEC_MICRODOG34_OP_CONVERT = 0x04, + SEC_MICRODOG34_OP_SET_PASS = 0x07, + SEC_MICRODOG34_OP_SET_SHARE = 0x08, + SEC_MICRODOG34_OP_GET_LOCK_NO = 0x0B, + SEC_MICRODOG34_OP_LOGIN = 0x14, + SEC_MICRODOG34_OP_DOG_CASCADE = 0x15, + SEC_MICRODOG34_OP_RAINBOW_TABLE_ADD = 0x539, + SEC_MICRODOG34_OP_DOG_SERIAL = 0x53A, + SEC_MICRODOG34_OP_SET_MFG_SERIAL = 0x53B, + SEC_MICRODOG34_OP_SET_VID = 0x53C, + SEC_MICRODOG34_OP_RELOAD_RAINBOW_TABLE = 0x53D +}; + +struct sec_microdog34_req { + uint16_t magic; + uint16_t opcode; + uint32_t dog_serial; + uint32_t mask_key; + uint16_t dog_addr; + uint16_t dog_bytes; + uint8_t dog_data[256]; + uint32_t dog_password; + uint8_t host_id; +} __attribute__((__packed__)); + +enum sec_microdog34_resp_code { + SEC_MICRODOG34_RESP_NO_ERR = 0, + SEC_MICRODOG34_RESP_ERR_PASS = 0x2745 +}; + +struct sec_microdog34_resp { + uint32_t dog_serial; + uint32_t return_code; + uint8_t dog_data[200]; +} __attribute__((__packed__)); + +struct sec_microdog34_header { + uint32_t serial; + uint32_t password; + uint8_t vendor_id[8]; + uint32_t mfg_serial; + uint8_t flash_memory[200]; + uint32_t num_keys; +}; + +struct sec_microdog34_key { + uint32_t response; + uint32_t algorithm; + uint32_t req_len; + uint8_t request[64]; +}; + +static void sec_microdog34_dispatch(struct sec_microdog34_req* req, + struct sec_microdog34_resp* resp); +static void sec_microdog34_crypt_req(struct sec_microdog34_req* req); +static void sec_microdog34_crypt_resp(struct sec_microdog34_req* req, + struct sec_microdog34_resp* resp); +static void sec_microdog34_dump_req(struct sec_microdog34_req* req); +static void sec_microdog34_dump_resq(struct sec_microdog34_resp* resp); +static uint32_t sec_microdog34_convert_req(const uint8_t* req_data, + uint16_t req_len); + +static struct sec_microdog34_header sec_microdog34_header; +static struct sec_microdog34_key* sec_microdog34_key_table; + +static uint8_t sec_microdog34_share; + +void sec_microdog34_init(const uint8_t* key_data, size_t len) +{ + memcpy(&sec_microdog34_header, key_data, + sizeof(struct sec_microdog34_header)); + + if (len - sizeof(struct sec_microdog34_header) < + sec_microdog34_header.num_keys * + sizeof(struct sec_microdog34_key)) { + log_error("Loading key table failed, incomplete key table " + "(num_keys entry: %d, num_keys size: %d", + sec_microdog34_header.num_keys, + (len - sizeof(struct sec_microdog34_header)) / + sizeof(struct sec_microdog34_key)); + return; + } + + sec_microdog34_key_table = util_xmalloc(sec_microdog34_header.num_keys * + sizeof(struct sec_microdog34_key)); + + memcpy(sec_microdog34_key_table, + key_data + sizeof(struct sec_microdog34_header), + sec_microdog34_header.num_keys * sizeof(struct sec_microdog34_key)); + + log_info("Loaded %d keys, serial 0x%X, password 0x%X", + sec_microdog34_header.num_keys, sec_microdog34_header.serial, + sec_microdog34_header.password); + + sec_microdog34_share = 0; +} + +int sec_microdog34_process(int request, void* data) +{ + if (request == IOCTL_REQUEST_MD_XACT) { + struct sec_microdog34_req req; + struct sec_microdog34_resp resp; + + memset(&resp, 0, sizeof(struct sec_microdog34_resp)); + + // where to get the data? weird stuff, but that's how it's done + // it really has to be like that, otherwise we get trash from data + uint32_t req_data_offset = *(unsigned int*) data; + memcpy(&req, (void*) req_data_offset, + sizeof(struct sec_microdog34_req)); + + sec_microdog34_crypt_req(&req); + sec_microdog34_dispatch(&req, &resp); + sec_microdog34_crypt_resp(&req, &resp); + + // again, same as req data + // it really has to be like that, otherwise we get trash from data + uint32_t resp_data_offset = *(unsigned int*) data - 272; + memcpy((void*) resp_data_offset, &resp, + sizeof(struct sec_microdog34_resp)); + + return 0; + } + + return -1; +} + +static void sec_microdog34_dispatch(struct sec_microdog34_req* req, + struct sec_microdog34_resp* resp) +{ + struct sec_microdog34_key* key; + + resp->dog_serial = req->dog_serial; + + if (req->magic != MAGIC) { + log_error("Bad magic 0x%X for request", req->magic); + } + + sec_microdog34_dump_req(req); + + switch (req->opcode) { + case SEC_MICRODOG34_OP_DOG_CHECK: + log_debug("SEC_MICRODOG34_OP_DOG_CHECK"); + break; + + case SEC_MICRODOG34_OP_READ_DOG: + log_debug("SEC_MICRODOG34_OP_READ_DOG"); + + if (req->dog_password == sec_microdog34_header.password) { + memcpy(resp->dog_data, + sec_microdog34_header.flash_memory + req->dog_addr, + req->dog_bytes); + } else { + resp->return_code = SEC_MICRODOG34_RESP_ERR_PASS; + } + + break; + + case SEC_MICRODOG34_OP_WRITE_DOG: + log_debug("SEC_MICRODOG34_OP_WRITE_DOG"); + + if (req->dog_password == sec_microdog34_header.password) { + memcpy(sec_microdog34_header.flash_memory + req->dog_addr, + req->dog_data, req->dog_bytes); + } else { + resp->return_code = SEC_MICRODOG34_RESP_ERR_PASS; + } + + break; + + case SEC_MICRODOG34_OP_CONVERT: + log_debug("SEC_MICRODOG34_OP_CONVERT"); + + uint32_t resp_data = sec_microdog34_convert_req(req->dog_data, + req->dog_bytes); + memcpy(resp->dog_data, &resp_data, sizeof(uint32_t)); + + break; + + case SEC_MICRODOG34_OP_SET_PASS: + log_debug("SEC_MICRODOG34_OP_SET_PASS: 0x%08X", + *((uint32_t*) req->dog_data)); + + if (req->dog_password == sec_microdog34_header.password) { + sec_microdog34_header.password = *((uint32_t*) req->dog_data); + } + else + resp->return_code = SEC_MICRODOG34_RESP_ERR_PASS; + + break; + + case SEC_MICRODOG34_OP_SET_SHARE: + log_debug("SEC_MICRODOG34_OP_SET_SHARE"); + + sec_microdog34_share = req->dog_data[0]; + resp->dog_data[0] = sec_microdog34_share; + + break; + + case SEC_MICRODOG34_OP_GET_LOCK_NO: + log_debug("SEC_MICRODOG34_OP_GET_LOCK_NO: 0x%08X", + sec_microdog34_header.mfg_serial); + + memcpy(resp->dog_data, &sec_microdog34_header.mfg_serial, 4); + + break; + + case SEC_MICRODOG34_OP_LOGIN: + log_debug("SEC_MICRODOG34_OP_LOGIN"); + + memcpy(resp->dog_data, sec_microdog34_header.vendor_id, + sizeof(sec_microdog34_header.vendor_id)); + + break; + + case SEC_MICRODOG34_OP_DOG_CASCADE: + log_debug("SEC_MICRODOG34_OP_DOG_CASCADE"); + + resp->dog_data[0] = req->dog_data[0]; + + break; + + case SEC_MICRODOG34_OP_RAINBOW_TABLE_ADD: + log_debug("SEC_MICRODOG34_OP_RAINBOW_TABLE_ADD"); + + /* expand table */ + sec_microdog34_key_table = util_xrealloc(sec_microdog34_key_table, + (sec_microdog34_header.num_keys + 1) * + sizeof(struct sec_microdog34_key)); + + key = &sec_microdog34_key_table[sec_microdog34_header.num_keys]; + key->response = req->dog_password; + /* algorithm(last 4 bytes of flash memory) */ + key->algorithm = + *((uint32_t*) (sec_microdog34_header.flash_memory + 196)); + key->req_len = req->dog_bytes; + memcpy(key->request, req->dog_data, sizeof(key->request)); + + sec_microdog34_header.num_keys++; + + break; + + case SEC_MICRODOG34_OP_DOG_SERIAL: + log_debug("SEC_MICRODOG34_OP_DOG_SERIAL: 0x%08X", + *((uint32_t*) req->dog_data)); + + sec_microdog34_header.serial = *((uint32_t*) req->dog_data); + + break; + + case SEC_MICRODOG34_OP_SET_MFG_SERIAL: + log_debug("SEC_MICRODOG34_OP_SET_MFG_SERIAL: 0x%08X ", + *((uint32_t*) req->dog_data)); + + sec_microdog34_header.mfg_serial = *((uint32_t*) req->dog_data); + + break; + + case SEC_MICRODOG34_OP_SET_VID: + log_debug("SEC_MICRODOG34_OP_SET_VID"); + + memcpy(sec_microdog34_header.vendor_id, req->dog_data, + sizeof(sec_microdog34_header.vendor_id)); + + break; + + case SEC_MICRODOG34_OP_RELOAD_RAINBOW_TABLE: + log_debug("SEC_MICRODOG34_OP_RELOAD_RAINBOW_TABLE"); + + /* nothing to load here */ + break; + + default: + log_warn("Unsupported opcode: %04X", req->opcode); + break; + } + + sec_microdog34_dump_resq(resp); +} + +static void sec_microdog34_crypt_req(struct sec_microdog34_req* req) +{ + uint32_t tmp_mask = (req->mask_key + GOLD_SALT) & 0xFFFFFFFF; + uint8_t tmb_mask[4] = {0, 0, 0, 0}; + memcpy(tmb_mask, &tmp_mask, 4); + + req->dog_addr ^= (tmp_mask & 0xFFFF); + req->dog_bytes ^= (tmp_mask & 0xFFFF); + req->dog_password ^= tmp_mask; + req->host_id ^= (tmp_mask & 0xFF); + + for (int i = 0 ; i < sizeof(req->dog_data); i++) { + req->dog_data[i] ^= tmb_mask[i % 4]; + } +} + +static void sec_microdog34_crypt_resp(struct sec_microdog34_req* req, + struct sec_microdog34_resp* resp) +{ + uint32_t tmp_mask = (req->mask_key + GOLD_SALT) & 0xFFFFFFFF; + uint8_t tmb_mask[4] = {0, 0, 0, 0}; + memcpy(tmb_mask, &tmp_mask, 4); + + for (int i = 0 ; i < sizeof(resp->dog_data); i++) { + resp->dog_data[i] ^= tmb_mask[i % 4]; + } +} + +static void sec_microdog34_dump_req(struct sec_microdog34_req* req) +{ + char* data_tmp = util_str_buffer(req->dog_data, sizeof(req->dog_data)); + + log_debug( + "Dog request:\n" + "magic: 0x%X\n" + "opcode: 0x%X\n" + "dog_serial: 0x%X\n" + "mask_key: 0x%X\n" + "dog_addr: 0x%X\n" + "dog_bytes: 0x%X\n" + "dog_data: %s\n" + "dog_pass: 0x%X\n" + "host_id: 0x%X", + req->magic, + req->opcode, + req->dog_serial, + req->mask_key, + req->dog_addr, + req->dog_bytes, + data_tmp, + req->dog_password, + req->host_id); + + free(data_tmp); +} + +static void sec_microdog34_dump_resq(struct sec_microdog34_resp* resp) +{ + char* data_tmp = util_str_buffer(resp->dog_data, sizeof(resp->dog_data)); + + log_debug( + "Dog response:\n" + "dog_serial: 0x%X\n" + "return_code: 0x%X\n" + "dog_data: %s\n", + resp->dog_serial, + resp->return_code, + data_tmp); + + free(data_tmp); +} + +static uint32_t sec_microdog34_convert_req(const uint8_t* req_data, + uint16_t req_len) +{ + uint32_t cur_algo; + + cur_algo = 0; + memcpy(&cur_algo, sec_microdog34_header.flash_memory + 196, 4); + + for (uint32_t i = 0; i < sec_microdog34_header.num_keys; i++) { + struct sec_microdog34_key* key = &sec_microdog34_key_table[i]; + + if (key->algorithm == cur_algo) { + + if (key->req_len == req_len) { + + if (!memcmp(key->request, req_data, req_len)) { + char* tmp = util_str_buffer(req_data, req_len); + log_debug("Converting: %s ==> %X", tmp, key->response); + free(tmp); + return key->response; + } + } + } + } + + char* tmp = util_str_buffer(req_data, req_len); + log_warn("Converting request %s failed, no matching response found", tmp); + free(tmp); + return 0; +} \ No newline at end of file diff --git a/src/main/sec/microdog34/microdog34.h b/src/main/sec/microdog34/microdog34.h new file mode 100644 index 0000000..04d75d8 --- /dev/null +++ b/src/main/sec/microdog34/microdog34.h @@ -0,0 +1,51 @@ +/** + * Microdog 3.4 emulator + * + * Credits to batteryshark for the emulation code and key files + */ +#ifndef SEC_MICRODOG34_H +#define SEC_MICRODOG34_H + +/** + * Header of a key file containing attributes of the dongle. This is followed + * by an arbitrary number of keys + */ +struct sec_microdog34_header { + uint32_t serial; + uint32_t password; + uint8_t vendor_id[8]; + uint32_t mfg_serial; + uint8_t flash_memory[200]; + uint32_t num_keys; +}; + +/** + * A single entry in the key table (comes after the header) + */ +struct sec_microdog34_key { + uint32_t response; + uint32_t algorithm; + uint32_t req_len; + uint8_t request[64]; +}; + +/** + * Initialize the microdog (3.4) emulation module. The dongle for this + * was used on Exceed2, Zero and NX + * + * @param key_data Pointer to loaded key data (dog attributes, key table) + * @param len Length of the buffer + */ +void sec_microdog34_init(const uint8_t* key_data, size_t len); + +/** + * Process an incoming request to the dongle + * + * @param request IOCTL request for the dongle + * @param data Pointer to a buffer with request data. Response is returned + * to the same buffer (request overwritten) + * @return 0 and success, -1 on failure + */ +int sec_microdog34_process(int request, void* data); + +#endif \ No newline at end of file diff --git a/src/main/sec/microdog40/microdog40.c b/src/main/sec/microdog40/microdog40.c new file mode 100644 index 0000000..18eb43d --- /dev/null +++ b/src/main/sec/microdog40/microdog40.c @@ -0,0 +1,427 @@ +#define LOG_MODULE "sec-microdog40" + +#include + +#include "crypt/aes.h" +#include "crypt/md5.h" + +#include "sec/microdog40/microdog40.h" + +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +#define REQHEAD_SIZE 0x120 +#define REQTAIL_SIZE 0x10 +#define PROTOCOL_VERSION 0x11 + +enum sec_microdog40_req_opcode { + SEC_MICRODOG40_REQ_OPCODE_SET_SHARE = 0x08, + SEC_MICRODOG40_REQ_OPCODE_GET_LOCK_NO = 0x0B, + SEC_MICRODOG40_REQ_OPCODE_GET_ID = 0x14, + SEC_MICRODOG40_REQ_OPCODE_CONVERT = 0x40, + SEC_MICRODOG40_REQ_OPCODE_AUTH_STEP_1 = 0x65, + SEC_MICRODOG40_REQ_OPCODE_AUTH_STEP_2 = 0x67, + SEC_MICRODOG40_REQ_OPCODE_AUTH_STEP_3 = 0x66 +}; + +struct sec_microdog40_month_key_hash_input { + uint8_t ssk_prefix[25]; + uint16_t year; + uint8_t const45; + uint16_t month; +} __attribute((__packed__)); + +static const uint8_t sec_microdog40_shared_secret[30] = { + 0x2A, 0x2F, 0xED, 0x5E, 0x49, + 0x26, 0x40, 0x19, 0x40, 0x40, + 0xE2, 0x51, 0xAA, 0xFA, 0xDB, + 0xCB, 0x67, 0x21, 0x4C, 0xA4, + 0x10, 0x7E, 0x51, 0x22, 0x25, + 0x11, 0x2B, 0x3C, 0x46, 0x5E +}; + +static const uint16_t sec_microdog40_magic = 0x484D; + +static struct sec_microdog40_header sec_microdog40_header; +static struct sec_microdog40_key* sec_microdog40_key_table; + +static uint8_t sec_microdog40_share; + +static void sec_microdog40_log_req(const struct sec_microdog40_req* req) +{ + char* data_tmp = util_str_buffer(req->payload, sizeof(req->payload)); + + log_debug( + "Processing request:\n" + "magic: 0x%X\n" + "req_type: 0x%X\n" + "mask_key1: 0x%X\n" + "dog_cascade: 0x%X\n" + "mfg_serial: 0x%X\n" + "mask_key2: 0x%X\n" + "dog_addr: 0x%X\n" + "payload_size: %d\n" + "mask_key3: 0x%X\n" + "payload: %s\n" + "dog_password: 0x%X\n" + "host_id: 0x%X\n" + "mask_key4: 0x%X\n" + "timestamp.year: %d\n" + "timestamp.month: %d\n" + "timestamp.day: %d\n" + "timestamp.hour: %d\n" + "timestamp.minute: %d\n" + "timestamp.second: %d", + req->magic, + req->req_type, + req->mask_key1, + req->dog_cascade, + req->mfg_serial, + req->mask_key2, + req->dog_addr, + req->payload_size, + req->mask_key3, + data_tmp, + req->dog_password, + req->host_id, + req->mask_key4, + req->timestamp.year, + req->timestamp.month, + req->timestamp.day, + req->timestamp.hour, + req->timestamp.minute, + req->timestamp.second); + + free(data_tmp); +} + +static void sec_microdog40_get_month_key(uint8_t* month_key, + const struct sec_microdog40_timestamp* timestamp) +{ + struct sec_microdog40_month_key_hash_input hash_in; + + memcpy(hash_in.ssk_prefix, sec_microdog40_shared_secret, + sizeof(hash_in.ssk_prefix)); + hash_in.year = timestamp->year; + hash_in.month = timestamp->month; + hash_in.const45 = 45; + + crypt_md5_hash(month_key, (const uint8_t*) &hash_in, + sizeof(struct sec_microdog40_month_key_hash_input)); +} + +static void sec_microdog40_dec_req(const struct sec_microdog40_req* enc_req, + struct sec_microdog40_req* dec_req) +{ + uint8_t key[16]; + + crypt_md5_hash(key, sec_microdog40_shared_secret, + sizeof(sec_microdog40_shared_secret)); + crypt_aes_dec(key, CRYPT_AES_KEY_LENGTH_16_BYTES, ((uint8_t *) dec_req) + + REQHEAD_SIZE, ((uint8_t*) enc_req) + REQHEAD_SIZE, REQTAIL_SIZE); + + sec_microdog40_get_month_key(key, &dec_req->timestamp); + crypt_aes_dec(key, CRYPT_AES_KEY_LENGTH_16_BYTES, ((uint8_t *) dec_req), + ((uint8_t*) enc_req), REQHEAD_SIZE); + + dec_req->magic ^= dec_req->mask_key4; + dec_req->req_type ^= dec_req->mask_key4; + dec_req->mask_key1 ^= dec_req->mask_key4; + dec_req->dog_cascade ^= dec_req->mask_key4; + dec_req->mfg_serial ^= dec_req->mask_key4; + dec_req->mask_key2 ^= dec_req->mask_key4; + dec_req->dog_addr ^= dec_req->mask_key4; + dec_req->payload_size ^= dec_req->mask_key4; + dec_req->mask_key3 ^= dec_req->mask_key4; + dec_req->dog_password ^= dec_req->mask_key4; + dec_req->host_id ^= dec_req->mask_key4; + + for (int i = 0 ; i < 64 ; i++) { + ((uint32_t *) dec_req->payload)[i] ^= dec_req->mask_key4; + } + + dec_req->magic ^= dec_req->mask_key3; + dec_req->req_type ^= dec_req->mask_key3; + dec_req->mask_key1 ^= dec_req->mask_key3; + dec_req->dog_cascade ^= dec_req->mask_key3; + dec_req->mfg_serial ^= dec_req->mask_key3; + dec_req->mask_key2 ^= dec_req->mask_key3; + dec_req->dog_addr ^= dec_req->mask_key3; + dec_req->payload_size ^= dec_req->mask_key3; + + dec_req->magic ^= dec_req->mask_key2; + dec_req->req_type ^= dec_req->mask_key2; + dec_req->mask_key1 ^= dec_req->mask_key2; + dec_req->dog_cascade ^= dec_req->mask_key2; + dec_req->mfg_serial ^= dec_req->mask_key2; + + dec_req->magic ^= dec_req->mask_key1; + dec_req->req_type ^= dec_req->mask_key1; +} + +/* unused */ +/* +static void sec_microdog40_dec_resp(const struct sec_microdog40_resp* enc_resp, + struct sec_microdog40_resp* dec_resp, + const struct sec_microdog40_timestamp* timestamp) +{ + uint8_t key[16]; + + sec_microdog40_get_month_key(key, timestamp); + crypt_aes_dec(key, CRYPT_AES_KEY_LENGTH_16_BYTES, (uint8_t*) dec_resp, + (uint8_t*) enc_resp, sizeof(struct sec_microdog40_resp)); + + for (int i = 0 ; i < 64 ; i++) { + ((uint32_t *) dec_resp->payload)[i] ^= dec_resp->mask_key4; + } + + dec_resp->mask_key3 ^= dec_resp->mask_key4; + dec_resp->krnl_retval ^= dec_resp->mask_key4; + dec_resp->mask_key2 ^= dec_resp->mask_key4; + dec_resp->req_type ^= dec_resp->mask_key4; + dec_resp->mask_key1 ^= dec_resp->mask_key4; + dec_resp->magic ^= dec_resp->mask_key4; + + dec_resp->krnl_retval ^= dec_resp->mask_key3; + dec_resp->mask_key2 ^= dec_resp->mask_key3; + dec_resp->req_type ^= dec_resp->mask_key3; + dec_resp->mask_key1 ^= dec_resp->mask_key3; + dec_resp->magic ^= dec_resp->mask_key3; + + dec_resp->req_type ^= dec_resp->mask_key2; + dec_resp->mask_key1 ^= dec_resp->mask_key2; + dec_resp->magic ^= dec_resp->mask_key2; + + dec_resp->magic ^= dec_resp->mask_key1; +} +*/ + +/* unused */ +/* +static void sec_microdog40_enc_req(const struct sec_microdog40_req* dec_req, + struct sec_microdog40_req* enc_req) +{ + uint8_t key[16]; + + // Mask keying not necessary + + sec_microdog40_get_month_key(key, &dec_req->timestamp); + crypt_aes_enc(key, CRYPT_AES_KEY_LENGTH_16_BYTES, (uint8_t*) enc_req, + (uint8_t*) dec_req, sizeof(struct sec_microdog40_req)); + + crypt_md5_hash(key, sec_microdog40_shared_secret, + sizeof(sec_microdog40_shared_secret)); + crypt_aes_enc(key, CRYPT_AES_KEY_LENGTH_16_BYTES, ((uint8_t*) enc_req) + + REQHEAD_SIZE, ((uint8_t*) &dec_req) + REQHEAD_SIZE, REQTAIL_SIZE); +} +*/ + +static void sec_microdog40_encrypt_resp( + const struct sec_microdog40_resp* dec_resp, + struct sec_microdog40_resp* enc_resp, + const struct sec_microdog40_timestamp* timestamp) +{ + uint8_t key[16]; + + sec_microdog40_get_month_key(key, timestamp); + crypt_aes_enc(key, CRYPT_AES_KEY_LENGTH_16_BYTES, (uint8_t*) enc_resp, + (uint8_t*) dec_resp, sizeof(struct sec_microdog40_resp)); +} + +static uint32_t sec_microdog40_convert_req(const uint8_t* req_data, + uint16_t req_len) +{ + uint32_t cur_algo; + + cur_algo = 0; + memcpy(&cur_algo, sec_microdog40_header.flash_memory + 196, 4); + + for (uint32_t i = 0; i < sec_microdog40_header.num_keys; i++) { + struct sec_microdog40_key* key = &sec_microdog40_key_table[i]; + + if (key->algorithm == cur_algo) { + + if (key->req_len == req_len) { + + if (!memcmp(key->request, req_data, req_len)) { + char* tmp = util_str_buffer(req_data, req_len); + log_debug("Converting: %s ==> %X", tmp, key->response); + free(tmp); + return key->response; + } + } + } + } + + char* tmp = util_str_buffer(req_data, req_len); + log_warn("Converting request %s failed, no matching response found", tmp); + free(tmp); + return 0; +} + +static void sec_microdog40_dispatch(const struct sec_microdog40_req* req, + struct sec_microdog40_resp* resp) +{ + // prepare response + resp->magic = sec_microdog40_magic; + resp->req_type = req->req_type; + + if (req->magic != sec_microdog40_magic) { + log_warn("Bad magic for request: %X", req->magic); + } + + sec_microdog40_log_req(req); + + switch (req->req_type) + { + case SEC_MICRODOG40_REQ_OPCODE_SET_SHARE: + { + log_debug("SEC_MICRODOG40_REQ_OPCODE_SET_SHARE"); + + sec_microdog40_share = req->payload[0]; + break; + } + + case SEC_MICRODOG40_REQ_OPCODE_GET_LOCK_NO: + { + log_debug("SEC_MICRODOG40_REQ_OPCODE_GET_LOCK_NO 0x%08X", + sec_microdog40_header.mfg_serial); + + memcpy(resp->payload, &sec_microdog40_header.mfg_serial, 4); + break; + } + + case SEC_MICRODOG40_REQ_OPCODE_GET_ID: + { + log_debug("SEC_MICRODOG40_REQ_OPCODE_GET_ID 0x%02X 0x%02X 0x%02X " + "0x%02X 0x%02X 0x%02X 0x%02X 0x%02X", + sec_microdog40_header.vendor_id[0], + sec_microdog40_header.vendor_id[1], + sec_microdog40_header.vendor_id[2], + sec_microdog40_header.vendor_id[3], + sec_microdog40_header.vendor_id[4], + sec_microdog40_header.vendor_id[5], + sec_microdog40_header.vendor_id[6], + sec_microdog40_header.vendor_id[7]); + + memcpy(resp->payload, sec_microdog40_header.vendor_id, + sizeof(sec_microdog40_header.vendor_id)); + + break; + } + + case SEC_MICRODOG40_REQ_OPCODE_CONVERT: + { + uint32_t resp_data; + + log_debug("SEC_MICRODOG40_REQ_OPCODE_CONVERT"); + + resp_data = sec_microdog40_convert_req(req->payload, + req->payload_size); + memcpy(resp->payload, &resp_data, sizeof(uint32_t)); + + break; + } + + case SEC_MICRODOG40_REQ_OPCODE_AUTH_STEP_1: + { + log_debug("SEC_MICRODOG40_REQ_OPCODE_AUTH_STEP_1"); + /* no-op */ + break; + } + + case SEC_MICRODOG40_REQ_OPCODE_AUTH_STEP_2: + { + log_debug("SEC_MICRODOG40_REQ_OPCODE_AUTH_STEP_2"); + /* no-op */ + break; + } + + case SEC_MICRODOG40_REQ_OPCODE_AUTH_STEP_3: + { + uint8_t month_key[16]; + + log_debug("SEC_MICRODOG40_REQ_OPCODE_AUTH_STEP_3"); + + sec_microdog40_get_month_key(month_key, &req->timestamp); + + crypt_aes_enc(month_key, CRYPT_AES_KEY_LENGTH_16_BYTES, + resp->payload, req->payload, 16); + + break; + } + + default: + { + log_warn("Unsupported Opcode: %04X", req->req_type); + resp->magic = 0; // mark to indicate error + break; + } + } +} + +void sec_microdog40_init(const uint8_t* key_data, size_t len) +{ + if (len < sizeof(struct sec_microdog40_header)) { + log_error("Loading key table failed, incomplete header"); + return; + } + + memcpy(&sec_microdog40_header, key_data, + sizeof(struct sec_microdog40_header)); + + if (len - sizeof(struct sec_microdog40_header) < + sec_microdog40_header.num_keys * + sizeof(struct sec_microdog40_key)) { + log_error("Loading key table failed, incomplete key table"); + return; + } + + sec_microdog40_key_table = util_xmalloc(sec_microdog40_header.num_keys * + sizeof(struct sec_microdog40_key)); + + memcpy(sec_microdog40_key_table, + key_data + sizeof(struct sec_microdog40_header), + sec_microdog40_header.num_keys * sizeof(struct sec_microdog40_key)); + + log_info("Loaded %d keys, serial 0x%X, password 0x%X", + sec_microdog40_header.num_keys, sec_microdog40_header.serial, + sec_microdog40_header.password); + + sec_microdog40_share = 0; +} + +bool sec_microdog40_do_transaction( + struct sec_microdog40_transaction* transaction) +{ + struct sec_microdog40_req dec_req; + struct sec_microdog40_resp dec_resp; + + if (transaction->header != PROTOCOL_VERSION) { + log_error("Wrong protocol version of transaction %X.", + transaction->header); + return false; + } + + log_debug("Decrypting request..."); + sec_microdog40_dec_req(&transaction->request, &dec_req); + + sec_microdog40_dispatch(&dec_req, &dec_resp); + + /* no error */ + if (dec_resp.magic != 0) { + transaction->header = 0; + + log_debug("Encrypting response..."); + sec_microdog40_encrypt_resp(&dec_resp, &transaction->response, + &dec_req.timestamp); + } else { + transaction->header = 20023; + log_error("Transaction failed."); + } + + log_debug("Transaction successful"); + + return true; +} \ No newline at end of file diff --git a/src/main/sec/microdog40/microdog40.h b/src/main/sec/microdog40/microdog40.h new file mode 100644 index 0000000..c356ad1 --- /dev/null +++ b/src/main/sec/microdog40/microdog40.h @@ -0,0 +1,111 @@ +/** + * Microdog 4.0 emulator + * + * Credits to batteryshark for the emulation code and key files + */ +#ifndef SEC_MICRODOG40_H +#define SEC_MICRODOG40_H + +#include +#include + +/** + * Header of a key file containing attributes of the dongle. This is followed + * by an arbitrary number of keys + */ +struct sec_microdog40_header { + uint32_t serial; + uint32_t password; + uint8_t vendor_id[8]; + uint32_t mfg_serial; + uint8_t flash_memory[200]; + uint32_t num_keys; +}; + +/** + * A single entry in the key table (comes after the header) + */ +struct sec_microdog40_key { + uint32_t response; + uint32_t algorithm; + uint32_t req_len; + uint8_t request[64]; +}; + +/** + * Timestamp for a dongle request + */ +struct sec_microdog40_timestamp { + uint16_t year; + uint16_t month; + uint16_t day; + uint16_t hour; + uint16_t minute; + uint16_t second; + uint8_t padding[3]; +} __attribute((__packed__)); + +/** + * Request to the dongle as part of a transaction + */ +struct sec_microdog40_req { + uint16_t magic; + uint8_t req_type; + uint32_t mask_key1; + uint8_t dog_cascade; + uint32_t mfg_serial; + uint32_t mask_key2; + uint16_t dog_addr; + uint16_t payload_size; + uint32_t mask_key3; + uint8_t payload[256]; + uint32_t dog_password; + uint8_t host_id; + uint32_t mask_key4; + struct sec_microdog40_timestamp timestamp; +} __attribute((__packed__)); + +/** + * Dongle response as part of a transaction + */ +struct sec_microdog40_resp { + uint16_t magic; + uint32_t mask_key1; + uint8_t req_type; + uint32_t mask_key2; + uint32_t krnl_retval; + uint32_t mask_key3; + uint8_t payload[256]; + uint32_t mask_key4; + uint8_t padding[9]; +} __attribute((__packed__)); + +/** + * Dongle emulator transaction data + */ +struct sec_microdog40_transaction { + uint32_t header; + struct sec_microdog40_req request; + struct sec_microdog40_resp response; +} __attribute((__packed__)); + +/** + * Initialize the microdog (4.0) emulation module. The dongle for this + * was used on NX2, NXA, Fiesta and FiestaEX + * + * @param key_data Pointer to loaded key data (dog attributes, key table) + * @param len Length of the buffer + */ +void sec_microdog40_init(const uint8_t* key_data, size_t len); + +/** + * Execute a transaction on the dongle + * + * @param transaction Transaction containing the request. The response is + * written to it after successfully processing the request + * @return True if the request was successfully processed and a response was + * written, false on failure + */ +bool sec_microdog40_do_transaction(struct sec_microdog40_transaction* transaction); + +#endif \ No newline at end of file diff --git a/src/main/sec/microdog40/microdog40d.c b/src/main/sec/microdog40/microdog40d.c new file mode 100644 index 0000000..1754896 --- /dev/null +++ b/src/main/sec/microdog40/microdog40d.c @@ -0,0 +1,109 @@ +#define LOG_MODULE "sec-microdog40d" + +#include +#include +#include +#include +#include +#include + +#include "sec/microdog40/microdog40.h" + +#include "util/log.h" + +static const char* sec_microdog40d_sock_path = "/var/run/microdog/u.daemon"; + +static bool sec_microdog40d_run_d; +static bool sec_microdog40d_is_running_d; + +void sec_microdog40d_init(const uint8_t* key_data, size_t len) +{ + sec_microdog40_init(key_data, len); + sec_microdog40d_run_d = true; + sec_microdog40d_is_running_d = false; +} + +void sec_microdog40d_run(void) +{ + int fd; + struct sec_microdog40_transaction transaction; + struct sockaddr_un peer_addr; + socklen_t peer_addr_len; + int retval; + + sec_microdog40d_is_running_d = true; + + fd = -1; + peer_addr_len = sizeof(peer_addr); + retval = 0; + + unlink(sec_microdog40d_sock_path); + + /* make sure /var/run/microdog folder exists */ + mkdir("/var/run/microdog", 0700); + + fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd == -1) { + log_error("Opening socket failed: %s", strerror(errno)); + sec_microdog40d_is_running_d = false; + return; + } + + memset(&peer_addr, 0, sizeof(struct sockaddr_un)); + peer_addr.sun_family = AF_UNIX; + strcpy(peer_addr.sun_path, sec_microdog40d_sock_path); + + retval = bind(fd, (struct sockaddr *) &peer_addr, + sizeof(struct sockaddr_un)); + + if (retval == -1) { + log_error("Binding socket failed: %s", strerror(errno)); + sec_microdog40d_is_running_d = false; + return; + } + + while (sec_microdog40d_run_d) { + peer_addr_len = sizeof(peer_addr); + retval = recvfrom(fd, &transaction, + sizeof(struct sec_microdog40_transaction), 0, + (struct sockaddr *) &peer_addr, &peer_addr_len); + + if (retval == -1) { + + if (errno == EBADF) { + break; + } else { + log_error("Receiving data failed: %s", strerror(errno)); + sec_microdog40d_is_running_d = false; + return; + } + } else if (retval != sizeof(struct sec_microdog40_transaction)) { + log_warn("Package too short, expected %d bytes, got %d bytes", + sizeof(struct sec_microdog40_transaction), retval); + } else { + if (sec_microdog40_do_transaction(&transaction)) { + sendto(fd, &transaction, + sizeof(struct sec_microdog40_transaction), 0, + (struct sockaddr *) &peer_addr, peer_addr_len); + } else { + log_error("Transaction failed."); + } + } + } + + // cleanup + close(fd); + unlink(sec_microdog40d_sock_path); + + sec_microdog40d_is_running_d = false; +} + +bool sec_microdog40d_is_running(void) +{ + return sec_microdog40d_is_running_d; +} + +void sec_microdog40d_shutdown(void) +{ + sec_microdog40d_run_d = false; +} \ No newline at end of file diff --git a/src/main/sec/microdog40/microdog40d.h b/src/main/sec/microdog40/microdog40d.h new file mode 100644 index 0000000..e429cbd --- /dev/null +++ b/src/main/sec/microdog40/microdog40d.h @@ -0,0 +1,35 @@ +/** + * Microdog 4.0 daemon module + */ +#ifndef SEC_MICRODOG40D_H +#define SEC_MICRODOG40D_H + +#include +#include + +/** + * Initialize the microdog (4.0) daemon module. This just initializes the + * dongle emulator module + * + * @param key_data Pointer to loaded key data (dog attributes, key table) + * @param len Length of the buffer + */ +void sec_microdog40d_init(const uint8_t* key_data, size_t len); + +/** + * Run the daemon. This call blocks until the daemon is shut down. + * Recommended to run this in a separate thread. + */ +void sec_microdog40d_run(void); + +/** + * Check if the daemon is running + */ +bool sec_microdog40d_is_running(void); + +/** + * Signal a shut down to the daemon + */ +void sec_microdog40d_shutdown(void); + +#endif \ No newline at end of file diff --git a/src/main/util/adler32.c b/src/main/util/adler32.c new file mode 100644 index 0000000..8d05078 --- /dev/null +++ b/src/main/util/adler32.c @@ -0,0 +1,17 @@ +#include "util/adler32.h" + +#define BASE 65521 + +uint32_t util_adler32_calc(uint32_t initval, const uint8_t* input, size_t length) +{ + unsigned int s1 = initval & 0xffff; + unsigned int s2 = (initval >> 16) & 0xffff; + unsigned int n; + + for (n = 0; n < length; n++) { + s1 = (s1 + input[n]) % BASE; + s2 = (s2 + s1) % BASE; + } + + return (s2 << 16) + s1; +} \ No newline at end of file diff --git a/src/main/util/adler32.h b/src/main/util/adler32.h new file mode 100644 index 0000000..1f74a50 --- /dev/null +++ b/src/main/util/adler32.h @@ -0,0 +1,17 @@ +#ifndef UTIL_ADLER32_H +#define UTIL_ADLER32_H + +#include +#include + +/** + * Calculate the adler32 checksum + * + * @param initval Initial value for calculation + * @param input Input buffer to checksum + * @param length Length of the input buffer + * @return Adler32 value of input buffer + */ +uint32_t util_adler32_calc(uint32_t initval, const uint8_t* input, size_t length); + +#endif \ No newline at end of file diff --git a/src/main/util/array.c b/src/main/util/array.c new file mode 100644 index 0000000..e9f61f3 --- /dev/null +++ b/src/main/util/array.c @@ -0,0 +1,55 @@ +#include +#include +#include + +#include "util/array.h" +#include "util/log.h" +#include "util/mem.h" + +void util_array_init(struct util_array* array) +{ + memset(array, 0, sizeof(*array)); +} + +void util_array_remove_(size_t itemsz, struct util_array* array, size_t i) +{ + array->nitems--; + + memmove(((uint8_t *) array->items) + i * itemsz, + ((uint8_t *) array->items) + (i + 1) * itemsz, + (array->nitems - i) * itemsz); +} + +void* util_array_reserve_(size_t itemsz, struct util_array* array, size_t nitems) +{ + size_t new_nalloced; + size_t new_nitems; + void* result; + + new_nitems = array->nitems + nitems; + + if (new_nitems > array->nalloced) { + new_nalloced = array->nalloced; + + if (new_nalloced == 0) { + new_nalloced = 1; + } + + while (new_nalloced < new_nitems) { + new_nalloced *= 2; + } + + array->items = util_xrealloc(array->items, new_nalloced * itemsz); + array->nalloced = new_nalloced; + } + + result = ((uint8_t *) array->items) + array->nitems * itemsz; + array->nitems = new_nitems; + + return result; +} + +void util_array_fini(struct util_array* array) +{ + free(array->items); +} diff --git a/src/main/util/array.h b/src/main/util/array.h new file mode 100644 index 0000000..8a541ce --- /dev/null +++ b/src/main/util/array.h @@ -0,0 +1,60 @@ +#ifndef UTIL_ARRAY_H +#define UTIL_ARRAY_H + +#include + +/** + * Array with a fixed number of items + */ +struct util_array { + void* items; + size_t nitems; + size_t nalloced; +}; + +#define util_array_item(type, array, i) \ + (&(((type*) (array)->items)[i])) + +#define util_array_reserve(type, array, nitems) \ + ((type*) util_array_reserve_(sizeof(type), array, nitems)) + +#define util_array_remove(type, array, i) \ + util_array_remove_(sizeof(type), array, i) + +#define util_array_append(type, array) \ + util_array_reserve(type, array, 1) + +/** + * Initialize the array after allocation + * + * @param array Pointer to allocated array to initialize + */ +void util_array_init(struct util_array* array); + +/** + * Reserve/allocate space for the specified number of items in the array + * + * @param itemsz Size of a single item + * @param array Array to use + * @param Number of items with itemsz each to reserve + * @return Pointer to first index of reserved slots in array + */ +void* util_array_reserve_(size_t itemsz, struct util_array* array, size_t nitems); + +/** + * Remove an item from the array. The array will be shrunken to current_size - 1 + * + * @param itemsz Size of a single item + * @param array Array to remove the item from + * @param i Index of the item to remove + */ +void util_array_remove_(size_t itemsz, struct util_array* array, size_t i); + +/** + * Free/Deallocate an allocated array + * + * @param array Allocated array to free + */ +void util_array_fini(struct util_array* array); + +#endif diff --git a/src/main/util/base64.c b/src/main/util/base64.c new file mode 100644 index 0000000..0ab955a --- /dev/null +++ b/src/main/util/base64.c @@ -0,0 +1,170 @@ +#include + +#include "util/base64.h" +#include "util/mem.h" + +// Copy-pasted with minor tweaks from: https://github.com/littlstar/b64.c + +static const char b64_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + +uint8_t* util_base64_encode(const uint8_t* src, size_t len, size_t* out_len) +{ + int i = 0; + int j = 0; + unsigned char *enc = NULL; + size_t size = 0; + unsigned char buf[4]; + unsigned char tmp[3]; + + enc = (unsigned char *) util_xmalloc(0); + + while (len--) { + tmp[i++] = *(src++); + + if (3 == i) { + buf[0] = (tmp[0] & 0xfc) >> 2; + buf[1] = ((tmp[0] & 0x03) << 4) + ((tmp[1] & 0xf0) >> 4); + buf[2] = ((tmp[1] & 0x0f) << 2) + ((tmp[2] & 0xc0) >> 6); + buf[3] = tmp[2] & 0x3f; + + enc = (unsigned char *) util_xrealloc(enc, size + 4); + + for (i = 0; i < 4; ++i) { + enc[size++] = b64_table[buf[i]]; + } + + i = 0; + } + } + + if (i > 0) { + for (j = i; j < 3; ++j) { + tmp[j] = '\0'; + } + + buf[0] = (tmp[0] & 0xfc) >> 2; + buf[1] = ((tmp[0] & 0x03) << 4) + ((tmp[1] & 0xf0) >> 4); + buf[2] = ((tmp[1] & 0x0f) << 2) + ((tmp[2] & 0xc0) >> 6); + buf[3] = tmp[2] & 0x3f; + + for (j = 0; (j < i + 1); ++j) { + enc = (unsigned char *) util_xrealloc(enc, size + 1); + enc[size++] = b64_table[buf[j]]; + } + + while ((i++ < 3)) { + enc = (unsigned char *) util_xrealloc(enc, size + 1); + enc[size++] = '='; + } + } + + enc = (unsigned char *) util_xrealloc(enc, size + 1); + enc[size] = '\0'; + + *out_len = size; + + return enc; +} + +uint8_t* util_base64_decode(const uint8_t* src, size_t len, size_t* out_len) +{ + int i = 0; + int j = 0; + int l = 0; + size_t size = 0; + unsigned char *dec = NULL; + unsigned char buf[3]; + unsigned char tmp[4]; + + dec = (unsigned char *) util_xmalloc(0); + + while (len--) { + if ('=' == src[j]) { + break; + } + if (!(isalnum(src[j]) || '+' == src[j] || '/' == src[j])) { + break; + } + + tmp[i++] = src[j++]; + + if (4 == i) { + for (i = 0; i < 4; ++i) { + for (l = 0; l < 64; ++l) { + if (tmp[i] == b64_table[l]) { + tmp[i] = l; + break; + } + } + } + + buf[0] = (tmp[0] << 2) + ((tmp[1] & 0x30) >> 4); + buf[1] = ((tmp[1] & 0xf) << 4) + ((tmp[2] & 0x3c) >> 2); + buf[2] = ((tmp[2] & 0x3) << 6) + tmp[3]; + + dec = (unsigned char *) util_xrealloc(dec, size + 3); + + if (dec != NULL){ + for (i = 0; i < 3; ++i) { + dec[size++] = buf[i]; + } + } else { + return NULL; + } + + i = 0; + } + } + + if (i > 0) { + for (j = i; j < 4; ++j) { + tmp[j] = '\0'; + } + + for (j = 0; j < 4; ++j) { + for (l = 0; l < 64; ++l) { + if (tmp[j] == b64_table[l]) { + tmp[j] = l; + break; + } + } + } + + buf[0] = (tmp[0] << 2) + ((tmp[1] & 0x30) >> 4); + buf[1] = ((tmp[1] & 0xf) << 4) + ((tmp[2] & 0x3c) >> 2); + buf[2] = ((tmp[2] & 0x3) << 6) + tmp[3]; + + dec = (unsigned char *)util_xrealloc(dec, size + (i - 1)); + + if (dec != NULL){ + for (j = 0; (j < i - 1); ++j) { + dec[size++] = buf[j]; + } + } else { + return NULL; + } + } + + dec = (unsigned char *)util_xrealloc(dec, size + 1); + + if (dec != NULL){ + dec[size] = '\0'; + } else { + return NULL; + } + + if (out_len != NULL) { + *out_len = size; + } + + return dec; +} \ No newline at end of file diff --git a/src/main/util/base64.h b/src/main/util/base64.h new file mode 100644 index 0000000..e3fdcf2 --- /dev/null +++ b/src/main/util/base64.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +uint8_t* util_base64_encode(const uint8_t* src, size_t len, size_t* out_len); + +uint8_t* util_base64_decode(const uint8_t* src, size_t len, size_t* out_len); \ No newline at end of file diff --git a/src/main/util/defs.h b/src/main/util/defs.h new file mode 100644 index 0000000..c90240d --- /dev/null +++ b/src/main/util/defs.h @@ -0,0 +1,18 @@ +#ifndef UTIL_DEFS_H +#define UTIL_DEFS_H + +#include +#include + +#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) +#define XSTRINGIFY(arg) STRINGIFY(arg) + +#define lengthof(x) (sizeof(x) / sizeof(x[0])) + +#define containerof(ptr, outer_t, member) \ + ((void *) (((uint8_t *) ptr) - offsetof(outer_t, member))) + +#endif diff --git a/src/main/util/fs.c b/src/main/util/fs.c new file mode 100644 index 0000000..2b5d773 --- /dev/null +++ b/src/main/util/fs.c @@ -0,0 +1,222 @@ +#define LOG_MODULE "util-fs" + +#include +#include +#include +#include +#include +#include +#include + +#include "util/defs.h" +#include "util/fs.h" +#include "util/log.h" +#include "util/mem.h" + +bool util_file_load(const char* filename, void** out_bytes, size_t *out_nbytes, + bool text_mode) +{ + FILE *f; + void *bytes; + size_t nbytes; + int result; + + log_assert(out_bytes != NULL); + *out_bytes = NULL; + + f = fopen(filename, "rb"); + + if (f == NULL) { + log_warn("%s: Error opening file: %s", filename, strerror(errno)); + + goto open_fail; + } + + fseek(f, 0, SEEK_END); + nbytes = ftell(f); + fseek(f, 0, SEEK_SET); + + /* Add for null terminator */ + if (text_mode) { + nbytes++; + } + + bytes = malloc(nbytes); + + if (bytes == NULL) { + log_warn("%s: malloc(%u) failed", filename, (unsigned int) nbytes); + + goto malloc_fail; + } + + if (text_mode) { + result = fread(bytes, nbytes - 1, 1, f); + } else { + result = fread(bytes, nbytes, 1, f); + } + + if (result != 1) { + log_warn("%s: Error reading file: %s", filename, strerror(errno)); + + goto read_fail; + } + + *out_bytes = bytes; + + if (out_nbytes != NULL) { + *out_nbytes = nbytes; + } + + fclose(f); + + /* Add null terminator */ + if (text_mode) { + ((char*) bytes)[nbytes - 1] = '\0'; + } + + log_debug("File loaded %s, size %d", filename, *out_nbytes); + + return true; + +read_fail: + free(bytes); + +malloc_fail: + fclose(f); + +open_fail: + return false; +} + +bool util_file_save(const char* filename, const void* bytes, size_t nbytes) +{ + FILE *f; + int result; + + f = fopen(filename, "wb"); + + if (f == NULL) { + log_warn("%s: Error creating file: %s", filename, strerror(errno)); + + goto open_fail; + } + + result = fwrite(bytes, nbytes, 1, f); + + if (result != 1) { + log_warn("%s: Error writing file: %s", filename, strerror(errno)); + + goto write_fail; + } + + fclose(f); + + log_debug("File saved %s, size %d", filename, nbytes); + + return true; + +write_fail: + fclose(f); + +open_fail: + return false; +} + +bool util_fs_path_exists(const char* path) +{ + // Don't use stat here because that doesn't work on mounts, e.g. sqashfs/appimage + + bool exists; + FILE* file = fopen(path, "r"); + + exists = file != NULL; + + if (file != NULL) { + fclose(file); + } + + return exists; +} + +bool util_fs_mkdir(const char* path) +{ + return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0; +} + +bool util_fs_mkfile(const char* path) +{ + FILE* file; + bool res; + + file = fopen(path, "a"); + + res = file != NULL; + + if (file) { + fclose(file); + } + + return res; +} + +char* util_fs_get_path_to_file(const char* path) +{ + size_t len; + char* res; + + len = strlen(path); + + for (ssize_t i = len - 1; i >= 0; i--) { + if (path[i] == '/') { + len--; + break; + } + + len--; + } + + res = (char*) util_xmalloc(sizeof(char) * (len + 1)); + strncpy(res, path, len); + res[len] = '\0'; + + return res; +} + +char* util_fs_get_filename(const char* path) +{ + size_t path_len; + size_t len; + char* res; + + len = strlen(path); + path_len = len; + + for (ssize_t i = len - 1; i >= 0; i--) { + if (path[i] == '/') { + len++; + break; + } + + len--; + } + + res = (char*) util_xmalloc(sizeof(char) * ((path_len - len) + 1)); + strncpy(res, path + len, path_len - len); + res[path_len - len] = '\0'; + + return res; +} + +char* util_fs_get_abs_path(const char* path) +{ + char* real_path; + + real_path = realpath(path, NULL); + + if (!real_path) { + log_error("Resolving path '%s' to absolute path failed: %s", path, + strerror(errno)); + } + + return real_path; +} \ No newline at end of file diff --git a/src/main/util/fs.h b/src/main/util/fs.h new file mode 100644 index 0000000..31f92af --- /dev/null +++ b/src/main/util/fs.h @@ -0,0 +1,81 @@ +#ifndef UTIL_FS_H +#define UTIL_FS_H + +#include +#include +#include +#include + +/** + * Load a file fully into memory + * + * @param filename Path of the file to load + * @param bytes Pointer to a pointer to return an allocated location with the + * loaded data to + * @param nbytes Pointer to a variable to return the read size of the file to + * @param text_mode True to read the file in text mode (add terminating \0), + * false for binary mode + * @return True on success, false on error + */ +bool util_file_load(const char* filename, void** bytes, size_t* nbytes, bool text_mode); + +/** + * Save a buffer to a file (overwrite any existing files with the same name) + * + * @param filename Path to save the data to + * @param bytes Pointer to buffer with contents to save + * @param nbytes Number of bytes to write to the file + * @return True on success, false on error + */ +bool util_file_save(const char* filename, const void* bytes, size_t nbytes); + +/** + * Check if a normal file, folder, symlink, ... exists + * + * @return True if exists, false otherwise + */ +bool util_fs_path_exists(const char* path); + +/** + * Create a new directory + * + * @param path Path to directory + * @return True if successful, false on failure + */ +bool util_fs_mkdir(const char* path); + +/** + * Create a new empty file + * + * @param path Path to the file to create + * @return True if successful, false on failure + */ +bool util_fs_mkfile(const char* path); + +/** + * Get the path pointing to the file (without the filename) + * + * @param path Full path to file + * @return Newly allocated string (caller has to free) containing the path, only + */ +char* util_fs_get_path_to_file(const char* path); + +/** + * Get the start position of the filename in the path + * + * @param path Full path to file + * @return Newly allocated string (caller has to free) containing the filename, + * only + */ +char* util_fs_get_filename(const char* path); + +/** + * Get the absolute path of the give (relative) path + * + * @param path (Relative) path + * @return Allocated string (caller has to free) containing the full absolute + * path for the given (relative) path + */ +char* util_fs_get_abs_path(const char* path); + +#endif diff --git a/src/main/util/glibc.c b/src/main/util/glibc.c new file mode 100644 index 0000000..bc0b506 --- /dev/null +++ b/src/main/util/glibc.c @@ -0,0 +1,10 @@ +#define LOG_MODULE "util-glibc" + +#include + +#include "util/log.h" + +void util_glibc_info_log() +{ + log_info("glibc release '%s', version '%s'", gnu_get_libc_release(), gnu_get_libc_version()); +} \ No newline at end of file diff --git a/src/main/util/glibc.h b/src/main/util/glibc.h new file mode 100644 index 0000000..35640aa --- /dev/null +++ b/src/main/util/glibc.h @@ -0,0 +1,6 @@ +#ifndef PUMPTOOLS_UTIL_GLIBC_H +#define PUMPTOOLS_UTIL_GLIBC_H + +void util_glibc_info_log(); + +#endif diff --git a/src/main/util/hex.c b/src/main/util/hex.c new file mode 100644 index 0000000..bcfd471 --- /dev/null +++ b/src/main/util/hex.c @@ -0,0 +1,73 @@ +#include +#include +#include + +#include "util/hex.h" +#include "util/log.h" + +static bool util_hex_decode_nibble(char c, uint8_t *nibble) +{ + if (c >= '0' && c <= '9') { + *nibble = c - '0'; + } else if (c >= 'A' && c <= 'F') { + *nibble = c - 'A' + 10; + } else if (c >= 'a' && c <= 'f') { + *nibble = c - 'a' + 10; + } else { + return false; + } + + return true; +} + +bool util_hex_decode(void *ptr, size_t nbytes, const char *chars, size_t nchars) +{ + uint8_t *bytes = ptr; + + uint32_t i; + uint8_t hi; + uint8_t lo; + + log_assert(nchars >= 2 * nbytes); + + for (i = 0 ; i < nbytes ; i++) { + if ( !util_hex_decode_nibble(chars[2 * i + 0], &hi) || + !util_hex_decode_nibble(chars[2 * i + 1], &lo)) { + return false; + } + + bytes[i] = (hi << 4) | lo; + } + + return true; +} + +static void util_hex_encode(const void *ptr, size_t nbytes, char *chars, + size_t nchars, const char *digits) +{ + const uint8_t *bytes = ptr; + + size_t i; + + log_assert(nchars >= 2 * nbytes + 1); + + for (i = 0 ; i < nbytes ; i++) { + chars[i * 2 + 0] = digits[bytes[i] >> 4]; + chars[i * 2 + 1] = digits[bytes[i] & 15]; + } + + chars[nbytes * 2] = '\0'; +} + +void util_hex_encode_lc(const void *bytes, size_t nbytes, char *chars, + size_t nchars) +{ + util_hex_encode(bytes, nbytes, chars, nchars, "0123456789abcdef"); +} + +void util_hex_encode_uc(const void *bytes, size_t nbytes, char *chars, + size_t nchars) +{ + util_hex_encode(bytes, nbytes, chars, nchars, "0123456789ABCDEF"); +} + diff --git a/src/main/util/hex.h b/src/main/util/hex.h new file mode 100644 index 0000000..96486cb --- /dev/null +++ b/src/main/util/hex.h @@ -0,0 +1,14 @@ +#ifndef UTIL_HEX_H +#define UTIL_HEX_H + +#include +#include + +bool util_hex_decode(void *bytes, size_t nbytes, const char *chars, + size_t nchars); +void util_hex_encode_uc(const void *bytes, size_t nbytes, char *chars, + size_t nchars); +void util_hex_encode_lc(const void *bytes, size_t nbytes, char *chars, + size_t nchars); + +#endif diff --git a/src/main/util/iobuf.c b/src/main/util/iobuf.c new file mode 100644 index 0000000..b8bd76e --- /dev/null +++ b/src/main/util/iobuf.c @@ -0,0 +1,22 @@ +#include "util/iobuf.h" + +#include + +size_t util_iobuf_move(struct util_iobuf* dest, struct util_const_iobuf* src) +{ + size_t dest_avail; + size_t src_avail; + size_t chunksz; + + dest_avail = dest->nbytes - dest->pos; + src_avail = src->nbytes - src->pos; + chunksz = dest_avail < src_avail ? dest_avail : src_avail; + + memcpy(&dest->bytes[dest->pos], &src->bytes[src->pos], chunksz); + + dest->pos += chunksz; + src->pos += chunksz; + + return chunksz; +} + diff --git a/src/main/util/iobuf.h b/src/main/util/iobuf.h new file mode 100644 index 0000000..4086482 --- /dev/null +++ b/src/main/util/iobuf.h @@ -0,0 +1,35 @@ +#ifndef UTIL_IOBUF_H +#define UTIL_IOBUF_H + +#include +#include + +/** + * Iobuffer struct holding data, size and position + */ +struct util_iobuf { + uint8_t* bytes; + size_t nbytes; + size_t pos; +}; + +/** + * Const variant of iobuffer struct holding data, size and position + */ +struct util_const_iobuf { + const uint8_t* bytes; + size_t nbytes; + size_t pos; +}; + +/** + * Move the contents of one iobuffer to another one (data, size and pos) + * + * @param dest Destination to move to + * @param src Source to move from + * @return Number of bytes moved (this can be less than the source's size + * e.g. if destination size < src size) + */ +size_t util_iobuf_move(struct util_iobuf* dest, struct util_const_iobuf* src); + +#endif diff --git a/src/main/util/list.c b/src/main/util/list.c new file mode 100644 index 0000000..b88d2c1 --- /dev/null +++ b/src/main/util/list.c @@ -0,0 +1,91 @@ +#include "util/list.h" + +#include +#include + +void util_list_init(struct util_list* list) +{ + memset(list, 0, sizeof(*list)); +} + +void util_list_append(struct util_list* list, struct util_list_node* node) +{ + node->next = NULL; + + if (list->tail != NULL) { + list->tail->next = node; + } else { + list->head = node; + } + + list->tail = node; +} + +bool util_list_contains(struct util_list* list, struct util_list_node* node) +{ + struct util_list_node* pos; + + for (pos = list->head ; pos != NULL ; pos = pos->next) { + if (pos == node) { + return true; + } + } + + return false; +} + +struct util_list_node* util_list_pop_head(struct util_list* list) +{ + struct util_list_node* node; + + if (list->head == NULL) { + return NULL; + } + + node = list->head; + list->head = node->next; + + if (node->next == NULL) { + list->tail = NULL; + } + + node->next = NULL; + + return node; +} + +void util_list_remove(struct util_list* list, struct util_list_node* node) +{ + struct util_list_node* pos; + struct util_list_node* prev; + + for (prev = NULL, pos = list->head + ; pos != NULL + ; prev = pos, pos = pos->next) { + if (pos == node) { + if (prev != NULL) { + prev->next = node->next; + } else { + list->head = node->next; + } + + if (node->next == NULL) { + list->tail = prev; + } + + node->next = NULL; + + return; + } + } +} + +bool util_list_empty(struct util_list* list) +{ + return list->head == NULL; +} + +inline struct util_list_node* util_list_peek_head(struct util_list* list) +{ + return list->head; +} diff --git a/src/main/util/list.h b/src/main/util/list.h new file mode 100644 index 0000000..7ab8aeb --- /dev/null +++ b/src/main/util/list.h @@ -0,0 +1,79 @@ +#ifndef UTIL_LIST_H +#define UTIL_LIST_H + +#include +#include + +/** + * (Doubly linked) list object + */ +struct util_list { + struct util_list_node* head; + struct util_list_node* tail; +}; + +/** + * List element/node + */ +struct util_list_node { + struct util_list_node* next; +}; + +/** + * Initialize an allocated list + * + * @param list Pointer to a list to initialize + */ +void util_list_init(struct util_list* list); + +/** + * Append an element to a list + * + * @param list List to append to + * @param node Node to append to the list + */ +void util_list_append(struct util_list* list, struct util_list_node* node); + +/** + * Check if the list contains the specified element + * + * @param list List to check + * @param node Node to look for in the list + * @return True if list contains the specified node, false otherwise + */ +bool util_list_contains(struct util_list* list, struct util_list_node* node); + +/** + * Peak (get but do not remove) the head of the list + * + * @param list List to peak + * @return Head element of the list (or null if empty) + */ +struct util_list_node* util_list_peek_head(struct util_list* list); + +/** + * Pop (get and remove) the head of the list + * + * @param list List to pop + * @return Removed head element of the list (or null if empty) + */ +struct util_list_node* util_list_pop_head(struct util_list* list); + +/** + * Remove a specific element of the list. If the element is not part of the, + * the list is not modified. + * + * @param list List to remove an element from + * @param node Element to remove from the list + */ +void util_list_remove(struct util_list* list, struct util_list_node* node); + +/** + * Check if a list is empty + * + * @param list List to check + * @return True if empty, false otherwise + */ +bool util_list_empty(struct util_list* list); + +#endif diff --git a/src/main/util/log.c b/src/main/util/log.c new file mode 100644 index 0000000..cee3fc6 --- /dev/null +++ b/src/main/util/log.c @@ -0,0 +1,149 @@ +#define LOG_MODULE "util-log" + +#include +#include +#include + +#include "util/defs.h" +#include "util/log.h" +#include "util/str.h" +#include "util/time.h" + +#define COLORIZING_CONTROL_SEQ "\033[%d;%dm%s\033[0m" + +static void util_log_format(enum util_log_level level, const char* module, + const char* trace_file, size_t trace_line, const char* fmt, va_list ap); +static void util_log_writer_console(void* ctx, enum util_log_level level, const char* module, const char* trace_file, + size_t trace_line, const char* msg); +static void util_log_writer_file(void* ctx, enum util_log_level level, const char* module, const char* trace_file, + size_t trace_line, const char* msg); + +static const char util_log_level_chars[] = "DEWIM"; + +static enum util_log_level util_log_level = LOG_LEVEL_DEBUG; +static FILE* util_log_file; + +void util_log_set_file(const char* path, bool append) +{ + const char* mode = append ? "a+" : "w+"; + + if (path) { + util_log_file = fopen(path, mode); + log_info("Open log file: %s", path); + } +} + +void util_log_set_level(enum util_log_level new_level) +{ + util_log_level = new_level; +} + +FILE* util_log_get_file_handle(void) +{ + return util_log_file; +} + +void util_log(enum util_log_level level, const char* module, const char* trace_file, size_t trace_line, + const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + util_log_format(level, module, trace_file, trace_line, fmt, ap); + va_end(ap); +} + +static void util_log_colorize_text(enum util_log_level level, const char* text, char* buffer) +{ + switch (level) { + case LOG_LEVEL_DEBUG: + sprintf(buffer, COLORIZING_CONTROL_SEQ, 0, 37, text); + break; + + case LOG_LEVEL_INFO: + sprintf(buffer, COLORIZING_CONTROL_SEQ, 0, 34, text); + break; + + case LOG_LEVEL_WARN: + sprintf(buffer, COLORIZING_CONTROL_SEQ, 0, 33, text); + break; + + case LOG_LEVEL_ERROR: + sprintf(buffer, COLORIZING_CONTROL_SEQ, 0, 31, text); + break; + + case LOG_LEVEL_DIE: + sprintf(buffer, COLORIZING_CONTROL_SEQ, 0, 31, text); + break; + + default: + log_die_illegal_state(); + break; + } +} + +static void util_log_format(enum util_log_level level, const char* module, + const char* trace_file, size_t trace_line, const char* fmt, va_list ap) +{ + char msg[8192]; + + if (level <= util_log_level) { + util_str_vformat(msg, sizeof(msg), fmt, ap); + + util_log_writer_console(NULL, level, module, trace_file, trace_line, msg); + + if (util_log_file != NULL) { + util_log_writer_file(util_log_file, level, module, trace_file, trace_line, msg); + } + } + + if (level == LOG_LEVEL_DIE) { + abort(); + } +} + +static void util_log_writer_console(void* ctx, enum util_log_level level, const char* module, const char* trace_file, + size_t trace_line, const char* msg) +{ + char text_level[128]; + char text_level_color[128]; + + util_str_format(text_level, sizeof(text_level), "[%c]", util_log_level_chars[level]); + util_log_colorize_text(level, text_level, text_level_color); + + // --------------------------- + + struct util_time_timestamp timestamp; + + util_time_get_current_time(×tamp); + + // --------------------------- + + char line[8192]; + + util_str_format(line, sizeof(line), "%s[%d/%d/%d-%d:%d:%d:%d][%s][%s:%d] %s\n", text_level_color, timestamp.year, + timestamp.month, timestamp.day, timestamp.hour, timestamp.min, timestamp.sec, timestamp.millisec, + module, trace_file, trace_line, msg); + + printf("%s", line); +} + +static void util_log_writer_file(void* ctx, enum util_log_level level, const char* module, const char* trace_file, + size_t trace_line, const char* msg) +{ + struct util_time_timestamp timestamp; + + util_time_get_current_time(×tamp); + + // --------------------------- + + char line[8192]; + size_t line_len; + + line_len = util_str_format(line, sizeof(line), "[%c][%d/%d/%d-%d:%d:%d:%d][%s][%s:%d] %s\n", + util_log_level_chars[level], timestamp.year, timestamp.month, timestamp.day, timestamp.hour, timestamp.min, + timestamp.sec, timestamp.millisec, module, trace_file, trace_line, msg); + + fwrite(line, 1, line_len, (FILE*) ctx); + fflush((FILE*) ctx); +} \ No newline at end of file diff --git a/src/main/util/log.h b/src/main/util/log.h new file mode 100644 index 0000000..708a0fb --- /dev/null +++ b/src/main/util/log.h @@ -0,0 +1,97 @@ +#ifndef UTIL_LOG_H +#define UTIL_LOG_H + +#include +#include +#include + +#include "util/defs.h" + +#ifndef LOG_MODULE +#define LOG_MODULE FILENAME +#endif + +#ifndef LOG_SUPPRESS + +/* Don't expose source file names and absolute paths on public releases */ +#ifdef PT_PUBLIC_RELEASE +#undef FILENAME +#define FILENAME "" +#define SOURCE_LINE 0 +#else +#define SOURCE_LINE __LINE__ +#endif + +#define log_debug(...) util_log(LOG_LEVEL_DEBUG, LOG_MODULE, FILENAME, SOURCE_LINE, __VA_ARGS__) +#define log_info(...) util_log(LOG_LEVEL_INFO, LOG_MODULE, FILENAME, SOURCE_LINE, __VA_ARGS__) +#define log_warn(...) util_log(LOG_LEVEL_WARN, LOG_MODULE, FILENAME, SOURCE_LINE, __VA_ARGS__) +#define log_error(...) util_log(LOG_LEVEL_ERROR, LOG_MODULE, FILENAME, SOURCE_LINE, __VA_ARGS__) +#define log_die(...) util_log(LOG_LEVEL_DIE, LOG_MODULE, FILENAME, SOURCE_LINE, __VA_ARGS__) +#define log_die_illegal_state() util_log(LOG_LEVEL_DIE, LOG_MODULE, FILENAME, SOURCE_LINE, "Illegal state") + +#else + +#define log_debug(...) +#define log_info(...) +#define log_warn(...) +#define log_error(...) +#define log_die(...) + +#endif + +#define log_assert(x) \ + do { \ + if (!(x)) { \ + log_die(FILENAME, __LINE__, __FUNCTION__); \ + } \ + } while (0) + +/** + * Different log levels for logging + */ +enum util_log_level { + LOG_LEVEL_DIE = 0, + LOG_LEVEL_ERROR = 1, + LOG_LEVEL_WARN = 2, + LOG_LEVEL_INFO = 3, + LOG_LEVEL_DEBUG = 4 +}; + +/** + * Open a log file to write log output to in addition to stdout + * + * @param path Path of the log file (existing files are overwritten of append argument false) + * @param append True to append to an existing file, false to create a new file with the specified name + */ +void util_log_set_file(const char* path, bool append); + +/** + * Set the log level for logging + * + * @param new_level New log level + */ +void util_log_set_level(enum util_log_level new_level); + +/** + * Get the log file handle (required for hooking to filter it on fwrite calls on the filehook monitor) + * + * @return FILE handle of log file or NULL if logging to file disabled + */ +FILE* util_log_get_file_handle(void); + +/** + * Log a message + * + * Don't use this call to log something, use the macros instead + * + * @param level Log level of the message + * @param module Name of the module where this function is called + * @param trace_file Additional trace information, file name + * @param trace_line Additional trace information, line number + * @param fmt Format string of log message + * @param ... Arguments for format string + */ +void util_log(enum util_log_level level, const char* module, const char* trace_file, size_t trace_line, + const char* fmt, ...); + +#endif \ No newline at end of file diff --git a/src/main/util/mem.c b/src/main/util/mem.c new file mode 100644 index 0000000..b6923c7 --- /dev/null +++ b/src/main/util/mem.c @@ -0,0 +1,108 @@ +#define LOG_MODULE "util-mem" + +#include +#include +#include + +#include "util/log.h" +#include "util/mem.h" + +static bool _util_mem_debug_output; + +static void* _util_xmalloc(size_t nbytes); +static void* _util_xrealloc(void* mem, size_t nbytes); +static void _util_xfree(void** mem); + +static const struct util_mem_interface _util_mem_interface_default = { + .xmalloc = _util_xmalloc, + .xrealloc = _util_xrealloc, + .xfree = _util_xfree, +}; + +static const struct util_mem_interface* _util_mem_interface = &_util_mem_interface_default; + +static void* _util_xmalloc(size_t nbytes) +{ + void* mem; + + mem = malloc(nbytes); + + if (_util_mem_debug_output) { + log_debug("xmalloc(%d): %p", nbytes, mem); + } + + if (mem == NULL) { + log_die("xmalloc(%u) failed", (uint32_t) nbytes); + return NULL; + } + + return mem; +} + +static void* _util_xrealloc(void* mem, size_t nbytes) +{ + void* newmem; + + newmem = realloc(mem, nbytes); + + if (_util_mem_debug_output) { + log_debug("xrealloc(%p %d): %p", mem, nbytes, newmem); + } + + if (newmem == NULL) { + log_die("xrealloc(%p, %u) failed", mem, (uint32_t) nbytes); + return NULL; + } + + return newmem; +} + +static void _util_xfree(void** mem) +{ + log_assert(mem); + + if (*mem != NULL) { + if (_util_mem_debug_output) { + log_debug("xfree(%p %p)", mem, *mem); + } + + free(*mem); + *mem = NULL; + } else { + if (_util_mem_debug_output) { + log_warn("xfree(%p NULL)", mem); + } + } +} + +void util_mem_init(const struct util_mem_interface* interface) +{ + log_assert(interface); + + _util_mem_interface = interface; +} + +void util_mem_init_default() +{ + _util_mem_interface = &_util_mem_interface_default; +} + +void util_mem_enable_debug_output() +{ + _util_mem_debug_output = true; +} + +void* util_xmalloc(size_t nbytes) +{ + return _util_mem_interface->xmalloc(nbytes); +} + +void* util_xrealloc(void* mem, size_t nbytes) +{ + return _util_mem_interface->xrealloc(mem, nbytes); +} + +void util_xfree(void** mem) +{ + _util_mem_interface->xfree(mem); +} \ No newline at end of file diff --git a/src/main/util/mem.h b/src/main/util/mem.h new file mode 100644 index 0000000..5eaedb1 --- /dev/null +++ b/src/main/util/mem.h @@ -0,0 +1,57 @@ +#ifndef UTIL_MEM_H +#define UTIL_MEM_H + +#include +#include + +typedef void* (*util_xmalloc_t)(size_t nbytes); +typedef void* (*util_xrealloc_t)(void* mem, size_t nbytes); +typedef void (*util_free_t)(void** mem); + +struct util_mem_interface { + util_xmalloc_t xmalloc; + util_xrealloc_t xrealloc; + util_free_t xfree; +}; + +/** + * Install a different memory interfacee, e.g. to use a different allocator or hooks for unit-testing. + */ +void util_mem_init(const struct util_mem_interface* interface); + +/** + * Optional, initialize the default memory interface. This needs to be called only if you want to switch back from + * a non-default interface that got installed using util_mem_init. + */ +void util_mem_init_default(); + +/** + * Enable debug output of all allocations and de-allocations that go through the default interface of this module. + */ +void util_mem_enable_debug_output(); + +/** + * Allocate memory on the heap (malloc wrapper) + * + * @param nbytes Number of bytes to allocate + * @return Pointer to allocated memory + */ +void* util_xmalloc(size_t nbytes); + +/** + * Realloc memory (realloc wrapper) + * + * @param mem Pointer to allocated memory to realloc/expand + * @param nbytes New size of memory region + * @return Pointer to reallocated memory region + */ +void* util_xrealloc(void* mem, size_t nbytes); + +/** + * Free allocated memory. + * + * @param mem Pointer to the pointer with allocated memory to free. The pointer to the memory will be set to NULL. + */ +void util_xfree(void** mem); + +#endif diff --git a/src/main/util/net.c b/src/main/util/net.c new file mode 100644 index 0000000..c65fd65 --- /dev/null +++ b/src/main/util/net.c @@ -0,0 +1,41 @@ +#include +#include + +#include "util/net.h" +#include "util/str.h" + +bool util_net_split_ipv4_str(const char* str, char** address, uint16_t* port) +{ + char* tmp = util_str_dup(str); + char* pch = strtok(tmp, ":"); + int counter = 0; + + *address = NULL; + *port = 0xFFFF; + + while (pch != NULL) { + switch (counter) { + case 0: + *address = util_str_dup(pch); + break; + + case 1: + *port = (uint16_t) strtol(pch, NULL, 10); + break; + + default: + if (*address) { + free(*address); + } + + free(tmp); + return false; + } + + pch = strtok (NULL, ":"); + counter++; + } + + free(tmp); + return true; +} \ No newline at end of file diff --git a/src/main/util/net.h b/src/main/util/net.h new file mode 100644 index 0000000..1aac758 --- /dev/null +++ b/src/main/util/net.h @@ -0,0 +1,17 @@ +#ifndef UTIL_NET_H +#define UTIL_NET_H + +#include +#include + +/** + * Split an ipv4 string (e.g. 127.0.0.1:22 or just 127.0.0.1) into address and port + * + * @param str String to split + * @param address Address part (must be free'd by caller) + * @param port Port part (or -1 if it doesn't exist) + * @return True if successful, false on format error + */ +bool util_net_split_ipv4_str(const char* str, char** address, uint16_t* port); + +#endif diff --git a/src/main/util/options.c b/src/main/util/options.c new file mode 100644 index 0000000..b086b22 --- /dev/null +++ b/src/main/util/options.c @@ -0,0 +1,758 @@ +#define LOG_MODULE "util-options" + +#include +#include +#include + +#include "util/defs.h" +#include "util/fs.h" +#include "util/hex.h" +#include "util/log.h" +#include "util/mem.h" +#include "util/options.h" +#include "util/str.h" + +static struct util_options_opts* util_options_get2(int argc, char** argv, + const struct util_options_defs* option_defs); +static bool util_options_init_from_args(struct util_options_opts* options, + const struct util_options_defs* option_defs, int argc, char** argv); +static bool util_options_init_from_file(struct util_options_opts* options, + const struct util_options_defs* option_defs, char* file_path); +static bool util_options_write_default_options_file( + const struct util_options_defs* option_defs, char* file_path); + +static int util_options_argc; +static char** util_options_argv; + +void util_options_init(int argc, char** argv) +{ + util_options_argc = argc; + util_options_argv = argv; +} + +struct util_options_opts* util_options_get( + const struct util_options_defs* option_defs) +{ + struct util_options_opts* options; + + options = util_options_get2(util_options_argc, util_options_argv, + option_defs); + + if (!options) { + exit(0); + } + + return options; +} + +int util_options_get_int(const struct util_options_opts* opts, + const char* name) +{ + for (uint32_t i = 0; i < opts->entries; i++) { + + if (!strcmp(opts->defs->defs[i].name, name)) { + + if (opts->defs->defs[i].type != UTIL_OPTIONS_TYPE_INT) { + log_warn("Option %s is not of type int", name); + return 0; + } + + if (opts->values[i].avail) { + return opts->values[i].value.i; + } else { + return opts->defs->defs[i].default_value.i; + } + } + } + + log_warn("Could not find int option %s", name); + return 0; +} + +double util_options_get_double(const struct util_options_opts* opts, const char* name) +{ + for (uint32_t i = 0; i < opts->entries; i++) { + + if (!strcmp(opts->defs->defs[i].name, name)) { + + if (opts->defs->defs[i].type != UTIL_OPTIONS_TYPE_DOUBLE) { + log_warn("Option %s is not of type double", name); + return 0; + } + + if (opts->values[i].avail) { + return opts->values[i].value.d; + } else { + return opts->defs->defs[i].default_value.d; + } + } + } + + log_warn("Could not find double option %s", name); + return 0; +} + +const char* util_options_get_str(const struct util_options_opts* opts, + const char* name) +{ + for (uint32_t i = 0; i < opts->entries; i++) { + + if (!strcmp(opts->defs->defs[i].name, name)) { + + if (opts->defs->defs[i].type != UTIL_OPTIONS_TYPE_STR) { + log_warn("Option %s is not of type str", name); + return 0; + } + + if (opts->values[i].avail) { + return opts->values[i].value.str; + } else { + return opts->defs->defs[i].default_value.str; + } + } + } + + log_warn("Could not find str option %s", name); + return NULL; +} + +bool util_options_get_bool(const struct util_options_opts* opts, + const char* name) +{ + for (uint32_t i = 0; i < opts->entries; i++) { + + if (!strcmp(opts->defs->defs[i].name, name)) { + + if (opts->defs->defs[i].type != UTIL_OPTIONS_TYPE_BOOL) { + log_warn("Option %s is not of type bool", name); + return false; + } + + if (opts->values[i].avail) { + return opts->values[i].value.b; + } else { + return opts->defs->defs[i].default_value.b; + } + } + } + + log_warn("Could not find bool option %s", name); + return false; +} + +const uint8_t* util_options_get_bin(const struct util_options_opts* opts, + const char* name, size_t* length) +{ + for (uint32_t i = 0; i < opts->entries; i++) { + + if (!strcmp(opts->defs->defs[i].name, name)) { + + if (opts->defs->defs[i].type != UTIL_OPTIONS_TYPE_BIN) { + log_warn("Option %s is not of type bin", name); + return 0; + } + + if (opts->values[i].avail) { + if (length) { + *length = opts->values[i].value.bin.len; + } + + return opts->values[i].value.bin.data; + } else { + if (length) { + *length = opts->defs->defs[i].default_value.bin.len; + } + + return opts->defs->defs[i].default_value.bin.data; + } + } + } + + log_warn("Could not find bin option %s", name); + return NULL; +} + +void util_options_print_usage(const struct util_options_defs* option_defs) +{ + printf( + "%s\nSpecify options as arguments or use --options [path file] to " + "specify a config file containing key=value per line entries\n", + option_defs->usage_header); + + for (uint32_t i = 0; i < option_defs->ndefs; i++) { + + switch (option_defs->defs[i].type) { + case UTIL_OPTIONS_TYPE_INT: + { + printf( + " -%c [int] %s: %s\n" + " default: %d\n", + option_defs->defs[i].param, option_defs->defs[i].name, + option_defs->defs[i].description, + option_defs->defs[i].default_value.i); + + break; + } + + case UTIL_OPTIONS_TYPE_DOUBLE: + { + printf( + " -%c [double] %s: %s\n" + " default: %f\n", + option_defs->defs[i].param, option_defs->defs[i].name, + option_defs->defs[i].description, + option_defs->defs[i].default_value.d); + + break; + } + + case UTIL_OPTIONS_TYPE_STR: + { + printf( + " -%c [str] %s: %s\n" + " default: %s\n", + option_defs->defs[i].param, option_defs->defs[i].name, + option_defs->defs[i].description, + option_defs->defs[i].default_value.str); + + break; + } + + case UTIL_OPTIONS_TYPE_BOOL: + { + printf( + " -%c [bool] %s: %s\n" + " default: %d\n", + option_defs->defs[i].param, option_defs->defs[i].name, + option_defs->defs[i].description, + option_defs->defs[i].default_value.b); + + break; + } + + case UTIL_OPTIONS_TYPE_BIN: + { + size_t len = option_defs->defs[i].default_value.bin.len * 2 + 1; + char* buf = util_xmalloc(len); + + util_hex_encode_uc(option_defs->defs[i].default_value.bin.data, + option_defs->defs[i].default_value.bin.len, buf, len); + + printf( + " -%c [bin] %s: %s\n" + " default (len %Iu): %s\n", + option_defs->defs[i].param, option_defs->defs[i].name, + option_defs->defs[i].description, + option_defs->defs[i].default_value.bin.len, + buf); + + free(buf); + + break; + } + + default: + { + printf( + " -%c [unkn] %s: %s\n" + " default: -invalid-\n", + option_defs->defs[i].param, option_defs->defs[i].name, + option_defs->defs[i].description); + + break; + } + } + } +} + +void util_options_free(struct util_options_opts* opts) +{ + for (uint32_t i = 0; i < opts->entries; i++) { + + switch (opts->defs->defs[i].type) { + case UTIL_OPTIONS_TYPE_STR: + { + if (opts->values[i].value.str) { + free(opts->values[i].value.str); + } + + break; + } + + case UTIL_OPTIONS_TYPE_BIN: + { + if (opts->values[i].value.bin.data) { + free(opts->values[i].value.bin.data); + } + + break; + } + + default: + break; + } + } + + free(opts->values); + free(opts); +} + +// if NULL, usage is printed +static struct util_options_opts* util_options_get2( + int argc, char** argv, const struct util_options_defs* option_defs) +{ + bool success; + char* options_file = NULL; + struct util_options_opts* options = + (struct util_options_opts*) + util_xmalloc(sizeof(struct util_options_opts)); + + if (option_defs == NULL) { + options->defs = NULL; + options->values = NULL; + options->entries = 0; + + return options; + } + + options->defs = option_defs; + options->entries = option_defs->ndefs; + + options->values = (struct util_options_value*) util_xmalloc( + sizeof(struct util_options_value) * options->entries); + + memset(options->values, 0, + sizeof(struct util_options_value) * options->entries); + + /* detect if we read options from a file */ + for (int i = 0; i < argc; i++) { + + if (!strcmp(argv[i], "--options")) { + + if (i + 1 >= argc) { + log_warn("Missing argument for --options\n"); + + util_options_print_usage(option_defs); + util_options_free(options); + return NULL; + } + + options_file = argv[i + 1]; + break; + } + } + + if (options_file) { + log_debug("Processing options file %s", options_file); + success = util_options_init_from_file(options, option_defs, + options_file); + + if (!success) { + exit(0); + } + } + + log_debug("Processing cmd args"); + success = util_options_init_from_args(options, option_defs, argc, argv); + + if (!success) { + util_options_print_usage(option_defs); + util_options_free(options); + return NULL; + } else { + return options; + } +} + +static bool util_options_init_from_args(struct util_options_opts* options, + const struct util_options_defs* option_defs, int argc, char** argv) +{ + for (int i = 0; i < argc; i++) { + + if (argv[i][0] != '-') { + continue; + } + + for (uint32_t j = 0; j < options->entries; j++) { + /* prints usage */ + if (argv[i][1] == option_defs->usage_param) { + return false; + } + + if (options->defs->defs[j].param == argv[i][1]) { + + switch (options->defs->defs[j].type) { + case UTIL_OPTIONS_TYPE_INT: + { + if (i + 1 >= argc) { + break; + } + + ++i; + + options->values[j].value.i = atoi(argv[i]); + + break; + } + + case UTIL_OPTIONS_TYPE_DOUBLE: + { + if (i + 1 >= argc) { + break; + } + + ++i; + + options->values[j].value.d = atof(argv[i]); + + break; + } + + case UTIL_OPTIONS_TYPE_STR: + { + if (i + 1 >= argc) { + break; + } + + ++i; + + /* FIXME memory leak if overriding string from options + file */ + + if (strlen(argv[i]) > 0) { + options->values[j].value.str = util_str_dup(argv[i]); + } else { + options->values[j].value.str = NULL; + } + + break; + } + + case UTIL_OPTIONS_TYPE_BOOL: + { + options->values[j].value.b = true; + + break; + } + + case UTIL_OPTIONS_TYPE_BIN: + { + if (i + 1 >= argc) { + break; + } + + ++i; + + /* FIXME memory leak if overriding bin data from options + file */ + + if (strlen(argv[i]) > 0) { + size_t len = strlen(argv[i]); + len = len / 2 + len % 2; + + options->values[j].value.bin.len = len; + options->values[j].value.bin.data = util_xmalloc(len); + + if (!util_hex_decode(options->values[j].value.bin.data, + options->values[j].value.bin.len, argv[i], + strlen(argv[i]))) { + log_warn("Decoding binary data for key %s " + "failed", options->defs->defs[j].name); + options->values[j].value.bin.len = 0; + options->values[j].value.bin.data = NULL; + } + } else { + options->values[j].value.bin.len = 0; + options->values[j].value.bin.data = NULL; + } + + break; + } + + default: + continue; + } + + log_debug("Cmd arg: -%c %s (type %d): %s", + options->defs->defs[j].param, options->defs->defs[j].name, + options->defs->defs[j].type, argv[i]); + + options->values[j].avail = true; + break; + } + } + } + + return true; +} + + + +static bool util_options_init_from_file(struct util_options_opts* options, + const struct util_options_defs* option_defs, char* file_path) +{ + char* pos_lines; + char* pos_key_val; + char* ctx_lines; + char* ctx_key_val; + char* data; + size_t len; + + if (!util_file_load(file_path, (void**) &data, &len, true)) { + /* If file does not exist, create one with default configuration + values */ + if (util_fs_path_exists(file_path)) { + log_warn("Loading options file %s failed, faulty or corrupted " + "configuration", file_path); + } else { + /* Create a default config file if the usage param is set. This + indicates that we are currently not on the inject process + but already on the inject library. We want all config params + of the inject lib */ + if (option_defs->usage_param) { + log_info("Options file %s does not exist, creating default...", + file_path); + + if (!util_options_write_default_options_file(option_defs, + file_path)) { + log_warn("Creating default options file %s failed", + file_path); + } else { + log_info("Options file %s created. Tweak it to your needs and restart the game.", + file_path); + } + } + } + + return false; + } + + pos_lines = strtok_r(data, "\n", &ctx_lines); + + while (pos_lines != NULL) { + char* pos_line_dup; + char* key = NULL; + char* val = NULL; + + /* ignore comments and empty lines */ + if (strlen(pos_lines) > 0 && pos_lines[0] != '#') { + pos_line_dup = util_str_dup(pos_lines); + pos_key_val = strtok_r(pos_line_dup, "=", &ctx_key_val); + + log_debug("Line: %s", pos_lines); + + if (pos_key_val != NULL) { + key = pos_key_val; + val = &pos_key_val[strlen(key) + 1]; + } else { + log_warn("Invalid options line %s in options file %s", pos_lines, file_path); + free(pos_line_dup); + free(data); + return false; + } + + log_debug("Key: %s, Value: %s", key, val); + + /* search for key */ + for (uint32_t j = 0; j < options->entries; j++) { + + if (!strcmp(options->defs->defs[j].name, key)) { + + switch (options->defs->defs[j].type) { + case UTIL_OPTIONS_TYPE_INT: + { + if (val) { + options->values[j].value.i = atoi(val); + } else { + options->values[j].value.i = 0; + } + + break; + } + + case UTIL_OPTIONS_TYPE_DOUBLE: + { + if (val) { + options->values[j].value.d = atof(val); + } else { + options->values[j].value.d = 0.0; + } + + break; + } + + case UTIL_OPTIONS_TYPE_STR: + { + if (val && strlen(val) > 0) { + options->values[j].value.str = + util_str_dup(val); + } else { + options->values[j].value.str = NULL; + } + + break; + } + + case UTIL_OPTIONS_TYPE_BOOL: + { + if (val) { + if (atoi(val) <= 0) { + options->values[j].value.b = false; + } else { + options->values[j].value.b = true; + } + } else { + options->values[j].value.b = false; + } + + + break; + } + + case UTIL_OPTIONS_TYPE_BIN: + { + if (val && strlen(val) > 0) { + size_t lenBin = strlen(val); + lenBin = lenBin / 2 + lenBin % 2; + + options->values[j].value.bin.len = lenBin; + options->values[j].value.bin.data = + util_xmalloc(lenBin); + + if (!util_hex_decode( + options->values[j].value.bin.data, + options->values[j].value.bin.len, val, + strlen(val))) { + log_warn("Decoding binary data for key " + "%s failed", + options->defs->defs[j].name); + options->values[j].value.bin.len = 0; + options->values[j].value.bin.data = NULL; + } + } else { + options->values[j].value.bin.len = 0; + options->values[j].value.bin.data = NULL; + } + + break; + } + + default: + continue; + } + + log_debug("Options file param: -%c %s (type %d): %s", + options->defs->defs[j].param, + options->defs->defs[j].name, + options->defs->defs[j].type, val ? val : "NULL"); + + options->values[j].avail = true; + break; + } + } + + free(pos_line_dup); + } + + pos_lines = strtok_r(NULL, "\n", &ctx_lines); + } + + free(data); + + return true; +} + +static bool util_options_write_default_options_file( + const struct util_options_defs* option_defs, char* file_path) +{ + FILE* file; + + file = fopen(file_path, "wb"); + + if (file == NULL) { + return false; + } + + for (uint32_t i = 0; i < option_defs->ndefs; i++) { + + switch (option_defs->defs[i].type) { + case UTIL_OPTIONS_TYPE_INT: + { + fprintf(file, "# [int]: %s\n", + option_defs->defs[i].description); + fprintf(file, "%s=%d\n\n", option_defs->defs[i].name, + option_defs->defs[i].default_value.i); + + break; + } + + case UTIL_OPTIONS_TYPE_DOUBLE: + { + fprintf(file, "# [double]: %s\n", + option_defs->defs[i].description); + fprintf(file, "%s=%f\n\n", option_defs->defs[i].name, + option_defs->defs[i].default_value.d); + + break; + } + + case UTIL_OPTIONS_TYPE_STR: + { + fprintf(file, "# [str]: %s\n", + option_defs->defs[i].description); + + if (option_defs->defs[i].default_value.str) { + fprintf(file, "%s=%s\n\n", option_defs->defs[i].name, + option_defs->defs[i].default_value.str); + } else { + fprintf(file, "%s=\n\n", option_defs->defs[i].name); + } + + break; + } + + case UTIL_OPTIONS_TYPE_BOOL: + { + fprintf(file, "# [bool (0/1)]: %s\n", + option_defs->defs[i].description); + fprintf(file, "%s=%d\n\n", option_defs->defs[i].name, + option_defs->defs[i].default_value.b); + + break; + } + + case UTIL_OPTIONS_TYPE_BIN: + { + fprintf(file, "# [bin]: %s\n", + option_defs->defs[i].description); + + if (option_defs->defs[i].default_value.bin.data) { + size_t len = + option_defs->defs[i].default_value.bin.len * 2 + 1; + char* buf = util_xmalloc(len); + + util_hex_encode_uc( + option_defs->defs[i].default_value.bin.data, + option_defs->defs[i].default_value.bin.len, buf, len); + + + fprintf(file, "%s=%s\n\n", option_defs->defs[i].name, buf); + + free(buf); + } + + break; + } + + default: + { + log_warn("Invalid value type %d found", + option_defs->defs[i].type); + break; + } + } + } + + fclose(file); + return true; +} \ No newline at end of file diff --git a/src/main/util/options.h b/src/main/util/options.h new file mode 100644 index 0000000..6a871c6 --- /dev/null +++ b/src/main/util/options.h @@ -0,0 +1,170 @@ +#ifndef UTIL_OPTIONS_H +#define UTIL_OPTIONS_H + +#include +#include +#include + +/** + * Value types support by option values + */ +enum util_options_type { + UTIL_OPTIONS_TYPE_INT, + UTIL_OPTIONS_TYPE_DOUBLE, + UTIL_OPTIONS_TYPE_STR, + UTIL_OPTIONS_TYPE_BOOL, + UTIL_OPTIONS_TYPE_BIN, +}; + +/** + * Single option definition element. Use this to define possible option + * key-value tuples to parse from an argument list + */ +struct util_options_def { + const char* name; + const char* description; + const char param; + enum util_options_type type; + union { + int i; + double d; + const char* str; + bool b; + struct { + const uint8_t* data; + size_t len; + } bin; + } default_value; +}; + +/** + * Option definitions. A list of key-value elements defining option values + * as well as usage information for the application + */ +struct util_options_defs { + const char* usage_header; + char usage_param; + const struct util_options_def* defs; + uint32_t ndefs; +}; + +/** + * A single option value as part of the key-value tuple + */ +struct util_options_value { + bool avail; + union { + int i; + double d; + char* str; + bool b; + struct { + uint8_t* data; + size_t len; + } bin; + } value; +}; + +/** + * Parsed option values based on an options definition + */ +struct util_options_opts { + const struct util_options_defs* defs; + struct util_options_value* values; + uint32_t entries; +}; + +/** + * Initialize the options module by passing the arguments from main on + * + * Make sure to call this as early as possible when entering main and before + * any other calls of this module are invoked + * + * @param argc Argc from main + * @param argv Argv from main + */ +void util_options_init(int argc, char** argv); + +/** + * Get options from the initial argument list + * + * @param option_defs A list of option definitions. The arguments are parsed + * using this list. If NULL, usage information with the definition + * is printed and this call also returns NULL. + * @return A list of parsed options based on the specified definitions or + * NULL if usage information was printed. + */ +struct util_options_opts* util_options_get( + const struct util_options_defs* option_defs); + +/** + * Get an integer value from an options list + * + * @param opts Parsed options list received from util_options_get + * @param name Name of the options key + * @return Value of the options key converted to an integer value. If the type + * of the options key is not matching, 0 is returned + */ +int util_options_get_int(const struct util_options_opts* opts, + const char* name); + +/** + * Get a double value from an options list + * + * @param opts Parsed options list received from util_options_get + * @param name Name of the options key + * @return Value of the options key converted to a double value. If the type + * of the options key is not matching, 0 is returned + */ +double util_options_get_double(const struct util_options_opts* opts, const char* name); + +/** + * Get a string from an options list + * + * @param opts Parsed options list received from util_options_get + * @param name Name of the options key + * @return Value of the options key converted to a string. If the type + * of the options key is not matching, NULL is returned + */ +const char* util_options_get_str(const struct util_options_opts* opts, + const char* name); + +/** + * Get a bool value from an options list + * + * @param opts Parsed options list received from util_options_get + * @param name Name of the options key + * @return Value of the options key converted to a bool value. If the type + * of the options key is not matching, false is returned + */ +bool util_options_get_bool(const struct util_options_opts* opts, + const char* name); + +/** + * Get binary data from an options list + * + * @param opts Parsed options list received from util_options_get + * @param name Name of the options key + * @param length Pointer to return the length of the binary data to + * (set to NULL if not required) + * @return Value of the options key converted to a binary array. If the type + * of the options key is not matching, NULL is returned + */ +const uint8_t* util_options_get_bin(const struct util_options_opts* opts, + const char* name, size_t* length); + +/** + * Print usage information + * + * @param option_defs A list of option definitions. + */ +void util_options_print_usage(const struct util_options_defs* opts); + +/** + * Free an options list + * + * @param opts Options list to free + */ +void util_options_free(struct util_options_opts* opts); + +#endif diff --git a/src/main/util/patch.c b/src/main/util/patch.c new file mode 100644 index 0000000..61bcb52 --- /dev/null +++ b/src/main/util/patch.c @@ -0,0 +1,59 @@ +#define LOG_MODULE "util-patch" + +#include + +#include "util/log.h" +#include "util/patch.h" + +/* addr needs to be a multiple of page size */ +#define UNPROTECT(addr,len) mprotect((void*)(addr-(addr%4096)),(addr%4096) + \ + len,PROT_READ|PROT_WRITE|PROT_EXEC) + +void util_patch_write_memory(uintptr_t mem, const void* data, size_t len) +{ + if (UNPROTECT(mem, len) != 0) { + log_die("Unprotecting memory %p, len %d failed", mem, len); + } + + memcpy((void*) mem, data, len); +} + +void util_patch_write_memory_byte(uintptr_t mem, uint8_t data) +{ + util_patch_write_memory(mem, (const void*) &data, sizeof(uint8_t)); +} + +void util_patch_function(uintptr_t func_addr, void* detour_func) +{ + /* uintptr_t is said to be portable (32/64-bit) */ + uint8_t offset = 5; + + /* 64-bit binary */ + if (sizeof(uintptr_t) == 8) { + offset += 4; + } + + uintptr_t call = + (uintptr_t) (((uintptr_t) detour_func) - func_addr - offset); + + if (UNPROTECT(func_addr, 4096) != 0) { + log_die("Unprotecting memory %p, len %d failed", func_addr); + } + + *((uint8_t*) (func_addr)) = 0xE9; + *((uintptr_t*) (func_addr + 1)) = call; +} + +void* util_patch_find_signiture(const uint8_t* sig, uint32_t sig_len, + int32_t sig_offset, void* start_addr, void* end_addr, int32_t alignment) +{ + size_t pos; + + for (pos = (size_t) start_addr; pos < (size_t) end_addr; pos += alignment) { + if (!memcmp((void*) pos, sig, sig_len)) { + return (void*) (pos + sig_offset); + } + } + + return NULL; +} \ No newline at end of file diff --git a/src/main/util/patch.h b/src/main/util/patch.h new file mode 100644 index 0000000..1bd5249 --- /dev/null +++ b/src/main/util/patch.h @@ -0,0 +1,52 @@ +#ifndef UTIL_PATCH_H +#define UTIL_PATCH_H + +#include +#include +#include + +/** + * Write data to an arbitrary memory location + * + * @param mem Memory location to write to + * @param data Pointer to buffer with data to write + * @param len Length of data to write + */ +void util_patch_write_memory(uintptr_t mem, const void* data, size_t len); + +/** + * Write a single byte to an arbitrary memory location + * + * @param mem Memory location to write to + * @param data Byte to write + */ +void util_patch_write_memory_byte(uintptr_t mem, uint8_t data); + +/** + * Write a function ptr/function call to a memory location + * + * Use this to patch existing call instructions and detour to your own code + * + * @param func_addr Address of the function/call instruction to replace + * @param detour_func Address of your function to detour to + */ +void util_patch_function(uintptr_t func_addr, void* detour_func); + +/** + * Scan a memory area limited by a start and end address for a signiture. + * + * @param sig Buffer with signiture to search for + * @param sig_len Length of signiture + * @param sig_offset Offset that gets added to the address where the signiture + * starts (if found) + * @param start_addr Pointer to the address to start searching at + * @param end_addr Pointer to the address to stop searching at + * @param alignment Aligns the search signiture, i.e. search in steps of + * X bytes + * @return If found, pointer to memory where the signiture was found (first + * occurance) with the signiture offset added, NULL otherwise + */ +void* util_patch_find_signiture(const uint8_t* sig, uint32_t sig_len, + int32_t sig_offset, void* start_addr, void* end_addr, int32_t alignment); + +#endif \ No newline at end of file diff --git a/src/main/util/proc.c b/src/main/util/proc.c new file mode 100644 index 0000000..e78ee6e --- /dev/null +++ b/src/main/util/proc.c @@ -0,0 +1,218 @@ +#define LOG_MODULE "util-proc" + +#include +#include +#include +#include + +#include "util/log.h" +#include "util/proc.h" +#include "util/str.h" + +static char* util_sys_read_entries_from_proc_file(const char* path, int* count_out) +{ + const char* sep = "\n"; + char* str = util_str_dup(""); + char* tmp; + + FILE* cmdline = fopen(path, "rb"); + + if (!cmdline) { + log_error("Opening %s failed: %s", path, strerror(errno)); + return str; + } + + char* arg = 0; + size_t size = 0; + int count = 0; + + while (getdelim(&arg, &size, 0, cmdline) != -1) { + if (strlen(str) > 0) { + tmp = util_str_merge(str, sep); + free(str); + str = tmp; + } + + tmp = util_str_merge(str, arg); + free(str); + str = tmp; + count++; + } + + free(arg); + fclose(cmdline); + + if (count_out) { + *count_out = count; + } + + return str; +} + +static void util_proc_log_current_working_dir() +{ + char tmp[PATH_MAX]; + + if (!getcwd(tmp, sizeof(tmp))) { + log_error("Getting current working directory failed"); + } else { + log_info("Current working directory: %s", tmp); + } +} + +static void util_proc_log_current_process() +{ + log_info("PID: %d", getpid()); +} + +static void util_sys_info_log_path_executable() +{ + char buffer[PATH_MAX]; + + if (!util_proc_get_path_executable(buffer, sizeof(buffer))) { + log_error("Getting path of executable failed"); + } else { + log_info("Executable: %s", buffer); + } +} + +static void util_sys_info_log_user() +{ + log_info("User: %d", getuid()); +} + +static void util_sys_info_log_running_as_root() +{ + log_info("Running as root: %d", util_proc_is_running_as_root()); +} + +static void util_sys_info_log_cmd_line_args() +{ + int count; + char* str = util_sys_read_entries_from_proc_file("/proc/self/cmdline", &count); + + log_info("Cmd arguments (%d):\n%s", count, str); + + free(str); +} + +static void util_sys_info_log_env_vars() +{ + int count; + char* str = util_sys_read_entries_from_proc_file("/proc/self/environ", &count); + + log_info("Environment variables (%d):\n%s", count, str); + + free(str); +} + +bool util_proc_is_running_as_root() +{ + return getuid() == 0; +} + +bool util_proc_get_path_executable(char* buffer, size_t size) +{ + // readlink does not null terminate + memset(buffer, 0, size); + + return readlink("/proc/self/exe", buffer, size) != -1; +} + +bool util_proc_get_folder_path_executable(char* buffer, size_t size) +{ + if (!util_proc_get_path_executable(buffer, size)) { + return false; + } + + size_t pos = strlen(buffer) - 1; + + // If executable in the root folder, keep the single / + while (pos > 0 && buffer[pos] != '/') { + buffer[pos] = '\0'; + pos--; + } + + // delete / + if (pos > 0) { + buffer[pos] = '\0'; + } + + return true; +} + +bool util_proc_get_folder_path_executable_no_ld_linux(char* buffer, size_t size) +{ + if (!util_proc_get_path_executable(buffer, size)) { + log_error("Getting executable path failed"); + return false; + } + + if (util_str_ends_with(buffer, "ld-linux.so.2")) { + int argc; + char* args = util_sys_read_entries_from_proc_file("/proc/self/cmdline", &argc); + + char* tok = strtok(args, "\n"); + bool awaiting_value = false; + + // Skip first token, ld-linux binary + tok = strtok(NULL, "\n"); + + while (tok != NULL) { + // Check if the next arg is declaring an option key for ld-linux + if (util_str_starts_with(tok, "--")) { + awaiting_value = 1; + } else if (awaiting_value) { + // Just hit the awaited value for the previously skipped option key + awaiting_value = false; + } else { + // Got executable arg passed to ld-linux + if (strlen(tok) > size) { + free(args); + return false; + } + + strcpy(buffer, tok); + + // Remove executable name + // If executable in the root folder, keep the single / + size_t pos = strlen(buffer) - 1; + while (pos > 0 && buffer[pos] != '/') { + buffer[pos] = '\0'; + pos--; + } + + // delete / + if (pos > 0) { + buffer[pos] = '\0'; + } + + free(args); + return true; + } + + tok = strtok(NULL, "\n"); + } + + free(args); + return false; + } else { + return util_proc_get_folder_path_executable(buffer, size); + } +} + + +void util_proc_log_info() +{ + log_info("================================ Current process information ================================"); + + util_proc_log_current_process(); + util_sys_info_log_path_executable(); + util_proc_log_current_working_dir(); + util_sys_info_log_user(); + util_sys_info_log_running_as_root(); + util_sys_info_log_cmd_line_args(); + util_sys_info_log_env_vars(); + + log_info("================================================================================================"); +} \ No newline at end of file diff --git a/src/main/util/proc.h b/src/main/util/proc.h new file mode 100644 index 0000000..601a0b8 --- /dev/null +++ b/src/main/util/proc.h @@ -0,0 +1,47 @@ +#ifndef UTIL_PROC_H +#define UTIL_PROC_H + +#include +#include + +/** + * Check if the current process is running as a root user. + * + * @return True if running as root, false otherwise. + */ +bool util_proc_is_running_as_root(); + +/** + * Get the full path of the executable of the current process (including the executable name). + * + * @param buffer Buffer to read the path into. + * @param size Size of the buffer + * @return True on success, false on failure, e.g. buffer too small. + */ +bool util_proc_get_path_executable(char* buffer, size_t size); + +/** + * Get the full path of the folder the executable of the current process is located in. + * + * @param buffer Buffer to read the path into. + * @param size Size of the buffer + * @return True on success, false on failure, e.g. buffer too small. + */ +bool util_proc_get_folder_path_executable(char* buffer, size_t size); + +/** + * Special version of util_proc_get_folder_path_executable that considers when the process is run explicitly with a + * ld-linux binary. Returns the path of the application's executable and not ld-linux in that case. + * + * @param buffer Buffer to read the path into. + * @param size Size of the buffer + * @return True on success, false on failure, e.g. buffer too small. + */ +bool util_proc_get_folder_path_executable_no_ld_linux(char* buffer, size_t size); + +/** + * Log information about the current process to the console. + */ +void util_proc_log_info(); + +#endif diff --git a/src/main/util/rand.c b/src/main/util/rand.c new file mode 100644 index 0000000..cbd365d --- /dev/null +++ b/src/main/util/rand.c @@ -0,0 +1,34 @@ +#include +#include + +#include "rand.h" + +static int _util_rand_init = 0; + +static void _util_rand_initialize() +{ + if (!_util_rand_init) { + _util_rand_init = 1; + srand((unsigned int) time(NULL)); + } +} + +uint32_t util_rand_gen_32() +{ + _util_rand_initialize(); + + return (uint32_t) rand(); +} + +uint64_t util_rand_gen_64() +{ + uint64_t value; + + _util_rand_initialize(); + + value = 0; + value |= (((uint64_t) rand()) << 32); + value |= ((uint64_t) rand()); + + return value; +} \ No newline at end of file diff --git a/src/main/util/rand.h b/src/main/util/rand.h new file mode 100644 index 0000000..cc79f0b --- /dev/null +++ b/src/main/util/rand.h @@ -0,0 +1,10 @@ +#ifndef UTIL_RAND_H +#define UTIL_RAND_H + +#include + +uint32_t util_rand_gen_32(); + +uint64_t util_rand_gen_64(); + +#endif diff --git a/src/main/util/sock-tcp.c b/src/main/util/sock-tcp.c new file mode 100644 index 0000000..655146e --- /dev/null +++ b/src/main/util/sock-tcp.c @@ -0,0 +1,185 @@ +#define LOG_MODULE "sock-tcp" + +#include +#include +#include +#include +#include + +#include "util/net.h" +#include "util/log.h" +#include "util/sock-tcp.h" + +bool util_sock_tcp_is_connectable(const char* ipv4, uint32_t timeout_ms) +{ + char* address; + uint16_t port; + + util_net_split_ipv4_str(ipv4, &address, &port); + + bool res = util_sock_tcp_is_connectable2(address, port, timeout_ms); + + free(address); + return res; +} + +bool util_sock_tcp_is_connectable2(const char* ipv4, uint16_t port, uint32_t timeout_ms) +{ + struct sockaddr_in sa; + + // store this IP address in sa: + inet_pton(AF_INET, ipv4, &(sa.sin_addr)); + + return util_sock_tcp_is_connectable3(htonl(sa.sin_addr.s_addr), port, timeout_ms); +} + +bool util_sock_tcp_is_connectable3(uint32_t ipv4, uint16_t port, uint32_t timeout_ms) +{ + int handle; + struct sockaddr_in addr; + fd_set fdset; + struct timeval tv; + bool res; + + handle = socket(AF_INET, SOCK_STREAM, 0); + + if (handle == -1) { + log_error("Creating socket (%X, %d) failed: %s", ipv4, port, strerror(errno)); + return false; + } + + fcntl(handle, F_SETFL, O_NONBLOCK); + + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(ipv4); + + connect(handle, (const struct sockaddr*) &addr, sizeof(addr)); + + FD_ZERO(&fdset); + FD_SET(handle, &fdset); + tv.tv_sec = (__time_t) (timeout_ms / 1000); + tv.tv_usec = (__suseconds_t) ((timeout_ms % 1000) * 1000); + + if (select(handle + 1, NULL, &fdset, NULL, &tv) == 1) { + int so_error; + socklen_t len = sizeof(so_error); + + getsockopt(handle, SOL_SOCKET, SO_ERROR, &so_error, &len); + + if (so_error == 0) { + res = true; + } else { + res = false; + } + } else { + res = false; + } + + close(handle); + + return res; +} + +int util_sock_tcp_connect(const char* ipv4) +{ + char* address; + uint16_t port; + + util_net_split_ipv4_str(ipv4, &address, &port); + + int res = util_sock_tcp_connect2(address, port); + + free(address); + return res; +} + +int util_sock_tcp_connect2(const char* ipv4, uint16_t port) +{ + struct sockaddr_in sa; + + // store this IP address in sa: + inet_pton(AF_INET, ipv4, &(sa.sin_addr)); + + return util_sock_tcp_connect3(htonl(sa.sin_addr.s_addr), port); +} + +int util_sock_tcp_connect3(uint32_t ipv4, uint16_t port) +{ + int handle; + struct sockaddr_in addr; + + handle = socket(AF_INET, SOCK_STREAM, 0); + + if (handle == -1) { + log_error("Creating socket (%X, %d) failed: %s", ipv4, port, strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(ipv4); + + if (connect(handle, (const struct sockaddr*) &addr, sizeof(addr)) == -1) { + close(handle); + log_error("Connecting to %X, %d failed: %s", ipv4, port, strerror(errno)); + return -1; + } + + log_debug("Connected to %X on port %d", ipv4, port); + + return handle; +} + +ssize_t util_sock_tcp_send(int handle, void* buffer, size_t size, uint32_t timeout_ms) +{ + struct timeval tv; + tv.tv_sec = (__time_t) (timeout_ms / 1000); + tv.tv_usec = (__suseconds_t) ((timeout_ms % 1000) * 1000); + + setsockopt(handle, SOL_SOCKET, SO_SNDTIMEO, (const char*) &tv, sizeof(tv)); + + ssize_t length = send(handle, buffer, size, 0); + + if (length < 0) { + // same as EWOULDBLOCK + if (errno == EAGAIN) { + // no data for non blocking + return 0; + } + + return -1; + } + + return length; +} + +ssize_t util_sock_tcp_recv(int handle, void* buffer, size_t size, uint32_t timeout_ms) +{ + struct timeval tv; + tv.tv_sec = (__time_t) (timeout_ms / 1000); + tv.tv_usec = (__suseconds_t) ((timeout_ms % 1000) * 1000); + + setsockopt(handle, SOL_SOCKET, SO_RCVTIMEO, (const char*) &tv, sizeof(tv)); + + ssize_t length = recv(handle, buffer, size, 0); + + if (length < 0) { + // same as EWOULDBLOCK + if (errno == EAGAIN) { + // no data for non blocking + return 0; + } + + return -1; + } + + return length; +} + +void util_sock_tcp_close(int handle) +{ + close(handle); +} \ No newline at end of file diff --git a/src/main/util/sock-tcp.h b/src/main/util/sock-tcp.h new file mode 100644 index 0000000..67134de --- /dev/null +++ b/src/main/util/sock-tcp.h @@ -0,0 +1,30 @@ +// +// Created by on 9/16/18. +// + +#ifndef UTIL_SOCK_TCP_H +#define UTIL_SOCK_TCP_H + +#include +#include +#include + +bool util_sock_tcp_is_connectable(const char* ipv4, uint32_t timeout_ms); + +bool util_sock_tcp_is_connectable2(const char* ipv4, uint16_t port, uint32_t timeout_ms); + +bool util_sock_tcp_is_connectable3(uint32_t ipv4, uint16_t port, uint32_t timeout_ms); + +int util_sock_tcp_connect(const char* ipv4); + +int util_sock_tcp_connect2(const char* ipv4, uint16_t port); + +int util_sock_tcp_connect3(uint32_t ipv4, uint16_t port); + +ssize_t util_sock_tcp_send(int handle, void* buffer, size_t size, uint32_t timeout_ms); + +ssize_t util_sock_tcp_recv(int handle, void* buffer, size_t size, uint32_t timeout_ms); + +void util_sock_tcp_close(int handle); + +#endif diff --git a/src/main/util/str.c b/src/main/util/str.c new file mode 100644 index 0000000..6e2c1ce --- /dev/null +++ b/src/main/util/str.c @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include + +#include "util/log.h" +#include "util/mem.h" +#include "util/str.h" + +bool util_str_starts_with(const char* haystack, const char* needle) +{ + size_t haystack_len; + size_t needle_len; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (needle_len > haystack_len) { + return false; + } + + return !memcmp(haystack, needle, needle_len); +} + +bool util_str_ends_with(const char* haystack, const char* needle) +{ + size_t haystack_len; + size_t needle_len; + + haystack_len = strlen(haystack); + needle_len = strlen(needle); + + if (needle_len > haystack_len) { + return false; + } + + return !memcmp(&haystack[haystack_len - needle_len], needle, needle_len); +} + +size_t util_str_format(char* buf, size_t nchars, const char* fmt, ...) +{ + va_list ap; + size_t result; + + va_start(ap, fmt); + result = util_str_vformat(buf, nchars, fmt, ap); + va_end(ap); + + return result; +} + +size_t util_str_vformat(char* buf, size_t nchars, const char* fmt, va_list ap) +{ + int result; + + result = vsnprintf(buf, nchars, fmt, ap); + + if (result >= (int) nchars || result < 0) { + abort(); + } + + return (size_t) result; +} + +void util_str_cat(char* dest, size_t dnchars, const char* src) +{ + size_t dlen; + size_t slen; + + dlen = strlen(dest); + slen = strlen(src); + + if (dlen + slen >= dnchars) { + abort(); + } + + memcpy(dest + dlen, src, (slen + 1) * sizeof(char)); +} + +void util_str_cpy(char* dest, size_t dnchars, const char* src) +{ + size_t slen; + + slen = strlen(src); + + if (slen >= dnchars) { + abort(); + } + + memcpy(dest, src, (slen + 1) * sizeof(char)); +} + +char* util_str_merge(const char* str1, const char* str2) +{ + char* out; + size_t str1_len; + size_t str2_len; + size_t total; + + str1_len = strlen(str1); + str2_len = strlen(str2); + total = str1_len + str2_len + 1; + + out = util_xmalloc(total); + memcpy(out, str1, str1_len); + memcpy(out + str1_len, str2, str2_len); + out[total - 1] = '\0'; + + return out; +} + +char* util_str_dup(const char* str) +{ + char *dest; + size_t nbytes; + + log_assert(str); + + nbytes = strlen(str) + 1; + dest = util_xmalloc(nbytes); + memcpy(dest, str, nbytes); + + return dest; +} + +void util_str_trim(char* str) +{ + char *pos; + + for (pos = str + strlen(str) - 1 ; pos > str ; pos--) { + if (!isspace(*pos)) { + return; + } + + *pos = '\0'; + } +} + +char* util_str_buffer(const uint8_t* buf, size_t len) +{ + char* ret; + size_t pos; + + pos = 0; + ret = util_xmalloc(len * 3 + 1); + + for (size_t i = 0; i < len; i++) { + uint8_t tmp = buf[i]; + + pos += sprintf(ret + pos, "%02X ", tmp); + } + + ret[len * 3] = '\0'; + + return ret; +} + +char** util_str_split(const char* str, const char* delim, size_t* count) +{ + char** toks; + char* copy; + char* cur; + size_t counter; + + log_assert(str); + log_assert(delim); + log_assert(count); + + *count = 0; + + copy = util_str_dup(str); + cur = strtok(copy, delim); + + // First iteration, just count items + while (cur != NULL) { + (*count)++; + cur = strtok(NULL, delim); + } + + util_xfree((void**) ©); + + toks = util_xmalloc(sizeof(char*) * (*count)); + + copy = util_str_dup(str); + cur = strtok(copy, delim); + + counter = 0; + + // Second iteration, grab tokens + while (cur != NULL) { + toks[counter] = util_str_dup(cur); + counter++; + + cur = strtok(NULL, delim); + } + + util_xfree((void**) ©); + + return toks; +} + +void util_str_free_split(char** split, size_t count) +{ + log_assert(split); + + for (int i = 0; i < count; i++) { + util_xfree((void**) &split[i]); + } + + util_xfree((void**) &split); +} \ No newline at end of file diff --git a/src/main/util/str.h b/src/main/util/str.h new file mode 100644 index 0000000..7720d5a --- /dev/null +++ b/src/main/util/str.h @@ -0,0 +1,107 @@ +#ifndef UTIL_STR_H +#define UTIL_STR_H + +#include +#include +#include +#include + +/** + * Check if a string starts with a specific (sub-) string + * + * @param haystack String to check + * @param needle String to compare with start of haystack + * @return True if haystack ends with needle, false otherwise + */ +bool util_str_starts_with(const char* haystack, const char* needle); + +/** + * Check if a string ends with a specific (sub-) string + * + * @param haystack String to check + * @param needle String to compare with end of haystack + * @return True if haystack ends with needle, false otherwise + */ +bool util_str_ends_with(const char* haystack, const char* needle); + +/** + * Print a formated string to a buffer + * + * @param buf Allocated buffer to print to + * @param nchars Size of allocated buffer + * @param fmt Format of the string to print + * @param ... Arguments for the format string + * @return Size of the string printed to the buffer + */ +size_t util_str_format(char* buf, size_t nchars, const char* fmt, ...); + +/** + * Print a formated string to a buffer + * + * @param buf Allocated buffer to print to + * @param nchars Size of allocated buffer + * @param fmt Format of the string to print + * @param ap Argument list for format + * @return Size of the string printed to the buffer + */ +size_t util_str_vformat(char* buf, size_t nchars, const char* fmt, va_list ap); + +/** + * Concat a string to another one + * + * @param dest Destination to append to + * @param dnchars Size of dest buffer + * @param src Source string to concat to dest + */ +void util_str_cat(char* dest, size_t dnchars, const char* src); + +/** + * Copy a string + * + * @param dest Destination to copy to + * @param dnchars Size of destination + * @param src Source string to copy from (to dest) + */ +void util_str_cpy(char* dest, size_t dnchars, const char* src); + +/** + * Merge to strings (str1 + str2) + * + * @param str1 String 1 to merge + * @param str2 String 2 to merge + * @return Newly allocated merged string: str1 + str2. Caller has to manage + * memory returned + */ +char* util_str_merge(const char* str1, const char* str2); + +/** + * Duplicate a string + * + * @param src String to duplicate + * @return Duplicated string (newly allocated memory). Caller has to manage + * memory returned + */ +char* util_str_dup(const char* src); + +/** + * Remove trailing spaces of a string + * + * @param str String to trim + */ +void util_str_trim(char* str); + +/** + * Print a buffer as hex digits to a string + * + * @param buf Buffer to print + * @param len Length of the buffer + * @return String representation of the buffer. Caller has to manage returned + * memory + */ +char* util_str_buffer(const uint8_t* buf, size_t len); + +char** util_str_split(const char* str, const char* delim, size_t* count); + +void util_str_free_split(char** split, size_t count); + +#endif diff --git a/src/main/util/sys-info.c b/src/main/util/sys-info.c new file mode 100644 index 0000000..32715c1 --- /dev/null +++ b/src/main/util/sys-info.c @@ -0,0 +1,22 @@ +#define LOG_MODULE "util-sys-info" + +#include + +#include "util/log.h" + +static void util_sys_info_log_kernel() +{ + struct utsname buf; + + if (uname(&buf)) { + log_error("Reading kernel version failed"); + return; + } + + log_info("%s|%s|%s|%s|%s", buf.sysname, buf.nodename, buf.release, buf.version, buf.machine); +} + +void util_sys_info_log() +{ + util_sys_info_log_kernel(); +} diff --git a/src/main/util/sys-info.h b/src/main/util/sys-info.h new file mode 100644 index 0000000..f51b2ae --- /dev/null +++ b/src/main/util/sys-info.h @@ -0,0 +1,9 @@ +#ifndef UTILSYS_INFO_H +#define UTIL_SYS_INFO_H + +/** + * Print system information to the log (kernel, cpu, ram etc) + */ +void util_sys_info_log(); + +#endif diff --git a/src/main/util/test.c b/src/main/util/test.c new file mode 100644 index 0000000..abfb6c1 --- /dev/null +++ b/src/main/util/test.c @@ -0,0 +1,159 @@ +#define LOG_MODULE "util-test" + +#include + +#include "list.h" +#include "log.h" +#include "mem.h" +#include "test.h" +#include "str.h" + +struct util_test_case { + struct util_list_node head; + char* name; + util_test_func_t func; +}; + +static size_t util_test_count; +static struct util_list util_test_list; + +void util_test_init() +{ + util_test_count = 0; + util_list_init(&util_test_list); + + log_info("Initialized"); +} + +void util_test_add(const char* name, util_test_func_t func) +{ + struct util_test_case* test; + + test = util_xmalloc(sizeof(struct util_test_case)); + + test->name = util_str_dup(name); + test->func = func; + + util_list_append(&util_test_list, &test->head); + util_test_count++; + + log_info("Added %s, func %p", name, func); +} + +void util_test_exec() +{ + log_info("Exec %d tests", util_test_count); + + struct util_list_node* pos; + struct util_test_case* test; + size_t counter = 1; + + for (pos = util_test_list.head; pos != NULL; pos = pos->next) { + test = containerof(pos, struct util_test_case, head); + + log_debug("%d/%d Executing %s...", counter++, util_test_count, test->name); + + if (!test->func()) { + log_error("%s failed", test->name); + return; + } + } + + log_info("All tests passed"); +} + +void util_test_shutdown() +{ + log_info("Shutdown"); + + struct util_list_node* pos; + struct util_test_case* test; + + // TODO list elems cleanup? + + for (pos = util_test_list.head; pos != NULL; pos = pos->next) { + test = containerof(pos, struct util_test_case, head); + + free(test->name); + } +} + +bool util_test_assert_exp(bool exp, const char* exp_str, const char* file, size_t line_num) +{ + if (!exp) { + log_error("Assert failed [%s:%d]: Expression %s failed", file, line_num, exp_str); + return false; + } else { + log_debug("Assert successful [%s:%d]: Expression %s", file, line_num, exp_str); + return true; + } +} + +bool util_test_assert_uint(size_t val_exp, size_t val_actual, const char* exp, const char* actual, const char* file, + size_t line_num) +{ + if (val_exp != val_actual) { + log_error("Assert failed [%s:%d]: %d != %d of expected %s and actual %s", file, line_num, val_exp, val_actual, + exp, actual); + return false; + } else { + log_debug("Assert successful [%s:%d]: %d == %d of expected %s and actual %s", file, line_num, val_exp, + val_actual, exp, actual); + return true; + } +} + +bool util_test_assert_str(const char* val_exp, const char* val_actual, const char* exp, const char* actual, + const char* file, size_t line_num) +{ + if (strcmp(val_exp, val_actual) != 0) { + log_error("Assert failed [%s:%d]: %s != %s of expected %s and actual %s", file, line_num, val_exp, val_actual, + exp, actual); + return false; + } else { + log_debug("Assert successful [%s:%d]: %s == %s of expected %s and actual %s", file, line_num, val_exp, + val_actual, exp, actual); + return true; + } +} + +bool util_test_assert_data(const void* data_exp, const void* data_actual, size_t len, const char* exp, + const char* actual, const char* file, size_t line_num) +{ + bool ret; + char* data_exp_str = util_str_buffer(data_exp, len); + char* data_actual_str = util_str_buffer(data_actual, len); + + if (memcmp(data_exp, data_actual, len) != 0) { + log_error("Assert failed [%s:%d]: %s != %s of expected %s and actual %s", file, line_num, data_exp_str, + data_actual_str, exp, actual); + + uint32_t cnt = 0; + + for (size_t i = 0; i < len; i++) { + /* print the first 5 diffs, only */ + if (((const uint8_t*) data_exp)[i] != ((const uint8_t*) data_actual)[i]) { + if (cnt < 5) { + log_error("pos %d: %x != %x", i, ((const uint8_t*) data_exp)[i], ((const uint8_t*) data_actual)[i]); + } + + cnt++; + } + } + + if (cnt > 5) { + log_error("...and more different bytes, total diffs %d", cnt); + } + + ret = false; + } else { + log_debug("Assert successful [%s:%d]: %s == %s of expected %s and actual %s", file, line_num, data_exp_str, + data_actual_str, exp, actual); + ret = true; + } + + free(data_exp_str); + free(data_actual_str); + + return ret; +} \ No newline at end of file diff --git a/src/main/util/test.h b/src/main/util/test.h new file mode 100644 index 0000000..c41eabe --- /dev/null +++ b/src/main/util/test.h @@ -0,0 +1,81 @@ +#ifndef UTIL_TEST_H +#define UTIL_TEST_H + +#include +#include + +#include "defs.h" +#include "log.h" + +#define UTIL_TEST_MAIN_START \ + int main() \ + { \ + log_info("Unit test %s", FILENAME); \ + util_test_init(); \ + +#define UTIL_TEST_MAIN_END \ + util_test_exec(); \ + util_test_shutdown(); \ + return 0; \ + } \ + +#define UTIL_TEST_FUNC_START(name) \ + bool name() \ +{ \ + +#define UTIL_TEST_FUNC_END \ + return true; \ +} \ + +#define UTIL_TEST_ADD(func) util_test_add(STRINGIFY(func), func); + +#define UTIL_TEST_ASSERT(exp) \ + do { \ + if (!util_test_assert_exp(exp, STRINGIFY(exp), FILENAME, __LINE__)) { \ + return false; \ + } \ + } while (0); \ + +#define UTIL_TEST_ASSERT_UINT(exp, actual) \ + do { \ + if (!util_test_assert_uint(exp, actual, STRINGIFY(exp), STRINGIFY(actual), FILENAME, __LINE__)) { \ + return false; \ + } \ + } while (0); \ + +#define UTIL_TEST_ASSERT_STR(exp, actual) \ + do { \ + if (!util_test_assert_str(exp, actual, STRINGIFY(exp), STRINGIFY(actual), FILENAME, __LINE__)) { \ + return false; \ + } \ + } while (0); \ + +#define UTIL_TEST_ASSERT_DATA(exp, actual, len) \ + do { \ + if (!util_test_assert_data(exp, actual, len, STRINGIFY(exp), STRINGIFY(actual), FILENAME, __LINE__)) { \ + return false; \ + } \ + } while (0); \ + +typedef bool (*util_test_func_t)(); + +void util_test_init(); + +void util_test_add(const char* name, util_test_func_t func); + +void util_test_exec(); + +void util_test_shutdown(); + +bool util_test_assert_exp(bool exp, const char* exp_str, const char* file, size_t line_num); + +bool util_test_assert_uint(size_t val_exp, size_t val_actual, const char* exp, const char* actual, const char* file, + size_t line_num); + +bool util_test_assert_str(const char* val_exp, const char* val_actual, const char* exp, const char* actual, + const char* file, size_t line_num); + +bool util_test_assert_data(const void* data_exp, const void* data_actual, size_t len, const char* exp, + const char* actual, const char* file, size_t line_num); + +#endif diff --git a/src/main/util/time.c b/src/main/util/time.c new file mode 100644 index 0000000..acee0f5 --- /dev/null +++ b/src/main/util/time.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +#include "util/time.h" + +#define SEC_PER_DAY 86400 +#define SEC_PER_HOUR 3600 +#define SEC_PER_MIN 60 + +void util_time_sleep_ns(uint64_t time_ns) +{ + struct timespec t; + t.tv_sec = (__time_t) (time_ns / (1000 * 1000 * 1000)); + t.tv_nsec = (__syscall_slong_t) time_ns % (1000 * 1000 * 1000); + + nanosleep(&t, NULL); +} + +void util_time_sleep_us(uint64_t time_us) +{ + struct timespec t; + t.tv_sec = (__time_t) (time_us / (1000 * 1000)); + t.tv_nsec = (__syscall_slong_t) (time_us % (1000 * 1000)) * 1000; + + nanosleep(&t, NULL); +} + +void util_time_sleep_ms(uint64_t time_ms) +{ + struct timespec t; + t.tv_sec = (__time_t) (time_ms / 1000); + t.tv_nsec = (__syscall_slong_t) (time_ms % 1000) * 1000 * 1000; + + nanosleep(&t, NULL); +} + +void util_time_sleep_sec(uint64_t time_sec) +{ + struct timespec t; + t.tv_sec = (__time_t) time_sec; + t.tv_nsec = 0; + + nanosleep(&t, NULL); +} + +void util_time_get_current_time(struct util_time_timestamp* timestamp) +{ + if (!timestamp) { + return; + } + + time_t t = time(NULL); + struct tm* tm = localtime(&t); + + struct timeval tv; + gettimeofday(&tv, NULL); + + timestamp->millisec = tv.tv_usec / 1000; + timestamp->sec = tm->tm_sec; + timestamp->min = tm->tm_min; + timestamp->hour = tm->tm_hour; + timestamp->day = tm->tm_mday; + // Months since January [0, 11] + timestamp->month = tm->tm_mon + 1; + // Years since 1900 + timestamp->year = tm->tm_year + 1900; +} \ No newline at end of file diff --git a/src/main/util/time.h b/src/main/util/time.h new file mode 100644 index 0000000..80f106b --- /dev/null +++ b/src/main/util/time.h @@ -0,0 +1,26 @@ +#ifndef UTIL_TIME_H +#define UTIL_TIME_H + +#include + +struct util_time_timestamp { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; + uint16_t millisec; +}; + +void util_time_sleep_ns(uint64_t time_ns); + +void util_time_sleep_us(uint64_t time_us); + +void util_time_sleep_ms(uint64_t time_ms); + +void util_time_sleep_sec(uint64_t time_sec); + +void util_time_get_current_time(struct util_time_timestamp* timestamp); + +#endif //UTIL_TIME_H diff --git a/src/test/capnhook/hook/lib/main.c b/src/test/capnhook/hook/lib/main.c new file mode 100644 index 0000000..f13cf59 --- /dev/null +++ b/src/test/capnhook/hook/lib/main.c @@ -0,0 +1,73 @@ +#include + +#include "capnhook/hook/lib.h" + +#include "util/mem.h" + +typedef int (*test_mock_function_t)(int a); + +static int test_mock_function(int a) +{ + return a * 2; +} + +static void test_cnh_lib_mock_init_unit_test(void** state) +{ + struct cnh_lib_unit_test_func_mocks* mocks; + size_t mocks_cnt; + void* ptr; + + mocks_cnt = 1; + mocks = cnh_lib_allocate_func_mocks(mocks_cnt); + + mocks[0].name = "test_mock_function"; + mocks[0].func = test_mock_function; + + cnh_lib_init_unit_test(mocks, mocks_cnt); + + ptr = cnh_lib_load("asdf"); + assert_ptr_not_equal(ptr, NULL); + + ptr = cnh_lib_get_func_addr_handle(ptr, "test_mock_function"); + assert_ptr_equal(ptr, test_mock_function); + + assert_int_equal(((test_mock_function_t) ptr)(2), 4); + + ptr = cnh_lib_get_func_addr_handle(ptr, "asdf"); + assert_ptr_equal(ptr, NULL); + + cnh_lib_unload(ptr); + // Nothing happens here + + ptr = cnh_lib_get_func_addr( "test_mock_function"); + assert_ptr_equal(ptr, test_mock_function); + + cnh_lib_shutdown_unit_test(); +} + +static void test_cnh_lib_real(void** state) +{ + void* ptr; + + cnh_lib_init(); + + ptr = cnh_lib_load("libc.so.6"); + + assert_ptr_not_equal(ptr, NULL); + assert_ptr_equal(cnh_lib_load("asdfasdas"), NULL); + + assert_ptr_not_equal(cnh_lib_get_func_addr_handle(ptr, "printf"), NULL); + assert_ptr_not_equal(cnh_lib_get_func_addr("printf"), NULL); + + assert_ptr_equal(cnh_lib_get_func_addr("asdasdas"), NULL); +} + +int main(int argc, char* argv[]) +{ + const struct CMUnitTest tests[] ={ + cmocka_unit_test(test_cnh_lib_mock_init_unit_test), + cmocka_unit_test(test_cnh_lib_real) + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} \ No newline at end of file diff --git a/src/test/hook/propatch/usb-fix/main.c b/src/test/hook/propatch/usb-fix/main.c new file mode 100644 index 0000000..01de203 --- /dev/null +++ b/src/test/hook/propatch/usb-fix/main.c @@ -0,0 +1,145 @@ +#include + +#include + +#include "capnhook/hook/lib.h" + +#include "hook/propatch/usb-fix.h" + +#include "test-util/mem.h" + +#include "util/log.h" +#include "util/mem.h" + +static const char* readlink_return_value = "../devices/pci0000:00/0000:00:14.0/usb2/2-2/2-2:1.0/host7/target7:0:0/7:0:0:0/block/sde"; +static const char* player1_match_buf = "../devices/pci0000:00/0000:00:14.0/usb2/1-1/1-1:1.0"; +static const char* player2_match_buf = "../devices/pci0000:00/0000:00:14.0/usb2/1-2/1-2:1.0"; + +extern ssize_t propatch_usb_fix_readlink(const char* path, char* buf, size_t len); + +static uint32_t readlink_call_count = 0; + +ssize_t readlink_mock(const char* path, char* buf, size_t len) +{ + ssize_t len_return; + + readlink_call_count++; + + len_return = strlen(readlink_return_value); + + assert_true(len >= len_return); + + strcpy(buf, readlink_return_value); + + return len_return; +} + +static int setup(void** state) +{ + struct cnh_lib_unit_test_func_mocks* func_mocks; + size_t func_mocks_cnt; + + test_util_mem_install_mem_interface(); + + func_mocks_cnt = 1; + func_mocks = cnh_lib_allocate_func_mocks(func_mocks_cnt); + + func_mocks[0].name = "readlink"; + func_mocks[0].func = readlink_mock; + + cnh_lib_init_unit_test(func_mocks, func_mocks_cnt); + + readlink_call_count = 0; + + return 0; +} + +static int teardown(void** state) +{ + propatch_usb_fix_shutdown(); + + cnh_lib_shutdown_unit_test(); + + return 0; +} + +static void test_usb_fix_disabled(void** state) +{ + ssize_t res; + char buffer[256]; + + res = propatch_usb_fix_readlink( + "/sys/block/sde/device", + buffer, + sizeof(buffer)); + + assert_int_equal(res, strlen(readlink_return_value)); + assert_string_equal(buffer, readlink_return_value); + + assert_int_equal(readlink_call_count, 1); +} + +static void test_no_match_usb_fix(void** state) +{ + ssize_t res; + char buffer[256]; + + propatch_usb_fix_init("sdd,sdf", "2-1", "2-2"); + + res = propatch_usb_fix_readlink( + "/sys/block/sde/device", + buffer, + sizeof(buffer)); + + assert_int_equal(res, strlen(readlink_return_value)); + + assert_int_equal(readlink_call_count, 1); +} + +static void test_match_player1_usb_fix(void** state) +{ + ssize_t res; + char buffer[256]; + + propatch_usb_fix_init("sde,sdf", "2-1", "2-2"); + + res = propatch_usb_fix_readlink( + "/sys/block/sde/device", + buffer, + sizeof(buffer)); + + assert_int_equal(res, strlen(player1_match_buf)); + assert_string_equal(buffer, player1_match_buf); + + assert_int_equal(readlink_call_count, 1); +} + +static void test_match_player2_usb_fix(void** state) +{ + ssize_t res; + char buffer[256]; + + propatch_usb_fix_init("sde,sdf", "2-2", "2-1"); + + res = propatch_usb_fix_readlink( + "/sys/block/sde/device", + buffer, + sizeof(buffer)); + + assert_int_equal(res, strlen(player2_match_buf)); + assert_string_equal(buffer, player2_match_buf); + + assert_int_equal(readlink_call_count, 1); +} + +int main(int argc, char* argv[]) +{ + const struct CMUnitTest tests[] ={ + cmocka_unit_test_setup_teardown(test_usb_fix_disabled, setup, teardown), + cmocka_unit_test_setup_teardown(test_no_match_usb_fix, setup, teardown), + cmocka_unit_test_setup_teardown(test_match_player1_usb_fix, setup, teardown), + cmocka_unit_test_setup_teardown(test_match_player2_usb_fix, setup, teardown) + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} \ No newline at end of file diff --git a/src/test/test-util/mem.c b/src/test/test-util/mem.c new file mode 100644 index 0000000..3a96046 --- /dev/null +++ b/src/test/test-util/mem.c @@ -0,0 +1,52 @@ +#define LOG_MODULE "test-util-mem" + +#include + +#include "test-util/mem.h" + +#include "util/log.h" +#include "util/mem.h" + +static void* _test_util_mem_xmalloc(size_t nbytes) +{ + void* ptr; + + ptr = test_malloc(nbytes); + + log_debug("malloc(%d): %p", nbytes, ptr); + + return ptr; +} + +static void* _test_util_mem_xrealloc(void* mem, size_t nbytes) +{ + void* ptr; + + ptr = test_realloc(mem, nbytes); + + log_debug("realloc(%p %d): %p", mem, nbytes, ptr); + + return ptr; +} + +static void _test_util_mem_xfree(void** mem) +{ + if (*mem != NULL) { + log_debug("free(%p %p)", mem, *mem); + test_free(*mem); + *mem = NULL; + } else { + log_warn("free(%p NULL)", mem); + } +} + +static const struct util_mem_interface _test_util_mem_interface_test = { + .xmalloc = _test_util_mem_xmalloc, + .xrealloc = _test_util_mem_xrealloc, + .xfree = _test_util_mem_xfree, +}; + +void test_util_mem_install_mem_interface() +{ + util_mem_init(&_test_util_mem_interface_test); +} \ No newline at end of file diff --git a/src/test/test-util/mem.h b/src/test/test-util/mem.h new file mode 100644 index 0000000..8e5d797 --- /dev/null +++ b/src/test/test-util/mem.h @@ -0,0 +1,6 @@ +#pragma once + +/** + * Install the memory test interface which is backed by cmocka for leak and error detection on unit tests. + */ +void test_util_mem_install_mem_interface(); \ No newline at end of file diff --git a/src/test/util/mem/main.c b/src/test/util/mem/main.c new file mode 100644 index 0000000..4f8551e --- /dev/null +++ b/src/test/util/mem/main.c @@ -0,0 +1,50 @@ +#include + +#include "test-util/mem.h" + +#include "util/mem.h" + +static void test_util_mem_mock_interface(void** state) +{ + void* ptr; + + test_util_mem_install_mem_interface(); + + ptr = util_xmalloc(100); + assert_ptr_not_equal(ptr, NULL); + + ptr = util_xrealloc(ptr, 200); + assert_ptr_not_equal(ptr, NULL); + + util_xfree(&ptr); + assert_ptr_equal(ptr, NULL); +} + +static void test_util_mem_real(void** state) +{ + void* ptr; + + util_mem_init_default(); + + ptr = util_xmalloc(100); + assert_ptr_not_equal(ptr, NULL); + + ptr = util_xrealloc(ptr, 200); + assert_ptr_not_equal(ptr, NULL); + + util_xfree(&ptr); + assert_ptr_equal(ptr, NULL); + + // Should not crash + util_xfree(&ptr); +} + +int main(int argc, char* argv[]) +{ + const struct CMUnitTest tests[] ={ + cmocka_unit_test(test_util_mem_mock_interface), + cmocka_unit_test(test_util_mem_real) + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} \ No newline at end of file diff --git a/src/test/util/str/main.c b/src/test/util/str/main.c new file mode 100644 index 0000000..743bcdd --- /dev/null +++ b/src/test/util/str/main.c @@ -0,0 +1,101 @@ +#include + +#include "test-util/mem.h" + +#include "util/mem.h" +#include "util/str.h" + +static int setup(void** state) +{ + test_util_mem_install_mem_interface(); + + return 0; +} + +static void test_str_dup(void** state) +{ + char* str; + + str = util_str_dup("aasdf"); + assert_string_equal(str, "aasdf"); + + util_xfree((void**) &str); +} + +static void test_str_dup_empty(void** state) +{ + char* str; + + str = util_str_dup(""); + assert_string_equal(str, ""); + + util_xfree((void**) &str); +} + +static void test_str_split0(void** state) +{ + size_t count; + char** toks; + + toks = util_str_split("", ",", &count); + + assert_int_equal(count, 0); + + util_str_free_split(toks, count); +} + +static void test_str_split1(void** state) +{ + size_t count; + char** toks; + + toks = util_str_split("asd", ",", &count); + + assert_int_equal(count, 1); + assert_string_equal(toks[0], "asd"); + + util_str_free_split(toks, count); +} + +static void test_str_split2(void** state) +{ + size_t count; + char** toks; + + toks = util_str_split("asd,123", ",", &count); + + assert_int_equal(count, 2); + assert_string_equal(toks[0], "asd"); + assert_string_equal(toks[1], "123"); + + util_str_free_split(toks, count); +} + +static void test_str_split3(void** state) +{ + size_t count; + char** toks; + + toks = util_str_split("/asd/123/ddd/", "/", &count); + + assert_int_equal(count, 3); + assert_string_equal(toks[0], "asd"); + assert_string_equal(toks[1], "123"); + assert_string_equal(toks[2], "ddd"); + + util_str_free_split(toks, count); +} + +int main(int argc, char* argv[]) +{ + const struct CMUnitTest tests[] ={ + cmocka_unit_test_setup(test_str_dup, setup), + cmocka_unit_test_setup(test_str_dup_empty, setup), + cmocka_unit_test_setup(test_str_split0, setup), + cmocka_unit_test_setup(test_str_split1, setup), + cmocka_unit_test_setup(test_str_split2, setup), + cmocka_unit_test_setup(test_str_split3, setup) + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} \ No newline at end of file diff --git a/utils/files-lowercase.sh b/utils/files-lowercase.sh new file mode 100755 index 0000000..1d2986b --- /dev/null +++ b/utils/files-lowercase.sh @@ -0,0 +1,15 @@ +#!/bin/env bash + +if [ ! "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +for SRC in `find $1 -depth` +do + DST=`dirname "${SRC}"`/`basename "${SRC}" | tr '[A-Z]' '[a-z]'` + if [ "${SRC}" != "${DST}" ] + then + [ ! -e "${DST}" ] && mv -T "${SRC}" "${DST}" || echo "${SRC} was not renamed" + fi +done diff --git a/utils/grab_deps.sh b/utils/grab_deps.sh new file mode 100755 index 0000000..2b890a0 --- /dev/null +++ b/utils/grab_deps.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Author : Hemanth.HM +# Email : hemanth[dot]hm[at]gmail[dot]com +# License : GNU GPLv3 +# + +function useage() +{ + cat << EOU +Useage: bash $0 +EOU +exit 1 +} + +#Validate the inputs +[[ $# < 2 ]] && useage + +#Check if the paths are vaild +[[ ! -e $1 ]] && echo "Not a vaild input $1" && exit 1 +[[ -d $2 ]] || echo "No such directory $2 creating..."&& mkdir -p "$2" + +#Get the library dependencies +echo "Collecting the shared library dependencies for $1..." +deps=$(ldd $1 | awk 'BEGIN{ORS=" "}$1~/^\//{print $1}$3~/^\//{print $3}' | sed 's/,$/\n/') +echo "Copying the dependencies to $2" + +#Copy the deps +for dep in $deps +do + echo "Copying $dep to $2" + cp "$dep" "$2" +done + +echo "Done!" +