Compare commits
95
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
786a4254d2 | ||
|
|
09ac50f2d5 | ||
|
|
2dc3c0cbe4 | ||
|
|
4c2a70bab6 | ||
|
|
46f76597fc | ||
|
|
b23640222c | ||
|
|
3f6862908e | ||
|
|
0934cce225 | ||
|
|
7c50fcc79e | ||
|
|
0f4ab63101 | ||
|
|
8b2f38307b | ||
|
|
3863d5a4ed | ||
|
|
94574c485a | ||
|
|
f857926ec3 | ||
|
|
adf4cccd4a | ||
|
|
bf8e194685 | ||
|
|
a2e508208c | ||
|
|
f5888609a8 | ||
|
|
c6cd72c528 | ||
|
|
82e0c053d0 | ||
|
|
3b29227dbc | ||
|
|
079bb35b1f | ||
|
|
95051b6525 | ||
|
|
ce2f93d234 | ||
|
|
13a171f199 | ||
|
|
3da352488e | ||
|
|
2386047c2f | ||
|
|
baa550037a | ||
|
|
ec3ca1a055 | ||
|
|
da77f60b71 | ||
|
|
0c1998abe4 | ||
|
|
64260623bf | ||
|
|
3ddd34d51d | ||
|
|
ce0344742e | ||
|
|
a7723148bc | ||
|
|
78621e5dca | ||
|
|
1d7fe00703 | ||
|
|
e923b7918f | ||
|
|
0834646097 | ||
|
|
82439962d6 | ||
|
|
97cca85bc8 | ||
|
|
bb0fd62ec3 | ||
|
|
734c762bd6 | ||
|
|
1cb5b7b52a | ||
|
|
244f4aa56a | ||
|
|
26bc8fdda5 | ||
|
|
f13c9d0e50 | ||
|
|
aa9ea9204b | ||
|
|
ea5d566218 | ||
|
|
56e8e7456b | ||
|
|
7f63b77ab3 | ||
|
|
42689377e6 | ||
|
|
3f7dcd72bb | ||
|
|
e15c689061 | ||
|
|
6561ea9230 | ||
|
|
2b3071c9a1 | ||
|
|
04fc516d47 | ||
|
|
652ad41845 | ||
|
|
2c188f2ac4 | ||
|
|
7b5d86fe65 | ||
|
|
127d07ed31 | ||
|
|
7ceeda8bca | ||
|
|
cd709f0a55 | ||
|
|
07249ebb1c | ||
|
|
f0855d9b53 | ||
|
|
0b6986e12c | ||
|
|
d72f4b780a | ||
|
|
5075efc6dc | ||
|
|
88a19a8811 | ||
|
|
4a45f4d9c4 | ||
|
|
a34014876a | ||
|
|
74ef5237ad | ||
|
|
933dcf72a9 | ||
|
|
8a5f1b5017 | ||
|
|
c70e12ff07 | ||
|
|
359716b850 | ||
|
|
c94de456b5 | ||
|
|
e72da8ad5f | ||
|
|
3da7aa5be0 | ||
|
|
40a755173a | ||
|
|
04dee88276 | ||
|
|
572cf9d12c | ||
|
|
6c53848ea4 | ||
|
|
32f4f1871d | ||
|
|
f6f62e0e9d | ||
|
|
15cd228b8d | ||
|
|
5951a550bb | ||
|
|
a0e44f9e4b | ||
|
|
90ebf58554 | ||
|
|
a3f2d56a9e | ||
|
|
d03cca1fe2 | ||
|
|
8957d9d446 | ||
|
|
6c914266d9 | ||
|
|
b7a60638a4 | ||
|
|
14215af0d1 |
@@ -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
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Generated
+10
@@ -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
|
||||||
Generated
+11
@@ -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 "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE="external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake"" />
|
||||||
|
<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 "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE="external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake"" />
|
||||||
|
<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 "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE="external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake"" />
|
||||||
|
<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 "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE="external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake"" />
|
||||||
|
</configurations>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+350
@@ -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
@@ -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>
|
||||||
Generated
+10
@@ -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>
|
||||||
Generated
+71
@@ -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>
|
||||||
Generated
+9
@@ -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>
|
||||||
Generated
+8
@@ -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>
|
||||||
Generated
+17
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
Generated
+2
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module classpath="CIDR" type="CPP_MODULE" version="4" />
|
||||||
Generated
+6
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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(¶m, "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(¶m, "baseline") < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->encoder = x264_encoder_open(¶m);
|
||||||
|
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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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, ×tamp, &width, &height);
|
screen, pixels, divide, ×tamp, &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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ¶m : 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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() {}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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"}"
|
||||||
|
|||||||
Executable
+88
@@ -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
@@ -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
@@ -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.
|
||||||
|
|||||||
+11023
-11023
File diff suppressed because one or more lines are too long
+552
-552
File diff suppressed because it is too large
Load Diff
+73
-73
@@ -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 */
|
||||||
|
|||||||
Vendored
+3222
File diff suppressed because it is too large
Load Diff
Vendored
+122
@@ -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
|
||||||
+835
-835
File diff suppressed because it is too large
Load Diff
Vendored
+62
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-10
@@ -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
@@ -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
@@ -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 :-)
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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") &&
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
+1067
-1067
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user