Compare commits

..

166 Commits

Author SHA1 Message Date
bicarus 99067b872e Add files via upload 2026-07-19 01:53:31 -07:00
bicarus e720e12a60 Add group patch example 2026-07-18 21:49:01 -07:00
bicarus-dev b8a9e0708b add nvencoder-amd 2026-07-10 01:48:20 -07:00
bicarus-dev 47a1ff101e show download count 2026-07-01 19:46:27 -07:00
bicarus-dev c99f59c69d remove news 2026-04-21 01:29:58 -07:00
bicarus-dev 657992ac1a doorstop 2026-04-20 16:03:46 -07:00
bicarus-dev 79d9f4a813 fix cdn 2026-02-17 02:37:35 -08:00
bicarus-dev f003a9d8b9 monospace downloads 2026-01-02 23:19:47 -08:00
bicarus-dev 23b07a0384 limit to prerelease 2026-01-01 21:10:44 -08:00
bicarus-dev 1b992d913f add script for showing latest release date 2026-01-01 20:46:03 -08:00
bicarus-dev 1c81cff2ab update datecode 2025-12-31 04:03:02 -08:00
bicarus-dev 8e17186b74 clean up 2025-12-31 01:48:15 -08:00
bicarus-dev 6c493fed2c patch placeholder html 2025-12-30 19:19:07 -08:00
bicarus-dev 7157194827 update notice 2025-12-30 19:04:38 -08:00
bicarus 51c4dcfcc5 Add files via upload 2025-12-30 18:50:49 -08:00
sp2xdev 56871ad162 update 2025-11-14 10:53:29 -08:00
sp2xdev fd1c8fbfb9 companion 2025-11-09 01:08:03 -08:00
sp2xdev 0ba81ed5b3 clean up 2025-11-09 00:40:02 -08:00
sp2xdev 974d486147 Update content.html 2025-10-21 16:41:57 -07:00
sp2xdev 385331d237 Update content.html 2025-10-21 16:41:35 -07:00
sp2xdev 77351c3b0a update notice 2025-10-20 18:07:44 -07:00
sp2xdev 292ef75692 link to archive of companion 2025-10-20 07:17:28 -07:00
sp2xdev 61a147d666 remove asio patch warning 2025-09-29 02:38:37 -07:00
sp2xdev 0d8fc842ae update download links 2025-09-28 00:49:28 -07:00
sp2xdev 2feece7fcf remove f5 notice 2025-09-27 19:24:45 -07:00
sp2xdev c2b138fe22 update download links to point to github 2025-09-27 19:24:19 -07:00
sp2xdev 8969f2f061 iidxasio issue update 2025-09-25 03:06:28 -07:00
sp2xdev 2acb02f8c0 notes about iidx32 2025-09-23 23:33:48 -07:00
sp2xdev 15793437e4 25-09-22 2025-09-21 19:54:48 -07:00
sp2xdev e77ef1904a 25-09-20 2025-09-20 04:33:39 -07:00
sp2xdev 102265aa23 25-09-18 2025-09-18 15:43:29 -07:00
sp2xdev cb9bbb0e7c 25-09-17 2025-09-17 10:58:21 -07:00
sp2xdev 0c1835859f remove css 2025-09-15 22:58:00 -07:00
sp2xdev 040e000ff5 link to pc faq 2025-09-15 22:40:01 -07:00
sp2xdev f058552e21 25-09-16 2025-09-15 21:58:46 -07:00
sp2xdev 275f77becf promote to stable 2025-09-07 16:44:49 -07:00
sp2xdev 123db97c19 update tagline 2025-08-26 17:07:02 -07:00
sp2xdev ff5471dc0f 25-08-21 2025-08-21 20:14:05 -07:00
sp2xdev 1ee44db3d2 25-08-17 2025-08-16 19:36:04 -07:00
sp2xdev 9807c477ce 25-08-11 2025-08-11 00:39:11 -07:00
sp2xdev 12bbc4d53a update link 2025-06-28 03:46:58 -07:00
sp2xdev 06e850c714 promote 25-06-03 to stable 2025-06-14 20:09:21 -07:00
sp2xdev a4da7269ff 25-06-03 2025-06-02 18:09:02 -07:00
sp2xdev c8d345f9c0 reupload 25-05-31 2025-05-30 23:32:12 -07:00
sp2xdev 74552cbb32 temporarily pull 25-05-31 2025-05-30 22:14:34 -07:00
sp2xdev 54ee18d2df 25-05-31 2025-05-30 20:45:42 -07:00
sp2xdev d8b82fae33 promote beta to stable 2025-05-17 00:17:40 -07:00
sp2xdev 7e070753c3 25-05-09 2025-05-09 15:20:21 -07:00
sp2xdev ddb00053d5 update link 2025-04-25 22:01:12 -07:00
sp2xdev d7842996c7 touch_effect 2025-04-25 17:04:16 -07:00
sp2xdev f9c1ecbeda 25-04-25 2025-04-24 17:43:10 -07:00
sp2xdev 74e2da6841 add nightly link 2025-04-22 02:02:35 -07:00
sp2xdev 888bb28449 add a warning 2025-04-08 09:23:11 -07:00
sp2xdev 3434d7dc76 25-04-08 2025-04-07 17:40:19 -07:00
sp2xdev acff49c8d9 add warning 2025-03-29 02:31:29 -07:00
sp2xdev 7752b234bc 25-03-29 2025-03-29 00:07:01 -07:00
sp2xdev f95555d4f5 25-03-25 2025-03-24 17:17:55 -07:00
sp2xdev 3970cba1ba 25-03-24 2025-03-23 19:05:15 -07:00
sp2xdev 8b3bd451e1 25-03-16 2025-03-16 00:52:25 -07:00
sp2xdev c9a3f5e8b8 Update KB number for Windows update 2025-03-11 16:21:35 -07:00
sp2xdev 3d4c9c481d people can't read 2025-03-07 16:14:37 -08:00
sp2xdev 4994da5b5b 25-03-03 2025-03-02 20:40:36 -08:00
sp2xdev bc5aefed6c 25-02-25 2025-02-24 18:24:06 -08:00
sp2xdev 923552bdba 25-02-22 2025-02-21 19:02:31 -08:00
sp2xdev d7af373b33 25-02-22 2025-02-21 18:03:48 -08:00
sp2xdev 5a9bba3158 promote to stable 2025-02-09 13:04:11 -08:00
sp2xdev 9c55710ee6 25-01-26 2025-01-26 17:20:59 -08:00
sp2xdev 53ec1798f3 update tagline 2025-01-21 01:14:06 -08:00
sp2xdev 0432eace7a update page title 2025-01-21 01:10:50 -08:00
sp2xdev 4ab705fc64 fix change log link 2025-01-18 15:03:21 -08:00
sp2xdev 707628d22c simplify disclaimers 2025-01-16 19:57:45 -08:00
sp2xdev c4349ec51c 25-01-16 2025-01-15 21:45:09 -08:00
sp2xdev 457d34dac0 Update links 2025-01-09 13:52:35 -08:00
sp2xdev ad63322c7d 25-01-09 2025-01-08 18:59:18 -08:00
sp2xdev 9527653ced iOS companion link 2025-01-04 15:33:55 -08:00
sp2xdev 0bbfc4e89c midi project 2025-01-03 04:33:56 -08:00
sp2xdev 24481b4fbe 24-12-31 2024-12-30 20:28:04 -08:00
sp2xdev ce5abec044 fix wording for latest update 2024-12-29 19:40:30 -08:00
sp2xdev fab4c6ed48 fix changelog link 2024-12-29 18:28:37 -08:00
sp2xdev 05078798e1 24-12-30 2024-12-29 18:28:21 -08:00
sp2xdev b4900062dd promote beta to stable 2024-12-21 20:39:24 -08:00
sp2xdev 66e20d0b6e 24-12-13 2024-12-13 01:01:35 -08:00
sp2xdev d068ba9123 ifs 2024-12-09 01:39:57 -08:00
sp2xdev 9bf72bd2ae clean up new features section 2024-12-08 02:35:43 -08:00
sp2xdev d3d14afaa4 restore 2024-12-06 03:33:08 -08:00
sp2xdev 1beab938ba taking down 24-012-06 for a bug fix 2024-12-06 02:11:00 -08:00
sp2xdev bbd287ed24 Add link to full changelog 2024-12-06 01:26:27 -08:00
sp2xdev fb8a3eeebf 24-12-06 2024-12-06 01:07:32 -08:00
sp2xdev 61b90e542b 24-11-20 2024-11-19 23:23:50 -08:00
sp2xdev 776b776bc3 24-11-20 2024-11-19 23:23:14 -08:00
sp2xdev aada5c0ba9 promote beta to stable 2024-11-13 10:24:42 -08:00
sp2xdev 598b86c03c stable promotion 2024-11-06 09:46:47 -08:00
sp2xdev 41ff36bcc8 24-10-29 2024-10-29 02:03:24 -07:00
sp2xdev 35ac588c66 24-10-14 2024-10-14 02:45:46 -07:00
sp2xdev d0c7a63917 24-10-12 2024-10-11 21:24:54 -07:00
sp2xdev 1d04695dd0 24-09-21 2024-09-21 00:41:42 -07:00
sp2xdev 2c89247f27 24-09-14 2024-09-14 12:27:04 -07:00
sp2xdev 4eb7a99fe9 what's new 2024-09-08 15:47:55 -07:00
sp2xdev e933e990c1 promote 24-08-24 to stable 2024-09-01 01:25:30 -07:00
sp2xdev 51d7509971 24-08-24 2024-08-23 17:59:37 -07:00
sp2xdev c031f58d9b 24-08-22 2024-08-21 21:30:28 -07:00
sp2xdev 9b8101ba59 24-08-12 2024-08-11 19:53:02 -07:00
sp2xdev fd7dbd87f8 24-08-07 2024-08-07 01:08:40 -07:00
sp2xdev 9d6f2a1bf1 24-08-06 2024-08-06 03:08:33 -07:00
sp2xdev d183ecf73f Promote 24-07-29 to stable 2024-08-05 19:10:53 -07:00
sp2xdev ba60dcb613 24-08-03 2024-08-03 16:30:58 -07:00
sp2xdev c7f299d839 24-07-29 2024-07-28 22:38:58 -07:00
sp2xdev acb820445b 24-07-28 2024-07-28 01:37:19 -07:00
sp2xdev 9f3cd90e22 24-07-25 2024-07-25 01:38:31 -07:00
sp2xdev a820843310 24-07-17 2024-07-17 00:15:29 -07:00
sp2xdev 1ab567fa8e fix 24-07-10 changelog 2024-07-15 01:54:20 -07:00
sp2xdev e4b3d6f585 24-07-15 2024-07-15 01:27:33 -07:00
sp2xdev 8d64ea5604 Promote 24-07-06 to stable 2024-07-12 18:53:06 -07:00
sp2xdev 7a0ec1e92d 24-07-13 2024-07-12 18:52:11 -07:00
sp2xdev 1a44e452ec touch issue 2024-07-11 00:32:34 -07:00
sp2xdev d2ed8e0278 24-07-10 2024-07-10 03:25:36 -07:00
sp2xdev 097ff4276a Merge branch 'docs' of https://github.com/spice2x/spice2x.github.io into docs 2024-07-05 19:18:51 -07:00
sp2xdev a68d64ae50 24-07-06 2024-07-05 19:18:46 -07:00
sp2xdev 8c496994f6 Update M32-634910da_15daec.json 2024-07-03 02:07:03 -07:00
sp2xdev 563f680d87 Update link 2024-07-03 02:05:27 -07:00
sp2xdev e025ad8102 Merge branch 'docs' of https://github.com/spice2x/spice2x.github.io into docs 2024-07-03 02:03:29 -07:00
sp2xdev caebf88f84 24-07-04 2024-07-03 02:03:21 -07:00
sp2xdev 816687c33b Update M32-634910da_15daec.json 2024-07-03 01:58:48 -07:00
sp2xdev ee19265436 Update LDJ-64ef0ff5_1037754.json 2024-07-03 01:57:09 -07:00
sp2xdev 8fd0345f22 Update LDJ-64ef0ff5_1037754.json 2024-07-03 01:56:09 -07:00
sp2xdev 0b7f0bff7d Update LDJ-64ef0ff5_1037754.json 2024-07-03 01:54:49 -07:00
sp2xdev 0dc321372c 24-06-26 2024-06-26 02:43:25 -07:00
sp2xdev ca5e36e445 24-06-22 2024-06-22 16:36:44 -07:00
sp2xdev 09dc3d6e18 add details to release note 2024-06-20 02:52:33 -07:00
sp2xdev 23ff8a32b9 24-06-20 2024-06-20 02:15:11 -07:00
sp2xdev ef5c0fe555 Update changelog for 24-06-14 2024-06-16 00:22:46 -07:00
sp2xdev 1f3f6b5246 24-06-14 2024-06-14 01:19:00 -07:00
sp2xdev 68739b03e5 24-06-08 2024-06-08 14:09:15 -07:00
sp2xdev 892088bb8d 24-06-03 2024-06-02 18:20:40 -07:00
sp2xdev 3a415623ed 24-06-02 2024-06-01 18:12:44 -07:00
sp2xdev 7566de7de8 24-05-31 2024-05-31 01:08:55 -07:00
sp2xdev aa6ec2bd30 Merge branch 'docs' of https://github.com/spice2x/spice2x.github.io into docs 2024-05-28 22:37:47 -07:00
sp2xdev 1eae57f234 24-05-29 2024-05-28 22:37:06 -07:00
sp2xdev 5c7a7848c7 Update M32-634910da_15daec.json 2024-05-28 21:50:58 -07:00
sp2xdev a99648816a Update LDJ-64ef0ff5_1037754.json 2024-05-28 21:37:31 -07:00
sp2xdev b24aaebad8 Update M32-634910da_15daec.json 2024-05-28 20:21:46 -07:00
sp2xdev 15d47845b8 Add example file LDJ-64ef0ff5_1037754.json 2024-05-28 20:11:31 -07:00
sp2xdev a6eb2faf2c 24-05-28 2024-05-27 17:38:56 -07:00
sp2xdev 92b56c0c6a 24-05-27 2024-05-27 01:38:17 -07:00
sp2xdev 9a5f884b1a Merge branch 'docs' of https://github.com/spice2x/spice2x.github.io into docs 2024-05-21 17:40:07 -07:00
sp2xdev 16bab28852 24-05-22 2024-05-21 17:39:55 -07:00
sp2xdev 2d464ee7b2 Update M32-634910da_15daec.json 2024-05-21 16:54:24 -07:00
sp2xdev 01d3e13632 Update patches.json 2024-05-21 16:53:01 -07:00
sp2xdev 233c515b64 Update companion links 2024-05-16 20:34:27 -07:00
sp2xdev 158842f699 promote stable, and combine beta release blobs 2024-05-12 14:56:57 -07:00
sp2xdev 580a67d984 24-05-12 2024-05-12 14:47:24 -07:00
sp2xdev bd86925381 24-05-05 2024-05-05 14:53:14 -07:00
sp2xdev 9caff7ebae 24-05-04 2024-05-03 19:12:59 -07:00
sp2xdev a7657fd77c Add a union example 2024-04-29 21:36:44 -07:00
sp2xdev 38c699fe04 Add an example patch 2024-04-29 21:20:50 -07:00
sp2xdev 620894d253 24-04-29 2024-04-29 02:28:35 -07:00
sp2xdev 0ae9b57ecc promote to stable 2024-04-17 00:35:48 -07:00
sp2xdev b84928e23d 24-04-08 2024-04-08 01:58:33 -07:00
sp2xdev e6e915aa2d Fix formatting 2024-04-04 18:50:30 -07:00
sp2xdev def03a1627 24-03-31 2024-03-31 00:49:23 -07:00
sp2xdev 7f5398b0a1 24-03-24 2024-03-24 15:27:45 -07:00
sp2xdev 696e4eabe2 24-03-17 2024-03-17 02:13:46 -07:00
sp2xdev 726ac1499e 24-03-16 2024-03-16 01:50:09 -07:00
sp2xdev 18f1d1bdee reword 2024-03-07 22:35:38 -08:00
sp2xdev 646a7b8931 24-03-06 2024-03-06 01:46:12 -08:00
sp2xdev ca0c68ba75 Update docs 2024-03-04 19:28:46 -08:00
1146 changed files with 1817 additions and 349739 deletions
-26
View File
@@ -1,26 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
## Game and version
*name of game, version of game*
## Version of spice2x
*please remember to test the LATEST beta version before posting a bug about it*
## Describe the issue
*what's the issue?*
## Attached log.txt file, if available
> [!WARNING]
> Please make sure you remove any personally identifiable information from the log file.
> Please set `-loglevel` to `all` before launching the game for more verbose logs.
## A note on issue management
`Closed as not planned` means exactly that - it's closed, because it's not on our plans to address them any time soon. It doesn't mean your issue is invalid, it just means it's not a priority, and we can always revisit them later.
@@ -1,10 +0,0 @@
---
name: Documentation update
about: For suggesting edits to the wiki page.
title: ''
labels: documentation
assignees: ''
---
-10
View File
@@ -1,10 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
-32
View File
@@ -1,32 +0,0 @@
on: [push, pull_request]
name: Continuous Integration
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
fw-ci:
name: Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./src/spice2x
steps:
- uses: actions/checkout@v4
- name: Set ccache environment variables
run: |
echo "CCACHE_DIR=${{ github.workspace }}/src/spice2x/.ccache" >> $GITHUB_ENV
- name: Install ccache
uses: hendrikmuhs/ccache-action@v1.2
- name: Calculate commit SHA
id: vars
run: |
calculatedSha=$(git rev-parse --short ${{ github.sha }})
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
- name: Compile
run: ./build_docker.sh
- uses: actions/upload-artifact@v4
with:
name: spice2x-ci-${{ env.COMMIT_SHORT_SHA }}
path: src/spice2x/bin
if-no-files-found: error
+3 -13
View File
@@ -1,13 +1,3 @@
.vscode/*
# JetBrains IDEs
.idea
.idea_modules
*.iws
# CMake
cmake-build*
# user config
build_all.local.sh
build_docker.local.sh
.vscode/*
docs/_site
docs/Gemfile.lock
+8
View File
@@ -0,0 +1,8 @@
{
"files.associations": {
"chrono": "cpp",
"future": "cpp",
"system_error": "cpp",
"xlocale": "cpp"
}
}
-104
View File
@@ -1,104 +0,0 @@
## Contributing
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
### Adding support for games
* No BomberGirl, as long as the Free To Play service is still alive.
* No support for that one touch screen game that dispenses plushies of mouse character (`O26`).
* No support for eaCloud ("Konasute") games.
* For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan.
* For new version of an already supported game: proceed with caution and use generally-accepted community guidelines.
### Forbidden "features" that will never be accepted
* Performing decryption of encrypted files
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data
* Any features that [phone home](https://en.wikipedia.org/wiki/Phoning_home)
* This includes automatic updaters.
* Exception: we have allowed the "patch import from URL" feature, but this has only been done under very careful considerations and plenty of warnings to the user.
### Avoiding regressions
The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions.
Additionally, there's a lot of code that get exercised on specific hardware - including hardware that you probably don't have access to (e.g., ICCA reader, real cabinet I/O, specific model of a IIDX controller, particular brand of touch screens...)
Therefore, when making code changes, please be extremely careful about containing / scoping your changes. Make targeted bug fixes scoped to handful of game versions and hardware configuration. When adding new features, make it off by default, unless there is a really good reason to make it the default. If you make a new default, add an option that disables it so that users can opt out as needed.
#### Config file compatibility
Do not change the names of buttons binds, analogs, strings for command line parameters, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
### Code quality requirements
* Test for regressions, at least in and around the component you are modifying:
* All currently supported games / versions must continue to work.
* Backwards compatibility must be preserved, unless there is a really good reason to break it. This includes (but not limited to): global/local config files, command line parameters, game patches, device interop (e.g., card readers), and SpiceAPI / Companion interop.
* Reasonable level of compatibility with the last release of original spicetools is expected. The stated goal of spice2x is to be a drop-in replacement for spicetools.
* Simply put, if someone has an existing install of spicetools/spice2x, copying over new version of spice2x should not result in vastly different behavior or major loss of functionality.
* Make sure you compile with the included Docker script and ensure you do not introduce **any** new compiler warnings or build breaks. The Docker script is the standard build environment, your custom Linux build environment or MSVC can be used during development, but you must validate the final build using Docker.
* Do not make code changes in unrelated areas; i.e., do not run code linters and auto-formatters for parts of the code that you didn't modify.
* Try to submit smaller chunks of code, instead one gigantic patch. For example, don't submit a patch for "Improve feature XYZ"; instead, submit "Change how A works to prepare for feature XYZ" "Refactor B for feature XYZ" "Add feature B to enable feature XYZ".
* Write to the log for anything useful - it helps immensely with troubleshooting and debugging. At the same time though, avoid spamming the log for something trivial.
### UI text
spice2x has a global audience; majority of the user base do not speak English as their first language.
Use simple English, avoid colloquialism, and use concise language, even if it's slightly technical.
### Using OS APIs
Avoid making permanent changes to user's OS configuration. For example, spice must not make a call to set power profile to Maximum Performance, or switch default audio device. Making the reverse call to restore settings on game shutdown is **not** good enough; there is no guarantee that spice will gracefully shutdown, since games (or spice) can crash. This is to avoid inadvertently putting user's PC into a bad state, which can be seen as malware-like behavior.
Watch out for legacy OS compatibility. Currently, the minimum support floor is Windows 7. If you use any Windows API, make sure it's supported in Windows 7. If you need to use API that is not present in Windows 7, you must not directly link against it, otherwise spice will fail to launch on older OSes. Take a look at Windows 8 touch code (win8.cpp) for examples on how to discover OS APIs via pointers.
### Code style requirements
* Indents are four spaces.
* Try to keep under 100 characters per line. This includes comments and string literals.
* Avoid making unnecessary formatting or whitespace changes in a PR; e.g., watch out for text editor automatically stripping trailing whitespace. It pollutes the diff and slows down code review.
* For signed/unsigned integer types, prefer `cstdint` types; e.g., use `int32_t`, `uint64_t` and not `int` and `unsigned long long`.
* Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc.
OK:
```c
if (conditional) {
DoSomething();
}
```
Not OK:
```c
if (conditional)
DoSomething();
```
Not OK:
```c
if (conditional) DoSomething();
```
* Opening curly braces should appear at the end, not in a line on its own.
OK:
```c
if (conditional) {
DoSomething();
}
```
Not OK:
```c
if (conditional)
{
DoSomething();
}
```
* Please give [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) a read. A few things to point out in spice codebase:
* We don't use GSL.
* Don't throw exceptions.
* Stick to smart pointers. For interfacing with C Win32 API and raw buffers, use `unique_plain_ptr`.
* Writing in C is also completely acceptable.
* Other than that, there are no strict rules for code formatting, but please attempt to emulate the style around the code you are modifying.
-674
View File
@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+1 -61
View File
@@ -1,61 +1 @@
<br/><br/><br/><br/><br/><br/><br/><br/>
# This is the GitHub page for code development. If you just want to download the latest spice2x EXE, visit the homepage instead:
# 🌶️🌶️ [https://spice2x.github.io/](https://spice2x.github.io/) 🌶️🌶️
# If you have problems, read [Known Issues (FAQ)](https://github.com/spice2x/spice2x.github.io/wiki/Known-issues) wiki page first, most of your questions are already answered.
<br/><br/><br/><br/><br/><br/><br/><br/>
# spice2x
## Overview
spice2x is a continuation of SpiceTools project. Original development of [SpiceTools](https://github.com/spicetools/spicetools) has stalled in 2022; spice2x is led by a new group of developers.
spice2x team does not provide any tools to circumvent software copy protection, nor distribute any copyright-protected game data.
[Features](https://github.com/spice2x/spice2x.github.io/wiki/spice2x-features)
[List of supported games](https://github.com/spice2x/spice2x.github.io/wiki/List-of-supported-games)
## How do I set up game data & start playing?
By policy,
* we do **NOT** provide links on where to acquire game data
* we do **NOT** provide tools or instructions on decrypting data
* we do **NOT** provide guides on setting up & basic troubleshooting
please do not ask for these, as it will never happen here.
## Submitting to the Issue Tracker
**Please file bugs if you find them!** If you just complain about something in Discord servers, we can't see them, so they'll never be fixed.
Rules for filing a new issue or adding comments to existing issues in the tracker:
* Low effort submissions will be simply deleted, and repeated attempts will get you banned.
* Check the [known issues](https://github.com/spice2x/spice2x.github.io/wiki/Known-issues) page first before reporting a new issue. If you have some new information or workarounds for a known issue, you can file a documentation bug as well.
* Use the search function and see if there is an existing issue.
* This is not the place to obtain a guide or receive basic troubleshooting.
* Don't file a bug demanding game XYZ to be supported.
* **Do not upload game data - any part of it, XML files included!**
* **Do not mention or link to external websites that distribute game data!**
* **Do not link to external websites that provide guides on how to run games!**
New GitHub accounts are prevented from creating new issues to prevent spam. Maintainers of this project reserve the right to close or delete any issues that violate the rules above, or any low effort issues. If you ask for very basic troubleshooting help with setting up a game, we'll just delete them without warning.
## Contributing
**We encourage the community to submit bugs and suggestions via the issue tracker, and to contribute code changes by opening pull requests**
If you want to resolve any reported (or not reported) bugs, implement features, add support for new games, or fix a [known issue](https://github.com/spice2x/spice2x.github.io/wiki/Known-issues) - feel free to reach out via the Issue tracker or by opening a Pull Request. All submitted code is assumed to be GPLv3 compliant.
We explicitly do **NOT** have a Discord server for dicussions - we try to do everything out in the open on this GitHub repo. That being said we haven't opened a Discussions tab here because we know it's going to be immediately filled with support requests.
Please see [CONTRIBUTING page](https://github.com/spice2x/spice2x.github.io/blob/main/CONTRIBUTING.md) for a full list of guidelines when submitting code.
## Additional information
Please read [README.md](https://github.com/spice2x/spice2x.github.io/blob/main/src/spice2x/README.md) inside `src/spice2x`.
# spice2x.github.io
View File
+3
View File
@@ -0,0 +1,3 @@
source 'https://rubygems.org'
gem 'github-pages'
gem 'webrick'
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2026 Omar Cornut
Copyright (c) 2015 Eduardo Rubio
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS 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.
+21
View File
@@ -0,0 +1,21 @@
# Documentation
Compass is a Jekyll theme designed with something very specific in mind: to be a simple and elegant personal landing page that can be easily deployed to [GitHub Pages](https://pages.github.com/).
## How to use Compass
1. Start by [installing Bundler](http://bundler.io) `gem install bundler`
2. [Fork the Compass repository](https://github.com/excentris/compass/fork)
3. Clone the repository you just forked: `git clone https://github.com/YOUR-USER/compass`
4. Then run `bundle install` to get [Jekyll](http://jekyllrb.com) and all the dependencies.
5. Edit `_config.yml` as needed.
6. Run the Jekyll server with `bundle exec jekyll serve`
7. Go to `http://localhost:4000`
## Deploy your site to GitHub Pages
If you want to use Compass as your personal landing page you can deploy your site to GitHub Pages as a [User Page](https://help.github.com/articles/user-organization-and-project-pages/#user--organization-pages). To do so, when you are done modifying your clone, you should rename your repository to `username.github.io` where username is your username. When GitHub builds the page it will be made available at `https://username.github.io`.
If you are planning on using a custom domain to direct to your site, modify the CNAME file as described [here](https://help.github.com/articles/adding-a-cname-file-to-your-repository/).
Check the [GitHub Pages Basics](https://help.github.com/categories/github-pages-basics/) for more information.
+33
View File
@@ -0,0 +1,33 @@
# general configuration
url: https://spice2x.github.io/spice2x
baseurl: ''
# compass configuration
compass:
# an avatar or logo (optional)
# logo: "/images/compass.png"
# author will appear under the avatar (optional)
author: "spice2x"
title: "spice2x"
# tagline will appear under the title (optional)
tagline: "continuation of SpiceTools project"
description: "spice2x is an updated version of SpiceTools with support for new games & new features"
# if include_content is true, the contents of the content.html include will be
# added right above the generated item list, if present
include_content: true
# if include_analytics is true, the content of the analytics.html include will
# be added at the end of the <head> section. You can add your Google Analytics
# Tracking Code to that file.
include_analytics: false
# Twitter @username (as defined by the Twitter Card specs)
# twitter: ""
# exclude
exclude: ["CNAME", "Gemfile", "Gemfile.lock", "LICENSE", "README.md"]
# Dependencies
markdown: kramdown
highlighter: rouge
# Permalinks
permalink: pretty
+2
View File
@@ -0,0 +1,2 @@
<!-- Here you can add your Google Analytics Tracking code. If you do so, do not
forget to set the include_analytics attribute to true on the _config.yml file -->
+124
View File
@@ -0,0 +1,124 @@
<h2>about spice2x</h2>
<p>spice2x is a continuation of SpiceTools project.</p>
<p>spice2x binaries are meant to be drop-in replacements for existing installs (spice.exe, spice64.exe, spicecfg.exe).
For a list of features, check out <a href="https://github.com/spice2x/spice2x.github.io/wiki/spice2x-features">this page</a>.
</p>
<!-- <h2>notice - December 31, 2025</h2>
<p style="font-size: x-large">
<ul>
<li>
If you got SDVX EG Final (2025120900), read <a href="https://github.com/spice2x/spice2x.github.io/issues/489"> this link </a>to resolve known issues.
</li>
</ul>
</p> -->
<h2>downloads</h2>
<p style="font-weight: bold";>
<a class="download-link" href="https://github.com/spice2x/spice2x.github.io/releases/latest">
latest stable release<span id="octo_latest_release"></span>
</a>
</p>
<p>
<a class="download-link" href="https://github.com/spice2x/spice2x.github.io/releases">
all releases, including pre-release
<span id="octo_latest_prerelease"></span>
</a>
</p>
<p><a class="download-link" href="https://nightly.link/spice2x/spice2x.github.io/workflows/ci/main">nightly builds (untested)</a></p>
<script type="module">
import { Octokit } from "https://esm.sh/octokit";
const octokit = new Octokit();
const owner = "spice2x";
const repo = "spice2x.github.io";
const sumDownloads = (assets) =>
(assets || []).reduce((total, asset) => total + (asset.download_count || 0), 0);
const latest = await octokit.request(
"GET /repos/{owner}/{repo}/releases/latest",
{ owner, repo });
if (latest && latest.data && latest.data.name) {
const downloads = sumDownloads(latest.data.assets).toLocaleString();
document.getElementById("octo_latest_release").textContent =
" (" + latest.data.name + ", " + downloads + " downloads)";
}
const releases = await octokit.request(
"GET /repos/{owner}/{repo}/releases",
{ owner, repo, per_page: 1, page: 1 });
if (releases && releases.data && releases.data.length >= 1 && releases.data[0].name && releases.data[0].prerelease) {
document.getElementById("octo_latest_prerelease").textContent = " (" + releases.data[0].name + ")";
}
</script>
<h2>documentation</h2>
<ul>
<li>
<a href="https://github.com/spice2x/spice2x.github.io/wiki/Known-issues">FAQ / Known issues</a>
</li>
<li>
<a href="https://github.com/spice2x/spice2x.github.io/wiki/PC-optimization">PC optimization guide</a>
</li>
<li>
<a href="https://github.com/spice2x/spice2x.github.io/wiki/Audio-modes-demystified">Audio modes demystified (ASIO, WASAPI, DirectSound...)</a>
</li>
<li>
<a href="https://github.com/spice2x/spice2x.github.io/wiki/Patching-DLLs-(hex-edits)">Patches / hex edits</a>
</li>
<li>
<a href="https://github.com/spice2x/spice2x.github.io/wiki/Asus-Xonar-AE">Info for Asus Xonar AE owners</a>
</li>
</ul>
For more documentation, please visit the <a href="https://github.com/spice2x/spice2x.github.io/wiki">wiki home</a>.
<h2>suggested addons</h2>
Check out these cool tools that can be used with spice:
<ul>
<li><a href="https://github.com/spice2x/spicecompanion_archive/releases">SpiceCompanion </a> -
control spice from your phone / tablet / another PC. Archive only, not actively maintained by anyone.
</li>
<li>
<a href="https://github.com/mon/ifs_layeredfs">ifs_layeredfs</a> - for game asset modding
</li>
<li>
<a href="https://github.com/ainlorn/SpiceDoorstop">SpiceDoorstop</a> - modding Unity-based games
</li>
<li>
<a href="https://github.com/kinetic-flow/spicehue">spicehue</a> - control Philips Hue lights over spice API
</li>
<li>
<a href="https://github.com/4yn/slidershim">slidershim</a> - use your phone / tablet as keyboard input
</li>
<li>
<a href="https://github.com/OffbeatDX/nvencoder-amf">nvencoder-amf</a> - IIDX play record for AMD GPU
</li>
</ul>
<h2>disclaimers</h2>
<p>spice2x team does not provide any tools to circumvent software copy protection, nor distribute any copyright-protected game data.</p>
<p>
<strong>If you want to run spice2x on a real arcade cabinet, do note that the developers take no responsibility for any damage done to your hardware, nor provide any support.</strong>
</p>
<h2>issues, questions, bugs, feature requests</h2>
Please use the github repo below.
<hr />
<ul class="fa-ul main-list">
<li class="main-list-item">
<span class="fa fa-github fa-lg main-list-item-icon"></span>
<a href="https://github.com/spice2x/spice2x.github.io">project home on GitHub</a>
</li>
</ul>
+51
View File
@@ -0,0 +1,51 @@
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% if site.compass.author %}{{ site.compass.author }} - {% endif %}{{ site.compass.tagline | strip_html }}</title>
<link rel="apple-touch-icon" href="{{ site.baseurl }}{{ site.compass.logo }}"/>
<link rel="shortcut icon" sizes="16x16 24x24 32x32 48x48 64x64" href="{{ site.url }}{{ site.baseurl }}/favicon.ico" />
<link rel="stylesheet" href="{{ site.baseurl }}/assets/normalize.css">
<link rel="stylesheet" href="{{ site.baseurl }}/assets/main.css">
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<link href='https://fonts.googleapis.com/css?family=Vollkorn' rel='stylesheet' type='text/css'>
<link rel="canonical" href="{{ site.url }}{{ site.baseurl }}{{ page.url }}">
<meta http-equiv='cache-control' content='no-cache'>
<meta http-equiv='expires' content='0'>
<meta http-equiv='pragma' content='no-cache'>
<meta name="author" content="{{ site.compass.author }}">
<meta name="description" content="{{ site.compass.description }}"/>
<meta http-equiv='content-language' content='en-us'>
<!-- Open Graph tags -->
<meta property="og:type" content="website"/>
<meta property="og:title" content="{{ site.compass.title }}"/>
<meta property="og:url" content="{{ site.url }}{{ site.baseurl }}{{ page.url }}"/>
<meta property="og:image" content="{{ site.url }}{{ site.baseurl }}{{ site.compass.logo }}"/>
<meta property="og:description" content="{{ site.compass.description }}"/>
<meta property="og:site_name" content="{{ site.compass.title }}">
<!-- Twitter Card tags -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{ site.compass.title }}">
{% if site.twitter.site %}
<meta name="twitter:site" content="{{ site.twitter.site }}">
{% endif %}
{% if site.twitter.creator %}
<meta name="twitter:creator" content="{{ site.twitter.creator }}">
{% endif %}
<meta name="twitter:url" content="{{ site.url }}{{ site.baseurl }}{{ page.url }}">
<meta name="twitter:description" content="{{ site.compass.description }}">
<meta name="twitter:image:src" content="{{ site.url }}{{ site.baseurl }}{{ site.compass.logo }}">
{% if site.compass.include_analytics %}
{% include analytics.html %}
{% endif %}
<meta name="theme-color" content="#F1EED9">
<meta name="google-site-verification" content="Pn7aB3jOynaV1QUDSO0KaX2j_qOpU898NCA97QiseME" />
</head>
+9
View File
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
{% include head.html %}
<body>
{{ content }}
</body>
</html>
+19
View File
@@ -0,0 +1,19 @@
---
layout: base
nav_exclude: true
sitemap: false
---
<div class="wrapper">
<header>
archived spice2x downloads
</header>
<main>
<div class="content">
<p>
These are old archived files. For latest version, visit <a href="https://spice2x.github.io/">https://spice2x.github.io/</a>
<ul class="downloads">
</ul>
</div>
</main>
</div>
+165
View File
@@ -0,0 +1,165 @@
---
---
$text-color: #46433A;
$anchor-text-color: #CE534D;
$background-color: #F1EED9;
body {
font-family: 'Vollkorn', serif;
color: $text-color;
background-color: $background-color;
}
header,
main {
padding: 0 20px;
}
/*** wrapper div for both header and main ***/
.wrapper {
margin-top: 4%;
}
/*** anchor tags ***/
a:link,
a:visited,
a:hover,
a:active {
color: $anchor-text-color;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/*** main content list ***/
.main-list-item {
font-weight: bold;
font-size: 1.2em;
margin: 0.8em 0;
}
/* override the left margin added by font awesome for the main content list,
since it must be aligned with the content */
.fa-ul.main-list {
margin-left: 0;
}
/* list icons */
.main-list-item-icon {
width: 36px;
color: $text-color;
}
/*** logo ***/
.logo-container {
text-align: center;
}
.logo {
width: 96px;
height: 96px;
display: inline-block;
background-size: cover;
border-radius: 50%;
-moz-border-radius: 50%;
border: 2px solid $background-color;
box-shadow: 0 0 0 3px $text-color;
}
pre {
font-size: 0.75em;
}
.download-link {
font-family: monospace;
font-size: 1.2em;
}
/*** author ***/
.author-container h1 {
font-size: 2.4em;
margin-top: 0;
margin-bottom: 0;
text-align: center;
}
/*** tagline ***/
.tagline-container p {
font-size: 1.3em;
text-align: center;
margin-bottom: 2em;
}
/******/
hr {
border: 0;
height: 1px;
background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), $text-color, rgba(0,0,0,0));
background-image: -moz-linear-gradient(left, rgba(0,0,0,0), $text-color, rgba(0,0,0,0));
background-image: -ms-linear-gradient(left, rgba(0,0,0,0), $text-color, rgba(0,0,0,0));
background-image: -o-linear-gradient(left, rgba(0,0,0,0), $text-color, rgba(0,0,0,0));
}
/*** footer ***/
footer {
position: fixed;
bottom: 0;
right: 0;
height: 20px;
}
.poweredby {
font-family: "Arial Narrow", Arial;
font-size: 0.6em;
line-height: 0.6em;
padding: 0 5px;
}
/*** media queries ***/
/* X-Small devices (phones, 480px and up) */
@media (min-width: 480px) {
/* wrapper stays 480px wide past 480px wide and is kept centered */
.wrapper {
width: 480px;
margin: 4% auto 0 auto;
}
main {
margin-bottom: 80px;
}
}
/* All other devices (768px and up) */
@media (min-width: 768px) {
/* past 768px the layout is changed and the wrapper has a fixed width of 680px
to accomodate both the header column and the content column */
.wrapper {
width: 680px;
}
/* the header column stays left and has a dynamic width with all contents
aligned right */
header {
float: left;
width: 20%;
text-align: right;
}
.author-container h1,
.logo-container,
.tagline-container p {
text-align: right;
}
main {
width: 70%;
margin-left: 30%;
margin-bottom: 100px;
padding: 0;
}
}
+427
View File
@@ -0,0 +1,427 @@
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+27
View File
@@ -0,0 +1,27 @@
---
layout: base
---
<div class="wrapper">
<header>
{% if site.compass.logo %}
<div class="logo-container">
<a class="logo" href="{{ page.baseurl }}" style="background-image: url('{{ site.baseurl }}{{ site.compass.logo }}')"></a>
</div>
{% endif %}
{% if site.compass.author %}
<div class="author-container"><h1>{{ site.compass.author }}</h1></div>
{% endif %}
{% if site.compass.tagline %}
<div class="tagline-container"><p>{{ site.compass.tagline }}</p></div>
{% endif %}
</header>
<main>
{% if site.compass.include_content %}
<div class="content">
{% include content.html %}
</div>
{% endif %}
</main>
</div>
+35
View File
@@ -0,0 +1,35 @@
[
{
"gameCode": "M32",
"dateCode": 2022102400,
"name": "Skip Tutorial",
"description": "",
"type": "memory",
"preset": true,
"patches": [
{
"dllName": "game.dll",
"dataDisabled": "0F87",
"dataEnabled": "90E9",
"dataOffset": 946042
}
]
},
{
"gameCode": "M32",
"dateCode": 2022102400,
"name": "Infinite Stage",
"description": "Play forever",
"caution": "Scores will not save",
"type": "memory",
"preset": true,
"patches": [
{
"dllName": "game.dll",
"dataDisabled": "0F85",
"dataEnabled": "90E9",
"dataOffset": 1143921
}
]
}
]
+573
View File
@@ -0,0 +1,573 @@
[
{
"gameCode": "KFC",
"version": "2025-12-09",
"lastUpdated": "2025-12-31 21:00:00",
"source": "https://spice2x.github.io/patches/"
},
{
"name": "Game FPS Target",
"description": "Forces the game to run at a specific FPS target.",
"gameCode": "KFC",
"type": "union",
"patches": [
{
"name": "60 FPS (Default)",
"patch": {
"offset": 9832382,
"dllName": "soundvoltex.dll",
"data": "4E"
}
},
{
"name": "120 FPS",
"patch": {
"offset": 9832382,
"dllName": "soundvoltex.dll",
"data": "5E"
}
}
]
},
{
"name": "Note FPS Target",
"description": "Forces the game's notes to run at a specific FPS target.",
"gameCode": "KFC",
"type": "union",
"patches": [
{
"name": "60 FPS (Default)",
"patch": {
"offset": 3531896,
"dllName": "soundvoltex.dll",
"data": "660F6EF0F30FE6F6F20F5935202C6000"
}
},
{
"name": "120 FPS",
"patch": {
"offset": 3531896,
"dllName": "soundvoltex.dll",
"data": "B878000000F20F2AF0F24890F2489090"
}
}
]
},
{
"name": "Shared mode WASAPI",
"description": "Allows for non-exclusive WASAPI audio.",
"caution": "Requires 48000Hz sample rate. Will slightly increase audio latency. Try combining this with the Low Latency Shared Audio option from spice2x.",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 6546524,
"dllName": "soundvoltex.dll",
"dataDisabled": "02",
"dataEnabled": "01"
}
]
},
{
"name": "Hide premium guide banner",
"description": "blpass_ef (rainbow outline on health gauge) is shown instead of pt_sousa_usr.",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 3758525,
"dllName": "soundvoltex.dll",
"dataDisabled": "774B5600",
"dataEnabled": "674B5600"
}
]
},
{
"name": "Hide all bottom text",
"description": "Hides all text typically found in the bottom corners of the screen such as \"PASELI\" or \"FREE PLAY\".",
"caution": "",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 8912520,
"dllName": "soundvoltex.dll",
"dataDisabled": "4652454520504C4159",
"dataEnabled": "000000000000000000"
},
{
"offset": 8912504,
"dllName": "soundvoltex.dll",
"dataDisabled": "4556454E54204D4F4445",
"dataEnabled": "00000000000000000000"
},
{
"offset": 8912488,
"dllName": "soundvoltex.dll",
"dataDisabled": "54454E4B4149434849204D4F4445",
"dataEnabled": "0000000000000000000000000000"
},
{
"offset": 8912608,
"dllName": "soundvoltex.dll",
"dataDisabled": "504153454C493A202573202B202573",
"dataEnabled": "000000000000000000000000000000"
},
{
"offset": 8912552,
"dllName": "soundvoltex.dll",
"dataDisabled": "4352454449543A202564202020434F494E3A202564202F202564",
"dataEnabled": "0000000000000000000000000000000000000000000000000000"
},
{
"offset": 8912536,
"dllName": "soundvoltex.dll",
"dataDisabled": "4352454449543A20256400",
"dataEnabled": "0000000000000000000000"
},
{
"offset": 8912456,
"dllName": "soundvoltex.dll",
"dataDisabled": "504153454C493A204E4F5420415641494C41424C45",
"dataEnabled": "000000000000000000000000000000000000000000"
},
{
"offset": 8912432,
"dllName": "soundvoltex.dll",
"dataDisabled": "504153454C493A204E4F204143434F554E54",
"dataEnabled": "000000000000000000000000000000000000"
},
{
"offset": 8912623,
"dllName": "soundvoltex.dll",
"dataDisabled": "00504153454C493A20257300",
"dataEnabled": "000000000000000000000000"
},
{
"offset": 8912584,
"dllName": "soundvoltex.dll",
"dataDisabled": "455854524120504153454C493A202573",
"dataEnabled": "00000000000000000000000000000000"
},
{
"offset": 8912479,
"dllName": "soundvoltex.dll",
"dataDisabled": "0025302A6400",
"dataEnabled": "000000000000"
},
{
"offset": 8912240,
"dllName": "soundvoltex.dll",
"dataDisabled": "8347815B83578393834F928682C582B728257381602989F08F9C82B782E982C982CD8365835883678381836A8385815B82C993FC82E8814183658393834C815B82F02030302C2030302C2030302C20362C20302082CC8F8782C5899F82B582C482AD82BE82B382A281420A8347815B83578393834F82F08DC48A4A82B782E98FEA8D8782CD8365835883678356815B839382C530302C2030302C2030302C20362C20312082CC8F8782C5899F82B582C482AD82BE82B382A28142",
"dataEnabled": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
]
},
{
"name": "Standard/Menu Timer Freeze",
"description": "Freezes all non-premium area timers.",
"caution": "",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 1872965,
"dllName": "soundvoltex.dll",
"dataDisabled": "84",
"dataEnabled": "85"
}
]
},
{
"name": "Premium Free Timer Freeze",
"description": "Freezes all premium area timers.",
"caution": "",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 4693346,
"dllName": "soundvoltex.dll",
"dataDisabled": "0F85",
"dataEnabled": "90E9"
},
{
"offset": 4392195,
"dllName": "soundvoltex.dll",
"dataDisabled": "0F84",
"dataEnabled": "90E9"
},
{
"offset": 3299322,
"dllName": "soundvoltex.dll",
"dataDisabled": "75",
"dataEnabled": "EB"
}
]
},
{
"name": "Premium Time Length",
"description": "Time at which the Premium timer starts. If you have it frozen, that'll be the time shown at all times",
"gameCode": "KFC",
"type": "union",
"patches": [
{
"name": "00:00",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "AAB9650000000000"
}
},
{
"name": "00:01",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00CA9A3B00000000"
}
},
{
"name": "04:20",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "002835893C000000"
}
},
{
"name": "07:27",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00B6461368000000"
}
},
{
"name": "13:37",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00AAFE38BE000000"
}
},
{
"name": "57:30",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "0044064423030000"
}
},
{
"name": "69:00",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00B83AEBC3030000"
}
},
{
"name": "10 Minutes (Default)",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "0070C9B28B000000"
}
},
{
"name": "15 Minutes",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00282E8CD1000000"
}
},
{
"name": "20 Minutes",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00E0926517010000"
}
},
{
"name": "30 Minutes",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00505C18A3010000"
}
},
{
"name": "45 Minutes",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00788AA474020000"
}
},
{
"name": "60 Minutes",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00A0B83046030000"
}
},
{
"name": "90 Minutes",
"patch": {
"offset": 4529554,
"dllName": "soundvoltex.dll",
"data": "00F01449E9040000"
}
}
]
},
{
"name": "Uncensor All Jackets",
"description": "Uncensors all song jackets no matter the region by breaking the path to jacket masks.",
"caution": "",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 9325432,
"dllName": "soundvoltex.dll",
"dataDisabled": "61",
"dataEnabled": "75"
}
]
},
{
"name": "Fake Region",
"description": "Forces the game to run as a specific region without altering language, which allows for region-specific content locking. Use Japan for everything to be unlocked.",
"gameCode": "KFC",
"type": "union",
"patches": [
{
"name": "Default",
"patch": {
"offset": 2540975,
"dllName": "soundvoltex.dll",
"data": "E82C5F300085C00F85FA000000"
}
},
{
"name": "Japan (J)",
"patch": {
"offset": 2540975,
"dllName": "soundvoltex.dll",
"data": "B8000000008905065786009090"
}
},
{
"name": "Korea (K)",
"patch": {
"offset": 2540975,
"dllName": "soundvoltex.dll",
"data": "B8010000008905065786009090"
}
},
{
"name": "Asia (A)",
"patch": {
"offset": 2540975,
"dllName": "soundvoltex.dll",
"data": "B8020000008905065786009090"
}
},
{
"name": "Indonesia (Y)",
"patch": {
"offset": 2540975,
"dllName": "soundvoltex.dll",
"data": "B8030000008905065786009090"
}
},
{
"name": "America (U)",
"patch": {
"offset": 2540975,
"dllName": "soundvoltex.dll",
"data": "B8040000008905065786009090"
}
}
]
},
{
"name": "Force English Sort Options",
"description": "Forces the game to use english sort options within the FX-R menu in song select.",
"caution": "",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 155852,
"dllName": "soundvoltex.dll",
"dataDisabled": "8B15F6702201",
"dataEnabled": "BA0100000090"
},
{
"offset": 155881,
"dllName": "soundvoltex.dll",
"dataDisabled": "8B15D9702201",
"dataEnabled": "BA0100000090"
},
{
"offset": 155910,
"dllName": "soundvoltex.dll",
"dataDisabled": "8B15BC702201",
"dataEnabled": "BA0100000090"
},
{
"offset": 155939,
"dllName": "soundvoltex.dll",
"dataDisabled": "8B159F702201",
"dataEnabled": "BA0100000090"
},
{
"offset": 155968,
"dllName": "soundvoltex.dll",
"dataDisabled": "8B1582702201",
"dataEnabled": "BA0100000090"
},
{
"offset": 156003,
"dllName": "soundvoltex.dll",
"dataDisabled": "8B155F702201",
"dataEnabled": "BA0100000090"
},
{
"offset": 156038,
"dllName": "soundvoltex.dll",
"dataDisabled": "8B153C702201",
"dataEnabled": "BA0100000090"
},
{
"offset": 9720876,
"dllName": "soundvoltex.dll",
"dataDisabled": "B930B330A23087730698",
"dataEnabled": "530063006F0072006500"
}
]
},
{
"name": "ASIO 2 Channels Mode",
"description": "Use 2 channels instead of 8. Useful for some audio cards.",
"caution": "ONLY for ASIO audio users.",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 6107030,
"dllName": "soundvoltex.dll",
"dataDisabled": "BA2B000000",
"dataEnabled": "BA2A000000"
}
]
},
{
"name": "Disable power change",
"description": "Prevents the game from changing power settings.",
"caution": "Typically not required with spice2x.",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 3534334,
"dllName": "soundvoltex.dll",
"dataDisabled": "75",
"dataEnabled": "EB"
}
]
},
{
"name": "Disable monitor change",
"description": "Prevents the game from changing monitor settings.",
"caution": "Typically not required with spice2x.",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 3534486,
"dllName": "soundvoltex.dll",
"dataDisabled": "75",
"dataEnabled": "EB"
}
]
},
{
"name": "Disable Subscreen in Valkyrie mode",
"description": "Forcefully disables the subscreen.",
"caution": "ONLY for Valkyrie (G spec). Consider using spice2x's \"-sdvxnosub\" option instead.",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 5528734,
"dllName": "soundvoltex.dll",
"dataDisabled": "83BDB800000002410F93C64488742451",
"dataEnabled": "41B60044887424519090909090909090"
}
]
},
{
"name": "Valkyrie Mode 60Hz",
"description": "Allows the game to run at 60Hz.",
"caution": "ONLY for Valkyrie (G Spec). Might require \"Force BIO2 (KFC) IO in Valkyrie mode\" to work.",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 3534525,
"dllName": "soundvoltex.dll",
"dataDisabled": "74",
"dataEnabled": "75"
},
{
"offset": 5530331,
"dllName": "soundvoltex.dll",
"dataDisabled": "74",
"dataEnabled": "75"
},
{
"offset": 5532672,
"dllName": "soundvoltex.dll",
"dataDisabled": "74",
"dataEnabled": "75"
}
]
},
{
"name": "Force BIO2 (KFC) IO in Valkyrie mode",
"description": "Forces the game to use BIO2 IO in Valkyrie mode.",
"caution": "Can be useful for arcade cabinets. If you don't know why you need this, ignore it.",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 5851557,
"dllName": "soundvoltex.dll",
"dataDisabled": "8705",
"dataEnabled": "A70C"
}
]
},
{
"name": "Force Enable Headphones",
"description": "Assumes headphones are always connected, allowing for volume control. Useful for cabinets.",
"caution": "",
"gameCode": "KFC",
"type": "memory",
"patches": [
{
"offset": 6021766,
"dllName": "soundvoltex.dll",
"dataDisabled": "0F10060F114760",
"dataEnabled": "C7476001000000"
}
]
}
]
+49
View File
@@ -0,0 +1,49 @@
[
{
"info": "example patch for spice2x documentation",
"extraInfo": "this object is completely optional and you can put whatever you want in it, it will be printed in the logs, but it will not be parsed in the code",
"downloadedFrom": "https://spice2x.github.io/patches/",
"lastUpdated": "2024-07-03T08:52:37.645Z",
"dateCode": "2023090500 (LDJ-003)"
},
{
"name": "Standard/Menu Timer Freeze",
"description": "Freezes menu timer. va=180B28FA5",
"gameCode": "LDJ",
"type": "memory",
"patches": [
{
"offset": 11699109,
"dllName": "bm2dx.dll",
"dataDisabled": "0F84",
"dataEnabled": "90E9"
}
]
},
{
"name" : "Monitor Adjust Offset",
"description": "Changes base timing offset value. va=0x181274D38",
"gameCode": "LDJ",
"type" : "number",
"patch": {
"dllName": "bm2dx.dll",
"offset" : 19347768,
"size": 4,
"min" : -1000,
"max" : 1000
}
},
{
"name" : "Full Screen Monitor Check FPS Target",
"description": "Changes full screen refresh rate. va=180AC9445",
"gameCode": "LDJ",
"type" : "number",
"patch": {
"dllName": "bm2dx.dll",
"offset" : 11307084,
"size": 2,
"min" : 60,
"max" : 1200
}
}
]
+188
View File
@@ -0,0 +1,188 @@
[
{
"gameCode": "LDJ",
"version": "2025-08-25 (010)",
"source": "https://spice2x.github.io/"
},
{
"type": "group",
"gameCode": "LDJ",
"id": "timer-freeze",
"name": "Timer Freeze Patches",
"description": "Freezes various timers in the game."
},
{
"name": "Standard/Menu Timer Freeze",
"description": "Freezes all non-premium area timers.",
"caution": "",
"gameCode": "LDJ",
"type": "memory",
"group": "timer-freeze",
"patches": [
{
"offset": 9962743,
"dllName": "bm2dx.dll",
"dataDisabled": "0F84",
"dataEnabled": "90E9"
}
]
},
{
"name": "Premium Free Timer Freeze",
"description": "Freezes all premium area timers.",
"caution": "",
"gameCode": "LDJ",
"type": "memory",
"group": "timer-freeze",
"patches": [
{
"offset": 9111965,
"dllName": "bm2dx.dll",
"dataDisabled": "7E",
"dataEnabled": "EB"
}
]
},
{
"name": "Video Purchase Timer Freeze",
"description": "Freezes the subscreen countdown on the transition from result to music select.",
"caution": "Only useful with a TDJ (010) dll.",
"gameCode": "LDJ",
"type": "memory",
"group": "timer-freeze",
"patches": [
{
"offset": 10340157,
"dllName": "bm2dx.dll",
"dataDisabled": "FFC8",
"dataEnabled": "9090"
}
]
},
{
"type": "group",
"gameCode": "LDJ",
"id": "quick-retry",
"name": "Quick Retry Patches",
"description": "Makes retrying songs quicker."
},
{
"name": "Skip Decide Screen",
"description": "Skips the splash animation that appears upon starting a song.",
"caution": "",
"gameCode": "LDJ",
"type": "memory",
"group": "quick-retry",
"patches": [
{
"offset": 6230736,
"dllName": "bm2dx.dll",
"dataDisabled": "488BC455",
"dataEnabled": "488BC1C3"
}
]
},
{
"name": "Quick Retry",
"description": "Makes retrying a song quicker.",
"caution": "",
"gameCode": "LDJ",
"type": "memory",
"group": "quick-retry",
"patches": [
{
"offset": 8901984,
"dllName": "bm2dx.dll",
"dataDisabled": "40534883EC20",
"dataEnabled": "B001C3909090"
}
]
},
{
"name": "Quicker Quick Retry",
"description": "Makes retrying a song even quicker. Shortens the duration of the 'Stage Failed' animation.",
"caution": "To be used along with \"Quick Retry\".",
"gameCode": "LDJ",
"type": "memory",
"group": "quick-retry",
"patches": [
{
"offset": 9623152,
"dllName": "bm2dx.dll",
"dataDisabled": "7C79",
"dataEnabled": "9090"
}
]
},
{
"type": "group",
"gameCode": "LDJ",
"id": "ldj-fps",
"name": "LDJ Custom FPS Patches",
"description": "Custom FPS for LDJ mode.",
"caution": "Only if you know what you're doing."
},
{
"name": "Force LDJ Custom Timing/Adapter FPS",
"description": "Forces Custom Timing/Adapter FPS in LDJ.",
"caution": "Enable this if \"Custom LDJ Timing/Adapter FPS\" is not default. May cause desync. Consider \"Force TDJ/LDJ Mode\" patches instead.",
"gameCode": "LDJ",
"type": "memory",
"group": "ldj-fps",
"patches": [
{
"offset": 8568291,
"dllName": "bm2dx.dll",
"dataDisabled": "75",
"dataEnabled": "EB"
},
{
"offset": 10906103,
"dllName": "bm2dx.dll",
"dataDisabled": "7403",
"dataEnabled": "9090"
}
]
},
{
"name": "Choose LDJ Custom Timing/Adapter FPS",
"description": "Sets the Timing/Adapter FPS in LDJ.",
"caution": "Requires \"Force Custom Timing/Adapter in LDJ\" to be enabled. May cause desync. Consider \"Force TDJ/LDJ Mode\" patches instead.",
"gameCode": "LDJ",
"type": "union",
"group": "ldj-fps",
"patches": [
{
"name": "60 FPS (Default)",
"patch": {
"offset": 10905569,
"dllName": "bm2dx.dll",
"data": "C745DB3C000000C745FF01000000488B45D748894503C745D701000000C745DB3C000000"
}
},
{
"name": "120 FPS",
"patch": {
"offset": 10905569,
"dllName": "bm2dx.dll",
"data": "C745DB78000000C745FF01000000488B45D748894503C745D701000000C745DB78000000"
}
}
]
},
{
"name": "Debug Mode",
"description": "While in game, press F1 to enable menu. (Disables Profile/Score saving)",
"caution": "Ignore if you're not sure know what this does.",
"gameCode": "LDJ",
"type": "memory",
"patches": [
{
"offset": 9803904,
"dllName": "bm2dx.dll",
"dataDisabled": "32C0",
"dataEnabled": "B001"
}
]
}
]
+72
View File
@@ -0,0 +1,72 @@
[
{
"info": "example patch for spice2x documentation",
"extraInfo": "this object is completely optional and you can put whatever you want in it, it will be printed in the logs, but it will not be parsed in the code",
"downloadedFrom": "https://spice2x.github.io/patches/",
"lastUpdated": "2024-07-03T08:52:37.645Z",
"dateCode": "2022102400"
},
{
"gameCode": "M32",
"name": "Skip Tutorial",
"description": "",
"type": "memory",
"preset": true,
"patches": [
{
"dllName": "game.dll",
"dataDisabled": "0F87",
"dataEnabled": "90E9",
"dataOffset": 946042
}
]
},
{
"gameCode": "M32",
"name": "Infinite Stage",
"description": "Play forever",
"caution": "Scores will not save",
"type": "memory",
"preset": true,
"patches": [
{
"dllName": "game.dll",
"dataDisabled": "0F85",
"dataEnabled": "90E9",
"dataOffset": 1143921
}
]
},
{
"name": "Change PLEASE WAIT A MOMENT message",
"description": "Just a dumb example",
"gameCode": "M32",
"type": "union",
"patches": [
{
"name": "MOMENT (Default)",
"patch": {
"dllName": "game.dll",
"data": "4D4F4D454E54",
"offset": 1609086
}
},
{
"name": "MOMENO",
"patch": {
"dllName": "game.dll",
"data": "4D4F4D454E4F",
"offset": 1609086
}
},
{
"name": "SECOND",
"patch": {
"dllName": "game.dll",
"data": "5345434F4E44",
"offset": 1609086
}
}
]
}
]
+5
View File
@@ -0,0 +1,5 @@
Use this URL to import patches into spice2x: <pre>https://spice2x.github.io/patches/</pre>
This is temporary & very few games are supported (namely, EG final).
There is NO intention turning this into a full blown database of patches.
-17
View File
@@ -1,17 +0,0 @@
> [!NOTE]
> Before submitting code changes...
> * Please do note that this is a GPL v3.0 open source project.
> * Please read the [CONTRIBUTING](https://github.com/spice2x/spice2x.github.io/blob/main/CONTRIBUTING.md) guide.
> * Maintainers reserve the right to reject or modify your submission without reason.
> * No new compiler warnings must be introduced. Check the CI build results.
>
> Feel free to remove this section after you have read it.
## Link to GitHub Issue or related Pull Request, if one exists
#0
## Description of change
*what does this code do?*
## Testing
*how was the code tested?*
-5
View File
@@ -1,5 +0,0 @@
bin/**
dist/**
docker/**
cmake-build*
.ccache/**
-26
View File
@@ -1,26 +0,0 @@
.vscode/*
# JetBrains IDEs
.idea
.idea_modules
*.iws
# CMake
cmake-build*
# user config
build_all.local.sh
build_docker.local.sh
# output from build script
bin/*
dist/*
external/cv2pdb/*
.ccache/*
# Visual Studio
.vs
out
CMakeSettings.json
File diff suppressed because it is too large Load Diff
-7
View File
@@ -1,7 +0,0 @@
FROM spicetools/deps
WORKDIR /src
COPY --from=gitroot . /src/.git
COPY . /src/src/spice2x
WORKDIR /src/src/spice2x
ENTRYPOINT ["./build_all.sh"]
-674
View File
@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
-308
View File
@@ -1,308 +0,0 @@
SpiceTools
==========
This is a loader for various arcade games developed by 573.
The project is using CMake as it's build system, with a custom build
script for making packages ready for distribution and to keep it easy
for people not knowing how to use CMake.
## Building/Distribution
We're currently using Arch Linux for building the binaries.
You'll need:
- MinGW-64 packages (can be found in the AUR)
- bash
- git
- zip
- upx (optional)
For any other GNU/Linux distributions (or Windows lol), you're on your
own.
To build the project, run:
$ ./build_all.sh
## Build Configuration
You can tweak some settings at the beginning of the build script. You
might want to modify the paths of the toolchains if yours differ. It's
also possible to disable UPX compression and source distribution for
example.
If you're not using an IDE and want to run the build script from command
line manually each time you make a change, you can set CLEAN_BUILD to 0
so it will only compile what's needed. When modifying the resources you
should build from scratch, since CMake isn't able to track changes of
those files (e.g. changelog.txt, licenses.txt).
You can put your custom build script under "build_all.local.sh" if you
don't want git to track the changes you made to the build settings.
## Debug Build
To get proper stackstraces, you need to build with the DEBUG setting
enabled. This will set the CMake build type to debug and the script
will try to make use of cv2pdb.
Check external/cv2pdb/README.md for details on how to set it up.
It is required to use cv2pdb to generate PDB output, because at time of
writing, MinGW still didn't support Microsoft's proprietary file format.
## API
SpiceTools is providing a TCP/JSON based API. You can enable it with the
`-api [PORT]` option. It's recommended to also set a password with
`-apipass [PASS]`.
The protocol is meant to be simple, easy to use and with few overhead.
To connect to the API, just open a TCP connection to the host computer
at the specified port.
To make a request you write the UTF-8 encoded JSON contents to the
socket. To mark the end of the request, you need to terminate the JSON
string with `0x00`. The server returns its answer on the same way,
UTF-8 encoded JSON terminated with NULL.
To save space, by default the JSONs are without whitespace. To enable
pretty printing, use `-apipretty`.
If you want to test the API server without running a game, you can
run it headless via `-apidebug`.
If a password is specified, both request and response are encrypted
using RC4 with the key being the password encoded in UTF-8.
While only providing weak security when the password is static,
it's simple to implement and requires no authentication protocol.
The password is also able to change dynamically, see
`control.session_refresh()` for details.
If you don't want to worry about all the details, you can just use one
of the supplied libraries (check /api/resources). There's also a little
remote control GUI available for python.
### WebSocket
SpiceTools opens a WebSocket server on the specified port plus one.
That means if the API is on 1337, the WebSocket server will be on 1338.
The protocol is the very same as for the normal API, but instead of
directly sending the data over TCP you need to send binary datapackets.
The included dart spiceapi library also has a WebSocket implementation.
### Example Call
This example inserts the a card into P1's reader slot.
If you need more examples, you can check the example code.
#### Request
```JSON
{
"id": 1,
"module": "card",
"function": "insert",
"params": [0, "E004010000000000"]
}
```
#### Response
```JSON
{
"id": 1,
"errors": [],
"data": []
}
```
### Modules
For the sake of simplifying this documentation, I will describe functions
as if they were called via a normal programming language. That means,
besides `module`/`function` needing to be set accordingly, the `params` field
must contain all the parameters described below. Some of them are optional.
The returning data will always be in the `data` field of the response.
Errors will be reported as strings in the `errors` field. Sometimes, you can
receive more than one error. The error messages may be changed/improved in the
future, so you shouldn't rely on them. If the error list is empty, the function
executed successfully.
The `id` of the response will always match the `id` of the request. The API
server itself doesn't expect any logic of your ID choices, however you can use
them to make sure your answers aren't arriving out of order. Currently it
doesn't matter since the TCP protocol doesn't allow for out of order data,
however this may change when/if support for UDP is being introduced. The only
restriction is that the ID has to be a valid 64-bit unsigned integer.
#### Card
- insert(index: uint, card_id: hex)
- inserts a card which gets read by the emulated card readers for the game
- index has to be either 0 (for P1) or 1 (for P2)
- card_id has to be a valid 16-character hex string
#### Coin
- get()
- returns the amount of unprocessed coins in queue
- not equal to the amount of coins shown ingame since the game may drain it
- set(amount: int)
- sets the amount of coins in queue for the game to process
- insert(amount: int)
- adds the amount to the coins in queue
- amount is optional and defaults to 1
- blocker_get()
- returns the current coin blocker state (false: open, true: closed)
#### Info
- avs()
- returns a dict including the AVS model, dest, spec, rev and ext
- launcher()
- returns a dict including the version, compile date/time, system time and
the arguments used to start the process
- memory()
- returns a dict including total, total used and bytes used by the process
for both physical and virtual RAM
#### Keypads
For all functions in this module, the keypad parameter must be
either 0 (for P1) or 1 (for P2). Accepted keypad characters are "0" to "9" for
the single digit numbers, "A" for the double zero and "D" for the decimal key.
- write(keypad: uint, input: str)
- writes all characters in input sequentially to the keypad
- this should be used for things like PIN input
- set(keypad: uint, key: char, ...)
- clears all overrides, then applies each given key as override
- if a key is overridden, it shows up as pressed in-game
- get(keypad: uint)
- returns all pressed keys of the specified keypad
#### Analogs/Buttons/Lights
All of those three modules have equally named methods for you to call.
- read()
- returns an array of state objects containing name, state and a bool
- the bool indicates if the object is active (e.g. the button was overridden)
- write([name: str, state: float], ...)
- applies the states as an override value
- the device binding via the config will be ignored while an override is set
- if an override value is set, the object will be marked as active
- write_reset([name: str], ...)
- removes the override value from the objects specified by name
- if no names were passed, all overrides will be removed
##### Additional API for lights
- read(name: string, ...)
- same as read(), but you can specify light names
- write_reset(name: str, ...)
- same as write_reset(), but it accepts a flat list of strings
#### Touch
- read()
- returns an array of state objects containing id, x and y
- write([id: uint, x: int, y: int], ...)
- adds the given touch points having an unknown ID
- overrides old touch points when the ID matches
- write_reset(id: uint, ...)
- removes the touch points matching one of the given IDs
#### Control
This module only functions if a password is being used to communicate,
with the single exception of session_refresh().
- raise(signal: str)
- raises a signal to the current process
- signal is a string and must be one of the following values:
"SIGABRT", "SIGFPE", "SIGILL", "SIGINT", "SIGSEGV", "SIGTERM"
- the signal will be raised while the message is being processed
- exit(code: int)
- code is optional and defaults to 0
- the process will end while the message is being processed
- restart()
- spawns a new instance with the same arguments as passed to the main executable
- session_refresh()
- generates a new secure password and applies it after the response
- can be used to increase the security by using the password as some kind of
session token
- recommended to call directly after connecting and once in a while for
persistent connections
- shutdown()
- tries to force shutdown the computer
- reboot()
- tries to force reboot the computer
#### Memory
This module only functions if a password is being used to communicate.
All offsets are specified in file offsets of the corresponding `dll_name`,
which also means that your hex edits are applicable directly.
- write(dll_name: str, data: hex, offset: uint)
- writes the bytes in data to the specified offset
- read(dll_name: str, offset: uint, size: uint)
- reads bytes starting from offset and returns it
- signature(dll_name: str, signature: hex, replacement: hex, offset: uint,
usage: uint)
- tries to find the signature in the module's memory
- unknown bytes are masked out via "??" (e.g. "75??90????74")
- masking out bytes works for both the signature and the replacement
- offset is the byte difference between the offset of the found signature and
the offset where the replacement data gets written to
- usage is the number of the result being used for replacement, starting at 0
- returns the offset where the replacement data gets written to
- the replacement string can be empty, so you can only get the address of the
signature while not actually replacing anything
#### IIDX
- ticker_get()
- returns a string of the characters displayed on the 16 segment display
- ticker_set(text: str)
- sets the contents of the 16 segment display and disables writes from game
- ticker_reset()
- re-enables writes from game
- tapeled_get(name: str, ...)
- returns a list containing a dict of the current tape LED states. The dict keys are:
- `Stage Left`
- `Stage Right`
- `Cabinet Left`
- `Cabinet Right`
- `Control Panel Under`
- `Ceiling Left`
- `Title Left`
- `Title Right`
- `Ceiling Right`
- `Touch Panel Left`
- `Touch Panel Right`
- `Side Panel Left Inner`
- `Side Panel Left Outer`
- `Side Panel Left`
- `Side Panel Right Outer`
- `Side Panel Right Inner`
- `Side Panel Right`
#### LCD
- info()
- returns information about the serial LCD controller some games use
#### Resize
- image_resize_enable(enable: bool)
- enables or disables image resize state
- image_resize_set_scene(scene: int)
- sets the active scene for image resize state; set to 0 to disable resize
## Native wrapper libraries
Spicetools provides wrapper libraries in: Arduino, C++, Dart, and Python.
Python is the only one that is fully spec compliant.
Other libraries may be missing features and contain bugs; please feel free to
contribute code to fill the gaps if you work on a project using these libraries.
## Spice SDK (for DLL hooks / plugins)
As an alternative to Spice API, Spice SDK is available.
This is a flat C, header-only library that can be included in your DLL. Once
initialized, you can call directly into helper routines provided by Spicetools
executable, making low-latency and high-throughput interactions possible.
Check the documentation on the wiki for more details.
## License
Unless otherwise noted, all files are licensed under the GPLv3.
See the LICENSE file for the full license text.
Files not originating from this project should be placed in "external",
they each have their own license.
###### What that means for you
- If you want to distribute the binaries, you'll have to include the
sources, otherwise you're violating the GPL.
- If you want to merge some of this project's code into similar
software, that software has to be put under the GPL as well.
You'll probably have to modify a lot anyways, so you can just use the
source as some kind of documentation.
-144
View File
@@ -1,144 +0,0 @@
#include "acio.h"
#include <fstream>
#include <iostream>
#include <windows.h>
#include "avs/game.h"
#include "cfg/config.h"
#include "cfg/api.h"
#include "hooks/libraryhook.h"
#include "misc/eamuse.h"
#include "util/fileutils.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
#include "bi2a/bi2a.h"
#include "bmpu/bmpu.h"
#include "core/core.h"
#include "hbhi/hbhi.h"
#include "hdxs/hdxs.h"
#include "hgth/hgth.h"
#include "i36g/i36g.h"
#include "i36i/i36i.h"
#include "icca/icca.h"
#include "j32d/j32d.h"
#include "kfca/kfca.h"
#include "klpa/klpa.h"
#include "mdxf/mdxf.h"
#include "nddb/nddb.h"
#include "panb/panb.h"
#include "pix/pix.h"
#include "pjec/pjec.h"
#include "pjei/pjei.h"
#include "la9a/la9a.h"
#include "module.h"
// globals
namespace acio {
HINSTANCE DLL_INSTANCE = nullptr;
std::vector<acio::ACIOModule *> MODULES;
std::atomic<bool> IO_INIT_IN_PROGRESS = false;
}
/*
* decide on hook mode used
* libacio compiled using ICC64 sometimes doesn't leave enough space to insert the inline hooks
* in this case, we want to use IAT instead
*/
static inline acio::HookMode get_hookmode() {
#ifdef SPICE64
return acio::HookMode::IAT;
#else
return acio::HookMode::INLINE;
#endif
}
void acio::attach() {
log_info("acio", "SpiceTools ACIO");
IO_INIT_IN_PROGRESS = true;
// load settings and instance
acio::DLL_INSTANCE = LoadLibraryA("libacio.dll");
/*
* library hook
* some games have a second DLL laying around which gets loaded dynamically
* we just give it the same instance as the normal one so the hooks still work
*/
libraryhook_hook_library("libacioex.dll", acio::DLL_INSTANCE);
libraryhook_hook_library("libacio_ex.dll", acio::DLL_INSTANCE);
libraryhook_hook_library("libacio_old.dll", acio::DLL_INSTANCE);
// libacioEx.dll for Road Fighters 3D
// needed as comparisons in LoadLibrary hooks are case-sensitive
libraryhook_hook_library("libacioEx.dll", acio::DLL_INSTANCE);
libraryhook_enable(avs::game::DLL_INSTANCE);
// get hook mode
acio::HookMode hook_mode = get_hookmode();
// load modules
MODULES.push_back(new acio::BI2AModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::BMPUModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::CoreModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::HBHIModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::HDXSModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::HGTHModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::I36GModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::I36IModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::ICCAModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::J32DModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::KFCAModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::KLPAModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::MDXFModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::NDDBModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::PANBModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::PJECModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::PJEIModule(acio::DLL_INSTANCE, hook_mode));
MODULES.push_back(new acio::LA9AModule(acio::DLL_INSTANCE, hook_mode));
/*
* PIX is special and needs another DLL.
* we load that module only if the file exists.
*/
if (fileutils::file_exists(MODULE_PATH / "libacio_pix.dll")) {
HINSTANCE pix_instance = libutils::load_library(MODULE_PATH / "libacio_pix.dll");
MODULES.push_back(new acio::PIXModule(pix_instance, hook_mode));
}
// apply modules
for (auto &module : MODULES) {
module->attach();
}
IO_INIT_IN_PROGRESS = false;
}
void acio::attach_icca() {
log_info("acio", "SpiceTools ACIO ICCA");
// load instance if needed
if (!acio::DLL_INSTANCE) {
acio::DLL_INSTANCE = LoadLibraryA("libacio.dll");
}
// get hook mode
acio::HookMode hook_mode = get_hookmode();
// load single module
auto icca_module = new acio::ICCAModule(acio::DLL_INSTANCE, hook_mode);
icca_module->attach();
MODULES.push_back(icca_module);
}
void acio::detach() {
// clear modules
while (!MODULES.empty()) {
delete MODULES.back();
MODULES.pop_back();
}
}
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include <atomic>
#include <vector>
#include <windows.h>
#include "module.h"
namespace acio {
// globals
extern HINSTANCE DLL_INSTANCE;
extern std::vector<acio::ACIOModule *> MODULES;
extern std::atomic<bool> IO_INIT_IN_PROGRESS;
void attach();
void attach_icca();
void detach();
}
-821
View File
@@ -1,821 +0,0 @@
#include "bi2a.h"
#include "avs/game.h"
#include "games/ddr/io.h"
#include "games/ddr/ddr.h"
#include "games/sdvx/sdvx.h"
#include "games/sdvx/io.h"
#include "games/drs/io.h"
#include "games/drs/drs.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
#include "util/tapeled.h"
using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// state
static uint8_t STATUS_BUFFER[272] {};
static bool STATUS_BUFFER_FREEZE = false;
static unsigned int BI2A_VOLL = 0;
static unsigned int BI2A_VOLR = 0;
static bool __cdecl ac_io_bi2a_init_is_finished() {
return true;
}
static bool __cdecl ac_io_bi2a_get_control_status_buffer(void *buffer) {
// copy buffer
memcpy(buffer, STATUS_BUFFER, std::size(STATUS_BUFFER));
return true;
}
static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// Sound Voltex
if (avs::game::is_model("KFC")) {
// clear buffer
memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
STATUS_BUFFER[0] = 1;
/*
* Unmapped Buttons
*
* Control Bit
* EX BUTTON 1 93
* EX BUTTON 2 92
* EX ANALOG 1 170-183
* EX ANALOG 2 186-199
*/
// get buttons
auto &buttons = games::sdvx::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Test))) {
ARRAY_SETB(STATUS_BUFFER, 19);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Service))) {
ARRAY_SETB(STATUS_BUFFER, 18);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::CoinMech))) {
ARRAY_SETB(STATUS_BUFFER, 17);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Start))) {
ARRAY_SETB(STATUS_BUFFER, 85);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_A))) {
ARRAY_SETB(STATUS_BUFFER, 84);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_B))) {
ARRAY_SETB(STATUS_BUFFER, 83);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_C))) {
ARRAY_SETB(STATUS_BUFFER, 82);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_D))) {
ARRAY_SETB(STATUS_BUFFER, 81);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_L))) {
ARRAY_SETB(STATUS_BUFFER, 80);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_R))) {
ARRAY_SETB(STATUS_BUFFER, 95);
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Headphone))) {
ARRAY_SETB(STATUS_BUFFER, 87);
}
// volume left
const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_l_state == socd::SocdCW) {
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_r_state == socd::SocdCW) {
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// update volumes
auto &analogs = games::sdvx::get_analogs();
auto vol_left = BI2A_VOLL;
auto vol_right = BI2A_VOLR;
if (analogs.at(0).isSet() || analogs.at(1).isSet()) {
vol_left += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_L)) * 1023.99f);
vol_right += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_R)) * 1023.99f);
}
// proper loops
vol_left %= 1024;
vol_right %= 1024;
// save volumes in buffer
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
log_debug(
"bi2a",
"knobs = {} {}",
*((uint16_t*) &STATUS_BUFFER[17]),
*((uint16_t*) &STATUS_BUFFER[19]));
}
// DanceDanceRevolution
if (avs::game::is_model("MDX")) {
// clear buffer
memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
STATUS_BUFFER[0] = 1;
// get buttons
auto &buttons = games::ddr::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::COIN_MECH)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[2] |= 1 << 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::SERVICE)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[2] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::TEST)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[2] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_START)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 7;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_UP)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 6;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_DOWN)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_LEFT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P1_MENU_RIGHT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[10] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_START)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_UP)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_DOWN)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_LEFT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ddr::Buttons::P2_MENU_RIGHT)) == Buttons::BUTTON_PRESSED) {
STATUS_BUFFER[11] |= 1 << 1;
}
}
// DANCERUSH
if (avs::game::is_model("REC")) {
// clear buffer
memset(STATUS_BUFFER, 0, std::size(STATUS_BUFFER));
STATUS_BUFFER[0] = 1;
// get buttons
auto &buttons = games::drs::get_buttons();
// test
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::Test)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 19);
}
// service
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::Service)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 18);
}
// coin
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::CoinMech)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 17);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Start)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 87);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Up)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 86);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Down)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 85);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Left)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 84);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P1_Right)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 83);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Start)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 93);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Up)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 92);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Down)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 91);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Left)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 90);
}
if (Buttons::getState(RI_MGR, buttons.at(games::drs::Buttons::P2_Right)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 89);
}
}
return true;
}
static bool __cdecl ac_io_bi2a_current_coinstock(size_t index, DWORD *coins) {
// check index
if (index > 1)
return false;
// get coins and return success
*coins = (DWORD) eamuse_coin_get_stock();
return true;
}
static bool __cdecl ac_io_bi2a_consume_coinstock(size_t index, int amount) {
// check index
if (index > 1)
return false;
// calculate new stock
auto stock = eamuse_coin_get_stock();
auto stock_new = stock - amount;
// check new stock
if (stock_new < 0)
return false;
// apply new stock
eamuse_coin_set_stock(stock_new);
return true;
}
static bool __cdecl ac_io_bi2a_lock_coincounter(size_t index) {
// check index
if (index > 1)
return false;
// enable coin blocker
eamuse_coin_set_block(true);
return true;
}
static bool __cdecl ac_io_bi2a_unlock_coincounter(size_t index) {
// check index
if (index > 1)
return false;
// disable coin blocker
eamuse_coin_set_block(false);
return true;
}
static void __cdecl ac_io_bi2a_control_coin_blocker_close(size_t index) {
// check index
if (index > 1)
return;
// enable coin blocker
eamuse_coin_set_block(true);
}
static void __cdecl ac_io_bi2a_control_coin_blocker_open(size_t index) {
// check index
if (index > 1)
return;
// disable coin blocker
eamuse_coin_set_block(false);
}
static long __cdecl ac_io_bi2a_control_led_bright(size_t index, uint8_t brightness) {
// Sound Voltex
if (avs::game::is_model("KFC")) {
/*
* Control R G B
* =======================
* WING UP 28 29 30
* WING LOW 31 32 33
* WOOFER 0 1 3
* CONTROLLER 4 5 6
*
* Values go up to 255.
*
*
* Control Index
* ==================
* START BUTTON 8
* A BUTTON 9
* B BUTTON 10
* C BUTTON 11
* D BUTTON 12
* FX L BUTTON 13
* FX R BUTTON 14
* POP 24
* TITLE LEFT 25
* TITLE RIGHT 26
*
* Values go up to 127.
*/
static const struct {
int light1, light2;
float max;
} mapping[] = {
{ games::sdvx::Lights::WOOFER_R, -1, 255 },
{ games::sdvx::Lights::WOOFER_G, -1, 255 },
{ -1, -1, 0 },
{ games::sdvx::Lights::WOOFER_B, -1, 255 },
{ games::sdvx::Lights::CONTROLLER_R, -1, 255 },
{ games::sdvx::Lights::CONTROLLER_G, -1, 255 },
{ games::sdvx::Lights::CONTROLLER_B, -1, 255 },
{ -1, -1, 0 },
{ games::sdvx::Lights::START, -1, 127 },
{ games::sdvx::Lights::BT_A, -1, 127 },
{ games::sdvx::Lights::BT_B, -1, 127 },
{ games::sdvx::Lights::BT_C, -1, 127 },
{ games::sdvx::Lights::BT_D, -1, 127 },
{ games::sdvx::Lights::FX_L, -1, 127 },
{ games::sdvx::Lights::FX_R, -1, 127 },
{ -1, -1, 0 }, { -1, -1, 0 }, { -1, -1, 0 },
{ games::sdvx::Lights::GENERATOR_R, -1, 255 },
{ games::sdvx::Lights::GENERATOR_G, -1, 255 },
{ games::sdvx::Lights::GENERATOR_B, -1, 255 },
{ -1, -1, 0 }, { -1, -1, 0 }, { -1, -1, 0 },
{ games::sdvx::Lights::POP, -1, 127 },
{ games::sdvx::Lights::TITLE_LEFT, -1, 127 },
{ games::sdvx::Lights::TITLE_RIGHT, -1, 127 },
{ -1, -1, 0 },
{ games::sdvx::Lights::WING_RIGHT_UP_R, games::sdvx::Lights::WING_LEFT_UP_R, 255 },
{ games::sdvx::Lights::WING_RIGHT_UP_G, games::sdvx::Lights::WING_LEFT_UP_G, 255 },
{ games::sdvx::Lights::WING_RIGHT_UP_B, games::sdvx::Lights::WING_LEFT_UP_B, 255 },
{ games::sdvx::Lights::WING_RIGHT_LOW_R, games::sdvx::Lights::WING_LEFT_LOW_R, 255 },
{ games::sdvx::Lights::WING_RIGHT_LOW_G, games::sdvx::Lights::WING_LEFT_LOW_G, 255 },
{ games::sdvx::Lights::WING_RIGHT_LOW_B, games::sdvx::Lights::WING_LEFT_LOW_B, 255 },
};
// ignore index out of range
if (index > std::size(mapping)) {
return true;
}
// get lights
auto &lights = games::sdvx::get_lights();
// get light from mapping
auto light = mapping[index];
// write lights
if (light.light1 >= 0) {
Lights::writeLight(RI_MGR, lights[light.light1], brightness / light.max);
} else {
log_warning("sdvx", "light unset {} {}", index, (int) brightness);
}
if (light.light2 >= 0) {
Lights::writeLight(RI_MGR, lights[light.light2], brightness / light.max);
}
// DANCERUSH
} else if (avs::game::is_model("REC")) {
/*
* Control R G B
* ==============================
* CARD UNIT 13 14 15
* TITLE PANEL 28 29 30
* MONITOR SIDE LEFT (tape LED - see ac_io_bi2a_control_tapeled_bright)
* MONITOR SIDE RIGHT (tape LED - see ac_io_bi2a_control_tapeled_bright)
*
* Values go up to 127.
*
* Control Index
* ==================
* 1P LEFT 11
* 1P RIGHT 12
* 1P UP 9
* 1P DOWN 10
* 1P START 8
* 2P LEFT 19
* 2P RIGHT 20
* 2P UP 17
* 2P DOWN 18
* 2P START 16
*
* Values go up to 127.
*/
static const struct {
int light;
float max;
} mapping[] = {
{ -1, 0 }, // 0
{ -1, 0 }, // 1
{ -1, 0 }, // 2
{ -1, 0 }, // 3
{ -1, 0 }, // 4
{ -1, 0 }, // 5
{ -1, 0 }, // 6
{ -1, 0 }, // 7
{ games::drs::Lights::P1_START, 127 }, // 8
{ games::drs::Lights::P1_MENU_UP, 127 }, // 9
{ games::drs::Lights::P1_MENU_DOWN, 127 }, // 10
{ games::drs::Lights::P1_MENU_LEFT, 127 }, // 11
{ games::drs::Lights::P1_MENU_RIGHT, 127 }, // 12
{ games::drs::Lights::CARD_READER_R, 127 }, // 13
{ games::drs::Lights::CARD_READER_G, 127 }, // 14
{ games::drs::Lights::CARD_READER_B, 127 }, // 15
{ games::drs::Lights::P2_START, 127 }, // 16
{ games::drs::Lights::P2_MENU_UP, 127 }, // 17
{ games::drs::Lights::P2_MENU_DOWN, 127 }, // 18
{ games::drs::Lights::P2_MENU_LEFT, 127 }, // 19
{ games::drs::Lights::P2_MENU_RIGHT, 127 }, // 20
{ -1, 0 }, // 21
{ -1, 0 }, // 22
{ -1, 0 }, // 23
{ -1, 0 }, // 24
{ -1, 0 }, // 25
{ -1, 0 }, // 26
{ -1, 0 }, // 27
{ games::drs::Lights::TITLE_PANEL_R, 127 }, // 28
{ games::drs::Lights::TITLE_PANEL_G, 127 }, // 29
{ games::drs::Lights::TITLE_PANEL_B, 127 }, // 30
};
// ignore index out of range
if (index > std::size(mapping)) {
return true;
}
// get lights
auto &lights = games::drs::get_lights();
// get light from mapping
auto light = mapping[index];
// write lights
if (light.light >= 0) {
Lights::writeLight(RI_MGR, lights[light.light], brightness / light.max);
} else {
log_warning("drs", "light unset {} {}", index, (int) brightness);
}
// DanceDanceRevolution
} else if (avs::game::is_model("MDX")) {
static const struct {
int light;
float max;
} mapping[] = {
{ -1, 0 }, // 0
{ -1, 0 }, // 1
{ -1, 0 }, // 2
{ -1, 0 }, // 3
{ -1, 0 }, // 4
{ -1, 0 }, // 5
{ -1, 0 }, // 6
{ -1, 0 }, // 7
{ games::ddr::Lights::GOLD_P1_MENU_START, 127 }, // 8
{ games::ddr::Lights::GOLD_P1_MENU_UP, 127 }, // 9
{ games::ddr::Lights::GOLD_P1_MENU_DOWN, 127 }, // 10
{ games::ddr::Lights::GOLD_P1_MENU_LEFT, 127 }, // 11
{ games::ddr::Lights::GOLD_P1_MENU_RIGHT, 127 }, // 12
{ games::ddr::Lights::GOLD_P1_CARD_UNIT_R, 127 }, // 13
{ games::ddr::Lights::GOLD_P1_CARD_UNIT_G, 127 }, // 14
{ games::ddr::Lights::GOLD_P1_CARD_UNIT_B, 127 }, // 15
{ games::ddr::Lights::GOLD_P2_MENU_START, 127 }, // 16
{ games::ddr::Lights::GOLD_P2_MENU_UP, 127 }, // 17
{ games::ddr::Lights::GOLD_P2_MENU_DOWN, 127 }, // 18
{ games::ddr::Lights::GOLD_P2_MENU_LEFT, 127 }, // 19
{ games::ddr::Lights::GOLD_P2_MENU_RIGHT, 127 }, // 20
{ games::ddr::Lights::GOLD_P2_CARD_UNIT_R, 0 }, // 21
{ games::ddr::Lights::GOLD_P2_CARD_UNIT_G, 0 }, // 22
{ games::ddr::Lights::GOLD_P2_CARD_UNIT_B, 0 }, // 23
{ -1, 0 }, // 24
{ -1, 0 }, // 25
{ -1, 0 }, // 26
{ -1, 0 }, // 27
{ games::ddr::Lights::GOLD_TITLE_PANEL_LEFT, 0 }, // 28
{ games::ddr::Lights::GOLD_TITLE_PANEL_CENTER, 0 }, // 29
{ games::ddr::Lights::GOLD_TITLE_PANEL_RIGHT, 0 }, // 30
{ games::ddr::Lights::GOLD_P1_WOOFER_CORNER, 0 }, // 31
{ games::ddr::Lights::GOLD_P2_WOOFER_CORNER, 0 } // 32
};
// ignore index out of range
if (index > std::size(mapping)) {
return true;
}
// get lights
auto &lights = games::ddr::get_lights();
// get light from mapping
auto light = mapping[index];
// write lights
if (light.light >= 0) {
Lights::writeLight(RI_MGR, lights[light.light], brightness / light.max);
}
}
// return success
return true;
}
static long __cdecl ac_io_bi2a_get_watchdog_time_min() {
return -1;
}
static long __cdecl ac_io_bi2a_get_watchdog_time_now() {
return -1;
}
static void __cdecl ac_io_bi2a_watchdog_off() {
}
static bool __cdecl ac_io_bi2a_init(uint8_t param) {
return true;
}
static bool __cdecl ac_io_bi2a_set_watchdog_time(uint16_t time) {
return true;
}
static bool __cdecl ac_io_bi2a_get_watchdog_status() {
return true;
}
static bool __cdecl ac_io_bi2a_set_amp_volume(uint8_t a1, uint8_t a2) {
return true;
}
static bool __cdecl ac_io_bi2a_tapeled_init(uint8_t a1, uint8_t a2) {
return true;
}
static bool __cdecl ac_io_bi2a_tapeled_init_is_finished() {
return true;
}
static bool __cdecl ac_io_bi2a_control_tapeled_rec_set(uint8_t* data, size_t x_sz, size_t y_sz) {
// check dimensions
if (x_sz != DRS_TAPELED_COLS || y_sz != DRS_TAPELED_ROWS) {
log_fatal("drs", "DRS tapeled wrong dimensions");
}
// copy data into our buffer - 4 bytes per pixel BGR
for (size_t i = 0; i < x_sz * y_sz; i++) {
games::drs::DRS_TAPELED[i][0] = data[i*4+2];
games::drs::DRS_TAPELED[i][1] = data[i*4+1];
games::drs::DRS_TAPELED[i][2] = data[i*4];
}
// success
return true;
}
// TODO: DRS tape lights
static bool __cdecl ac_io_bi2a_control_tapeled_bright(size_t off1, size_t off2,
uint8_t r, uint8_t g, uint8_t b, uint8_t bank) {
if (!tapeledutils::is_enabled()) {
return true;
}
if (avs::game::is_model("MDX")) {
/*
* r, g, b values range from [0-255]
* bank always seems to be [0]
*
* [off1.off2] [LEDs] [tape name]
* 0.0 25 P1 Foot Up (0.0 to 0.24, inclusive)
* 0.25 25 P1 Foot Right
* 1.0 25 P1 Foot Left
* 1.25 25 P1 Foot Down
*
* 2.0 25 P2 Foot Up
* 2.25 25 P2 Foot Right
* 3.0 25 P2 Foot Left
* 3.25 25 P2 Foot Down
*
* 5.0 50 Top Panel
* 6.0 50 Monitor side left
* 7.0 50 Monitor side right
*/
// In order to set the data that can be output via Spice API, we first
// need to figure out which device in our buffers this data belongs to
int device = -1;
if (off1 == 0) {
if (off2 < 25) device = 0; // P1 Foot Up
else device = 1; // P1 Foot Right
} else if (off1 == 1) {
if (off2 < 25) device = 2; // P1 Foot Left
else device = 3; // P1 Foot Down
} else if (off1 == 2) {
if (off2 < 25) device = 4; // P2 Foot Up
else device = 5; // P2 Foot Right
} else if (off1 == 3) {
if (off2 < 25) device = 6; // P2 Foot Left
else device = 7; // P2 Foot Down
} else if (off1 >= 5 && off1 <= 7) {
device = off1 + 3; // Top Panel / Monitor Side Left / Monitor Side Right
}
if (device != -1) {
// We subtract 25 from off2 to get the device's LED index, if it's for one of the
// arrow panels that is on the latter half of the logical strip
size_t subtractor = 0;
if (off1 <= 3 && off2 >= 25) {
subtractor = 25;
}
size_t led_index = off2 - subtractor;
games::ddr::DDR_TAPELEDS[device][led_index][0] = r;
games::ddr::DDR_TAPELEDS[device][led_index][1] = g;
games::ddr::DDR_TAPELEDS[device][led_index][2] = b;
}
static struct TapeLedMapping {
bool split; // true == 50 LEDs for one light, false == 25 for two lights
uint8_t index_r0, index_g0, index_b0;
uint8_t index_r1, index_g1, index_b1;
size_t index_for_avg0 = UINT8_MAX;
size_t index_for_avg1 = UINT8_MAX;
TapeLedMapping(
uint8_t index_r0, uint8_t index_g0, uint8_t index_b0,
uint8_t index_r1, uint8_t index_g1, uint8_t index_b1)
: index_r0(index_r0), index_g0(index_g0), index_b0(index_b0),
index_r1(index_r1), index_g1(index_g1), index_b1(index_b1) {
split = (index_r1 != UINT8_MAX);
if (split) {
index_for_avg0 = tapeledutils::get_led_index_using_avg_algo(25);
index_for_avg1 = index_for_avg0 + 25;
} else {
index_for_avg0 = tapeledutils::get_led_index_using_avg_algo(50);
index_for_avg1 = -1;
}
}
} mapping[] = {
{
games::ddr::Lights::GOLD_P1_FOOT_UP_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_UP_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_UP_AVG_B,
games::ddr::Lights::GOLD_P1_FOOT_RIGHT_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_RIGHT_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_RIGHT_AVG_B
},
{
games::ddr::Lights::GOLD_P1_FOOT_LEFT_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_LEFT_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_LEFT_AVG_B,
games::ddr::Lights::GOLD_P1_FOOT_DOWN_AVG_R, games::ddr::Lights::GOLD_P1_FOOT_DOWN_AVG_G, games::ddr::Lights::GOLD_P1_FOOT_DOWN_AVG_B
},
{
games::ddr::Lights::GOLD_P2_FOOT_UP_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_UP_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_UP_AVG_B,
games::ddr::Lights::GOLD_P2_FOOT_RIGHT_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_RIGHT_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_RIGHT_AVG_B
},
{
games::ddr::Lights::GOLD_P2_FOOT_LEFT_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_LEFT_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_LEFT_AVG_B,
games::ddr::Lights::GOLD_P2_FOOT_DOWN_AVG_R, games::ddr::Lights::GOLD_P2_FOOT_DOWN_AVG_G, games::ddr::Lights::GOLD_P2_FOOT_DOWN_AVG_B
},
{
games::ddr::Lights::GOLD_TOP_PANEL_AVG_R, games::ddr::Lights::GOLD_TOP_PANEL_AVG_G, games::ddr::Lights::GOLD_TOP_PANEL_AVG_B,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
{
UINT8_MAX, UINT8_MAX, UINT8_MAX,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
{
games::ddr::Lights::GOLD_MONITOR_SIDE_LEFT_AVG_R, games::ddr::Lights::GOLD_MONITOR_SIDE_LEFT_AVG_G, games::ddr::Lights::GOLD_MONITOR_SIDE_LEFT_AVG_B,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
{
games::ddr::Lights::GOLD_MONITOR_SIDE_RIGHT_AVG_R, games::ddr::Lights::GOLD_MONITOR_SIDE_RIGHT_AVG_G, games::ddr::Lights::GOLD_MONITOR_SIDE_RIGHT_AVG_B,
UINT8_MAX, UINT8_MAX, UINT8_MAX
},
};
if (off1 < std::size(mapping)) {
auto &map = mapping[off1];
size_t off2_match = -1;
if (!map.split || off2 < 25) {
off2_match = map.index_for_avg0;
} else {
off2_match = map.index_for_avg1;
}
if (off2_match == off2 && map.index_r0 != UINT8_MAX) {
auto &lights = games::ddr::get_lights();
if (!map.split || off2 < 25) {
Lights::writeLight(RI_MGR, lights[map.index_r0], r / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_g0], g / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_b0], b / 255.f);
} else {
Lights::writeLight(RI_MGR, lights[map.index_r1], r / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_g1], g / 255.f);
Lights::writeLight(RI_MGR, lights[map.index_b1], b / 255.f);
}
}
}
}
return true;
}
static bool __cdecl ac_io_bi2a_tapeled_send() {
return true;
}
static int __cdecl ac_io_bi2a_get_exbio2_status(uint8_t *info) {
// surely this meme never gets old
info[5] = 5;
info[6] = 7;
info[7] = 3;
return 0;
}
acio::BI2AModule::BI2AModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("BI2A", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::BI2AModule::attach() {
ACIOModule::attach();
ACIO_MODULE_HOOK(ac_io_bi2a_init_is_finished);
ACIO_MODULE_HOOK(ac_io_bi2a_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bi2a_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bi2a_current_coinstock);
ACIO_MODULE_HOOK(ac_io_bi2a_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_bi2a_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_bi2a_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_bi2a_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_bi2a_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_bi2a_control_led_bright);
ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_bi2a_watchdog_off);
ACIO_MODULE_HOOK(ac_io_bi2a_init);
ACIO_MODULE_HOOK(ac_io_bi2a_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_bi2a_get_watchdog_status);
ACIO_MODULE_HOOK(ac_io_bi2a_set_amp_volume);
ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_init);
ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_init_is_finished);
ACIO_MODULE_HOOK(ac_io_bi2a_get_exbio2_status);
ACIO_MODULE_HOOK(ac_io_bi2a_control_tapeled_rec_set);
ACIO_MODULE_HOOK(ac_io_bi2a_control_tapeled_bright);
ACIO_MODULE_HOOK(ac_io_bi2a_tapeled_send);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class BI2AModule : public ACIOModule {
public:
BI2AModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-616
View File
@@ -1,616 +0,0 @@
#include "bmpu.h"
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "cfg/api.h"
#include "cfg/light.h"
#include "games/bbc/io.h"
#include "games/dea/io.h"
#include "games/ftt/io.h"
#include "games/museca/io.h"
#include "games/silentscope/io.h"
#include "launcher/launcher.h"
#include "misc/eamuse.h"
using namespace GameAPI;
// state
static uint8_t STATUS_BUFFER[64] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static char __cdecl ac_io_bmpu_consume_coinstock(int a1, int a2) {
eamuse_coin_consume_stock();
return 1;
}
static int __cdecl ac_io_bmpu_control_1p_start_led_off() {
// dance evolution
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P1Start), 0.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Start), 0.f);
}
return 1;
}
static int __cdecl ac_io_bmpu_control_1p_start_led_on() {
// dance evolution
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P1Start), 1.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Start), 1.f);
}
return 1;
}
static int __cdecl ac_io_bmpu_control_2p_start_led_off() {
// dance evolution
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P2Start), 0.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Keypad), 0.f);
}
return 1;
}
static int __cdecl ac_io_bmpu_control_2p_start_led_on() {
// dance evolution
if (avs::game::is_model("KDM")) {
auto &lights = games::dea::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::P2Start), 1.f);
// MUSECA
} else if (avs::game::is_model("PIX")) {
auto &lights = games::museca::get_lights();
Lights::writeLight(RI_MGR, lights.at(games::museca::Lights::Keypad), 1.f);
}
return 1;
}
static int __cdecl ac_io_bmpu_control_coin_blocker_close() {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_bmpu_control_coin_blocker_open() {
eamuse_coin_set_block(false);
return 1;
}
static bool __cdecl ac_io_bmpu_control_led_bright(uint32_t led_field, uint8_t brightness) {
// MUSECA
if (avs::game::is_model("PIX")) {
// get lights
auto &lights = games::museca::get_lights();
// control mapping
static const int mapping[] = {
games::museca::Lights::UnderLED3G,
games::museca::Lights::UnderLED3R,
games::museca::Lights::UnderLED2B,
games::museca::Lights::UnderLED2G,
games::museca::Lights::UnderLED2R,
games::museca::Lights::UnderLED1B,
games::museca::Lights::UnderLED1G,
games::museca::Lights::UnderLED1R,
-1, -1, -1, -1,
games::museca::Lights::SideB,
games::museca::Lights::SideG,
games::museca::Lights::SideR,
games::museca::Lights::UnderLED3B,
};
// write light
float value = brightness > 127.f ? 1.f : brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// BISHI BASHI CHANNEL
if (avs::game::is_model("R66")) {
// get lights
auto &lights = games::bbc::get_lights();
// control mapping
static int mapping[] = {
games::bbc::Lights::UNDER_LED3_G,
games::bbc::Lights::UNDER_LED3_R,
games::bbc::Lights::UNDER_LED2_B,
games::bbc::Lights::UNDER_LED2_G,
games::bbc::Lights::UNDER_LED2_R,
games::bbc::Lights::UNDER_LED1_B,
games::bbc::Lights::UNDER_LED1_G,
games::bbc::Lights::UNDER_LED1_R,
-1, -1, -1, -1,
games::bbc::Lights::IC_CARD_B,
games::bbc::Lights::IC_CARD_G,
games::bbc::Lights::IC_CARD_R,
games::bbc::Lights::UNDER_LED3_B,
};
// write light
float value = brightness > 127.f ? 1.f : brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// FutureTomTom
if (avs::game::is_model("MMD")) {
// get lights
auto &lights = games::ftt::get_lights();
// control mapping
static int mapping[] = {
games::ftt::Lights::Pad3_G,
games::ftt::Lights::Pad3_R,
games::ftt::Lights::Pad2_B,
games::ftt::Lights::Pad2_G,
games::ftt::Lights::Pad2_R,
games::ftt::Lights::Pad1_B,
games::ftt::Lights::Pad1_G,
games::ftt::Lights::Pad1_R,
-1, -1, -1, -1,
games::ftt::Lights::Pad4_B,
games::ftt::Lights::Pad4_G,
games::ftt::Lights::Pad4_R,
games::ftt::Lights::Pad3_B,
};
// write light
float value = brightness > 127.f ? 1.f : brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// Dance Evolution
if (avs::game::is_model("KDM")) {
// get lights
auto &lights = games::dea::get_lights();
// control mapping
static int mapping[] = {
-1,
-1,
-1,
-1,
-1,
-1,
games::dea::Lights::P2LRButton,
games::dea::Lights::P1LRButton,
-1,
games::dea::Lights::TitleB,
games::dea::Lights::TitleR,
games::dea::Lights::TitleG,
-1,
};
// write light
float value = brightness > 128.f ? 1.f : brightness / 128.f;
for (size_t i = 0; i < std::size(mapping); i++)
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
// return success
return true;
}
static bool __cdecl ac_io_bmpu_control_led_bright_pack(int a1, int a2, int a3) {
// TODO(felix): NDD lights
return true;
}
static bool __cdecl ac_io_bmpu_create_get_status_thread() {
return true;
}
static char __cdecl ac_io_bmpu_current_coinstock(int a1, int *a2) {
*a2 = eamuse_coin_get_stock();
return 1;
}
static bool __cdecl ac_io_bmpu_destroy_get_status_thread() {
return true;
}
static char __cdecl ac_io_bmpu_get_control_status_buffer(void *buffer) {
size_t buffer_len = 0;
if (avs::game::is_model({ "KDM", "MMD" })) {
buffer_len = sizeof(STATUS_BUFFER);
} else if (avs::game::is_model("PIX")) {
buffer_len = 16;
} else if (avs::game::is_model("R66")) {
buffer_len = 56;
} else if (avs::game::is_model("NDD")) {
buffer_len = 56;
}
if (buffer_len > 0) {
memcpy(buffer, &STATUS_BUFFER, buffer_len);
}
// success
return true;
}
static char *__cdecl ac_io_bmpu_get_softwareid(char *a1) {
*a1 = 0;
return a1;
}
static char *__cdecl ac_io_bmpu_get_systemid(char *a1) {
*a1 = 0;
return a1;
}
static char __cdecl ac_io_bmpu_init_outport() {
return 1;
}
static char __cdecl ac_io_bmpu_lock_coincounter(signed int a1) {
return 1;
}
static char __cdecl ac_io_bmpu_req_secplug_check_isfinished(DWORD *a1) {
return 1;
}
static char __cdecl ac_io_bmpu_req_secplug_check_softwareplug(char *a1) {
return 1;
}
static char __cdecl ac_io_bmpu_req_secplug_check_systemplug() {
return 1;
}
static char __cdecl ac_io_bmpu_req_secplug_missing_check() {
return 1;
}
static int __cdecl ac_io_bmpu_req_secplug_missing_check_isfinished(DWORD *a1) {
return 1;
}
static int __cdecl ac_io_bmpu_set_outport_led(uint8_t *data1, uint8_t *data2) {
// dance evolution
if (avs::game::is_model("KDM")) {
// get lights
auto &lights = games::dea::get_lights();
// mapping
static const size_t mapping[] {
games::dea::Lights::SideUpperLeftR,
games::dea::Lights::SideUpperLeftG,
games::dea::Lights::SideUpperLeftB,
games::dea::Lights::SideLowerLeft1R,
games::dea::Lights::SideLowerLeft1G,
games::dea::Lights::SideLowerLeft1B,
games::dea::Lights::SideLowerLeft2R,
games::dea::Lights::SideLowerLeft2G,
games::dea::Lights::SideLowerLeft2B,
games::dea::Lights::SideLowerLeft3R,
games::dea::Lights::SideLowerLeft3G,
games::dea::Lights::SideLowerLeft3B,
games::dea::Lights::SideUpperRightR,
games::dea::Lights::SideUpperRightG,
games::dea::Lights::SideUpperRightB,
games::dea::Lights::SideLowerRight1R,
games::dea::Lights::SideLowerRight1G,
games::dea::Lights::SideLowerRight1B,
games::dea::Lights::SideLowerRight2R,
games::dea::Lights::SideLowerRight2G,
games::dea::Lights::SideLowerRight2B,
games::dea::Lights::SideLowerRight3R,
games::dea::Lights::SideLowerRight3G,
games::dea::Lights::SideLowerRight3B,
};
// write lights
for (size_t i = 0; i < std::size(mapping); i++) {
float brightness = data1[i * 2] / 255.f;
Lights::writeLight(RI_MGR, lights.at(mapping[i]), brightness);
}
}
// success
return true;
}
static int __cdecl ac_io_bmpu_set_output_mode(__int16 a1) {
return 1;
}
static char __cdecl ac_io_bmpu_unlock_coincounter(int a1) {
return 1;
}
static bool __cdecl ac_io_bmpu_update_control_status_buffer() {
unsigned int control_data = 0;
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// DEA
if (avs::game::is_model("KDM")) {
// keypad mirror fix
acio::ICCA_FLIP_ROWS = true;
// get buttons
auto &buttons = games::dea::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::Test))) {
control_data |= 0xF0000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::Service))) {
control_data |= 0x0F000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P1Start))) {
control_data |= 0x00000001;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P1Left))) {
control_data |= 0x00000008;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P1Right))) {
control_data |= 0x00000010;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P2Start))) {
control_data |= 0x00000100;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P2Left))) {
control_data |= 0x00000800;
}
if (Buttons::getState(RI_MGR, buttons.at(games::dea::Buttons::P2Right))) {
control_data |= 0x00001000;
}
// set control data
auto buffer = reinterpret_cast<unsigned int *>(STATUS_BUFFER);
for (size_t i = 0; i < 16; i++) {
buffer[i] = control_data;
}
}
// FutureTomTom
if (avs::game::is_model("MMD")) {
// keypad mirror fix
acio::ICCA_FLIP_ROWS = true;
// get buttons
auto &buttons = games::ftt::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Service))) {
control_data |= 0x0F000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Test))) {
control_data |= 0xF0000000;
}
// set control data
auto buffer = reinterpret_cast<unsigned int *>(STATUS_BUFFER);
for (size_t i = 0; i < 16; i++) {
buffer[i] = control_data;
}
}
// MUSECA
if (avs::game::is_model("PIX")) {
// get buttons
auto &buttons = games::museca::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Service))) {
control_data |= 0x0F000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Test))) {
control_data |= 0xF0000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Start))) {
control_data |= 0x00000001;
}
// set control data
auto buffer = reinterpret_cast<unsigned int *>(STATUS_BUFFER);
for (size_t i = 0; i < 4; i++) {
buffer[i] = control_data;
}
}
// BISHI BASHI CHANNEL
if (avs::game::is_model("R66")) {
// get buttons
auto &buttons = games::bbc::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::Service))) {
control_data |= 0x0F000000;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::Test))) {
control_data |= 0xF0000000;
}
// set control data
auto buffer = reinterpret_cast<unsigned int *>(STATUS_BUFFER);
for (size_t i = 0; i < 4; i++) {
buffer[i] = control_data;
}
}
// Silent Scope Bone Eater
if (avs::game::is_model("NDD")) {
// clear state
memset(STATUS_BUFFER, 0, 56);
// get buttons
auto &buttons = games::silentscope::get_buttons();
// get control data
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::TEST))) {
STATUS_BUFFER[7] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::SERVICE))) {
STATUS_BUFFER[7] |= 0x2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::COIN_MECH))) {
STATUS_BUFFER[7] |= 0x1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::START))) {
STATUS_BUFFER[5] |= 0x1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::UP))) {
STATUS_BUFFER[5] |= 0x2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::DOWN))) {
STATUS_BUFFER[5] |= 0x4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::LEFT))) {
STATUS_BUFFER[5] |= 0x8;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::RIGHT))) {
STATUS_BUFFER[5] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::SCOPE_RIGHT))) {
STATUS_BUFFER[4] |= 0x80;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::SCOPE_LEFT))) {
STATUS_BUFFER[4] |= 0x40;
}
if (Buttons::getState(RI_MGR, buttons.at(games::silentscope::Buttons::GUN_PRESSED))) {
STATUS_BUFFER[4] |= 0x20;
}
// joy stick raw input
auto &analogs = games::silentscope::get_analogs();
unsigned short joy_x = 0x7FFF;
unsigned short joy_y = 0x7FFF;
if (analogs.at(games::silentscope::Analogs::GUN_X).isSet()) {
joy_x = (unsigned short) (Analogs::getState(RI_MGR, analogs.at(games::silentscope::Analogs::GUN_X)) * USHRT_MAX);
}
if (analogs.at(games::silentscope::Analogs::GUN_Y).isSet()) {
joy_y = (unsigned short) (Analogs::getState(RI_MGR, analogs.at(games::silentscope::Analogs::GUN_Y)) * USHRT_MAX);
}
// invert X axis
joy_x = USHRT_MAX - joy_x;
STATUS_BUFFER[8] = HIBYTE(joy_x);
STATUS_BUFFER[9] = LOBYTE(joy_x);
STATUS_BUFFER[10] = HIBYTE(joy_y);
STATUS_BUFFER[11] = LOBYTE(joy_y);
}
// success
return true;
}
static bool __cdecl ac_io_bmpu_set_watchdog_time(char a1) {
return true;
}
static char __cdecl ac_io_bmpu_get_watchdog_time_min() {
return 0;
}
static char __cdecl ac_io_bmpu_get_watchdog_time_now() {
return 0;
}
static void __cdecl ac_io_bmpu_watchdog_off() {
}
/*
* Module stuff
*/
acio::BMPUModule::BMPUModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("BMPU", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::BMPUModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_bmpu_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_bmpu_control_1p_start_led_off);
ACIO_MODULE_HOOK(ac_io_bmpu_control_1p_start_led_on);
ACIO_MODULE_HOOK(ac_io_bmpu_control_2p_start_led_off);
ACIO_MODULE_HOOK(ac_io_bmpu_control_2p_start_led_on);
ACIO_MODULE_HOOK(ac_io_bmpu_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_bmpu_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_bmpu_control_led_bright);
ACIO_MODULE_HOOK(ac_io_bmpu_control_led_bright_pack);
ACIO_MODULE_HOOK(ac_io_bmpu_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_bmpu_current_coinstock);
ACIO_MODULE_HOOK(ac_io_bmpu_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_bmpu_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bmpu_get_softwareid);
ACIO_MODULE_HOOK(ac_io_bmpu_get_systemid);
ACIO_MODULE_HOOK(ac_io_bmpu_init_outport);
ACIO_MODULE_HOOK(ac_io_bmpu_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_check_isfinished);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_check_softwareplug);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_check_systemplug);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_missing_check);
ACIO_MODULE_HOOK(ac_io_bmpu_req_secplug_missing_check_isfinished);
ACIO_MODULE_HOOK(ac_io_bmpu_set_outport_led);
ACIO_MODULE_HOOK(ac_io_bmpu_set_output_mode);
ACIO_MODULE_HOOK(ac_io_bmpu_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_bmpu_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_bmpu_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_bmpu_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_bmpu_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_bmpu_watchdog_off);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class BMPUModule : public ACIOModule {
public:
BMPUModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-190
View File
@@ -1,190 +0,0 @@
#include "core.h"
#include "avs/game.h"
#include "launcher/launcher.h"
#include "misc/wintouchemu.h"
#include "rawinput/rawinput.h"
// static stuff
static int ACIO_WARMUP = 0;
static HHOOK ACIO_KB_HOOK = nullptr;
/*
* Implementations
*/
// needed for some games to make GetAsyncKeyState() working
static LRESULT CALLBACK ac_io_kb_hook_callback(int nCode, WPARAM wParam, LPARAM lParam) {
CallNextHookEx(ACIO_KB_HOOK, nCode, wParam, lParam);
return 0;
}
static char __cdecl ac_io_begin(
size_t dev,
const char *ver,
unsigned int *val,
size_t flags,
void *ptr,
size_t baud)
{
if (ACIO_KB_HOOK == nullptr) {
ACIO_KB_HOOK = SetWindowsHookEx(WH_KEYBOARD_LL, ac_io_kb_hook_callback, GetModuleHandle(nullptr), 0);
}
// always return success
if (val && avs::game::is_model("KFC")) {
*val = 2;
}
return 1;
}
static char __cdecl ac_io_begin_get_status() {
return 1;
}
static int __cdecl ac_io_end(int a1) {
return 1;
}
static int __cdecl ac_io_end_get_status(int a1) {
return 1;
}
static void *__cdecl ac_io_get_rs232c_status(char *a1, int a2) {
return memset(a1, 0, 88);
}
static char __cdecl ac_io_get_version(uint8_t *a1, int a2) {
// some games have version checks
// pop'n music only accepts versions bigger than 1.X.X (check yourself), anything starting with 2 works though
memset(a1 + 5, 2, 1);
memset(a1 + 6, 0, 1);
memset(a1 + 7, 0, 1);
return 1;
}
static const char *__cdecl ac_io_get_version_string() {
static const char *version = "1.25.0";
return version;
}
static char __cdecl ac_io_is_active(int a1, int a2) {
if (a1 == 1 && avs::game::is_model("JMA")) {
return 1;
}
return (char) (++ACIO_WARMUP > 601 ? 1 : 0);
}
static int __cdecl ac_io_is_active2(int a1, int *a2, int a3) {
ACIO_WARMUP = 601;
*a2 = 6;
return 1;
}
static char __cdecl ac_io_is_active_device(int index, int a2) {
// for scotto
static bool CHECKED_24 = false;
// dance evolution
if (avs::game::is_model("KDM")) {
// disable mysterious LED devices
if (index >= 12 && index <= 15)
return false;
}
// scotto
if (avs::game::is_model("NSC") && index == 24) {
// scotto expects device index 24 to come online after
// it initializes device index 22
if (!CHECKED_24) {
CHECKED_24 = true;
return false;
}
return true;
}
// dunno for what game we did this again
return (char) (index != 5);
}
static int __cdecl ac_io_reset(int a1) {
return a1;
}
static int __cdecl ac_io_secplug_set_encodedpasswd(void *a1, int a2) {
return 1;
}
static int __cdecl ac_io_set_soft_watch_dog(int a1, int a2) {
return 1;
}
static int __cdecl ac_io_soft_watch_dog_on(int a1) {
return 1;
}
static int __cdecl ac_io_soft_watch_dog_off() {
return 1;
}
static int __cdecl ac_io_update(int a1) {
// flush device output
RI_MGR->devices_flush_output();
// update wintouchemu
wintouchemu::update();
return 1;
}
static int __cdecl ac_io_get_firmware_update_device_index() {
return 0xFF;
}
static void __cdecl ac_io_go_firmware_update() {
}
static int __cdecl ac_io_set_get_status_device(int a1) {
return a1;
}
/*
* Module stuff
*/
acio::CoreModule::CoreModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("Core", module, hookMode) {
}
void acio::CoreModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_begin);
ACIO_MODULE_HOOK(ac_io_begin_get_status);
ACIO_MODULE_HOOK(ac_io_end);
ACIO_MODULE_HOOK(ac_io_end_get_status);
ACIO_MODULE_HOOK(ac_io_get_rs232c_status);
ACIO_MODULE_HOOK(ac_io_get_version);
ACIO_MODULE_HOOK(ac_io_get_version_string);
ACIO_MODULE_HOOK(ac_io_is_active);
ACIO_MODULE_HOOK(ac_io_is_active2);
ACIO_MODULE_HOOK(ac_io_is_active_device);
ACIO_MODULE_HOOK(ac_io_reset);
ACIO_MODULE_HOOK(ac_io_secplug_set_encodedpasswd);
ACIO_MODULE_HOOK(ac_io_set_soft_watch_dog);
ACIO_MODULE_HOOK(ac_io_soft_watch_dog_on);
ACIO_MODULE_HOOK(ac_io_soft_watch_dog_off);
ACIO_MODULE_HOOK(ac_io_update);
ACIO_MODULE_HOOK(ac_io_get_firmware_update_device_index);
ACIO_MODULE_HOOK(ac_io_go_firmware_update);
ACIO_MODULE_HOOK(ac_io_set_get_status_device);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class CoreModule : public ACIOModule {
public:
CoreModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-565
View File
@@ -1,565 +0,0 @@
#include "hbhi.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "misc/eamuse.h"
#include "games/rf3d/io.h"
#include "games/sc/io.h"
#include "games/hpm/io.h"
#include "avs/game.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace GameAPI;
// state
static uint8_t STATUS_BUFFER[64] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static int __cdecl ac_io_hbhi_add_coin(int a1, int a2) {
eamuse_coin_add();
return 1;
}
static char __cdecl ac_io_hbhi_consume_coinstock(int a1, int a2) {
eamuse_coin_consume_stock();
return 1;
}
static int __cdecl ac_io_hbhi_control_coin_blocker_close(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_hbhi_control_coin_blocker_open(int a1) {
eamuse_coin_set_block(0);
return 1;
}
/*
* Helper method, not a real ACIO one
*/
static inline int __cdecl ac_io_hbhi_control_lamp_set(uint32_t lamp_bits, float value) {
// steel chronicle
if (avs::game::is_model("KGG")) {
// get lights
auto &lights = games::sc::get_lights();
// write lights
if (lamp_bits & 0x01) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::SideRed), value);
}
if (lamp_bits & 0x02) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::SideGreen), value);
}
if (lamp_bits & 0x04) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::SideBlue), value);
}
if (lamp_bits & 0x08) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::CenterRed), value);
}
if (lamp_bits & 0x10) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::CenterGreen), value);
}
if (lamp_bits & 0x20) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::CenterBlue), value);
}
if (lamp_bits & 0x40) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::ControllerRed), value);
}
if (lamp_bits & 0x80) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::sc::Lights::ControllerBlue), value);
}
}
// hello popn music
if (avs::game::is_model("JMP")) {
// get lights
auto &lights = games::hpm::get_lights();
// write lights
if (lamp_bits & 0x01) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_RED_P2_GREEN), value);
}
if (lamp_bits & 0x02) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_BLUE), value);
}
if (lamp_bits & 0x04) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_YELLOW), value);
}
if (lamp_bits & 0x08) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_GREEN), value);
}
if (lamp_bits & 0x10) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P2_RED), value);
}
if (lamp_bits & 0x20) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P2_BLUE), value);
}
if (lamp_bits & 0x40) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P2_YELLOW), value);
}
if (lamp_bits & 0x80) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P2_START), value);
}
}
// return success
return 1;
}
static bool __cdecl ac_io_hbhi_control_lamp_bright(uint32_t lamp_bits, uint8_t value) {
ac_io_hbhi_control_lamp_set(lamp_bits, value / 31.f);
return true;
}
static int __cdecl ac_io_hbhi_control_lamp_mode(uint32_t mode) {
return 1;
}
static int __cdecl ac_io_hbhi_control_lamp_off(uint8_t lamp_bits) {
return ac_io_hbhi_control_lamp_set(lamp_bits, 0.f);
}
static int __cdecl ac_io_hbhi_control_lamp_on(uint8_t lamp_bits) {
return ac_io_hbhi_control_lamp_set(lamp_bits, 1.f);
}
/*
* Helper method, not a real ACIO one
*/
static inline int __cdecl ac_io_hbhi_control_parallel_set(uint8_t lamp_bits, float value) {
// hello popn music
if (avs::game::is_model("JMP")) {
// get lights
auto &lights = games::hpm::get_lights();
// write lights
if (lamp_bits & 0x01) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::P1_START), value);
}
if (lamp_bits & 0x02) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::SPEAKER_BLUE), value);
}
if (lamp_bits & 0x04) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::SPEAKER_ORANGE), value);
}
if (lamp_bits & 0x08) {
GameAPI::Lights::writeLight(RI_MGR, lights.at(games::hpm::Lights::SPEAKER_RED), value);
}
}
// return success
return 1;
}
static int __cdecl ac_io_hbhi_control_parallel_off(uint8_t lamp_bits) {
return ac_io_hbhi_control_parallel_set(lamp_bits, 0.f);
}
static int __cdecl ac_io_hbhi_control_parallel_on(uint8_t lamp_bits) {
return ac_io_hbhi_control_parallel_set(lamp_bits, 1.f);
}
static int __cdecl ac_io_hbhi_control_reset() {
return 1;
}
static bool __cdecl ac_io_hbhi_create_get_status_thread(void *a1) {
return true;
}
static char __cdecl ac_io_hbhi_current_coinstock(int a1, int *coinstock) {
*coinstock = eamuse_coin_get_stock();
return 1;
}
static int __cdecl ac_io_hbhi_destroy_get_status_thread() {
return 1;
}
static char __cdecl ac_io_hbhi_get_coin_input_wave_buffer(int *a1) {
return 1;
}
static void *__cdecl ac_io_hbhi_get_control_status_buffer(uint8_t *buffer) {
// return buffer
memcpy(buffer, STATUS_BUFFER, std::size(STATUS_BUFFER));
return buffer;
}
static char __cdecl ac_io_hbhi_get_softwareid(char *a1) {
memset(a1, 'F', 16);
return 1;
}
static char __cdecl ac_io_hbhi_get_systemid(char *a1) {
memset(a1, 'F', 16);
return 1;
}
static bool __cdecl ac_io_hbhi_get_watchdog_status() {
return true;
}
static short __cdecl ac_io_hbhi_get_watchdog_time_min() {
return 0;
}
static short __cdecl ac_io_hbhi_get_watchdog_time_now() {
return 0;
}
static char __cdecl ac_io_hbhi_lock_coincounter(int a1) {
return 1;
}
static char __cdecl ac_io_hbhi_req_carddispenser_disburse() {
return 1;
}
static bool __cdecl ac_io_hbhi_req_carddispenser_disburse_isfinished(int *a1) {
*a1 += 1;
return true;
}
static char __cdecl ac_io_hbhi_req_carddispenser_get_status() {
return 1;
}
static int __cdecl ac_io_hbhi_req_carddispenser_get_status_isfinished(int *a1) {
*a1 += 1;
return 2;
}
static char __cdecl ac_io_hbhi_req_carddispenser_init() {
return 1;
}
static bool __cdecl ac_io_hbhi_req_carddispenser_init_isfinished(int *a1) {
*a1 += 1;
return true;
}
static char __cdecl ac_io_hbhi_req_coin_input_wave() {
return 1;
}
static char __cdecl ac_io_hbhi_req_get_control_status(int *a1) {
return 1;
}
static char __cdecl ac_io_hbhi_req_secplug_check(char *a1) {
return 1;
}
static bool __cdecl ac_io_hbhi_req_secplug_check_isfinished(int *a1) {
return true;
}
static char __cdecl ac_io_hbhi_req_secplug_check_softwareplug(char *a1) {
return 1;
}
static char __cdecl ac_io_hbhi_req_secplug_check_systemplug() {
return 1;
}
static char __cdecl ac_io_hbhi_req_secplug_missing_check() {
return 1;
}
static bool __cdecl ac_io_hbhi_req_secplug_missing_check_isfinished(int *a1) {
return true;
}
static bool __cdecl ac_io_hbhi_req_volume_control(char a1, char a2) {
return true;
}
static bool __cdecl ac_io_hbhi_req_volume_control_isfinished(int *a1) {
return true;
}
static int __cdecl ac_io_hbhi_reset_coin_slot_noise_flag(int a1) {
return 1;
}
static int __cdecl ac_io_hbhi_set_framing_err_packet_send_interval(int a1) {
return 1;
}
static bool __cdecl ac_io_hbhi_set_watchdog_time(short a1) {
return true;
}
static char __cdecl ac_io_hbhi_unlock_coincounter(int a1) {
return 1;
}
static char __cdecl ac_io_hbhi_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// steel chronicle
if (avs::game::is_model("KGG")) {
// get buttons
auto &buttons = games::sc::get_buttons();
// reset
memset(STATUS_BUFFER, 0, 64);
// check buttons
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::Service))) {
STATUS_BUFFER[5] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::Test))) {
STATUS_BUFFER[5] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::LButton))) {
STATUS_BUFFER[12] |= 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::L1))) {
STATUS_BUFFER[12] |= 1 << 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::L2))) {
STATUS_BUFFER[12] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::JogLeft))) {
STATUS_BUFFER[12] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::JogRight))) {
STATUS_BUFFER[12] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::RButton))) {
STATUS_BUFFER[12] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::R1))) {
STATUS_BUFFER[12] |= 1 << 6;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sc::Buttons::R2))) {
STATUS_BUFFER[12] |= 1 << 7;
}
// get analogs
auto &analogs = games::sc::get_analogs();
auto joy_left_x = Analogs::getState(RI_MGR, analogs.at(games::sc::Analogs::LEFT_X)) * USHRT_MAX;
auto joy_left_y = Analogs::getState(RI_MGR, analogs.at(games::sc::Analogs::LEFT_Y)) * USHRT_MAX;
auto joy_right_x = Analogs::getState(RI_MGR, analogs.at(games::sc::Analogs::RIGHT_X)) * USHRT_MAX;
auto joy_right_y = Analogs::getState(RI_MGR, analogs.at(games::sc::Analogs::RIGHT_Y)) * USHRT_MAX;
// because these are flight sticks, the X axis is inverted
*((uint16_t *) &STATUS_BUFFER[20]) = USHRT_MAX - (uint16_t) joy_left_x;
*((uint16_t *) &STATUS_BUFFER[22]) = (uint16_t) joy_left_y;
*((uint16_t *) &STATUS_BUFFER[24]) = USHRT_MAX - (uint16_t) joy_right_x;
*((uint16_t *) &STATUS_BUFFER[26]) = (uint16_t) joy_right_y;
}
// hello popn music
if (avs::game::is_model("JMP")) {
// get buttons
auto &buttons = games::hpm::get_buttons();
// reset
memset(STATUS_BUFFER, 0x00, 64);
// check buttons
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::Service))) {
STATUS_BUFFER[5] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::Test))) {
STATUS_BUFFER[5] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::CoinMech))) {
STATUS_BUFFER[5] |= 1 << 2;
}
if (!Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_Start))) {
STATUS_BUFFER[4] |= 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_1))) {
STATUS_BUFFER[12] |= 1 << 0;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_2))) {
STATUS_BUFFER[12] |= 1 << 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_3))) {
STATUS_BUFFER[12] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P1_4))) {
STATUS_BUFFER[12] |= 1 << 3;
}
if (!Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_Start))) {
STATUS_BUFFER[6] |= 1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_1))) {
STATUS_BUFFER[12] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_2))) {
STATUS_BUFFER[12] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_3))) {
STATUS_BUFFER[12] |= 1 << 6;
}
if (Buttons::getState(RI_MGR, buttons.at(games::hpm::Buttons::P2_4))) {
STATUS_BUFFER[12] |= 1 << 7;
}
}
// road fighters 3D
if (avs::game::is_model("JGT")) {
static int lever_state = 0;
// get buttons
auto &buttons = games::rf3d::get_buttons();
// reset
memset(STATUS_BUFFER, 0x00, 64);
// check buttons
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Service))) {
STATUS_BUFFER[5] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Test))) {
STATUS_BUFFER[5] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::CoinMech))) {
STATUS_BUFFER[5] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::View))) {
STATUS_BUFFER[12] |= 1 << 2;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Toggle2D3D))) {
STATUS_BUFFER[12] |= 1 << 3;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::LeverUp))) {
STATUS_BUFFER[12] |= 1 << 4;
lever_state = 0;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::LeverDown))) {
STATUS_BUFFER[12] |= 1 << 5;
lever_state = 0;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::LeverLeft))) {
STATUS_BUFFER[12] |= 1 << 6;
lever_state = 0;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::LeverRight))) {
STATUS_BUFFER[12] |= 1 << 7;
lever_state = 0;
}
// auto lever buttons
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::AutoLeverUp)) && lever_state < 6) {
lever_state++;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::AutoLeverDown)) && lever_state > 0) {
lever_state--;
}
// auto lever logic
switch (lever_state) {
case 1:
STATUS_BUFFER[12] |= 1 << 4 | 1 << 6;
break;
case 2:
STATUS_BUFFER[12] |= 1 << 4 | 1 << 6;
break;
case 3:
STATUS_BUFFER[12] |= 1 << 4;
break;
case 4:
STATUS_BUFFER[12] |= 1 << 5;
break;
case 5:
STATUS_BUFFER[12] |= 1 << 4 | 1 << 7;
break;
case 6:
STATUS_BUFFER[12] |= 1 << 5 | 1 << 7;
break;
default:
lever_state = 0;
break;
}
}
// success
return true;
}
static void __cdecl ac_io_hbhi_watchdog_off() {
}
/*
* Module stuff
*/
acio::HBHIModule::HBHIModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("HBHI", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::HBHIModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_hbhi_add_coin);
ACIO_MODULE_HOOK(ac_io_hbhi_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_hbhi_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_hbhi_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_hbhi_control_lamp_bright);
ACIO_MODULE_HOOK(ac_io_hbhi_control_lamp_mode);
ACIO_MODULE_HOOK(ac_io_hbhi_control_lamp_off);
ACIO_MODULE_HOOK(ac_io_hbhi_control_lamp_on);
ACIO_MODULE_HOOK(ac_io_hbhi_control_parallel_off);
ACIO_MODULE_HOOK(ac_io_hbhi_control_parallel_on);
ACIO_MODULE_HOOK(ac_io_hbhi_control_reset);
ACIO_MODULE_HOOK(ac_io_hbhi_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_hbhi_current_coinstock);
ACIO_MODULE_HOOK(ac_io_hbhi_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_hbhi_get_coin_input_wave_buffer);
ACIO_MODULE_HOOK(ac_io_hbhi_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_hbhi_get_softwareid);
ACIO_MODULE_HOOK(ac_io_hbhi_get_systemid);
ACIO_MODULE_HOOK(ac_io_hbhi_get_watchdog_status);
ACIO_MODULE_HOOK(ac_io_hbhi_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_hbhi_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_hbhi_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_disburse);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_disburse_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_get_status);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_get_status_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_init);
ACIO_MODULE_HOOK(ac_io_hbhi_req_carddispenser_init_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_coin_input_wave);
ACIO_MODULE_HOOK(ac_io_hbhi_req_get_control_status);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_check);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_check_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_check_softwareplug);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_check_systemplug);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_missing_check);
ACIO_MODULE_HOOK(ac_io_hbhi_req_secplug_missing_check_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_req_volume_control);
ACIO_MODULE_HOOK(ac_io_hbhi_req_volume_control_isfinished);
ACIO_MODULE_HOOK(ac_io_hbhi_reset_coin_slot_noise_flag);
ACIO_MODULE_HOOK(ac_io_hbhi_set_framing_err_packet_send_interval);
ACIO_MODULE_HOOK(ac_io_hbhi_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_hbhi_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_hbhi_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_hbhi_watchdog_off);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class HBHIModule : public ACIOModule {
public:
HBHIModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-200
View File
@@ -1,200 +0,0 @@
#include "hdxs.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "games/popn/io.h"
#include "games/rb/io.h"
#include "games/dea/io.h"
#include "avs/game.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace GameAPI;
// state
static uint8_t STATUS_BUFFER[32] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static int __cdecl ac_io_hdxs_get_control_status_buffer(int a1, void *a2) {
// copy buffer
memcpy(a2, STATUS_BUFFER, sizeof(STATUS_BUFFER));
return true;
}
static int __cdecl ac_io_hdxs_led_scroll(int a1, char a2, char a3, char a4, char a5, char a6, char a7, char a8, char a9,
char a10, char a11, char a12, char a13) {
return 1;
}
static int __cdecl ac_io_hdxs_led_set_pattern(int index, char r, char g, char b, uint64_t led_bits) {
// reflec beat
if (avs::game::is_model({"KBR", "LBR", "MBR"})) {
// get lights
auto &lights = games::rb::get_lights();
// set values
Lights::writeLight(RI_MGR, lights.at(games::rb::Lights::PoleR), r / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::rb::Lights::PoleG), g / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::rb::Lights::PoleB), b / 127.f);
}
// dance evolution
if (avs::game::is_model("KDM")) {
// get lights
auto &lights = games::dea::get_lights();
// decide on index
switch (index) {
case 12:
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperLeftR), r / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperLeftG), g / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperLeftB), b / 127.f);
break;
case 14:
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperRightR), r / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperRightG), g / 127.f);
Lights::writeLight(RI_MGR, lights.at(games::dea::Lights::SideUpperRightB), b / 127.f);
}
}
// popn
if (avs::game::is_model("M39")) {
// mappings
static const uint64_t top_led_bits[] = {
0x80000000000000,
0x40000000000000,
0x20000000000000,
0x10000000000000,
0x8000000000000,
0x4000000000000,
0x2000000000000,
0x1000000000000,
0x800000000000,
0x400000000000,
0x200000000000,
0x100000000000,
0x80000000000,
0x40000000000,
0x20000000000,
0x10000000000,
0x8000000000,
0x4000000000,
0x2000000000,
0x1000000000,
0x800000000,
0x400000000,
0x200000000,
0x100000000,
0x80000000,
0x40000000,
0x20000000,
0x10000000,
0x8000000,
0x4000000,
0x2000000,
0x1000000,
};
static const size_t light_mapping[] {
games::popn::Lights::TopLED1,
games::popn::Lights::TopLED2,
games::popn::Lights::TopLED3,
games::popn::Lights::TopLED4,
games::popn::Lights::TopLED5,
games::popn::Lights::TopLED6,
games::popn::Lights::TopLED7,
games::popn::Lights::TopLED8,
games::popn::Lights::TopLED9,
games::popn::Lights::TopLED10,
games::popn::Lights::TopLED11,
games::popn::Lights::TopLED12,
games::popn::Lights::TopLED13,
games::popn::Lights::TopLED14,
games::popn::Lights::TopLED15,
games::popn::Lights::TopLED16,
games::popn::Lights::TopLED17,
games::popn::Lights::TopLED18,
games::popn::Lights::TopLED19,
games::popn::Lights::TopLED20,
games::popn::Lights::TopLED21,
games::popn::Lights::TopLED22,
games::popn::Lights::TopLED23,
games::popn::Lights::TopLED24,
games::popn::Lights::TopLED25,
games::popn::Lights::TopLED26,
games::popn::Lights::TopLED27,
games::popn::Lights::TopLED28,
games::popn::Lights::TopLED29,
games::popn::Lights::TopLED30,
games::popn::Lights::TopLED31,
games::popn::Lights::TopLED32,
};
// get lights
auto &lights = games::popn::get_lights();
// bit scan
for (int i = 0; i < 32; i++) {
bool value = (led_bits & top_led_bits[i]) > 0;
Lights::writeLight(RI_MGR, lights.at(light_mapping[i]), value ? 1.f : 0.f);
}
// write RGB
auto value_r = r / 127.f;
auto value_g = g / 127.f;
auto value_b = b / 127.f;
Lights::writeLight(RI_MGR, lights.at(games::popn::Lights::TopLED_R), value_r);
Lights::writeLight(RI_MGR, lights.at(games::popn::Lights::TopLED_G), value_g);
Lights::writeLight(RI_MGR, lights.at(games::popn::Lights::TopLED_B), value_b);
}
return 1;
}
static int __cdecl ac_io_hdxs_led_set_rgb_mask(int a1, char a2, char a3, long a4) {
return 1;
}
static char __cdecl ac_io_hdxs_update_control_status_buffer(int a1) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// success
return true;
}
static int __cdecl ac_io_hdxs_set_framing_err_packet_send_interval(int a1) {
return a1;
}
/*
* Module stuff
*/
acio::HDXSModule::HDXSModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("HDXS", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::HDXSModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_hdxs_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_hdxs_led_scroll);
ACIO_MODULE_HOOK(ac_io_hdxs_led_set_pattern);
ACIO_MODULE_HOOK(ac_io_hdxs_led_set_rgb_mask);
ACIO_MODULE_HOOK(ac_io_hdxs_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_hdxs_set_framing_err_packet_send_interval);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class HDXSModule : public ACIOModule {
public:
HDXSModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-122
View File
@@ -1,122 +0,0 @@
#include "hgth.h"
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "cfg/api.h"
#include "games/rf3d/io.h"
#include "launcher/launcher.h"
using namespace GameAPI;
// state
static uint8_t STATUS_BUFFER[32] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static int __cdecl ac_io_hgth_set_senddata(int a1) {
return 1;
}
static char __cdecl ac_io_hgth_update_recvdata() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// Road Fighters 3D
if (avs::game::is_model("JGT")) {
// keypad mirror fix
acio::ICCA_FLIP_ROWS = true;
// variables
uint16_t wheel = 0x7FFF;
uint16_t accelerator = 0x00;
uint16_t brake = 0x00;
// get buttons
auto &buttons = games::rf3d::get_buttons();
// check buttons
bool wheel_button = false;
bool accelerate_button = false;
bool brake_button = false;
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::WheelLeft))) {
wheel -= 0x7FFF;
wheel_button = true;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::WheelRight))) {
wheel += 0x8000;
wheel_button = true;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Accelerate))) {
accelerator = 0xFFFF;
accelerate_button = true;
}
if (Buttons::getState(RI_MGR, buttons.at(games::rf3d::Buttons::Brake))) {
brake = 0xFFFF;
brake_button = true;
}
// analogs
auto &analogs = games::rf3d::get_analogs();
if (!wheel_button && analogs.at(games::rf3d::Analogs::Wheel).isSet()) {
wheel = (uint16_t) (Analogs::getState(RI_MGR, analogs.at(games::rf3d::Analogs::Wheel)) * 0xFFFF);
}
if (!accelerate_button && analogs.at(games::rf3d::Analogs::Accelerate).isSet()) {
accelerator = (uint16_t) (Analogs::getState(RI_MGR, analogs.at(games::rf3d::Analogs::Accelerate)) * 0xFFFF);
}
if (!brake_button && analogs.at(games::rf3d::Analogs::Brake).isSet()) {
brake = (uint16_t) (Analogs::getState(RI_MGR, analogs.at(games::rf3d::Analogs::Brake)) * 0xFFFF);
}
// write values
*((uint16_t *) STATUS_BUFFER + 1) = wheel;
*((uint16_t *) STATUS_BUFFER + 2) = accelerator;
*((uint16_t *) STATUS_BUFFER + 3) = brake;
}
// success
return true;
}
static void __cdecl ac_io_hgth_get_recvdata(void *buffer) {
// copy buffer
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static char __cdecl ac_io_hgth_directreq_set_handle_limit(char a1, int *a2) {
*a2 = 1;
return 1;
}
static bool __cdecl ac_io_hgth_directreq_set_handle_limit_isfinished(int *a1) {
*a1 = 2;
return true;
}
/*
* Module stuff
*/
acio::HGTHModule::HGTHModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("HGTH", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::HGTHModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_hgth_set_senddata);
ACIO_MODULE_HOOK(ac_io_hgth_update_recvdata);
ACIO_MODULE_HOOK(ac_io_hgth_get_recvdata);
ACIO_MODULE_HOOK(ac_io_hgth_directreq_set_handle_limit);
ACIO_MODULE_HOOK(ac_io_hgth_directreq_set_handle_limit_isfinished);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class HGTHModule : public ACIOModule {
public:
HGTHModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-375
View File
@@ -1,375 +0,0 @@
#include "i36g.h"
#include "launcher/launcher.h"
#include "avs/game.h"
#include "rawinput/rawinput.h"
#include "games/mga/io.h"
#include "misc/eamuse.h"
#include "util/utils.h"
using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[88 * 2] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static int __cdecl ac_io_i36g_add_coin(int a1, int a2, int a3) {
// not so sure we want to add coins
return 1;
}
static char __cdecl ac_io_i36g_consume_coinstock(int a1, int a2, int a3) {
eamuse_coin_consume_stock();
return 1;
}
static int __cdecl ac_io_i36g_control_coin_blocker_close(int a1, int a2) {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_i36g_control_coin_blocker_open(int a1, int a2) {
eamuse_coin_set_block(false);
return 1;
}
static int __cdecl ac_io_i36g_control_lamp_bright(uint32_t device, uint32_t lamp_bits, uint8_t brightness) {
// calculate value
float value = (float) brightness / 255.f;
// get lights
auto &lights = games::mga::get_lights();
// cabinet device
if (device == 21) {
if (lamp_bits & 1) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::LeftR], value);
}
if (lamp_bits & 2) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::LeftG], value);
}
if (lamp_bits & 4) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::LeftB], value);
}
if (lamp_bits & 8) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::RightR], value);
}
if (lamp_bits & 16) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::RightG], value);
}
if (lamp_bits & 32) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::RightB], value);
}
if (lamp_bits & 512) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::Start], value);
}
}
// gun device
if (device == 22) {
if (lamp_bits & 1) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::GunR], value);
}
if (lamp_bits & 2) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::GunG], value);
}
if (lamp_bits & 4) {
Lights::writeLight(RI_MGR, lights[games::mga::Lights::GunB], value);
}
}
// success
return 1;
}
static int __cdecl ac_io_i36g_control_motor_power(int device, uint8_t strength) {
// gun device
if (device == 22) {
float value = (float) strength / 255.f;
auto &lights = games::mga::get_lights();
Lights::writeLight(RI_MGR, lights[games::mga::Lights::GunVibration], value);
}
// success
return 1;
}
static char __cdecl ac_io_i36g_current_coinstock(int a1, int a2, int *a3) {
// get coinstock
*a3 = eamuse_coin_get_stock();
return 1;
}
static char __cdecl ac_io_i36g_get_coin_input_wave_buffer(int a1, char *a2) {
return 1;
}
static void __cdecl ac_io_i36g_get_control_status_buffer(int device, void *buffer) {
// cabinet buffer
if (device == 21) {
memcpy(buffer, &STATUS_BUFFER[0], 88);
}
// gun buffer
if (device == 22) {
memcpy(buffer, &STATUS_BUFFER[88], 88);
}
}
static char __cdecl ac_io_i36g_get_softwareid(int a1, int a2) {
return 1;
}
static char __cdecl ac_io_i36g_get_systemid(int a1, int a2) {
return 1;
}
static bool __cdecl ac_io_i36g_get_watchdog_status(int a1) {
return false;
}
static short __cdecl ac_io_i36g_get_watchdog_time_min(int a1) {
return 0;
}
static short __cdecl ac_io_i36g_get_watchdog_time_now(int a1) {
return 0;
}
static char __cdecl ac_io_i36g_lock_coincounter(int a1, int a2) {
return 1;
}
static char __cdecl ac_io_i36g_req_coin_input_wave(int a1) {
return 1;
}
static char __cdecl ac_io_i36g_req_get_control_status(int a1, int *a2) {
return 1;
}
static char __cdecl ac_io_i36g_req_secplug_check(int a1, char *a2) {
return 1;
}
static bool __cdecl ac_io_i36g_req_secplug_check_isfinished(int a1, int *a2) {
return true;
}
static char __cdecl ac_io_i36g_req_secplug_check_softwareplug(int a1, char *a2) {
return 1;
}
static char __cdecl ac_io_i36g_req_secplug_check_systemplug(int a1) {
return 1;
}
static char __cdecl ac_io_i36g_req_secplug_missing_check(int a1) {
return 1;
}
static bool __cdecl ac_io_i36g_req_secplug_missing_check_isfinished(int a1, int *a2) {
return true;
}
static bool __cdecl ac_io_i36g_req_volume_control(int a1, char a2, char a3, char a4, char a5) {
return true;
}
static bool __cdecl ac_io_i36g_req_volume_control_isfinished(int a1, int *ret_state) {
*ret_state = 3;
return true;
}
static int __cdecl ac_io_i36g_set_cmdmode(int a1, int a2) {
return 1;
}
static int __cdecl ac_io_i36g_set_framing_err_packet_send_interval(int a1) {
return 1;
}
static bool __cdecl ac_io_i36g_set_watchdog_time(int a1, short a2) {
return true;
}
static char __cdecl ac_io_i36g_unlock_coincounter(int a1, int a2) {
return 1;
}
static bool __cdecl ac_io_i36g_update_control_status_buffer(int node) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// Metal Gear Arcade
if (avs::game::is_model("I36")) {
// get buttons
auto &buttons = games::mga::get_buttons();
// cabinet device
if (node == 21) {
// clear status buffer
memset(&STATUS_BUFFER[0], 0, 88);
// update buttons
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::Service))) {
ARRAY_SETB(&STATUS_BUFFER[0], 44);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::Test))) {
ARRAY_SETB(&STATUS_BUFFER[0], 45);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::CoinMech))) {
ARRAY_SETB(&STATUS_BUFFER[0], 42);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::Start))) {
ARRAY_SETB(&STATUS_BUFFER[0], 124);
}
}
// gun device
if (node == 22) {
// clear status buffer
memset(&STATUS_BUFFER[88], 0, 88);
// update buttons
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::TriggerButton))
|| (GetKeyState(VK_LBUTTON) & 0x100) != 0) { // mouse button
ARRAY_SETB(&STATUS_BUFFER[88], 109);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::FrontTop))) {
ARRAY_SETB(&STATUS_BUFFER[88], 108);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::FrontBottom))) {
ARRAY_SETB(&STATUS_BUFFER[88], 106);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::SideLeft))) {
ARRAY_SETB(&STATUS_BUFFER[88], 107);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::SideRight))) {
ARRAY_SETB(&STATUS_BUFFER[88], 105);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::SideLever))) {
ARRAY_SETB(&STATUS_BUFFER[88], 104);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::SwitchButton))) {
ARRAY_SETB(&STATUS_BUFFER[88], 125);
}
if (Buttons::getState(RI_MGR, buttons.at(games::mga::Buttons::Top))) {
ARRAY_SETB(&STATUS_BUFFER[88], 124);
}
// joy stick
unsigned short joy_x = 0x7FFF;
unsigned short joy_y = 0x7FFF;
bool joy_x_pressed = false;
bool joy_y_pressed = false;
if (Buttons::getState(RI_MGR, buttons[games::mga::Buttons::JoyForwards])) {
joy_y -= 0x7FFF;
joy_y_pressed = true;
}
if (Buttons::getState(RI_MGR, buttons[games::mga::Buttons::JoyBackwards])) {
joy_y += 0x7FFF;
joy_y_pressed = true;
}
if (Buttons::getState(RI_MGR, buttons[games::mga::Buttons::JoyLeft])) {
joy_x -= 0x7FFF;
joy_x_pressed = true;
}
if (Buttons::getState(RI_MGR, buttons[games::mga::Buttons::JoyRight])) {
joy_x += 0x7FFF;
joy_x_pressed = true;
}
// joy stick raw input
auto &analogs = games::mga::get_analogs();
if (!joy_x_pressed && analogs[games::mga::Analogs::JoyX].isSet()) {
joy_x = (unsigned short) (Analogs::getState(RI_MGR, analogs[games::mga::Analogs::JoyX]) * 0xFFFF);
}
if (!joy_y_pressed && analogs[games::mga::Analogs::JoyY].isSet()) {
joy_y = (unsigned short) (Analogs::getState(RI_MGR, analogs[games::mga::Analogs::JoyY]) * 0xFFFF);
}
// save joy stick
STATUS_BUFFER[88 + 42] = LOBYTE(joy_y);
STATUS_BUFFER[88 + 43] = HIBYTE(joy_y);
STATUS_BUFFER[88 + 44] = LOBYTE(joy_x);
STATUS_BUFFER[88 + 45] = HIBYTE(joy_x);
}
}
// return success
return true;
}
static int __cdecl ac_io_i36g_watchdog_off(int a1) {
return 1;
}
/*
* Module stuff
*/
acio::I36GModule::I36GModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("I36G", module, hookMode) {
this->status_buffer = &STATUS_BUFFER[0];
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::I36GModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_i36g_add_coin);
ACIO_MODULE_HOOK(ac_io_i36g_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_i36g_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_i36g_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_i36g_control_lamp_bright);
ACIO_MODULE_HOOK(ac_io_i36g_control_motor_power);
ACIO_MODULE_HOOK(ac_io_i36g_current_coinstock);
ACIO_MODULE_HOOK(ac_io_i36g_get_coin_input_wave_buffer);
ACIO_MODULE_HOOK(ac_io_i36g_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_i36g_get_softwareid);
ACIO_MODULE_HOOK(ac_io_i36g_get_systemid);
ACIO_MODULE_HOOK(ac_io_i36g_get_watchdog_status);
ACIO_MODULE_HOOK(ac_io_i36g_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_i36g_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_i36g_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_i36g_req_coin_input_wave);
ACIO_MODULE_HOOK(ac_io_i36g_req_get_control_status);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_check);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_check_isfinished);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_check_softwareplug);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_check_systemplug);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_missing_check);
ACIO_MODULE_HOOK(ac_io_i36g_req_secplug_missing_check_isfinished);
ACIO_MODULE_HOOK(ac_io_i36g_req_volume_control);
ACIO_MODULE_HOOK(ac_io_i36g_req_volume_control_isfinished);
ACIO_MODULE_HOOK(ac_io_i36g_set_cmdmode);
ACIO_MODULE_HOOK(ac_io_i36g_set_framing_err_packet_send_interval);
ACIO_MODULE_HOOK(ac_io_i36g_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_i36g_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_i36g_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_i36g_watchdog_off);
// I36S links
this->hook((void *) ac_io_i36g_update_control_status_buffer,
"ac_io_i36s_update_control_status_buffer");
this->hook((void *) ac_io_i36g_get_control_status_buffer,
"ac_io_i36s_get_control_status_buffer");
this->hook((void *) ac_io_i36g_set_cmdmode,
"ac_io_i36s_set_cmdmode");
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class I36GModule : public ACIOModule {
public:
I36GModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-125
View File
@@ -1,125 +0,0 @@
#include "i36i.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "misc/eamuse.h"
#include "avs/game.h"
//using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[48];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static bool __cdecl ac_io_i36i_ps3_controller_pwr_on() {
return true;
}
static bool __cdecl ac_io_i36i_ps3_controller_pwr_off() {
return true;
}
static bool __cdecl ac_io_i36i_create_get_status_thread() {
return true;
}
static bool __cdecl ac_io_i36i_destroy_get_status_thread() {
return true;
}
static bool __cdecl ac_io_i36i_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// Winning Eleven
if (avs::game::is_model({ "KCK", "NCK" })) {
// TODO
}
// success
return true;
}
static bool __cdecl ac_io_i36i_get_control_status_buffer(uint8_t *buffer) {
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
return true;
}
static bool __cdecl ac_io_i36i_usb_controller_bus_IO() {
return true;
}
static bool __cdecl ac_io_i36i_usb_controller_bus_PC() {
return true;
}
static bool __cdecl ac_io_i36i_req_get_usb_desc(int a1) {
return true;
}
static bool __cdecl ac_io_i36i_req_get_usb_desc_isfinished(
int a1, uint32_t *a2, int a3, uint32_t *out_size, uint8_t *in_data, unsigned int in_size) {
// DualShock 3 device descriptor
static uint8_t DS3_DESC[] {
0x12, // bLength
0x01, // bDescriptorType (Device)
0x00, 0x02, // bcdUSB 2.00
0x00, // bDeviceClass (Use class information in the Interface Descriptors)
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
0x40, // bMaxPacketSize0 64
0x4C, 0x05, // idVendor 0x054C
0x68, 0x02, // idProduct 0x0268
0x00, 0x01, // bcdDevice 1.00
0x01, // iManufacturer (String Index)
0x02, // iProduct (String Index)
0x00, // iSerialNumber (String Index)
0x01, // bNumConfigurations 1
};
// copy descriptor to buffer
*out_size = MIN(sizeof(DS3_DESC), in_size);
memcpy(in_data, DS3_DESC, *out_size);
// we apparently need this too
*a2 = 3;
// return success
return true;
}
/*
* Module stuff
*/
acio::I36IModule::I36IModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("I36I", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::I36IModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_i36i_ps3_controller_pwr_on);
ACIO_MODULE_HOOK(ac_io_i36i_ps3_controller_pwr_off);
ACIO_MODULE_HOOK(ac_io_i36i_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_i36i_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_i36i_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_i36i_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_i36i_usb_controller_bus_IO);
ACIO_MODULE_HOOK(ac_io_i36i_usb_controller_bus_PC);
ACIO_MODULE_HOOK(ac_io_i36i_req_get_usb_desc);
ACIO_MODULE_HOOK(ac_io_i36i_req_get_usb_desc_isfinished);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class I36IModule : public ACIOModule {
public:
I36IModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-590
View File
@@ -1,590 +0,0 @@
#include <cmath>
#include "icca.h"
#include "avs/game.h"
#include "misc/eamuse.h"
#include "util/time.h"
// settings
namespace acio {
bool ICCA_FLIP_ROWS = false;
bool ICCA_COMPAT_ACTIVE = false;
}
/*
* Helpers
*/
struct ICCA_STATUS {
uint8_t status_code;
uint8_t solenoid;
uint8_t front_sensor;
uint8_t rear_sensor;
uint8_t uid[8];
int32_t error;
uint32_t key_edge;
uint32_t key_level;
};
struct ICCA_STATUS_LA9 {
uint8_t status_code;
uint8_t card_in;
uint8_t uid[8];
uint8_t error;
uint8_t uid2[8];
};
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
enum ICCA_WORKFLOW {
STEP = 0,
SLEEP = 1,
START = 2,
INIT = 3,
READY = 4,
GET_USERID = 5,
ACTIVE = 6,
EJECT = 7,
EJECT_CHECK = 8,
END = 9,
CLOSE_EJECT = 10,
CLOSE_E_CHK = 11,
CLOSE_END = 12,
ERR_GETUID = -2,
ERR_REMOVED = -11
};
// used by RA as the status_code
enum ICCA_STATUS_CODE {
EMPTY = 1,
ENGAGED = 2
};
struct ICCA_UNIT {
struct ICCA_STATUS status {};
enum ICCA_WORKFLOW state = STEP;
enum ICCA_WORKFLOW state_jdz = SLEEP;
bool card_cmd_pressed = false;
bool card_in = false;
double card_in_time = 0.0;
char key_serial = 0;
bool uid_skip = false;
bool initialized = false;
int felica_retries = 0;
};
static ICCA_UNIT ICCA_UNITS[2] {};
static bool IS_LAST_CARD_FELICA = false;
static bool STATUS_BUFFER_FREEZE = false;
static double CARD_TIMEOUT = 2.0;
static inline int icca_get_active_count() {
int active_count = 0;
for (auto unit : ICCA_UNITS) {
active_count += unit.initialized;
}
return active_count;
}
static inline int icca_get_unit_id(int unit_id) {
// on RA the unit_id is zero-indexed
if (avs::game::is_model("JDZ")) {
return unit_id;
}
if (icca_get_active_count() < 2)
return 1;
else {
if (unit_id > 1) {
return 1;
} else {
return 0;
}
}
}
static inline void update_card(int unit_id) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return;
}
// eamio keypress
int index = unit_id > 0 && icca_get_active_count() > 1 ? 1 : 0;
bool kb_insert_press = (eamuse_get_keypad_state(index) & (1 << EAM_IO_INSERT)) > 0;
static bool kb_insert_press_old[2] = {false, false};
// get unit
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
const bool card_presented = eamuse_card_insert_consume(icca_get_active_count(), unit_id);
const bool key_pressed = (kb_insert_press && !kb_insert_press_old[unit_id]);
// beatstream and nostalgia have logic that requires ac_io_icca_get_uid_felica to return the
// exact same card number multiple times in a row in order for the card number to be read...
// for whatever reason setting this to 3-4 doesn't work the very first time the game boots up
// so we use 10 just to be safe
const bool need_felica_retries = avs::game::is_model({"NBT", "PAN"});
if (need_felica_retries && (card_presented || key_pressed)) {
unit->felica_retries = 10;
}
// check for card insert
if (card_presented || key_pressed || (0 < unit->felica_retries)) {
if (!unit->card_cmd_pressed) {
unit->card_cmd_pressed = true;
if (unit->state == GET_USERID || unit->state == CLOSE_EJECT || unit->state == STEP) {
if (unit->uid_skip || eamuse_get_card(icca_get_active_count(), unit_id, unit->status.uid)) {
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->state_jdz = ACTIVE;
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
} else {
unit->state = ERR_GETUID;
unit->state_jdz = ERR_GETUID;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
memset(unit->status.uid, 0, 8);
}
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
} else if (unit->state == EJECT_CHECK) {
unit->state = SLEEP;
unit->card_in = false;
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} else if (unit->state_jdz == READY) {
unit->state = GET_USERID;
unit->state_jdz = GET_USERID;
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
}
} else {
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
if (unit->state_jdz == READY) {
unit->state_jdz = GET_USERID;
}
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
}
} else {
unit->card_cmd_pressed = false;
unit->state = CLOSE_EJECT;
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
unit->card_in = false;
memset(unit->status.uid, 0, 8);
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
unit->state_jdz = ERR_REMOVED;
}
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
}
}
// save state
kb_insert_press_old[unit_id] = kb_insert_press;
}
static bool KEYPAD_LAST[2][12];
static uint32_t KEYPAD_EAMUSE_MAPPING[] = {
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
static uint32_t KEYPAD_KEY_CODES[] = {
0x100,
0x200,
0x2000,
2,
0x400,
0x4000,
4,
0x800,
0x8000,
8,
1,
0x1000
};
static uint32_t KEYPAD_KEY_CODE_NUMS[] = {
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
static inline void keypad_update(int unit_id) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return;
}
// reset unit
struct ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->status.key_level = 0;
unit->status.error = 0;
// get eamu state
int index = unit_id > 0 && icca_get_active_count() > 1 ? 1 : 0;
uint16_t eamu_state = eamuse_get_keypad_state(index);
// iterate keypad
for (int n = 0; n < 12; n++) {
int i = n;
// flip 123 with 789
if (acio::ICCA_FLIP_ROWS) {
if (!n)
i = 11;
else if (n < 4)
i = n + 6;
else if (n > 6 && n < 10)
i = n - 6;
else if (n == 11)
i = 0;
}
// check if pressed
if ((eamu_state & (1 << KEYPAD_EAMUSE_MAPPING[i])))
{
unit->status.key_level |= KEYPAD_KEY_CODES[i];
if (!KEYPAD_LAST[unit_id][i]) {
unit->status.key_edge = KEYPAD_KEY_CODES[n] << 16;
unit->status.key_edge |= 0x80 | (unit->key_serial << 4) | KEYPAD_KEY_CODE_NUMS[n];
unit->key_serial += 1;
unit->key_serial &= 0x07;
}
KEYPAD_LAST[unit_id][i] = true;
} else {
unit->status.key_edge &= ~(KEYPAD_KEY_CODES[n] << 16);
KEYPAD_LAST[unit_id][i] = false;
}
}
}
/*
* Implementations
*/
static bool __cdecl ac_io_icca_cardunit_init(int unit_id) {
unit_id = icca_get_unit_id(unit_id);
// dirty workaround code
if (icca_get_active_count() < 1)
ICCA_UNITS[unit_id].initialized = true;
else {
ICCA_UNITS[0].initialized = true;
ICCA_UNITS[1].initialized = true;
}
// initial poll
eamuse_get_keypad_state(unit_id);
// return success
return true;
}
static char __cdecl ac_io_icca_cardunit_init_isfinished(int unit_id, DWORD *status) {
*status = READY;
return 1;
}
static char __cdecl ac_io_icca_crypt_init(int unit_id) {
return 1;
}
static char __cdecl ac_io_icca_device_control_iccard_power_supply_off(int unit_id) {
return 1;
}
static char __cdecl ac_io_icca_device_control_iccard_power_supply_on(int unit_id) {
return 1;
}
static bool __cdecl ac_io_icca_device_control_isfinished(int unit_id, DWORD *a2) {
if (a2 && avs::game::is_model("KFC")) {
*a2 = 6;
}
return true;
}
static bool __cdecl ac_io_icca_get_keep_alive_error(int unit_id, DWORD *a2) {
*a2 = 0;
return false;
}
static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
// Metal Gear Arcade and Charge Machine had the args swapped so we need to check for valid pointers!
if (reinterpret_cast<uintptr_t>(a2) > 2) {
std::swap(a1, a2);
// honestly this could be used to detect if compat mode should be active
// but we are too lazy to check if all games still work with this change
//acio::ICCA_COMPAT_ACTIVE = true;
}
// and best just leave this casting mess alone unless something is wrong with it.
// long long is required because casting to int loses precision on 64-bit
void *status = a1;
int unit_id = static_cast<int>(reinterpret_cast<long long>(a2));
// update state
unit_id = icca_get_unit_id(unit_id);
keypad_update(unit_id);
update_card(unit_id);
// copy state to output buffer
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (avs::game::is_model("JDZ")) {
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
} else {
unit->status.status_code = unit->state;
}
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
// funny workaround
if (acio::ICCA_COMPAT_ACTIVE) {
if (avs::game::is_model("LA9")) {
auto p = (ICCA_STATUS*) status;
ICCA_STATUS_LA9 p_la9;
p_la9.status_code = unit->state;
p_la9.card_in = unit->card_in;
memcpy(p_la9.uid, p->uid, sizeof(p_la9.uid));
p_la9.error = p->error;
memcpy(p_la9.uid2, p->uid, sizeof(p_la9.uid));
memcpy(status, &p_la9, sizeof(ICCA_STATUS_LA9));
} else {
// the struct is different (28 bytes instead of 24) but nobody ain't got time for that
auto p = (ICCA_STATUS*) status;
p->error = p->key_level << 16;
p->front_sensor = p->uid[0];
p->rear_sensor = p->uid[1];
for (size_t i = 2; i < sizeof(p->uid); i++) {
p->uid[i - 2] = p->uid[i];
}
p->uid[sizeof(p->uid) - 2] = 0;
p->uid[sizeof(p->uid) - 1] = 0;
}
}
return 1;
}
static char __cdecl ac_io_icca_get_uid(int unit_id, char *card) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
// copy card
memcpy(card, unit->status.uid, 8);
// set felica flag
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
// check for error
return unit->state != ERR_GETUID;
}
static char __cdecl ac_io_icca_get_uid_felica(int unit_id, char *card) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
// copy card
memcpy(card, unit->status.uid, 8);
// set felica flag
bool felica = is_card_uid_felica(unit->status.uid);
card[8] = (char) (felica ? 1 : 0);
IS_LAST_CARD_FELICA = felica;
if (0 < unit->felica_retries) {
unit->felica_retries--;
}
// check for error
return unit->state != ERR_GETUID;
}
static bool __cdecl ac_io_icca_is_felica() {
return IS_LAST_CARD_FELICA;
}
static char __cdecl ac_io_icca_req_uid(int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->state = GET_USERID;
update_card(unit_id);
return 1;
}
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (avs::game::is_model("JDZ")) {
// hack for RA - only ever seen it as this
*read_state = 8;
return 1;
}
if (unit->card_in) {
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
unit->state = END;
} else {
unit->state = ERR_GETUID;
}
unit->card_in = false;
}
*read_state = unit->state;
return 1;
}
static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
return 0;
}
static int __cdecl ac_io_icca_workflow_jdz(int workflow, int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
// RA uses "STEP" as more of a polling thing it seems
case STEP:
switch (unit->state_jdz) {
case CLOSE_EJECT:
unit->state_jdz = CLOSE_E_CHK;
break;
case CLOSE_E_CHK:
unit->state_jdz = CLOSE_END;
break;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
break;
}
break;
case SLEEP:
unit->state = SLEEP;
break;
case START:
unit->state_jdz = INIT;
break;
case INIT:
case READY:
unit->state_jdz = READY;
break;
case CLOSE_EJECT:
unit->state_jdz = CLOSE_EJECT;
return CLOSE_E_CHK;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
// should probably log a warning here
break;
}
return unit->state_jdz;
}
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
// RA uses a different workflow process for the slotted readers; treat it differently
if (avs::game::is_model("JDZ")) {
return ac_io_icca_workflow_jdz(workflow, unit_id);
}
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
case STEP:
unit->state = STEP;
break;
case SLEEP:
unit->state = SLEEP;
break;
case INIT:
unit->state = READY;
break;
case START:
if (unit->card_in)
unit->state = ACTIVE;
else
unit->state = READY;
break;
case EJECT:
unit->card_in = false;
break;
case CLOSE_EJECT:
unit->state = unit->card_in ? EJECT_CHECK : SLEEP;
break;
case CLOSE_END:
unit->state = SLEEP;
break;
case GET_USERID:
unit->state = GET_USERID;
break;
default:
break;
}
return unit->state;
}
static char __cdecl ac_io_icca_req_status(int a1, char a2) {
return 1;
}
static bool __cdecl ac_io_icca_req_status_isfinished(int a1, int *a2) {
*a2 = 11;
return true;
}
/*
* Module stuff
*/
acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("ICCA", module, hookMode) {
this->status_buffer = (uint8_t*) &ICCA_UNITS[0];
this->status_buffer_size = sizeof(ICCA_UNITS);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::ICCAModule::attach() {
ACIOModule::attach();
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
if (avs::game::is_model("JDZ")) {
ICCA_UNITS[0].initialized = true;
ICCA_UNITS[1].initialized = true;
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
}
// hooks
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
ACIO_MODULE_HOOK(ac_io_icca_crypt_init);
ACIO_MODULE_HOOK(ac_io_icca_device_control_iccard_power_supply_off);
ACIO_MODULE_HOOK(ac_io_icca_device_control_iccard_power_supply_on);
ACIO_MODULE_HOOK(ac_io_icca_device_control_isfinished);
ACIO_MODULE_HOOK(ac_io_icca_get_keep_alive_error);
ACIO_MODULE_HOOK(ac_io_icca_get_status);
ACIO_MODULE_HOOK(ac_io_icca_get_uid);
ACIO_MODULE_HOOK(ac_io_icca_get_uid_felica);
ACIO_MODULE_HOOK(ac_io_icca_is_felica);
ACIO_MODULE_HOOK(ac_io_icca_req_uid);
ACIO_MODULE_HOOK(ac_io_icca_req_uid_isfinished);
ACIO_MODULE_HOOK(ac_io_icca_send_keep_alive_packet);
ACIO_MODULE_HOOK(ac_io_icca_workflow);
ACIO_MODULE_HOOK(ac_io_icca_req_status);
ACIO_MODULE_HOOK(ac_io_icca_req_status_isfinished);
}
-21
View File
@@ -1,21 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
// settings
extern bool ICCA_FLIP_ROWS;
extern bool ICCA_COMPAT_ACTIVE;
class ICCAModule : public ACIOModule {
public:
ICCAModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
static inline bool is_card_uid_felica(uint8_t *uid) {
return uid[0] != 0xE0 && uid[1] != 0x04;
}
-158
View File
@@ -1,158 +0,0 @@
#include "j32d.h"
#include "avs/game.h"
#include "games/ftt/io.h"
#include "games/scotto/io.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace GameAPI;
// static stuff
static uint32_t STATUS_BUFFER[20] {};
static bool STATUS_BUFFER_FREEZE = false;
static uint32_t STATUS_BUFFER_COUNTER = 1;
/*
* Implementations
*/
static bool __cdecl ac_io_j32d_get_control_status_buffer(size_t a1, void* buffer, int a3) {
// set counter
STATUS_BUFFER[14] = STATUS_BUFFER_COUNTER++;
// copy buffer
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
// return success
return true;
}
static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// FutureTomTom
if (avs::game::is_model("MMD")) {
// process buttons
auto &buttons = games::ftt::get_buttons();
float pad1_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad1));
float pad2_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad2));
float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3));
float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4));
// FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad1))) {
STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) {
STATUS_BUFFER[7] = (int) (51.f * pad2_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad3))) {
STATUS_BUFFER[8] = (int) (51.f * pad3_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad4))) {
STATUS_BUFFER[9] = (int) (51.f * pad4_vel + 0.5f);
}
// process analogs
auto &analogs = games::ftt::get_analogs();
auto &pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
auto &pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
auto &pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
auto &pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
if (pad1_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f);
STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val);
}
if (pad2_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad2_analog) + 0.5f);
STATUS_BUFFER[7] = MAX(STATUS_BUFFER[7], val);
}
if (pad3_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad3_analog) + 0.5f);
STATUS_BUFFER[8] = MAX(STATUS_BUFFER[8], val);
}
if (pad4_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad4_analog) + 0.5f);
STATUS_BUFFER[9] = MAX(STATUS_BUFFER[9], val);
}
}
// Scotto
if (avs::game::is_model("NSC")) {
// get buttons
auto &buttons = games::scotto::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Cup1))) {
STATUS_BUFFER[5] |= 0x1;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Cup2))) {
STATUS_BUFFER[5] |= 0x2;
}
// process button emulation for pads
float first_pad_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::FirstPad));
float pad_a_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadA));
float pad_b_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadB));
float pad_c_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadC));
float pad_d_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadD));
float pad_e_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadE));
float pad_f_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::scotto::Buttons::PadF));
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::FirstPad))) {
STATUS_BUFFER[6] = (int) (191.f * first_pad_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadA))) {
STATUS_BUFFER[7] = (int) (51.f * pad_a_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadB))) {
STATUS_BUFFER[8] = (int) (51.f * pad_b_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadC))) {
STATUS_BUFFER[9] = (int) (51.f * pad_c_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadD))) {
STATUS_BUFFER[10] = (int) (51.f * pad_d_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadE))) {
STATUS_BUFFER[11] = (int) (51.f * pad_e_vel + 0.5f);
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::PadF))) {
STATUS_BUFFER[12] = (int) (51.f * pad_f_vel + 0.5f);
}
// TODO(felix): analogs
}
// success
return true;
}
/*
* Module stuff
*/
acio::J32DModule::J32DModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("J32D", module, hookMode) {
this->status_buffer = (uint8_t*) &STATUS_BUFFER[0];
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::J32DModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_j32d_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_j32d_update_control_status_buffer);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class J32DModule : public ACIOModule {
public:
J32DModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-504
View File
@@ -1,504 +0,0 @@
#include "kfca.h"
#include "avs/game.h"
#include "games/bs/io.h"
#include "games/nost/io.h"
#include "games/scotto/io.h"
#include "games/sdvx/sdvx.h"
#include "games/sdvx/io.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h"
using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// globals
uint8_t KFCA_VOL_SOUND = 96;
uint8_t KFCA_VOL_HEADPHONE = 96;
uint8_t KFCA_VOL_EXTERNAL = 96;
uint8_t KFCA_VOL_WOOFER = 96;
// static stuff
static uint8_t STATUS_BUFFER[64] {};
static bool STATUS_BUFFER_FREEZE = false;
static unsigned int KFCA_VOLL = 0;
static unsigned int KFCA_VOLR = 0;
/*
* Implementations
*/
static int __cdecl ac_io_kfca_control_button_led(unsigned int button, bool state) {
// Sound Voltex
if (avs::game::is_model("KFC")) {
// control mapping
static const size_t mapping[] = {
games::sdvx::Lights::BT_A,
games::sdvx::Lights::BT_B,
games::sdvx::Lights::BT_C,
games::sdvx::Lights::BT_D,
games::sdvx::Lights::FX_L,
games::sdvx::Lights::FX_R,
games::sdvx::Lights::START,
games::sdvx::Lights::GENERATOR_B,
};
// check if button is mapped
if (button < 8) {
// get lights
auto &lights = games::sdvx::get_lights();
// write light
float value = state ? 1.f : 0.f;
Lights::writeLight(RI_MGR, lights.at(mapping[button]), value);
}
}
// Scotto
if (avs::game::is_model("NSC")) {
// control mapping
static const size_t mapping[] = {
games::scotto::Lights::PAD_F_B,
games::scotto::Lights::PAD_E_R,
games::scotto::Lights::PAD_E_B,
~0u,
~0u,
~0u,
games::scotto::Lights::PAD_F_R,
games::scotto::Lights::BUTTON,
};
// check if button is mapped
if (button < std::size(mapping) && button[mapping] != ~0u) {
// get lights
auto &lights = games::scotto::get_lights();
// write light
float value = state ? 1.f : 0.f;
Lights::writeLight(RI_MGR, lights.at(mapping[button]), value);
}
}
// return success
return 1;
}
static int __cdecl ac_io_kfca_control_coin_blocker_close(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_kfca_control_coin_blocker_open(int a1) {
eamuse_coin_set_block(false);
return 1;
}
static int __cdecl ac_io_kfca_control_led_bright(uint32_t led_field, uint8_t brightness) {
// Sound Voltex
if (avs::game::is_model("KFC")) {
// get lights
auto &lights = games::sdvx::get_lights();
// control mapping
static const size_t mapping[] {
games::sdvx::Lights::WING_LEFT_UP_R,
games::sdvx::Lights::WING_LEFT_UP_G,
games::sdvx::Lights::WING_LEFT_UP_B,
games::sdvx::Lights::WING_RIGHT_UP_R,
games::sdvx::Lights::WING_RIGHT_UP_G,
games::sdvx::Lights::WING_RIGHT_UP_B,
games::sdvx::Lights::WING_LEFT_LOW_R,
games::sdvx::Lights::WING_LEFT_LOW_G,
games::sdvx::Lights::WING_LEFT_LOW_B,
games::sdvx::Lights::WING_RIGHT_LOW_R,
games::sdvx::Lights::WING_RIGHT_LOW_G,
games::sdvx::Lights::WING_RIGHT_LOW_B,
games::sdvx::Lights::WOOFER_R,
games::sdvx::Lights::WOOFER_G,
games::sdvx::Lights::WOOFER_B,
games::sdvx::Lights::CONTROLLER_R,
games::sdvx::Lights::CONTROLLER_G,
games::sdvx::Lights::CONTROLLER_B,
games::sdvx::Lights::GENERATOR_R,
games::sdvx::Lights::GENERATOR_G,
};
// write light
float value = brightness / 255.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
}
// BeatStream
if (avs::game::is_model("NBT")) {
// get lights
auto &lights = games::bs::get_lights();
// mapping
static const size_t mapping[] {
~0u, ~0u, ~0u,
games::bs::Lights::RightR,
games::bs::Lights::RightG,
games::bs::Lights::RightB,
games::bs::Lights::LeftR,
games::bs::Lights::LeftG,
games::bs::Lights::LeftB,
games::bs::Lights::BottomR,
games::bs::Lights::BottomG,
games::bs::Lights::BottomB,
};
// write light
float value = brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] != ~0u && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
}
// Nostalgia
if (avs::game::is_model("PAN")) {
// get lights
auto &lights = games::nost::get_lights();
// mapping
static const size_t mapping[] {
~0u, ~0u, ~0u,
games::nost::Lights::TitleR,
games::nost::Lights::TitleG,
games::nost::Lights::TitleB,
~0u, ~0u, ~0u,
games::nost::Lights::BottomR,
games::nost::Lights::BottomG,
games::nost::Lights::BottomB,
};
// write light
float value = brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] != ~0u && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
}
// Scotto
if (avs::game::is_model("NSC")) {
// get lights
auto &lights = games::scotto::get_lights();
// mapping
static const size_t mapping[] {
games::scotto::Lights::CUP_R,
games::scotto::Lights::CUP_G,
games::scotto::Lights::CUP_B,
games::scotto::Lights::PAD_A_R,
games::scotto::Lights::PAD_A_G,
games::scotto::Lights::PAD_A_B,
games::scotto::Lights::PAD_B_R,
games::scotto::Lights::PAD_B_G,
games::scotto::Lights::PAD_B_B,
games::scotto::Lights::PAD_C_R,
games::scotto::Lights::PAD_C_G,
games::scotto::Lights::PAD_C_B,
games::scotto::Lights::PAD_D_R,
games::scotto::Lights::PAD_D_G,
games::scotto::Lights::PAD_D_B,
games::scotto::Lights::FIRST_PAD_R,
games::scotto::Lights::FIRST_PAD_G,
games::scotto::Lights::FIRST_PAD_B,
games::scotto::Lights::PAD_F_G,
games::scotto::Lights::PAD_E_G,
};
// write light
float value = brightness / 255.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at(mapping[i]), value);
}
}
}
// return success
return 1;
}
static char __cdecl ac_io_kfca_current_coinstock(int a1, DWORD *a2) {
*a2 = (DWORD) eamuse_coin_get_stock();
return 1;
}
static void *__cdecl ac_io_kfca_get_control_status_buffer(void *target_buffer) {
// copy buffer
return memcpy(target_buffer, STATUS_BUFFER, 64);
}
static int __cdecl ac_io_kfca_lock_coincounter(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static bool __cdecl ac_io_kfca_req_volume_control(
uint8_t vol_sound, uint8_t vol_headphone, uint8_t vol_external, uint8_t vol_woofer) {
// update globals
KFCA_VOL_SOUND = vol_sound;
KFCA_VOL_HEADPHONE = vol_headphone;
KFCA_VOL_EXTERNAL = vol_external;
KFCA_VOL_WOOFER = vol_woofer;
// Sound Voltex
if (avs::game::is_model("KFC")) {
// get lights
auto &lights = games::sdvx::get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_SOUND],
(100 - vol_sound) / 100.f);
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_HEADPHONE],
(100 - vol_headphone) / 100.f);
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_EXTERNAL],
(100 - vol_external) / 100.f);
GameAPI::Lights::writeLight(RI_MGR, lights[games::sdvx::Lights::VOLUME_WOOFER],
(100 - vol_woofer) / 100.f);
}
return true;
}
static bool __cdecl ac_io_kfca_set_watchdog_time(short a1) {
return true;
}
static char __cdecl ac_io_kfca_unlock_coincounter(int a1) {
eamuse_coin_set_block(false);
return 1;
}
static char __cdecl ac_io_kfca_update_control_status_buffer() {
static const int input_offset = 4;
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, 64);
// SDVX
if (avs::game::is_model("KFC")) {
// get buttons
auto &buttons = games::sdvx::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Test))) {
STATUS_BUFFER[input_offset + 1] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Service))) {
STATUS_BUFFER[input_offset + 1] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::CoinMech))) {
STATUS_BUFFER[input_offset + 1] |= 0x04;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Start))) {
STATUS_BUFFER[input_offset + 9] |= 0x08;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_A))) {
STATUS_BUFFER[input_offset + 9] |= 0x04;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_B))) {
STATUS_BUFFER[input_offset + 9] |= 0x02;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_C))) {
STATUS_BUFFER[input_offset + 9] |= 0x01;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::BT_D))) {
STATUS_BUFFER[input_offset + 11] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_L))) {
STATUS_BUFFER[input_offset + 11] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::FX_R))) {
STATUS_BUFFER[input_offset + 11] |= 0x08;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::Headphone))) {
STATUS_BUFFER[input_offset + 9] |= 0x20;
}
// volume left
const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_l_state == socd::SocdCW) {
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} else if (vol_r_state == socd::SocdCW) {
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// update volumes
auto &analogs = games::sdvx::get_analogs();
auto vol_left = KFCA_VOLL;
auto vol_right = KFCA_VOLR;
if (analogs.at(0).isSet() || analogs.at(1).isSet()) {
vol_left += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_L)) * 1023.99f);
vol_right += (unsigned int) (Analogs::getState(RI_MGR,
analogs.at(games::sdvx::Analogs::VOL_R)) * 1023.99f);
}
// proper loops
vol_left %= 1024;
vol_right %= 1024;
log_debug("kfca", "knobs = {} {}", vol_left, vol_right);
// save volumes in buffer
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
STATUS_BUFFER[input_offset + 16 + 1] |= (unsigned char) ((vol_left >> 2) & 0xFF);
STATUS_BUFFER[input_offset + 16 + 2] |= (unsigned char) ((vol_right << 6) & 0xFF);
STATUS_BUFFER[input_offset + 16 + 3] |= (unsigned char) ((vol_right >> 2) & 0xFF);
}
// Beatstream
if (avs::game::is_model("NBT")) {
// get buttons
auto &buttons = games::bs::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::bs::Buttons::Test))) {
STATUS_BUFFER[input_offset + 1] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bs::Buttons::Service))) {
STATUS_BUFFER[input_offset + 1] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bs::Buttons::CoinMech))) {
STATUS_BUFFER[input_offset + 1] |= 0x04;
}
}
// Nostalgia
if (avs::game::is_model("PAN")) {
// get buttons
auto &buttons = games::nost::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::Service))) {
STATUS_BUFFER[input_offset + 1] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::Test))) {
STATUS_BUFFER[input_offset + 1] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::nost::Buttons::CoinMech))) {
STATUS_BUFFER[input_offset + 1] |= 0x04;
}
}
// Scotto
if (avs::game::is_model("NSC")) {
// get buttons
auto &buttons = games::scotto::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Test)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 1] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Service)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 1] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::CoinMech)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 1] |= 0x04;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Start)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 9] |= 0x20;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Up)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 9] |= 0x10;
}
if (Buttons::getState(RI_MGR, buttons.at(games::scotto::Buttons::Down)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[input_offset + 9] |= 0x08;
}
// the code also checks `input_offset + 9` for 0x01 but that does not trigger any response
// in the "I/O CHECK" scene
}
// success
return true;
}
static void __cdecl ac_io_kfca_watchdog_off() {
}
// yes this is spelled "marge" instead of "merge"
static int __cdecl ac_io_kfca_set_status_marge_func(void *cb) {
return 1;
}
/*
* Module stuff
*/
acio::KFCAModule::KFCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("KFCA", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::KFCAModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_kfca_control_button_led);
ACIO_MODULE_HOOK(ac_io_kfca_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_kfca_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_kfca_control_led_bright);
ACIO_MODULE_HOOK(ac_io_kfca_current_coinstock);
ACIO_MODULE_HOOK(ac_io_kfca_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_kfca_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_kfca_req_volume_control);
ACIO_MODULE_HOOK(ac_io_kfca_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_kfca_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_kfca_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_kfca_watchdog_off);
ACIO_MODULE_HOOK(ac_io_kfca_set_status_marge_func);
}
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
extern uint8_t KFCA_VOL_SOUND;
extern uint8_t KFCA_VOL_HEADPHONE;
extern uint8_t KFCA_VOL_EXTERNAL;
extern uint8_t KFCA_VOL_WOOFER;
class KFCAModule : public ACIOModule {
public:
KFCAModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-238
View File
@@ -1,238 +0,0 @@
#include "klpa.h"
#include "avs/game.h"
#include "games/loveplus/io.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
using namespace GameAPI;
static uint8_t STATUS_BUFFER[48];
static bool STATUS_BUFFER_FREEZE = false;
static const size_t LOVEPLUS_LIGHTS_MAPPING[] = {
games::loveplus::Lights::Red,
games::loveplus::Lights::Green,
games::loveplus::Lights::Blue,
SIZE_MAX,
games::loveplus::Lights::Right,
games::loveplus::Lights::Left,
};
static char __cdecl ac_io_klpa_consume_coinstock(int a1, DWORD *a2) {
*a2 = (DWORD) eamuse_coin_get_stock();
return 1;
}
static int __cdecl ac_io_klpa_control_coin_blocker_close(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static int __cdecl ac_io_klpa_control_coin_blocker_open(int a1) {
eamuse_coin_set_block(false);
return 1;
}
static int __cdecl ac_io_klpa_control_led_off(size_t index) {
// LovePlus
if (avs::game::is_model("KLP") && index < std::size(LOVEPLUS_LIGHTS_MAPPING)) {
// get lights
auto &lights = games::loveplus::get_lights();
if (LOVEPLUS_LIGHTS_MAPPING[index] != SIZE_MAX) {
Lights::writeLight(RI_MGR, lights.at(LOVEPLUS_LIGHTS_MAPPING[index]), 0.f);
}
}
// return success
return 1;
}
static int __cdecl ac_io_klpa_control_led_on(size_t index) {
// LovePlus
if (avs::game::is_model("KLP") && index < std::size(LOVEPLUS_LIGHTS_MAPPING)) {
// get lights
auto &lights = games::loveplus::get_lights();
if (LOVEPLUS_LIGHTS_MAPPING[index] != SIZE_MAX) {
Lights::writeLight(RI_MGR, lights.at(LOVEPLUS_LIGHTS_MAPPING[index]), 1.f);
}
}
// return success
return 1;
}
static bool __cdecl ac_io_klpa_create_get_status_thread() {
return 1;
}
static char __cdecl ac_io_klpa_current_coinstock(int a1, DWORD *a2) {
// check bounds
if (a1 < 0 || a1 >= 2) {
return 0;
}
*a2 = (DWORD) eamuse_coin_get_stock();
// return success
return 1;
}
static bool __cdecl ac_io_klpa_destroy_get_status_thread() {
return 1;
}
static void* __cdecl ac_io_klpa_get_control_status_buffer(void *a1) {
// copy buffer
return memcpy(a1, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static void __cdecl ac_io_klpa_get_io_command_mode(void *a1) {
memset(a1, 0, 4);
}
static int __cdecl ac_io_klpa_led_reset() {
if (avs::game::is_model("KLP")) {
// get lights
auto &lights = games::loveplus::get_lights();
for (const auto &mapping : LOVEPLUS_LIGHTS_MAPPING) {
if (mapping != SIZE_MAX) {
Lights::writeLight(RI_MGR, lights.at(mapping), 0.f);
}
}
}
return 1;
}
static int __cdecl ac_io_klpa_lock_coincounter(int a1) {
eamuse_coin_set_block(true);
return 1;
}
static bool __cdecl ac_io_klpa_set_io_command_mode(int a1) {
return true;
}
static bool __cdecl ac_io_klpa_set_io_command_mode_is_finished(uint8_t *a1) {
*a1 = 0;
return true;
}
static int __cdecl ac_io_klpa_set_led_bright(size_t index, uint8_t brightness) {
// LovePlus
if (avs::game::is_model("KLP") && index < std::size(LOVEPLUS_LIGHTS_MAPPING)) {
// get lights
auto &lights = games::loveplus::get_lights();
if (LOVEPLUS_LIGHTS_MAPPING[index] != SIZE_MAX) {
Lights::writeLight(RI_MGR, lights.at(LOVEPLUS_LIGHTS_MAPPING[index]), brightness / 127.f);
}
}
return 1;
}
static bool __cdecl ac_io_klpa_set_sound_mute(int a1) {
return true;
}
static bool __cdecl ac_io_klpa_set_sound_mute_is_finished(int a1) {
return true;
}
static bool __cdecl ac_io_klpa_set_watchdog_time(short a1) {
return true;
}
static char __cdecl ac_io_klpa_unlock_coincounter(int a1) {
eamuse_coin_set_block(false);
return 1;
}
static bool __cdecl ac_io_klpa_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// reset buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// LovePlus
if (avs::game::is_model("KLP")) {
// get buttons
auto &buttons = games::loveplus::get_buttons();
if (Buttons::getState(RI_MGR, buttons.at(games::loveplus::Buttons::Test)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[5] |= 1 << 5;
}
if (Buttons::getState(RI_MGR, buttons.at(games::loveplus::Buttons::Service)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[5] |= 1 << 4;
}
if (Buttons::getState(RI_MGR, buttons.at(games::loveplus::Buttons::Left)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[12] |= 1 << 6;
}
if (Buttons::getState(RI_MGR, buttons.at(games::loveplus::Buttons::Right)) == Buttons::State::BUTTON_PRESSED) {
STATUS_BUFFER[12] |= 1 << 7;
}
// x[9] & 0x3F) = volume output level?
// x[11] & 0x3F) = volume output level?
// x[12] |= (1 << 4) = headphone jack
}
// success
return true;
}
/*
* Module stuff
*/
acio::KLPAModule::KLPAModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("KLPA", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::KLPAModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_klpa_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_klpa_control_coin_blocker_close);
ACIO_MODULE_HOOK(ac_io_klpa_control_coin_blocker_open);
ACIO_MODULE_HOOK(ac_io_klpa_control_led_off);
ACIO_MODULE_HOOK(ac_io_klpa_control_led_on);
ACIO_MODULE_HOOK(ac_io_klpa_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_klpa_current_coinstock);
ACIO_MODULE_HOOK(ac_io_klpa_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_klpa_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_klpa_get_io_command_mode);
ACIO_MODULE_HOOK(ac_io_klpa_led_reset);
ACIO_MODULE_HOOK(ac_io_klpa_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_klpa_set_io_command_mode);
ACIO_MODULE_HOOK(ac_io_klpa_set_io_command_mode_is_finished);
ACIO_MODULE_HOOK(ac_io_klpa_set_led_bright);
ACIO_MODULE_HOOK(ac_io_klpa_set_sound_mute);
ACIO_MODULE_HOOK(ac_io_klpa_set_sound_mute_is_finished);
ACIO_MODULE_HOOK(ac_io_klpa_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_klpa_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_klpa_update_control_status_buffer);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class KLPAModule : public ACIOModule {
public:
KLPAModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-113
View File
@@ -1,113 +0,0 @@
#include "la9a.h"
#include "games/pcm/io.h"
#include "hooks/graphics/graphics.h"
#include "touch/touch.h"
#include "util/utils.h"
#ifdef max
#undef max
#endif
namespace acio {
#pragma pack(push, 1)
struct la9a_control_status {
uint8_t p1 : 6;
uint8_t service_button : 1;
uint8_t test_button : 1;
uint8_t p2[9];
uint8_t lcd_counter;
uint8_t p3[5];
uint16_t touch_x;
uint16_t touch_y;
uint16_t touch_z;
uint8_t p4[26];
};
#pragma pack(pop)
static struct la9a_control_status CONTROL_STATUS {};
static bool TOUCH_ATTACHED = false;
static bool __cdecl ac_io_la9a_set_error_message(int, unsigned int, int) {
return true;
}
static bool __cdecl ac_io_la9a_update_control_status_buffer() {
CONTROL_STATUS.touch_z = 0xFF;
CONTROL_STATUS.test_button = 0;
CONTROL_STATUS.service_button = 0;
// attach touch handler on the first call to this function
if (!TOUCH_ATTACHED) {
log_misc("la9a", "attach touch handler");
HWND hwnd = FindWindowBeginsWith("LA9");
if (!hwnd) {
log_fatal("la9a", "LA9 window not found");
}
touch_create_wnd(hwnd);
graphics_hook_window(hwnd, nullptr);
if (GRAPHICS_SHOW_CURSOR) {
ShowCursor(1);
}
TOUCH_ATTACHED = true;
}
// update touch
std::vector<TouchPoint> touch_points;
touch_get_points(touch_points);
if (!touch_points.empty()) {
auto &touch_point = touch_points[0];
// TODO: `x` and `y` should be clamped [0, std::numeric_limits<uint16_t>::max())
CONTROL_STATUS.touch_x = static_cast<uint16_t>(touch_point.x);
CONTROL_STATUS.touch_y = static_cast<uint16_t>(touch_point.y);
CONTROL_STATUS.touch_z = 0;
}
CONTROL_STATUS.lcd_counter++;
// update buttons
auto &buttons = games::pcm::get_buttons();
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::pcm::Buttons::Test])) {
CONTROL_STATUS.test_button = 1;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::pcm::Buttons::Service])) {
CONTROL_STATUS.service_button = 1;
}
return true;
}
static int __cdecl ac_io_la9a_update_counter(int, int) {
return 0;
}
static int __cdecl ac_io_la9a_update_lcd(int) {
return 1;
}
static void __cdecl ac_io_la9a_get_control_status_buffer(struct la9a_control_status *control_status) {
*control_status = CONTROL_STATUS;
}
LA9AModule::LA9AModule(HMODULE module, HookMode hookMode) : ACIOModule("LA9A", module, hookMode) {
//this->status_buffer = STATUS_BUFFER;
//this->status_buffer_size = sizeof(STATUS_BUFFER);
//this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void LA9AModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_la9a_set_error_message);
ACIO_MODULE_HOOK(ac_io_la9a_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_la9a_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_la9a_update_lcd);
ACIO_MODULE_HOOK(ac_io_la9a_update_counter);
}
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class LA9AModule : public ACIOModule {
public:
LA9AModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-518
View File
@@ -1,518 +0,0 @@
#include "mdxf.h"
#include "mdxf_poll.h"
#include <mutex>
#include "avs/game.h"
#include "games/ddr/ddr.h"
#include "games/ddr/io.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#define MDFX_DEBUG_VERBOSE 0
#if MDFX_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// constants
const size_t STATUS_BUFFER_SIZE = 32;
const size_t STATUS_BUFFER_NUM_ENTRIES = 16;
// static stuff
static uint8_t HEAD_P1 = 0;
static uint8_t HEAD_P2 = 0;
static uint16_t PREV_STATE_P1 = 0;
static uint16_t PREV_STATE_P2 = 0;
static uint64_t PREV_TIME_P1 = 0;
static uint64_t PREV_TIME_P2 = 0;
static std::mutex MUTEX_P1;
static std::mutex MUTEX_P2;
static bool IS_MDXF_ACTIVE = false;
static const uint8_t BACKFILL_INTERVAL_MS = 4;
static const uint8_t BACKFILL_PADDING_MS = 2;
// These are used to determine if a thread needs to be spun up to keep pad state ring buffers populated with enough recent polls
static uint64_t START_TIME = 0;
static int CALL_COUNT = 0;
static const int THRESHOLD_REFRESH_RATE = 120;
static std::atomic<bool> IS_REFRESH_RATE_MEASUREMENT_STARTED{false};
static std::atomic<bool> IS_REFRESH_RATE_DETERMINED{false};
static std::atomic<bool> IS_THREAD_NEEDED{false};
static std::atomic<bool> MDXF_THREAD_RUNNING{false};
static std::thread MDXF_THREAD;
static constexpr int THREAD_REFRESH_RATE_HZ = 125;
static constexpr auto THREAD_PERIOD = std::chrono::milliseconds(1000 / THREAD_REFRESH_RATE_HZ);
// buffers
#pragma pack(push, 1)
static struct {
uint8_t STATUS_BUFFER_P1[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {};
uint8_t STATUS_BUFFER_P2[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {};
} BUFFERS {};
#pragma pack(pop)
static bool STATUS_BUFFER_FREEZE = false;
// Decides which method to use for populating ring buffer entries for "padding".
// Overwritten in spicecfg using P4IO Buffer Algorithm option.
// THREAD_MODE: Spins a thread running at THREAD_REFRESH_RATE_HZ which periodically fills the ring
// buffer with auxiliary entries. Falls back on BACKFILL_MODE
// BACKFILL_MODE: On every update cycle, fill the ring buffer with entries for the last known state
// BACKFILL_INTERVAL_MS apart from each other from the time of the last entry to the
// current time before adding the entry for the current state.
// AUTO_MODE: thread mode if <120Hz, backfill if >=120Hz
acio::MDXFBufferFillMode acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::AUTO_MODE;
typedef enum {
ARKMDXP4_POLL = 0,
INTERNAL_POLL = 1,
EXTERNAL_POLL = 2
} MDXFPollSource;
typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)();
static uint64_t arkGetTickTime64() {
static ARK_GET_TICK_TIME64_T getTickTime64 = nullptr;
if (!getTickTime64) {
HMODULE h = avs::game::DLL_INSTANCE;
if (h) {
getTickTime64 = (ARK_GET_TICK_TIME64_T)GetProcAddress(h, "arkGetTickTime64");
}
}
// this works on 32-bit versions of avs, but not on 64.
// it's better than nothing though.
return getTickTime64 ? getTickTime64() : timeGetTime();
}
// Used to keep the ring buffer populated with steady updates. 60Hz interval is too slow
static void mdxf_thread_start() {
bool expected = false;
if (!MDXF_THREAD_RUNNING.compare_exchange_strong(expected, true)) {
return;
}
log_info("mdxf", "starting poll thread");
MDXF_THREAD = std::thread([] {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
timeutils::PreciseSleepTimer timer;
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
mdxf_poll(false);
timer.sleep(THREAD_PERIOD);
}
});
}
static void mdxf_thread_stop() {
if (!MDXF_THREAD_RUNNING.exchange(false)) {
return;
}
if (MDXF_THREAD.joinable()) {
MDXF_THREAD.join();
}
}
// Snaps measured refresh rate to best fit
static int snap_refresh_rate(int measured_hz) {
static constexpr std::array<int, 6> rates = {
60, 120, 144, 165, 180, 240
};
int best = rates[0];
int best_err = std::fabs(measured_hz - best);
for (int r : rates) {
int err = std::fabs(measured_hz - r);
if (err < best_err) {
best = r;
best_err = err;
}
}
return best;
}
// Increments the number of times the update function was called,
// then calculates the current refresh rate of the game
// (20 seconds after the game starts, until 25 seconds)
static void count_calls_from_game() {
if (IS_REFRESH_RATE_DETERMINED) {
return;
}
const uint64_t current_time = arkGetTickTime64();
if (!IS_REFRESH_RATE_MEASUREMENT_STARTED) {
if (START_TIME == 0) {
START_TIME = current_time;
}
// boot screen takes about 10 seconds, so let's wait for double that
if ((current_time - START_TIME) < 20000) {
// too early, do nothing
return;
} else {
// 20s has passed for the first time, start measuring on next call
IS_REFRESH_RATE_MEASUREMENT_STARTED = true;
START_TIME = current_time;
log_debug("mdxf", "measurement begin");
return;
}
}
const uint64_t elapsed_time = current_time - START_TIME;
CALL_COUNT++;
if (elapsed_time >= 5000) {
double measured_hz = static_cast<double>(CALL_COUNT) * 1000.0 / static_cast<double>(elapsed_time);
// Account for the main loop calling this twice per iteration
measured_hz *= 0.5;
const int snapped_hz = snap_refresh_rate(static_cast<int>(measured_hz));
IS_REFRESH_RATE_DETERMINED = true;
IS_THREAD_NEEDED = (snapped_hz < THRESHOLD_REFRESH_RATE);
log_info(
"mdxf",
"detected: {} Hz, best fit: {} Hz",
static_cast<int>(measured_hz),
snapped_hz);
if (IS_THREAD_NEEDED) {
mdxf_thread_start();
}
}
}
/*
* Implementations
*/
static uint64_t __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *out, uint8_t index, uint8_t head_in) {
// Default error value (matches original mask behavior)
auto error_ret = static_cast<uint64_t>(node - 0x11) & 0xFFFFFFFFFFFFFF00;
// Dance Dance Revolution
if (avs::game::is_model("MDX")) {
// Select player-specific state
std::mutex* mutex = nullptr;
uint8_t* head = nullptr;
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
size_t size = STATUS_BUFFER_NUM_ENTRIES;
if (node == 17 || node == 25) {
mutex = &MUTEX_P1;
head = &HEAD_P1;
buffer = BUFFERS.STATUS_BUFFER_P1;
} else if (node == 18 || node == 26) {
mutex = &MUTEX_P2;
head = &HEAD_P2;
buffer = BUFFERS.STATUS_BUFFER_P2;
} else {
memset(out, 0, STATUS_BUFFER_SIZE);
return error_ret;
}
std::lock_guard<std::mutex> lock(*mutex);
const uint8_t start_index = (head_in == 0xFF) ? *head : head_in;
// Compute ring index: walk backwards from start_index as index increases
// Assumes ring buffer size is a power of two
const size_t mask = size - 1;
const size_t offset = static_cast<size_t>(index) & mask;
const size_t i = (static_cast<size_t>(start_index) - offset + size) & mask;
// Copy the chosen entry
memcpy(out, buffer[i], STATUS_BUFFER_SIZE);
// Return the start value actually used
return static_cast<uint64_t>(start_index);
}
return error_ret;
}
static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2, uint8_t value) {
if (avs::game::is_model("MDX")) {
static const struct {
int a2[4];
} mapping[] = {
{
// a1 == 17
{
games::ddr::Lights::GOLD_P1_STAGE_UP_RIGHT,
games::ddr::Lights::GOLD_P1_STAGE_DOWN_LEFT,
games::ddr::Lights::GOLD_P1_STAGE_UP_LEFT,
games::ddr::Lights::GOLD_P1_STAGE_DOWN_RIGHT
}
},
{
// a1 == 18
{
games::ddr::Lights::GOLD_P2_STAGE_UP_RIGHT,
games::ddr::Lights::GOLD_P2_STAGE_DOWN_LEFT,
games::ddr::Lights::GOLD_P2_STAGE_UP_LEFT,
games::ddr::Lights::GOLD_P2_STAGE_DOWN_RIGHT
}
}
};
if ((a1 == 17 || a1 == 18) && (a2 < 4)) {
// get light from mapping
const auto light = mapping[a1 - 17].a2[a2];
// get lights
auto &lights = games::ddr::get_lights();
// write lights
GameAPI::Lights::writeLight(RI_MGR, lights[light], value / 128.f);
}
}
return true;
}
static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFPollSource source, uint64_t current_time) {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// Dance Dance Revolution
if (avs::game::is_model("MDX")) {
// Marks this module as actively being used, allowing this function to be called from other sources
if (source == ARKMDXP4_POLL) {
if (!IS_MDXF_ACTIVE) {
log_debug("mdxf", "initializing mdxf I/O support");
IS_MDXF_ACTIVE = true;
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::THREAD_MODE) {
IS_THREAD_NEEDED = true;
mdxf_thread_start();
}
}
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::AUTO_MODE) {
count_calls_from_game();
}
}
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
uint8_t *head = nullptr;
uint16_t *prev_state = nullptr;
uint64_t *prev_time = nullptr;
std::mutex* mutex = nullptr;
switch (node) {
case 17:
case 25:
mutex = &MUTEX_P1;
head = &HEAD_P1;
prev_state = &PREV_STATE_P1;
prev_time = &PREV_TIME_P1;
buffer = BUFFERS.STATUS_BUFFER_P1;
break;
case 18:
case 26:
mutex = &MUTEX_P2;
head = &HEAD_P2;
prev_state = &PREV_STATE_P2;
prev_time = &PREV_TIME_P2;
buffer = BUFFERS.STATUS_BUFFER_P2;
break;
default:
// return failure on unknown node
return false;
}
// Sensor Map (LDUR):
// FOOT DOWN = bit 32-35 = byte 4, bit 0-3
// FOOT UP = bit 36-39 = byte 4, bit 4-7
// FOOT RIGHT = bit 40-43 = byte 5, bit 0-3
// FOOT LEFT = bit 44-47 = byte 5, bit 4-7
static const size_t buttons_p1[] = {
games::ddr::Buttons::P1_PANEL_UP,
games::ddr::Buttons::P1_PANEL_DOWN,
games::ddr::Buttons::P1_PANEL_LEFT,
games::ddr::Buttons::P1_PANEL_RIGHT,
};
static const size_t buttons_p2[] = {
games::ddr::Buttons::P2_PANEL_UP,
games::ddr::Buttons::P2_PANEL_DOWN,
games::ddr::Buttons::P2_PANEL_LEFT,
games::ddr::Buttons::P2_PANEL_RIGHT,
};
// decide on button map
const size_t *button_map = nullptr;
int player = 0;
switch (node) {
case 17:
case 25:
button_map = &buttons_p1[0];
player = 1;
break;
case 18:
case 26:
button_map = &buttons_p2[0];
player = 2;
break;
}
uint16_t current_state;
// Only call getState() if called externally when actual input events happen, otherwise use previous known state
if (source == EXTERNAL_POLL) {
// get buttons
auto &buttons = games::ddr::get_buttons();
// get analogs
bool analog_left = false;
bool analog_right = false;
games::ddr::get_analog_x_axis(player, analog_left, analog_right);
bool analog_up = false;
bool analog_down = false;
games::ddr::get_analog_y_axis(player, analog_up, analog_down);
uint8_t up_down = 0;
uint8_t left_right = 0;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0])) || analog_up) {
up_down |= 0xF0;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1])) || analog_down) {
up_down |= 0x0F;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2])) || analog_left) {
left_right |= 0xF0;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3])) || analog_right) {
left_right |= 0x0F;
}
current_state = (uint16_t(up_down) << 8) | left_right;
} else {
current_state = *prev_state;
}
std::lock_guard<std::mutex> lock(*mutex);
const bool has_state_changed = *prev_state != current_state;
const bool has_time_changed = *prev_time < current_time;
// If state hasn't changed and either the update was triggered externally or the time hasn't changed, then don't advance head pointer or write a new entry
if (!has_state_changed && (source == EXTERNAL_POLL || !has_time_changed)) {
return true;
}
// The start and stop time cutoffs for backfilling entries. Min(..) ensures times aren't negative.
// The stop time is just before the current_time, set by BACKFILL_PADDING_MS, which avoids the last backfilled entry being too close to current_time.
uint64_t start_time = *prev_time;
const uint64_t stop_time = current_time - std::min<uint64_t>(current_time, BACKFILL_PADDING_MS);
// Ensures the first iteration will write the first entry at current_time and not backfill to time 0ms.
if (start_time == 0) {
start_time = current_time - std::min<uint64_t>(current_time, BACKFILL_INTERVAL_MS);
}
// Ensures only STATUS_BUFFER_NUM_ENTRIES entries at most are backfilled
const uint64_t max_backfill = BACKFILL_INTERVAL_MS * STATUS_BUFFER_NUM_ENTRIES;
const uint64_t min_time = current_time - std::min<uint64_t>(current_time, max_backfill);
if (start_time < min_time) {
start_time = min_time;
}
// Don't backfill entries if called externally or if a separate thread is being used to fill auxiliary entries
if (source == EXTERNAL_POLL || IS_THREAD_NEEDED) {
start_time = stop_time - 1;
}
uint64_t time = start_time;
uint16_t state = *prev_state;
// Backfill entries a fixed interval apart from each other between prev_time and current_time
while (time < stop_time) {
// Advance head pointer
*head = (*head + 1) % STATUS_BUFFER_NUM_ENTRIES;
uint8_t* buffer_entry = buffer[*head];
// Clear buffer
memset(buffer_entry, 0, STATUS_BUFFER_SIZE);
time += BACKFILL_INTERVAL_MS;
// If the stop time is reached, then write current_time and current_state instead for this final iteration
const bool isEdge = (time >= stop_time);
if (isEdge) {
state = current_state;
time = current_time;
}
// Write button state
buffer_entry[4] = (state >> 8) & 0xFF;
buffer_entry[5] = state & 0xFF;
// Write game time
*(uint64_t*)&buffer_entry[0x18] = time;
}
*prev_state = current_state;
*prev_time = current_time;
}
// return success
return true;
}
static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
return ac_io_mdxf_update_control_status_buffer_impl(node, ARKMDXP4_POLL, arkGetTickTime64());
}
// Used for triggering updates of the controller states from outside arkmdxp4.dll main refresh loop (i.e. within rawinput.cpp on controller events)
void mdxf_poll(bool isExternal) {
if (IS_MDXF_ACTIVE) {
const MDXFPollSource source = isExternal ? EXTERNAL_POLL : INTERNAL_POLL;
const uint64_t call_time_ms = arkGetTickTime64();
ac_io_mdxf_update_control_status_buffer_impl(17, source, call_time_ms);
ac_io_mdxf_update_control_status_buffer_impl(18, source, call_time_ms);
}
}
/*
* Module stuff
*/
acio::MDXFModule::MDXFModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("MDXF", module, hookMode) {
this->status_buffer = (uint8_t*) &BUFFERS;
this->status_buffer_size = sizeof(BUFFERS);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::MDXFModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_mdxf_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_mdxf_set_output_level);
ACIO_MODULE_HOOK(ac_io_mdxf_update_control_status_buffer);
}
acio::MDXFModule::~MDXFModule() {
if (IS_THREAD_NEEDED) {
mdxf_thread_stop();
}
}
-27
View File
@@ -1,27 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
enum class MDXFBufferFillMode {
// backfill mode, but if <120Hz, enable poll thread
AUTO_MODE,
// forces poll thread
THREAD_MODE,
// forces backfill mode (no poll thread)
BACKFILL_MODE
};
extern MDXFBufferFillMode MDXF_BUFFER_FILL_MODE;
class MDXFModule : public ACIOModule {
public:
MDXFModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
~MDXFModule() override;
};
}
-5
View File
@@ -1,5 +0,0 @@
// mdxf_poll.h
#pragma once
// Called from rawinput thread whenever inputs have just been updated
void mdxf_poll(bool isExternal);
-39
View File
@@ -1,39 +0,0 @@
#include "module.h"
#include "util/logging.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "avs/game.h"
const char *acio::hook_mode_str(acio::HookMode hook_mode) {
switch (hook_mode) {
case HookMode::INLINE:
return "Inline";
case HookMode::IAT:
return "IAT";
default:
return "Unknown";
}
}
/*
* Hook functions depending on the specified mode.
* We don't care about errors here since different versions of libacio contain different feature sets,
* which means that not all hooks must/can succeed.
*/
void acio::ACIOModule::hook(void *func, const char *func_name) {
switch (this->hook_mode) {
case HookMode::INLINE:
detour::inline_hook(func, libutils::try_proc(this->module, func_name));
break;
case HookMode::IAT:
detour::iat_try(func_name, func);
break;
default:
log_warning("acio", "unable to hook using mode {}", hook_mode_str(this->hook_mode));
}
}
void acio::ACIOModule::attach() {
log_info("acio", "module attach: {} {}", this->name, hook_mode_str(this->hook_mode));
this->attached = true;
}
-56
View File
@@ -1,56 +0,0 @@
#pragma once
#include <cstdint>
#include <string>
#include <windows.h>
// macro for lazy typing of hooks
#define ACIO_MODULE_HOOK(f) this->hook(reinterpret_cast<void *>(f), #f)
namespace acio {
/*
* Hook Modes
* Since some versions can't handle inline hooking
*/
enum class HookMode {
INLINE,
IAT
};
// this makes logging easier
const char *hook_mode_str(HookMode hook_mode);
/*
* The ACIO module itself
* Inherit this for extending our libacio implementation
*/
class ACIOModule {
protected:
// the magic
void hook(void* func, const char *func_name);
public:
ACIOModule(std::string name, HMODULE module, HookMode hook_mode) :
name(std::move(name)),
module(module),
hook_mode(hook_mode) {};
virtual ~ACIOModule() = default;
virtual void attach();
// settings
std::string name;
HMODULE module;
HookMode hook_mode;
bool attached = false;
// buffer state (optional)
uint8_t *status_buffer = nullptr;
size_t status_buffer_size = 0;
bool *status_buffer_freeze = nullptr;
};
}
-64
View File
@@ -1,64 +0,0 @@
#include "nddb.h"
#include "avs/game.h"
#include "misc/eamuse.h"
#include "util/utils.h"
// static stuff
static uint8_t STATUS_BUFFER[4] {};
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static void __cdecl ac_io_nddb_control_pwm(int a1, int a2) {
log_misc("acio::nddb", "ac_io_nddb_control_pwm({}, {})", a1, a2);
}
static void __cdecl ac_io_nddb_control_solenoide(int a1, int a2) {
log_misc("acio::nddb", "ac_io_nddb_control_solenoide({}, {})", a1, a2);
}
static bool __cdecl ac_io_nddb_create_get_status_thread() {
return true;
}
static bool __cdecl ac_io_nddb_destroy_get_status_thread() {
return true;
}
static void __cdecl ac_io_nddb_get_control_status_buffer(void *buffer) {
}
static bool __cdecl ac_io_nddb_req_solenoide_control(uint8_t *buffer) {
log_misc("acio::nddb", "ac_io_nddb_req_solenoide_control");
return true;
}
static bool __cdecl ac_io_nddb_update_control_status_buffer() {
return true;
}
/*
* Module stuff
*/
acio::NDDBModule::NDDBModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("NDDB", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::NDDBModule::attach() {
ACIOModule::attach();
ACIO_MODULE_HOOK(ac_io_nddb_control_pwm);
ACIO_MODULE_HOOK(ac_io_nddb_control_solenoide);
ACIO_MODULE_HOOK(ac_io_nddb_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_nddb_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_nddb_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_nddb_req_solenoide_control);
ACIO_MODULE_HOOK(ac_io_nddb_update_control_status_buffer);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class NDDBModule : public ACIOModule {
public:
NDDBModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-304
View File
@@ -1,304 +0,0 @@
#include "panb.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "games/nost/io.h"
#include "games/nost/nost.h"
#include "util/logging.h"
#include "avs/game.h"
// std::min
#ifdef min
#undef min
#endif
using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[277];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static long __cdecl ac_io_panb_control_led_bright(size_t index, uint8_t value) {
// nostalgia
if (avs::game::is_model("PAN")) {
// get lights
auto &lights = games::nost::get_lights();
// mapping
static const size_t mapping[] {
games::nost::Lights::Key1R, games::nost::Lights::Key1G, games::nost::Lights::Key1B,
games::nost::Lights::Key2R, games::nost::Lights::Key2G, games::nost::Lights::Key2B,
games::nost::Lights::Key3R, games::nost::Lights::Key3G, games::nost::Lights::Key3B,
games::nost::Lights::Key4R, games::nost::Lights::Key4G, games::nost::Lights::Key4B,
games::nost::Lights::Key5R, games::nost::Lights::Key5G, games::nost::Lights::Key5B,
games::nost::Lights::Key6R, games::nost::Lights::Key6G, games::nost::Lights::Key6B,
games::nost::Lights::Key7R, games::nost::Lights::Key7G, games::nost::Lights::Key7B,
games::nost::Lights::Key8R, games::nost::Lights::Key8G, games::nost::Lights::Key8B,
games::nost::Lights::Key9R, games::nost::Lights::Key9G, games::nost::Lights::Key9B,
games::nost::Lights::Key10R, games::nost::Lights::Key10G, games::nost::Lights::Key10B,
games::nost::Lights::Key11R, games::nost::Lights::Key11G, games::nost::Lights::Key11B,
games::nost::Lights::Key12R, games::nost::Lights::Key12G, games::nost::Lights::Key12B,
games::nost::Lights::Key13R, games::nost::Lights::Key13G, games::nost::Lights::Key13B,
games::nost::Lights::Key14R, games::nost::Lights::Key14G, games::nost::Lights::Key14B,
games::nost::Lights::Key15R, games::nost::Lights::Key15G, games::nost::Lights::Key15B,
games::nost::Lights::Key16R, games::nost::Lights::Key16G, games::nost::Lights::Key16B,
games::nost::Lights::Key17R, games::nost::Lights::Key17G, games::nost::Lights::Key17B,
games::nost::Lights::Key18R, games::nost::Lights::Key18G, games::nost::Lights::Key18B,
games::nost::Lights::Key19R, games::nost::Lights::Key19G, games::nost::Lights::Key19B,
games::nost::Lights::Key20R, games::nost::Lights::Key20G, games::nost::Lights::Key20B,
games::nost::Lights::Key21R, games::nost::Lights::Key21G, games::nost::Lights::Key21B,
games::nost::Lights::Key22R, games::nost::Lights::Key22G, games::nost::Lights::Key22B,
games::nost::Lights::Key23R, games::nost::Lights::Key23G, games::nost::Lights::Key23B,
games::nost::Lights::Key24R, games::nost::Lights::Key24G, games::nost::Lights::Key24B,
games::nost::Lights::Key25R, games::nost::Lights::Key25G, games::nost::Lights::Key25B,
games::nost::Lights::Key26R, games::nost::Lights::Key26G, games::nost::Lights::Key26B,
games::nost::Lights::Key27R, games::nost::Lights::Key27G, games::nost::Lights::Key27B,
games::nost::Lights::Key28R, games::nost::Lights::Key28G, games::nost::Lights::Key28B,
};
// write light
if (index < std::size(mapping)) {
Lights::writeLight(RI_MGR, lights.at(mapping[index]), value / 127.f);
}
}
return 1;
}
static long __cdecl ac_io_panb_control_reset() {
return 0;
}
static void* __cdecl ac_io_panb_get_control_status_buffer(uint8_t* buffer) {
// copy buffer
return memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static bool __cdecl ac_io_panb_start_auto_input() {
return true;
}
static uint8_t panb_get_button_velocity(Button& button, Button& button_soft, Button& button_medium, Button& button_hard) {
const auto velocity = Buttons::getVelocity(RI_MGR, button);
const auto velocity_soft = Buttons::getVelocity(RI_MGR, button_soft);
const auto velocity_medium = Buttons::getVelocity(RI_MGR, button_medium);
const auto velocity_hard = Buttons::getVelocity(RI_MGR, button_hard);
// note that the digital values have been obtained via trial-and-error in recital mode
// based on Op3:
// * soft presses glow blue, should trigger Elegant in blue sections
// * hard presses glow red/orange, should trigger Elegant in yellow sections
// * default (medium) presses glow green, triggers Elegant in both blue and yellow sections
// digital-only values
if (velocity_hard > 0.f) {
// do NOT use 15 here!! 14 properly registers as a hard press, but 15 does not
return 14;
} else if (velocity_medium > 0.f) {
return 11;
} else if (velocity_soft > 0.f) {
return 1;
}
// digital or midi (velocity-sensitive) values
return std::min((uint8_t)(velocity * 15.999f), (uint8_t)14);
}
static bool __cdecl ac_io_panb_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, 277);
/*
* first byte is number of input data
* when it's set to 0 the game will not update it's key states
* setting it too high will make the game read over the buffer
*
* unsure why you would send more than one set of data, so
* we just set it to 1 and provide our current status
*/
STATUS_BUFFER[0] = 1;
// Nostalgia
if (avs::game::is_model("PAN")) {
// get buttons/analogs
auto &buttons = games::nost::get_buttons();
auto &analogs = games::nost::get_analogs();
// mappings
// "normal" buttons - these are velocity sensitive (digital or MIDI)
static const size_t button_mapping[] = {
games::nost::Buttons::Key1, games::nost::Buttons::Key2,
games::nost::Buttons::Key3, games::nost::Buttons::Key4,
games::nost::Buttons::Key5, games::nost::Buttons::Key6,
games::nost::Buttons::Key7, games::nost::Buttons::Key8,
games::nost::Buttons::Key9, games::nost::Buttons::Key10,
games::nost::Buttons::Key11, games::nost::Buttons::Key12,
games::nost::Buttons::Key13, games::nost::Buttons::Key14,
games::nost::Buttons::Key15, games::nost::Buttons::Key16,
games::nost::Buttons::Key17, games::nost::Buttons::Key18,
games::nost::Buttons::Key19, games::nost::Buttons::Key20,
games::nost::Buttons::Key21, games::nost::Buttons::Key22,
games::nost::Buttons::Key23, games::nost::Buttons::Key24,
games::nost::Buttons::Key25, games::nost::Buttons::Key26,
games::nost::Buttons::Key27, games::nost::Buttons::Key28,
};
// soft (digital) button - always registers as soft press
static const size_t soft_button_mapping[] = {
games::nost::Buttons::Key1Soft, games::nost::Buttons::Key2Soft,
games::nost::Buttons::Key3Soft, games::nost::Buttons::Key4Soft,
games::nost::Buttons::Key5Soft, games::nost::Buttons::Key6Soft,
games::nost::Buttons::Key7Soft, games::nost::Buttons::Key8Soft,
games::nost::Buttons::Key9Soft, games::nost::Buttons::Key10Soft,
games::nost::Buttons::Key11Soft, games::nost::Buttons::Key12Soft,
games::nost::Buttons::Key13Soft, games::nost::Buttons::Key14Soft,
games::nost::Buttons::Key15Soft, games::nost::Buttons::Key16Soft,
games::nost::Buttons::Key17Soft, games::nost::Buttons::Key18Soft,
games::nost::Buttons::Key19Soft, games::nost::Buttons::Key20Soft,
games::nost::Buttons::Key21Soft, games::nost::Buttons::Key22Soft,
games::nost::Buttons::Key23Soft, games::nost::Buttons::Key24Soft,
games::nost::Buttons::Key25Soft, games::nost::Buttons::Key26Soft,
games::nost::Buttons::Key27Soft, games::nost::Buttons::Key28Soft,
};
// medium (digital) button - always registers as medium press
static const size_t medium_button_mapping[] = {
games::nost::Buttons::Key1Medium, games::nost::Buttons::Key2Medium,
games::nost::Buttons::Key3Medium, games::nost::Buttons::Key4Medium,
games::nost::Buttons::Key5Medium, games::nost::Buttons::Key6Medium,
games::nost::Buttons::Key7Medium, games::nost::Buttons::Key8Medium,
games::nost::Buttons::Key9Medium, games::nost::Buttons::Key10Medium,
games::nost::Buttons::Key11Medium, games::nost::Buttons::Key12Medium,
games::nost::Buttons::Key13Medium, games::nost::Buttons::Key14Medium,
games::nost::Buttons::Key15Medium, games::nost::Buttons::Key16Medium,
games::nost::Buttons::Key17Medium, games::nost::Buttons::Key18Medium,
games::nost::Buttons::Key19Medium, games::nost::Buttons::Key20Medium,
games::nost::Buttons::Key21Medium, games::nost::Buttons::Key22Medium,
games::nost::Buttons::Key23Medium, games::nost::Buttons::Key24Medium,
games::nost::Buttons::Key25Medium, games::nost::Buttons::Key26Medium,
games::nost::Buttons::Key27Medium, games::nost::Buttons::Key28Medium,
};
// hard (digital) button - always registers as hard press
static const size_t hard_button_mapping[] = {
games::nost::Buttons::Key1Hard, games::nost::Buttons::Key2Hard,
games::nost::Buttons::Key3Hard, games::nost::Buttons::Key4Hard,
games::nost::Buttons::Key5Hard, games::nost::Buttons::Key6Hard,
games::nost::Buttons::Key7Hard, games::nost::Buttons::Key8Hard,
games::nost::Buttons::Key9Hard, games::nost::Buttons::Key10Hard,
games::nost::Buttons::Key11Hard, games::nost::Buttons::Key12Hard,
games::nost::Buttons::Key13Hard, games::nost::Buttons::Key14Hard,
games::nost::Buttons::Key15Hard, games::nost::Buttons::Key16Hard,
games::nost::Buttons::Key17Hard, games::nost::Buttons::Key18Hard,
games::nost::Buttons::Key19Hard, games::nost::Buttons::Key20Hard,
games::nost::Buttons::Key21Hard, games::nost::Buttons::Key22Hard,
games::nost::Buttons::Key23Hard, games::nost::Buttons::Key24Hard,
games::nost::Buttons::Key25Hard, games::nost::Buttons::Key26Hard,
games::nost::Buttons::Key27Hard, games::nost::Buttons::Key28Hard,
};
static const size_t analog_mapping[] = {
games::nost::Analogs::Key1, games::nost::Analogs::Key2,
games::nost::Analogs::Key3, games::nost::Analogs::Key4,
games::nost::Analogs::Key5, games::nost::Analogs::Key6,
games::nost::Analogs::Key7, games::nost::Analogs::Key8,
games::nost::Analogs::Key9, games::nost::Analogs::Key10,
games::nost::Analogs::Key11, games::nost::Analogs::Key12,
games::nost::Analogs::Key13, games::nost::Analogs::Key14,
games::nost::Analogs::Key15, games::nost::Analogs::Key16,
games::nost::Analogs::Key17, games::nost::Analogs::Key18,
games::nost::Analogs::Key19, games::nost::Analogs::Key20,
games::nost::Analogs::Key21, games::nost::Analogs::Key22,
games::nost::Analogs::Key23, games::nost::Analogs::Key24,
games::nost::Analogs::Key25, games::nost::Analogs::Key26,
games::nost::Analogs::Key27, games::nost::Analogs::Key28,
};
// iterate pairs of keys
for (size_t key_pair = 0; key_pair < 28 / 2; key_pair++) {
// default states
uint8_t state0 = 0;
uint8_t state1 = 0;
// check analogs
//
// while 15 is technically allowed by the I/O board & is recognized correctly in test
// menu, when you play recital mode, 15 fails to register Elegant in yellow sections.
// therefore, cap the value at 14. tested with Nostroller.
auto &analog0 = analogs.at(analog_mapping[key_pair * 2 + 0]);
auto &analog1 = analogs.at(analog_mapping[key_pair * 2 + 1]);
if (analog0.isSet()) {
state0 = std::min((uint8_t)(Analogs::getState(RI_MGR, analog0) * 15.999f), (uint8_t)14);
}
if (analog1.isSet()) {
state1 = std::min((uint8_t)(Analogs::getState(RI_MGR, analog1) * 15.999f), (uint8_t)14);
}
// check digital buttons
const auto button0 = panb_get_button_velocity(
buttons.at(button_mapping[key_pair * 2 + 0]),
buttons.at(soft_button_mapping[key_pair * 2 + 0]),
buttons.at(medium_button_mapping[key_pair * 2 + 0]),
buttons.at(hard_button_mapping[key_pair * 2 + 0])
);
if (button0 > 0) {
state0 = button0;
}
const auto button1 = panb_get_button_velocity(
buttons.at(button_mapping[key_pair * 2 + 1]),
buttons.at(soft_button_mapping[key_pair * 2 + 1]),
buttons.at(medium_button_mapping[key_pair * 2 + 1]),
buttons.at(hard_button_mapping[key_pair * 2 + 1])
);
if (button1 > 0) {
state1 = button1;
}
// build value
uint8_t value = 0;
value |= state0 << 4;
value |= state1 & 0xF;
// set value
STATUS_BUFFER[key_pair + 3] = value;
}
}
// return success
return true;
}
/*
* Module stuff
*/
acio::PANBModule::PANBModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("PANB", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::PANBModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_panb_control_led_bright);
ACIO_MODULE_HOOK(ac_io_panb_control_reset);
ACIO_MODULE_HOOK(ac_io_panb_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_panb_start_auto_input);
ACIO_MODULE_HOOK(ac_io_panb_update_control_status_buffer);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class PANBModule : public ACIOModule {
public:
PANBModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-520
View File
@@ -1,520 +0,0 @@
#include "pix.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "games/museca/io.h"
#include "games/bbc/io.h"
#include "util/utils.h"
#include "avs/game.h"
using namespace GameAPI;
// static stuff
static int ACIO_PIX_WARMUP = 0;
static uint8_t STATUS_BUFFER[60];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static char __cdecl ac_io_pix_begin(char a1, long long a2, int a3, int a4, int a5, int a6) {
return 1;
}
static char __cdecl ac_io_pix_begin_get_status(int a1, int a2) {
return 1;
}
static char __cdecl ac_io_pix_end(int a1) {
return 1;
}
static char __cdecl ac_io_pix_end_get_status(int a1) {
return 1;
}
static char __cdecl ac_io_pix_get_firmware_update_device_index(int a1) {
return 1;
}
static char __cdecl ac_io_pix_get_node_no(int a1, int a2) {
return 1;
}
static void *__cdecl ac_io_pix_get_recv_log(long long a1, void *a2, int a3) {
return a2;
}
static void *__cdecl ac_io_pix_get_rs232c_status(void *a1, int a2) {
return a1;
}
static void *__cdecl ac_io_pix_get_send_log(long long a1, void *a2, int a3) {
return a2;
}
static char __cdecl ac_io_pix_get_version(void *a1, int a2, int a3) {
return 1;
}
static const char *__cdecl ac_io_pix_get_version_string() {
static const char *version = "1.25.0";
return version;
}
static char __cdecl ac_io_pix_go_firmware_update(int a1) {
return 1;
}
static char __cdecl ac_io_pix_is_active(int a1, int a2) {
return (char) (++ACIO_PIX_WARMUP > 601 ? 1 : 0);
}
static char __cdecl ac_io_pix_is_active2(int a1, int *a2, int a3) {
ACIO_PIX_WARMUP = 601;
*a2 = 6;
return 1;
}
static char __cdecl ac_io_pix_is_active_device(int a1, int a2) {
return (char) (a1 != 5);
}
static long long __cdecl ac_io_pix_reset(int a1) {
return a1;
}
static bool __cdecl ac_io_pix_rvol_change_expand_mode(char a1) {
return true;
}
static long long __cdecl ac_io_pix_rvol_control_led_bright(uint32_t led_field, uint8_t brightness) {
// MUSECA
if (avs::game::is_model("PIX")) {
// get lights
auto &lights = games::museca::get_lights();
// control mapping
static int mapping[] = {
games::museca::Lights::Spinner1R,
games::museca::Lights::Spinner1G,
games::museca::Lights::Spinner1B,
games::museca::Lights::Spinner2R,
games::museca::Lights::Spinner2G,
games::museca::Lights::Spinner2B,
games::museca::Lights::Spinner3R,
games::museca::Lights::Spinner3G,
games::museca::Lights::Spinner3B,
games::museca::Lights::Spinner4R,
games::museca::Lights::Spinner4G,
games::museca::Lights::Spinner4B,
games::museca::Lights::Spinner5R,
games::museca::Lights::Spinner5G,
games::museca::Lights::Spinner5B,
games::museca::Lights::TitleR,
games::museca::Lights::TitleG,
games::museca::Lights::TitleB
};
// write light
float value = brightness > 127.f ? 1.f : brightness / 127.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// BISHI BASHI CHANNEL
if (avs::game::is_model("R66")) {
// get lights
auto &lights = games::bbc::get_lights();
// control mapping
static int mapping[] = {
games::bbc::Lights::P1_DISC_R,
games::bbc::Lights::P1_DISC_G,
games::bbc::Lights::P1_DISC_B,
games::bbc::Lights::P3_DISC_R,
games::bbc::Lights::P3_DISC_G,
games::bbc::Lights::P3_DISC_B,
games::bbc::Lights::P2_DISC_R,
games::bbc::Lights::P2_DISC_G,
games::bbc::Lights::P2_DISC_B,
games::bbc::Lights::P4_DISC_R,
games::bbc::Lights::P4_DISC_G,
games::bbc::Lights::P4_DISC_B,
games::bbc::Lights::P1_R,
games::bbc::Lights::P1_B,
-1, -1, -1, -1, -1, -1,
games::bbc::Lights::P2_R,
games::bbc::Lights::P2_B,
games::bbc::Lights::P3_R,
games::bbc::Lights::P3_B,
games::bbc::Lights::P4_R,
games::bbc::Lights::P4_B,
};
// write light
float value = brightness / 255.f;
for (size_t i = 0; i < std::size(mapping); i++) {
if (mapping[i] >= 0 && led_field & (1 << i)) {
Lights::writeLight(RI_MGR, lights.at((size_t) mapping[i]), value);
}
}
}
// return success
return 1;
}
static long long __cdecl ac_io_pix_rvol_control_reset() {
return 1;
}
static bool __cdecl ac_io_pix_rvol_create_get_status_thread() {
return true;
}
static long long __cdecl ac_io_pix_rvol_destroy_get_status_thread() {
return 1;
}
static void *__cdecl ac_io_pix_rvol_get_control_status_buffer(void *a1) {
// copy buffer
return memcpy(a1, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static bool __cdecl ac_io_pix_rvol_get_watchdog_status() {
return true;
}
static short __cdecl ac_io_pix_rvol_get_watchdog_time_min() {
return 0;
}
static short __cdecl ac_io_pix_rvol_get_watchdog_time_now() {
return 0;
}
static bool __cdecl ac_io_pix_rvol_modify_auto_input_get(long long a1, long long a2) {
return true;
}
static char __cdecl ac_io_pix_rvol_req_get_control_status(DWORD *a1) {
*a1 = 1;
return 1;
}
static bool __cdecl ac_io_pix_rvol_req_volume_control(char a1, char a2, char a3, char a4) {
return true;
}
static bool __cdecl ac_io_pix_rvol_req_volume_control_isfinished(DWORD *a1) {
*a1 = 5;
return true;
}
static long long __cdecl ac_io_pix_rvol_set_framing_err_packet_send_interval(long long a1) {
return a1;
}
static bool __cdecl ac_io_pix_rvol_set_watchdog_time(short a1) {
return true;
}
static bool __cdecl ac_io_pix_rvol_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// MUSECA
if (avs::game::is_model("PIX")) {
// get input
auto &buttons = games::museca::get_buttons();
// get slowdown status
bool slowdown = Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::AnalogSlowdown));
// update disk buttons
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk1Press)))
ARRAY_SETB(STATUS_BUFFER, 107);
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk2Press)))
ARRAY_SETB(STATUS_BUFFER, 104);
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk3Press)))
ARRAY_SETB(STATUS_BUFFER, 123);
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk4Press)))
ARRAY_SETB(STATUS_BUFFER, 42);
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk5Press)))
ARRAY_SETB(STATUS_BUFFER, 44);
// foot pedal (inverted)
if (!Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::FootPedal)))
ARRAY_SETB(STATUS_BUFFER, 43);
// update analogs
static uint8_t analogs[5] = { 0, 0, 0, 0, 0 };
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk1Minus))) {
analogs[0] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk1Plus))) {
analogs[0] += slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk2Minus))) {
analogs[1] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk2Plus))) {
analogs[1] += slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk3Minus))) {
analogs[2] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk3Plus))) {
analogs[2] += slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk4Minus))) {
analogs[3] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk4Plus))) {
analogs[3] += slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk5Minus))) {
analogs[4] -= slowdown ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::museca::Buttons::Disk5Plus))) {
analogs[4] += slowdown ? 3 : 12;
}
// raw input analogs
auto &analog_list = games::museca::get_analogs();
size_t analog_mapping[] = {
games::museca::Analogs::Disk1,
games::museca::Analogs::Disk2,
games::museca::Analogs::Disk3,
games::museca::Analogs::Disk4,
games::museca::Analogs::Disk5,
};
uint8_t set_values[5];
std::copy(std::begin(analogs), std::end(analogs), std::begin(set_values));
for (size_t i = 0; i < 5; i++) {
auto &analog_item = analog_list.at(analog_mapping[i]);
if (analog_item.isSet()) {
set_values[i] = analogs[i] + (uint8_t) (Analogs::getState(RI_MGR, analog_item) * 255.99f);
}
}
// set analogs
for (int i = 0; i < 5; i++)
STATUS_BUFFER[20 + i] = set_values[i];
}
// BISHI BASHI CHANNEL
if (avs::game::is_model("R66")) {
// get input
auto &buttons = games::bbc::get_buttons();
auto &analogs = games::bbc::get_analogs();
// get slowdown status
bool slowdown1 = Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_DiskSlowdown));
bool slowdown2 = Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_DiskSlowdown));
bool slowdown3 = Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_DiskSlowdown));
bool slowdown4 = Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_DiskSlowdown));
// update buttons
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_R)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 44);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_G)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 107);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_B)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 41);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_R)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 39);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_G)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 123);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_B)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 55);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_R)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 71);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_G)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 104);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_B)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 87);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_R)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 103);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_G)) == Buttons::State::BUTTON_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 42);
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_B)) == Buttons::State::BUTTON_NOT_PRESSED) {
ARRAY_SETB(STATUS_BUFFER, 119);
}
// update analogs
static uint8_t analog_states[4] = { 0, 0, 0, 0 };
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_DiskMinus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[0] -= slowdown1 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P1_DiskPlus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[0] += slowdown1 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_DiskMinus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[1] -= slowdown2 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P2_DiskPlus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[1] += slowdown2 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_DiskMinus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[2] -= slowdown3 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P3_DiskPlus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[2] += slowdown3 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_DiskMinus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[3] -= slowdown4 ? 3 : 12;
}
if (Buttons::getState(RI_MGR, buttons.at(games::bbc::Buttons::P4_DiskPlus)) == Buttons::State::BUTTON_PRESSED) {
analog_states[3] += slowdown4 ? 3 : 12;
}
// raw input analogs
uint8_t set_values[4];
size_t analog_mappings[] = {
games::bbc::Analogs::P1_Disk,
games::bbc::Analogs::P2_Disk,
games::bbc::Analogs::P3_Disk,
games::bbc::Analogs::P4_Disk,
};
std::copy(std::begin(analog_states), std::end(analog_states), std::begin(set_values));
for (size_t i = 0; i < 4; i++) {
auto &analog_item = analogs.at(analog_mappings[i]);
if (analog_item.isSet()) {
set_values[i] = analog_states[i] + (uint8_t) (Analogs::getState(RI_MGR, analog_item) * 255.99f);
}
}
// flip disk 2/3
set_values[1] ^= set_values[2];
set_values[2] ^= set_values[1];
set_values[1] ^= set_values[2];
// set analogs
for (int i = 0; i < 4; i++) {
STATUS_BUFFER[20 + i] = set_values[i];
}
}
// success
return true;
}
static void __cdecl ac_io_pix_rvol_watchdog_off() {
}
static void *__cdecl ac_io_pix_secplug_set_encodedpasswd(void *a1, unsigned int a2) {
return a1;
}
static void *__cdecl ac_io_pix_set_get_status_device(void *a1, int a2) {
return a1;
}
static void *__cdecl ac_io_pix_set_soft_watch_dog(void *a1, int a2) {
return a1;
}
static char __cdecl ac_io_pix_soft_watch_dog_off(int a1) {
return 1;
}
static char __cdecl ac_io_pix_soft_watch_dog_on(int a1) {
return 1;
}
static char __cdecl ac_io_pix_update(long long a1) {
// flush outputs
RI_MGR->devices_flush_output();
return 1;
}
static const char* __cdecl ac_io_pix_version() {
static const char *version = "Version: 1.25.0\nBuild Date: Sep 20 2016 15:16:13\nBuild Host: DEMETER\n";
return version;
}
/*
* Module stuff
*/
acio::PIXModule::PIXModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("PIX", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::PIXModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_pix_begin);
ACIO_MODULE_HOOK(ac_io_pix_begin_get_status);
ACIO_MODULE_HOOK(ac_io_pix_end);
ACIO_MODULE_HOOK(ac_io_pix_end_get_status);
ACIO_MODULE_HOOK(ac_io_pix_get_firmware_update_device_index);
ACIO_MODULE_HOOK(ac_io_pix_get_node_no);
ACIO_MODULE_HOOK(ac_io_pix_get_recv_log);
ACIO_MODULE_HOOK(ac_io_pix_get_rs232c_status);
ACIO_MODULE_HOOK(ac_io_pix_get_send_log);
ACIO_MODULE_HOOK(ac_io_pix_get_version);
ACIO_MODULE_HOOK(ac_io_pix_get_version_string);
ACIO_MODULE_HOOK(ac_io_pix_go_firmware_update);
ACIO_MODULE_HOOK(ac_io_pix_is_active);
ACIO_MODULE_HOOK(ac_io_pix_is_active2);
ACIO_MODULE_HOOK(ac_io_pix_is_active_device);
ACIO_MODULE_HOOK(ac_io_pix_reset);
ACIO_MODULE_HOOK(ac_io_pix_rvol_change_expand_mode);
ACIO_MODULE_HOOK(ac_io_pix_rvol_control_led_bright);
ACIO_MODULE_HOOK(ac_io_pix_rvol_control_reset);
ACIO_MODULE_HOOK(ac_io_pix_rvol_create_get_status_thread);
ACIO_MODULE_HOOK(ac_io_pix_rvol_destroy_get_status_thread);
ACIO_MODULE_HOOK(ac_io_pix_rvol_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pix_rvol_get_watchdog_status);
ACIO_MODULE_HOOK(ac_io_pix_rvol_get_watchdog_time_min);
ACIO_MODULE_HOOK(ac_io_pix_rvol_get_watchdog_time_now);
ACIO_MODULE_HOOK(ac_io_pix_rvol_modify_auto_input_get);
ACIO_MODULE_HOOK(ac_io_pix_rvol_req_get_control_status);
ACIO_MODULE_HOOK(ac_io_pix_rvol_req_volume_control);
ACIO_MODULE_HOOK(ac_io_pix_rvol_req_volume_control_isfinished);
ACIO_MODULE_HOOK(ac_io_pix_rvol_set_framing_err_packet_send_interval);
ACIO_MODULE_HOOK(ac_io_pix_rvol_set_watchdog_time);
ACIO_MODULE_HOOK(ac_io_pix_rvol_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pix_rvol_watchdog_off);
ACIO_MODULE_HOOK(ac_io_pix_secplug_set_encodedpasswd);
ACIO_MODULE_HOOK(ac_io_pix_set_get_status_device);
ACIO_MODULE_HOOK(ac_io_pix_set_soft_watch_dog);
ACIO_MODULE_HOOK(ac_io_pix_soft_watch_dog_off);
ACIO_MODULE_HOOK(ac_io_pix_soft_watch_dog_on);
ACIO_MODULE_HOOK(ac_io_pix_update);
ACIO_MODULE_HOOK(ac_io_pix_version);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class PIXModule : public ACIOModule {
public:
PIXModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-156
View File
@@ -1,156 +0,0 @@
#include "pjec.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "avs/game.h"
#include "games/we/io.h"
//using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[72];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static bool __cdecl ac_io_pjec_get_ps2() {
return true;
}
static void __cdecl ac_io_pjec_get_control_status_buffer(uint8_t *buffer) {
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
}
static bool __cdecl ac_io_pjec_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// Winning Eleven
if (avs::game::is_model({ "KCK", "NCK" })) {
auto &buttons = games::we::get_buttons();
auto &analogs = games::we::get_analogs();
/*
* Device Types
* 0x00 - Unknown Device
* 0x01 - Mouse
* 0x02 - Rotate Controller
* 0x03 - Gun Controller K
* 0x04 - Digital Controller <- Accepted
* 0x05 - Analog Joystick
* 0x06 - Gun Controller N
* 0x07 - Analog Controller <- Accepted
* 0x08 - USB Analog Controller
*/
// set device type
STATUS_BUFFER[0] = 0x07;
// set device present
STATUS_BUFFER[2] = 0x5A;
// reset analogs to center
STATUS_BUFFER[8] = 0x7F;
STATUS_BUFFER[9] = 0x7F;
STATUS_BUFFER[10] = 0x7F;
STATUS_BUFFER[11] = 0x7F;
// apply analogs
if (analogs[games::we::Analogs::PadStickLeftX].isSet()) {
STATUS_BUFFER[8] = (uint8_t) (GameAPI::Analogs::getState(RI_MGR,
analogs[games::we::Analogs::PadStickLeftX]) * 255.9999f);
}
if (analogs[games::we::Analogs::PadStickLeftY].isSet()) {
STATUS_BUFFER[9] = (uint8_t) (GameAPI::Analogs::getState(RI_MGR,
analogs[games::we::Analogs::PadStickLeftY]) * 255.9999f);
}
if (analogs[games::we::Analogs::PadStickRightX].isSet()) {
STATUS_BUFFER[10] = (uint8_t) (GameAPI::Analogs::getState(RI_MGR,
analogs[games::we::Analogs::PadStickRightX]) * 255.9999f);
}
if (analogs[games::we::Analogs::PadStickRightY].isSet()) {
STATUS_BUFFER[11] = (uint8_t) (GameAPI::Analogs::getState(RI_MGR,
analogs[games::we::Analogs::PadStickRightY]) * 255.9999f);
}
// apply buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadStart])) {
STATUS_BUFFER[4] |= 0x08;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadSelect])) {
STATUS_BUFFER[4] |= 0x01;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadUp])) {
STATUS_BUFFER[4] |= 0x10;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadDown])) {
STATUS_BUFFER[4] |= 0x40;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadLeft])) {
STATUS_BUFFER[4] |= 0x80;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadRight])) {
STATUS_BUFFER[4] |= 0x20;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadTriangle])) {
STATUS_BUFFER[5] |= 0x10;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadCross])) {
STATUS_BUFFER[5] |= 0x40;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadSquare])) {
STATUS_BUFFER[5] |= 0x80;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadCircle])) {
STATUS_BUFFER[5] |= 0x20;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadL1])) {
STATUS_BUFFER[5] |= 0x04;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadL2])) {
STATUS_BUFFER[5] |= 0x01;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadL3])) {
STATUS_BUFFER[4] |= 0x02;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadR1])) {
STATUS_BUFFER[5] |= 0x08;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadR2])) {
STATUS_BUFFER[5] |= 0x02;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::PadR3])) {
STATUS_BUFFER[4] |= 0x04;
}
}
// success
return true;
}
/*
* Module stuff
*/
acio::PJECModule::PJECModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("PJEC", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::PJECModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_pjec_get_ps2);
ACIO_MODULE_HOOK(ac_io_pjec_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pjec_update_control_status_buffer);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class PJECModule : public ACIOModule {
public:
PJECModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-223
View File
@@ -1,223 +0,0 @@
#include "pjei.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
#include "misc/eamuse.h"
#include "games/we/io.h"
#include "avs/game.h"
//using namespace GameAPI;
// static stuff
static uint8_t STATUS_BUFFER[40];
static bool STATUS_BUFFER_FREEZE = false;
/*
* Implementations
*/
static bool __cdecl ac_io_pjei_current_coinstock(int a1, uint32_t *coinstock) {
*coinstock = eamuse_coin_get_stock();
return true;
}
static bool __cdecl ac_io_pjei_consume_coinstock(int a1, uint32_t amount) {
return eamuse_coin_consume(amount);
}
static bool __cdecl ac_io_pjei_get_softwareid(char *dst) {
static char DATA[] = "0140FFFFFFFFFFFFFFFF";
memcpy(dst, DATA, sizeof(DATA));
return true;
}
static bool __cdecl ac_io_pjei_get_systemid(char *dst) {
static char DATA[] = "0140FFFFFFFFFFFFFFFF";
memcpy(dst, DATA, sizeof(DATA));
return true;
}
static bool __cdecl ac_io_pjei_update_control_status_buffer() {
// check freeze
if (STATUS_BUFFER_FREEZE) {
return true;
}
// clear buffer
memset(STATUS_BUFFER, 0, sizeof(STATUS_BUFFER));
// Winning Eleven
if (avs::game::is_model({ "KCK", "NCK" })) {
// get buttons
auto &buttons = games::we::get_buttons();
// apply buttons
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Service])) {
STATUS_BUFFER[16] |= 0x10;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Test])) {
STATUS_BUFFER[16] |= 0x20;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::CoinMech])) {
STATUS_BUFFER[16] |= 0x04;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Start])) {
STATUS_BUFFER[4] |= 0x80;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Up])) {
STATUS_BUFFER[4] |= 0x40;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Down])) {
STATUS_BUFFER[4] |= 0x20;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Left])) {
STATUS_BUFFER[4] |= 0x10;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::Right])) {
STATUS_BUFFER[4] |= 0x08;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonA])) {
STATUS_BUFFER[4] |= 0x04;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonB])) {
STATUS_BUFFER[4] |= 0x02;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonC])) {
STATUS_BUFFER[4] |= 0x01;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonD])) {
STATUS_BUFFER[6] |= 0x80;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonE])) {
STATUS_BUFFER[6] |= 0x40;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[games::we::Buttons::ButtonF])) {
STATUS_BUFFER[6] |= 0x20;
}
}
// success
return true;
}
static bool ac_io_pjei_get_control_status_buffer(uint8_t *buffer) {
memcpy(buffer, STATUS_BUFFER, sizeof(STATUS_BUFFER));
return true;
}
static bool __cdecl ac_io_pjei_req_secplug_check() {
return true;
}
static bool __cdecl ac_io_pjei_req_secplug_check_isfinished() {
return true;
}
static bool __cdecl ac_io_pjei_req_secplug_missing_check() {
return true;
}
static bool __cdecl ac_io_pjei_req_secplug_missing_check_isfinished() {
return true;
}
static bool __cdecl ac_io_pjei_lock_coincounter(int a1) {
eamuse_coin_set_block(true);
return true;
}
static bool __cdecl ac_io_pjei_unlock_coincounter(int a1) {
eamuse_coin_set_block(false);
return true;
}
static bool __cdecl ac_io_pjei_control_coin_blocker_on(bool a1) {
eamuse_coin_set_block(true);
return true;
}
static bool __cdecl ac_io_pjei_control_coin_blocker_off(bool a1) {
eamuse_coin_set_block(false);
return true;
}
/*
* Helper method for easily setting the light values
*/
static void ac_io_pjei_control_lamp_set(uint8_t lamp_bits, uint8_t brightness) {
auto &lights = games::we::get_lights();
float value = CLAMP(brightness / 31.f, 0.f, 1.f);
if (lamp_bits & 0x20) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::LeftRed], value);
}
if (lamp_bits & 0x10) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::LeftGreen], value);
}
if (lamp_bits & 0x08) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::LeftBlue], value);
}
if (lamp_bits & 0x04) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::RightRed], value);
}
if (lamp_bits & 0x02) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::RightGreen], value);
}
if (lamp_bits & 0x01) {
GameAPI::Lights::writeLight(RI_MGR, lights[games::we::Lights::RightBlue], value);
}
}
static bool __cdecl ac_io_pjei_control_lamp_on(uint8_t lamp_bits) {
ac_io_pjei_control_lamp_set(lamp_bits, 31);
return true;
}
static bool __cdecl ac_io_pjei_control_lamp_off(uint8_t lamp_bits) {
ac_io_pjei_control_lamp_set(lamp_bits, 0);
return true;
}
static bool __cdecl ac_io_pjei_control_lamp_bright(uint8_t lamp_bit, uint8_t brightness) {
ac_io_pjei_control_lamp_set(lamp_bit, brightness);
return true;
}
static bool __cdecl ac_io_pjei_control_lamp_mode(int mode) {
// mode -> [0,1] (0 is static, 1 is brightness?)
return true;
}
/*
* Module stuff
*/
acio::PJEIModule::PJEIModule(HMODULE module, acio::HookMode hookMode) : ACIOModule("PJEI", module, hookMode) {
this->status_buffer = STATUS_BUFFER;
this->status_buffer_size = sizeof(STATUS_BUFFER);
this->status_buffer_freeze = &STATUS_BUFFER_FREEZE;
}
void acio::PJEIModule::attach() {
ACIOModule::attach();
// hooks
ACIO_MODULE_HOOK(ac_io_pjei_current_coinstock);
ACIO_MODULE_HOOK(ac_io_pjei_consume_coinstock);
ACIO_MODULE_HOOK(ac_io_pjei_get_softwareid);
ACIO_MODULE_HOOK(ac_io_pjei_get_systemid);
ACIO_MODULE_HOOK(ac_io_pjei_update_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pjei_get_control_status_buffer);
ACIO_MODULE_HOOK(ac_io_pjei_req_secplug_check);
ACIO_MODULE_HOOK(ac_io_pjei_req_secplug_check_isfinished);
ACIO_MODULE_HOOK(ac_io_pjei_req_secplug_missing_check);
ACIO_MODULE_HOOK(ac_io_pjei_req_secplug_missing_check_isfinished);
ACIO_MODULE_HOOK(ac_io_pjei_lock_coincounter);
ACIO_MODULE_HOOK(ac_io_pjei_unlock_coincounter);
ACIO_MODULE_HOOK(ac_io_pjei_control_coin_blocker_on);
ACIO_MODULE_HOOK(ac_io_pjei_control_coin_blocker_off);
ACIO_MODULE_HOOK(ac_io_pjei_control_lamp_on);
ACIO_MODULE_HOOK(ac_io_pjei_control_lamp_off);
ACIO_MODULE_HOOK(ac_io_pjei_control_lamp_bright);
ACIO_MODULE_HOOK(ac_io_pjei_control_lamp_mode);
}
-13
View File
@@ -1,13 +0,0 @@
#pragma once
#include "../module.h"
namespace acio {
class PJEIModule : public ACIOModule {
public:
PJEIModule(HMODULE module, HookMode hookMode);
virtual void attach() override;
};
}
-67
View File
@@ -1,67 +0,0 @@
#include "bi2x.h"
namespace acio2emu::firmware {
bool BI2XNode::handle_packet(const acio2emu::Packet &in, std::vector<uint8_t> &out) {
auto cur = in.payload.begin();
while ((cur + 1) < in.payload.end()) {
auto cmd = (cur[0] << 8) | cur[1];
out.push_back(*cur++);
out.push_back(*cur++);
out.push_back(0);
switch (cmd) {
case 2: // query firmware version
read_firmware_version(out);
cur = in.payload.end();
break;
case 16:
out.push_back(2);
cur = in.payload.end();
break;
case 800:
case 802:
case 19:
cur = in.payload.end();
break;
case 120:
out.push_back(3);
cur = in.payload.end();
break;
case 801:
out.push_back(33);
out.push_back(0);
cur = in.payload.end();
break;
case 784: // poll input
if (!read_input(out)) {
return false;
}
break;
case 785: { // write output
auto count = write_output(std::span{&*cur, static_cast<size_t>(in.payload.end() - cur)});
if (count < 0) {
return false;
}
cur += count;
break;
}
case 786:
cur += 4;
break;
default:
log_warning("bi2x", "unknown command: {}", cmd);
return false;
}
}
return true;
}
}
-22
View File
@@ -1,22 +0,0 @@
#pragma once
#include <vector>
#include <span>
#include <cstdint>
#include "acio2emu/node.h"
#include "util/logging.h"
namespace acio2emu::firmware {
class BI2XNode : public Node {
virtual void read_firmware_version(std::vector<uint8_t> &buffer) = 0;
virtual bool read_input(std::vector<uint8_t> &buffer) = 0;
virtual int write_output(std::span<const uint8_t> buffer) = 0;
/*
* acio2emu::Node
*/
bool handle_packet(const acio2emu::Packet &in, std::vector<uint8_t> &out) override;
};
}
-120
View File
@@ -1,120 +0,0 @@
#include "handle.h"
#include "util/logging.h"
#include "util/utils.h" // ws2s
namespace acio2emu {
class MasterNode : public Node {
private:
const IOBHandle *iob_;
public:
MasterNode(const IOBHandle *iob) : iob_(iob) { }
bool handle_packet(const Packet &in, std::vector<uint8_t> &out) {
// were we sent a command?
if (in.payload.size() >= 2) {
if (in.payload[0] != 0 || in.payload[1] != 1) {
// unknown command
return false;
}
// assign node ids
out.push_back(0);
out.push_back(1);
for (int i = 0; i < iob_->number_of_nodes(); i++) {
out.push_back(i * 16);
}
}
return true;
}
};
IOBHandle::IOBHandle(LPCWSTR device) : device_(device) {
nodes_[0] = std::make_unique<MasterNode>(this);
}
bool IOBHandle::register_node(std::unique_ptr<Node> node) {
if ((number_of_nodes_ - 1) >= 16) {
// too many nodes
return false;
}
nodes_[number_of_nodes_++] = std::move(node);
return true;
}
int IOBHandle::number_of_nodes() const {
// don't include the master node
return number_of_nodes_ - 1;
}
void IOBHandle::forward_packet_(const Packet &packet) {
// clear the output queue
output_ = {};
auto node = packet.node / 2;
if (node >= number_of_nodes_) {
log_warning("acio2emu", "cannot forward packet: node out of range: {} >= {}", node, number_of_nodes_);
return;
}
// forward the packet to the node
std::vector<uint8_t> payload;
if (!nodes_[node]->handle_packet(packet, payload)) {
// error in handler
return;
}
// encode the response
encode_packet(output_, node, packet.tag, payload);
}
/*
* CustomHandle
*/
bool IOBHandle::open(LPCWSTR lpFileName) {
if (device_ != lpFileName) {
return false;
}
log_info("acio2emu", "Opened {} (ACIO2)", ws2s(device_));
return true;
}
bool IOBHandle::close() {
log_info("acio2emu", "Closed {} (ACIO2)", ws2s(device_));
return true;
}
int IOBHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
DWORD i = 0;
while (!output_.empty() && i < nNumberOfBytesToRead) {
buffer[i++] = output_.front();
output_.pop();
}
return i;
}
int IOBHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
if (decoder_.update(buffer[i])) {
// forward the packet to a node
forward_packet_(decoder_.packet());
}
}
return nNumberOfBytesToWrite;
}
int IOBHandle::device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) {
return -1;
}
}
-44
View File
@@ -1,44 +0,0 @@
#pragma once
#include <string>
#include <array>
#include <queue>
#include <memory> // std::unique_ptr
#include <cstdint>
#include "acio2emu/packet.h"
#include "acio2emu/node.h"
#include "hooks/devicehook.h"
namespace acio2emu {
class IOBHandle : public CustomHandle {
private:
std::wstring device_;
std::array<std::unique_ptr<Node>, 17> nodes_;
// the first node is reserved for the "master" node
int number_of_nodes_ = 1;
PacketDecoder decoder_;
std::queue<uint8_t> output_;
void forward_packet_(const Packet &packet);
public:
IOBHandle(LPCWSTR device);
bool register_node(std::unique_ptr<Node> node);
int number_of_nodes() const;
/*
* CustomHandle
*/
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) override;
bool close() override;
};
}
-40
View File
@@ -1,40 +0,0 @@
#pragma once
#include <cstdint>
#include <cstddef>
namespace acio2emu::detail {
inline uint8_t crc4_lgp_c(uint8_t crc, const uint8_t *data, size_t len) {
static constexpr uint8_t tbl[] = {
0x00, 0x0D, 0x03, 0x0E,
0x06, 0x0B, 0x05, 0x08,
0x0C, 0x01, 0x0F, 0x02,
0x0A, 0x07, 0x09, 0x04,
};
crc &= 15;
for (size_t i = 0; i < len; i++) {
auto b = data[i];
crc = (((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) >> 4) ^ tbl[(((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) ^ (b >> 4)) & 0x0F];
}
return crc;
}
inline uint8_t crc7_lgp_48(uint8_t crc, const uint8_t *data, size_t len) {
static constexpr uint8_t tbl[] = {
0x00, 0x09, 0x12, 0x1B,
0x24, 0x2D, 0x36, 0x3F,
0x48, 0x41, 0x5A, 0x53,
0x6C, 0x65, 0x7E, 0x77
};
crc &= 127;
for (size_t i = 0; i < len; i++) {
auto b = data[i];
crc = (((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) >> 4) ^ tbl[(((crc >> 4) ^ (tbl[(b ^ crc) & 0x0F])) ^ (b >> 4)) & 0x0F];
}
return crc;
}
}
-140
View File
@@ -1,140 +0,0 @@
#pragma once
#include <queue>
#include <cstdint>
namespace acio2emu::detail {
class InflateTransformer {
private:
std::queue<uint8_t> output_;
uint8_t flags_ = 0, flag_shift_ = 0;
uint8_t window_[85] = {};
int window_offset_ = 81;
enum class inflateStep {
readFlags,
processFlags,
copyStored,
copyFromWindow,
} step_ = inflateStep::readFlags;
void window_put_(uint8_t b) {
window_[window_offset_++] = b;
window_offset_ %= sizeof(window_);
}
uint8_t window_get_(int offset) {
return window_[offset % sizeof(window_)];
}
public:
void put(uint8_t b) {
auto consumed = false;
while (true) {
switch (step_) {
case inflateStep::readFlags:
if (consumed) {
// need more data
return;
}
consumed = true;
flags_ = b;
flag_shift_ = 0;
step_ = inflateStep::processFlags;
break;
case inflateStep::processFlags:
// have we processed every flag?
if (flag_shift_ > 6) {
step_ = inflateStep::readFlags;
break;
}
if (flags_ & (1 << flag_shift_)) {
flag_shift_++;
if (flags_ & (1 << flag_shift_)) {
// emit 0xAA when both bits are set
output_.push(0xAA);
}
else {
// copy from the window when only the lower bit is set
step_ = inflateStep::copyFromWindow;
}
}
else {
step_ = inflateStep::copyStored;
}
flag_shift_++;
break;
case inflateStep::copyFromWindow: {
if (consumed) {
// need more data
return;
}
consumed = true;
// determine the match size, default is 2-bytes
auto offset = b;
auto size = 2;
if (offset >= 0xAA) {
// 4-byte match
size = 4;
offset -= 0xAB;
}
else if (offset >= 0x55) {
// 3-byte match
size = 3;
offset -= 0x55;
}
for (auto i = 0; i < size; i ++) {
auto cur = window_get_(offset + i);
window_put_(cur);
output_.push(cur);
}
// continue processing flags
step_ = inflateStep::processFlags;
break;
}
case inflateStep::copyStored:
if (consumed) {
// need more data
return;
}
consumed = true;
window_put_(b);
output_.push(b);
// continue processing flags
step_ = inflateStep::processFlags;
break;
}
}
}
int get() {
if (output_.empty()) {
// output queue is empty
return -1;
}
auto b = output_.front();
output_.pop();
return b;
}
};
}
-14
View File
@@ -1,14 +0,0 @@
#pragma once
#include <vector>
#include "acio2emu/packet.h"
namespace acio2emu {
class Node {
public:
virtual ~Node() {}
virtual bool handle_packet(const Packet &in, std::vector<uint8_t> &out) = 0;
};
}
-266
View File
@@ -1,266 +0,0 @@
#include "packet.h"
#include "util/logging.h"
#include "acio2emu/internal/crc.h"
namespace acio2emu {
static constexpr uint8_t SOF = 0xAA;
static constexpr uint8_t ESC = 0xFF;
static void encode_payload_(std::queue<uint8_t> &out, const std::vector<uint8_t> &payload) {
for (auto b : payload) {
if (b == SOF || b == ESC) {
out.push(ESC);
b = ~b;
}
out.push(b);
}
// compute and write the payload's CRC
out.push(detail::crc7_lgp_48(0x7F, payload.data(), payload.size()) ^ 0x7F);
}
bool encode_packet(std::queue<uint8_t> &out, uint8_t node, uint8_t tag, const std::vector<uint8_t> &payload) {
auto size = payload.size();
if (size > 127) {
log_warning("acio2emu", "cannot encode packet: payload too large: {} > 127", payload.size());
return false;
}
// build the header
uint8_t header[5] = {
SOF,
static_cast<uint8_t>(node * 3),
tag,
static_cast<uint8_t>(size),
0,
};
// compute the header's CRC
header[4] = detail::crc4_lgp_c(0x0F, &header[1], sizeof(header) - 1) ^ 0x0F;
// push the header to the output queue
for (size_t i = 0; i < sizeof(header); i++) {
out.push(header[i]);
}
encode_payload_(out, payload);
return true;
}
void PacketDecoder::set_step_(readStep s) {
#ifndef NDEBUG
auto valid = true;
switch (s) {
case readStep::idle:
case readStep::readNode:
// transition from any step/state allowed
break;
case readStep::readTag:
if (step_ != readStep::readNode) {
valid = false;
}
break;
case readStep::readPayloadSize:
if (step_ != readStep::readTag) {
valid = false;
}
break;
case readStep::readPayloadFlags:
if (step_ != readStep::readPayloadSize) {
valid = false;
}
break;
case readStep::readReplacementByte:
if (step_ != readStep::readPayloadFlags) {
valid = false;
}
break;
case readStep::readPayload:
if (step_ != readStep::readPayloadFlags &&
step_ != readStep::readReplacementByte &&
step_ != readStep::readEscaped
) {
valid = false;
}
break;
case readStep::readEscaped:
if (step_ != readStep::readPayload) {
valid = false;
}
break;
default:
log_fatal(
"acio2emu",
"cannot set step: unknown value: {}",
static_cast<uint32_t>(s));
break;
}
if (!valid) {
log_fatal(
"acio2emu",
"illegal transition detected: {} -> {}",
static_cast<uint32_t>(step_),
static_cast<uint32_t>(s));
}
#endif
step_ = s;
}
int PacketDecoder::update_payload_size_(uint8_t b) {
if ((b & 0x80) == 0) {
payload_size_ = (payload_size_ << 7) | (b & 0x7F);
// finished
return 0;
}
else if ((b & 0x40) != 0 && payload_size_count_ < 5) {
payload_size_count_++;
payload_size_count_ = (payload_size_count_ << 6) | (b & 0x3F);
// continuation required
return 1;
}
else {
// invalid value or invalid state
return -1;
}
}
uint8_t PacketDecoder::deobfuscate_(uint8_t b) {
if ((b ^ 0xAA) == 0) {
return b;
}
auto mask = 0x55;
if ((b & 0x80) == 0) {
mask = 0x7F;
}
return (b ^ lcg_()) & mask;
}
void PacketDecoder::reset_(readStep s) {
set_step_(s);
packet_ = {};
payload_size_ = 0;
payload_size_count_ = 0;
}
bool PacketDecoder::update(uint8_t b) {
// is this the start of a packet?
if (b == SOF) {
reset_(readStep::readNode);
return false;
}
switch (step_) {
case readStep::readNode:
packet_.node = b;
set_step_(readStep::readTag);
break;
case readStep::readTag:
packet_.tag = b;
set_step_(readStep::readPayloadSize);
break;
case readStep::readPayloadSize: {
auto status = update_payload_size_(b);
if (status == 0) {
// finished reading payload size
packet_.payload.reserve(payload_size_);
set_step_(readStep::readPayloadFlags);
}
else if (status == -1) {
// reset on error
reset_(readStep::idle);
}
break;
}
case readStep::readPayloadFlags:
obfuscated_ = (b & (1 << 4)) != 0;
encoding_ = static_cast<payloadEncoding>(b >> 5);
if (obfuscated_) {
lcg_.seed(packet_.tag ^ 0x55);
}
if (encoding_ == payloadEncoding::replace) {
set_step_(readStep::readReplacementByte);
}
else {
set_step_(readStep::readPayload);
if (encoding_ == payloadEncoding::lz) {
// reset the InflateTransformer
inflate_ = {};
}
}
break;
case readStep::readReplacementByte:
substitute_ = b;
set_step_(readStep::readPayload);
break;
case readStep::readPayload:
// do we need to deobfuscate?
if (obfuscated_) {
b = deobfuscate_(b);
}
if (encoding_ == payloadEncoding::lz) {
inflate_.put(b);
for (int i = inflate_.get(); i >= 0; i = inflate_.get()) {
packet_.payload.push_back(i);
}
}
else if (encoding_ == payloadEncoding::replace && b == substitute_) {
packet_.payload.push_back(SOF);
}
else if (encoding_ == payloadEncoding::byteStuffing && b == ESC) {
set_step_(readStep::readEscaped);
break;
}
else {
packet_.payload.push_back(b);
}
break;
case readStep::readEscaped:
b = ~b;
if (obfuscated_) {
b = deobfuscate_(b);
}
packet_.payload.push_back(b);
set_step_(readStep::readPayload);
break;
default:
break;
}
if ((step_ == readStep::readPayload || step_ == readStep::readPayloadFlags) &&
(packet_.payload.size() >= payload_size_)) {
set_step_(readStep::idle);
// finished reading packet
return true;
}
return false;
}
const Packet &PacketDecoder::packet() {
return packet_;
}
}
-65
View File
@@ -1,65 +0,0 @@
#pragma once
#include <vector>
#include <queue>
#include <random> // std::linear_congruential_engine
#include <cstdint>
#include "acio2emu/internal/lz.h"
namespace acio2emu {
struct Packet {
uint8_t node;
uint8_t tag;
std::vector<uint8_t> payload;
};
class PacketDecoder {
private:
Packet packet_ = {};
// order matters, don't change this enum!
enum payloadEncoding {
byteStuffing,
raw,
unknown,
replace,
lz,
} encoding_;
uint32_t payload_size_ = 0, payload_size_count_ = 0;
// payloadEncoding::replace state
uint8_t substitute_;
// payloadEncoding::lz state
detail::InflateTransformer inflate_;
// deobfuscation state
bool obfuscated_;
std::linear_congruential_engine<uint32_t, 1103515245, 12345, 0> lcg_;
enum class readStep {
idle,
readNode,
readTag,
readPayloadSize,
readPayloadFlags,
readReplacementByte,
readPayload,
readEscaped,
} step_ = readStep::idle;
void set_step_(readStep s);
void reset_(readStep s);
int update_payload_size_(uint8_t b);
uint8_t deobfuscate_(uint8_t b);
public:
bool update(uint8_t b);
const Packet &packet();
};
bool encode_packet(std::queue<uint8_t> &out, uint8_t node, uint8_t tag, const std::vector<uint8_t> &payload);
}
-209
View File
@@ -1,209 +0,0 @@
#include "acioemu.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace acioemu;
ACIOEmu::ACIOEmu() {
this->devices = new std::vector<ACIODeviceEmu *>();
this->response_buffer = new circular_buffer<uint8_t>(4096);
this->read_buffer = new circular_buffer<uint8_t>(1024);
}
ACIOEmu::~ACIOEmu() {
// delete devices
for (auto device : *this->devices) {
delete device;
}
delete this->devices;
// delete buffers
delete this->response_buffer;
delete this->read_buffer;
}
void ACIOEmu::add_device(ACIODeviceEmu *device) {
this->devices->push_back(device);
}
void ACIOEmu::write(uint8_t byte) {
// insert into buffer
if (!invert) {
if (byte == ACIO_ESCAPE) {
invert = true;
} else {
this->read_buffer->put(byte);
}
} else {
byte = ~byte;
invert = false;
this->read_buffer->put(byte);
}
// clean garbage
while (!this->read_buffer->empty() && this->read_buffer->peek() != 0xAA) {
this->read_buffer->get();
}
while (this->read_buffer->size() > 1 && this->read_buffer->peek(1) == 0xAA) {
this->read_buffer->get();
}
// handshake counter
static unsigned int handshake_counter = 0;
if (byte == 0xAA) {
handshake_counter++;
} else {
handshake_counter = 0;
}
// check for handshake
if (handshake_counter > 1) {
/*
* small hack - BIO2 seems to expect more bytes here - sending two bytes each time fixes it
* TODO replace this handshake code with something better
*/
this->response_buffer->put(ACIO_SOF);
this->response_buffer->put(ACIO_SOF);
handshake_counter--;
return;
}
// parse
if (!this->read_buffer->empty() && this->read_buffer->size() >= 6) {
bool is_complete = false;
// check if broadcast
if (this->read_buffer->peek(1) == ACIO_BROADCAST) {
// check msg data size
auto data_size = this->read_buffer->peek(2);
// check if msg is complete (SOF + checksum + broadcast header + data_size)
is_complete = this->read_buffer->size() >= 2u + 2u + data_size;
} else {
// check msg data size
auto data_size = this->read_buffer->peek(5);
// check if msg is complete (SOF + checksum + command header + data_size)
is_complete = this->read_buffer->size() >= 2u + MSG_HEADER_SIZE + data_size;
}
// parse message if complete
if (is_complete) {
this->msg_parse();
this->read_buffer->reset();
}
}
}
std::optional<uint8_t> ACIOEmu::read() {
if (this->response_buffer->empty()) {
return std::nullopt;
}
return this->response_buffer->get();
}
size_t ACIOEmu::bytes_available() {
return this->response_buffer->size();
}
void ACIOEmu::msg_parse() {
#ifdef ACIOEMU_LOG
log_info("acioemu", "MSG RECV: {}", bin2hex(*this->read_buffer));
#endif
// calculate checksum
uint8_t chk = 0;
size_t max = this->read_buffer->size() - 1;
for (size_t i = 1; i < max; i++) {
chk += this->read_buffer->peek(i);
}
// check checksum
uint8_t chk_receive = this->read_buffer->peek(this->read_buffer->size() - 1);
if (chk != chk_receive) {
#ifdef ACIOEMU_LOG
log_info("acioemu", "detected wrong checksum: {}/{}", chk, chk_receive);
#endif
return;
}
// get message data
auto msg_data = this->read_buffer->peek_all();
auto msg_in = (MessageData *) &msg_data[1];
// correct cmd code endianness if this is not a broadcast
if (msg_in->addr != ACIO_BROADCAST) {
msg_in->cmd.code = acio_u16(msg_in->cmd.code);
}
// pass to applicable device
uint8_t node_offset = 0;
for (auto device : *this->devices) {
if (device->is_applicable(node_offset, msg_in->addr)) {
auto cur_offset = msg_in->addr - node_offset - 1;
if (cur_offset < 0) {
break;
}
if (device->parse_msg(msg_in, this->response_buffer)) {
return;
} else {
break;
}
}
node_offset += device->node_count;
}
// ignore broadcast messages by default
if (msg_in->addr == ACIO_BROADCAST) {
return;
}
/*
* Default Behavior
* If you want to do anything different, just handle the
* commands in your own device implementation.
*/
switch (msg_in->cmd.code) {
// node count report
case ACIO_CMD_ASSIGN_ADDRS: {
if (msg_in->addr == 0x00 && node_offset > 0) {
auto msg = ACIODeviceEmu::create_msg(msg_in, 1, &node_offset);
ACIODeviceEmu::write_msg(msg, this->response_buffer);
delete msg;
return;
}
break;
}
// status 0 defaults
case ACIO_CMD_CLEAR:
case ACIO_CMD_STARTUP:
case 0x80: // KEEPALIVE
case 0xFF: // BROADCAST
{
// send status 0
auto msg = ACIODeviceEmu::create_msg_status(msg_in, 0);
ACIODeviceEmu::write_msg(msg, response_buffer);
delete msg;
return;
}
default:
break;
}
#ifdef ACIOEMU_LOG
log_info("acioemu", "UNHANDLED MSG FOR ADDR: {}, CMD: 0x{:x}), DATA: {}",
msg_in->addr,
msg_in->cmd.code,
bin2hex(*this->read_buffer));
#endif
}
-33
View File
@@ -1,33 +0,0 @@
#pragma once
#include <cstdint>
#include <vector>
#include "util/circular_buffer.h"
#include "device.h"
#include "icca.h"
namespace acioemu {
class ACIOEmu {
private:
std::vector<ACIODeviceEmu *> *devices;
circular_buffer<uint8_t> *response_buffer;
circular_buffer<uint8_t> *read_buffer;
bool invert = false;
void msg_parse();
public:
explicit ACIOEmu();
~ACIOEmu();
void add_device(ACIODeviceEmu *device);
void write(uint8_t byte);
std::optional<uint8_t> read();
size_t bytes_available();
};
}
-80
View File
@@ -1,80 +0,0 @@
#pragma once
#include <ctime>
#include <thread>
#include <mutex>
#include <cstring>
#include "device.h"
#include "hooks/sleephook.h"
namespace acioemu {
#pragma pack(push, 1)
struct bio2_bi2a_state_in {
uint8_t pad0[3];
uint8_t panel[4];
uint8_t deck_switch[14];
uint8_t pad21[2];
uint8_t led_ticker[9];
uint8_t spot_light_1[4];
uint8_t neon_light;
uint8_t spot_light_2[4];
uint8_t pad41[7];
};
struct bio2_bi2a_status {
uint8_t slider_1;
uint8_t system;
uint8_t slider_2;
uint8_t pad3;
uint8_t slider_3;
uint8_t pad5;
uint8_t slider_4;
uint8_t slider_5;
uint8_t pad8;
uint8_t panel;
uint8_t pad10[6];
uint8_t tt_p1;
uint8_t tt_p2;
uint8_t p1_s1;
uint8_t pad20;
uint8_t p1_s2;
uint8_t pad22;
uint8_t p1_s3;
uint8_t pad24;
uint8_t p1_s4;
uint8_t pad26;
uint8_t p1_s5;
uint8_t pad28;
uint8_t p1_s6;
uint8_t pad30;
uint8_t p1_s7;
uint8_t pad32;
uint8_t p2_s1;
uint8_t pad34;
uint8_t p2_s2;
uint8_t pad36;
uint8_t p2_s3;
uint8_t pad38;
uint8_t p2_s4;
uint8_t pad40;
};
#pragma pack(pop)
class BI2A : public ACIODeviceEmu {
private:
uint8_t coin_counter = 0;
public:
explicit BI2A(bool type_new, bool flip_order, bool keypad_thread, uint8_t node_count);
~BI2A() override;
bool parse_msg(unsigned int node_offset,
MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer) override;
void update_card(int unit);
void update_keypad(int unit, bool update_edge);
void update_status(int unit);
};
}
-118
View File
@@ -1,118 +0,0 @@
#include "device.h"
#include "util/logging.h"
#include "util/utils.h"
using namespace acioemu;
void ACIODeviceEmu::set_header(MessageData* data, uint8_t addr, uint16_t code, uint8_t pid,
uint8_t data_size)
{
// flag as response
if (addr != 0) {
addr |= ACIO_RESPONSE_FLAG;
}
// set header data
data->addr = addr;
data->cmd.code = acio_u16(code);
data->cmd.pid = pid;
data->cmd.data_size = data_size;
}
void ACIODeviceEmu::set_version(MessageData* data, uint32_t type, uint8_t flag,
uint8_t ver_major, uint8_t ver_minor, uint8_t ver_rev, std::string code)
{
// set version data
auto data_version = &data->cmd.data_version;
data_version->type = type;
data_version->flag = flag;
data_version->ver_major = ver_major;
data_version->ver_minor = ver_minor;
data_version->ver_rev = ver_rev;
strncpy(data_version->code, code.c_str(), sizeof(data_version->code));
strncpy(data_version->date, __DATE__, sizeof(data_version->date));
strncpy(data_version->time, __TIME__, sizeof(data_version->time));
}
MessageData *ACIODeviceEmu::create_msg(uint8_t addr, uint16_t code, uint8_t pid, size_t data_size,
uint8_t *data)
{
// check data size
if (data_size > 0xFF) {
log_warning("acio", "data size > 255: {}", data_size);
data_size = 0xFF;
}
// allocate data
auto data_raw = new uint8_t[MSG_HEADER_SIZE + data_size];
// set header
auto msg = (MessageData *) &data_raw[0];
set_header(msg, addr, code, pid, (uint8_t) data_size);
// set data
if (data) {
memcpy(data_raw + MSG_HEADER_SIZE, data, data_size);
} else {
memset(data_raw + MSG_HEADER_SIZE, 0, data_size);
}
// return prepared message
return msg;
}
MessageData *ACIODeviceEmu::create_msg(MessageData *msg_in, size_t data_size, uint8_t *data) {
return create_msg(msg_in->addr, msg_in->cmd.code, msg_in->cmd.pid, data_size, data);
}
MessageData *ACIODeviceEmu::create_msg_status(uint8_t addr, uint16_t code, uint8_t pid, uint8_t status) {
return create_msg(addr, code, pid, 1, &status);
}
MessageData *ACIODeviceEmu::create_msg_status(MessageData *msg_in, uint8_t status) {
return create_msg_status(msg_in->addr, msg_in->cmd.code, msg_in->cmd.pid, status);
}
bool ACIODeviceEmu::is_applicable(uint8_t node_offset, uint8_t node) {
return node > node_offset && node <= node_offset + this->node_count;
}
void ACIODeviceEmu::write_msg(const uint8_t *data, size_t size, circular_buffer<uint8_t> *response_buffer) {
// header
for (int i = 0; i < 2; i++) {
response_buffer->put(ACIO_SOF);
}
// msg data and checksum
uint8_t b, chk = 0;
for (size_t i = 0; i <= size; i++) {
// set byte to data or checksum
if (i < size) {
b = data[i];
chk += b;
} else {
b = chk;
}
// check for escape
if (b == ACIO_SOF || b == ACIO_ESCAPE) {
response_buffer->put(ACIO_ESCAPE);
response_buffer->put(~b);
} else {
response_buffer->put(b);
}
}
#ifdef ACIOEMU_LOG
log_info("acioemu", "ACIO MSG OUT: AA{}{:02X}", bin2hex(data, size), chk);
#endif
}
void ACIODeviceEmu::write_msg(MessageData *msg, circular_buffer<uint8_t> *response_buffer) {
auto data = reinterpret_cast<const uint8_t *>(msg);
write_msg(data, MSG_HEADER_SIZE + msg->cmd.data_size, response_buffer);
}
-104
View File
@@ -1,104 +0,0 @@
#pragma once
#include <cstdint>
#include <string>
#include "util/circular_buffer.h"
// convert big-endian to little-endian
#define acio_u16 _byteswap_ushort
#define acio_u32 _byteswap_ulong
namespace acioemu {
constexpr uint8_t ACIO_SOF = 0xAA;
constexpr uint8_t ACIO_ESCAPE = 0xFF;
constexpr uint8_t ACIO_BROADCAST = 0x70;
constexpr uint8_t ACIO_RESPONSE_FLAG = 0x80;
// general command codes
enum acio_cmd_codes {
ACIO_CMD_ASSIGN_ADDRS = 0x0001,
ACIO_CMD_GET_VERSION = 0x0002,
ACIO_CMD_STARTUP = 0x0003,
ACIO_CMD_KEEPALIVE = 0x0080,
ACIO_CMD_CLEAR = 0x0100,
};
// message structs
#pragma pack(push, 1)
struct VersionData {
uint32_t type;
uint8_t flag;
uint8_t ver_major;
uint8_t ver_minor;
uint8_t ver_rev;
char code[4];
char date[16];
char time[16];
};
struct MessageData {
uint8_t addr;
union {
struct {
uint16_t code;
uint8_t pid;
uint8_t data_size;
union {
uint8_t raw[0xFF];
uint8_t status;
VersionData data_version;
};
} cmd;
struct {
uint8_t data_size;
uint8_t raw[0xFF];
} broadcast;
};
};
#pragma pack(pop)
// message sizes
constexpr size_t MSG_HEADER_SIZE = 5;
constexpr size_t MSG_VERSION_SIZE = sizeof(VersionData);
class ACIODeviceEmu {
public:
// attributes
uint8_t node_count = 0;
/*
* Helper functions for getting/setting the message contents
*/
static void set_header(MessageData* data, uint8_t addr, uint16_t code, uint8_t pid, uint8_t data_size);
static void set_version(MessageData* data, uint32_t type, uint8_t flag,
uint8_t ver_major, uint8_t ver_minor, uint8_t ver_rev,
std::string code);
/*
* This function creates a basic message with optional parameter data.
* If data is set to null, the parameter data will be initialized with 0x00
*/
static MessageData* create_msg(uint8_t addr, uint16_t cmd, uint8_t pid,
size_t data_size, uint8_t *data = nullptr);
static MessageData* create_msg(MessageData* msg_in, size_t data_size, uint8_t *data = nullptr);
/*
* Helper functions for generating messages
*/
static MessageData* create_msg_status(uint8_t addr, uint16_t code, uint8_t pid, uint8_t status);
static MessageData* create_msg_status(MessageData* msg_in, uint8_t status);
virtual ~ACIODeviceEmu() = default;
virtual bool is_applicable(uint8_t node_offset, uint8_t node);
virtual bool parse_msg(MessageData *msg_in, circular_buffer<uint8_t> *response_buffer) = 0;
static void write_msg(const uint8_t *data, size_t size, circular_buffer<uint8_t> *response_buffer);
static void write_msg(MessageData *msg, circular_buffer<uint8_t> *response_buffer);
};
}
-74
View File
@@ -1,74 +0,0 @@
#include "handle.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/utils.h"
acioemu::ACIOHandle::ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount) {
this->com_port = lpCOMPort;
this->icca_node_count = iccaNodeCount;
}
bool acioemu::ACIOHandle::open(LPCWSTR lpFileName) {
if (wcscmp(lpFileName, com_port) != 0) {
return false;
}
log_info("acioemu", "Opened {} (ACIO)", ws2s(com_port));
// ACIO device
acio_emu.add_device(new acioemu::ICCADevice(false, true, icca_node_count));
return true;
}
int acioemu::ACIOHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
// read from emu
DWORD bytes_read = 0;
while (bytes_read < nNumberOfBytesToRead) {
auto cur_byte = acio_emu.read();
if (cur_byte.has_value()) {
buffer[bytes_read++] = cur_byte.value();
} else {
break;
}
}
// return amount of bytes read
return (int) bytes_read;
}
int acioemu::ACIOHandle::write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) {
auto buffer = reinterpret_cast<const uint8_t *>(lpBuffer);
// write to emu
for (DWORD i = 0; i < nNumberOfBytesToWrite; i++) {
acio_emu.write(buffer[i]);
}
// return all data written
return (int) nNumberOfBytesToWrite;
}
int acioemu::ACIOHandle::device_io(
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize
) {
return -1;
}
size_t acioemu::ACIOHandle::bytes_available() {
return acio_emu.bytes_available();
}
bool acioemu::ACIOHandle::close() {
log_info("acioemu", "Closed {} (ACIO)", ws2s(com_port));
return true;
}
-32
View File
@@ -1,32 +0,0 @@
#pragma once
#include "acioemu/acioemu.h"
#include "hooks/devicehook.h"
namespace acioemu {
class ACIOHandle : public CustomHandle {
private:
LPCWSTR com_port;
uint8_t icca_node_count;
acioemu::ACIOEmu acio_emu;
public:
ACIOHandle(LPCWSTR lpCOMPort, uint8_t iccaNodeCount = 2);
bool open(LPCWSTR lpFileName) override;
int read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) override;
int write(LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite) override;
int device_io(DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer,
DWORD nOutBufferSize) override;
size_t bytes_available() override;
bool close() override;
};
}
-580
View File
@@ -1,580 +0,0 @@
#include "icca.h"
#include "acio/icca/icca.h"
#include "avs/game.h"
#include "games/sdvx/sdvx.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h"
using namespace acioemu;
namespace acioemu {
bool ICCA_DEVICE_HACK = false;
}
ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count) {
// init defaults
this->type_new = false;
this->flip_order = flip_order;
this->node_count = node_count;
this->cards = new uint8_t *[node_count] {};
this->cards_time = new time_t[node_count] {};
this->status = new uint8_t[node_count * 16] {};
this->accept = new bool[node_count] {};
for (int i = 0; i < node_count; i++) {
this->accept[i] = true;
}
this->hold = new bool[node_count] {};
this->keydown = new uint8_t[node_count] {};
this->keypad = new uint16_t[node_count] {};
this->keypad_last = new bool*[node_count] {};
for (int i = 0; i < node_count; i++) {
this->keypad_last[i] = new bool[12] {};
}
this->keypad_capture = new uint8_t[node_count] {};
for (int i = 0; i < node_count; i++) {
this->keypad_capture[i] = 0x08;
}
this->crypt = new std::optional<Crypt>[node_count] {};
this->counter = new uint8_t[node_count] {};
for (int i = 0; i < node_count; i++) {
this->counter[i] = 2;
}
// keypad thread for faster polling
if (keypad_thread) {
this->keypad_thread = new std::thread([this]() {
timeutils::PreciseSleepTimer timer;
while (this->cards) {
for (int unit = 0; unit < this->node_count; unit++) {
this->update_keypad(unit, false);
}
timer.sleep(7);
}
});
}
}
ICCADevice::~ICCADevice() {
// stop thread
delete keypad_thread;
// delete cards in array
for (int i = 0; i < node_count; i++) {
delete cards[i];
}
// delete the rest
delete[] cards;
delete[] cards_time;
delete[] status;
delete[] accept;
delete[] hold;
delete[] keydown;
delete[] keypad;
delete[] keypad_last;
delete[] keypad_capture;
delete[] crypt;
delete[] counter;
}
bool ICCADevice::parse_msg(MessageData *msg_in,
circular_buffer<uint8_t> *response_buffer) {
// get unit
int unit = msg_in->addr - 1;
if (this->flip_order) {
unit = this->node_count - unit - 1;
}
if (unit != 0 && unit != 1) {
log_fatal("icca", "invalid unit: {}", unit);
}
#ifdef ACIOEMU_LOG
log_info("acioemu", "ICCA ADDR: {}, CMD: 0x{:04x}", unit, msg_in->cmd.code);
#endif
// check command
switch (msg_in->cmd.code) {
case ACIO_CMD_GET_VERSION: {
// send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
if (avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
games::sdvx::is_valkyrie_model()) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} else if (avs::game::is_model("VFG")) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
} else {
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
}
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0130: { // REINITIALIZE
// send status 0
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0131: { // READ CARD UID
// build data array
auto msg = this->create_msg(msg_in, 16);
// update things
update_card(unit);
update_keypad(unit, true);
update_status(unit);
// copy status
memcpy(msg->cmd.raw, &status[unit * 16], 16);
// explicitly set no card since this is just read
msg->cmd.raw[0] = 0x01;
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0135: { // SET ACTION
// check for data
if (msg_in->cmd.data_size >= 2) {
// subcommand
switch (msg_in->cmd.raw[1]) {
case 0x00: // ACCEPT DISABLE
this->accept[unit] = false;
break;
case 0x11: // ACCEPT ENABLE
this->accept[unit] = true;
break;
case 0x12: // EJECT
if (this->cards[unit] != nullptr) {
delete this->cards[unit];
}
this->cards[unit] = nullptr;
this->hold[unit] = false;
default:
break;
}
}
// no break, return status
[[fallthrough]];
}
case 0x0134: { // GET STATUS
// build data array
auto msg = this->create_msg(msg_in, 16);
// update things
update_card(unit);
update_keypad(unit, true);
update_status(unit);
// copy status
memcpy(msg->cmd.raw, &status[unit * 16], 16);
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0160: { // KEY EXCHANGE
// if this cmd is called, the reader type must be new
this->type_new = true;
// build data array
auto msg = this->create_msg(msg_in, 4);
// set key
msg->cmd.raw[0] = 0xBE;
msg->cmd.raw[1] = 0xEF;
msg->cmd.raw[2] = 0xCA;
msg->cmd.raw[3] = 0xFE;
// convert keys
uint32_t game_key =
msg_in->cmd.raw[0] << 24 |
msg_in->cmd.raw[1] << 16 |
msg_in->cmd.raw[2] << 8 |
msg_in->cmd.raw[3];
uint32_t reader_key =
msg->cmd.raw[0] << 24 |
msg->cmd.raw[1] << 16 |
msg->cmd.raw[2] << 8 |
msg->cmd.raw[3];
log_info("icca", "client key: {:08x}", game_key);
log_info("icca", "reader key: {:08x}", reader_key);
this->crypt[unit].emplace();
this->crypt[unit]->set_keys(reader_key, game_key);
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x0161: { // READ CARD UID NEW
// if this cmd is called, the reader type must be new
this->type_new = true;
// decide on answer
int answer_type = 0;
//if (avs::game::is_model("LDJ"))
//answer_type = 1;
// SDVX Old cabinet mode
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
answer_type = 1;
if (avs::game::is_model("L44"))
answer_type = 2;
// check answer type
switch (answer_type) {
case 1: {
// send status 1
auto msg = this->create_msg_status(msg_in, 1);
write_msg(msg, response_buffer);
delete msg;
break;
}
case 2: {
// build data array
auto msg = this->create_msg(msg_in, 16);
// update card
update_card(unit);
// check for card
if (this->cards[unit] != nullptr) {
// copy into data buffer
memcpy(msg->cmd.raw, this->cards[unit], 8);
// delete card
delete this->cards[unit];
this->cards[unit] = nullptr;
this->hold[unit] = false;
}
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
default: {
// send response with no data
auto msg = this->create_msg(msg_in, 0);
write_msg(msg, response_buffer);
delete msg;
break;
}
}
break;
}
case 0x0164: { // GET STATUS ENC
// build data array
auto msg = this->create_msg(msg_in, 18);
// update things
update_card(unit);
update_keypad(unit, true);
update_status(unit);
// copy status
memcpy(msg->cmd.raw, &status[unit * 16], 16);
if (this->crypt[unit].has_value()) {
auto &crypt = this->crypt[unit];
uint16_t crc = crypt->crc(msg->cmd.raw, 16);
msg->cmd.raw[16] = (uint8_t) (crc >> 8);
msg->cmd.raw[17] = (uint8_t) crc;
crypt->crypt(msg->cmd.raw, 18);
} else {
log_warning("icca", "'GET STATUS ENC' message received with no crypt keys initialized");
}
// write message
write_msg(msg, response_buffer);
delete msg;
break;
}
case 0x013A: { // POWER CONTROL (tentative name, used in 1.7 firmware)
// TODO(felix): isolate this logic to LDJ and/or firmware 1.7 emulation
if (this->counter[unit] > 0) {
this->counter[unit]--;
}
//log_info("icca", "counter[{}] = {}", unit, this->counter[unit]);
auto msg = this->create_msg_status(msg_in, this->counter[unit]);
write_msg(msg, response_buffer);
delete msg;
break;
}
case ACIO_CMD_STARTUP:
case ACIO_CMD_CLEAR:
case 0x30: // GetBoardProductNumber
case 0x31: // GetMicomInfo
case 0x3A: // ???
case 0x0116: // ???
case 0x0120: // ???
case 0xFF: // BROADCAST
{
// send status 0
auto msg = this->create_msg_status(msg_in, 0x00);
write_msg(msg, response_buffer);
delete msg;
break;
}
default:
return false;
}
// mark as handled
return true;
}
void ICCADevice::update_card(int unit) {
// wavepass timeout after 10s
if (this->cards[unit] != nullptr) {
time_t t_now;
time(&t_now);
if (difftime(t_now, this->cards_time[unit]) >= 10.f) {
if (this->cards[unit] != nullptr) {
delete this->cards[unit];
}
this->cards[unit] = nullptr;
this->hold[unit] = false;
}
}
bool kb_insert_press = false;
// eamio keypress
kb_insert_press |= static_cast<bool>(eamuse_get_keypad_state((size_t) unit) & (1 << EAM_IO_INSERT));
// check for card
if (this->cards[unit] == nullptr && (eamuse_card_insert_consume(this->node_count, unit) || kb_insert_press)) {
auto card = new uint8_t[8];
if (!eamuse_get_card(this->node_count, unit, card)) {
// invalid card found
delete[] card;
} else {
this->cards[unit] = card;
time(&this->cards_time[unit]);
}
}
}
static int KEYPAD_EAMUSE_MAPPING[] = {
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
// map for KEYPAD_KEY_CODES:
// 7 8 9 | 800 8000 8
// 4 5 6 | 400 4000 4
// 1 2 3 | 200 2000 2
// 0 00 . | 100 1000 1
static int KEYPAD_KEY_CODES[]{
0x100, // 0
0x200, // 1
0x2000, // 2
2, // 3
0x400, // 4
0x4000, // 5
4, // 6
0x800, // 7
0x8000, // 8
8, // 9
1, // .
0x1000 // 00
};
// map for KEYPAD_KEY_CODES_ALT:
// 7 8 9 | 8 80 800
// 4 5 6 | 4 40 400
// 1 2 3 | 2 20 200
// 0 00 . | 1 10 100
//
// note that the only game that needs this (SDVX VM) does not accept decimal,
// so that key is untested
static int KEYPAD_KEY_CODES_ALT[]{
1, // 0
2, // 1
0x20, // 2
0x200, // 3
4, // 4
0x40, // 5
0x400, // 6
8, // 7
0x80, // 8
0x800, // 9
0x100, // .
0x10 // 00
};
static uint8_t KEYPAD_KEY_CODE_NUMS[]{
0, 1, 5, 9, 2, 6, 10, 3, 7, 11, 8, 4
};
void ICCADevice::update_keypad(int unit, bool update_edge) {
// lock keypad so threads can't interfere
std::lock_guard<std::mutex> lock(this->keypad_mutex);
// reset unit
this->keypad[unit] = 0;
// get eamu key states
uint16_t eamu_state = eamuse_get_keypad_state((size_t) unit);
// iterate keypad
bool edge = false;
for (int n = 0; n < 12; n++) {
int i = n;
// check if pressed
if (eamu_state & (1 << KEYPAD_EAMUSE_MAPPING[i])) {
if (ICCA_DEVICE_HACK) {
this->keypad[unit] |= KEYPAD_KEY_CODES_ALT[i];
} else {
this->keypad[unit] |= KEYPAD_KEY_CODES[i];
}
if (!this->keypad_last[unit][i] && update_edge) {
this->keydown[unit] = (this->keypad_capture[unit] << 4) | KEYPAD_KEY_CODE_NUMS[n];
this->keypad_last[unit][i] = true;
edge = true;
}
} else {
this->keypad_last[unit][i] = false;
}
}
// update keypad capture
if (update_edge && edge) {
this->keypad_capture[unit]++;
this->keypad_capture[unit] |= 0x08;
} else {
this->keydown[unit] = 0;
}
}
void ICCADevice::update_status(int unit) {
// get buffer
uint8_t *buffer = &this->status[unit * 16];
// clear buffer
memset(buffer, 0x00, 16);
// check for card
bool card = false;
if (this->cards[unit] != nullptr) {
// copy card into buffer
memcpy(buffer + 2, this->cards[unit], 8);
card = true;
}
// check for reader type
if (this->type_new) {
// check for card
if (card) {
// set status to card present
buffer[0] = 0x02;
/*
* set card type
* 0x00 - ISO15696
* 0x01 - FELICA
*/
bool felica = buffer[2] != 0xE0 && buffer[3] != 0x04;
buffer[1] = felica ? 0x01 : 0x00;
buffer[10] = felica ? 0x01 : 0x00;
} else if (avs::game::is_model({"LDJ", "TBS", "XIF"}) || games::sdvx::is_valkyrie_model()) {
// set status to 0 otherwise reader power on fails
buffer[0] = 0x00;
} else {
// set status to no card present (1 or 4)
buffer[0] = 0x04;
}
} else { // old reader
// check for card
if (card && accept[unit]) {
this->hold[unit] = true;
}
// check for hold
if (this->hold[unit]) {
// set status to card present
buffer[0] = 0x02;
/*
* sensors
* 0x10 - OLD READER FRONT
* 0x20 - OLD READER BACK
*/
// activate both sensors
buffer[1] = 0x30;
} else {
// card present but reader isn't accepting it
if (card) {
// set card present
buffer[0] = 0x02;
// set front sensor
buffer[1] = 0x10;
} else {
// no card present
buffer[0] = 0x01;
}
}
// card type not present for old reader
buffer[10] = 0x00;
}
// other flags
buffer[11] = 0x03;
buffer[12] = keydown[unit];
buffer[13] = 0x00;
buffer[14] = (uint8_t) (keypad[unit] >> 8);
buffer[15] = (uint8_t) (keypad[unit] & 0xFF);
}

Some files were not shown because too many files have changed in this diff Show More