95 Commits
Author SHA1 Message Date
REDCODE 786a4254d2 Fixes build failing due to dsdmo.i686.dll being downloaded via wget command
Continuous Integration / Build (push) Successful in 11m51s
2026-08-21 14:38:32 +02:00
REDCODE 09ac50f2d5 Merge branch 'github-main' into r3d-spice2x
Continuous Integration / Build (push) Failing after 5m3s
2026-08-21 14:26:23 +02:00
bicarusandGitHub 2dc3c0cbe4 patcher: make the checkbox label toggleable (#882)
Requested by a certain sea creature. Don't know why ImGui doesn't do
this by default.

Also, use ImGui internal mixed state (tri-state) checkbox instead
rendering our own.
2026-08-20 23:48:11 -07:00
bicarusandGitHub 4c2a70bab6 build: improve dockerfile (#881)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change

`mingw-w64-libjpeg-turbo` and `mingw-w64-x264` are AUR packages, so the
deps image compiles them from source, once per mingw target arch. Stock
`makepkg.conf` leaves `MAKEFLAGS` commented out and both PKGBUILDs call
bare `make`, so all of that was building single-threaded.

Adds a `~/.makepkg.conf` for the build user setting
`MAKEFLAGS="-j$(nproc)"`. It is kept literal so it evaluates when
makepkg sources the file, rather than baking in the core count of
whichever machine built the image. Using `~/.makepkg.conf` rather than
`/etc` leaves the pacman-owned system file untouched.

Measured on 20 cores, compiling both libraries from scratch: **310s to
64s, a 4.8x improvement.**

Also merges the two `yay` invocations into a single layer.

## Testing
2026-08-20 23:25:00 -07:00
bicarusandGitHub 46f76597fc cfg: active only filter for options search (#879)
Requested by a sea creature
2026-08-20 22:38:30 -07:00
bicarusandGitHub b23640222c api: add CORS policy (#878)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Adds `Access-Control-Allow-Origin: *` to the video stream server's
responses.

## Testing
Tested with substream project.
2026-08-20 02:58:22 -07:00
bicarusandGitHub 3f6862908e api: drop stale websocket / stream connections (#877) 2026-08-19 13:37:41 -07:00
bicarusandGitHub 0934cce225 api: h.264 video stream (#876)
## Link to GitHub Issue or related Pull Request, if one exists
fixes #875

## Description of change
Adds `-apistream`, an optional HTTP video stream of the mirrored screen.
It listens on the API port +2.

Two endpoints, sharing the same `screen`, `fps` and `q` parameters:

    /stream.mjpg    JPEG frames, for clients with no container support
/stream.h264 H.264 annex-b, for an app driving MediaCodec or
VideoToolbox itself

One encoder per connection, fed by a per-screen pump that always hands
over the newest frame, so a slow reader drops frames instead of building
a backlog. `capture.get_jpg` behaviour is unchanged.

Additional documentation for developers:
https://github.com/spice2x/spice2x.github.io/wiki/Video-Stream

## Testing
2026-08-19 03:10:02 -07:00
bicarusandGitHub 7c50fcc79e graphics: remove libjpeg from XP builds, fetch at build time (#874)
Replaces the vendored `external/libjpeg-turbo` tree (191 files) with the
`mingw-w64-libjpeg-turbo` AUR package, already built into the Docker
deps image.

The WinXP toolchains set `CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY`
against their own sysroot, so they cannot see AUR mingw packages. Those
targets now build without JPEG support: `jpeg_encoder::encode` compiles
to a stub returning `false`, which callers already treat as "no frame
available". The practical effect is that `capture.get_jpg` over the API
is unavailable on WinXP builds; every other target is unchanged.

CMake names `libjpeg.a` outright, since the package also ships an import
library and linking that would add a runtime DLL dependency.
2026-08-18 08:57:18 -07:00
0f4ab63101 patchmanager: fix signature patches reporting neither on or off (#873)
## Link to GitHub Issue or related Pull Request, if one exists
None

## Description of change
Patch Manager showed valid signature patches as broken: **"Bad patch;
patch is neither on or off"** instead of Enabled/Disabled, so they could
not be toggled.

`SignaturePatch::to_memory` had two bugs. In the configurator it cached
a file offset as `data_offset_ptr`, so status checks `memcmp`'d a fake
address. It also passed JSON `offset` into `find_pattern` while still
indexing the signature/replacement from 0, which mis-aligned every patch
with `offset != 0`. This change locates the signature start, applies
`offset` afterward, compares only the replacement window, and leaves the
pointer null so `is_patch_active` re-resolves from `data_offset`.

Not proposed as built-in patches. The JSON below is the reproduction
case: each entry uses `offset > 0` and a replacement shorter than the
signature.

## Testing
Reproduced in Patch Manager against `bm2dx.dll` using the signature JSON
below. Before the fix, every patch reported "neither on or off". After
the fix, each patch locates, shows Disabled/Enabled, and toggling writes
only the replacement bytes at `signature_match + offset`.

## Demo
<details>
<summary>Signature JSON used to reproduce (offset + short
replacement)</summary>

```json
[
  {
    "info": "streaming / getcm patches (type=signature)",
    "gameCode": "LDJ",
    "notes": "Each site uses a unique signature (usage=0 only)."
  },
  {
    "type": "group",
    "id": "streaming-getcm",
    "name": "Streaming getcm",
    "description": "Enable all children so streaming.common merges without Banner FS and getcm can fire without visiting Test Mode.",
    "gameCode": "LDJ"
  },
  {
    "name": "Streaming: merge common without Banner",
    "description": "NOP jz in streaming.common callback so CM work table is filled even when Banner FS is still null.",
    "caution": "Required. Without this, early common responses are discarded and getcm stays empty.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "E8????????4885C00F84????????488D0D????????488D15????????41B848080000",
    "replacement": "909090909090",
    "offset": 8,
    "usage": 0
  },
  {
    "name": "Streaming: scheduler without Banner (common)",
    "description": "NOP jz after Banner getter on the common branch of the periodic scheduler.",
    "caution": "Enable with the getcm scheduler sibling. Unique via imul of common-interval dword.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "E8????????4885C0742469057E791C0AE8030000",
    "replacement": "9090",
    "offset": 8,
    "usage": 0
  },
  {
    "name": "Streaming: scheduler without Banner (getcm)",
    "description": "NOP jz after Banner getter on the getcm branch of the periodic scheduler.",
    "caution": "Enable with the common scheduler sibling. Unique via imul of getcm-interval dword. Without this, getcm never schedules while Banner FS is null.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "E8????????4885C07424690574B91D0AE8030000",
    "replacement": "9090",
    "offset": 8,
    "usage": 0
  },
  {
    "name": "Streaming: fall into getcm after common",
    "description": "NOP jmp-after-common so the same scheduler tick can evaluate getcm instead of returning early.",
    "caution": "Pair with Banner scheduler skips (or a live Banner FS).",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "891D????????E9????????E8????????4885C074246905",
    "replacement": "9090909090",
    "offset": 6,
    "usage": 0
  },
  {
    "name": "Streaming: getcm interval 1s #1",
    "description": "Default getcm poll interval 1800s to 1s (first init store).",
    "caution": "Enable #1 and #2 together. Trailing BF3C000000 distinguishes this init site.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "C705????????18150000C705????????08070000C705????????201C0000BF3C000000",
    "replacement": "01000000",
    "offset": 16,
    "usage": 0
  },
  {
    "name": "Streaming: getcm interval 1s #2",
    "description": "Default getcm poll interval 1800s to 1s (second init store).",
    "caution": "Enable #1 and #2 together. Trailing 448925 distinguishes this init site.",
    "gameCode": "LDJ",
    "type": "signature",
    "group": "streaming-getcm",
    "dllName": "bm2dx.dll",
    "signature": "C705????????18150000C705????????08070000C705????????201C0000448925",
    "replacement": "01000000",
    "offset": 16,
    "usage": 0
  }
]
```
</details>

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 08:16:12 -07:00
bicarusandGitHub 8b2f38307b graphics: rewrite screenshot and api capture image processing (#870)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Significantly speeds up API screen capture and D3D9 screenshots saving.
Two reasons for doing this:

1. We now have a 4K game (GITADORA) and existing capture code was taking
multiple seconds.
2. Renewed user interest on streaming as we have a couple more companion
apps in active development.

**API screen capture (streaming), 1280x720:** 14.3ms -> 6.3ms per frame.
Back buffer copies go to pooled `D3DPOOL_SYSTEMMEM` surfaces via
`GetRenderTargetData` instead of allocating a lockable render target
every frame, and TooJpeg is replaced with libjpeg-turbo (encode 9.8ms ->
3.0ms). MSAA remains unsupported

**Screenshots for GITADORA arena model, across 4 screens with one of
them 4K**: 4068ms -> 124ms. `D3DXSaveSurfaceToFileA` is replaced with
fpng (encode 4043ms -> 76ms) and the screens encode in parallel.
Dropping D3DX also removes the `d3dx9_43.dll` ... `d3dx9_24.dll` probing
loop, so screenshots no longer fail outright on machines with no D3DX9
runtime installed.

Screenshot surfaces are read on the present thread, so no D3D call
reaches another thread for screenshots. This fixes a hang in DDR X2
introduced earlier in the branch: its device has no internal locking,
and reading the surface on a pool thread while the present thread sat
inside `GetRenderTargetData` left the game's own render thread
deadlocked.


## Testing

- **GITADORA** (arena model, D3D9Ex, 4K main plus three subscreens,
windowed) with
`-screenshotsub`: three sets of four screenshots, images verified
correct. Completion
order differs between sets, so the screens really are encoding in
parallel.
- **LovePlus** (KLP, plain D3D9, 768x1360): covers the inline path used
by games whose
  image processing must not leave the present thread. 
- **API screen capture** through a companion app: live video correct
throughout.
- **Print Screen** bound as the screenshot key: the clipboard copy
succeeded on every shot.
- Quitting the game after capturing leaves no `IDirect3DDevice9`
reference count warning,
  so the pooled readback surfaces are released along with the device.
2026-08-18 00:22:45 -07:00
bicarusandGitHub 3863d5a4ed misc: various clean up for diagnosing launch failures (#872)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

## Description of change

**IIDX TDJ rom probe no longer touches removable media** —
`C:\000rom.txt` and `D:\001rom.txt` are not emulated paths; they hit
whatever is actually mounted on the user's machine. `D:` is commonly an
optical drive or card reader, and the launcher clears
`SEM_FAILCRITICALERRORS` process-wide before attach, so an empty drive
raises the modal *"insert a disk"* dialog and blocks the attaching
thread. The probe now checks `GetDriveTypeW` and only reads fixed and
RAM disks.

**`iat_find` no longer calls `log_fatal` on an unparseable module** —
`iat_try(nullptr)` walks every loaded module, including foreign ones
(injected, manually mapped, header wiped by AV/EDR/overlays). A non-`MZ`
DOS header called `log_fatal`. There is nothing to hook in such a
module, so it is skipped.

**`logger::stop()` can no longer hang forever** — hook installation
suspends every other thread, including the logging thread. `stop()`
unconditionally joined that thread, so `log_fatal` and the 30-second
`show_popup` watchdog both wedged instead of terminating, and logging is
asynchronous so nothing reached log.txt either. It now waits with a
timeout, then detaches and flushes synchronously.

**`GetFileSizeEx` was never hooked** — the hook was registered under the
name `"GetFileSize"`, so it re-patched that slot instead.

**Warn when `-modules` is set** — it changes where the game is run from,
and is usually set accidentally.

## Testing
*how was the code tested?*
2026-08-17 22:41:07 -07:00
bicarus-dev 94574c485a exlude line ending enforcement for external dir 2026-08-16 21:27:03 -07:00
bicarus-dev f857926ec3 fix line endings 2026-08-16 21:23:41 -07:00
bicarusandGitHub adf4cccd4a overlay: fix subscreen overlay rendering for popn (#869)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
For popn HC, some transparency effects, like the one for touch feedback,
rendered incorrectly; this is due to how alpha channel is handled for
subscreen overlay drawing on top of ImGui.

## Testing
Tested popn and iidx.
2026-08-16 04:24:14 -07:00
bicarusandGitHub bf8e194685 sdvx: fix api touch and -sdvxnosub sub window (#868)
## Link to GitHub Issue or related Pull Request, if one exists
Related: #859 and #862.

## Description of change

Three SDVX (Valkyrie model) fixes:

* **API touch went to the wrong window.** SDVX registers touch on both
its Main Screen and Sub Screen windows, and the native injector kept
whichever attached last. The touch surface is now published explicitly:
sub screen window when windowed, main window in fullscreen.
* **Landscape never rotated API coordinates.** Synthetic contacts bypass
`transform::hardware_to_game`, so they missed the rotation a real finger
gets. Applied before injection now, as portrait already did. Extracted
to `sdvx_landscape_rotate` so both paths share it; gated on the native
path since `wintouchemu` rotates via the subscreen overlay instead.
* **`-sdvxnosub` didn't hide the sub window in fullscreen.**
`ShowWindow_hook` had branches for GITADORA, pop'n and IIDX but not
SDVX. Added the missing one.

## Testing
Tested Nabla
2026-08-16 04:03:42 -07:00
bicarusandGitHub a2e508208c gitadora: fix synthetic touch (mouse and api) (#867)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Fix mouse and touch API handling for gitadora.

* hook native touch in every arena window mode (previously only using
wintouchemu for 1 windowed mode)
* publish the touch window (SMALL panel, or the main window drawing the
overlay) and only attach injection there
* route touch on the SMALL window straight through instead of the
overlay transform, and accept the mouse only there
* fix spiceapi by forcing 800x1280 touch canvas size
* run API capture before the subscreen present to fix api not showing
sub image


## Testing
Test:

gitadora 1/2/4 windowed mode, fullscreen

iidx /sdvx / popn windowed/fullscreen for regressions
2026-08-16 01:43:20 -07:00
bicarusandGitHub f5888609a8 graphics: option to include subscreens in screenshot (#866)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Adds `-screenshotsub`. Off by default. When on, screenshots include the
subscreens. All subscreens are captured, even if they are hidden from
view. Works for all four screens of gitadora arena model as well.

This should also fix an issue with API / companion app not capturing
gitadora subscreen correctly.

Note: only done for DX9... DX11 will need another PR to make this work.

Unrelated to this PR, there seems to be a bug with gitadora not
accepting mouse or api touch input.

## Testing
2026-08-14 04:05:50 -07:00
bicarusandGitHub c6cd72c528 graphics: exclude overlay from screenshots (#865)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change

Adds the `-screenshotoverlay` option.

By default, file screenshots exclude the Spice overlay. Enabling this
option captures screenshots after the overlay is rendered.

Also updates DX11 backend to allow screenshots even when overlay is
disabled.

## Testing
2026-08-13 23:21:46 -07:00
bicarusandGitHub 82e0c053d0 overlay: create a dedicated thread for polling hotkeys (#864)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Create a new thread that polls for the following hotkeys:

* super exit (both alt+f4 and bound key)
* coin insert
* screenshot

The goal is to make the capture of these more reliable, because before
this PR it wasn't.

## Testing
2026-08-12 20:04:59 -07:00
James LiuandGitHub 3b29227dbc gitadora: fix XG2 gibberish text showing (#863)
> [!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

## Description of change
Fix gibberish in dynamic Japanese text in GuitarFreaks/DrumMania XG2
(K32/K33) when Windows uses a non-Japanese system code page.

XG2 converts UTF-8 property strings through WideCharToMultiByte(CP_ACP)
before rendering. On systems where the active code page is not
Shift-JIS, these strings are converted using the system code page and
become corrupted or turn into question marks.

Spice2x already hooks WideCharToMultiByte and redirects these
conversions to CP932 for newer 64-bit Gitadora models. This change makes
the existing hook available to 32-bit builds and enables it specifically
for XG2 models K32 and K33.

The existing 64-bit Gitadora Arena and T44 conditions remain unchanged.

## Testing
Before
<img width="1280" height="745" alt="image"
src="https://github.com/user-attachments/assets/34aa2c93-89e8-4c4f-b267-6cbc236f19de"
/>
<img width="2054" height="1188" alt="image"
src="https://github.com/user-attachments/assets/d88b5770-5545-4402-b9e7-3126fe048fb9"
/>

After
<img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/d43b4e47-b6af-4b97-8898-53849c9a0ca4"
/>
<img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/884301e2-1edd-4ad2-92ac-ece656853fe7"
/>

- Tested GuitarFreaks XG2 on a non-Japanese (English US) Windows.
- Verified that Community Log preset comments render correctly in
Japanese.
- Verified that Cooperation Challenge descriptions, rewards, and
progress text render correctly.
- Confirmed that the previous mojibake and question marks no longer
appear.
- GitHub Actions build completed successfully for both architectures.
2026-08-12 00:08:05 -07:00
REDCODE 079bb35b1f idea Run-configs
Continuous Integration / Build (push) Successful in 9m2s
2026-08-11 14:11:58 +02:00
REDCODE 95051b6525 Updates to changes of upstream spice 2026-08-11 2026-08-11 14:11:58 +02:00
bicarusandGitHub ce2f93d234 graphics: refactor DX9 screenshot (part 2) (#861) 2026-08-11 01:11:42 -07:00
bicarusandGitHub 13a171f199 sdvx: fix api touch in windowed mode (#862)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #858 

## Description of change
Fix orientation being wrong in windowed mode when API touch is used

Fix mouse no longer working when API client is in use.

This PR only affects windowed mode SDVX.

## Testing
2026-08-10 18:58:48 -07:00
bicarusandGitHub 3da352488e graphics: refactor screenshots (#860)
First commit - pure refactoring, no functional changes

Second commit - clean up & minor bug fix.
2026-08-09 20:47:38 -07:00
bicarusandGitHub 2386047c2f sdvx: fix touchscreen and API touch in landscape mode (#859)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #858 

## Description of change
Landscape mode needs another adjustment applied to touch coordinates.

This fixex both native touch and wintouchemu paths.

## Testing
Tested landscape, also with full screen resolution changes.
2026-08-08 16:22:29 -07:00
bicarusandGitHub baa550037a api: sdvx tape led (#857)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #852

## Description of change
Add SDVX valk cab tape LED output over API

## Testing
tested with custom python script over api
2026-08-07 00:38:17 -07:00
REDCODE ec3ca1a055 Fixed CI to run on gitea
Continuous Integration / Build (push) Successful in 6m54s
2026-08-07 01:53:09 +02:00
REDCODE da77f60b71 fix docker build: missing wget and C++ includes for GCC16
Continuous Integration / Build (push) Failing after 1m0s
- add wget to deps Dockerfile for dsdmo.dll download
- include <mutex> in wmischook.cpp
- include <codecvt> and load SHCreateMemStream dynamically in sndbhook.cpp for XP compat
2026-08-07 00:21:58 +02:00
REDCODE 0c1998abe4 Merge branch 'main' into nac-spice22x
Merge official spice2x 'main', up to b53447b
Notes: dc82c98  is where the nixac fork starts.

- Upstream's OBJECT-library + forbidden-static-DLL-import build refactor
  merged with nixAC's blob/dsdmo resources
- SPICE_XP / runtime large-address-aware detection builds have been
  integrated from upstream
- README.md grabs from upstream; .gitignore keeps nixAC's fork-specific
  entries
2026-08-07 00:21:58 +02:00
REDCODE 64260623bf Align nixAC to spice2x folder structure 2026-08-06 16:03:22 +02:00
[ ] 3ddd34d51d feat: Provide DMO functionality using dsdmo blob
> fetch dsdmo.dll blobs and embed into spicetools
> unpack and register overriding relevant COM server on runtime
> may be replaced in the future, see #12
2026-02-21 20:54:23 +09:00
[ ] ce0344742e refactor/feat: Further signature scan changes
> funcionality cleanup, API compatibility should be the same
> improve patching performance by skipping vector copy (replaced with span)
> chain call wrapper (fixes soudbank hook breaking 32-bit styles)
2026-02-20 18:36:42 +09:00
[ ] a7723148bc chore: Revert upstream
> temporarily disable upstream#471 (should be addressed in #10 and #11)
> disable upstream#466
> disable upstream#474
> noop for upstream#455 (excluded in supported build targets)
> partial revert for upstream#405 (including previous merge commit)
2026-02-19 16:56:51 +09:00
[ ] 78621e5dca Merge branch 'spice2x' into spice22x 2026-02-19 16:05:17 +09:00
[ ] 1d7fe00703 Update to spice2x-26-02-17 (completed)
> adjust cmake.sh (ignore separate linux targets)
> adjust .gitignore
2026-02-19 15:59:06 +09:00
[ ] e923b7918f Update to spice2x-26-02-17 (pre-apply)
> broken commit
2026-02-19 15:41:42 +09:00
[ ] 0834646097 refactor/feat: Signature scan improvements
> use reverse pattern search instead, fixes performance issue for some hooks
> sigscan still requires overhead of module copy for each operation
2026-02-18 07:42:11 +09:00
[ ] 82439962d6 refactor: Soundbank runtime hooking improvement
> change default behaviour to pattern based patch for hooks::soundbank, see #13
> fix broken check for hooks::soundbank unsupported game version
2026-02-17 19:30:01 +09:00
[ ] 97cca85bc8 refactor: Lazy load winscard
> still requires proper testing with hardware
2026-01-22 08:47:14 +09:00
[ ] bb0fd62ec3 feat: Crude SDVX 5 support
> disable lobby video (cause for crashes in lobby), see #9
> suppress dmoeffector log messages, see #9
2026-01-20 08:31:17 +09:00
[ ] 734c762bd6 code/refactor: Deduplicate sigscan code
> cleanup unnecessary code, should break nothing
> probably will cause merge issues someday
> add missing `replace_pattern_from`
2026-01-20 07:56:22 +09:00
[ ] 1cb5b7b52a fix: Sound Voltex 6 pipewire support
> should resolve `-audiobackend` incompatibilities, see #9
2026-01-16 03:32:44 +09:00
[ ] 244f4aa56a refactor/fix: Resolve some wmisc issues
> fix crashes around missing call convention/log hook
> enable wmisc for M39 as well, see #8
2026-01-11 21:11:55 +09:00
[ ] 26bc8fdda5 fix: Solve some missed multi-soundbank song errors
> missed some rarer cases, as long as soundbank is alphanumeric, it should work now
> affected 2dx soundbanks like 01004 and non-digit ones like 28102
2026-01-06 00:53:31 +09:00
[ ] f13c9d0e50 refactor/code: Change hooks::soundbank::snd_bank::resampler_ to user definable
> add support for `extra.sndb_resampler` in linux.json
> revert default resampler back to `bmswac_resampler_s16`
> backward support for 0.2.x
2025-12-22 22:50:50 +09:00
[ ] aa9ea9204b refactor: Enable spicetools logging for bmsound-wine by default
> optional bmsound-wine API until v0.3.0
2025-12-21 09:05:47 +09:00
[ ] ea5d566218 feat: Test spicetools logging bmsound-wine side
> broken commit
> route bmsound messages back to spicetools log
2025-12-20 21:23:22 +09:00
[ ] 56e8e7456b refactor: Cleanup develhook logic
> refactor hooks::devel to work better with hooks::wmisc
> minor naming consistency changes
2025-12-17 00:00:40 +09:00
[ ] 7f63b77ab3 refactor: Unify string hashing
> moved to util/utils.h
2025-12-15 23:53:18 +09:00
[ ] 42689377e6 feat: Resolve long load times before get ready for IIDX
> see #4
> partial improvements for result screen hang, see #2
2025-12-14 23:17:40 +09:00
[ ] 3f7dcd72bb code: Minor changes
> related to previous commit
2025-11-19 20:36:10 +09:00
[ ] e15c689061 feat: Resolve low framerate during song select for IIDX32+
> caused by bad code around thumbnail fetching logic including file checks in every draw call, see #4
> added `hooks::wmisc` for linux specific minor performance fixes and similar
2025-11-19 08:13:37 +09:00
[ ] 6561ea9230 refactor: Add support for LDJ-20250825
> account for changes in IIDX32 onward affecting `hooks::soundbank`
> `hooks::soundbank` should support any ambiguous IIDX32/33 datacodes again
2025-11-12 00:40:42 +09:00
[ ] 2b3071c9a1 Merge branch 'spice2x' into spice22x 2025-11-11 06:00:36 +09:00
[ ] 04fc516d47 Update to spice2x-25-10-07 (completed)
> adjust run configurations
> adjust cmake.sh
> adjust .gitignore
2025-11-11 05:50:32 +09:00
[ ] 652ad41845 Update to spice2x-25-10-07 (pre-apply)
> broken commit
2025-11-11 04:57:26 +09:00
[ ] 2c188f2ac4 refactor: Minor changes
> use bmswac_resampler_s16_ad temporarily (reduces load times)
> fix hook name
2025-11-11 04:33:40 +09:00
[ ] 7b5d86fe65 fix: Fix header size assert
> probably wont ever matter, change for consistency to use `adpcm_ms` header size instead
2025-06-04 22:15:35 +09:00
[ ] 127d07ed31 refactor: Ambiguous game version support for hooks::soundbank
> user supplied sndbhook offset through `prop/linux.json`
> fix tests
2025-06-02 23:34:42 +09:00
[ ] 7ceeda8bca refactor: Handle optional experimental code better
> introduce `hooks::devel` to aggregate debug specific code
> fix support for IIDX31 (target LDJ-010 instead)
2025-06-02 21:34:28 +09:00
[ ] cd709f0a55 fix: Support per difficulty soundbanks
> related to #1
2025-05-30 23:11:47 +09:00
[ ] 07249ebb1c feat: Add support for IIDX25-26 to transcoder
> added support for adpcm_ms wav codec required by these versions
> related to #1
2025-05-29 02:41:23 +09:00
[ ] f0855d9b53 code: Cleanups around added code
> related to #1
2025-05-28 00:27:46 +09:00
[ ] 0b6986e12c fix: stop_subsystems() missing some log messages
> related to #1
2025-05-27 23:30:45 +09:00
[ ] d72f4b780a refactor: Make assertsz_asf() resampler aware
> resampling free conversion support (results in sound data errors, game only expects s16le for wavs)
> related to #1
2025-05-27 23:11:02 +09:00
[ ] 5075efc6dc refactor: Enable unreachable cleanup logic in main()
> refactor `main_implemntation()` cleanup to run once on `launcher::shutdown()`, not sure why it was defined like that (unreachable code, at least for iidx)
> not thread safe, but probably not needed (no obvious failures as well)
> related to #1
2025-05-27 23:08:16 +09:00
[ ] 88a19a8811 refactor/build: Sanitize MF logic
> revert linking to MF libraries on build
> move MF init to be used for tests only
> fix 32-bit builds
> related to #1
2025-05-27 20:49:49 +09:00
[ ] 4a45f4d9c4 refactor: Add transcoding support for more styles
> some styles still won't work due to SOUND DATA CREATE ERROR
> related to #1
2025-05-26 23:14:57 +09:00
[ ] a34014876a feat: Add avs_fs_umount()
> only added for avs 2.17.3.0
> change `avs_fs_mount()` return type to avs_file_t (should be avs_handle_t to begin with)
> related to #1
2025-05-26 20:26:00 +09:00
[ ] 74ef5237ad feat: Integrate linux-side avcodec support
> support only IIDX30
> working full wma=>wav transcoding pipeline with ifs support
> missing unmounting behaviour (will add later)
> depends on latest bmsound_wine for avcodec transcoding
> related to #1
2025-05-25 21:05:24 +09:00
[ ] 933dcf72a9 feat: Add iidx soundbank hook
> mockup using MF decoder (which by itself is broken, it seems)
> working .s3p unpacking and .2dx packing
> use avs filesystem (read only)
> soundbank patching disabled (missing some logic)
> unity games may be broken (linker)
> related to #1
2025-05-22 23:53:19 +09:00
[ ] 8a5f1b5017 build: Fix merge inconsistencies
> revert no longer needed fixes
> update clion configs
2025-05-09 02:56:04 +09:00
[ ] c70e12ff07 Merge branch 'spice2x' into spice22x 2025-05-09 02:54:57 +09:00
[ ] 359716b850 Update to spice2x-25-04-25 (completed)
> adjust run configurations
> adjust cmake.sh
2025-05-09 01:38:46 +09:00
[ ] c94de456b5 Update to spice2x-25-04-25 (pre-apply)
> broken commit
2025-05-07 00:25:31 +09:00
[ ] e72da8ad5f build: Fix building for spice2x-20230929
> resolve third party libraries not building
> force include stdint.h in place of cstdint missing from windows.h now
> to be reverted after updates to spice2x master
2025-05-06 23:52:24 +09:00
[ ] 3da7aa5be0 feat: Update pipewire backend to support the latest bmsound_wine
> minimal necessary implementation for `loopback_signal` bmsound_wine profile
2025-05-05 03:16:01 +09:00
[ ] 40a755173a Merge branch 'spice2x' into spice22x 2023-10-24 20:25:56 +09:00
[ ] 04dee88276 Update to spice2x-23-09-29
> omitted mostly useless path length warning
2023-10-24 20:24:09 +09:00
[ ] 572cf9d12c refactor: Update pipewire backend to the latest version
> support for linux config
> some cleanup around bmsw API usage
2023-10-19 02:49:48 +09:00
[ ] 6c53848ea4 feat: First working pipewire endpoint
> uses `T_NOTIF_SPICE` experimental endpoint on bmsound-pw's side
> latency should be on par with wasapi shared?
2023-10-09 01:55:17 +09:00
[ ] 32f4f1871d feat: Add global access for selected Game object
> separated commit in case this functionality is already provided through other means and needs to be reverted
> probably target for rehaul in the future for better/less invasive approach
2023-10-02 23:56:13 +09:00
[ ] f6f62e0e9d feat: Add pipewire backend
> only for testing current bmsound_wine implementation wine-side
2023-10-02 23:51:45 +09:00
[ ] 15cd228b8d feat: Dummy none backend
> to run games with no sound
2023-09-28 20:21:06 +09:00
[ ] 5951a550bb refactor: Bad attempt of fixing waveout
> tried to unify use of hooks::audio::FORMAT between backends
2023-09-26 05:20:02 +09:00
[ ] a0e44f9e4b build: Add .idea project files tracking 2023-09-26 05:09:15 +09:00
[ ] 90ebf58554 build: Add .idea project files tracking 2023-09-25 00:31:06 +09:00
[ ] a3f2d56a9e build: Add cmake.sh wrapper 2023-09-20 04:06:02 +09:00
[ ] d03cca1fe2 Update to spice2x-23-06-08 2023-09-20 02:09:17 +09:00
smpn2 8957d9d446 Apply spicetools-22-10-21 2022-12-16 23:19:02 +09:00
smpn2 6c914266d9 chore: CRLF to LF 2022-12-16 23:18:35 +09:00
smpn2 b7a60638a4 Let us start here, from square one. No, from Zero! 2022-09-30 22:45:51 +09:00
smpn2 14215af0d1 Initial commit 2022-09-30 22:44:38 +09:00
210 changed files with 35780 additions and 26886 deletions
+46
View File
@@ -0,0 +1,46 @@
# Normalize every text file to LF in the repository.
# The build runs under Linux/MinGW containers, so LF is also used in the
# working tree; Windows editors and toolchains handle LF fine.
* text=auto eol=lf
# Windows-only files that must keep CRLF in the working tree.
*.bat text eol=crlf
*.cmd text eol=crlf
*.sln text eol=crlf
*.vcproj text eol=crlf
*.vcxproj text eol=crlf
*.props text eol=crlf
*.filters text eol=crlf
# Files that must keep LF even if a Windows editor rewrites them.
*.sh text eol=lf
*.in text eol=lf
*.cmake text eol=lf
*.mk text eol=lf
Makefile text eol=lf
Dockerfile text eol=lf
# Binary files - never touch the contents.
*.bin binary
*.ico binary
*.ttf binary
*.otf binary
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.bmp binary
*.zip binary
*.7z binary
*.gz binary
*.dll binary
*.exe binary
*.lib binary
*.a binary
*.o binary
*.obj binary
*.pdb binary
# Vendored code is stored and checked out byte-for-byte as upstream ships it,
# so re-importing a library never produces line-ending-only diffs.
src/spice2x/external/** -text
+33 -31
View File
@@ -1,32 +1,34 @@
on: [push, pull_request] on: [push, pull_request]
name: Continuous Integration name: Continuous Integration
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
fw-ci: fw-ci:
name: Build name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
working-directory: ./src/spice2x working-directory: ./src/spice2x
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Set ccache environment variables - name: Calculate commit SHA
run: | id: vars
echo "CCACHE_DIR=${{ github.workspace }}/src/spice2x/.ccache" >> $GITHUB_ENV run: |
- name: Install ccache calculatedSha=$(git rev-parse --short ${{ github.sha }})
uses: hendrikmuhs/ccache-action@v1.2.23 echo "sha=$calculatedSha" >> "$GITHUB_OUTPUT"
- name: Calculate commit SHA echo "bin=${{ github.workspace }}/src/spice2x/bin" >> "$GITHUB_OUTPUT"
id: vars - name: Compile
run: | run: |
calculatedSha=$(git rev-parse --short ${{ github.sha }}) docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
- name: Compile docker run --name spice-build spicetools/spice:latest
run: ./build_docker.sh mkdir -p "$PWD/bin"
- uses: actions/upload-artifact@v6 docker cp spice-build:/src/src/spice2x/bin/. "$PWD/bin"
with: docker rm spice-build
name: spice2x-ci-${{ env.COMMIT_SHORT_SHA }} - uses: actions/upload-artifact@v3
path: src/spice2x/bin with:
name: spice2x-ci-${{ steps.vars.outputs.sha }}
path: ${{ steps.vars.outputs.bin }}
if-no-files-found: error if-no-files-found: error
+49 -49
View File
@@ -1,49 +1,49 @@
name: Draft Release name: Draft Release
on: on:
workflow_dispatch: workflow_dispatch:
permissions: permissions:
contents: write contents: write
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
release: release:
name: Build and Draft Release name: Build and Draft Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
working-directory: ./src/spice2x working-directory: ./src/spice2x
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
with: with:
ref: main ref: main
fetch-depth: 0 fetch-depth: 0
- name: Clean leftover build artifacts - name: Clean leftover build artifacts
run: | run: |
rm -rf .ccache dist bin cmake-build-* rm -rf .ccache dist bin cmake-build-*
- name: Compile - name: Compile
run: ./build_docker.sh run: ./build_docker.sh
- name: Determine release name from dist filename - name: Determine release name from dist filename
run: | run: |
dist=$(basename "$(ls dist/spice2x-*.zip | grep -v -- '-full.zip')") dist=$(basename "$(ls dist/spice2x-*.zip | grep -v -- '-full.zip')")
# strip the ".zip" to get the base name, e.g. spice2x-26-06-28 # strip the ".zip" to get the base name, e.g. spice2x-26-06-28
name="${dist%.zip}" name="${dist%.zip}"
# the tag is the date portion, e.g. 26-06-28 # the tag is the date portion, e.g. 26-06-28
tag="${name#spice2x-}" tag="${name#spice2x-}"
echo "RELEASE_NAME=$name" >> $GITHUB_ENV echo "RELEASE_NAME=$name" >> $GITHUB_ENV
echo "RELEASE_TAG=$tag" >> $GITHUB_ENV echo "RELEASE_TAG=$tag" >> $GITHUB_ENV
- name: Create draft release - name: Create draft release
uses: softprops/action-gh-release@v3 uses: softprops/action-gh-release@v3
with: with:
draft: true draft: true
prerelease: true prerelease: true
tag_name: ${{ env.RELEASE_TAG }} tag_name: ${{ env.RELEASE_TAG }}
name: ${{ env.RELEASE_NAME }} name: ${{ env.RELEASE_NAME }}
target_commitish: main target_commitish: main
generate_release_notes: true generate_release_notes: true
files: | files: |
src/spice2x/dist/spice2x-*.zip src/spice2x/dist/spice2x-*.zip
+14 -1
View File
@@ -1,7 +1,6 @@
.vscode/* .vscode/*
# JetBrains IDEs # JetBrains IDEs
.idea
.idea_modules .idea_modules
*.iws *.iws
@@ -11,3 +10,17 @@ cmake-build*
# user config # user config
build_all.local.sh build_all.local.sh
build_docker.local.sh build_docker.local.sh
# output from build script
bin/*
dist/*
build/*.dll
external/cv2pdb/*
.ccache/*
# Visual Studio
.vs
out
CMakeSettings.json
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Unnecessary plugin data
/markdown-navigator.xml
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSharedSettings">
<configurations>
<configuration PROFILE_NAME="Release_x64[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x64/Release" CONFIG_NAME="Release" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake&quot;" />
<configuration PROFILE_NAME="Release_x86[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x86/Release" CONFIG_NAME="Release" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake&quot;" />
<configuration PROFILE_NAME="Debug_x64[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x64/Debug" CONFIG_NAME="Debug" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake&quot;" />
<configuration PROFILE_NAME="Debug_x86[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x86/Debug" CONFIG_NAME="Debug" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake&quot;" />
</configurations>
</component>
</project>
+350
View File
@@ -0,0 +1,350 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BackendCodeEditorSettings">
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CDeclarationWithImplicitIntType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConstevalIfIsAlwaysConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractClassWithoutSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractVirtualFunctionCallInCtor/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAccessSpecifierWithNoDeclarations/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAwaiterTypeIsNotClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBooleanIncrementExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatBadCode/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatLegacyCode/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatMixedArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooFewArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCompileTimeConstantCanBeReplacedWithBooleanConstant/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConceptNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConditionalExpressionCanBeSimplified/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstValueFunctionReturnType/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstexprIfDiscardedBranch/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCoroutineCallResolveError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantConditions/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFADeletedPointer/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInvalidatedMemory/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesFunction/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesScope/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALoopConditionNotUpdated/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAMemoryLeak/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANotInitializedField/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANullDereference/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesLocal/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationSpecifierWithoutDeclarators/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorDisambiguatedAsFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorUsedBeforeInitialization/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultCaseNotHandledInSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultInitializationWithNoUserConstructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultIsUsedAsIdentifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultedSpecialMemberFunctionIsImplicitlyDeleted/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefinitionsOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeletingVoidPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTemplateWithoutTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTypeWithoutTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedEntity/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedRegisterStorageClassSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDereferenceOperatorLimitExceeded/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDiscardedPostfixOperatorResult/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenSyntaxError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUndocumentedParameter/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUnresolvedReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEmptyDeclaration/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersOrder/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersPlacement/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceDoStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceForStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceFunctionDeclarationStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceIfStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceNestedNamespacesStyle/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingDestructorStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingFunctionStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceTypeAliasCodeStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceWhileStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityAssignedButNoRead/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityUsedOnlyInUnevaluatedContext/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnumeratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEqualOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEvaluationFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExplicitSpecializationInNonNamespaceScope/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExpressionWithoutSideEffects/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalFunctionInFinalClass/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalNonOverridingVirtualFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForLoopCanBeReplacedWithWhile/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForwardEnumDeclarationWithoutUnderlyingType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionDoesntReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionResultShouldBeUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionalStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHeaderHasBeenAlreadyIncluded/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHiddenFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHidingFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIdenticalOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIfCanBeReplacedByConstexprIf/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberInitializersOrder/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMismatchedClassTags/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingIncludeGuard/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingKeywordThrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfClassRValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtBindingRValueToLvalueReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtCopyElisionInCopyInitDeclarator/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtDoubleUserConversionInCopyInit/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtNotInitializedStaticConstLocalVar/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtReinterpretCastFromNullptr/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterWideLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMustBePublicVirtualToImplementInterface/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMutableSpecifierOnReferenceMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNoDiscardExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNodiscardFunctionWithoutReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExceptionSafeResourceAcquisition/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConversionOperator/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConvertingConstructor/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineFunctionDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineVariableDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOverrideWithDifferentVisibility/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPassValueParameterByConstReference/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerConversionDropsQualifiers/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerToIntegralConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPolymorphicClassWithNonVirtualPublicDestructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyErroneousEmptyStatements/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUnintendedObjectSlicing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderIsNotIncluded/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderNotFound/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfBadFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfExtraArg/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfMissedArg/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfRiskyFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrivateSpecialMemberFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRangeBasedForIncompatibleReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedefinitionOfDefaultArgumentInOverrideFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBooleanExpressionArgument/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantCastExpression/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantComplexityInComparison/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConditionalExpression/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConstSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantControlFlowJump/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantDereferencingAndTakingAddress/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElaboratedTypeSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeyword/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeywordInsideCompoundStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyDeclaration/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantExportKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantInlineSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantLambdaParameterList/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantMemberInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantNamespaceDefinition/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantParentheses/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnMemberAllocationFunction/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnThreadLocalLocalVariable/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateArguments/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantVoidArgumentList/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantZeroInitializerInAggregateInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReferenceToOverriddenVirtualFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReinterpretCastFromVoidPtr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRemoveRedundantBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceMemsetWithZeroInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceTieWithStructuredBinding/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReturnNoValueInNonVoidFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSmartPointerVsMakeFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSomeObjectMembersMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSpecialFunctionWithoutNoexceptSpecification/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticAssertFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppThrowExpressionCanBeReplacedWithRethrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScope/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScopeInitStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTypeAliasNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedDependentBaseClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedNonStaticDataMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnionMemberOfReferenceType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAssociativeContains/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAuto/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAutoForNumeric/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseDesignatedInitializers/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseElementsView/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseEraseAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseFamiliarTemplateSyntaxForGenericLambdas/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseInternalLinkage/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseRangeAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStdSize/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStructuredBinding/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseTypeTraitAlias/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUserDefinedLiteralSuffixDoesNotStartWithUnderscore/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUsingResultOfAssignmentAsCondition/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVariableCanBeMadeConstexpr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionCallInsideCtor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionInFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVolatileParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWarningDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongIncludesOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongSlashesInIncludeDirective/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroConstantCanBeReplacedWithNullptr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroValuedExpressionUsedAsNullPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppClangFormat/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_ARGUMENT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXPRESSION/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_FOR_STMT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_PARAMETER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_ARGUMENT/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_PARAMETER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTIPLE_DECLARATION/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_TERNARY/@EntryValue" value="ALIGN_ALL" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_CLASS_DEFINITION/@EntryValue" value="1" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_DECLARATIONS/@EntryValue" value="0" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DECLARATION/@EntryValue" value="1" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DEFINITION/@EntryValue" value="1" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BREAK_TEMPLATE_DECLARATION/@EntryValue" value="LINE_BREAK" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CONTINUOUS_LINE_INDENT/@EntryValue" value="Double" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_ACCESS_SPECIFIERS_FROM_CLASS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CLASS_MEMBERS_FROM_ACCESS_SPECIFIERS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_COMMENT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_SIZE/@EntryValue" value="4" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_STYLE/@EntryValue" value="Space" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INITIALIZER_BRACES/@EntryValue" value="END_OF_LINE_NO_SPACE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_EQ/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue" value="2" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue" value="2" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_USER_LINEBREAKS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/LINE_BREAK_AFTER_COLON_IN_MEMBER_INITIALIZER_LISTS/@EntryValue" value="ON_SINGLE_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/MEMBER_INITIALIZER_LIST_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_INDENTATION/@EntryValue" value="All" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_NAMESPACE_DEFINITIONS_ON_SAME_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_BLOCK_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_PARAMS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_COLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_SEMICOLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_METHOD/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_NESTED_DECLARATOR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_METHOD/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_UNARY_OPERATOR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_COLON/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_SEMICOLON/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_METHOD/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_METHOD/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BETWEEN_CLOSING_ANGLE_BRACKETS_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_ARRAY_ACCESS_BRACKETS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_DECLARATION_PARENTHESES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_BLOCKS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_METHOD_PARENTHESES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPECIAL_ELSE_IF_TREATMENT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TAB_WIDTH/@EntryValue" value="4" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_BINARY_OPSIGN/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_ARGUMENTS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_TERNARY_OPSIGNS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_PARAMETERS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
<option name="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
</component>
</project>
+72
View File
@@ -0,0 +1,72 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ConstantConditionsOC" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyBroadExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="2.7" />
<item index="1" class="java.lang.String" itemvalue="3.7" />
<item index="2" class="java.lang.String" itemvalue="3.8" />
<item index="3" class="java.lang.String" itemvalue="3.9" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyDefaultArgumentInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E266" />
<option value="E701" />
<option value="E265" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N806" />
<option value="N803" />
<option value="N802" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyTypeCheckerInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnusedLocalInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ShellCheck" enabled="true" level="ERROR" enabled_by_default="true">
<shellcheck_settings value="SC2164" />
</inspection_tool>
<inspection_tool class="Simplify" enabled="false" level="WARNING" enabled_by_default="false">
<option name="clangTidyCheckOptions">
<list>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyConstantConditions" />
<option name="optionValue" value="1" />
</ClangTidyCheckOption>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyIfWithReturn" />
<option name="optionValue" value="0" />
</ClangTidyCheckOption>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyEqualToTrueFalse" />
<option name="optionValue" value="1" />
</ClangTidyCheckOption>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyTernaryWithConstantBranch" />
<option name="optionValue" value="1" />
</ClangTidyCheckOption>
</list>
</option>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownEnhProjectSettings">
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
</project>
+71
View File
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FlexmarkProjectSettings">
<FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false">
<flexmarkSectionLanguages>
<option name="1" value="Markdown" />
<option name="2" value="HTML" />
<option name="3" value="flexmark-ast:1" />
</flexmarkSectionLanguages>
</FlexmarkHtmlSettings>
</component>
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" lastLayoutSetsDefault="false">
<PanelProvider>
<provider providerId="com.vladsch.md.nav.editor.swing.html.panel" providerName="Default - Swing" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0">
<PegdownExtensions>
<option name="ATXHEADERSPACE" value="true" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" />
<option name="RELAXEDHRULES" value="true" />
<option name="STRIKETHROUGH" value="true" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
</PegdownExtensions>
<ParserOptions>
<option name="COMMONMARK_LISTS" value="true" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="PRODUCTION_SPEC_PARSER" value="true" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="false" addAnchorLinks="false" anchorLinksWrapText="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" defaultUrlTitle="false" migratedPlantUml="true" migratedAnchorLinks="true" plantUmlConversion="0">
<GeneratorProvider>
<provider providerId="com.vladsch.md.nav.editor.text.html.generator" providerName="Unmodified HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
<fencedCodeConversions>
<option name="c4plantuml" value="NONE" />
<option name="ditaa" value="NONE" />
<option name="erd" value="NONE" />
<option name="graphviz" value="NONE" />
<option name="latex" value="KATEX" />
<option name="math" value="KATEX" />
<option name="mermaid" value="NONE" />
<option name="nomnoml" value="NONE" />
<option name="plantuml" value="NONE" />
<option name="puml" value="NONE" />
<option name="svgbob" value="NONE" />
<option name="umlet" value="NONE" />
<option name="vega" value="NONE" />
<option name="vegalite" value="NONE" />
<option name="wavedrom" value="NONE" />
</fencedCodeConversions>
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.md.nav.editor.text.html.css" providerName="No Stylesheet" />
</StylesheetProvider>
<ScriptProviders />
<cssText />
<cssUriHistory />
</CssSettings>
</component>
</project>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakePythonSetting">
<option name="pythonIntegrationState" value="YES" />
</component>
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$/src/spice2x">
<contentRoot DIR="$PROJECT_DIR$" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/spicetools.iml" filepath="$PROJECT_DIR$/.idea/spicetools.iml" />
</modules>
</component>
</project>
+17
View File
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clion[Init]" type="ShConfigurationType" activateToolWindowBeforeRun="false">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/spice2x/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="clion init" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$/src/spice2x/" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</component>
+23
View File
@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Debug_x64]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/spice2x/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="true" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_spice64" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Debug_x64[WIN_32]" />
</method>
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Debug_x86]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/spice2x/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="true" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_cfg" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Debug_x86[WIN_32]" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_spice" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Debug_x86[WIN_32]" />
</method>
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Extra_x64]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/spice2x/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_stubs_nvEncodeAPI64" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_stubs_nvcuda" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_stubs_nvcuvid" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
</method>
</configuration>
</component>
+23
View File
@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Extra_x86]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/spice2x/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_stubs_cpusbxpkm" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_spice_laa" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
</method>
</configuration>
</component>
+23
View File
@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Release_x64]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/spice2x/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_spice64" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
</method>
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Release_x86]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/spice2x/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_cfg" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_spice" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
</method>
</configuration>
</component>
+25
View File
@@ -0,0 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distribute[Release]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/spice2x/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="distribute all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Release_x64]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Release_x86]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Extra_x64]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Extra_x86]" run_configuration_type="ShConfigurationType" />
</method>
</configuration>
</component>
+9
View File
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_cfg" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Project" TARGET_NAME="spicetools_cfg" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+9
View File
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_spice" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Project" TARGET_NAME="spicetools_spice" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+9
View File
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_spice64" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Project" TARGET_NAME="spicetools_spice64" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt64" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld64" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+9
View File
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_spice_laa" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Project" TARGET_NAME="spicetools_spice_laa" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_cpusbxpkm" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Project" TARGET_NAME="spicetools_stubs_cpusbxpkm" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_nvEncodeAPI64" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Project" TARGET_NAME="spicetools_stubs_nvEncodeAPI64" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_nvcuda" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Project" TARGET_NAME="spicetools_stubs_nvcuda" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_nvcuvid" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Project" TARGET_NAME="spicetools_stubs_nvcuvid" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CIDR" type="CPP_MODULE" version="4" />
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+87 -11
View File
@@ -124,8 +124,6 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
else() else()
# warnings as errors
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
# warnings # warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff
@@ -256,6 +254,57 @@ add_subdirectory(external/imgui EXCLUDE_FROM_ALL)
add_subdirectory(external/minhook EXCLUDE_FROM_ALL) add_subdirectory(external/minhook EXCLUDE_FROM_ALL)
add_subdirectory(external/cpu_features EXCLUDE_FROM_ALL) add_subdirectory(external/cpu_features EXCLUDE_FROM_ALL)
# libjpeg-turbo, prebuilt into the deps image. The WinXP toolchains have their own
# sysroot and cannot see it, so those targets build without JPEG support.
add_library(spice_jpeg INTERFACE)
if(NOT SPICE_XP)
# search static archives only: the mingw package also ships an import library,
# and linking that one would pull in a libjpeg DLL at runtime
set(SPICE_JPEG_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX})
find_package(JPEG)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${SPICE_JPEG_SUFFIXES})
if(JPEG_FOUND)
target_link_libraries(spice_jpeg INTERFACE JPEG::JPEG)
target_compile_definitions(spice_jpeg INTERFACE SPICE_JPEG=1)
else()
message(WARNING
"libjpeg-turbo not found: screen capture over the API is disabled")
endif()
endif()
# x264 for the API H.264 video stream, installed into the mingw sysroots by the
# deps image. The WinXP toolchains deliberately go without it and serve MJPEG only.
add_library(spice_x264 INTERFACE)
if(NOT SPICE_XP)
# search static archives only: the mingw package also ships an import library,
# and linking that one would pull in a libx264 DLL at runtime
set(SPICE_X264_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX})
find_library(X264_LIBRARY NAMES x264 libx264)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${SPICE_X264_SUFFIXES})
find_path(X264_INCLUDE_DIR NAMES x264.h)
if(X264_LIBRARY AND X264_INCLUDE_DIR)
target_include_directories(spice_x264 INTERFACE "${X264_INCLUDE_DIR}")
target_link_libraries(spice_x264 INTERFACE "${X264_LIBRARY}")
target_compile_definitions(spice_x264 INTERFACE SPICE_H264=1)
else()
message(WARNING
"x264 not found: the api video stream will only offer MJPEG")
endif()
endif()
# fpng's SIMD needs the whole unit built for SSE4.1, which its runtime CPU check
# cannot undo, so keep it scalar rather than raising the CPU baseline
set_source_files_properties(external/fpng/fpng.cpp PROPERTIES
COMPILE_DEFINITIONS "FPNG_NO_SSE=1")
if(NOT MSVC)
set_source_files_properties(external/fpng/fpng.cpp PROPERTIES
COMPILE_OPTIONS "-fno-strict-aliasing")
endif()
# set link time optimizations (disabled for Debug builds for speed, disabled # set link time optimizations (disabled for Debug builds for speed, disabled
# for RelWithDebInfo builds due to "lto1: error: two or more sections for" # for RelWithDebInfo builds due to "lto1: error: two or more sections for"
# errors) # errors)
@@ -273,7 +322,20 @@ set_source_files_properties(cfg/manifest.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC) set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC) set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC) set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/blob.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC) set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC)
add_custom_command(
OUTPUT build/dsdmo.i686.dll
COMMAND sh -c "wget -qO- 'https://web.archive.org/web/20260212204718if_/https://download.zip.dll-files.com/d01cab97ad497201c4ad99eb6e4182b3/dsdmo.zip?token=f7cVie5KOSz-hnFUYC37Ow&expires=1770974229' | bsdtar -xvf - -C '${CMAKE_SOURCE_DIR}/build' -s '/dsdmo.dll/dsdmo.i686.dll/' dsdmo.dll"
COMMENT "Fetching dsdmo.dll blob (i686)"
VERBATIM
)
add_custom_command(
OUTPUT build/dsdmo.x86_64.dll
COMMAND sh -c "wget -qO- 'https://web.archive.org/web/20260212203516if_/https://download.zip.dll-files.com/2627c69b89807078f5fdf63ee104dddf/dsdmo.zip?token=1rLcDAA2LFA6YRyinX0GXg&expires=1770973374' | bsdtar -xvf - -C '${CMAKE_SOURCE_DIR}/build' -s '/dsdmo.dll/dsdmo.x86_64.dll/' dsdmo.dll"
COMMENT "Fetching dsdmo.dll blob (x86_64)"
VERBATIM
)
# sources # sources
######### #########
@@ -317,6 +379,10 @@ set(SOURCE_FILES ${SOURCE_FILES}
# api # api
api/controller.cpp api/controller.cpp
api/websocket.cpp api/websocket.cpp
api/capture_pump.cpp
api/h264_stream.cpp
api/stream_format.cpp
api/stream_server.cpp
api/request.cpp api/request.cpp
api/response.cpp api/response.cpp
api/module.cpp api/module.cpp
@@ -332,6 +398,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
api/modules/control.cpp api/modules/control.cpp
api/modules/touch.cpp api/modules/touch.cpp
api/modules/iidx.cpp api/modules/iidx.cpp
api/modules/sdvx.cpp
api/serial.cpp api/serial.cpp
api/modules/drs.cpp api/modules/drs.cpp
api/modules/lcd.cpp api/modules/lcd.cpp
@@ -379,7 +446,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
external/tinyxml2/tinyxml2.cpp external/tinyxml2/tinyxml2.cpp
external/http-parser/http_parser.c external/http-parser/http_parser.c
external/usbhidusage/usb-hid-usage.c external/usbhidusage/usb-hid-usage.c
external/toojpeg/toojpeg.cpp external/fpng/fpng.cpp
external/scard/scard.cpp external/scard/scard.cpp
# games # games
@@ -525,16 +592,24 @@ set(SOURCE_FILES ${SOURCE_FILES}
hooks/audio/backends/wasapi/util.cpp hooks/audio/backends/wasapi/util.cpp
hooks/audio/implementations/asio.cpp hooks/audio/implementations/asio.cpp
hooks/audio/implementations/wave_out.cpp hooks/audio/implementations/wave_out.cpp
hooks/audio/implementations/none.cpp
hooks/audio/implementations/pipewire.cpp
hooks/develhook.cpp
hooks/sndbhook.cpp
hooks/wmischook.cpp
hooks/avshook.cpp hooks/avshook.cpp
hooks/cfgmgr32hook.cpp hooks/cfgmgr32hook.cpp
hooks/debughook.cpp hooks/debughook.cpp
hooks/devicehook.cpp hooks/devicehook.cpp
hooks/graphics/graphics.cpp hooks/graphics/graphics.cpp
hooks/graphics/graphics_windowed.cpp hooks/graphics/graphics_windowed.cpp
hooks/graphics/jpeg_encoder.cpp
hooks/graphics/nvapi_impl.cpp hooks/graphics/nvapi_impl.cpp
hooks/graphics/nvapi_hook.cpp hooks/graphics/nvapi_hook.cpp
hooks/graphics/nvenc_hook.cpp hooks/graphics/nvenc_hook.cpp
hooks/graphics/backends/d3d9/d3d9_backend.cpp hooks/graphics/backends/d3d9/d3d9_backend.cpp
hooks/graphics/backends/d3d9/d3d9_readback.cpp
hooks/graphics/backends/d3d9/d3d9_screenshot.cpp
hooks/graphics/backends/d3d9/d3d9_device.cpp hooks/graphics/backends/d3d9/d3d9_device.cpp
hooks/graphics/backends/d3d9/d3d9_gfdm.cpp hooks/graphics/backends/d3d9/d3d9_gfdm.cpp
hooks/graphics/backends/d3d9/d3d9_live2d.cpp hooks/graphics/backends/d3d9/d3d9_live2d.cpp
@@ -576,6 +651,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
misc/device.cpp misc/device.cpp
misc/eamuse.cpp misc/eamuse.cpp
misc/extdev.cpp misc/extdev.cpp
misc/hotkeys.cpp
misc/sciunit.cpp misc/sciunit.cpp
misc/sde.cpp misc/sde.cpp
misc/wintouchemu.cpp misc/wintouchemu.cpp
@@ -737,7 +813,7 @@ endfunction()
add_library(spicetools_spice_objs OBJECT ${SOURCE_FILES}) add_library(spicetools_spice_objs OBJECT ${SOURCE_FILES})
target_link_libraries(spicetools_spice_objs target_link_libraries(spicetools_spice_objs
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264)
target_link_libraries(spicetools_spice_objs PUBLIC winscard) target_link_libraries(spicetools_spice_objs PUBLIC winscard)
if(NOT MSVC) if(NOT MSVC)
@@ -747,7 +823,7 @@ endif()
# spice.exe # spice.exe
########### ###########
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc) set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc build/blob.rc build/dsdmo.i686.dll)
add_executable(spicetools_spice ${RESOURCE_FILES}) add_executable(spicetools_spice ${RESOURCE_FILES})
target_link_libraries(spicetools_spice PRIVATE spicetools_spice_objs) target_link_libraries(spicetools_spice PRIVATE spicetools_spice_objs)
set_target_properties(spicetools_spice PROPERTIES PREFIX "") set_target_properties(spicetools_spice PROPERTIES PREFIX "")
@@ -760,7 +836,7 @@ endif()
# spice_laa.exe # spice_laa.exe
########### ###########
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc) set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc build/blob.rc build/dsdmo.i686.dll)
add_executable(spicetools_spice_laa ${RESOURCE_FILES}) add_executable(spicetools_spice_laa ${RESOURCE_FILES})
target_link_libraries(spicetools_spice_laa PRIVATE spicetools_spice_objs) target_link_libraries(spicetools_spice_laa PRIVATE spicetools_spice_objs)
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "") set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
@@ -777,7 +853,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/W
add_executable(spicetools_spice_linux ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_spice_linux ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice_linux target_link_libraries(spicetools_spice_linux
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264)
set_target_properties(spicetools_spice_linux PROPERTIES PREFIX "") set_target_properties(spicetools_spice_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_spice_linux PROPERTIES OUTPUT_NAME "spice_linux") set_target_properties(spicetools_spice_linux PROPERTIES OUTPUT_NAME "spice_linux")
target_compile_definitions(spicetools_spice_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1) target_compile_definitions(spicetools_spice_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
@@ -789,13 +865,13 @@ endif()
# spice64.exe # spice64.exe
############# #############
set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg/Win32D.rc) set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg/Win32D.rc build/blob.rc build/dsdmo.x86_64.dll)
add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break # do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
target_link_libraries(spicetools_spice64 target_link_libraries(spicetools_spice64
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2 PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264)
target_link_libraries(spicetools_spice64 PUBLIC winscard) target_link_libraries(spicetools_spice64 PUBLIC winscard)
set_target_properties(spicetools_spice64 PROPERTIES PREFIX "") set_target_properties(spicetools_spice64 PROPERTIES PREFIX "")
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64") set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
@@ -818,7 +894,7 @@ add_executable(spicetools_spice64_linux ${SOURCE_FILES} ${RESOURCE_FILES})
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break # do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
target_link_libraries(spicetools_spice64_linux target_link_libraries(spicetools_spice64_linux
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2 PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features spice_jpeg spice_x264)
set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "") set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux") set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux")
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1) target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1)
@@ -837,10 +913,10 @@ endif()
set(SOURCE_FILES ${SOURCE_FILES} launcher/options.h launcher/options.cpp) set(SOURCE_FILES ${SOURCE_FILES} launcher/options.h launcher/options.cpp)
set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc) set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc)
add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
# the configurator serves neither the API nor the video stream, so it needs no codecs
target_link_libraries(spicetools_cfg target_link_libraries(spicetools_cfg
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_cfg PUBLIC winscard)
set_target_properties(spicetools_cfg PROPERTIES PREFIX "") set_target_properties(spicetools_cfg PROPERTIES PREFIX "")
set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg") set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg")
target_compile_definitions(spicetools_cfg PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1) target_compile_definitions(spicetools_cfg PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1)
+50
View File
@@ -267,6 +267,20 @@ which also means that your hex edits are applicable directly.
- `Side Panel Right Inner` - `Side Panel Right Inner`
- `Side Panel Right` - `Side Panel Right`
#### SDVX
- tapeled_get(name: str, ...)
- returns a list containing a dict of the current tape LED states. The dict keys are:
- `Title`
- `Upper Left Speaker`
- `Upper Right Speaker`
- `Left Wing`
- `Right Wing`
- `Control Panel`
- `Lower Left Speaker`
- `Lower Right Speaker`
- `Woofer`
- `V Unit`
#### LCD #### LCD
- info() - info()
- returns information about the serial LCD controller some games use - returns information about the serial LCD controller some games use
@@ -277,6 +291,42 @@ which also means that your hex edits are applicable directly.
- image_resize_set_scene(scene: int) - image_resize_set_scene(scene: int)
- sets the active scene for image resize state; set to 0 to disable resize - sets the active scene for image resize state; set to 0 to disable resize
## Video Stream
Separate from the JSON API, spice can serve the mirrored screen as a video
stream over plain HTTP. Enable it with `-apistream`. It listens on the API port
plus two, in the same way the WebSocket server uses the API port plus one, so
`-api 1337` puts the stream on 1339. This means `-api` has to be enabled too.
Two formats are served:
http://host:1339/stream.mjpg JPEG frames, multipart/x-mixed-replace
http://host:1339/stream.h264 H.264 annex-b, no container
All accept the same optional query parameters:
- `screen` - which screen to mirror, 0-3. Defaults to the subscreen when the
game has one, otherwise the main screen.
- `fps` - frames per second, 1-60. Default 30.
- `q` - quality, 1-100. Default 70. This is the JPEG quality for `stream.mjpg`
and is mapped onto the H.264 rate factor for `stream.h264`, so the same number
does not mean the same thing for both.
For example:
http://host:1339/stream.h264?screen=1&fps=30&q=70
See the wiki for format tradeoffs, latency tuning, testing commands and client
notes.
The stream is view only. Touch and other input still go through the JSON API,
so a companion app needs both. There is no authentication on the stream port -
anyone who can reach it can watch the screen.
WinXP builds have no video stream. Neither encoder is compiled in, so every
endpoint returns 404, and the JSON API's JPEG screen capture is unavailable for
the same reason.
## Native wrapper libraries ## Native wrapper libraries
Spicetools provides wrapper libraries in: Arduino, C++, Dart, and Python. Spicetools provides wrapper libraries in: Arduino, C++, Dart, and Python.
Python is the only one that is fully spec compliant. Python is the only one that is fully spec compliant.
+58
View File
@@ -0,0 +1,58 @@
#include "capture_pump.h"
#include <array>
#include <mutex>
#include "hooks/graphics/graphics.h"
namespace api::capture_pump {
namespace {
std::array<std::mutex, GRAPHICS_CAPTURE_SCREEN_NO> CONSUMER_M;
std::mutex CLAIMED_M;
std::array<bool, GRAPHICS_CAPTURE_SCREEN_NO> CLAIMED {};
bool valid_screen(int screen) {
return 0 <= screen && screen < static_cast<int>(GRAPHICS_CAPTURE_SCREEN_NO);
}
}
bool capture_direct(int screen, std::shared_ptr<uint8_t[]> &out, int divide,
uint64_t *timestamp, int *width, int *height) {
if (!valid_screen(screen)) {
return false;
}
std::lock_guard<std::mutex> lock(CONSUMER_M[screen]);
graphics_capture_trigger(screen);
return graphics_capture_receive_raw(
screen, out, divide, timestamp, width, height);
}
bool claim_screen(int screen) {
if (!valid_screen(screen)) {
return false;
}
std::lock_guard<std::mutex> lock(CLAIMED_M);
if (CLAIMED[screen]) {
return false;
}
CLAIMED[screen] = true;
return true;
}
void release_screen(int screen) {
if (!valid_screen(screen)) {
return;
}
std::lock_guard<std::mutex> lock(CLAIMED_M);
CLAIMED[screen] = false;
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
#include <memory>
namespace api::capture_pump {
struct Frame {
// packed 24bpp RGB, width * height * 3 bytes
std::shared_ptr<uint8_t[]> pixels;
uint64_t timestamp = 0;
int width = 0;
int height = 0;
};
// the graphics layer has one capture slot per screen, so concurrent waiters would steal
// each other's frames; everything that captures goes through here to keep it serialized
bool capture_direct(int screen, std::shared_ptr<uint8_t[]> &out, int divide,
uint64_t *timestamp = nullptr, int *width = nullptr, int *height = nullptr);
// a screen carries one stream at a time; false when another connection already holds it
bool claim_screen(int screen);
void release_screen(int screen);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <atomic>
#include <cstdint>
namespace api {
extern std::atomic_uint32_t CLIENT_COUNT;
inline bool has_clients() {
return CLIENT_COUNT.load(std::memory_order_relaxed) > 0;
}
}
+9
View File
@@ -5,6 +5,7 @@
#include <utility> #include <utility>
#include "client.h"
#include "cfg/configurator.h" #include "cfg/configurator.h"
#include "external/rapidjson/document.h" #include "external/rapidjson/document.h"
#include "util/crypt.h" #include "util/crypt.h"
@@ -28,6 +29,7 @@
#include "modules/lcd.h" #include "modules/lcd.h"
#include "modules/lights.h" #include "modules/lights.h"
#include "modules/memory.h" #include "modules/memory.h"
#include "modules/sdvx.h"
#include "modules/touch.h" #include "modules/touch.h"
#include "modules/resize.h" #include "modules/resize.h"
#include "request.h" #include "request.h"
@@ -36,6 +38,8 @@
using namespace rapidjson; using namespace rapidjson;
using namespace api; using namespace api;
std::atomic_uint32_t api::CLIENT_COUNT = 0;
Controller::Controller(unsigned short port, std::string password, bool pretty) Controller::Controller(unsigned short port, std::string password, bool pretty)
: port(port), password(std::move(password)), pretty(pretty) : port(port), password(std::move(password)), pretty(pretty)
{ {
@@ -411,8 +415,11 @@ void Controller::init_state(api::ClientState *state) {
state->modules.push_back(new modules::LCD()); state->modules.push_back(new modules::LCD());
state->modules.push_back(new modules::Lights()); state->modules.push_back(new modules::Lights());
state->modules.push_back(new modules::Memory()); state->modules.push_back(new modules::Memory());
state->modules.push_back(new modules::SDVX());
state->modules.push_back(new modules::Touch()); state->modules.push_back(new modules::Touch());
state->modules.push_back(new modules::Resize()); state->modules.push_back(new modules::Resize());
CLIENT_COUNT.fetch_add(1, std::memory_order_relaxed);
} }
void Controller::free_state(api::ClientState *state) { void Controller::free_state(api::ClientState *state) {
@@ -424,6 +431,8 @@ void Controller::free_state(api::ClientState *state) {
// free cipher // free cipher
delete state->cipher; delete state->cipher;
CLIENT_COUNT.fetch_sub(1, std::memory_order_relaxed);
} }
void Controller::free_socket() { void Controller::free_socket() {
+224
View File
@@ -0,0 +1,224 @@
#include "h264_stream.h"
#ifdef SPICE_H264
#include <vector>
#include <x264.h>
#include "util/logging.h"
namespace api {
namespace {
// BT.601 limited range, the range every decoder assumes for H.264 without
// explicit colour metadata
inline uint8_t rgb_to_y(int r, int g, int b) {
return static_cast<uint8_t>(((66 * r + 129 * g + 25 * b + 128) >> 8) + 16);
}
inline uint8_t rgb_to_u(int r, int g, int b) {
return static_cast<uint8_t>(((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128);
}
inline uint8_t rgb_to_v(int r, int g, int b) {
return static_cast<uint8_t>(((112 * r - 94 * g - 18 * b + 128) >> 8) + 128);
}
// a bare annex-b elementary stream, one encoder per connection so every client
// starts on its own keyframe. no container, so nothing here keeps a media clock
class H264Writer : public StreamWriter {
public:
H264Writer(int quality, int fps) : quality(quality), fps(fps) {}
~H264Writer() override {
this->close();
}
std::string content_type() const override {
return "video/h264";
}
bool write(const StreamSend &send, const capture_pump::Frame &frame) override {
// I420 needs even dimensions
const int width = frame.width & ~1;
const int height = frame.height & ~1;
if (width <= 0 || height <= 0) {
return true;
}
if (this->encoder == nullptr) {
if (!this->open(width, height)) {
return false;
}
} else if (width != this->width || height != this->height) {
// the encoder is fixed at the size it opened with; let the client reconnect
log_info("api::stream", "capture size changed, ending H.264 client");
return false;
}
this->convert(frame.pixels.get(), frame.width);
this->picture.i_pts = this->frame_index;
x264_nal_t *nals = nullptr;
int nal_count = 0;
x264_picture_t picture_out;
const int size = x264_encoder_encode(
this->encoder, &nals, &nal_count, &this->picture, &picture_out);
if (size < 0) {
log_warning("api::stream", "H.264 encode failed");
return false;
}
this->frame_index++;
if (size == 0) {
return true;
}
// x264 lays every NAL of the frame out back to back. an SEI or delimiter
// carries no picture, so only the parameter sets and the slice go through
this->annexb.clear();
for (int i = 0; i < nal_count; i++) {
switch (nals[i].i_type) {
case NAL_SEI:
case NAL_AUD:
case NAL_FILLER:
continue;
default:
break;
}
this->annexb.insert(this->annexb.end(),
nals[i].p_payload, nals[i].p_payload + nals[i].i_payload);
}
if (this->annexb.empty()) {
return true;
}
return send(this->annexb.data(), this->annexb.size());
}
private:
bool open(int width, int height) {
x264_param_t param;
if (x264_param_default_preset(&param, "ultrafast", "zerolatency") < 0) {
return false;
}
param.i_csp = X264_CSP_I420;
param.i_width = width;
param.i_height = height;
param.i_fps_num = this->fps;
param.i_fps_den = 1;
param.i_threads = 1;
param.b_annexb = 1;
// SPS/PPS ahead of every IDR, so a client can start decoding cold
param.b_repeat_headers = 1;
// a keyframe every two seconds bounds how long a new client waits
param.i_keyint_max = this->fps * 2;
param.i_log_level = X264_LOG_NONE;
param.rc.i_rc_method = X264_RC_CRF;
param.rc.f_rf_constant = 40.0f - (this->quality * 0.25f);
// baseline keeps hardware decode available on the widest range of phones
if (x264_param_apply_profile(&param, "baseline") < 0) {
return false;
}
this->encoder = x264_encoder_open(&param);
if (this->encoder == nullptr) {
log_warning("api::stream", "could not open the H.264 encoder");
return false;
}
if (x264_picture_alloc(&this->picture, X264_CSP_I420, width, height) < 0) {
this->close();
return false;
}
this->picture_ready = true;
this->width = width;
this->height = height;
return true;
}
void close() {
if (this->picture_ready) {
x264_picture_clean(&this->picture);
this->picture_ready = false;
}
if (this->encoder != nullptr) {
x264_encoder_close(this->encoder);
this->encoder = nullptr;
}
}
// packed 24bpp RGB to I420, averaging each 2x2 block for the chroma planes
void convert(const uint8_t *rgb, int source_width) {
uint8_t *plane_y = this->picture.img.plane[0];
uint8_t *plane_u = this->picture.img.plane[1];
uint8_t *plane_v = this->picture.img.plane[2];
const int stride_y = this->picture.img.i_stride[0];
const int stride_u = this->picture.img.i_stride[1];
const int stride_v = this->picture.img.i_stride[2];
for (int y = 0; y < this->height; y++) {
const uint8_t *row = rgb + static_cast<size_t>(y) * source_width * 3;
uint8_t *out_y = plane_y + static_cast<size_t>(y) * stride_y;
for (int x = 0; x < this->width; x++) {
const uint8_t *pixel = row + x * 3;
out_y[x] = rgb_to_y(pixel[0], pixel[1], pixel[2]);
}
}
for (int y = 0; y < this->height / 2; y++) {
const uint8_t *row0 = rgb + static_cast<size_t>(y * 2) * source_width * 3;
const uint8_t *row1 = row0 + static_cast<size_t>(source_width) * 3;
uint8_t *out_u = plane_u + static_cast<size_t>(y) * stride_u;
uint8_t *out_v = plane_v + static_cast<size_t>(y) * stride_v;
for (int x = 0; x < this->width / 2; x++) {
const uint8_t *p00 = row0 + (x * 2) * 3;
const uint8_t *p01 = p00 + 3;
const uint8_t *p10 = row1 + (x * 2) * 3;
const uint8_t *p11 = p10 + 3;
const int r = (p00[0] + p01[0] + p10[0] + p11[0] + 2) / 4;
const int g = (p00[1] + p01[1] + p10[1] + p11[1] + 2) / 4;
const int b = (p00[2] + p01[2] + p10[2] + p11[2] + 2) / 4;
out_u[x] = rgb_to_u(r, g, b);
out_v[x] = rgb_to_v(r, g, b);
}
}
}
int quality;
int fps;
int width = 0;
int height = 0;
int64_t frame_index = 0;
std::vector<uint8_t> annexb;
x264_t *encoder = nullptr;
x264_picture_t picture {};
bool picture_ready = false;
};
}
std::unique_ptr<StreamWriter> make_h264_writer(int quality, int fps) {
return std::make_unique<H264Writer>(quality, fps);
}
}
#endif // SPICE_H264
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <memory>
#include "stream_format.h"
namespace api {
// bare annex-b H.264; null when the build has no encoder
std::unique_ptr<StreamWriter> make_h264_writer(int quality, int fps);
}
+12 -6
View File
@@ -2,8 +2,10 @@
#include <functional> #include <functional>
#include <mutex> #include <mutex>
#include <unordered_map> #include <unordered_map>
#include "api/capture_pump.h"
#include "external/rapidjson/document.h" #include "external/rapidjson/document.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "hooks/graphics/jpeg_encoder.h"
#include "util/crypt.h" #include "util/crypt.h"
using namespace std::placeholders; using namespace std::placeholders;
@@ -93,8 +95,6 @@ namespace api::modules {
* reduce: uint for dividing image size * reduce: uint for dividing image size
*/ */
void Capture::get_jpg(Request &req, Response &res) { void Capture::get_jpg(Request &req, Response &res) {
CAPTURE_BUFFER.clear();
CAPTURE_BUFFER.reserve(1024 * 128);
// settings // settings
int screen = 0; int screen = 0;
@@ -120,10 +120,16 @@ namespace api::modules {
uint64_t timestamp = 0; uint64_t timestamp = 0;
int width = 0; int width = 0;
int height = 0; int height = 0;
graphics_capture_trigger(screen);
bool success = graphics_capture_receive_jpeg(screen, [] (uint8_t byte) { std::shared_ptr<uint8_t[]> pixels;
CAPTURE_BUFFER.push_back(byte); bool success = capture_pump::capture_direct(
}, true, quality, true, divide, &timestamp, &width, &height); screen, pixels, divide, &timestamp, &width, &height);
if (success) {
CAPTURE_BUFFER.clear();
success = jpeg_encoder::encode(
CAPTURE_BUFFER, pixels.get(), width, height, quality);
}
if (success) { if (success) {
add_jpeg_response(screen, timestamp, width, height, CAPTURE_BUFFER, res); add_jpeg_response(screen, timestamp, width, height, CAPTURE_BUFFER, res);
+54 -54
View File
@@ -1,54 +1,54 @@
#include "ddr.h" #include "ddr.h"
#include <functional> #include <functional>
#include "external/rapidjson/document.h" #include "external/rapidjson/document.h"
#include "games/ddr/ddr.h" #include "games/ddr/ddr.h"
using namespace std::placeholders; using namespace std::placeholders;
using namespace rapidjson; using namespace rapidjson;
namespace api::modules { namespace api::modules {
DDR::DDR() : Module("ddr") { DDR::DDR() : Module("ddr") {
functions["tapeled_get"] = std::bind(&DDR::tapeled_get, this, _1, _2); functions["tapeled_get"] = std::bind(&DDR::tapeled_get, this, _1, _2);
} }
/** /**
* Allows fetching of the RGB LED strips that are gold cabinets, via SpiceAPI * Allows fetching of the RGB LED strips that are gold cabinets, via SpiceAPI
*/ */
void DDR::tapeled_get(Request &req, Response &res) { void DDR::tapeled_get(Request &req, Response &res) {
static const char* device_names[11] = { static const char* device_names[11] = {
"p1_foot_up", "p1_foot_up",
"p1_foot_right", "p1_foot_right",
"p1_foot_left", "p1_foot_left",
"p1_foot_down", "p1_foot_down",
"p2_foot_up", "p2_foot_up",
"p2_foot_right", "p2_foot_right",
"p2_foot_left", "p2_foot_left",
"p2_foot_down", "p2_foot_down",
"top_panel", "top_panel",
"monitor_left", "monitor_left",
"monitor_right" "monitor_right"
}; };
Value response_object(kObjectType); Value response_object(kObjectType);
// Iterate through each device and dump its lights data into the response // Iterate through each device and dump its lights data into the response
for (size_t device = 0; device < 11; device++) { for (size_t device = 0; device < 11; device++) {
size_t num_leds = 25; size_t num_leds = 25;
if (device > 7) if (device > 7)
num_leds = 50; num_leds = 50;
Value light_state(kArrayType); Value light_state(kArrayType);
light_state.Reserve(num_leds * 3, res.doc()->GetAllocator()); light_state.Reserve(num_leds * 3, res.doc()->GetAllocator());
for (size_t led = 0; led < num_leds; led++) { for (size_t led = 0; led < num_leds; led++) {
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][0], res.doc()->GetAllocator()); light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][0], res.doc()->GetAllocator());
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][1], res.doc()->GetAllocator()); light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][1], res.doc()->GetAllocator());
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][2], res.doc()->GetAllocator()); light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][2], res.doc()->GetAllocator());
} }
response_object.AddMember(StringRef(device_names[device]), light_state, res.doc()->GetAllocator()); response_object.AddMember(StringRef(device_names[device]), light_state, res.doc()->GetAllocator());
} }
res.add_data(response_object); res.add_data(response_object);
} }
} }
+17 -17
View File
@@ -1,17 +1,17 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include "api/module.h" #include "api/module.h"
#include "api/request.h" #include "api/request.h"
namespace api::modules { namespace api::modules {
class DDR : public Module { class DDR : public Module {
public: public:
DDR(); DDR();
private: private:
// function definitions // function definitions
void tapeled_get(Request &req, Response &res); void tapeled_get(Request &req, Response &res);
}; };
} }
+4 -2
View File
@@ -106,14 +106,16 @@ namespace api::modules {
void IIDX::copy_tapeled_data(Response &res, Value &response_object, const tapeledutils::tape_led &mapping) { void IIDX::copy_tapeled_data(Response &res, Value &response_object, const tapeledutils::tape_led &mapping) {
// Create an array for the light state // Create an array for the light state
Value light_state(kArrayType); Value light_state(kArrayType);
light_state.Reserve(mapping.data.capacity() * 3, res.doc()->GetAllocator()); light_state.Reserve(
static_cast<SizeType>(mapping.data.size() * 3),
res.doc()->GetAllocator());
for (const auto [r, g, b] : mapping.data) { for (const auto [r, g, b] : mapping.data) {
light_state.PushBack(r, res.doc()->GetAllocator()); light_state.PushBack(r, res.doc()->GetAllocator());
light_state.PushBack(g, res.doc()->GetAllocator()); light_state.PushBack(g, res.doc()->GetAllocator());
light_state.PushBack(b, res.doc()->GetAllocator()); light_state.PushBack(b, res.doc()->GetAllocator());
} }
// Can't use StringRef here, turns some strings partially into null bytes for some reason // can't use StringRef here, turns some strings partially into null bytes for some reason
Value light_name(mapping.lightName.c_str(), res.doc()->GetAllocator()); Value light_name(mapping.lightName.c_str(), res.doc()->GetAllocator());
response_object.AddMember(light_name, light_state, res.doc()->GetAllocator()); response_object.AddMember(light_name, light_state, res.doc()->GetAllocator());
} }
+67
View File
@@ -0,0 +1,67 @@
#include "sdvx.h"
#include <functional>
using namespace std::placeholders;
using namespace rapidjson;
namespace api::modules {
SDVX::SDVX() : Module("sdvx") {
functions["tapeled_get"] = std::bind(&SDVX::tapeled_get, this, _1, _2);
for (auto &light : games::sdvx::TAPELED_MAPPING) {
lights_by_names.emplace(light.lightName, light);
}
}
/**
* tapeled_get()
* tapeled_get(name: str, ...)
*/
void SDVX::tapeled_get(Request &req, Response &res) {
Value response_object(kObjectType);
// all tape leds
if (req.params.Size() == 0) {
// iterate through each device and dump its lights data into the response
for (const auto &mapping : games::sdvx::TAPELED_MAPPING) {
copy_tapeled_data(res, response_object, mapping);
}
} else {
// specified light names
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsString()) {
error_type(res, "name", "string");
return;
}
const auto name = param.GetString();
if (const auto &it = lights_by_names.find(name); it != lights_by_names.end()) {
copy_tapeled_data(res, response_object, it->second.get());
}
}
}
res.add_data(response_object);
}
void SDVX::copy_tapeled_data(Response &res, Value &response_object,
const tapeledutils::tape_led &mapping)
{
Value light_state(kArrayType);
light_state.Reserve(
static_cast<SizeType>(mapping.data.size() * 3),
res.doc()->GetAllocator());
for (const auto [r, g, b] : mapping.data) {
light_state.PushBack(r, res.doc()->GetAllocator());
light_state.PushBack(g, res.doc()->GetAllocator());
light_state.PushBack(b, res.doc()->GetAllocator());
}
// can't use StringRef here, turns some strings partially into null bytes for some reason
Value light_name(mapping.lightName.c_str(), res.doc()->GetAllocator());
response_object.AddMember(light_name, light_state, res.doc()->GetAllocator());
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <functional>
#include <string>
#include "api/module.h"
#include "api/request.h"
#include "external/robin_hood.h"
#include "games/sdvx/sdvx.h"
namespace api::modules {
class SDVX : public Module {
public:
SDVX();
private:
robin_hood::unordered_map<std::string, std::reference_wrapper<tapeledutils::tape_led>> lights_by_names;
void tapeled_get(Request &req, Response &res);
void copy_tapeled_data(Response &res, rapidjson::Value &response_object,
const tapeledutils::tape_led &mapping);
};
}
+44 -11
View File
@@ -12,7 +12,9 @@
#include "touch/touch.h" #include "touch/touch.h"
#include "touch/native/inject.h" #include "touch/native/inject.h"
#include "touch/native/nativetouchhook.h" #include "touch/native/nativetouchhook.h"
#include "touch/native/transform.h"
#include "util/utils.h" #include "util/utils.h"
#include "games/gitadora/gitadora.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
using namespace std::placeholders; using namespace std::placeholders;
@@ -40,6 +42,35 @@ namespace api::modules {
return nativetouch::inject::inject_synthetic_touch(position, true); return nativetouch::inject::inject_synthetic_touch(position, true);
} }
// map API coordinates onto the touch space SDVX reads, which depends on how it is displayed
static void sdvx_touch_errata(
int &x, int &y, bool use_native, int canvas_w, int canvas_h) {
// windowed coordinates already match the sub screen window they land on
if (GRAPHICS_WINDOWED) {
return;
}
// landscape mode: native injection hands the game these coordinates
// unchanged, so apply the rotation the touchscreen gets, while wintouchemu instead
// rotates them later through the subscreen overlay
if (GRAPHICS_FS_ORIENTATION_SWAP) {
if (use_native) {
POINT position { x, y };
if (nativetouch::transform::sdvx_landscape_rotate(&position, canvas_w, canvas_h)) {
x = position.x;
y = position.y;
}
}
return;
}
// rotate into the portrait touch space
const int x_raw = x;
x = canvas_w - y;
y = x_raw;
}
Touch::Touch() : Module("touch") { Touch::Touch() : Module("touch") {
is_sdvx = avs::game::is_model("KFC"); is_sdvx = avs::game::is_model("KFC");
@@ -55,9 +86,12 @@ namespace api::modules {
native_canvas_w = 0; native_canvas_w = 0;
native_canvas_h = 0; native_canvas_h = 0;
if (is_sdvx) { if (is_sdvx) {
// exceed gear subscreen, portrait after the rotation applied in apply_touch_errata // windowed API coordinates land on the sub screen window as-is; fullscreen
native_canvas_w = 1080; // coordinates are rotated into the game's touch space by apply_touch_errata
native_canvas_h = 1920; const bool landscape_coordinates =
GRAPHICS_WINDOWED || GRAPHICS_FS_ORIENTATION_SWAP;
native_canvas_w = landscape_coordinates ? 1920 : 1080;
native_canvas_h = landscape_coordinates ? 1080 : 1920;
} else if (avs::game::is_model("LDJ")) { } else if (avs::game::is_model("LDJ")) {
// TDJ subscreen; FHD models are upscaled to 1080p by apply_touch_errata // TDJ subscreen; FHD models are upscaled to 1080p by apply_touch_errata
native_canvas_w = is_tdj_fhd ? 1920 : 1280; native_canvas_w = is_tdj_fhd ? 1920 : 1280;
@@ -66,6 +100,10 @@ namespace api::modules {
// pop'n music API touch surface // pop'n music API touch surface
native_canvas_w = 1280; native_canvas_w = 1280;
native_canvas_h = 800; native_canvas_h = 800;
} else if (games::gitadora::is_arena_model()) {
// GITADORA arena SMALL subscreen, either in its own window or in the overlay
native_canvas_w = games::gitadora::ARENA_SUBSCREEN_WIDTH;
native_canvas_h = games::gitadora::ARENA_SUBSCREEN_HEIGHT;
} }
functions["read"] = std::bind(&Touch::read, this, _1, _2); functions["read"] = std::bind(&Touch::read, this, _1, _2);
@@ -209,19 +247,14 @@ namespace api::modules {
} }
void Touch::apply_touch_errata(int &x, int &y) { void Touch::apply_touch_errata(int &x, int &y) {
int x_raw = x;
int y_raw = y;
if (is_tdj_fhd) { if (is_tdj_fhd) {
// deal with TDJ FHD resolution mismatch (upgrade 720p to 1080p) // deal with TDJ FHD resolution mismatch (upgrade 720p to 1080p)
// we don't know what screen is being shown on the companion and the API doesn't specify // we don't know what screen is being shown on the companion and the API doesn't specify
// the target of the touch events so just assume it's the sub screen // the target of the touch events so just assume it's the sub screen
x = x_raw * 1920 / 1280; x = x * 1920 / 1280;
y = y_raw * 1080 / 720; y = y * 1080 / 720;
} else if (is_sdvx) { } else if (is_sdvx) {
// for exceed gear, they are both 1080p screens, but need to apply transformation sdvx_touch_errata(x, y, use_native, native_canvas_w, native_canvas_h);
x = 1080 - y_raw;
y = x_raw;
} }
} }
} }
@@ -7,6 +7,7 @@ from .coin import *
from .control import * from .control import *
from .exceptions import * from .exceptions import *
from .iidx import * from .iidx import *
from .sdvx import *
from .info import * from .info import *
from .keypads import * from .keypads import *
from .lights import * from .lights import *
@@ -0,0 +1,12 @@
from .connection import Connection
from .request import Request
def sdvx_tapeled_get(con: Connection, *light_names):
req = Request("sdvx", "tapeled_get")
for light_name in light_names:
req.add_param(light_name)
res = con.request(req)
return res.get_data()
+70
View File
@@ -0,0 +1,70 @@
#include "stream_format.h"
#include <vector>
#include "h264_stream.h"
#include "hooks/graphics/jpeg_encoder.h"
namespace api {
namespace {
#ifdef SPICE_JPEG
constexpr const char *MJPEG_BOUNDARY = "spice2xframe";
// multipart/x-mixed-replace: every frame is a standalone JPEG, no inter-frame state
class MjpegWriter : public StreamWriter {
public:
explicit MjpegWriter(int quality) : quality(quality) {}
std::string content_type() const override {
return std::string("multipart/x-mixed-replace; boundary=") + MJPEG_BOUNDARY;
}
bool write(const StreamSend &send, const capture_pump::Frame &frame) override {
this->jpeg.clear();
if (!jpeg_encoder::encode(
this->jpeg, frame.pixels.get(),
frame.width, frame.height, this->quality)) {
// a frame the encoder rejects is not worth dropping the client over
return true;
}
const std::string part =
"--" + std::string(MJPEG_BOUNDARY) + "\r\n"
"Content-Type: image/jpeg\r\n"
"Content-Length: " + std::to_string(this->jpeg.size()) + "\r\n"
"\r\n";
return send(part.data(), part.size())
&& send(this->jpeg.data(), this->jpeg.size())
&& send("\r\n", 2);
}
private:
int quality;
std::vector<uint8_t> jpeg;
};
#endif
}
// both parameters go unused on toolchains that compile in neither format
std::unique_ptr<StreamWriter> make_stream_writer(
const std::string &path, [[maybe_unused]] int quality, [[maybe_unused]] int fps) {
#ifdef SPICE_JPEG
if (path == "/stream.mjpg") {
return std::make_unique<MjpegWriter>(quality);
}
#endif
#ifdef SPICE_H264
if (path == "/stream.h264") {
return make_h264_writer(quality, fps);
}
#endif
return nullptr;
}
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <cstddef>
#include <functional>
#include <memory>
#include <string>
#include "capture_pump.h"
namespace api {
// writes bytes to the client; false once the connection is gone
using StreamSend = std::function<bool(const void *, size_t)>;
// one wire format, instantiated per connection so it can keep encoder state across frames
class StreamWriter {
public:
virtual ~StreamWriter() = default;
StreamWriter(const StreamWriter &) = delete;
StreamWriter &operator=(const StreamWriter &) = delete;
// value for the HTTP Content-Type response header
virtual std::string content_type() const = 0;
// for formats that open with an init segment; runs once before any frame
virtual bool begin(const StreamSend &send) { return true; }
virtual bool write(const StreamSend &send, const capture_pump::Frame &frame) = 0;
protected:
StreamWriter() = default;
};
// null when the path does not name a format this build supports
std::unique_ptr<StreamWriter> make_stream_writer(
const std::string &path, int quality, int fps);
}
+476
View File
@@ -0,0 +1,476 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include "stream_server.h"
#include <algorithm>
#include <cctype>
#include <chrono>
#include <limits>
#include <map>
#include <string>
#include <thread>
#include <vector>
#include "capture_pump.h"
#include "hooks/graphics/graphics.h"
#include "stream_format.h"
#include "util/logging.h"
#include "util/utils.h"
namespace api {
namespace {
struct HttpRequest {
std::string method;
std::string path;
std::map<std::string, std::string> query;
};
bool send_all(SOCKET socket, const void *data, size_t size) {
auto cursor = reinterpret_cast<const char *>(data);
size_t remaining = size;
while (remaining > 0) {
const int sent = send(socket, cursor, static_cast<int>(remaining), 0);
if (sent <= 0) {
return false;
}
cursor += sent;
remaining -= static_cast<size_t>(sent);
}
return true;
}
bool send_all(SOCKET socket, const std::string &text) {
return send_all(socket, text.data(), text.size());
}
// a viewer leaving is normally noticed by a failing send, so a stream with no frame
// to push has to ask the socket instead
bool client_gone(SOCKET socket) {
fd_set read_set;
FD_ZERO(&read_set);
FD_SET(socket, &read_set);
// the socket is blocking with a receive timeout, so poll before touching it
timeval immediately {};
const int ready = select(0, &read_set, nullptr, nullptr, &immediately);
if (ready == 0) {
return false;
}
if (ready < 0) {
return true;
}
// consumed rather than peeked: a stray byte would otherwise sit in front of the
// FIN and keep hiding it for as long as the stream runs
char discard[256];
return recv(socket, discard, sizeof(discard), 0) <= 0;
}
std::string url_decode(const std::string &input) {
std::string out;
out.reserve(input.size());
for (size_t i = 0; i < input.size(); i++) {
if (input[i] == '+') {
out.push_back(' ');
} else if (input[i] == '%' && i + 2 < input.size()
&& isxdigit(static_cast<unsigned char>(input[i + 1]))
&& isxdigit(static_cast<unsigned char>(input[i + 2]))) {
out.push_back(static_cast<char>(
std::stoi(input.substr(i + 1, 2), nullptr, 16)));
i += 2;
} else {
out.push_back(input[i]);
}
}
return out;
}
void parse_query(const std::string &query, HttpRequest &request) {
size_t pos = 0;
while (pos < query.size()) {
auto end = query.find('&', pos);
if (end == std::string::npos) {
end = query.size();
}
const auto pair = query.substr(pos, end - pos);
const auto split = pair.find('=');
if (split != std::string::npos && split > 0) {
request.query[url_decode(pair.substr(0, split))] =
url_decode(pair.substr(split + 1));
}
pos = end + 1;
}
}
// reads the request head only; anything oversized or malformed is refused
bool read_request(SOCKET socket, size_t size_limit, HttpRequest &request) {
std::string head;
char buffer[1024];
while (head.find("\r\n\r\n") == std::string::npos) {
if (head.size() >= size_limit) {
return false;
}
// read no further than the limit, so the head cannot overshoot it
const size_t budget = std::min(sizeof(buffer), size_limit - head.size());
const int received = recv(socket, buffer, static_cast<int>(budget), 0);
if (received <= 0) {
return false;
}
head.append(buffer, static_cast<size_t>(received));
}
const auto line_end = head.find("\r\n");
const auto line = head.substr(0, line_end);
const auto method_end = line.find(' ');
if (method_end == std::string::npos) {
return false;
}
const auto target_end = line.find(' ', method_end + 1);
if (target_end == std::string::npos) {
return false;
}
request.method = line.substr(0, method_end);
auto target = line.substr(method_end + 1, target_end - method_end - 1);
const auto query_start = target.find('?');
if (query_start != std::string::npos) {
parse_query(target.substr(query_start + 1), request);
target = target.substr(0, query_start);
}
request.path = url_decode(target);
return true;
}
int query_int(const HttpRequest &request, const std::string &name, int fallback,
int min, int max) {
const auto pos = request.query.find(name);
if (pos == request.query.end()) {
return fallback;
}
try {
return std::clamp(std::stoi(pos->second), min, max);
} catch (const std::exception &) {
return fallback;
}
}
// an <img> can show a cross-origin stream without this, but a browser client that
// decodes the bytes itself has to fetch() them, and fetch is subject to CORS. errors
// carry it too, or the client sees an opaque failure instead of the status.
constexpr const char *cors_header = "Access-Control-Allow-Origin: *\r\n";
void send_error(SOCKET socket, const char *status) {
const std::string response =
std::string("HTTP/1.0 ") + status + "\r\n"
+ cors_header +
"Content-Length: 0\r\n"
"Connection: close\r\n"
"\r\n";
send_all(socket, response);
}
}
StreamServer::StreamServer(unsigned short port)
: port(port)
{
if (!this->open_listener()) {
// the stream was asked for explicitly, so say plainly that it is not there
log_warning("api::stream",
"the video stream is not available on port {}", this->port);
return;
}
this->running = true;
this->acceptor = std::thread([this] {
this->accept_worker();
});
// deliberately not logging a full URL; local IPs would leak into shared logs
log_info("api::stream", "video stream is listening on port: {}", this->port);
log_warning("api::stream",
"the video stream is unauthenticated - anyone who can reach port {} can watch "
"the game screen", this->port);
}
bool StreamServer::open_listener() {
WSADATA wsa_data;
const int error = WSAStartup(MAKEWORD(2, 2), &wsa_data);
if (error != 0) {
log_warning("api::stream", "WSAStartup() returned {}", error);
return false;
}
this->wsa_started = true;
this->listener = socket(AF_INET, SOCK_STREAM, 0);
if (this->listener == INVALID_SOCKET) {
log_warning("api::stream", "could not create listener socket: {}",
get_last_error_string());
return false;
}
int opt_enable = 1;
if (setsockopt(this->listener, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char *>(&opt_enable), sizeof(int)) == -1) {
log_warning("api::stream", "could not set socket option SO_REUSEADDR: {}",
get_last_error_string());
}
sockaddr_in server_address {};
server_address.sin_family = AF_INET;
server_address.sin_port = htons(this->port);
server_address.sin_addr.s_addr = INADDR_ANY;
if (bind(this->listener, (sockaddr *) &server_address, sizeof(sockaddr)) == -1) {
log_warning("api::stream", "could not bind socket on port {}: {}",
this->port, get_last_error_string());
closesocket(this->listener);
this->listener = INVALID_SOCKET;
return false;
}
if (listen(this->listener, server_backlog) == -1) {
log_warning("api::stream", "could not listen on port {}: {}",
this->port, get_last_error_string());
closesocket(this->listener);
this->listener = INVALID_SOCKET;
return false;
}
return true;
}
StreamServer::~StreamServer() {
this->running = false;
if (this->listener != INVALID_SOCKET) {
closesocket(this->listener);
this->listener = INVALID_SOCKET;
}
// drops the client threads out of their blocking send/recv
{
std::lock_guard<std::mutex> lock(this->clients_m);
for (auto &client : this->clients) {
if (client.socket != INVALID_SOCKET) {
::shutdown(client.socket, SD_BOTH);
}
}
}
if (this->acceptor.joinable()) {
this->acceptor.join();
}
// joining is what guarantees no client thread outlives this object
for (auto &client : this->clients) {
if (client.thread.joinable()) {
client.thread.join();
}
}
if (this->wsa_started) {
WSACleanup();
}
}
void StreamServer::accept_worker() {
while (this->running) {
sockaddr_in client_address {};
int client_address_size = sizeof(sockaddr_in);
const SOCKET client = accept(
this->listener, (sockaddr *) &client_address, &client_address_size);
if (client == INVALID_SOCKET) {
// on shutdown the listener is closed under us; otherwise do not spin
if (this->running) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
continue;
}
if (!this->running) {
closesocket(client);
break;
}
char address_data[INET_ADDRSTRLEN] {};
inet_ntop(AF_INET, &client_address.sin_addr, address_data, INET_ADDRSTRLEN);
std::string address(address_data);
// every client costs an encode and real bandwidth, so the cap protects the game
int slot = -1;
{
std::lock_guard<std::mutex> lock(this->clients_m);
for (size_t i = 0; i < this->clients.size(); i++) {
if (!this->clients[i].active) {
this->clients[i].active = true;
this->clients[i].socket = client;
slot = static_cast<int>(i);
break;
}
}
}
if (slot < 0) {
log_warning("api::stream", "client limit of {} hit", client_limit);
send_error(client, "503 Service Unavailable");
closesocket(client);
continue;
}
// this thread is the only one that touches the thread objects, so the slot's
// previous occupant gets reaped here rather than being detached
if (this->clients[slot].thread.joinable()) {
this->clients[slot].thread.join();
}
this->clients[slot].thread = std::thread([this, slot, client, address] {
this->client_worker(slot, client, address);
});
}
}
void StreamServer::client_worker(int slot, SOCKET socket, std::string address) {
DWORD timeout = request_timeout_ms;
setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
timeout = send_timeout_ms;
setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO,
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
int opt_enable = 1;
setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<const char *>(&opt_enable), sizeof(int));
// whatever sits in the send buffer is already stale, and the default holds about a
// third of a second of H.264 because the bitrate is so low. keeping it small makes a
// slow reader block the sender, which then skips to the newest frame instead of
// handing over a backlog
int send_buffer = send_buffer_bytes;
setsockopt(socket, SOL_SOCKET, SO_SNDBUF,
reinterpret_cast<const char *>(&send_buffer), sizeof(send_buffer));
HttpRequest request;
if (read_request(socket, request_size_limit, request)) {
if (request.method != "GET") {
send_error(socket, "405 Method Not Allowed");
} else {
const int fps = query_int(request, "fps", 30, 1, fps_limit);
const int quality = query_int(request, "q", 70, 1, 100);
auto writer = make_stream_writer(request.path, quality, fps);
if (!writer) {
send_error(socket, "404 Not Found");
} else {
std::vector<int> screens;
graphics_screens_get(screens);
// registration takes a raw swapchain index and never bounds it, so the
// capture range has to be enforced here rather than assumed
const auto streamable = [&screens](int screen) {
return screen < static_cast<int>(GRAPHICS_CAPTURE_SCREEN_NO)
&& std::find(screens.begin(), screens.end(), screen)
!= screens.end();
};
// screen 1 is the subscreen in every game that has one; single-screen games
// only ever register screen 0, so resolve the default against what exists.
// left unclamped so a nonsense screen is reported as what was asked for
int screen = query_int(request, "screen", -1, 0,
std::numeric_limits<int>::max());
if (screen < 0) {
screen = streamable(1) ? 1 : 0;
}
// the default always lands on a screen that exists, so this is only ever
// an explicit request for one that cannot be captured
if (!streamable(screen)) {
log_warning("api::stream",
"screen {} is not available, refusing {}", screen, address);
send_error(socket, "404 Not Found");
} else if (!capture_pump::claim_screen(screen)) {
log_warning("api::stream",
"screen {} is already being streamed, refusing {}",
screen, address);
send_error(socket, "503 Service Unavailable");
} else {
log_info("api::stream",
"client connected: {} ({}, screen={}, fps={}, quality={})",
address, request.path, screen, fps, quality);
const std::string header =
"HTTP/1.0 200 OK\r\n"
"Connection: close\r\n"
+ std::string(cors_header) +
"Cache-Control: no-store, no-cache, must-revalidate\r\n"
"Pragma: no-cache\r\n"
"Content-Type: " + writer->content_type() + "\r\n"
"\r\n";
const StreamSend stream_send = [socket](const void *data, size_t size) {
return send_all(socket, data, size);
};
if (send_all(socket, header) && writer->begin(stream_send)) {
const auto interval = std::chrono::microseconds(1000000 / fps);
while (this->running) {
const auto started = std::chrono::steady_clock::now();
capture_pump::Frame frame;
const bool ok = capture_pump::capture_direct(
screen, frame.pixels, 1,
&frame.timestamp, &frame.width, &frame.height);
if (ok && frame.pixels) {
if (!writer->write(stream_send, frame)) {
break;
}
} else if (client_gone(socket)) {
break;
}
// a failed capture still paces, or a stalled game spins this
std::this_thread::sleep_until(started + interval);
}
}
capture_pump::release_screen(screen);
log_info("api::stream", "client disconnected: {}", address);
}
}
}
}
{
std::lock_guard<std::mutex> lock(this->clients_m);
this->clients[slot].socket = INVALID_SOCKET;
this->clients[slot].active = false;
}
closesocket(socket);
}
}
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <array>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <string>
#include <thread>
#include <winsock2.h>
namespace api {
class StreamServer {
public:
explicit StreamServer(unsigned short port);
~StreamServer();
StreamServer(const StreamServer &) = delete;
StreamServer &operator=(const StreamServer &) = delete;
private:
// configuration
static constexpr int server_backlog = 4;
static constexpr int client_limit = 4;
static constexpr int request_size_limit = 8 * 1024;
static constexpr int request_timeout_ms = 5000;
static constexpr int send_timeout_ms = 5000;
// small enough that a low bitrate stream cannot hide a backlog of stale frames in it
static constexpr int send_buffer_bytes = 16 * 1024;
static constexpr int fps_limit = 60;
struct Client {
std::thread thread;
SOCKET socket = INVALID_SOCKET;
bool active = false;
};
void accept_worker();
bool open_listener();
void client_worker(int slot, SOCKET socket, std::string address);
unsigned short port;
SOCKET listener = INVALID_SOCKET;
bool wsa_started = false;
std::atomic_bool running { false };
std::thread acceptor;
std::mutex clients_m;
// socket and active are guarded by clients_m; only the acceptor touches thread
std::array<Client, client_limit> clients;
};
}
+29
View File
@@ -12,6 +12,20 @@ using namespace headsocket;
namespace api { namespace api {
namespace {
// how long a single handshake read may stall before the connection is dropped;
// headsocket reads the request a byte at a time, so this is an idle timeout between
// bytes rather than a deadline for the whole handshake
constexpr int handshake_timeout_ms = 5000;
void set_recv_timeout(connection &conn, int milliseconds) {
DWORD timeout = static_cast<DWORD>(milliseconds);
setsockopt(conn.impl()->socket, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
}
}
/* /*
* Client class declaration * Client class declaration
*/ */
@@ -37,6 +51,21 @@ namespace api {
HEADSOCKET_SERVER(WebSocketServer, web_socket_server); HEADSOCKET_SERVER(WebSocketServer, web_socket_server);
public: public:
WebSocketController *websocket; WebSocketController *websocket;
protected:
bool handshake(connection &conn) override {
// headsocket runs the handshake on its single accept thread with a blocking
// recv, so a peer that connects and then says nothing would park that thread and
// leave every later connection sitting unaccepted in the backlog
set_recv_timeout(conn, handshake_timeout_ms);
const bool accepted = base_t::handshake(conn);
// from here the client thread owns the socket and wants to block on reads
set_recv_timeout(conn, 0);
return accepted;
}
}; };
void api::WebSocketServer::init() {} void api::WebSocketServer::init() {}
+5
View File
@@ -41,6 +41,7 @@ namespace avs {
AVS_FS_CLOSE_T avs_fs_close {}; AVS_FS_CLOSE_T avs_fs_close {};
AVS_FS_DUMP_MOUNTPOINT_T avs_fs_dump_mountpoint {}; AVS_FS_DUMP_MOUNTPOINT_T avs_fs_dump_mountpoint {};
AVS_FS_MOUNT_T avs_fs_mount {}; AVS_FS_MOUNT_T avs_fs_mount {};
AVS_FS_UMOUNT_T avs_fs_umount {};
AVS_FS_COPY_T avs_fs_copy {}; AVS_FS_COPY_T avs_fs_copy {};
AVS_FS_FSTAT_T avs_fs_fstat {}; AVS_FS_FSTAT_T avs_fs_fstat {};
AVS_FS_LSTAT_T avs_fs_lstat {}; AVS_FS_LSTAT_T avs_fs_lstat {};
@@ -927,6 +928,7 @@ namespace avs {
.avs_fs_close = "XCgsqzn0000055", .avs_fs_close = "XCgsqzn0000055",
.avs_fs_dump_mountpoint = "XCgsqzn0000068", .avs_fs_dump_mountpoint = "XCgsqzn0000068",
.avs_fs_mount = "XCgsqzn000004b", .avs_fs_mount = "XCgsqzn000004b",
.avs_fs_umount = "XCgsqzn000004d",
.avs_fs_fstat = "XCgsqzn0000062", .avs_fs_fstat = "XCgsqzn0000062",
.avs_fs_lstat = "XCgsqzn0000063", .avs_fs_lstat = "XCgsqzn0000063",
.avs_fs_lseek = "XCgsqzn000004f", .avs_fs_lseek = "XCgsqzn000004f",
@@ -1018,6 +1020,7 @@ namespace avs {
.avs_fs_close = "XCgsqzn0000055", .avs_fs_close = "XCgsqzn0000055",
.avs_fs_dump_mountpoint = "XCgsqzn0000068", .avs_fs_dump_mountpoint = "XCgsqzn0000068",
.avs_fs_mount = "XCgsqzn000004b", .avs_fs_mount = "XCgsqzn000004b",
.avs_fs_umount = "XCgsqzn000004d",
.avs_fs_fstat = "XCgsqzn0000062", .avs_fs_fstat = "XCgsqzn0000062",
.avs_fs_lstat = "XCgsqzn0000063", .avs_fs_lstat = "XCgsqzn0000063",
.avs_fs_lseek = "XCgsqzn000004f", .avs_fs_lseek = "XCgsqzn000004f",
@@ -1601,6 +1604,8 @@ namespace avs {
DLL_INSTANCE, IMPORTS[ver].avs_fs_dump_mountpoint); DLL_INSTANCE, IMPORTS[ver].avs_fs_dump_mountpoint);
avs_fs_mount = libutils::try_proc<AVS_FS_MOUNT_T>( avs_fs_mount = libutils::try_proc<AVS_FS_MOUNT_T>(
DLL_INSTANCE, IMPORTS[ver].avs_fs_mount); DLL_INSTANCE, IMPORTS[ver].avs_fs_mount);
avs_fs_umount = libutils::try_proc<AVS_FS_UMOUNT_T>(
DLL_INSTANCE, IMPORTS[ver].avs_fs_umount);
avs_fs_lstat = libutils::try_proc<AVS_FS_LSTAT_T>( avs_fs_lstat = libutils::try_proc<AVS_FS_LSTAT_T>(
DLL_INSTANCE, IMPORTS[ver].avs_fs_lstat); DLL_INSTANCE, IMPORTS[ver].avs_fs_lstat);
+5 -1
View File
@@ -338,6 +338,7 @@ namespace avs {
const char *avs_fs_close = "?"; const char *avs_fs_close = "?";
const char *avs_fs_dump_mountpoint = "?"; const char *avs_fs_dump_mountpoint = "?";
const char *avs_fs_mount = "?"; const char *avs_fs_mount = "?";
const char *avs_fs_umount = "?";
const char *avs_fs_fstat = "?"; const char *avs_fs_fstat = "?";
const char *avs_fs_lstat = "?"; const char *avs_fs_lstat = "?";
const char *avs_fs_lseek = "?"; const char *avs_fs_lseek = "?";
@@ -513,9 +514,12 @@ namespace avs {
typedef int (*AVS_FS_DUMP_MOUNTPOINT_T)(void); typedef int (*AVS_FS_DUMP_MOUNTPOINT_T)(void);
extern AVS_FS_DUMP_MOUNTPOINT_T avs_fs_dump_mountpoint; extern AVS_FS_DUMP_MOUNTPOINT_T avs_fs_dump_mountpoint;
typedef int (*AVS_FS_MOUNT_T)(const char *mountpoint, const char *fsroot, const char *fstype, void *data); typedef avs_file_t (*AVS_FS_MOUNT_T)(const char *mountpoint, const char *fsroot, const char *fstype, void *data);
extern AVS_FS_MOUNT_T avs_fs_mount; extern AVS_FS_MOUNT_T avs_fs_mount;
typedef void (*AVS_FS_UMOUNT_T)(avs_file_t mount);
extern AVS_FS_UMOUNT_T avs_fs_umount;
typedef int (*AVS_FS_COPY_T)(const char *sname, const char *dname); typedef int (*AVS_FS_COPY_T)(const char *sname, const char *dname);
extern AVS_FS_COPY_T avs_fs_copy; extern AVS_FS_COPY_T avs_fs_copy;
+10
View File
@@ -0,0 +1,10 @@
#ifndef RCDATA
#define RCDATA 10
#endif
#ifdef SPICE64
DMO_BLOB RCDATA "dsdmo.x86_64.dll"
#else
DMO_BLOB RCDATA "dsdmo.i686.dll"
#endif
+1
View File
@@ -40,6 +40,7 @@ done
# settings # settings
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none") GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_BRANCH="${GIT_BRANCH//\//-}"
GIT_HEAD=$(git rev-parse HEAD || echo "none") GIT_HEAD=$(git rev-parse HEAD || echo "none")
TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}" TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}"
TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}" TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}"
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
: ${_G_buildir:="bin/build"}
: ${_G_bindir:="bin"}
: ${_G_debug:=false}
: ${_G_clean:=false}
# Hidden build all temporary environment for sourcing
<<__BUILDALLINIT__
# Auto-config for build_all.sh
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse --short HEAD || echo "none")
BUILD_TYPE=$({ $_G_debug && echo Debug; } || echo Release)
OUTDIR="$_G_bindir/${BUILD_TYPE}"
OUTDIR_EXTRAS="$OUTDIR/extras"
DIST_FOLDER="$_G_bindir/Distribution"
DIST_NAME="${GIT_BRANCH}-${GIT_HEAD}.zip"
DIST_COMMENT="$(date +%Y)/$(date +%m)/$(date +%d):$(git rev-parse HEAD)"
DIST_ENABLE=0
UPX_ENABLE="$DIST_ENABLE"
IGNORE_CACHE=0
INCLUDE_SRC=0
UPX_FLAGS="-q --best --lzma --compress-exports=0"
CLEAN_BUILD=$({ $_G_clean && echo 1; } || echo 0)
DEBUG=$({ $_G_debug && echo 1; } || echo 0)
TOOLCHAIN_32="external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake"
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_spice spicetools_spice_laa spicetools_stubs_cpusbxpkm"
BUILDDIR_32="$_G_buildir/x86/${BUILD_TYPE}"
TOOLCHAIN_64="external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_spice64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda"
BUILDDIR_64="$_G_buildir/x64/${BUILD_TYPE}"
__BUILDALLINIT__
run_build_all() {
# Init build_all.sh
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
source <(head -n +20 ./build_all.sh)
# Run build_all.sh
source <(tail -n +80 ./build_all.sh | head -n -3 | sed -e 's~mkdir -p ${OUTDIR}/src~~' -e '/linux/d')
}
run_deploy_all() {
# Init build_all.sh
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
source <(head -n +20 ./build_all.sh)
# Run deploy in build_all.sh
set +e
source <(tail -n +182 ./build_all.sh | head -n -3 | sed -e 's~mkdir -p ${OUTDIR}/src~~' -e '/linux/d')
}
run_distribute_all() {
# Init build_all.sh
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
source <(head -n +20 ./build_all.sh)
# Run deploy in build_all.sh
source <(tail -n +182 ./build_all.sh | head -n -3 | sed -e 's~mkdir -p ${OUTDIR}/src~~' -e '/linux/d')
# Use old distribute logic
echo "Building dist..."
mkdir -p ${DIST_FOLDER}
rm -rf ${DIST_FOLDER}/${DIST_NAME}
pushd ${OUTDIR} > /dev/null
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} . -z <<< "$DIST_COMMENT"
popd > /dev/null
}
run_clion_init() {
# Init cmake.sh only
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
# Clion specific first-time setup
}
# Sanity checks
[ -d "external/cmake-various/toolchain" ] || { echo "Toolchain not found" ; exit 1; }
[ "$(md5sum ./build_all.sh | cut -c -32)" = "5c5d7152a1bff96962223458fdf690a2" ] || { echo "Version mismatch for build_all.sh " ; exit 1; }
# Run requested task
if [[ $(type -t "run_${1}_${2}") == function ]]; then
echo "Starting task: 'run_${1}_${2}'"
"run_${1}_${2}"
else
echo "Invalid task: 'run_${1}_${2}'"
exit 1
fi
@@ -1,72 +1,72 @@
# fails the build if a PE binary statically imports a forbidden DLL. # fails the build if a PE binary statically imports a forbidden DLL.
# #
# some DLLs must never end up in spice's static import table, for two reasons: # some DLLs must never end up in spice's static import table, for two reasons:
# #
# 1. user-overridable DLLs (e.g. DXVK's d3d9.dll): users drop their own copy # 1. user-overridable DLLs (e.g. DXVK's d3d9.dll): users drop their own copy
# into the modules directory to replace the system one. a static import # into the modules directory to replace the system one. a static import
# forces the loader to load the SYSTEM copy at process startup - before the # forces the loader to load the SYSTEM copy at process startup - before the
# modules directory is added to the DLL search path and before the game DLL # modules directory is added to the DLL search path and before the game DLL
# loads - so the user-supplied override never takes effect (see issue #779). # loads - so the user-supplied override never takes effect (see issue #779).
# #
# 2. DLLs that break games when present (e.g. Media Foundation: mf/mfplat/ # 2. DLLs that break games when present (e.g. Media Foundation: mf/mfplat/
# mfreadwrite): a static import loads them eagerly and breaks Unity games. # mfreadwrite): a static import loads them eagerly and breaks Unity games.
# #
# in both cases the DLL must instead be loaded dynamically (libutils::try_library # in both cases the DLL must instead be loaded dynamically (libutils::try_library
# / GetProcAddress / delay load) so it is only pulled in when actually needed. # / GetProcAddress / delay load) so it is only pulled in when actually needed.
# #
# invoked via `cmake -P` from a POST_BUILD step. required -D variables: # invoked via `cmake -P` from a POST_BUILD step. required -D variables:
# OBJDUMP - path to objdump (CMAKE_OBJDUMP) # OBJDUMP - path to objdump (CMAKE_OBJDUMP)
# TARGET_FILE - path to the PE binary to inspect # TARGET_FILE - path to the PE binary to inspect
# FORBIDDEN - semicolon-separated list of lowercase DLL names to reject # FORBIDDEN - semicolon-separated list of lowercase DLL names to reject
if(NOT OBJDUMP OR NOT EXISTS "${OBJDUMP}") if(NOT OBJDUMP OR NOT EXISTS "${OBJDUMP}")
message(WARNING message(WARNING
"check_no_static_dll_imports: objdump not found, skipping import check for ${TARGET_FILE}") "check_no_static_dll_imports: objdump not found, skipping import check for ${TARGET_FILE}")
return() return()
endif() endif()
execute_process( execute_process(
COMMAND "${OBJDUMP}" -p "${TARGET_FILE}" COMMAND "${OBJDUMP}" -p "${TARGET_FILE}"
OUTPUT_VARIABLE dump_output OUTPUT_VARIABLE dump_output
RESULT_VARIABLE dump_result RESULT_VARIABLE dump_result
ERROR_VARIABLE dump_error) ERROR_VARIABLE dump_error)
if(NOT dump_result EQUAL 0) if(NOT dump_result EQUAL 0)
message(WARNING message(WARNING
"check_no_static_dll_imports: objdump failed for ${TARGET_FILE}: ${dump_error}") "check_no_static_dll_imports: objdump failed for ${TARGET_FILE}: ${dump_error}")
return() return()
endif() endif()
# both GNU objdump and llvm-objdump print one "DLL Name: <name>" line per # both GNU objdump and llvm-objdump print one "DLL Name: <name>" line per
# statically imported DLL in their PE private-header dump. # statically imported DLL in their PE private-header dump.
string(REGEX MATCHALL "DLL Name:[ \t]*[^\n\r]+" dll_lines "${dump_output}") string(REGEX MATCHALL "DLL Name:[ \t]*[^\n\r]+" dll_lines "${dump_output}")
set(violations "") set(violations "")
foreach(line IN LISTS dll_lines) foreach(line IN LISTS dll_lines)
string(REGEX REPLACE "DLL Name:[ \t]*" "" dll_name "${line}") string(REGEX REPLACE "DLL Name:[ \t]*" "" dll_name "${line}")
string(STRIP "${dll_name}" dll_name) string(STRIP "${dll_name}" dll_name)
string(TOLOWER "${dll_name}" dll_name_lower) string(TOLOWER "${dll_name}" dll_name_lower)
if(dll_name_lower IN_LIST FORBIDDEN) if(dll_name_lower IN_LIST FORBIDDEN)
list(APPEND violations "${dll_name}") list(APPEND violations "${dll_name}")
endif() endif()
endforeach() endforeach()
if(violations) if(violations)
list(REMOVE_DUPLICATES violations) list(REMOVE_DUPLICATES violations)
string(REPLACE ";" ", " violations_str "${violations}") string(REPLACE ";" ", " violations_str "${violations}")
message(FATAL_ERROR message(FATAL_ERROR
"static DLL import check FAILED for ${TARGET_FILE}\n" "static DLL import check FAILED for ${TARGET_FILE}\n"
" forbidden static imports found: ${violations_str}\n" " forbidden static imports found: ${violations_str}\n"
"\n" "\n"
" these DLLs must never be statically imported by spice:\n" " these DLLs must never be statically imported by spice:\n"
" * user-overridable DLLs (e.g. DXVK d3d9.dll) - a static import loads the\n" " * user-overridable DLLs (e.g. DXVK d3d9.dll) - a static import loads the\n"
" system copy at startup and preempts the modules override (issue #779).\n" " system copy at startup and preempts the modules override (issue #779).\n"
" * Media Foundation DLLs (mf/mfplat/mfreadwrite) - a static import breaks\n" " * Media Foundation DLLs (mf/mfplat/mfreadwrite) - a static import breaks\n"
" Unity games.\n" " Unity games.\n"
"\n" "\n"
" fix: load the DLL dynamically instead - replace the direct API call with a\n" " fix: load the DLL dynamically instead - replace the direct API call with a\n"
" libutils::try_library() + libutils::try_proc() lookup (or a delay load), then\n" " libutils::try_library() + libutils::try_proc() lookup (or a delay load), then\n"
" call through the resolved function pointer.") " call through the resolved function pointer.")
endif() endif()
message(STATUS "static DLL import check passed for ${TARGET_FILE}") message(STATUS "static DLL import check passed for ${TARGET_FILE}")
+11
View File
@@ -7,6 +7,8 @@ RUN pacman --noconfirm -Syu git \
ninja \ ninja \
cmake \ cmake \
unzip \ unzip \
nasm \
wget \
mingw-w64-crt \ mingw-w64-crt \
mingw-w64-winpthreads \ mingw-w64-winpthreads \
mingw-w64-gcc \ mingw-w64-gcc \
@@ -23,3 +25,12 @@ ENV PATH="$PATH:/opt/llvm-mingw-xp/bin"
RUN curl -fsSL "https://github.com/mon/windows-dll-compat-checker/releases/download/v1.3/windows_dll_compat_checker-linux-x86_64.tar.xz" \ RUN curl -fsSL "https://github.com/mon/windows-dll-compat-checker/releases/download/v1.3/windows_dll_compat_checker-linux-x86_64.tar.xz" \
| tar -xJ -C /usr/local/bin | tar -xJ -C /usr/local/bin
# Stock makepkg.conf builds serially; this makes the AUR compiles below parallel.
RUN printf '%s\n' 'MAKEFLAGS="-j$(nproc)"' > /home/user/.makepkg.conf \
&& chown user: /home/user/.makepkg.conf
# libjpeg-turbo for JPEG encoding, x264 for the API H.264 video stream. Only the
# mingw-w64 toolchains get these; the WinXP targets build without JPEG support
# and without the stream encoder.
RUN su user -c "yay --noconfirm -S mingw-w64-libjpeg-turbo mingw-w64-x264"
+95 -95
View File
@@ -1,95 +1,95 @@
Copyright (c) 2017, keshikan (http://www.keshikan.net), Copyright (c) 2017, keshikan (http://www.keshikan.net),
with Reserved Font Name "DSEG". with Reserved Font Name "DSEG".
This Font Software is licensed under the SIL Open Font License, Version 1.1. This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL http://scripts.sil.org/OFL
----------------------------------------------------------- -----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
----------------------------------------------------------- -----------------------------------------------------------
PREAMBLE PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership open framework in which fonts may be shared and improved in partnership
with others. with others.
The OFL allows the licensed fonts to be used, studied, modified and The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives, names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives. to any document created using the fonts or their derivatives.
DEFINITIONS DEFINITIONS
"Font Software" refers to the set of files released by the Copyright "Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation. include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the "Reserved Font Name" refers to any names specified as such after the
copyright statement(s). copyright statement(s).
"Original Version" refers to the collection of Font Software components as "Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s). distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, "Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a Original Version, by changing formats or by porting the Font Software to a
new environment. new environment.
"Author" refers to any designer, engineer, programmer, technical "Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software. writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify, a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions: Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, 1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself. in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, 2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user. binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font 3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as Copyright Holder. This restriction only applies to the primary font name as
presented to the users. presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written Copyright Holder(s) and the Author(s) or with their explicit written
permission. permission.
5) The Font Software, modified or unmodified, in part or in whole, 5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created remain under this license does not apply to any document created
using the Font Software. using the Font Software.
TERMINATION TERMINATION
This license becomes null and void if any of the above conditions are This license becomes null and void if any of the above conditions are
not met. not met.
DISCLAIMER DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE. OTHER DEALINGS IN THE FONT SOFTWARE.
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+73 -73
View File
@@ -1,73 +1,73 @@
#ifndef EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD #ifndef EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD
#define EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD #define EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD
// This code comes from: // This code comes from:
// https://github.com/dhbaird/easywsclient // https://github.com/dhbaird/easywsclient
// //
// To get the latest version: // To get the latest version:
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp // wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp // wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp
#include <string> #include <string>
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
namespace easywsclient { namespace easywsclient {
struct Callback_Imp { virtual void operator()(const std::string& message) = 0; }; struct Callback_Imp { virtual void operator()(const std::string& message) = 0; };
struct BytesCallback_Imp { virtual void operator()(const std::vector<uint8_t>& message) = 0; }; struct BytesCallback_Imp { virtual void operator()(const std::vector<uint8_t>& message) = 0; };
class WebSocket { class WebSocket {
public: public:
typedef WebSocket * pointer; typedef WebSocket * pointer;
typedef enum readyStateValues { CLOSING, CLOSED, CONNECTING, OPEN } readyStateValues; typedef enum readyStateValues { CLOSING, CLOSED, CONNECTING, OPEN } readyStateValues;
// Factories: // Factories:
static pointer create_dummy(); static pointer create_dummy();
static pointer from_url(const std::string& url, const std::string& origin = std::string()); static pointer from_url(const std::string& url, const std::string& origin = std::string());
static pointer from_url_no_mask(const std::string& url, const std::string& origin = std::string()); static pointer from_url_no_mask(const std::string& url, const std::string& origin = std::string());
// Interfaces: // Interfaces:
virtual ~WebSocket() { } virtual ~WebSocket() { }
virtual void poll(int timeout = 0) = 0; // timeout in milliseconds virtual void poll(int timeout = 0) = 0; // timeout in milliseconds
virtual void send(const std::string& message) = 0; virtual void send(const std::string& message) = 0;
virtual void sendBinary(const std::string& message) = 0; virtual void sendBinary(const std::string& message) = 0;
virtual void sendBinary(const std::vector<uint8_t>& message) = 0; virtual void sendBinary(const std::vector<uint8_t>& message) = 0;
virtual void sendPing() = 0; virtual void sendPing() = 0;
virtual void close() = 0; virtual void close() = 0;
virtual readyStateValues getReadyState() const = 0; virtual readyStateValues getReadyState() const = 0;
template<class Callable> template<class Callable>
void dispatch(Callable callable) void dispatch(Callable callable)
// For callbacks that accept a string argument. // For callbacks that accept a string argument.
{ // N.B. this is compatible with both C++11 lambdas, functors and C function pointers { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers
struct _Callback : public Callback_Imp { struct _Callback : public Callback_Imp {
Callable& callable; Callable& callable;
_Callback(Callable& callable) : callable(callable) { } _Callback(Callable& callable) : callable(callable) { }
void operator()(const std::string& message) { callable(message); } void operator()(const std::string& message) { callable(message); }
}; };
_Callback callback(callable); _Callback callback(callable);
_dispatch(callback); _dispatch(callback);
} }
template<class Callable> template<class Callable>
void dispatchBinary(Callable callable) void dispatchBinary(Callable callable)
// For callbacks that accept a std::vector<uint8_t> argument. // For callbacks that accept a std::vector<uint8_t> argument.
{ // N.B. this is compatible with both C++11 lambdas, functors and C function pointers { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers
struct _Callback : public BytesCallback_Imp { struct _Callback : public BytesCallback_Imp {
Callable& callable; Callable& callable;
_Callback(Callable& callable) : callable(callable) { } _Callback(Callable& callable) : callable(callable) { }
void operator()(const std::vector<uint8_t>& message) { callable(message); } void operator()(const std::vector<uint8_t>& message) { callable(message); }
}; };
_Callback callback(callable); _Callback callback(callable);
_dispatchBinary(callback); _dispatchBinary(callback);
} }
protected: protected:
virtual void _dispatch(Callback_Imp& callable) = 0; virtual void _dispatch(Callback_Imp& callable) = 0;
virtual void _dispatchBinary(BytesCallback_Imp& callable) = 0; virtual void _dispatchBinary(BytesCallback_Imp& callable) = 0;
}; };
} // namespace easywsclient } // namespace easywsclient
#endif /* EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD */ #endif /* EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD */
+3222
View File
File diff suppressed because it is too large Load Diff
+122
View File
@@ -0,0 +1,122 @@
// fpng.h - unlicense (see end of fpng.cpp)
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <vector>
#ifndef FPNG_TRAIN_HUFFMAN_TABLES
// Set to 1 when using the -t (training) option in fpng_test to generate new opaque/alpha Huffman tables for the single pass encoder.
#define FPNG_TRAIN_HUFFMAN_TABLES (0)
#endif
namespace fpng
{
// ---- Library initialization - call once to identify if the processor supports SSE.
// Otherwise you'll only get scalar fallbacks.
void fpng_init();
// ---- Useful Utilities
// Returns true if the CPU supports SSE 4.1, and SSE support wasn't disabled by setting FPNG_NO_SSE=1.
// fpng_init() must have been called first, or it'll assert and return false.
bool fpng_cpu_supports_sse41();
// Fast CRC-32 SSE4.1+pclmul or a scalar fallback (slice by 4)
const uint32_t FPNG_CRC32_INIT = 0;
uint32_t fpng_crc32(const void* pData, size_t size, uint32_t prev_crc32 = FPNG_CRC32_INIT);
// Fast Adler32 SSE4.1 Adler-32 with a scalar fallback.
const uint32_t FPNG_ADLER32_INIT = 1;
uint32_t fpng_adler32(const void* pData, size_t size, uint32_t adler = FPNG_ADLER32_INIT);
// ---- Compression
enum
{
// Enables computing custom Huffman tables for each file, instead of using the custom global tables.
// Results in roughly 6% smaller files on average, but compression is around 40% slower.
FPNG_ENCODE_SLOWER = 1,
// Only use raw Deflate blocks (no compression at all). Intended for testing.
FPNG_FORCE_UNCOMPRESSED = 2,
};
// Fast PNG encoding. The resulting file can be decoded either using a standard PNG decoder or by the fpng_decode_memory() function below.
// pImage: pointer to RGB or RGBA image pixels, R first in memory, B/A last.
// w/h - image dimensions. Image's row pitch in bytes must is w*num_chans.
// num_chans must be 3 or 4.
bool fpng_encode_image_to_memory(const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, std::vector<uint8_t>& out_buf, uint32_t flags = 0);
#ifndef FPNG_NO_STDIO
// Fast PNG encoding to the specified file.
bool fpng_encode_image_to_file(const char* pFilename, const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, uint32_t flags = 0);
#endif
// ---- Decompression
enum
{
FPNG_DECODE_SUCCESS = 0, // file is a valid PNG file and written by FPNG and the decode succeeded
FPNG_DECODE_NOT_FPNG, // file is a valid PNG file, but it wasn't written by FPNG so you should try decoding it with a general purpose PNG decoder
FPNG_DECODE_INVALID_ARG, // invalid function parameter
FPNG_DECODE_FAILED_NOT_PNG, // file cannot be a PNG file
FPNG_DECODE_FAILED_HEADER_CRC32, // a chunk CRC32 check failed, file is likely corrupted or not PNG
FPNG_DECODE_FAILED_INVALID_DIMENSIONS, // invalid image dimensions in IHDR chunk (0 or too large)
FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE, // decoding the file fully into memory would likely require too much memory (only on 32bpp builds)
FPNG_DECODE_FAILED_CHUNK_PARSING, // failed while parsing the chunk headers, or file is corrupted
FPNG_DECODE_FAILED_INVALID_IDAT, // IDAT data length is too small and cannot be valid, file is either corrupted or it's a bug
// fpng_decode_file() specific errors
FPNG_DECODE_FILE_OPEN_FAILED,
FPNG_DECODE_FILE_TOO_LARGE,
FPNG_DECODE_FILE_READ_FAILED,
FPNG_DECODE_FILE_SEEK_FAILED
};
// Fast PNG decoding of files ONLY created by fpng_encode_image_to_memory() or fpng_encode_image_to_file().
// If fpng_get_info() or fpng_decode_memory() returns FPNG_DECODE_NOT_FPNG, you should decode the PNG by falling back to a general purpose decoder.
//
// fpng_get_info() parses the PNG header and iterates through all chunks to determine if it's a file written by FPNG, but does not decompress the actual image data so it's relatively fast.
//
// pImage, image_size: Pointer to PNG image data and its size
// width, height: output image's dimensions
// channels_in_file: will be 3 or 4
//
// Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above.
// If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder.
// If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail).
int fpng_get_info(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file);
// fpng_decode_memory() decompresses 24/32bpp PNG files ONLY encoded by this module.
// If the image was written by FPNG, it will decompress the image data, otherwise it will return FPNG_DECODE_NOT_FPNG in which case you should fall back to a general purpose PNG decoder (lodepng, stb_image, libpng, etc.)
//
// pImage, image_size: Pointer to PNG image data and its size
// out: Output 24/32bpp image buffer
// width, height: output image's dimensions
// channels_in_file: will be 3 or 4
// desired_channels: must be 3 or 4
//
// If the image is 24bpp and 32bpp is requested, the alpha values will be set to 0xFF.
// If the image is 32bpp and 24bpp is requested, the alpha values will be discarded.
//
// Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above.
// If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder.
// If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail).
int fpng_decode_memory(const void* pImage, uint32_t image_size, std::vector<uint8_t>& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels);
#ifndef FPNG_NO_STDIO
int fpng_decode_file(const char* pFilename, std::vector<uint8_t>& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels);
#endif
// ---- Internal API used for Huffman table training purposes
#if FPNG_TRAIN_HUFFMAN_TABLES
const uint32_t HUFF_COUNTS_SIZE = 288;
extern uint64_t g_huff_counts[HUFF_COUNTS_SIZE];
bool create_dynamic_block_prefix(uint64_t* pFreq, uint32_t num_chans, std::vector<uint8_t>& prefix, uint64_t& bit_buf, int& bit_buf_size, uint32_t *pCodes, uint8_t *pCodesizes);
#endif
} // namespace fpng
File diff suppressed because it is too large Load Diff
+62
View File
@@ -31,8 +31,65 @@
#include <thread> #include <thread>
#include "util/utils.h" #include "util/utils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/libutils.h"
#include "hooks/sleephook.h" #include "hooks/sleephook.h"
//_INFO: Lazy load winscard, not tested on actual hardware
struct Winscard
{
decltype(&::SCardEstablishContext) SCardEstablishContext;
decltype(&::SCardListReadersA) SCardListReadersA;
decltype(&::SCardFreeMemory) SCardFreeMemory;
decltype(&::SCardGetStatusChangeA) SCardGetStatusChangeA;
decltype(&::SCardConnectA) SCardConnectA;
decltype(&::SCardTransmit) SCardTransmit;
decltype(&::SCardStatusA) SCardStatusA;
decltype(&::SCardDisconnect) SCardDisconnect;
decltype(::g_rgSCardT0Pci)* g_rgSCardT0Pci;
decltype(::g_rgSCardT1Pci)* g_rgSCardT1Pci;
inline static Winscard *instance = nullptr;
static HMODULE attach()
{
if (instance) return instance->lib_;
instance = new Winscard();
return instance->lib_;
}
private:
HMODULE lib_;
Winscard()
{
#define GETPROC(name) (name) = reinterpret_cast<decltype(name)>(GetProcAddress(lib_, #name))
if ((lib_ = libutils::try_library("winscard.dll")))
{
GETPROC(SCardEstablishContext);
GETPROC(SCardListReadersA);
GETPROC(SCardFreeMemory);
GETPROC(SCardGetStatusChangeA);
GETPROC(SCardConnectA);
GETPROC(SCardTransmit);
GETPROC(SCardStatusA);
GETPROC(SCardDisconnect);
GETPROC(g_rgSCardT0Pci);
GETPROC(g_rgSCardT1Pci);
}
#undef GETPROC
}
};
#define SCardEstablishContext Winscard::instance->SCardEstablishContext
#define SCardListReadersA Winscard::instance->SCardListReadersA
#define SCardFreeMemory Winscard::instance->SCardFreeMemory
#define SCardGetStatusChangeA Winscard::instance->SCardGetStatusChangeA
#define SCardConnectA Winscard::instance->SCardConnectA
#define SCardTransmit Winscard::instance->SCardTransmit
#define SCardStatusA Winscard::instance->SCardStatusA
#define SCardDisconnect Winscard::instance->SCardDisconnect
#define g_rgSCardT0Pci (*Winscard::instance->g_rgSCardT0Pci)
#define g_rgSCardT1Pci (*Winscard::instance->g_rgSCardT1Pci)
#define MAX_APDU_SIZE 255 #define MAX_APDU_SIZE 255
#define SCARD_POLL_INTERVAL_MS 100 #define SCARD_POLL_INTERVAL_MS 100
@@ -419,6 +476,11 @@ int scard_threadmain() {
} }
void scard_threadstart() { void scard_threadstart() {
if(!Winscard::attach())
{
log_warning("scard", "Support disabled due to missing winscard.dll");
return;
}
std::thread t(scard_threadmain); std::thread t(scard_threadmain);
t.detach(); t.detach();
} }
-10
View File
@@ -1,10 +0,0 @@
zlib License
Copyright (c) 2011-2016 Stephan Brumme
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
-665
View File
@@ -1,665 +0,0 @@
// //////////////////////////////////////////////////////////
// toojpeg.cpp
// written by Stephan Brumme, 2018-2019
// see https://create.stephan-brumme.com/toojpeg/
//
#include "toojpeg.h"
// - the "official" specifications: https://www.w3.org/Graphics/JPEG/itu-t81.pdf and https://www.w3.org/Graphics/JPEG/jfif3.pdf
// - Wikipedia has a short description of the JFIF/JPEG file format: https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
// - the popular STB Image library includes Jon's JPEG encoder as well: https://github.com/nothings/stb/blob/master/stb_image_write.h
// - the most readable JPEG book (from a developer's perspective) is Miano's "Compressed Image File Formats" (1999, ISBN 0-201-60443-4),
// used copies are really cheap nowadays and include a CD with C++ sources as well (plus great format descriptions of GIF & PNG)
// - much more detailled is Mitchell/Pennebaker's "JPEG: Still Image Data Compression Standard" (1993, ISBN 0-442-01272-1)
// which contains the official JPEG standard, too - fun fact: I bought a signed copy in a second-hand store without noticing
namespace // anonymous namespace to hide local functions / constants / etc.
{
// ////////////////////////////////////////
// data types
using uint8_t = unsigned char;
using uint16_t = unsigned short;
using int16_t = short;
using int32_t = int; // at least four bytes
// ////////////////////////////////////////
// constants
// quantization tables from JPEG Standard, Annex K
const uint8_t DefaultQuantLuminance[8*8] =
{ 16, 11, 10, 16, 24, 40, 51, 61, // there are a few experts proposing slightly more efficient values,
12, 12, 14, 19, 26, 58, 60, 55, // e.g. https://www.imagemagick.org/discourse-server/viewtopic.php?t=20333
14, 13, 16, 24, 40, 57, 69, 56, // btw: Google's Guetzli project optimizes the quantization tables per image
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99 };
const uint8_t DefaultQuantChrominance[8*8] =
{ 17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99 };
// 8x8 blocks are processed in zig-zag order
// most encoders use a zig-zag "forward" table, I switched to its inverse for performance reasons
// note: ZigZagInv[ZigZag[i]] = i
const uint8_t ZigZagInv[8*8] =
{ 0, 1, 8,16, 9, 2, 3,10, // ZigZag[] = 0, 1, 5, 6,14,15,27,28,
17,24,32,25,18,11, 4, 5, // 2, 4, 7,13,16,26,29,42,
12,19,26,33,40,48,41,34, // 3, 8,12,17,25,30,41,43,
27,20,13, 6, 7,14,21,28, // 9,11,18,24,31,40,44,53,
35,42,49,56,57,50,43,36, // 10,19,23,32,39,45,52,54,
29,22,15,23,30,37,44,51, // 20,22,33,38,46,51,55,60,
58,59,52,45,38,31,39,46, // 21,34,37,47,50,56,59,61,
53,60,61,54,47,55,62,63 }; // 35,36,48,49,57,58,62,63
// static Huffman code tables from JPEG standard Annex K
// - CodesPerBitsize tables define how many Huffman codes will have a certain bitsize (plus 1 because there nothing with zero bits),
// e.g. DcLuminanceCodesPerBitsize[2] = 5 because there are 5 Huffman codes being 2+1=3 bits long
// - Values tables are a list of values ordered by their Huffman code bitsize,
// e.g. AcLuminanceValues => Huffman(0x01,0x02 and 0x03) will have 2 bits, Huffman(0x00) will have 3 bits, Huffman(0x04,0x11 and 0x05) will have 4 bits, ...
// Huffman definitions for first DC/AC tables (luminance / Y channel)
const uint8_t DcLuminanceCodesPerBitsize[16] = { 0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 }; // sum = 12
const uint8_t DcLuminanceValues [12] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; // => 12 codes
const uint8_t AcLuminanceCodesPerBitsize[16] = { 0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125 }; // sum = 162
const uint8_t AcLuminanceValues [162] = // => 162 codes
{ 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xA1,0x08, // 16*10+2 symbols because
0x23,0x42,0xB1,0xC1,0x15,0x52,0xD1,0xF0,0x24,0x33,0x62,0x72,0x82,0x09,0x0A,0x16,0x17,0x18,0x19,0x1A,0x25,0x26,0x27,0x28, // upper 4 bits can be 0..F
0x29,0x2A,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x53,0x54,0x55,0x56,0x57,0x58,0x59, // while lower 4 bits can be 1..A
0x5A,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x83,0x84,0x85,0x86,0x87,0x88,0x89, // plus two special codes 0x00 and 0xF0
0x8A,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xB2,0xB3,0xB4,0xB5,0xB6, // order of these symbols was determined empirically by JPEG committee
0xB7,0xB8,0xB9,0xBA,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xE1,0xE2,
0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA };
// Huffman definitions for second DC/AC tables (chrominance / Cb and Cr channels)
const uint8_t DcChrominanceCodesPerBitsize[16] = { 0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; // sum = 12
const uint8_t DcChrominanceValues [12] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; // => 12 codes (identical to DcLuminanceValues)
const uint8_t AcChrominanceCodesPerBitsize[16] = { 0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119 }; // sum = 162
const uint8_t AcChrominanceValues [162] = // => 162 codes
{ 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, // same number of symbol, just different order
0xA1,0xB1,0xC1,0x09,0x23,0x33,0x52,0xF0,0x15,0x62,0x72,0xD1,0x0A,0x16,0x24,0x34,0xE1,0x25,0xF1,0x17,0x18,0x19,0x1A,0x26, // (which is more efficient for AC coding)
0x27,0x28,0x29,0x2A,0x35,0x36,0x37,0x38,0x39,0x3A,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x53,0x54,0x55,0x56,0x57,0x58,
0x59,0x5A,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8A,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xB2,0xB3,0xB4,
0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,
0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA };
const int16_t CodeWordLimit = 2048; // +/-2^11, maximum value after DCT
// ////////////////////////////////////////
// structs
// represent a single Huffman code
struct BitCode
{
BitCode() = default; // undefined state, must be initialized at a later time
BitCode(uint16_t code_, uint8_t numBits_)
: code(code_), numBits(numBits_) {}
uint16_t code; // JPEG's Huffman codes are limited to 16 bits
uint8_t numBits; // number of valid bits
};
// wrapper for bit output operations
struct BitWriter
{
// user-supplied callback that writes/stores one byte
TooJpeg::WRITE_ONE_BYTE output;
// initialize writer
explicit BitWriter(TooJpeg::WRITE_ONE_BYTE output_) : output(output_) {}
// store the most recently encoded bits that are not written yet
struct BitBuffer
{
int32_t data = 0; // actually only at most 24 bits are used
uint8_t numBits = 0; // number of valid bits (the right-most bits)
} buffer;
// write Huffman bits stored in BitCode, keep excess bits in BitBuffer
BitWriter& operator<<(const BitCode& data)
{
// append the new bits to those bits leftover from previous call(s)
buffer.numBits += data.numBits;
buffer.data <<= data.numBits;
buffer.data |= data.code;
// write all "full" bytes
while (buffer.numBits >= 8)
{
// extract highest 8 bits
buffer.numBits -= 8;
auto oneByte = uint8_t(buffer.data >> buffer.numBits);
output(oneByte);
if (oneByte == 0xFF) // 0xFF has a special meaning for JPEGs (it's a block marker)
output(0); // therefore pad a zero to indicate "nope, this one ain't a marker, it's just a coincidence"
// note: I don't clear those written bits, therefore buffer.bits may contain garbage in the high bits
// if you really want to "clean up" (e.g. for debugging purposes) then uncomment the following line
//buffer.bits &= (1 << buffer.numBits) - 1;
}
return *this;
}
// write all non-yet-written bits, fill gaps with 1s (that's a strange JPEG thing)
void flush()
{
// at most seven set bits needed to "fill" the last byte: 0x7F = binary 0111 1111
*this << BitCode(0x7F, 7); // I should set buffer.numBits = 0 but since there are no single bits written after flush() I can safely ignore it
}
// NOTE: all the following BitWriter functions IGNORE the BitBuffer and write straight to output !
// write a single byte
BitWriter& operator<<(uint8_t oneByte)
{
output(oneByte);
return *this;
}
// write an array of bytes
template <typename T, int Size>
BitWriter& operator<<(T (&manyBytes)[Size])
{
for (auto c : manyBytes)
output(c);
return *this;
}
// start a new JFIF block
void addMarker(uint8_t id, uint16_t length)
{
output(0xFF); output(id); // ID, always preceded by 0xFF
output(uint8_t(length >> 8)); // length of the block (big-endian, includes the 2 length bytes as well)
output(uint8_t(length & 0xFF));
}
};
// ////////////////////////////////////////
// functions / templates
// same as std::min()
template <typename Number>
Number minimum(Number value, Number maximum)
{
return value <= maximum ? value : maximum;
}
// restrict a value to the interval [minimum, maximum]
template <typename Number, typename Limit>
Number clamp(Number value, Limit minValue, Limit maxValue)
{
if (value <= minValue) return minValue; // never smaller than the minimum
if (value >= maxValue) return maxValue; // never bigger than the maximum
return value; // value was inside interval, keep it
}
// convert from RGB to YCbCr, constants are similar to ITU-R, see https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion
float rgb2y (float r, float g, float b) { return +0.299f * r +0.587f * g +0.114f * b; }
float rgb2cb(float r, float g, float b) { return -0.16874f * r -0.33126f * g +0.5f * b; }
float rgb2cr(float r, float g, float b) { return +0.5f * r -0.41869f * g -0.08131f * b; }
// forward DCT computation "in one dimension" (fast AAN algorithm by Arai, Agui and Nakajima: "A fast DCT-SQ scheme for images")
void DCT(float block[8*8], uint8_t stride) // stride must be 1 (=horizontal) or 8 (=vertical)
{
const auto SqrtHalfSqrt = 1.306562965f; // sqrt((2 + sqrt(2)) / 2) = cos(pi * 1 / 8) * sqrt(2)
const auto InvSqrt = 0.707106781f; // 1 / sqrt(2) = cos(pi * 2 / 8)
const auto HalfSqrtSqrt = 0.382683432f; // sqrt(2 - sqrt(2)) / 2 = cos(pi * 3 / 8)
const auto InvSqrtSqrt = 0.541196100f; // 1 / sqrt(2 - sqrt(2)) = cos(pi * 3 / 8) * sqrt(2)
// modify in-place
auto& block0 = block[0 ];
auto& block1 = block[1 * stride];
auto& block2 = block[2 * stride];
auto& block3 = block[3 * stride];
auto& block4 = block[4 * stride];
auto& block5 = block[5 * stride];
auto& block6 = block[6 * stride];
auto& block7 = block[7 * stride];
// based on https://dev.w3.org/Amaya/libjpeg/jfdctflt.c , the original variable names can be found in my comments
auto add07 = block0 + block7; auto sub07 = block0 - block7; // tmp0, tmp7
auto add16 = block1 + block6; auto sub16 = block1 - block6; // tmp1, tmp6
auto add25 = block2 + block5; auto sub25 = block2 - block5; // tmp2, tmp5
auto add34 = block3 + block4; auto sub34 = block3 - block4; // tmp3, tmp4
auto add0347 = add07 + add34; auto sub07_34 = add07 - add34; // tmp10, tmp13 ("even part" / "phase 2")
auto add1256 = add16 + add25; auto sub16_25 = add16 - add25; // tmp11, tmp12
block0 = add0347 + add1256; block4 = add0347 - add1256; // "phase 3"
auto z1 = (sub16_25 + sub07_34) * InvSqrt; // all temporary z-variables kept their original names
block2 = sub07_34 + z1; block6 = sub07_34 - z1; // "phase 5"
auto sub23_45 = sub25 + sub34; // tmp10 ("odd part" / "phase 2")
auto sub12_56 = sub16 + sub25; // tmp11
auto sub01_67 = sub16 + sub07; // tmp12
auto z5 = (sub23_45 - sub01_67) * HalfSqrtSqrt;
auto z2 = sub23_45 * InvSqrtSqrt + z5;
auto z3 = sub12_56 * InvSqrt;
auto z4 = sub01_67 * SqrtHalfSqrt + z5;
auto z6 = sub07 + z3; // z11 ("phase 5")
auto z7 = sub07 - z3; // z13
block1 = z6 + z4; block7 = z6 - z4; // "phase 6"
block5 = z7 + z2; block3 = z7 - z2;
}
// run DCT, quantize and write Huffman bit codes
int16_t encodeBlock(BitWriter& writer, float block[8][8], const float scaled[8*8], int16_t lastDC,
const BitCode huffmanDC[256], const BitCode huffmanAC[256], const BitCode* codewords)
{
// "linearize" the 8x8 block, treat it as a flat array of 64 floats
auto block64 = (float*) block;
// DCT: rows
for (auto offset = 0; offset < 8; offset++)
DCT(block64 + offset*8, 1);
// DCT: columns
for (auto offset = 0; offset < 8; offset++)
DCT(block64 + offset*1, 8);
// scale
for (auto i = 0; i < 8*8; i++)
block64[i] *= scaled[i];
// encode DC (the first coefficient is the "average color" of the 8x8 block)
auto DC = int(block64[0] + (block64[0] >= 0 ? +0.5f : -0.5f)); // C++11's nearbyint() achieves a similar effect
// quantize and zigzag the other 63 coefficients
auto posNonZero = 0; // find last coefficient which is not zero (because trailing zeros are encoded differently)
int16_t quantized[8*8];
for (auto i = 1; i < 8*8; i++) // start at 1 because block64[0]=DC was already processed
{
auto value = block64[ZigZagInv[i]];
// round to nearest integer
quantized[i] = int(value + (value >= 0 ? +0.5f : -0.5f)); // C++11's nearbyint() achieves a similar effect
// remember offset of last non-zero coefficient
if (quantized[i] != 0)
posNonZero = i;
}
// same "average color" as previous block ?
auto diff = DC - lastDC;
if (diff == 0)
writer << huffmanDC[0x00]; // yes, write a special short symbol
else
{
auto bits = codewords[diff]; // nope, encode the difference to previous block's average color
writer << huffmanDC[bits.numBits] << bits;
}
// encode ACs (quantized[1..63])
auto offset = 0; // upper 4 bits count the number of consecutive zeros
for (auto i = 1; i <= posNonZero; i++) // quantized[0] was already written, skip all trailing zeros, too
{
// zeros are encoded in a special way
while (quantized[i] == 0) // found another zero ?
{
offset += 0x10; // add 1 to the upper 4 bits
// split into blocks of at most 16 consecutive zeros
if (offset > 0xF0) // remember, the counter is in the upper 4 bits, 0xF = 15
{
writer << huffmanAC[0xF0]; // 0xF0 is a special code for "16 zeros"
offset = 0;
}
i++;
}
auto encoded = codewords[quantized[i]];
// combine number of zeros with the number of bits of the next non-zero value
writer << huffmanAC[offset + encoded.numBits] << encoded; // and the value itself
offset = 0;
}
// send end-of-block code (0x00), only needed if there are trailing zeros
if (posNonZero < 8*8 - 1) // = 63
writer << huffmanAC[0x00];
return DC;
}
// Jon's code includes the pre-generated Huffman codes
// I don't like these "magic constants" and compute them on my own :-)
void generateHuffmanTable(const uint8_t numCodes[16], const uint8_t* values, BitCode result[256])
{
// process all bitsizes 1 thru 16, no JPEG Huffman code is allowed to exceed 16 bits
auto huffmanCode = 0;
for (auto numBits = 1; numBits <= 16; numBits++)
{
// ... and each code of these bitsizes
for (auto i = 0; i < numCodes[numBits - 1]; i++) // note: numCodes array starts at zero, but smallest bitsize is 1
result[*values++] = BitCode(huffmanCode++, numBits);
// next Huffman code needs to be one bit wider
huffmanCode <<= 1;
}
}
} // end of anonymous namespace
// -------------------- externally visible code --------------------
namespace TooJpeg
{
// the only exported function ...
bool writeJpeg(WRITE_ONE_BYTE output, const void* pixels_, unsigned short width, unsigned short height,
bool isRGB, unsigned char quality_, bool downsample, const char* comment)
{
// reject invalid pointers
if (output == nullptr || pixels_ == nullptr)
return false;
// check image format
if (width == 0 || height == 0)
return false;
// number of components
const auto numComponents = isRGB ? 3 : 1;
// note: if there is just one component (=grayscale), then only luminance needs to be stored in the file
// thus everything related to chrominance need not to be written to the JPEG
// I still compute a few things, like quantization tables to avoid a complete code mess
// grayscale images can't be downsampled (because there are no Cb + Cr channels)
if (!isRGB)
downsample = false;
// wrapper for all output operations
BitWriter bitWriter(output);
// ////////////////////////////////////////
// JFIF headers
const uint8_t HeaderJfif[2+2+16] =
{ 0xFF,0xD8, // SOI marker (start of image)
0xFF,0xE0, // JFIF APP0 tag
0,16, // length: 16 bytes (14 bytes payload + 2 bytes for this length field)
'J','F','I','F',0, // JFIF identifier, zero-terminated
1,1, // JFIF version 1.1
0, // no density units specified
0,1,0,1, // density: 1 pixel "per pixel" horizontally and vertically
0,0 }; // no thumbnail (size 0 x 0)
bitWriter << HeaderJfif;
// ////////////////////////////////////////
// comment (optional)
if (comment != nullptr)
{
// look for zero terminator
auto length = 0; // = strlen(comment);
while (comment[length] != 0)
length++;
// write COM marker
bitWriter.addMarker(0xFE, 2+length); // block size is number of bytes (without zero terminator) + 2 bytes for this length field
// ... and write the comment itself
for (auto i = 0; i < length; i++)
bitWriter << comment[i];
}
// ////////////////////////////////////////
// adjust quantization tables to desired quality
// quality level must be in 1 ... 100
auto quality = clamp<uint16_t>(quality_, 1, 100);
// convert to an internal JPEG quality factor, formula taken from libjpeg
quality = quality < 50 ? 5000 / quality : 200 - quality * 2;
uint8_t quantLuminance [8*8];
uint8_t quantChrominance[8*8];
for (auto i = 0; i < 8*8; i++)
{
int luminance = (DefaultQuantLuminance [ZigZagInv[i]] * quality + 50) / 100;
int chrominance = (DefaultQuantChrominance[ZigZagInv[i]] * quality + 50) / 100;
// clamp to 1..255
quantLuminance [i] = clamp(luminance, 1, 255);
quantChrominance[i] = clamp(chrominance, 1, 255);
}
// write quantization tables
bitWriter.addMarker(0xDB, 2 + (isRGB ? 2 : 1) * (1 + 8*8)); // length: 65 bytes per table + 2 bytes for this length field
// each table has 64 entries and is preceded by an ID byte
bitWriter << 0x00 << quantLuminance; // first quantization table
if (isRGB)
bitWriter << 0x01 << quantChrominance; // second quantization table, only relevant for color images
// ////////////////////////////////////////
// write image infos (SOF0 - start of frame)
bitWriter.addMarker(0xC0, 2+6+3*numComponents); // length: 6 bytes general info + 3 per channel + 2 bytes for this length field
// 8 bits per channel
bitWriter << 0x08
// image dimensions (big-endian)
<< (height >> 8) << (height & 0xFF)
<< (width >> 8) << (width & 0xFF);
// sampling and quantization tables for each component
bitWriter << numComponents; // 1 component (grayscale, Y only) or 3 components (Y,Cb,Cr)
for (auto id = 1; id <= numComponents; id++)
bitWriter << id // component ID (Y=1, Cb=2, Cr=3)
// bitmasks for sampling: highest 4 bits: horizontal, lowest 4 bits: vertical
<< (id == 1 && downsample ? 0x22 : 0x11) // 0x11 is default YCbCr 4:4:4 and 0x22 stands for YCbCr 4:2:0
<< (id == 1 ? 0 : 1); // use quantization table 0 for Y, table 1 for Cb and Cr
// ////////////////////////////////////////
// Huffman tables
// DHT marker - define Huffman tables
bitWriter.addMarker(0xC4, isRGB ? (2+208+208) : (2+208));
// 2 bytes for the length field, store chrominance only if needed
// 1+16+12 for the DC luminance
// 1+16+162 for the AC luminance (208 = 1+16+12 + 1+16+162)
// 1+16+12 for the DC chrominance
// 1+16+162 for the AC chrominance (208 = 1+16+12 + 1+16+162, same as above)
// store luminance's DC+AC Huffman table definitions
bitWriter << 0x00 // highest 4 bits: 0 => DC, lowest 4 bits: 0 => Y (baseline)
<< DcLuminanceCodesPerBitsize
<< DcLuminanceValues;
bitWriter << 0x10 // highest 4 bits: 1 => AC, lowest 4 bits: 0 => Y (baseline)
<< AcLuminanceCodesPerBitsize
<< AcLuminanceValues;
// compute actual Huffman code tables (see Jon's code for precalculated tables)
BitCode huffmanLuminanceDC[256];
BitCode huffmanLuminanceAC[256];
generateHuffmanTable(DcLuminanceCodesPerBitsize, DcLuminanceValues, huffmanLuminanceDC);
generateHuffmanTable(AcLuminanceCodesPerBitsize, AcLuminanceValues, huffmanLuminanceAC);
// chrominance is only relevant for color images
BitCode huffmanChrominanceDC[256];
BitCode huffmanChrominanceAC[256];
if (isRGB)
{
// store luminance's DC+AC Huffman table definitions
bitWriter << 0x01 // highest 4 bits: 0 => DC, lowest 4 bits: 1 => Cr,Cb (baseline)
<< DcChrominanceCodesPerBitsize
<< DcChrominanceValues;
bitWriter << 0x11 // highest 4 bits: 1 => AC, lowest 4 bits: 1 => Cr,Cb (baseline)
<< AcChrominanceCodesPerBitsize
<< AcChrominanceValues;
// compute actual Huffman code tables (see Jon's code for precalculated tables)
generateHuffmanTable(DcChrominanceCodesPerBitsize, DcChrominanceValues, huffmanChrominanceDC);
generateHuffmanTable(AcChrominanceCodesPerBitsize, AcChrominanceValues, huffmanChrominanceAC);
}
// ////////////////////////////////////////
// start of scan (there is only a single scan for baseline JPEGs)
bitWriter.addMarker(0xDA, 2+1+2*numComponents+3); // 2 bytes for the length field, 1 byte for number of components,
// then 2 bytes for each component and 3 bytes for spectral selection
// assign Huffman tables to each component
bitWriter << numComponents;
for (auto id = 1; id <= numComponents; id++)
// highest 4 bits: DC Huffman table, lowest 4 bits: AC Huffman table
bitWriter << id << (id == 1 ? 0x00 : 0x11); // Y: tables 0 for DC and AC; Cb + Cr: tables 1 for DC and AC
// constant values for our baseline JPEGs (which have a single sequential scan)
static const uint8_t Spectral[3] = { 0, 63, 0 }; // spectral selection: must be from 0 to 63; successive approximation must be 0
bitWriter << Spectral;
// ////////////////////////////////////////
// adjust quantization tables with AAN scaling factors to simplify DCT
float scaledLuminance [8*8];
float scaledChrominance[8*8];
for (auto i = 0; i < 8*8; i++)
{
auto row = ZigZagInv[i] / 8; // same as ZigZagInv[i] >> 3
auto column = ZigZagInv[i] % 8; // same as ZigZagInv[i] & 7
// scaling constants for AAN DCT algorithm: AanScaleFactors[0] = 1, AanScaleFactors[k=1..7] = cos(k*PI/16) * sqrt(2)
static const float AanScaleFactors[8] = { 1, 1.387039845f, 1.306562965f, 1.175875602f, 1, 0.785694958f, 0.541196100f, 0.275899379f };
auto factor = 1 / (AanScaleFactors[row] * AanScaleFactors[column] * 8);
scaledLuminance [ZigZagInv[i]] = factor / quantLuminance [i];
scaledChrominance[ZigZagInv[i]] = factor / quantChrominance[i];
// if you really want JPEGs that are bitwise identical to Jon Olick's code then you need slightly different formulas (note: sqrt(8) = 2.828427125f)
//static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; // line 240 of jo_jpeg.cpp
//scaledLuminance [ZigZagInv[i]] = 1 / (quantLuminance [i] * aasf[row] * aasf[column]); // lines 266-267 of jo_jpeg.cpp
//scaledChrominance[ZigZagInv[i]] = 1 / (quantChrominance[i] * aasf[row] * aasf[column]);
}
// ////////////////////////////////////////
// precompute JPEG codewords for quantized DCT
BitCode codewordsArray[2 * CodeWordLimit]; // note: quantized[i] is found at codewordsArray[quantized[i] + CodeWordLimit]
BitCode* codewords = &codewordsArray[CodeWordLimit]; // allow negative indices, so quantized[i] is at codewords[quantized[i]]
uint8_t numBits = 1; // each codeword has at least one bit (value == 0 is undefined)
int32_t mask = 1; // mask is always 2^numBits - 1, initial value 2^1-1 = 2-1 = 1
for (int16_t value = 1; value < CodeWordLimit; value++)
{
// numBits = position of highest set bit (ignoring the sign)
// mask = (2^numBits) - 1
if (value > mask) // one more bit ?
{
numBits++;
mask = (mask << 1) | 1; // append a set bit
}
codewords[-value] = BitCode(mask - value, numBits); // note that I use a negative index => codewords[-value] = codewordsArray[CodeWordLimit value]
codewords[+value] = BitCode( value, numBits);
}
// just convert image data from void*
auto pixels = (const uint8_t*)pixels_;
// the next two variables are frequently used when checking for image borders
const auto maxWidth = width - 1; // "last row"
const auto maxHeight = height - 1; // "bottom line"
// process MCUs (minimum codes units) => image is subdivided into a grid of 8x8 or 16x16 tiles
const auto sampling = downsample ? 2 : 1; // 1x1 or 2x2 sampling
const auto mcuSize = 8 * sampling;
// average color of the previous MCU
int16_t lastYDC = 0, lastCbDC = 0, lastCrDC = 0;
// convert from RGB to YCbCr
float Y[8][8], Cb[8][8], Cr[8][8];
for (auto mcuY = 0; mcuY < height; mcuY += mcuSize) // each step is either 8 or 16 (=mcuSize)
for (auto mcuX = 0; mcuX < width; mcuX += mcuSize)
{
// YCbCr 4:4:4 format: each MCU is a 8x8 block - the same applies to grayscale images, too
// YCbCr 4:2:0 format: each MCU represents a 16x16 block, stored as 4x 8x8 Y-blocks plus 1x 8x8 Cb and 1x 8x8 Cr block)
for (auto blockY = 0; blockY < mcuSize; blockY += 8) // iterate once (YCbCr444 and grayscale) or twice (YCbCr420)
for (auto blockX = 0; blockX < mcuSize; blockX += 8)
{
// now we finally have an 8x8 block ...
for (auto deltaY = 0; deltaY < 8; deltaY++)
{
auto column = minimum(mcuX + blockX , maxWidth); // must not exceed image borders, replicate last row/column if needed
auto row = minimum(mcuY + blockY + deltaY, maxHeight);
for (auto deltaX = 0; deltaX < 8; deltaX++)
{
// find actual pixel position within the current image
auto pixelPos = row * int(width) + column; // the cast ensures that we don't run into multiplication overflows
if (column < maxWidth)
column++;
// grayscale images have solely a Y channel which can be easily derived from the input pixel by shifting it by 128
if (!isRGB)
{
Y[deltaY][deltaX] = pixels[pixelPos] - 128.f;
continue;
}
// RGB: 3 bytes per pixel (whereas grayscale images have only 1 byte per pixel)
auto r = pixels[3 * pixelPos ];
auto g = pixels[3 * pixelPos + 1];
auto b = pixels[3 * pixelPos + 2];
Y [deltaY][deltaX] = rgb2y (r, g, b) - 128; // again, the JPEG standard requires Y to be shifted by 128
// YCbCr444 is easy - the more complex YCbCr420 has to be computed about 20 lines below in a second pass
if (!downsample)
{
Cb[deltaY][deltaX] = rgb2cb(r, g, b); // standard RGB-to-YCbCr conversion
Cr[deltaY][deltaX] = rgb2cr(r, g, b);
}
}
}
// encode Y channel
lastYDC = encodeBlock(bitWriter, Y, scaledLuminance, lastYDC, huffmanLuminanceDC, huffmanLuminanceAC, codewords);
// Cb and Cr are encoded about 50 lines below
}
// grayscale images don't need any Cb and Cr information
if (!isRGB)
continue;
// ////////////////////////////////////////
// the following lines are only relevant for YCbCr420:
// average/downsample chrominance of four pixels while respecting the image borders
if (downsample)
for (short deltaY = 7; downsample && deltaY >= 0; deltaY--) // iterating loop in reverse increases cache read efficiency
{
auto row = minimum(mcuY + 2*deltaY, maxHeight); // each deltaX/Y step covers a 2x2 area
auto column = mcuX; // column is updated inside next loop
auto pixelPos = (row * int(width) + column) * 3; // numComponents = 3
// deltas (in bytes) to next row / column, must not exceed image borders
auto rowStep = (row < maxHeight) ? 3 * int(width) : 0; // always numComponents*width except for bottom line
auto columnStep = (column < maxWidth ) ? 3 : 0; // always numComponents except for rightmost pixel
for (short deltaX = 0; deltaX < 8; deltaX++)
{
// let's add all four samples (2x2 area)
auto right = pixelPos + columnStep;
auto down = pixelPos + rowStep;
auto downRight = pixelPos + columnStep + rowStep;
// note: cast from 8 bits to >8 bits to avoid overflows when adding
auto r = short(pixels[pixelPos ]) + pixels[right ] + pixels[down ] + pixels[downRight ];
auto g = short(pixels[pixelPos + 1]) + pixels[right + 1] + pixels[down + 1] + pixels[downRight + 1];
auto b = short(pixels[pixelPos + 2]) + pixels[right + 2] + pixels[down + 2] + pixels[downRight + 2];
// convert to Cb and Cr
Cb[deltaY][deltaX] = rgb2cb(r, g, b) / 4; // I still have to divide r,g,b by 4 to get their average values
Cr[deltaY][deltaX] = rgb2cr(r, g, b) / 4; // it's a bit faster if done AFTER CbCr conversion
// step forward to next 2x2 area
pixelPos += 2*3; // 2 pixels => 6 bytes (2*numComponents)
column += 2;
// reached right border ?
if (column >= maxWidth)
{
columnStep = 0;
pixelPos = ((row + 1) * int(width) - 1) * 3; // same as (row * width + maxWidth) * numComponents => current's row last pixel
}
}
} // end of YCbCr420 code for Cb and Cr
// encode Cb and Cr
lastCbDC = encodeBlock(bitWriter, Cb, scaledChrominance, lastCbDC, huffmanChrominanceDC, huffmanChrominanceAC, codewords);
lastCrDC = encodeBlock(bitWriter, Cr, scaledChrominance, lastCrDC, huffmanChrominanceDC, huffmanChrominanceAC, codewords);
}
bitWriter.flush(); // now image is completely encoded, write any bits still left in the buffer
// ///////////////////////////
// EOI marker
bitWriter << 0xFF << 0xD9; // this marker has no length, therefore I can't use addMarker()
return true;
} // writeJpeg()
} // namespace TooJpeg
-62
View File
@@ -1,62 +0,0 @@
// //////////////////////////////////////////////////////////
// toojpeg.h
// written by Stephan Brumme, 2018-2019
// see https://create.stephan-brumme.com/toojpeg/
//
// This is a compact baseline JPEG/JFIF writer, written in C++ (but looks like C for the most part).
// Its interface has only one function: writeJpeg() - and that's it !
//
// basic example:
// => create an image with any content you like, e.g. 1024x768, RGB = 3 bytes per pixel
// auto pixels = new unsigned char[1024*768*3];
// => you need to define a callback that receives the compressed data byte-by-byte from my JPEG writer
// void myOutput(unsigned char oneByte) { fputc(oneByte, myFileHandle); } // save byte to file
// => let's go !
// TooJpeg::writeJpeg(myOutput, mypixels, 1024, 768);
#pragma once
namespace TooJpeg
{
// write one byte (to disk, memory, ...)
typedef void (*WRITE_ONE_BYTE)(unsigned char);
// this callback is called for every byte generated by the encoder and behaves similar to fputc
// if you prefer stylish C++11 syntax then it can be a lambda, too:
// auto myOutput = [](unsigned char oneByte) { fputc(oneByte, output); };
// output - callback that stores a single byte (writes to disk, memory, ...)
// pixels - stored in RGB format or grayscale, stored from upper-left to lower-right
// width,height - image size
// isRGB - true if RGB format (3 bytes per pixel); false if grayscale (1 byte per pixel)
// quality - between 1 (worst) and 100 (best)
// downsample - if true then YCbCr 4:2:0 format is used (smaller size, minor quality loss) instead of 4:4:4, not relevant for grayscale
// comment - optional JPEG comment (0/NULL if no comment), must not contain ASCII code 0xFF
bool writeJpeg(WRITE_ONE_BYTE output, const void* pixels, unsigned short width, unsigned short height,
bool isRGB = true, unsigned char quality = 90, bool downsample = false, const char* comment = nullptr);
} // namespace TooJpeg
// My main inspiration was Jon Olick's Minimalistic JPEG writer
// ( https://www.jonolick.com/code.html => direct link is https://www.jonolick.com/uploads/7/9/2/1/7921194/jo_jpeg.cpp ).
// However, his code documentation is quite sparse - probably because it wasn't written from scratch and is (quote:) "based on a javascript jpeg writer",
// most likely Andreas Ritter's code: https://github.com/eugeneware/jpeg-js/blob/master/lib/encoder.js
//
// Therefore I wrote the whole lib from scratch and tried hard to add tons of comments to my code, especially describing where all those magic numbers come from.
// And I managed to remove the need for any external includes ...
// yes, that's right: my library has no (!) includes at all, not even #include <stdlib.h>
// Depending on your callback WRITE_ONE_BYTE, the library writes either to disk, or in-memory, or wherever you wish.
// Moreover, no dynamic memory allocations are performed, just a few bytes on the stack.
//
// In contrast to Jon's code, compression can be significantly improved in many use cases:
// a) grayscale JPEG images need just a single Y channel, no need to save the superfluous Cb + Cr channels
// b) YCbCr 4:2:0 downsampling is often about 20% more efficient (=smaller) than the default YCbCr 4:4:4 with only little visual loss
//
// TooJpeg 1.2+ compresses about twice as fast as jo_jpeg (and about half as fast as libjpeg-turbo).
// A few benchmark numbers can be found on my website https://create.stephan-brumme.com/toojpeg/#benchmark
//
// Last but not least you can optionally add a JPEG comment.
//
// Your C++ compiler needs to support a reasonable subset of C++11 (g++ 4.7 or Visual C++ 2013 are sufficient).
// I haven't tested the code on big-endian systems or anything that smells like an apple.
//
// USE AT YOUR OWN RISK. Because you are a brave soul :-)
+4
View File
@@ -5,6 +5,10 @@ games::Game::Game(std::string name) {
this->name = name; this->name = name;
} }
const char *games::Game::title() {
return this->name.c_str();
}
void games::Game::attach() { void games::Game::attach() {
log_info("game", "attach: {}", name); log_info("game", "attach: {}", name);
} }
+1
View File
@@ -15,6 +15,7 @@ namespace games {
// where the main magic will happen // where the main magic will happen
virtual void attach(); virtual void attach();
virtual const char *title();
// optional // optional
virtual void pre_attach(); virtual void pre_attach();
+171 -171
View File
@@ -1,171 +1,171 @@
#include "asio.h" #include "asio.h"
#include <windows.h> #include <windows.h>
#include <cstring> #include <cstring>
#include "avs/game.h" #include "avs/game.h"
#include "gitadora.h" #include "gitadora.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
namespace games::gitadora { namespace games::gitadora {
// Redirects the game's hard-coded "XONAR" ASIO driver lookup to the // Redirects the game's hard-coded "XONAR" ASIO driver lookup to the
// driver name in ASIO_DRIVER by intercepting registry calls to // driver name in ASIO_DRIVER by intercepting registry calls to
// HKLM\SOFTWARE\ASIO. Sentinel HKEY values mark the redirected handles // HKLM\SOFTWARE\ASIO. Sentinel HKEY values mark the redirected handles
// so we can recognise them on subsequent reg* calls. // so we can recognise them on subsequent reg* calls.
static const HKEY PARENT_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x4001); static const HKEY PARENT_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x4001);
static const HKEY DEVICE_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x4002); static const HKEY DEVICE_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x4002);
static const char *FAKE_ASIO_DEVICE_NAME = "XONAR"; static const char *FAKE_ASIO_DEVICE_NAME = "XONAR";
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr; static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr; static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
static decltype(RegOpenKeyA) *RegOpenKeyA_orig = nullptr; static decltype(RegOpenKeyA) *RegOpenKeyA_orig = nullptr;
static decltype(RegOpenKeyExA) *RegOpenKeyExA_orig = nullptr; static decltype(RegOpenKeyExA) *RegOpenKeyExA_orig = nullptr;
static decltype(RegQueryValueExA) *RegQueryValueExA_orig = nullptr; static decltype(RegQueryValueExA) *RegQueryValueExA_orig = nullptr;
static HKEY real_asio_reg_handle = nullptr; static HKEY real_asio_reg_handle = nullptr;
static HKEY real_asio_device_reg_handle = nullptr; static HKEY real_asio_device_reg_handle = nullptr;
static LONG WINAPI RegOpenKeyExA_hook(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, static LONG WINAPI RegOpenKeyExA_hook(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
PHKEY phkResult) PHKEY phkResult)
{ {
if (ASIO_DRIVER.has_value() && if (ASIO_DRIVER.has_value() &&
lpSubKey != nullptr && lpSubKey != nullptr &&
phkResult != nullptr && phkResult != nullptr &&
hKey == PARENT_ASIO_REG_HANDLE && hKey == PARENT_ASIO_REG_HANDLE &&
_stricmp(lpSubKey, FAKE_ASIO_DEVICE_NAME) == 0) { _stricmp(lpSubKey, FAKE_ASIO_DEVICE_NAME) == 0) {
*phkResult = DEVICE_ASIO_REG_HANDLE; *phkResult = DEVICE_ASIO_REG_HANDLE;
log_info("gitadora::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value()); log_info("gitadora::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
const auto result = RegOpenKeyExA_orig( const auto result = RegOpenKeyExA_orig(
real_asio_reg_handle, real_asio_reg_handle,
ASIO_DRIVER.value().c_str(), ASIO_DRIVER.value().c_str(),
ulOptions, ulOptions,
samDesired, samDesired,
&real_asio_device_reg_handle); &real_asio_device_reg_handle);
if (result != ERROR_SUCCESS) { if (result != ERROR_SUCCESS) {
log_warning( log_warning(
"gitadora::asio", "gitadora::asio",
"failed to open registry subkey '{}', error=0x{:x}", "failed to open registry subkey '{}', error=0x{:x}",
ASIO_DRIVER.value(), result); ASIO_DRIVER.value(), result);
log_warning( log_warning(
"gitadora::asio", "gitadora::asio",
"due to improper ASIO setting, audio init will fail"); "due to improper ASIO setting, audio init will fail");
} }
return result; return result;
} }
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult); return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
} }
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) { static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
if (ASIO_DRIVER.has_value() && if (ASIO_DRIVER.has_value() &&
lpSubKey != nullptr && lpSubKey != nullptr &&
phkResult != nullptr && phkResult != nullptr &&
hKey == HKEY_LOCAL_MACHINE && hKey == HKEY_LOCAL_MACHINE &&
_stricmp(lpSubKey, "software\\asio") == 0) _stricmp(lpSubKey, "software\\asio") == 0)
{ {
*phkResult = PARENT_ASIO_REG_HANDLE; *phkResult = PARENT_ASIO_REG_HANDLE;
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle); return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
} }
return RegOpenKeyA_orig(hKey, lpSubKey, phkResult); return RegOpenKeyA_orig(hKey, lpSubKey, phkResult);
} }
static LONG WINAPI RegEnumKeyA_hook(HKEY hKey, DWORD dwIndex, LPSTR lpName, DWORD cchName) { static LONG WINAPI RegEnumKeyA_hook(HKEY hKey, DWORD dwIndex, LPSTR lpName, DWORD cchName) {
if (hKey == PARENT_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) { if (hKey == PARENT_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
if (dwIndex == 0) { if (dwIndex == 0) {
// forward to real handle just to verify the key exists; we // forward to real handle just to verify the key exists; we
// overwrite the name with our fake driver string regardless // overwrite the name with our fake driver string regardless
auto ret = RegEnumKeyA_orig(real_asio_reg_handle, dwIndex, lpName, cchName); auto ret = RegEnumKeyA_orig(real_asio_reg_handle, dwIndex, lpName, cchName);
if (ret == ERROR_SUCCESS && lpName != nullptr && cchName > 0) { if (ret == ERROR_SUCCESS && lpName != nullptr && cchName > 0) {
log_info("gitadora::asio", "stubbing '{}' with '{}'", lpName, FAKE_ASIO_DEVICE_NAME); log_info("gitadora::asio", "stubbing '{}' with '{}'", lpName, FAKE_ASIO_DEVICE_NAME);
strncpy(lpName, FAKE_ASIO_DEVICE_NAME, cchName); strncpy(lpName, FAKE_ASIO_DEVICE_NAME, cchName);
lpName[cchName - 1] = '\0'; lpName[cchName - 1] = '\0';
} }
return ret; return ret;
} else { } else {
return ERROR_NO_MORE_ITEMS; return ERROR_NO_MORE_ITEMS;
} }
} }
return RegEnumKeyA_orig(hKey, dwIndex, lpName, cchName); return RegEnumKeyA_orig(hKey, dwIndex, lpName, cchName);
} }
static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
LPBYTE lpData, LPDWORD lpcbData) LPBYTE lpData, LPDWORD lpcbData)
{ {
HKEY target = hKey; HKEY target = hKey;
if (ASIO_DRIVER.has_value() && if (ASIO_DRIVER.has_value() &&
lpValueName != nullptr && lpValueName != nullptr &&
lpData != nullptr && lpData != nullptr &&
lpcbData != nullptr && lpcbData != nullptr &&
hKey == DEVICE_ASIO_REG_HANDLE) { hKey == DEVICE_ASIO_REG_HANDLE) {
if (_stricmp(lpValueName, "Description") == 0) { if (_stricmp(lpValueName, "Description") == 0) {
// engine may verify the driver name after open; ensure it still // engine may verify the driver name after open; ensure it still
// sees something containing "XONAR" so the substring check passes // sees something containing "XONAR" so the substring check passes
const size_t len = strlen(FAKE_ASIO_DEVICE_NAME) + 1; const size_t len = strlen(FAKE_ASIO_DEVICE_NAME) + 1;
if (*lpcbData < len) { if (*lpcbData < len) {
*lpcbData = static_cast<DWORD>(len); *lpcbData = static_cast<DWORD>(len);
return ERROR_MORE_DATA; return ERROR_MORE_DATA;
} }
memcpy(lpData, FAKE_ASIO_DEVICE_NAME, len); memcpy(lpData, FAKE_ASIO_DEVICE_NAME, len);
*lpcbData = static_cast<DWORD>(len); *lpcbData = static_cast<DWORD>(len);
if (lpType != nullptr) { if (lpType != nullptr) {
*lpType = REG_SZ; *lpType = REG_SZ;
} }
return ERROR_SUCCESS; return ERROR_SUCCESS;
} }
// for everything else (CLSID etc.) defer to the real driver subkey // for everything else (CLSID etc.) defer to the real driver subkey
target = real_asio_device_reg_handle; target = real_asio_device_reg_handle;
} }
return RegQueryValueExA_orig(target, lpValueName, lpReserved, lpType, lpData, lpcbData); return RegQueryValueExA_orig(target, lpValueName, lpReserved, lpType, lpData, lpcbData);
} }
static LONG WINAPI RegCloseKey_hook(HKEY hKey) { static LONG WINAPI RegCloseKey_hook(HKEY hKey) {
if (hKey == PARENT_ASIO_REG_HANDLE) { if (hKey == PARENT_ASIO_REG_HANDLE) {
if (real_asio_reg_handle != nullptr) { if (real_asio_reg_handle != nullptr) {
RegCloseKey_orig(real_asio_reg_handle); RegCloseKey_orig(real_asio_reg_handle);
real_asio_reg_handle = nullptr; real_asio_reg_handle = nullptr;
} }
return ERROR_SUCCESS; return ERROR_SUCCESS;
} }
if (hKey == DEVICE_ASIO_REG_HANDLE) { if (hKey == DEVICE_ASIO_REG_HANDLE) {
if (real_asio_device_reg_handle != nullptr) { if (real_asio_device_reg_handle != nullptr) {
RegCloseKey_orig(real_asio_device_reg_handle); RegCloseKey_orig(real_asio_device_reg_handle);
real_asio_device_reg_handle = nullptr; real_asio_device_reg_handle = nullptr;
} }
return ERROR_SUCCESS; return ERROR_SUCCESS;
} }
return RegCloseKey_orig(hKey); return RegCloseKey_orig(hKey);
} }
void asio_hook_init() { void asio_hook_init() {
if (!ASIO_DRIVER.has_value()) { if (!ASIO_DRIVER.has_value()) {
return; return;
} }
log_info("gitadora::asio", "installing ASIO driver redirect: XONAR -> {}", ASIO_DRIVER.value()); log_info("gitadora::asio", "installing ASIO driver redirect: XONAR -> {}", ASIO_DRIVER.value());
RegCloseKey_orig = detour::iat_try( RegCloseKey_orig = detour::iat_try(
"RegCloseKey", RegCloseKey_hook, avs::game::DLL_INSTANCE); "RegCloseKey", RegCloseKey_hook, avs::game::DLL_INSTANCE);
RegEnumKeyA_orig = detour::iat_try( RegEnumKeyA_orig = detour::iat_try(
"RegEnumKeyA", RegEnumKeyA_hook, avs::game::DLL_INSTANCE); "RegEnumKeyA", RegEnumKeyA_hook, avs::game::DLL_INSTANCE);
RegOpenKeyA_orig = detour::iat_try( RegOpenKeyA_orig = detour::iat_try(
"RegOpenKeyA", RegOpenKeyA_hook, avs::game::DLL_INSTANCE); "RegOpenKeyA", RegOpenKeyA_hook, avs::game::DLL_INSTANCE);
RegOpenKeyExA_orig = detour::iat_try( RegOpenKeyExA_orig = detour::iat_try(
"RegOpenKeyExA", RegOpenKeyExA_hook, avs::game::DLL_INSTANCE); "RegOpenKeyExA", RegOpenKeyExA_hook, avs::game::DLL_INSTANCE);
RegQueryValueExA_orig = detour::iat_try( RegQueryValueExA_orig = detour::iat_try(
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE); "RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
} }
} }
+12 -12
View File
@@ -1,12 +1,12 @@
#pragma once #pragma once
namespace games::gitadora { namespace games::gitadora {
// installs IAT registry hooks in gfdm.dll that redirect the game's // installs IAT registry hooks in gfdm.dll that redirect the game's
// ASIO driver lookup (hard-coded "XONAR" substring) to a user-chosen // ASIO driver lookup (hard-coded "XONAR" substring) to a user-chosen
// driver name read from games::gitadora::ASIO_DRIVER. // driver name read from games::gitadora::ASIO_DRIVER.
// //
// safe to call unconditionally; if ASIO_DRIVER is unset the hooks // safe to call unconditionally; if ASIO_DRIVER is unset the hooks
// forward every call straight through to advapi32. // forward every call straight through to advapi32.
void asio_hook_init(); void asio_hook_init();
} }
+11 -11
View File
@@ -41,6 +41,7 @@ namespace games::gitadora {
bool ARENA_TWO_HEAD_EXCLUSIVE = false; bool ARENA_TWO_HEAD_EXCLUSIVE = false;
std::optional<std::string> ASIO_DRIVER = std::nullopt; std::optional<std::string> ASIO_DRIVER = std::nullopt;
bool ALLOW_REALTEK_AUDIO = false; bool ALLOW_REALTEK_AUDIO = false;
bool NATIVE_TOUCH = false;
/* /*
* Prevent GitaDora from creating folders on F drive * Prevent GitaDora from creating folders on F drive
@@ -809,17 +810,16 @@ namespace games::gitadora {
hooks::audio::INJECT_FAKE_REALTEK_AUDIO = true; hooks::audio::INJECT_FAKE_REALTEK_AUDIO = true;
} }
// Single-window mode needs touch injection for its overlay. Two-head fullscreen // touch injection drives mouse-as-touch and API touch for the subscreen,
// mode uses the real SMALL output, so it only needs the display-topology shim. // no matter whether it is drawn by the overlay (single-window mode) or by
if (GRAPHICS_PREVENT_SECONDARY_WINDOWS) { // the dedicated SMALL window
// enable touch hook for subscreen overlay NATIVE_TOUCH = !wintouchemu::FORCE &&
const auto native_touch_ready = !wintouchemu::FORCE && nativetouch::hook(avs::game::DLL_INSTANCE);
nativetouch::hook(avs::game::DLL_INSTANCE); if (!NATIVE_TOUCH && GRAPHICS_PREVENT_SECONDARY_WINDOWS) {
if (!native_touch_ready) { // the legacy fallback can only feed the subscreen overlay
wintouchemu::FORCE = true; wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true; wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE); wintouchemu::hook("GITADORA", avs::game::DLL_INSTANCE);
}
} }
#if !SPICE_XP #if !SPICE_XP
+5
View File
@@ -23,6 +23,11 @@ namespace games::gitadora {
extern bool ARENA_TWO_HEAD_EXCLUSIVE; extern bool ARENA_TWO_HEAD_EXCLUSIVE;
extern std::optional<std::string> ASIO_DRIVER; extern std::optional<std::string> ASIO_DRIVER;
extern bool ALLOW_REALTEK_AUDIO; extern bool ALLOW_REALTEK_AUDIO;
extern bool NATIVE_TOUCH;
// arena SMALL subscreen (touch panel) resolution
static constexpr int ARENA_SUBSCREEN_WIDTH = 800;
static constexpr int ARENA_SUBSCREEN_HEIGHT = 1280;
class GitaDoraGame : public games::Game { class GitaDoraGame : public games::Game {
public: public:
+7 -4
View File
@@ -3,6 +3,7 @@
#if SPICE64 #if SPICE64
#include <cstdint> #include <cstdint>
#include "api/client.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
@@ -443,10 +444,12 @@ namespace games::iidx {
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
for (unsigned int i = 0; i < data_size; ++i) { if (api::has_clients()) {
map.data[i].r = data[i * 3]; for (size_t i = 0; i < data_size; ++i) {
map.data[i].g = data[i * 3 + 1]; map.data[i].r = data[i * 3];
map.data[i].b = data[i * 3 + 2]; map.data[i].g = data[i * 3 + 1];
map.data[i].b = data[i * 3 + 2];
}
} }
} }
+26 -4
View File
@@ -279,6 +279,26 @@ namespace games::iidx {
return nullptr; return nullptr;
} }
// best-effort read of a TDJ ROM file to determine if the game is running in TDJ mode
//
// these paths are not emulated - they hit whatever is actually mounted at that drive letter.
// an empty optical or removable drive raises the modal "insert a disk" error (the launcher
// clears SEM_FAILCRITICALERRORS process-wide) and a downed network drive stalls on redirector
// timeouts, so only probe what a TDJ cabinet would actually be laid out on.
// drive_path must be absolute and start with a drive letter.
static bool tdj_rom_matches(const char *drive_path, const char *expected) {
const wchar_t root[] = { (wchar_t) drive_path[0], L':', L'\\', L'\0' };
const auto drive_type = GetDriveTypeW(root);
if (drive_type != DRIVE_FIXED && drive_type != DRIVE_RAMDISK) {
log_misc("iidx", "not probing '{}' for TDJ, not a local disk (drive type {})",
drive_path, drive_type);
return false;
}
return fileutils::text_read(drive_path) == expected;
}
#endif #endif
IIDXGame::IIDXGame() : Game("Beatmania IIDX") { IIDXGame::IIDXGame() : Game("Beatmania IIDX") {
@@ -339,8 +359,10 @@ namespace games::iidx {
HAS_LIBAIO = true; HAS_LIBAIO = true;
// check TDJ mode // check TDJ mode
TDJ_MODE |= fileutils::text_read("C:\\000rom.txt") == "TDJ-JA"; if (!TDJ_MODE) {
TDJ_MODE |= fileutils::text_read("D:\\001rom.txt") == "TDJ"; TDJ_MODE = tdj_rom_matches("C:\\000rom.txt", "TDJ-JA")
|| tdj_rom_matches("D:\\001rom.txt", "TDJ");
}
// force TDJ mode // force TDJ mode
if (TDJ_MODE) { if (TDJ_MODE) {
@@ -490,8 +512,8 @@ namespace games::iidx {
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead"); log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
} }
// check -dxmainadapter + TDJ mode // check -monitor + TDJ mode
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) { if (false && !GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && TDJ_MODE) {
IIDX_TDJ_MONITOR_WARNING = true; IIDX_TDJ_MONITOR_WARNING = true;
log_warning( log_warning(
"iidx", "iidx",
+131 -131
View File
@@ -1,132 +1,132 @@
#include "mf_wrappers.h" #include "mf_wrappers.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
namespace games::iidx { namespace games::iidx {
static bool INITIALIZED = false; static bool INITIALIZED = false;
static HMODULE mf_dll = nullptr; static HMODULE mf_dll = nullptr;
static HMODULE mfreadwrite_dll = nullptr; static HMODULE mfreadwrite_dll = nullptr;
static HMODULE mfplat_dll = nullptr; static HMODULE mfplat_dll = nullptr;
typedef HRESULT (__stdcall * MFCreateAttributes_t)( typedef HRESULT (__stdcall * MFCreateAttributes_t)(
_Out_ IMFAttributes** ppMFAttributes, _Out_ IMFAttributes** ppMFAttributes,
_In_ UINT32 cInitialSize _In_ UINT32 cInitialSize
); );
typedef HRESULT (__stdcall * MFCreateMediaType_t)( typedef HRESULT (__stdcall * MFCreateMediaType_t)(
_Out_ IMFMediaType** ppMFType _Out_ IMFMediaType** ppMFType
); );
typedef HRESULT (__stdcall * MFEnumDeviceSources_t)( typedef HRESULT (__stdcall * MFEnumDeviceSources_t)(
_In_ IMFAttributes* pAttributes, _In_ IMFAttributes* pAttributes,
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate, _Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
_Out_ UINT32* pcSourceActivate _Out_ UINT32* pcSourceActivate
); );
typedef HRESULT (__stdcall * MFCreateSourceReaderFromMediaSource_t)( typedef HRESULT (__stdcall * MFCreateSourceReaderFromMediaSource_t)(
_In_ IMFMediaSource *pMediaSource, _In_ IMFMediaSource *pMediaSource,
_In_opt_ IMFAttributes *pAttributes, _In_opt_ IMFAttributes *pAttributes,
_Out_ IMFSourceReader **ppSourceReader _Out_ IMFSourceReader **ppSourceReader
); );
typedef HRESULT (__stdcall * MFGetService_t)( typedef HRESULT (__stdcall * MFGetService_t)(
IUnknown* punkObject, IUnknown* punkObject,
REFGUID guidService, REFGUID guidService,
REFIID riid, REFIID riid,
_Outptr_ LPVOID* ppvObject _Outptr_ LPVOID* ppvObject
); );
static MFCreateAttributes_t MFCreateAttributes = nullptr; static MFCreateAttributes_t MFCreateAttributes = nullptr;
static MFCreateMediaType_t MFCreateMediaType = nullptr; static MFCreateMediaType_t MFCreateMediaType = nullptr;
static MFEnumDeviceSources_t MFEnumDeviceSources = nullptr; static MFEnumDeviceSources_t MFEnumDeviceSources = nullptr;
static MFCreateSourceReaderFromMediaSource_t MFCreateSourceReaderFromMediaSource = nullptr; static MFCreateSourceReaderFromMediaSource_t MFCreateSourceReaderFromMediaSource = nullptr;
static MFGetService_t MFGetService = nullptr; static MFGetService_t MFGetService = nullptr;
void init_mf_library() { void init_mf_library() {
// why was all of this needed? // why was all of this needed?
// //
// when iidx camhook was initially implemented, we linked to mf.lib, mfreadwrite.lib, and mfplat.lib // when iidx camhook was initially implemented, we linked to mf.lib, mfreadwrite.lib, and mfplat.lib
// this made Unity-based really unhappy, causing them to skip over the logic that loads mf library // this made Unity-based really unhappy, causing them to skip over the logic that loads mf library
// causing videos to not play ("Initializing Microsoft Media Foundation failed." in the cmd prompt) // causing videos to not play ("Initializing Microsoft Media Foundation failed." in the cmd prompt)
// //
// as a result, the static linking to mf libs were removed, and we are now doing the mess that is this file // as a result, the static linking to mf libs were removed, and we are now doing the mess that is this file
if (INITIALIZED) { if (INITIALIZED) {
return; return;
} }
INITIALIZED = true; INITIALIZED = true;
log_misc("mf_wrappers", "creating delay-loaded wrappers for MF routines - BEGIN"); log_misc("mf_wrappers", "creating delay-loaded wrappers for MF routines - BEGIN");
mf_dll = libutils::load_library("mf.dll", true); mf_dll = libutils::load_library("mf.dll", true);
mfreadwrite_dll = libutils::load_library("mfreadwrite.dll", true); mfreadwrite_dll = libutils::load_library("mfreadwrite.dll", true);
mfplat_dll = libutils::load_library("mfplat.dll", true); mfplat_dll = libutils::load_library("mfplat.dll", true);
MFCreateAttributes = (MFCreateAttributes_t) MFCreateAttributes = (MFCreateAttributes_t)
libutils::get_proc(mfplat_dll, "MFCreateAttributes"); libutils::get_proc(mfplat_dll, "MFCreateAttributes");
if (!MFCreateAttributes) { if (!MFCreateAttributes) {
log_fatal("mf_wrappers", "MFCreateAttributes failed to hook"); log_fatal("mf_wrappers", "MFCreateAttributes failed to hook");
} }
MFCreateMediaType = (MFCreateMediaType_t) MFCreateMediaType = (MFCreateMediaType_t)
libutils::get_proc(mfplat_dll, "MFCreateMediaType"); libutils::get_proc(mfplat_dll, "MFCreateMediaType");
if (!MFCreateMediaType) { if (!MFCreateMediaType) {
log_fatal("mf_wrappers", "MFCreateMediaType failed to hook"); log_fatal("mf_wrappers", "MFCreateMediaType failed to hook");
} }
MFEnumDeviceSources = (MFEnumDeviceSources_t) MFEnumDeviceSources = (MFEnumDeviceSources_t)
libutils::get_proc(mf_dll, "MFEnumDeviceSources"); libutils::get_proc(mf_dll, "MFEnumDeviceSources");
if (!MFEnumDeviceSources) { if (!MFEnumDeviceSources) {
log_fatal("mf_wrappers", "MFEnumDeviceSources failed to hook"); log_fatal("mf_wrappers", "MFEnumDeviceSources failed to hook");
} }
MFCreateSourceReaderFromMediaSource = (MFCreateSourceReaderFromMediaSource_t) MFCreateSourceReaderFromMediaSource = (MFCreateSourceReaderFromMediaSource_t)
libutils::get_proc(mfreadwrite_dll, "MFCreateSourceReaderFromMediaSource"); libutils::get_proc(mfreadwrite_dll, "MFCreateSourceReaderFromMediaSource");
if (!MFCreateSourceReaderFromMediaSource) { if (!MFCreateSourceReaderFromMediaSource) {
log_fatal("mf_wrappers", "MFCreateSourceReaderFromMediaSource failed to hook"); log_fatal("mf_wrappers", "MFCreateSourceReaderFromMediaSource failed to hook");
} }
MFGetService = (MFGetService_t)libutils::get_proc(mf_dll, "MFGetService"); MFGetService = (MFGetService_t)libutils::get_proc(mf_dll, "MFGetService");
if (!MFGetService) { if (!MFGetService) {
log_fatal("mf_wrappers", "MFGetService failed to hook"); log_fatal("mf_wrappers", "MFGetService failed to hook");
} }
log_misc("mf_wrappers", "creating delay-loaded wrappers for MF routines - DONE"); log_misc("mf_wrappers", "creating delay-loaded wrappers for MF routines - DONE");
} }
HRESULT WrappedMFCreateAttributes ( HRESULT WrappedMFCreateAttributes (
_Out_ IMFAttributes** ppMFAttributes, _Out_ IMFAttributes** ppMFAttributes,
_In_ UINT32 cInitialSize) { _In_ UINT32 cInitialSize) {
return MFCreateAttributes(ppMFAttributes, cInitialSize); return MFCreateAttributes(ppMFAttributes, cInitialSize);
} }
HRESULT WrappedMFCreateMediaType ( HRESULT WrappedMFCreateMediaType (
_Out_ IMFMediaType** ppMFType) { _Out_ IMFMediaType** ppMFType) {
return MFCreateMediaType(ppMFType); return MFCreateMediaType(ppMFType);
} }
HRESULT WrappedMFEnumDeviceSources ( HRESULT WrappedMFEnumDeviceSources (
_In_ IMFAttributes* pAttributes, _In_ IMFAttributes* pAttributes,
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate, _Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
_Out_ UINT32* pcSourceActivate) { _Out_ UINT32* pcSourceActivate) {
return MFEnumDeviceSources(pAttributes, pppSourceActivate, pcSourceActivate); return MFEnumDeviceSources(pAttributes, pppSourceActivate, pcSourceActivate);
} }
HRESULT WrappedMFCreateSourceReaderFromMediaSource ( HRESULT WrappedMFCreateSourceReaderFromMediaSource (
_In_ IMFMediaSource *pMediaSource, _In_ IMFMediaSource *pMediaSource,
_In_opt_ IMFAttributes *pAttributes, _In_opt_ IMFAttributes *pAttributes,
_Out_ IMFSourceReader **ppSourceReader) { _Out_ IMFSourceReader **ppSourceReader) {
return MFCreateSourceReaderFromMediaSource(pMediaSource, pAttributes, ppSourceReader); return MFCreateSourceReaderFromMediaSource(pMediaSource, pAttributes, ppSourceReader);
} }
HRESULT WrappedMFGetService ( HRESULT WrappedMFGetService (
IUnknown* punkObject, IUnknown* punkObject,
REFGUID guidService, REFGUID guidService,
REFIID riid, REFIID riid,
_Outptr_ LPVOID* ppvObject) { _Outptr_ LPVOID* ppvObject) {
return MFGetService(punkObject, guidService, riid, ppvObject); return MFGetService(punkObject, guidService, riid, ppvObject);
} }
} }
+32 -32
View File
@@ -1,33 +1,33 @@
#include <mfapi.h> #include <mfapi.h>
#include <mfidl.h> #include <mfidl.h>
#include <mfreadwrite.h> #include <mfreadwrite.h>
#include <mfobjects.h> #include <mfobjects.h>
#pragma once #pragma once
namespace games::iidx { namespace games::iidx {
void init_mf_library(); void init_mf_library();
HRESULT WrappedMFCreateAttributes ( HRESULT WrappedMFCreateAttributes (
_Out_ IMFAttributes** ppMFAttributes, _Out_ IMFAttributes** ppMFAttributes,
_In_ UINT32 cInitialSize); _In_ UINT32 cInitialSize);
HRESULT WrappedMFCreateMediaType ( HRESULT WrappedMFCreateMediaType (
_Out_ IMFMediaType** ppMFType); _Out_ IMFMediaType** ppMFType);
HRESULT WrappedMFEnumDeviceSources ( HRESULT WrappedMFEnumDeviceSources (
_In_ IMFAttributes* pAttributes, _In_ IMFAttributes* pAttributes,
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate, _Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
_Out_ UINT32* pcSourceActivate); _Out_ UINT32* pcSourceActivate);
HRESULT WrappedMFCreateSourceReaderFromMediaSource ( HRESULT WrappedMFCreateSourceReaderFromMediaSource (
_In_ IMFMediaSource *pMediaSource, _In_ IMFMediaSource *pMediaSource,
_In_opt_ IMFAttributes *pAttributes, _In_opt_ IMFAttributes *pAttributes,
_Out_ IMFSourceReader **ppSourceReader); _Out_ IMFSourceReader **ppSourceReader);
HRESULT WrappedMFGetService ( HRESULT WrappedMFGetService (
IUnknown* punkObject, IUnknown* punkObject,
REFGUID guidService, REFGUID guidService,
REFIID riid, REFIID riid,
_Outptr_ LPVOID* ppvObject); _Outptr_ LPVOID* ppvObject);
} }
+233 -233
View File
@@ -1,233 +1,233 @@
#include "touch_mode.h" #include "touch_mode.h"
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <unordered_map> #include <unordered_map>
#include "touch/native/nativetouchhook.h" #include "touch/native/nativetouchhook.h"
#include "util/logging.h" #include "util/logging.h"
namespace games::nost::touch_mode { namespace games::nost::touch_mode {
// native contact positions feed piano input directly. native events reach the game // native contact positions feed piano input directly. native events reach the game
// in nav mode and are suppressed in piano mode. mode-button contacts are always // in nav mode and are suppressed in piano mode. mode-button contacts are always
// suppressed and change that routing after release. // suppressed and change that routing after release.
static constexpr LONG PIANO_LEFT_GAP = 11; static constexpr LONG PIANO_LEFT_GAP = 11;
static constexpr LONG PIANO_RIGHT_GAP = 10; static constexpr LONG PIANO_RIGHT_GAP = 10;
static constexpr uint32_t PIANO_KEY_COUNT = 28; static constexpr uint32_t PIANO_KEY_COUNT = 28;
struct TouchGeometry { struct TouchGeometry {
HWND window = nullptr; HWND window = nullptr;
RECT mode_button {}; RECT mode_button {};
LONG client_width = 0; LONG client_width = 0;
LONG client_height = 0; LONG client_height = 0;
bool valid() const { bool valid() const {
return window != nullptr && client_width > 0 && client_height > 0; return window != nullptr && client_width > 0 && client_height > 0;
} }
}; };
struct NativeContact { struct NativeContact {
POINT position {}; POINT position {};
// position contains game-client coordinates // position contains game-client coordinates
bool client_position_valid = false; bool client_position_valid = false;
// down began on the mode switch button; remains true through up // down began on the mode switch button; remains true through up
bool mode_button = false; bool mode_button = false;
}; };
static std::atomic_bool accept_events { false }; static std::atomic_bool accept_events { false };
static std::atomic<Mode> current_mode_state { Mode::Nav }; static std::atomic<Mode> current_mode_state { Mode::Nav };
static std::mutex state_mutex; static std::mutex state_mutex;
static TouchGeometry touch_geometry; static TouchGeometry touch_geometry;
// native contacts are kept by ID so each contact contributes exactly one position // native contacts are kept by ID so each contact contributes exactly one position
static std::unordered_map<DWORD, NativeContact> active_contacts; static std::unordered_map<DWORD, NativeContact> active_contacts;
// a hardware button release requests one change after all contacts are released // a hardware button release requests one change after all contacts are released
static bool mode_change_pending = false; static bool mode_change_pending = false;
static void reset_state_locked() { static void reset_state_locked() {
current_mode_state.store(Mode::Nav, std::memory_order_release); current_mode_state.store(Mode::Nav, std::memory_order_release);
touch_geometry = {}; touch_geometry = {};
active_contacts.clear(); active_contacts.clear();
mode_change_pending = false; mode_change_pending = false;
} }
// hardware contacts arrive in screen coordinates // hardware contacts arrive in screen coordinates
static bool native_touch_in_button(const nativetouch::NativeTouchEvent &event) { static bool native_touch_in_button(const nativetouch::NativeTouchEvent &event) {
if (!touch_geometry.valid()) { if (!touch_geometry.valid()) {
return false; return false;
} }
POINT position { event.x, event.y }; POINT position { event.x, event.y };
if (!ScreenToClient(touch_geometry.window, &position)) { if (!ScreenToClient(touch_geometry.window, &position)) {
return false; return false;
} }
return PtInRect(&touch_geometry.mode_button, position) != FALSE; return PtInRect(&touch_geometry.mode_button, position) != FALSE;
} }
static bool update_touch_state(const nativetouch::NativeTouchEvent &event) { static bool update_touch_state(const nativetouch::NativeTouchEvent &event) {
std::lock_guard<std::mutex> lock(state_mutex); std::lock_guard<std::mutex> lock(state_mutex);
// first, process down / move events // first, process down / move events
if (event.down || event.move) { if (event.down || event.move) {
auto contact = active_contacts.try_emplace(event.id).first; auto contact = active_contacts.try_emplace(event.id).first;
// keep track of IDs that began as a down on the mode switch button // keep track of IDs that began as a down on the mode switch button
if (event.down) { if (event.down) {
contact->second.mode_button = native_touch_in_button(event); contact->second.mode_button = native_touch_in_button(event);
} }
// check for valid position // check for valid position
POINT position { event.x, event.y }; POINT position { event.x, event.y };
if (touch_geometry.window != nullptr && if (touch_geometry.window != nullptr &&
ScreenToClient(touch_geometry.window, &position)) { ScreenToClient(touch_geometry.window, &position)) {
contact->second.position = position; contact->second.position = position;
contact->second.client_position_valid = true; contact->second.client_position_valid = true;
} }
} }
const auto contact = active_contacts.find(event.id); const auto contact = active_contacts.find(event.id);
const bool mode_button_contact = contact != active_contacts.end() && const bool mode_button_contact = contact != active_contacts.end() &&
contact->second.mode_button; contact->second.mode_button;
// process up events // process up events
if (event.up) { if (event.up) {
active_contacts.erase(event.id); active_contacts.erase(event.id);
// if a contact that began down event on the mode switch button has // if a contact that began down event on the mode switch button has
// been released, a mode switch is now pending // been released, a mode switch is now pending
if (mode_button_contact) { if (mode_button_contact) {
mode_change_pending = true; mode_change_pending = true;
} }
// apply the change on the final hardware up. switching earlier would // apply the change on the final hardware up. switching earlier would
// split another contact's down and up events across different modes // split another contact's down and up events across different modes
if (mode_change_pending && active_contacts.empty()) { if (mode_change_pending && active_contacts.empty()) {
mode_change_pending = false; mode_change_pending = false;
const auto next_mode = current_mode() == Mode::Nav ? Mode::Piano : Mode::Nav; const auto next_mode = current_mode() == Mode::Nav ? Mode::Piano : Mode::Nav;
current_mode_state.store(next_mode, std::memory_order_release); current_mode_state.store(next_mode, std::memory_order_release);
} }
} }
return mode_button_contact; return mode_button_contact;
} }
// install the Nostalgia-specific native touch interception // install the Nostalgia-specific native touch interception
void enable() { void enable() {
if (accept_events.exchange(true, std::memory_order_acq_rel)) { if (accept_events.exchange(true, std::memory_order_acq_rel)) {
return; return;
} }
{ {
std::lock_guard<std::mutex> lock(state_mutex); std::lock_guard<std::mutex> lock(state_mutex);
reset_state_locked(); reset_state_locked();
} }
nativetouch::set_input_filter(filter_native_touch); nativetouch::set_input_filter(filter_native_touch);
log_info("nost::touch", "enabled"); log_info("nost::touch", "enabled");
} }
void disable() { void disable() {
if (!accept_events.exchange(false, std::memory_order_acq_rel)) { if (!accept_events.exchange(false, std::memory_order_acq_rel)) {
return; return;
} }
nativetouch::set_input_filter(nullptr); nativetouch::set_input_filter(nullptr);
std::lock_guard<std::mutex> lock(state_mutex); std::lock_guard<std::mutex> lock(state_mutex);
reset_state_locked(); reset_state_locked();
} }
bool enabled() { bool enabled() {
return accept_events.load(std::memory_order_acquire); return accept_events.load(std::memory_order_acquire);
} }
Mode current_mode() { Mode current_mode() {
return current_mode_state.load(std::memory_order_acquire); return current_mode_state.load(std::memory_order_acquire);
} }
// publish the rendered overlay button rectangle in game-client pixels // publish the rendered overlay button rectangle in game-client pixels
void publish_button_bounds(HWND window, const RECT &client_bounds) { void publish_button_bounds(HWND window, const RECT &client_bounds) {
TouchGeometry next {}; TouchGeometry next {};
RECT client_rect {}; RECT client_rect {};
if (window != nullptr && GetClientRect(window, &client_rect) && if (window != nullptr && GetClientRect(window, &client_rect) &&
client_rect.right > 0 && client_rect.bottom > 0) { client_rect.right > 0 && client_rect.bottom > 0) {
next.window = window; next.window = window;
next.mode_button = client_bounds; next.mode_button = client_bounds;
next.client_width = client_rect.right; next.client_width = client_rect.right;
next.client_height = client_rect.bottom; next.client_height = client_rect.bottom;
} }
std::lock_guard<std::mutex> lock(state_mutex); std::lock_guard<std::mutex> lock(state_mutex);
touch_geometry = next; touch_geometry = next;
} }
// return the active 28-key piano bitfield for the PANB input update // return the active 28-key piano bitfield for the PANB input update
uint32_t piano_key_state() { uint32_t piano_key_state() {
if (!enabled() || current_mode() != Mode::Piano) { if (!enabled() || current_mode() != Mode::Piano) {
return 0; return 0;
} }
std::lock_guard<std::mutex> lock(state_mutex); std::lock_guard<std::mutex> lock(state_mutex);
if (current_mode() != Mode::Piano || !touch_geometry.valid()) { if (current_mode() != Mode::Piano || !touch_geometry.valid()) {
return 0; return 0;
} }
uint32_t state = 0; uint32_t state = 0;
for (const auto &contact : active_contacts) { for (const auto &contact : active_contacts) {
// invalid position or mode-button contact; ignore these contacts // invalid position or mode-button contact; ignore these contacts
if (!contact.second.client_position_valid || contact.second.mode_button) { if (!contact.second.client_position_valid || contact.second.mode_button) {
continue; continue;
} }
const auto &position = contact.second.position; const auto &position = contact.second.position;
// outside the client area or on the mode button; ignore these contacts // outside the client area or on the mode button; ignore these contacts
if (position.x < 0 || position.x >= touch_geometry.client_width || if (position.x < 0 || position.x >= touch_geometry.client_width ||
position.y < 0 || position.y >= touch_geometry.client_height || position.y < 0 || position.y >= touch_geometry.client_height ||
PtInRect(&touch_geometry.mode_button, position)) { PtInRect(&touch_geometry.mode_button, position)) {
continue; continue;
} }
// divide the inset width evenly; touches in either side gap clamp to // divide the inset width evenly; touches in either side gap clamp to
// the nearest outer key so the physical screen edges remain playable // the nearest outer key so the physical screen edges remain playable
const auto piano_width = const auto piano_width =
touch_geometry.client_width - PIANO_LEFT_GAP - PIANO_RIGHT_GAP; touch_geometry.client_width - PIANO_LEFT_GAP - PIANO_RIGHT_GAP;
uint32_t key = 0; uint32_t key = 0;
if (position.x >= touch_geometry.client_width - PIANO_RIGHT_GAP) { if (position.x >= touch_geometry.client_width - PIANO_RIGHT_GAP) {
key = PIANO_KEY_COUNT - 1; key = PIANO_KEY_COUNT - 1;
} else if (position.x >= PIANO_LEFT_GAP && piano_width > 0) { } else if (position.x >= PIANO_LEFT_GAP && piano_width > 0) {
key = static_cast<uint32_t>( key = static_cast<uint32_t>(
(position.x - PIANO_LEFT_GAP) * PIANO_KEY_COUNT / piano_width); (position.x - PIANO_LEFT_GAP) * PIANO_KEY_COUNT / piano_width);
} }
state |= UINT32_C(1) << key; state |= UINT32_C(1) << key;
} }
return state; return state;
} }
// update native contacts and report whether this event should be hidden from the game // update native contacts and report whether this event should be hidden from the game
bool filter_native_touch(const nativetouch::NativeTouchEvent &event) { bool filter_native_touch(const nativetouch::NativeTouchEvent &event) {
// synthetic events are outside this hardware-only feature // synthetic events are outside this hardware-only feature
if (!enabled() || event.synthetic) { if (!enabled() || event.synthetic) {
// false leaves the event visible to the game // false leaves the event visible to the game
return false; return false;
} }
// snapshot routing before an up event can commit a pending mode switch // snapshot routing before an up event can commit a pending mode switch
const bool piano_mode_before_update = current_mode() == Mode::Piano; const bool piano_mode_before_update = current_mode() == Mode::Piano;
// update the contact lifetime and commit any pending switch when safe // update the contact lifetime and commit any pending switch when safe
const bool mode_button_contact = update_touch_state(event); const bool mode_button_contact = update_touch_state(event);
// hide every event in a contact that began on the mode switch button // hide every event in a contact that began on the mode switch button
if (mode_button_contact) { if (mode_button_contact) {
return true; return true;
} }
// piano mode consumes hardware events; nav mode forwards them to the game // piano mode consumes hardware events; nav mode forwards them to the game
return piano_mode_before_update; return piano_mode_before_update;
} }
} }
+27 -27
View File
@@ -1,27 +1,27 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <windows.h> #include <windows.h>
#include "touch/native/nativetouchhook.h" #include "touch/native/nativetouchhook.h"
namespace games::nost::touch_mode { namespace games::nost::touch_mode {
// nav mode forwards contacts to the game; piano mode converts them into piano keys // nav mode forwards contacts to the game; piano mode converts them into piano keys
enum class Mode { enum class Mode {
Nav, Nav,
Piano, Piano,
}; };
void enable(); void enable();
void disable(); void disable();
bool enabled(); bool enabled();
Mode current_mode(); Mode current_mode();
void publish_button_bounds(HWND window, const RECT &client_bounds); void publish_button_bounds(HWND window, const RECT &client_bounds);
uint32_t piano_key_state(); uint32_t piano_key_state();
bool filter_native_touch(const nativetouch::NativeTouchEvent &event); bool filter_native_touch(const nativetouch::NativeTouchEvent &event);
} }
+143 -143
View File
@@ -1,143 +1,143 @@
#include "touch_debug.h" #include "touch_debug.h"
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <cstring> #include <cstring>
#include <mutex> #include <mutex>
#include "external/imgui/imgui.h" #include "external/imgui/imgui.h"
#include "games/rb/rb.h" #include "games/rb/rb.h"
#include "games/rb/touch_defs.h" #include "games/rb/touch_defs.h"
namespace games::rb { namespace games::rb {
struct TouchDebugState { struct TouchDebugState {
std::array<unsigned char, TOUCH_PACKET_SIZE> packet {}; std::array<unsigned char, TOUCH_PACKET_SIZE> packet {};
bool is_landscape = false; bool is_landscape = false;
}; };
std::atomic_bool TOUCH_DEBUG_OVERLAY = false; std::atomic_bool TOUCH_DEBUG_OVERLAY = false;
static std::atomic_bool TOUCH_ACTIVE = false; static std::atomic_bool TOUCH_ACTIVE = false;
static std::mutex TOUCH_DEBUG_STATE_M; static std::mutex TOUCH_DEBUG_STATE_M;
static TouchDebugState TOUCH_DEBUG_STATE; static TouchDebugState TOUCH_DEBUG_STATE;
static float touch_scale_factor() { static float touch_scale_factor() {
return TOUCH_SCALING / (float) TOUCH_SCALE_DEFAULT; return TOUCH_SCALING / (float) TOUCH_SCALE_DEFAULT;
} }
static void clear_touch_debug_state() { static void clear_touch_debug_state() {
std::lock_guard<std::mutex> lock(TOUCH_DEBUG_STATE_M); std::lock_guard<std::mutex> lock(TOUCH_DEBUG_STATE_M);
TOUCH_DEBUG_STATE = {}; TOUCH_DEBUG_STATE = {};
} }
static TouchDebugState get_touch_debug_state() { static TouchDebugState get_touch_debug_state() {
std::lock_guard<std::mutex> lock(TOUCH_DEBUG_STATE_M); std::lock_guard<std::mutex> lock(TOUCH_DEBUG_STATE_M);
return TOUCH_DEBUG_STATE; return TOUCH_DEBUG_STATE;
} }
static bool packet_bit_active( static bool packet_bit_active(
const std::array<unsigned char, TOUCH_PACKET_SIZE> &packet, int bit) { const std::array<unsigned char, TOUCH_PACKET_SIZE> &packet, int bit) {
return (packet[TOUCH_PACKET_DATA_OFFSET + bit / 8] & (1u << (bit % 8))) != 0; return (packet[TOUCH_PACKET_DATA_OFFSET + bit / 8] & (1u << (bit % 8))) != 0;
} }
static int sensor_center(int sensor, int sensor_count, int extent) { static int sensor_center(int sensor, int sensor_count, int extent) {
return ((sensor * 2 + 1) * extent) / (sensor_count * 2); return ((sensor * 2 + 1) * extent) / (sensor_count * 2);
} }
static float sensor_span_position(int sensor, int sensor_count, int extent) { static float sensor_span_position(int sensor, int sensor_count, int extent) {
return sensor * (extent - 1) / (float) (sensor_count - 1); return sensor * (extent - 1) / (float) (sensor_count - 1);
} }
bool touch_debug_overlay_enabled() { bool touch_debug_overlay_enabled() {
return TOUCH_DEBUG_OVERLAY && TOUCH_ACTIVE.load(std::memory_order_acquire); return TOUCH_DEBUG_OVERLAY && TOUCH_ACTIVE.load(std::memory_order_acquire);
} }
void touch_draw_debug_overlay() { void touch_draw_debug_overlay() {
if (!touch_debug_overlay_enabled()) { if (!touch_debug_overlay_enabled()) {
return; return;
} }
const auto &io = ImGui::GetIO(); const auto &io = ImGui::GetIO();
int width = static_cast<int>(io.DisplaySize.x); int width = static_cast<int>(io.DisplaySize.x);
int height = static_cast<int>(io.DisplaySize.y); int height = static_cast<int>(io.DisplaySize.y);
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
return; return;
} }
const float scale_factor = touch_scale_factor(); const float scale_factor = touch_scale_factor();
const float left = width * (1.f - scale_factor) / 2.f; const float left = width * (1.f - scale_factor) / 2.f;
const float top = height * (1.f - scale_factor) / 2.f; const float top = height * (1.f - scale_factor) / 2.f;
const float right = width - left; const float right = width - left;
const float bottom = height - top; const float bottom = height - top;
TouchDebugState state = get_touch_debug_state(); TouchDebugState state = get_touch_debug_state();
ImDrawList *draw_list = ImGui::GetBackgroundDrawList(); ImDrawList *draw_list = ImGui::GetBackgroundDrawList();
auto draw_line = [&](float x1, float y1, float x2, float y2) { auto draw_line = [&](float x1, float y1, float x2, float y2) {
draw_list->AddLine( draw_list->AddLine(
ImVec2(x1, y1), ImVec2(x2, y2), ImVec2(x1, y1), ImVec2(x2, y2),
IM_COL32(0, 255, 64, 255), 2.f); IM_COL32(0, 255, 64, 255), 2.f);
}; };
// show the valid input area when touch scaling restricts it // show the valid input area when touch scaling restricts it
if (TOUCH_SCALING != TOUCH_SCALE_DEFAULT) { if (TOUCH_SCALING != TOUCH_SCALE_DEFAULT) {
draw_list->AddRect( draw_list->AddRect(
ImVec2(left, top), ImVec2(right, bottom), ImVec2(left, top), ImVec2(right, bottom),
IM_COL32(255, 255, 255, 255), 0.f, 0, 2.f); IM_COL32(255, 255, 255, 255), 0.f, 0, 2.f);
} }
// spread the usable X sensors 2..45 from edge to edge // spread the usable X sensors 2..45 from edge to edge
for (int sensor = X_SENSOR_FIRST_ACTIVE; sensor <= X_SENSOR_LAST_ACTIVE; sensor++) { for (int sensor = X_SENSOR_FIRST_ACTIVE; sensor <= X_SENSOR_LAST_ACTIVE; sensor++) {
if (!packet_bit_active(state.packet, X_SENSOR_FIRST_BIT + sensor)) { if (!packet_bit_active(state.packet, X_SENSOR_FIRST_BIT + sensor)) {
continue; continue;
} }
float position = sensor_span_position( float position = sensor_span_position(
sensor - X_SENSOR_FIRST_ACTIVE, X_SENSOR_ACTIVE_COUNT, sensor - X_SENSOR_FIRST_ACTIVE, X_SENSOR_ACTIVE_COUNT,
state.is_landscape ? height : width); state.is_landscape ? height : width);
if (state.is_landscape) { if (state.is_landscape) {
float y = top + position * scale_factor; float y = top + position * scale_factor;
draw_line(left, y, right, y); draw_line(left, y, right, y);
} else { } else {
float x = left + position * scale_factor; float x = left + position * scale_factor;
draw_line(x, top, x, bottom); draw_line(x, top, x, bottom);
} }
} }
for (int sensor = 0; sensor < Y_SENSOR_COUNT; sensor++) { for (int sensor = 0; sensor < Y_SENSOR_COUNT; sensor++) {
if (!packet_bit_active(state.packet, Y_SENSOR_FIRST_BIT - sensor)) { if (!packet_bit_active(state.packet, Y_SENSOR_FIRST_BIT - sensor)) {
continue; continue;
} }
int position = sensor_center( int position = sensor_center(
sensor, Y_SENSOR_COUNT, sensor, Y_SENSOR_COUNT,
state.is_landscape ? width : height); state.is_landscape ? width : height);
if (state.is_landscape) { if (state.is_landscape) {
float x = right - position * scale_factor; float x = right - position * scale_factor;
draw_line(x, top, x, bottom); draw_line(x, top, x, bottom);
} else { } else {
float y = top + position * scale_factor; float y = top + position * scale_factor;
draw_line(left, y, right, y); draw_line(left, y, right, y);
} }
} }
} }
void touch_debug_attach() { void touch_debug_attach() {
clear_touch_debug_state(); clear_touch_debug_state();
TOUCH_ACTIVE.store(true, std::memory_order_release); TOUCH_ACTIVE.store(true, std::memory_order_release);
} }
void touch_debug_detach() { void touch_debug_detach() {
TOUCH_ACTIVE.store(false, std::memory_order_release); TOUCH_ACTIVE.store(false, std::memory_order_release);
clear_touch_debug_state(); clear_touch_debug_state();
} }
void touch_debug_publish(const unsigned char *data, bool is_landscape) { void touch_debug_publish(const unsigned char *data, bool is_landscape) {
if (!TOUCH_DEBUG_OVERLAY) { if (!TOUCH_DEBUG_OVERLAY) {
return; return;
} }
std::lock_guard<std::mutex> lock(TOUCH_DEBUG_STATE_M); std::lock_guard<std::mutex> lock(TOUCH_DEBUG_STATE_M);
memcpy(TOUCH_DEBUG_STATE.packet.data(), data, TOUCH_PACKET_SIZE); memcpy(TOUCH_DEBUG_STATE.packet.data(), data, TOUCH_PACKET_SIZE);
TOUCH_DEBUG_STATE.is_landscape = is_landscape; TOUCH_DEBUG_STATE.is_landscape = is_landscape;
} }
} }
+14 -14
View File
@@ -1,14 +1,14 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
namespace games::rb { namespace games::rb {
extern std::atomic_bool TOUCH_DEBUG_OVERLAY; extern std::atomic_bool TOUCH_DEBUG_OVERLAY;
bool touch_debug_overlay_enabled(); bool touch_debug_overlay_enabled();
void touch_draw_debug_overlay(); void touch_draw_debug_overlay();
void touch_debug_attach(); void touch_debug_attach();
void touch_debug_detach(); void touch_debug_detach();
void touch_debug_publish(const unsigned char *data, bool is_landscape); void touch_debug_publish(const unsigned char *data, bool is_landscape);
} }
+17 -17
View File
@@ -1,17 +1,17 @@
#pragma once #pragma once
namespace games::rb { namespace games::rb {
inline constexpr int TOUCH_SCALE_DEFAULT = 1000; inline constexpr int TOUCH_SCALE_DEFAULT = 1000;
inline constexpr int TOUCH_PACKET_SIZE = 20; inline constexpr int TOUCH_PACKET_SIZE = 20;
inline constexpr int TOUCH_PACKET_DATA_OFFSET = 3; inline constexpr int TOUCH_PACKET_DATA_OFFSET = 3;
inline constexpr int X_SENSOR_COUNT = 48; inline constexpr int X_SENSOR_COUNT = 48;
inline constexpr int X_SENSOR_FIRST_ACTIVE = 2; inline constexpr int X_SENSOR_FIRST_ACTIVE = 2;
inline constexpr int X_SENSOR_LAST_ACTIVE = 45; inline constexpr int X_SENSOR_LAST_ACTIVE = 45;
inline constexpr int X_SENSOR_ACTIVE_COUNT = inline constexpr int X_SENSOR_ACTIVE_COUNT =
X_SENSOR_LAST_ACTIVE - X_SENSOR_FIRST_ACTIVE + 1; X_SENSOR_LAST_ACTIVE - X_SENSOR_FIRST_ACTIVE + 1;
inline constexpr int X_SENSOR_FIRST_BIT = 88; inline constexpr int X_SENSOR_FIRST_BIT = 88;
inline constexpr int Y_SENSOR_COUNT = 76; inline constexpr int Y_SENSOR_COUNT = 76;
inline constexpr int Y_SENSOR_FIRST_BIT = 75; inline constexpr int Y_SENSOR_FIRST_BIT = 75;
} }
+13 -26
View File
@@ -3,6 +3,7 @@
#if SPICE64 #if SPICE64
#include <cstdint> #include <cstdint>
#include "api/client.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
@@ -349,43 +350,29 @@ namespace games::sdvx {
* 9 - v unit - 258 bytes - 86 colors * 9 - v unit - 258 bytes - 86 colors
* *
* data is stored in RGB order, 3 bytes per color * data is stored in RGB order, 3 bytes per color
*
* TODO: expose this data via API
*/ */
// data mapping
static struct TapeLedMapping {
size_t data_size;
int index_r, index_g, index_b;
TapeLedMapping(size_t data_size, int index_r, int index_g, int index_b)
: data_size(data_size), index_r(index_r), index_g(index_g), index_b(index_b) {}
} mapping[] = {
{ 74, Lights::TITLE_AVG_R, Lights::TITLE_AVG_G, Lights::TITLE_AVG_B },
{ 12, Lights::UPPER_LEFT_SPEAKER_AVG_R, Lights::UPPER_LEFT_SPEAKER_AVG_G, Lights::UPPER_LEFT_SPEAKER_AVG_B },
{ 12, Lights::UPPER_RIGHT_SPEAKER_AVG_R, Lights::UPPER_RIGHT_SPEAKER_AVG_G, Lights::UPPER_RIGHT_SPEAKER_AVG_B },
{ 56, Lights::LEFT_WING_AVG_R, Lights::LEFT_WING_AVG_G, Lights::LEFT_WING_AVG_B },
{ 56, Lights::RIGHT_WING_AVG_R, Lights::RIGHT_WING_AVG_G, Lights::RIGHT_WING_AVG_B },
{ 94, Lights::CONTROL_PANEL_AVG_R, Lights::CONTROL_PANEL_AVG_G, Lights::CONTROL_PANEL_AVG_B },
{ 12, Lights::LOWER_LEFT_SPEAKER_AVG_R, Lights::LOWER_LEFT_SPEAKER_AVG_G, Lights::LOWER_LEFT_SPEAKER_AVG_B },
{ 12, Lights::LOWER_RIGHT_SPEAKER_AVG_R, Lights::LOWER_RIGHT_SPEAKER_AVG_G, Lights::LOWER_RIGHT_SPEAKER_AVG_B },
{ 14, Lights::WOOFER_AVG_R, Lights::WOOFER_AVG_G, Lights::WOOFER_AVG_B },
{ 86, Lights::V_UNIT_AVG_R, Lights::V_UNIT_AVG_G, Lights::V_UNIT_AVG_B },
};
// check index bounds // check index bounds
if (tapeledutils::is_enabled() && index < std::size(mapping)) { if (tapeledutils::is_enabled() && index < std::size(TAPELED_MAPPING)) {
auto &map = mapping[index]; auto &map = TAPELED_MAPPING[index];
const auto data_size = map.data.size();
// pick a color to use // pick a color to use
const auto rgb = tapeledutils::pick_color_from_led_tape(data, map.data_size); const auto rgb = tapeledutils::pick_color_from_led_tape(data, data_size);
// program the lights into API // program the lights into API
auto &lights = get_lights(); auto &lights = get_lights();
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_r], rgb.r);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_g], rgb.g);
GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b); GameAPI::Lights::writeLight(RI_MGR, lights[map.index_b], rgb.b);
if (api::has_clients()) {
for (size_t i = 0; i < data_size; ++i) {
map.data[i].r = data[i * 3];
map.data[i].g = data[i * 3 + 1];
map.data[i].b = data[i * 3 + 2];
}
}
} }
if (This != custom_node) { if (This != custom_node) {
+15 -2
View File
@@ -61,6 +61,19 @@ namespace games::sdvx {
static HKEY real_asio_reg_handle = nullptr; static HKEY real_asio_reg_handle = nullptr;
static HKEY real_asio_device_reg_handle = nullptr; static HKEY real_asio_device_reg_handle = nullptr;
tapeledutils::tape_led TAPELED_MAPPING[SDVX_TAPELED_TOTAL] = {
{ 74, Lights::TITLE_AVG_R, Lights::TITLE_AVG_G, Lights::TITLE_AVG_B, "Title" },
{ 12, Lights::UPPER_LEFT_SPEAKER_AVG_R, Lights::UPPER_LEFT_SPEAKER_AVG_G, Lights::UPPER_LEFT_SPEAKER_AVG_B, "Upper Left Speaker" },
{ 12, Lights::UPPER_RIGHT_SPEAKER_AVG_R, Lights::UPPER_RIGHT_SPEAKER_AVG_G, Lights::UPPER_RIGHT_SPEAKER_AVG_B, "Upper Right Speaker" },
{ 56, Lights::LEFT_WING_AVG_R, Lights::LEFT_WING_AVG_G, Lights::LEFT_WING_AVG_B, "Left Wing" },
{ 56, Lights::RIGHT_WING_AVG_R, Lights::RIGHT_WING_AVG_G, Lights::RIGHT_WING_AVG_B, "Right Wing" },
{ 94, Lights::CONTROL_PANEL_AVG_R, Lights::CONTROL_PANEL_AVG_G, Lights::CONTROL_PANEL_AVG_B, "Control Panel" },
{ 12, Lights::LOWER_LEFT_SPEAKER_AVG_R, Lights::LOWER_LEFT_SPEAKER_AVG_G, Lights::LOWER_LEFT_SPEAKER_AVG_B, "Lower Left Speaker" },
{ 12, Lights::LOWER_RIGHT_SPEAKER_AVG_R, Lights::LOWER_RIGHT_SPEAKER_AVG_G, Lights::LOWER_RIGHT_SPEAKER_AVG_B, "Lower Right Speaker" },
{ 14, Lights::WOOFER_AVG_R, Lights::WOOFER_AVG_G, Lights::WOOFER_AVG_B, "Woofer" },
{ 86, Lights::V_UNIT_AVG_R, Lights::V_UNIT_AVG_G, Lights::V_UNIT_AVG_B, "V Unit" },
};
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) { static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
if (lpSubKey != nullptr && if (lpSubKey != nullptr &&
phkResult != nullptr && phkResult != nullptr &&
@@ -353,8 +366,8 @@ namespace games::sdvx {
} }
#ifdef SPICE64 // SDVX5+ specific code #ifdef SPICE64 // SDVX5+ specific code
// check -dxmainadapter + UFC mode // check -monitor + UFC mode
if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) { if (false && !GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value() && is_valkyrie_model()) {
SHOW_VM_MONITOR_WARNING = true; SHOW_VM_MONITOR_WARNING = true;
log_warning( log_warning(
"sdvx", "sdvx",
+4
View File
@@ -6,6 +6,7 @@
#include "avs/game.h" #include "avs/game.h"
#include "games/game.h" #include "games/game.h"
#include "util/tapeled.h"
namespace games::sdvx { namespace games::sdvx {
@@ -25,6 +26,9 @@ namespace games::sdvx {
// states // states
extern bool SHOW_VM_MONITOR_WARNING; extern bool SHOW_VM_MONITOR_WARNING;
constexpr int SDVX_TAPELED_TOTAL = 10;
extern tapeledutils::tape_led TAPELED_MAPPING[SDVX_TAPELED_TOTAL];
static inline bool is_valkyrie_model() { static inline bool is_valkyrie_model() {
return ( return (
avs::game::is_model("KFC") && avs::game::is_model("KFC") &&
+72 -72
View File
@@ -1,72 +1,72 @@
#include "sdvx_live2d.h" #include "sdvx_live2d.h"
// only the Live2D-capable SDVX versions are 64-bit, so the whole feature is // only the Live2D-capable SDVX versions are 64-bit, so the whole feature is
// compiled out of 32-bit builds. // compiled out of 32-bit builds.
#ifdef SPICE64 #ifdef SPICE64
#include <string> #include <string>
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
#include "launcher/logger.h" #include "launcher/logger.h"
#include "util/logging.h" #include "util/logging.h"
namespace games::sdvx { namespace games::sdvx {
// Live2D in-game scene detection (for the -sdvxnolive2d "ingame" option). // Live2D in-game scene detection (for the -sdvxnolive2d "ingame" option).
// //
// the game logs scene transitions as "I:Attach: in <SCENE>" / "I:Detach: in // the game logs scene transitions as "I:Attach: in <SCENE>" / "I:Detach: in
// <SCENE>". several scenes correspond to in-song gameplay (with the heavy // <SCENE>". several scenes correspond to in-song gameplay (with the heavy
// Live2D rendering); we watch those log lines and keep the shared flag // Live2D rendering); we watch those log lines and keep the shared flag
// the d3d9 backend reads up to date. the hook never alters the log output // the d3d9 backend reads up to date. the hook never alters the log output
// (always returns false). // (always returns false).
static bool live2d_scene_log_hook( static bool live2d_scene_log_hook(
void *user, const std::string &data, logger::Style style, std::string &out) { void *user, const std::string &data, logger::Style style, std::string &out) {
// any of these scenes counts as in-song gameplay (different play modes) // any of these scenes counts as in-song gameplay (different play modes)
static const char *const gameplay_scenes[] = { static const char *const gameplay_scenes[] = {
"in ALTERNATIVE_GAME_SCENE", "in ALTERNATIVE_GAME_SCENE",
"in MEGAMIX_GAME_SCENE", "in MEGAMIX_GAME_SCENE",
"in MEGAMIX_BATTLE", "in MEGAMIX_BATTLE",
"in BATTLE_GAME_SCENE", "in BATTLE_GAME_SCENE",
"in AUTOMATION_GAME_SCENE", "in AUTOMATION_GAME_SCENE",
"in ARENA_GAME_SCENE", "in ARENA_GAME_SCENE",
}; };
bool in_gameplay_scene = false; bool in_gameplay_scene = false;
for (const auto *scene : gameplay_scenes) { for (const auto *scene : gameplay_scenes) {
if (data.find(scene) != std::string::npos) { if (data.find(scene) != std::string::npos) {
in_gameplay_scene = true; in_gameplay_scene = true;
break; break;
} }
} }
if (!in_gameplay_scene) { if (!in_gameplay_scene) {
return false; return false;
} }
// note: log messages here must NOT contain any matched scene token, else // note: log messages here must NOT contain any matched scene token, else
// this hook would re-enter itself when the message is pushed. // this hook would re-enter itself when the message is pushed.
if (data.find("I:Attach: in ") != std::string::npos) { if (data.find("I:Attach: in ") != std::string::npos) {
if (!GRAPHICS_SDVX_LIVE2D_IN_GAMEPLAY.exchange(true, std::memory_order_relaxed)) { if (!GRAPHICS_SDVX_LIVE2D_IN_GAMEPLAY.exchange(true, std::memory_order_relaxed)) {
log_info("sdvx", "Live2D skip: entering gameplay"); log_info("sdvx", "Live2D skip: entering gameplay");
} }
} else if (data.find("I:Detach: in ") != std::string::npos) { } else if (data.find("I:Detach: in ") != std::string::npos) {
if (GRAPHICS_SDVX_LIVE2D_IN_GAMEPLAY.exchange(false, std::memory_order_relaxed)) { if (GRAPHICS_SDVX_LIVE2D_IN_GAMEPLAY.exchange(false, std::memory_order_relaxed)) {
log_info("sdvx", "Live2D skip: leaving gameplay"); log_info("sdvx", "Live2D skip: leaving gameplay");
} }
} }
return false; return false;
} }
void live2d_scene_detection_init() { void live2d_scene_detection_init() {
static bool installed = false; static bool installed = false;
if (installed) { if (installed) {
return; return;
} }
installed = true; installed = true;
// the logger's hook list is a persistent static, so registering here is // the logger's hook list is a persistent static, so registering here is
// safe even though this runs before logger::start(). we intentionally do // safe even though this runs before logger::start(). we intentionally do
// NOT log a confirmation now: at this point the log file isn't open yet // NOT log a confirmation now: at this point the log file isn't open yet
// and the message would be dropped. the entering/leaving-gameplay lines // and the message would be dropped. the entering/leaving-gameplay lines
// above provide runtime confirmation once the logger is running. // above provide runtime confirmation once the logger is running.
logger::hook_add(live2d_scene_log_hook, nullptr); logger::hook_add(live2d_scene_log_hook, nullptr);
} }
} }
#endif // SPICE64 #endif // SPICE64
+14 -14
View File
@@ -1,14 +1,14 @@
#pragma once #pragma once
namespace games::sdvx { namespace games::sdvx {
#ifdef SPICE64 #ifdef SPICE64
// installs the Live2D in-game scene-detection log hook used by the // installs the Live2D in-game scene-detection log hook used by the
// -sdvxnolive2d "ingame" option. does not require the SDVX game module to // -sdvxnolive2d "ingame" option. does not require the SDVX game module to
// be attached, so it can be enabled purely from the launcher option. // be attached, so it can be enabled purely from the launcher option.
// only the Live2D-capable SDVX versions are 64-bit, so this is compiled out // only the Live2D-capable SDVX versions are 64-bit, so this is compiled out
// of 32-bit builds. // of 32-bit builds.
void live2d_scene_detection_init(); void live2d_scene_detection_init();
#endif #endif
} }
+84 -84
View File
@@ -1,84 +1,84 @@
#include "asio_driver_scan.h" #include "asio_driver_scan.h"
#include <algorithm> #include <algorithm>
#include <windows.h> #include <windows.h>
#include "util/utils.h" #include "util/utils.h"
namespace hooks::audio { namespace hooks::audio {
static constexpr char ASIO_REG_PATH[] = "software\\asio"; static constexpr char ASIO_REG_PATH[] = "software\\asio";
static constexpr char ASIO_REG_DESC[] = "description"; static constexpr char ASIO_REG_DESC[] = "description";
// enumerate a single registry view, appending to entries while merging // enumerate a single registry view, appending to entries while merging
// duplicates discovered in another view. Drivers are matched by name (not // duplicates discovered in another view. Drivers are matched by name (not
// CLSID): the game's ASIO loader selects drivers by name, and some vendors // CLSID): the game's ASIO loader selects drivers by name, and some vendors
// register the same CLSID under different 32-bit/64-bit names (e.g. "XONAR // register the same CLSID under different 32-bit/64-bit names (e.g. "XONAR
// SOUND CARD" vs "XONAR SOUND CARD(64)"), which are distinct user choices. // SOUND CARD" vs "XONAR SOUND CARD(64)"), which are distinct user choices.
static void scan_view( static void scan_view(
REGSAM wow64_flag, REGSAM wow64_flag,
bool is_64bit, bool is_64bit,
std::vector<AsioDriverScanEntry> &entries) { std::vector<AsioDriverScanEntry> &entries) {
HKEY hkEnum = nullptr; HKEY hkEnum = nullptr;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, ASIO_REG_PATH, 0, if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, ASIO_REG_PATH, 0,
KEY_READ | wow64_flag, &hkEnum) != ERROR_SUCCESS) { KEY_READ | wow64_flag, &hkEnum) != ERROR_SUCCESS) {
return; return;
} }
char key_name[256]; char key_name[256];
for (DWORD index = 0; for (DWORD index = 0;
RegEnumKeyA(hkEnum, index, key_name, sizeof(key_name)) == ERROR_SUCCESS; RegEnumKeyA(hkEnum, index, key_name, sizeof(key_name)) == ERROR_SUCCESS;
index++) { index++) {
// read description (display name), fall back to the key name. // read description (display name), fall back to the key name.
// RegOpenKeyExA + RegQueryValueExA is used instead of RegGetValueA // RegOpenKeyExA + RegQueryValueExA is used instead of RegGetValueA
// because the latter is unavailable on Windows XP. // because the latter is unavailable on Windows XP.
char desc[256] = { 0 }; char desc[256] = { 0 };
DWORD size = sizeof(desc); DWORD size = sizeof(desc);
std::string name = key_name; std::string name = key_name;
HKEY hkDriver = nullptr; HKEY hkDriver = nullptr;
if (RegOpenKeyExA(hkEnum, key_name, 0, if (RegOpenKeyExA(hkEnum, key_name, 0,
KEY_QUERY_VALUE | wow64_flag, &hkDriver) == ERROR_SUCCESS) { KEY_QUERY_VALUE | wow64_flag, &hkDriver) == ERROR_SUCCESS) {
DWORD type = 0; DWORD type = 0;
if (RegQueryValueExA(hkDriver, if (RegQueryValueExA(hkDriver,
ASIO_REG_DESC, ASIO_REG_DESC,
nullptr, nullptr,
&type, &type,
reinterpret_cast<LPBYTE>(desc), reinterpret_cast<LPBYTE>(desc),
&size) == ERROR_SUCCESS &size) == ERROR_SUCCESS
&& type == REG_SZ && desc[0]) { && type == REG_SZ && desc[0]) {
// ensure null termination // ensure null termination
desc[sizeof(desc) - 1] = '\0'; desc[sizeof(desc) - 1] = '\0';
name = desc; name = desc;
} }
RegCloseKey(hkDriver); RegCloseKey(hkDriver);
} }
// merge with an existing entry from the other view (match by name) // merge with an existing entry from the other view (match by name)
const std::string name_lower = strtolower(name); const std::string name_lower = strtolower(name);
auto it = std::find_if(entries.begin(), entries.end(), [&](const auto &e) { auto it = std::find_if(entries.begin(), entries.end(), [&](const auto &e) {
return strtolower(e.name) == name_lower; return strtolower(e.name) == name_lower;
}); });
if (it == entries.end()) { if (it == entries.end()) {
entries.push_back({ name }); entries.push_back({ name });
it = entries.end() - 1; it = entries.end() - 1;
} }
it->found_32bit |= !is_64bit; it->found_32bit |= !is_64bit;
it->found_64bit |= is_64bit; it->found_64bit |= is_64bit;
} }
RegCloseKey(hkEnum); RegCloseKey(hkEnum);
} }
std::vector<AsioDriverScanEntry> scan_asio_drivers() { std::vector<AsioDriverScanEntry> scan_asio_drivers() {
std::vector<AsioDriverScanEntry> entries; std::vector<AsioDriverScanEntry> entries;
// 64-bit view first so it wins ordering when present in both // 64-bit view first so it wins ordering when present in both
scan_view(KEY_WOW64_64KEY, true, entries); scan_view(KEY_WOW64_64KEY, true, entries);
scan_view(KEY_WOW64_32KEY, false, entries); scan_view(KEY_WOW64_32KEY, false, entries);
return entries; return entries;
} }
} }
+15 -15
View File
@@ -1,15 +1,15 @@
#pragma once #pragma once
#include <string> #include <string>
#include <vector> #include <vector>
namespace hooks::audio { namespace hooks::audio {
struct AsioDriverScanEntry { struct AsioDriverScanEntry {
std::string name; std::string name;
bool found_32bit = false; bool found_32bit = false;
bool found_64bit = false; bool found_64bit = false;
}; };
std::vector<AsioDriverScanEntry> scan_asio_drivers(); std::vector<AsioDriverScanEntry> scan_asio_drivers();
} }
File diff suppressed because it is too large Load Diff
+240 -240
View File
@@ -1,240 +1,240 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <windows.h> #include <windows.h>
#include "external/asio/asio.h" #include "external/asio/asio.h"
#include "external/asio/iasiodrv.h" #include "external/asio/iasiodrv.h"
namespace hooks::audio::asio { namespace hooks::audio::asio {
// returns true if a CoCreateInstance call is instantiating a registered ASIO driver. // returns true if a CoCreateInstance call is instantiating a registered ASIO driver.
// ASIO hosts pass the driver CLSID as both class id and interface id; we also validate // ASIO hosts pass the driver CLSID as both class id and interface id; we also validate
// it against the system's registered ASIO drivers to avoid false positives // it against the system's registered ASIO drivers to avoid false positives
bool is_asio_creation(REFCLSID rclsid, REFIID riid); bool is_asio_creation(REFCLSID rclsid, REFIID riid);
// wrap a real ASIO driver instance, taking ownership of the supplied reference, and // wrap a real ASIO driver instance, taking ownership of the supplied reference, and
// return a proxy that forwards every call to it. also records it as the cached // return a proxy that forwards every call to it. also records it as the cached
// instance for its CLSID so later CoCreate calls can reuse it (see wrap_existing) // instance for its CLSID so later CoCreate calls can reuse it (see wrap_existing)
IUnknown *wrap(REFCLSID clsid, void *real); IUnknown *wrap(REFCLSID clsid, void *real);
// if a cached wrapper already exists for this CLSID, return it (with an added // if a cached wrapper already exists for this CLSID, return it (with an added
// reference); otherwise nullptr to signal the caller to create the real driver and // reference); otherwise nullptr to signal the caller to create the real driver and
// wrap it. lets the host reuse one driver instance instead of re-instantiating it // wrap it. lets the host reuse one driver instance instead of re-instantiating it
IUnknown *wrap_existing(REFCLSID clsid); IUnknown *wrap_existing(REFCLSID clsid);
// drop the process-lifetime references taken by wrap() so cached drivers can be released // drop the process-lifetime references taken by wrap() so cached drivers can be released
// at shutdown. only relinquishes our pin, so a real driver is torn down once the host // at shutdown. only relinquishes our pin, so a real driver is torn down once the host
// has released its own references too. call from a controlled shutdown point, never from // has released its own references too. call from a controlled shutdown point, never from
// a static destructor (the driver DLL may already be unloaded) // a static destructor (the driver DLL may already be unloaded)
void release_all_wrappers(); void release_all_wrappers();
} }
// transparent proxy around a real ASIO driver; a single place to intercept ASIO traffic // transparent proxy around a real ASIO driver; a single place to intercept ASIO traffic
struct WrappedAsio final : IAsio { struct WrappedAsio final : IAsio {
WrappedAsio(IAsio *real, REFCLSID clsid, std::string name) WrappedAsio(IAsio *real, REFCLSID clsid, std::string name)
: pReal(real), clsid(clsid), driver_name(std::move(name)) { : pReal(real), clsid(clsid), driver_name(std::move(name)) {
} }
WrappedAsio(const WrappedAsio &) = delete; WrappedAsio(const WrappedAsio &) = delete;
WrappedAsio &operator=(const WrappedAsio &) = delete; WrappedAsio &operator=(const WrappedAsio &) = delete;
virtual ~WrappedAsio(); virtual ~WrappedAsio();
// selects which source channel pair of a multichannel ASIO output reaches the device's // selects which source channel pair of a multichannel ASIO output reaches the device's
// 2.0 front pair. when not None, the proxy presents the game's expected multichannel // 2.0 front pair. when not None, the proxy presents the game's expected multichannel
// layout to the host so it proceeds to create_buffers, then opens only a two-channel // layout to the host so it proceeds to create_buffers, then opens only a two-channel
// stream on the real device and routes the selected pair onto it (see create_buffers). // stream on the real device and routes the selected pair onto it (see create_buffers).
// Front is the plain "force two channel" case (forward the device's own front pair); // Front is the plain "force two channel" case (forward the device's own front pair);
// the others copy a different pair onto 0/1. assumes a standard 7.1 layout (0-indexed). // the others copy a different pair onto 0/1. assumes a standard 7.1 layout (0-indexed).
// set once at boot, before any wrapper exists, so it needs no synchronization // set once at boot, before any wrapper exists, so it needs no synchronization
enum class StereoDownmix { enum class StereoDownmix {
None, // feature disabled - full multichannel passthrough None, // feature disabled - full multichannel passthrough
Front, // channels 0/1 - the device front pair is forwarded as-is (no copy) Front, // channels 0/1 - the device front pair is forwarded as-is (no copy)
Center, // channel 2 duplicated to both 0 and 1 Center, // channel 2 duplicated to both 0 and 1
Rear, // channels 4/5 -> 0/1 Rear, // channels 4/5 -> 0/1
Side, // channels 6/7 -> 0/1 Side, // channels 6/7 -> 0/1
}; };
static StereoDownmix STEREO_DOWNMIX; static StereoDownmix STEREO_DOWNMIX;
// true when a stereo extraction is configured, i.e. the real device should open a 2.0 // true when a stereo extraction is configured, i.e. the real device should open a 2.0
// stream and only the selected pair should reach it. the former standalone // stream and only the selected pair should reach it. the former standalone
// FORCE_TWO_CHANNELS flag is now just the Front case of this // FORCE_TWO_CHANNELS flag is now just the Front case of this
static bool force_two_channels() { static bool force_two_channels() {
return STEREO_DOWNMIX != StereoDownmix::None; return STEREO_DOWNMIX != StereoDownmix::None;
} }
// some games hardcode a multichannel ASIO output and bail before create_buffers if // some games hardcode a multichannel ASIO output and bail before create_buffers if
// get_channels reports fewer, so we report at least this many output channels when a // get_channels reports fewer, so we report at least this many output channels when a
// stereo extraction is active // stereo extraction is active
static constexpr long FORCED_OUTPUT_CHANNELS = 8; static constexpr long FORCED_OUTPUT_CHANNELS = 8;
// maps an option string ("front", "center", "rear", "side") to a StereoDownmix value, // maps an option string ("front", "center", "rear", "side") to a StereoDownmix value,
// returning None for anything unrecognized // returning None for anything unrecognized
static StereoDownmix name_to_stereo_downmix(const char *name); static StereoDownmix name_to_stereo_downmix(const char *name);
#pragma region IUnknown #pragma region IUnknown
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) override; HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) override;
ULONG STDMETHODCALLTYPE AddRef() override; ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override; ULONG STDMETHODCALLTYPE Release() override;
#pragma endregion #pragma endregion
#pragma region IAsio #pragma region IAsio
AsioBool __thiscall init(void *sys_handle) override; AsioBool __thiscall init(void *sys_handle) override;
void __thiscall get_driver_name(char *name) override; void __thiscall get_driver_name(char *name) override;
long __thiscall get_driver_version() override; long __thiscall get_driver_version() override;
void __thiscall get_error_message(char *string) override; void __thiscall get_error_message(char *string) override;
AsioError __thiscall start() override; AsioError __thiscall start() override;
AsioError __thiscall stop() override; AsioError __thiscall stop() override;
AsioError __thiscall get_channels(long *num_input_channels, long *num_output_channels) override; AsioError __thiscall get_channels(long *num_input_channels, long *num_output_channels) override;
AsioError __thiscall get_latencies(long *input_latency, long *output_latency) override; AsioError __thiscall get_latencies(long *input_latency, long *output_latency) override;
AsioError __thiscall get_buffer_size( AsioError __thiscall get_buffer_size(
long *min_size, long *min_size,
long *max_size, long *max_size,
long *preferred_size, long *preferred_size,
long *granularity) override; long *granularity) override;
AsioError __thiscall can_sample_rate(AsioSampleRate sample_rate) override; AsioError __thiscall can_sample_rate(AsioSampleRate sample_rate) override;
AsioError __thiscall get_sample_rate(AsioSampleRate *sample_rate) override; AsioError __thiscall get_sample_rate(AsioSampleRate *sample_rate) override;
AsioError __thiscall set_sample_rate(AsioSampleRate sample_rate) override; AsioError __thiscall set_sample_rate(AsioSampleRate sample_rate) override;
AsioError __thiscall get_clock_sources(ASIOClockSource *clocks, long *num_sources) override; AsioError __thiscall get_clock_sources(ASIOClockSource *clocks, long *num_sources) override;
AsioError __thiscall set_clock_source(long reference) override; AsioError __thiscall set_clock_source(long reference) override;
AsioError __thiscall get_sample_position(ASIOSamples *s_pos, ASIOTimeStamp *t_stamp) override; AsioError __thiscall get_sample_position(ASIOSamples *s_pos, ASIOTimeStamp *t_stamp) override;
AsioError __thiscall get_channel_info(AsioChannelInfo *info) override; AsioError __thiscall get_channel_info(AsioChannelInfo *info) override;
AsioError __thiscall create_buffers( AsioError __thiscall create_buffers(
AsioBufferInfo *buffer_infos, AsioBufferInfo *buffer_infos,
long num_channels, long num_channels,
long buffer_size, long buffer_size,
AsioCallbacks *callbacks) override; AsioCallbacks *callbacks) override;
AsioError __thiscall dispose_buffers() override; AsioError __thiscall dispose_buffers() override;
AsioError __thiscall control_panel() override; AsioError __thiscall control_panel() override;
AsioError __thiscall future(long selector, void *opt) override; AsioError __thiscall future(long selector, void *opt) override;
AsioError __thiscall output_ready() override; AsioError __thiscall output_ready() override;
#pragma endregion #pragma endregion
// quiesces any leftover stream/buffer state before the cached wrapper is handed back // quiesces any leftover stream/buffer state before the cached wrapper is handed back
// for reuse, without destroying the real driver (see wrap_existing) // for reuse, without destroying the real driver (see wrap_existing)
void quiesce_for_reuse(); void quiesce_for_reuse();
private: private:
// create_buffers implementation used when a stereo extraction is active: forwards only // create_buffers implementation used when a stereo extraction is active: forwards only
// the channels the real device has and hands the game throwaway buffers for the rest // the channels the real device has and hands the game throwaway buffers for the rest
AsioError create_buffers_front_pair( AsioError create_buffers_front_pair(
AsioBufferInfo *buffer_infos, AsioBufferInfo *buffer_infos,
long num_channels, long num_channels,
long buffer_size, long buffer_size,
AsioCallbacks *callbacks); AsioCallbacks *callbacks);
// if any post-processing effect (volume boost or stereo downmix) is active, saves the // if any post-processing effect (volume boost or stereo downmix) is active, saves the
// game's callbacks and returns a proxy callback set (our buffer-switch trampolines) to // game's callbacks and returns a proxy callback set (our buffer-switch trampolines) to
// hand the real driver instead, so we can rework its output buffers after the game // hand the real driver instead, so we can rework its output buffers after the game
// fills them. otherwise returns the game's callbacks unchanged. called at create_buffers // fills them. otherwise returns the game's callbacks unchanged. called at create_buffers
// time, before the stream starts // time, before the stream starts
AsioCallbacks *install_proxy_callbacks(AsioCallbacks *game_callbacks); AsioCallbacks *install_proxy_callbacks(AsioCallbacks *game_callbacks);
// records a device output channel whose buffers we scale by the volume boost. queries // records a device output channel whose buffers we scale by the volume boost. queries
// the real driver for the channel's sample format. called at create_buffers time // the real driver for the channel's sample format. called at create_buffers time
void record_volume_output_channel(const AsioBufferInfo &info); void record_volume_output_channel(const AsioBufferInfo &info);
// the real device's output sample format, queried from its first output channel. all // the real device's output sample format, queried from its first output channel. all
// output channels of a device share one format, so this characterizes them all. returns // output channels of a device share one format, so this characterizes them all. returns
// ASIOSTLastEntry if the device has no output channels or the query fails // ASIOSTLastEntry if the device has no output channels or the query fails
AsioSampleType device_output_sample_type(); AsioSampleType device_output_sample_type();
// locates the destination pair (device channels 0/1) and the configured source channels // locates the destination pair (device channels 0/1) and the configured source channels
// in the game's buffer set so the realtime path can copy the selected pair onto 0/1. // in the game's buffer set so the realtime path can copy the selected pair onto 0/1.
// a no-op unless STEREO_DOWNMIX selects a non-front pair. called at create_buffers time // a no-op unless STEREO_DOWNMIX selects a non-front pair. called at create_buffers time
void record_downmix_channels(AsioBufferInfo *buffer_infos, long num_channels, long buffer_size); void record_downmix_channels(AsioBufferInfo *buffer_infos, long num_channels, long buffer_size);
// publishes the captured post-process state to the realtime thread once the buffers // publishes the captured post-process state to the realtime thread once the buffers
// exist, making our trampolines start reworking output. called at the end of either // exist, making our trampolines start reworking output. called at the end of either
// create_buffers path // create_buffers path
void publish_post_process(long buffer_size); void publish_post_process(long buffer_size);
// detaches this instance from the realtime trampolines so they stop touching its // detaches this instance from the realtime trampolines so they stop touching its
// buffers. called from dispose_buffers and the destructor // buffers. called from dispose_buffers and the destructor
void detach_post_process(); void detach_post_process();
// multiplies every recorded output channel's buffer for the given double-buffer index // multiplies every recorded output channel's buffer for the given double-buffer index
// by the volume boost. runs on the driver's realtime thread from our buffer switch // by the volume boost. runs on the driver's realtime thread from our buffer switch
void apply_output_volume(long double_buffer_index); void apply_output_volume(long double_buffer_index);
// copies the configured source channel pair onto device channels 0/1 for the given // copies the configured source channel pair onto device channels 0/1 for the given
// double-buffer index. runs on the driver's realtime thread from our buffer switch // double-buffer index. runs on the driver's realtime thread from our buffer switch
void apply_downmix(long double_buffer_index); void apply_downmix(long double_buffer_index);
// realtime-thread trampolines for the buffer-switch callbacks, handed to the real // realtime-thread trampolines for the buffer-switch callbacks, handed to the real
// driver in place of the game's; ASIO callbacks carry no user data, so they reach the // driver in place of the game's; ASIO callbacks carry no user data, so they reach the
// active wrapper through active_instance, call the game's original, then rework output. // active wrapper through active_instance, call the game's original, then rework output.
// the other two callbacks (sample_rate_did_change, asio_message) are forwarded as the // the other two callbacks (sample_rate_did_change, asio_message) are forwarded as the
// game's own pointers, so they need no trampoline // game's own pointers, so they need no trampoline
static void __cdecl proxy_buffer_switch(long double_buffer_index, AsioBool direct_process); static void __cdecl proxy_buffer_switch(long double_buffer_index, AsioBool direct_process);
static AsioTime * __cdecl proxy_buffer_switch_time_info( static AsioTime * __cdecl proxy_buffer_switch_time_info(
AsioTime *params, long double_buffer_index, AsioBool direct_process); AsioTime *params, long double_buffer_index, AsioBool direct_process);
// the single wrapper whose proxy callbacks are installed (ASIO is single-instance with // the single wrapper whose proxy callbacks are installed (ASIO is single-instance with
// one running stream); read by the static trampolines to reach the right wrapper // one running stream); read by the static trampolines to reach the right wrapper
static std::atomic<WrappedAsio *> active_instance; static std::atomic<WrappedAsio *> active_instance;
IAsio *const pReal; IAsio *const pReal;
const CLSID clsid; const CLSID clsid;
// registry name of the driver (not get_driver_name), used in our logs as a single // registry name of the driver (not get_driver_name), used in our logs as a single
// unambiguous name; constant for our lifetime // unambiguous name; constant for our lifetime
std::string driver_name; std::string driver_name;
// the real driver is initialized exactly once; repeat init() calls are a no-op success // the real driver is initialized exactly once; repeat init() calls are a no-op success
bool initialized = false; bool initialized = false;
// whether the real driver currently has a buffer set / running stream. used to quiesce // whether the real driver currently has a buffer set / running stream. used to quiesce
// leftover state when the cached wrapper is reused (see quiesce_for_reuse) // leftover state when the cached wrapper is reused (see quiesce_for_reuse)
bool buffers_created = false; bool buffers_created = false;
bool started = false; bool started = false;
// our own reference count; we hold one reference on pReal and release it when this // our own reference count; we hold one reference on pReal and release it when this
// drops to zero // drops to zero
std::atomic<ULONG> ref_count {1}; std::atomic<ULONG> ref_count {1};
// throwaway double buffers handed to the channels we discard when a stereo extraction // throwaway double buffers handed to the channels we discard when a stereo extraction
// is active (see create_buffers). owned for the lifetime of the buffer set and freed // is active (see create_buffers). owned for the lifetime of the buffer set and freed
// in dispose_buffers; only read by the game from its own bufferSwitch, never by us // in dispose_buffers; only read by the game from its own bufferSwitch, never by us
std::vector<std::unique_ptr<uint8_t[]>> dummy_buffers; std::vector<std::unique_ptr<uint8_t[]>> dummy_buffers;
// one device output channel scaled by the volume boost in our buffer switch // one device output channel scaled by the volume boost in our buffer switch
struct VolumeOutputChannel { struct VolumeOutputChannel {
void *buffers[2]; void *buffers[2];
AsioSampleType type; AsioSampleType type;
}; };
// the game's original callbacks (captured when we install our proxy set) and the proxy // the game's original callbacks (captured when we install our proxy set) and the proxy
// set we hand the real driver; the realtime trampolines reach the game's buffer_switch // set we hand the real driver; the realtime trampolines reach the game's buffer_switch
// through game_callbacks regardless of which effect is active // through game_callbacks regardless of which effect is active
AsioCallbacks game_callbacks {}; AsioCallbacks game_callbacks {};
AsioCallbacks proxy_callbacks {}; AsioCallbacks proxy_callbacks {};
// volume boost state, captured at create_buffers time and published to the realtime // volume boost state, captured at create_buffers time and published to the realtime
// thread via active_instance once fully built; untouched while the stream runs. // thread via active_instance once fully built; untouched while the stream runs.
// volume_active gates whether the realtime path scales any buffers // volume_active gates whether the realtime path scales any buffers
bool volume_active = false; bool volume_active = false;
float volume_gain = 1.0f; float volume_gain = 1.0f;
long volume_buffer_size = 0; long volume_buffer_size = 0;
std::vector<VolumeOutputChannel> volume_channels; std::vector<VolumeOutputChannel> volume_channels;
// one device channel (0 or 1) fed by a source channel during stereo downmix; both // one device channel (0 or 1) fed by a source channel during stereo downmix; both
// buffer pointers are indexed by the ASIO double-buffer index, the same as the channels // buffer pointers are indexed by the ASIO double-buffer index, the same as the channels
struct DownmixCopy { struct DownmixCopy {
void *dst[2]; void *dst[2];
void *src[2]; void *src[2];
}; };
// stereo downmix state, captured at create_buffers time and published alongside the // stereo downmix state, captured at create_buffers time and published alongside the
// volume state; untouched while the stream runs. downmix_active gates whether the // volume state; untouched while the stream runs. downmix_active gates whether the
// realtime path copies the selected source pair onto device channels 0/1. copies[0] // realtime path copies the selected source pair onto device channels 0/1. copies[0]
// feeds device channel 0, copies[1] feeds device channel 1 // feeds device channel 0, copies[1] feeds device channel 1
bool downmix_active = false; bool downmix_active = false;
DownmixCopy downmix_copies[2] {}; DownmixCopy downmix_copies[2] {};
size_t downmix_bytes = 0; size_t downmix_bytes = 0;
}; };
+6
View File
@@ -15,6 +15,8 @@ namespace hooks::audio {
enum class Backend { enum class Backend {
Asio, Asio,
WaveOut, WaveOut,
Pipewire,
None
}; };
// surround-to-stereo downmix algorithm // surround-to-stereo downmix algorithm
@@ -64,6 +66,10 @@ namespace hooks::audio {
return Backend::Asio; return Backend::Asio;
} else if (_stricmp(value, "waveout") == 0) { } else if (_stricmp(value, "waveout") == 0) {
return Backend::WaveOut; return Backend::WaveOut;
} else if (_stricmp(value, "pipewire") == 0) {
return Backend::Pipewire;
} else if (_stricmp(value, "none") == 0) {
return Backend::None;
} }
return std::nullopt; return std::nullopt;
@@ -1,43 +1,43 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include <endpointvolume.h> #include <endpointvolume.h>
struct WrappedIAudioEndpointVolume : IAudioEndpointVolume { struct WrappedIAudioEndpointVolume : IAudioEndpointVolume {
explicit WrappedIAudioEndpointVolume(IAudioEndpointVolume *orig) : pReal(orig) {} explicit WrappedIAudioEndpointVolume(IAudioEndpointVolume *orig) : pReal(orig) {}
WrappedIAudioEndpointVolume(const WrappedIAudioEndpointVolume &) = delete; WrappedIAudioEndpointVolume(const WrappedIAudioEndpointVolume &) = delete;
WrappedIAudioEndpointVolume &operator=(const WrappedIAudioEndpointVolume &) = delete; WrappedIAudioEndpointVolume &operator=(const WrappedIAudioEndpointVolume &) = delete;
virtual ~WrappedIAudioEndpointVolume() = default; virtual ~WrappedIAudioEndpointVolume() = default;
#pragma region IUnknown #pragma region IUnknown
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
ULONG STDMETHODCALLTYPE AddRef() override; ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override; ULONG STDMETHODCALLTYPE Release() override;
#pragma endregion #pragma endregion
#pragma region IAudioEndpointVolume #pragma region IAudioEndpointVolume
HRESULT STDMETHODCALLTYPE RegisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) override; HRESULT STDMETHODCALLTYPE RegisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) override;
HRESULT STDMETHODCALLTYPE UnregisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) override; HRESULT STDMETHODCALLTYPE UnregisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) override;
HRESULT STDMETHODCALLTYPE GetChannelCount(uint32_t *pnChannelCount) override; HRESULT STDMETHODCALLTYPE GetChannelCount(uint32_t *pnChannelCount) override;
HRESULT STDMETHODCALLTYPE SetMasterVolumeLevel(float fLevelDB, LPCGUID pguidEventContext) override; HRESULT STDMETHODCALLTYPE SetMasterVolumeLevel(float fLevelDB, LPCGUID pguidEventContext) override;
HRESULT STDMETHODCALLTYPE SetMasterVolumeLevelScalar(float fLevel, LPCGUID pguidEventContext) override; HRESULT STDMETHODCALLTYPE SetMasterVolumeLevelScalar(float fLevel, LPCGUID pguidEventContext) override;
HRESULT STDMETHODCALLTYPE GetMasterVolumeLevel(float *fLevelDB) override; HRESULT STDMETHODCALLTYPE GetMasterVolumeLevel(float *fLevelDB) override;
HRESULT STDMETHODCALLTYPE GetMasterVolumeLevelScalar(float *fLevel) override; HRESULT STDMETHODCALLTYPE GetMasterVolumeLevelScalar(float *fLevel) override;
HRESULT STDMETHODCALLTYPE SetChannelVolumeLevel(uint32_t nChannel, float fLevelDB, LPCGUID pguidEventContext) override; HRESULT STDMETHODCALLTYPE SetChannelVolumeLevel(uint32_t nChannel, float fLevelDB, LPCGUID pguidEventContext) override;
HRESULT STDMETHODCALLTYPE SetChannelVolumeLevelScalar(uint32_t nChannel, float fLevel, LPCGUID pguidEventContext) override; HRESULT STDMETHODCALLTYPE SetChannelVolumeLevelScalar(uint32_t nChannel, float fLevel, LPCGUID pguidEventContext) override;
HRESULT STDMETHODCALLTYPE GetChannelVolumeLevel(uint32_t nChannel, float *fLevelDB) override; HRESULT STDMETHODCALLTYPE GetChannelVolumeLevel(uint32_t nChannel, float *fLevelDB) override;
HRESULT STDMETHODCALLTYPE GetChannelVolumeLevelScalar(uint32_t nChannel, float *fLevel) override; HRESULT STDMETHODCALLTYPE GetChannelVolumeLevelScalar(uint32_t nChannel, float *fLevel) override;
HRESULT STDMETHODCALLTYPE SetMute(WINBOOL bMute, LPCGUID pguidEventContext) override; HRESULT STDMETHODCALLTYPE SetMute(WINBOOL bMute, LPCGUID pguidEventContext) override;
HRESULT STDMETHODCALLTYPE GetMute(WINBOOL *bMute) override; HRESULT STDMETHODCALLTYPE GetMute(WINBOOL *bMute) override;
HRESULT STDMETHODCALLTYPE GetVolumeStepInfo(uint32_t *pnStep, uint32_t *pnStepCount) override; HRESULT STDMETHODCALLTYPE GetVolumeStepInfo(uint32_t *pnStep, uint32_t *pnStepCount) override;
HRESULT STDMETHODCALLTYPE VolumeStepUp(LPCGUID pguidEventContext) override; HRESULT STDMETHODCALLTYPE VolumeStepUp(LPCGUID pguidEventContext) override;
HRESULT STDMETHODCALLTYPE VolumeStepDown(LPCGUID pguidEventContext) override; HRESULT STDMETHODCALLTYPE VolumeStepDown(LPCGUID pguidEventContext) override;
HRESULT STDMETHODCALLTYPE QueryHardwareSupport(DWORD *pdwHardwareSupportMask) override; HRESULT STDMETHODCALLTYPE QueryHardwareSupport(DWORD *pdwHardwareSupportMask) override;
HRESULT STDMETHODCALLTYPE GetVolumeRange(float *pflVolumeMindB, float *pflVolumeMaxdB, float *pflVolumeIncrementdB) override; HRESULT STDMETHODCALLTYPE GetVolumeRange(float *pflVolumeMindB, float *pflVolumeMaxdB, float *pflVolumeIncrementdB) override;
#pragma endregion #pragma endregion
private: private:
IAudioEndpointVolume *const pReal; IAudioEndpointVolume *const pReal;
}; };
@@ -1,185 +1,185 @@
#include "null_device.h" #include "null_device.h"
#include <atomic> #include <atomic>
#include <cstring> #include <cstring>
#include <audioclient.h> #include <audioclient.h>
#include "hooks/audio/audio.h" #include "hooks/audio/audio.h"
#include "hooks/audio/audio_private.h" #include "hooks/audio/audio_private.h"
#include "hooks/audio/backends/wasapi/dummy_audio_client.h" #include "hooks/audio/backends/wasapi/dummy_audio_client.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
#include "null_discard_backend.h" #include "null_discard_backend.h"
// friendly name reported by the synthetic device. must contain "Realtek" so the // friendly name reported by the synthetic device. must contain "Realtek" so the
// gitadora arena device search matches it. // gitadora arena device search matches it.
static const wchar_t NULL_DEVICE_FRIENDLY_NAME[] = L"Realtek High Definition Audio"; static const wchar_t NULL_DEVICE_FRIENDLY_NAME[] = L"Realtek High Definition Audio";
// arbitrary identifier reported by the synthetic device. // arbitrary identifier reported by the synthetic device.
static const wchar_t NULL_DEVICE_ID[] = L"{spice2x-null-render-device}"; static const wchar_t NULL_DEVICE_ID[] = L"{spice2x-null-render-device}";
// PKEY_Device_FriendlyName, hardcoded to avoid pulling in functiondiscoverykeys_devpkey.h // PKEY_Device_FriendlyName, hardcoded to avoid pulling in functiondiscoverykeys_devpkey.h
static const PROPERTYKEY PKEY_DEVICE_FRIENDLY_NAME_LOCAL = { static const PROPERTYKEY PKEY_DEVICE_FRIENDLY_NAME_LOCAL = {
{ 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } }, { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } },
14 14
}; };
bool null_render_device_enabled() { bool null_render_device_enabled() {
return hooks::audio::INJECT_FAKE_REALTEK_AUDIO; return hooks::audio::INJECT_FAKE_REALTEK_AUDIO;
} }
// duplicate a wide string into CoTaskMem so the caller can free it with // duplicate a wide string into CoTaskMem so the caller can free it with
// CoTaskMemFree / PropVariantClear as the COM API contract requires. // CoTaskMemFree / PropVariantClear as the COM API contract requires.
static LPWSTR co_task_wcsdup(const wchar_t *src) { static LPWSTR co_task_wcsdup(const wchar_t *src) {
const size_t bytes = (wcslen(src) + 1) * sizeof(wchar_t); const size_t bytes = (wcslen(src) + 1) * sizeof(wchar_t);
auto *dst = static_cast<LPWSTR>(CoTaskMemAlloc(bytes)); auto *dst = static_cast<LPWSTR>(CoTaskMemAlloc(bytes));
if (dst != nullptr) { if (dst != nullptr) {
memcpy(dst, src, bytes); memcpy(dst, src, bytes);
} }
return dst; return dst;
} }
namespace { namespace {
// minimal IPropertyStore that only answers PKEY_Device_FriendlyName. // minimal IPropertyStore that only answers PKEY_Device_FriendlyName.
struct NullPropertyStore : IPropertyStore { struct NullPropertyStore : IPropertyStore {
std::atomic<ULONG> ref_cnt = 1; std::atomic<ULONG> ref_cnt = 1;
virtual ~NullPropertyStore() = default; virtual ~NullPropertyStore() = default;
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override { HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override {
if (ppvObj == nullptr) { if (ppvObj == nullptr) {
return E_POINTER; return E_POINTER;
} }
if (riid == __uuidof(IUnknown) || riid == __uuidof(IPropertyStore)) { if (riid == __uuidof(IUnknown) || riid == __uuidof(IPropertyStore)) {
this->AddRef(); this->AddRef();
*ppvObj = this; *ppvObj = this;
return S_OK; return S_OK;
} }
*ppvObj = nullptr; *ppvObj = nullptr;
return E_NOINTERFACE; return E_NOINTERFACE;
} }
ULONG STDMETHODCALLTYPE AddRef() override { ULONG STDMETHODCALLTYPE AddRef() override {
return ++this->ref_cnt; return ++this->ref_cnt;
} }
ULONG STDMETHODCALLTYPE Release() override { ULONG STDMETHODCALLTYPE Release() override {
const ULONG refs = --this->ref_cnt; const ULONG refs = --this->ref_cnt;
if (refs == 0) { if (refs == 0) {
delete this; delete this;
} }
return refs; return refs;
} }
HRESULT STDMETHODCALLTYPE GetCount(DWORD *cProps) override { HRESULT STDMETHODCALLTYPE GetCount(DWORD *cProps) override {
if (cProps == nullptr) { if (cProps == nullptr) {
return E_POINTER; return E_POINTER;
} }
*cProps = 1; *cProps = 1;
return S_OK; return S_OK;
} }
HRESULT STDMETHODCALLTYPE GetAt(DWORD iProp, PROPERTYKEY *pkey) override { HRESULT STDMETHODCALLTYPE GetAt(DWORD iProp, PROPERTYKEY *pkey) override {
if (pkey == nullptr) { if (pkey == nullptr) {
return E_POINTER; return E_POINTER;
} }
if (iProp != 0) { if (iProp != 0) {
return E_INVALIDARG; return E_INVALIDARG;
} }
*pkey = PKEY_DEVICE_FRIENDLY_NAME_LOCAL; *pkey = PKEY_DEVICE_FRIENDLY_NAME_LOCAL;
return S_OK; return S_OK;
} }
HRESULT STDMETHODCALLTYPE GetValue(REFPROPERTYKEY key, PROPVARIANT *pv) override { HRESULT STDMETHODCALLTYPE GetValue(REFPROPERTYKEY key, PROPVARIANT *pv) override {
if (pv == nullptr) { if (pv == nullptr) {
return E_POINTER; return E_POINTER;
} }
PropVariantInit(pv); PropVariantInit(pv);
if (key.fmtid == PKEY_DEVICE_FRIENDLY_NAME_LOCAL.fmtid if (key.fmtid == PKEY_DEVICE_FRIENDLY_NAME_LOCAL.fmtid
&& key.pid == PKEY_DEVICE_FRIENDLY_NAME_LOCAL.pid) { && key.pid == PKEY_DEVICE_FRIENDLY_NAME_LOCAL.pid) {
pv->pwszVal = co_task_wcsdup(NULL_DEVICE_FRIENDLY_NAME); pv->pwszVal = co_task_wcsdup(NULL_DEVICE_FRIENDLY_NAME);
if (pv->pwszVal == nullptr) { if (pv->pwszVal == nullptr) {
return E_OUTOFMEMORY; return E_OUTOFMEMORY;
} }
pv->vt = VT_LPWSTR; pv->vt = VT_LPWSTR;
} }
// unknown keys are returned as VT_EMPTY / S_OK // unknown keys are returned as VT_EMPTY / S_OK
return S_OK; return S_OK;
} }
HRESULT STDMETHODCALLTYPE SetValue(REFPROPERTYKEY, REFPROPVARIANT) override { HRESULT STDMETHODCALLTYPE SetValue(REFPROPERTYKEY, REFPROPVARIANT) override {
return STG_E_ACCESSDENIED; return STG_E_ACCESSDENIED;
} }
HRESULT STDMETHODCALLTYPE Commit() override { HRESULT STDMETHODCALLTYPE Commit() override {
return S_OK; return S_OK;
} }
}; };
} }
#pragma region IUnknown #pragma region IUnknown
HRESULT STDMETHODCALLTYPE NullMMDevice::QueryInterface(REFIID riid, void **ppvObj) { HRESULT STDMETHODCALLTYPE NullMMDevice::QueryInterface(REFIID riid, void **ppvObj) {
if (ppvObj == nullptr) { if (ppvObj == nullptr) {
return E_POINTER; return E_POINTER;
} }
if (riid == __uuidof(IUnknown) || riid == __uuidof(IMMDevice)) { if (riid == __uuidof(IUnknown) || riid == __uuidof(IMMDevice)) {
this->AddRef(); this->AddRef();
*ppvObj = this; *ppvObj = this;
return S_OK; return S_OK;
} }
*ppvObj = nullptr; *ppvObj = nullptr;
return E_NOINTERFACE; return E_NOINTERFACE;
} }
ULONG STDMETHODCALLTYPE NullMMDevice::AddRef() { ULONG STDMETHODCALLTYPE NullMMDevice::AddRef() {
return ++this->ref_cnt; return ++this->ref_cnt;
} }
ULONG STDMETHODCALLTYPE NullMMDevice::Release() { ULONG STDMETHODCALLTYPE NullMMDevice::Release() {
const ULONG refs = --this->ref_cnt; const ULONG refs = --this->ref_cnt;
if (refs == 0) { if (refs == 0) {
delete this; delete this;
} }
return refs; return refs;
} }
#pragma endregion #pragma endregion
#pragma region IMMDevice #pragma region IMMDevice
HRESULT STDMETHODCALLTYPE NullMMDevice::Activate( HRESULT STDMETHODCALLTYPE NullMMDevice::Activate(
REFIID iid, REFIID iid,
DWORD, DWORD,
PROPVARIANT *, PROPVARIANT *,
void **ppInterface) void **ppInterface)
{ {
if (ppInterface == nullptr) { if (ppInterface == nullptr) {
return E_POINTER; return E_POINTER;
} }
*ppInterface = nullptr; *ppInterface = nullptr;
log_info("audio::null", "NullMMDevice::Activate {}", guid2s(iid)); log_info("audio::null", "NullMMDevice::Activate {}", guid2s(iid));
if (iid == IID_IAudioClient) { if (iid == IID_IAudioClient) {
auto *client = static_cast<IAudioClient *>(new DummyIAudioClient(new NullDiscardBackend())); auto *client = static_cast<IAudioClient *>(new DummyIAudioClient(new NullDiscardBackend()));
*ppInterface = client; *ppInterface = client;
return S_OK; return S_OK;
} }
return E_NOINTERFACE; return E_NOINTERFACE;
} }
HRESULT STDMETHODCALLTYPE NullMMDevice::OpenPropertyStore(DWORD, IPropertyStore **ppProperties) { HRESULT STDMETHODCALLTYPE NullMMDevice::OpenPropertyStore(DWORD, IPropertyStore **ppProperties) {
if (ppProperties == nullptr) { if (ppProperties == nullptr) {
return E_POINTER; return E_POINTER;
} }
*ppProperties = new NullPropertyStore(); *ppProperties = new NullPropertyStore();
return S_OK; return S_OK;
} }
HRESULT STDMETHODCALLTYPE NullMMDevice::GetId(LPWSTR *ppstrId) { HRESULT STDMETHODCALLTYPE NullMMDevice::GetId(LPWSTR *ppstrId) {
if (ppstrId == nullptr) { if (ppstrId == nullptr) {
return E_POINTER; return E_POINTER;
} }
*ppstrId = co_task_wcsdup(NULL_DEVICE_ID); *ppstrId = co_task_wcsdup(NULL_DEVICE_ID);
return *ppstrId != nullptr ? S_OK : E_OUTOFMEMORY; return *ppstrId != nullptr ? S_OK : E_OUTOFMEMORY;
} }
HRESULT STDMETHODCALLTYPE NullMMDevice::GetState(DWORD *pdwState) { HRESULT STDMETHODCALLTYPE NullMMDevice::GetState(DWORD *pdwState) {
if (pdwState == nullptr) { if (pdwState == nullptr) {
return E_POINTER; return E_POINTER;
} }
*pdwState = DEVICE_STATE_ACTIVE; *pdwState = DEVICE_STATE_ACTIVE;
return S_OK; return S_OK;
} }
#pragma endregion #pragma endregion
@@ -1,39 +1,39 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <mmdeviceapi.h> #include <mmdeviceapi.h>
// returns true when a synthetic render endpoint should be injected into device // returns true when a synthetic render endpoint should be injected into device
// enumeration. games like gitadora arena search the render endpoint list for a // enumeration. games like gitadora arena search the render endpoint list for a
// device whose friendly name contains "Realtek" and crash with a null pointer // device whose friendly name contains "Realtek" and crash with a null pointer
// dereference when no match exists. presenting a fake match that routes to the // dereference when no match exists. presenting a fake match that routes to the
// null audio backend lets the search succeed while discarding the audio. // null audio backend lets the search succeed while discarding the audio.
bool null_render_device_enabled(); bool null_render_device_enabled();
// fake IMMDevice that reports a "Realtek" friendly name and activates straight // fake IMMDevice that reports a "Realtek" friendly name and activates straight
// into the null audio backend, never touching real hardware. // into the null audio backend, never touching real hardware.
struct NullMMDevice : IMMDevice { struct NullMMDevice : IMMDevice {
NullMMDevice() = default; NullMMDevice() = default;
NullMMDevice(const NullMMDevice &) = delete; NullMMDevice(const NullMMDevice &) = delete;
NullMMDevice &operator=(const NullMMDevice &) = delete; NullMMDevice &operator=(const NullMMDevice &) = delete;
virtual ~NullMMDevice() = default; virtual ~NullMMDevice() = default;
#pragma region IUnknown #pragma region IUnknown
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override; HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
ULONG STDMETHODCALLTYPE AddRef() override; ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override; ULONG STDMETHODCALLTYPE Release() override;
#pragma endregion #pragma endregion
#pragma region IMMDevice #pragma region IMMDevice
HRESULT STDMETHODCALLTYPE Activate(REFIID iid, DWORD dwClsCtx, PROPVARIANT *pActivationParams, void **ppInterface) override; HRESULT STDMETHODCALLTYPE Activate(REFIID iid, DWORD dwClsCtx, PROPVARIANT *pActivationParams, void **ppInterface) override;
HRESULT STDMETHODCALLTYPE OpenPropertyStore(DWORD stgmAccess, IPropertyStore **ppProperties) override; HRESULT STDMETHODCALLTYPE OpenPropertyStore(DWORD stgmAccess, IPropertyStore **ppProperties) override;
HRESULT STDMETHODCALLTYPE GetId(LPWSTR *ppstrId) override; HRESULT STDMETHODCALLTYPE GetId(LPWSTR *ppstrId) override;
HRESULT STDMETHODCALLTYPE GetState(DWORD *pdwState) override; HRESULT STDMETHODCALLTYPE GetState(DWORD *pdwState) override;
#pragma endregion #pragma endregion
private: private:
std::atomic<ULONG> ref_cnt = 1; std::atomic<ULONG> ref_cnt = 1;
}; };
@@ -1,139 +1,139 @@
#include "null_discard_backend.h" #include "null_discard_backend.h"
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include "hooks/audio/util.h" #include "hooks/audio/util.h"
#include "util/logging.h" #include "util/logging.h"
NullDiscardBackend::~NullDiscardBackend() { NullDiscardBackend::~NullDiscardBackend() {
this->running = false; this->running = false;
if (this->pacing_thread.joinable()) { if (this->pacing_thread.joinable()) {
this->pacing_thread.join(); this->pacing_thread.join();
} }
} }
const WAVEFORMATEXTENSIBLE &NullDiscardBackend::format() const noexcept { const WAVEFORMATEXTENSIBLE &NullDiscardBackend::format() const noexcept {
return this->format_; return this->format_;
} }
HRESULT NullDiscardBackend::on_initialize( HRESULT NullDiscardBackend::on_initialize(
AUDCLNT_SHAREMODE *, AUDCLNT_SHAREMODE *,
DWORD *, DWORD *,
REFERENCE_TIME *hnsBufferDuration, REFERENCE_TIME *hnsBufferDuration,
REFERENCE_TIME *, REFERENCE_TIME *,
const WAVEFORMATEX *pFormat, const WAVEFORMATEX *pFormat,
LPCGUID) LPCGUID)
{ {
copy_wave_format(&this->format_, pFormat); copy_wave_format(&this->format_, pFormat);
// honor the game's requested buffer duration, falling back to 10 ms // honor the game's requested buffer duration, falling back to 10 ms
constexpr REFERENCE_TIME DEFAULT_REFTIME = 100000; // 10 ms in 100-ns units constexpr REFERENCE_TIME DEFAULT_REFTIME = 100000; // 10 ms in 100-ns units
this->period_reftime = (hnsBufferDuration && *hnsBufferDuration > 0) this->period_reftime = (hnsBufferDuration && *hnsBufferDuration > 0)
? *hnsBufferDuration ? *hnsBufferDuration
: DEFAULT_REFTIME; : DEFAULT_REFTIME;
this->buffer_frames = std::max<uint32_t>(1, static_cast<uint32_t>( this->buffer_frames = std::max<uint32_t>(1, static_cast<uint32_t>(
static_cast<double>(this->format_.Format.nSamplesPerSec) static_cast<double>(this->format_.Format.nSamplesPerSec)
* this->period_reftime / 10000000.0 + 0.5)); * this->period_reftime / 10000000.0 + 0.5));
log_info("audio::null", "initializing null render device with {} channels, {} Hz, {}-bit", log_info("audio::null", "initializing null render device with {} channels, {} Hz, {}-bit",
this->format_.Format.nChannels, this->format_.Format.nChannels,
this->format_.Format.nSamplesPerSec, this->format_.Format.nSamplesPerSec,
this->format_.Format.wBitsPerSample); this->format_.Format.wBitsPerSample);
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_get_buffer_size(uint32_t *buffer_frames) { HRESULT NullDiscardBackend::on_get_buffer_size(uint32_t *buffer_frames) {
*buffer_frames = this->buffer_frames; *buffer_frames = this->buffer_frames;
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_get_stream_latency(REFERENCE_TIME *latency) { HRESULT NullDiscardBackend::on_get_stream_latency(REFERENCE_TIME *latency) {
*latency = this->period_reftime; *latency = this->period_reftime;
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_get_current_padding(std::optional<uint32_t> &padding_frames) { HRESULT NullDiscardBackend::on_get_current_padding(std::optional<uint32_t> &padding_frames) {
// discarded immediately, so the buffer always reads as fully drained // discarded immediately, so the buffer always reads as fully drained
padding_frames = 0; padding_frames = 0;
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_is_format_supported( HRESULT NullDiscardBackend::on_is_format_supported(
AUDCLNT_SHAREMODE *, AUDCLNT_SHAREMODE *,
const WAVEFORMATEX *, const WAVEFORMATEX *,
WAVEFORMATEX **ppClosestMatch) WAVEFORMATEX **ppClosestMatch)
{ {
if (ppClosestMatch) { if (ppClosestMatch) {
*ppClosestMatch = nullptr; *ppClosestMatch = nullptr;
} }
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_get_mix_format(WAVEFORMATEX **) { HRESULT NullDiscardBackend::on_get_mix_format(WAVEFORMATEX **) {
return E_NOTIMPL; return E_NOTIMPL;
} }
HRESULT NullDiscardBackend::on_get_device_period( HRESULT NullDiscardBackend::on_get_device_period(
REFERENCE_TIME *default_device_period, REFERENCE_TIME *default_device_period,
REFERENCE_TIME *minimum_device_period) REFERENCE_TIME *minimum_device_period)
{ {
if (default_device_period) { if (default_device_period) {
*default_device_period = this->period_reftime; *default_device_period = this->period_reftime;
} }
if (minimum_device_period) { if (minimum_device_period) {
*minimum_device_period = this->period_reftime; *minimum_device_period = this->period_reftime;
} }
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_start() { HRESULT NullDiscardBackend::on_start() {
if (!this->running.exchange(true)) { if (!this->running.exchange(true)) {
this->pacing_thread = std::thread(&NullDiscardBackend::pace_loop, this); this->pacing_thread = std::thread(&NullDiscardBackend::pace_loop, this);
} }
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_stop() { HRESULT NullDiscardBackend::on_stop() {
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_set_event_handle(HANDLE *event_handle) { HRESULT NullDiscardBackend::on_set_event_handle(HANDLE *event_handle) {
// keep the game's event so pace_loop() can wake it; there is no real device behind it // keep the game's event so pace_loop() can wake it; there is no real device behind it
this->relay_handle = *event_handle; this->relay_handle = *event_handle;
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) { HRESULT NullDiscardBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) {
const size_t buffer_size = const size_t buffer_size =
static_cast<size_t>(this->format_.Format.nBlockAlign) * num_frames_requested; static_cast<size_t>(this->format_.Format.nBlockAlign) * num_frames_requested;
if (this->scratch.size() < buffer_size) { if (this->scratch.size() < buffer_size) {
this->scratch.resize(buffer_size); this->scratch.resize(buffer_size);
} }
*ppData = this->scratch.data(); *ppData = this->scratch.data();
return S_OK; return S_OK;
} }
HRESULT NullDiscardBackend::on_release_buffer(uint32_t, DWORD) { HRESULT NullDiscardBackend::on_release_buffer(uint32_t, DWORD) {
// discard the audio entirely // discard the audio entirely
return S_OK; return S_OK;
} }
void NullDiscardBackend::pace_loop() { void NullDiscardBackend::pace_loop() {
using namespace std::chrono; using namespace std::chrono;
// audio is discarded, so timing precision and drift do not matter; just wake the // audio is discarded, so timing precision and drift do not matter; just wake the
// game once per buffer period to keep its render thread from blocking on the event. // game once per buffer period to keep its render thread from blocking on the event.
const auto period = duration_cast<steady_clock::duration>( const auto period = duration_cast<steady_clock::duration>(
duration<double>(this->period_reftime / 10000000.0)); duration<double>(this->period_reftime / 10000000.0));
while (this->running.load()) { while (this->running.load()) {
if (this->relay_handle) { if (this->relay_handle) {
SetEvent(this->relay_handle); SetEvent(this->relay_handle);
} }
std::this_thread::sleep_for(period); std::this_thread::sleep_for(period);
} }
} }
@@ -1,54 +1,54 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <optional> #include <optional>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include <audioclient.h> #include <audioclient.h>
#include "hooks/audio/implementations/backend.h" #include "hooks/audio/implementations/backend.h"
// discards all audio while pacing the game's event handle once per buffer period, so the game // discards all audio while pacing the game's event handle once per buffer period, so the game
// keeps running normally with nothing output to any real device. routed through the shared // keeps running normally with nothing output to any real device. routed through the shared
// DummyIAudioClient, the same plumbing the asio backend uses. // DummyIAudioClient, the same plumbing the asio backend uses.
struct NullDiscardBackend final : AudioBackend { struct NullDiscardBackend final : AudioBackend {
~NullDiscardBackend() final; ~NullDiscardBackend() final;
const WAVEFORMATEXTENSIBLE &format() const noexcept override; const WAVEFORMATEXTENSIBLE &format() const noexcept override;
HRESULT on_initialize( HRESULT on_initialize(
AUDCLNT_SHAREMODE *, AUDCLNT_SHAREMODE *,
DWORD *, DWORD *,
REFERENCE_TIME *hnsBufferDuration, REFERENCE_TIME *hnsBufferDuration,
REFERENCE_TIME *, REFERENCE_TIME *,
const WAVEFORMATEX *pFormat, const WAVEFORMATEX *pFormat,
LPCGUID) override; LPCGUID) override;
HRESULT on_get_buffer_size(uint32_t *buffer_frames) override; HRESULT on_get_buffer_size(uint32_t *buffer_frames) override;
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) override; HRESULT on_get_stream_latency(REFERENCE_TIME *latency) override;
HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) override; HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) override;
HRESULT on_is_format_supported( HRESULT on_is_format_supported(
AUDCLNT_SHAREMODE *, AUDCLNT_SHAREMODE *,
const WAVEFORMATEX *, const WAVEFORMATEX *,
WAVEFORMATEX **ppClosestMatch) override; WAVEFORMATEX **ppClosestMatch) override;
HRESULT on_get_mix_format(WAVEFORMATEX **) override; HRESULT on_get_mix_format(WAVEFORMATEX **) override;
HRESULT on_get_device_period( HRESULT on_get_device_period(
REFERENCE_TIME *default_device_period, REFERENCE_TIME *default_device_period,
REFERENCE_TIME *minimum_device_period) override; REFERENCE_TIME *minimum_device_period) override;
HRESULT on_start() override; HRESULT on_start() override;
HRESULT on_stop() override; HRESULT on_stop() override;
HRESULT on_set_event_handle(HANDLE *event_handle) override; HRESULT on_set_event_handle(HANDLE *event_handle) override;
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) override; HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) override;
HRESULT on_release_buffer(uint32_t, DWORD) override; HRESULT on_release_buffer(uint32_t, DWORD) override;
private: private:
void pace_loop(); void pace_loop();
WAVEFORMATEXTENSIBLE format_ {}; WAVEFORMATEXTENSIBLE format_ {};
uint32_t buffer_frames = 0; uint32_t buffer_frames = 0;
REFERENCE_TIME period_reftime = 0; REFERENCE_TIME period_reftime = 0;
HANDLE relay_handle = nullptr; HANDLE relay_handle = nullptr;
std::vector<BYTE> scratch; std::vector<BYTE> scratch;
std::thread pacing_thread; std::thread pacing_thread;
std::atomic<bool> running = false; std::atomic<bool> running = false;
}; };

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