Compare commits

73 Commits
Author SHA1 Message Date
REDCODE da77f60b71 fix docker build: missing wget and C++ includes for GCC16
Continuous Integration / Build (push) Failing after 1m0s
- add wget to deps Dockerfile for dsdmo.dll download
- include <mutex> in wmischook.cpp
- include <codecvt> and load SHCreateMemStream dynamically in sndbhook.cpp for XP compat
2026-08-07 00:21:58 +02:00
REDCODE 0c1998abe4 Merge branch 'main' into nac-spice22x
Merge official spice2x 'main', up to b53447b
Notes: dc82c98  is where the nixac fork starts.

- Upstream's OBJECT-library + forbidden-static-DLL-import build refactor
  merged with nixAC's blob/dsdmo resources
- SPICE_XP / runtime large-address-aware detection builds have been
  integrated from upstream
- README.md grabs from upstream; .gitignore keeps nixAC's fork-specific
  entries
2026-08-07 00:21:58 +02:00
REDCODE 64260623bf Align nixAC to spice2x folder structure 2026-08-06 16:03:22 +02:00
bicarusandGitHub b53447bed5 popn: fix subscreen redraw option causing graphical glitches (#854)
Continuous Integration / Build (push) Failing after 6s
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Old behavior: Forced redraw presented the subscreen every main frame,
even when the game already presented it, causing duplicate presents and
tearing in popn (was fine in sdvx)

New behavior: Forced redraw acts as a fallback, presenting only when the
game skips or fails a subscreen update.

## Testing
Popn - no more glitching
Nabla - no regression
2026-08-06 04:14:31 -07:00
bicarusandGitHub 4959a58de3 iidx: don't hook legacy camera unless requested (#856)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #855 

## Description of change
Legacy camera hooks are specifically for IIDX 25/26 and they only
perform redirection of device discovery (emulates USB vendor/device ID
and USB port). It has very limited use since most cameras are
practically unusable in IIDX 25/26. Don't enable it, unless explicitly
requested by the user.

New truth table for `-iidxcabcams` :

| Mode | `-iidx` off (cab setup) | `-iidx` on |
|---|---|---|
| `auto` | Native cameras enabled (same as `on`) | Cameras disabled
(same as `off`) |
| `off` | Cameras disabled | Cameras disabled |
| `on` | Native cameras enabled | Native cameras enabled |
| `legacy` | Native cameras enabled | IIDX 25/26 discovery emulation
enabled |

## Testing
2026-08-06 03:44:52 -07:00
bicarusandGitHub 9141ff453b lang: detect forced UTF-8 ACP, try to opt-out (#853)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change

Fixes broken Japanese text when Windows `Use Unicode UTF-8 for worldwide
language support` setting is enabled. This setting changes the system
ACP to UTF-8 (65001), causing legacy Shift-JIS lead-byte checks to fail.

Windows 11: requests the legacy process code page through the manifest.

Windows 10: detects ACP 65001 (UTF-8), warns the user via deferred log,
and applies compatibility hook (only for popn pika model for now)

Windows 7 and below - UTF-8 option doesn't exist.

## Testing
2026-08-04 09:04:29 -07:00
bicarusandGitHub f955ab984c iidx: allow main window to receive touches in windows TDJ subscreen mode (#850)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
This allows API touches to be recognized even if the main window is in
focus.

## Testing
2026-08-02 16:46:33 -07:00
bicarus-dev 435b9ce9aa output device name in message 2026-08-02 12:29:21 -07:00
bicarusandGitHub 1ec7528dac gitadora: option to disable frame limiter (#848)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
There is a patch for GITADORA series that fixes frame pacing on modern
Windows. Turn it into a signature patch and embed it into the game.

## Testing
should work for all 64-bit gitadora versions, pre-GWD
2026-08-02 03:35:07 -07:00
bicarus-dev e7822aad8e remove volume boost from quick options 2026-08-02 01:04:11 -07:00
[ ] 3ddd34d51d feat: Provide DMO functionality using dsdmo blob
> fetch dsdmo.dll blobs and embed into spicetools
> unpack and register overriding relevant COM server on runtime
> may be replaced in the future, see #12
2026-02-21 20:54:23 +09:00
[ ] ce0344742e refactor/feat: Further signature scan changes
> funcionality cleanup, API compatibility should be the same
> improve patching performance by skipping vector copy (replaced with span)
> chain call wrapper (fixes soudbank hook breaking 32-bit styles)
2026-02-20 18:36:42 +09:00
[ ] a7723148bc chore: Revert upstream
> temporarily disable upstream#471 (should be addressed in #10 and #11)
> disable upstream#466
> disable upstream#474
> noop for upstream#455 (excluded in supported build targets)
> partial revert for upstream#405 (including previous merge commit)
2026-02-19 16:56:51 +09:00
[ ] 78621e5dca Merge branch 'spice2x' into spice22x 2026-02-19 16:05:17 +09:00
[ ] 1d7fe00703 Update to spice2x-26-02-17 (completed)
> adjust cmake.sh (ignore separate linux targets)
> adjust .gitignore
2026-02-19 15:59:06 +09:00
[ ] e923b7918f Update to spice2x-26-02-17 (pre-apply)
> broken commit
2026-02-19 15:41:42 +09:00
[ ] 0834646097 refactor/feat: Signature scan improvements
> use reverse pattern search instead, fixes performance issue for some hooks
> sigscan still requires overhead of module copy for each operation
2026-02-18 07:42:11 +09:00
[ ] 82439962d6 refactor: Soundbank runtime hooking improvement
> change default behaviour to pattern based patch for hooks::soundbank, see #13
> fix broken check for hooks::soundbank unsupported game version
2026-02-17 19:30:01 +09:00
[ ] 97cca85bc8 refactor: Lazy load winscard
> still requires proper testing with hardware
2026-01-22 08:47:14 +09:00
[ ] bb0fd62ec3 feat: Crude SDVX 5 support
> disable lobby video (cause for crashes in lobby), see #9
> suppress dmoeffector log messages, see #9
2026-01-20 08:31:17 +09:00
[ ] 734c762bd6 code/refactor: Deduplicate sigscan code
> cleanup unnecessary code, should break nothing
> probably will cause merge issues someday
> add missing `replace_pattern_from`
2026-01-20 07:56:22 +09:00
[ ] 1cb5b7b52a fix: Sound Voltex 6 pipewire support
> should resolve `-audiobackend` incompatibilities, see #9
2026-01-16 03:32:44 +09:00
[ ] 244f4aa56a refactor/fix: Resolve some wmisc issues
> fix crashes around missing call convention/log hook
> enable wmisc for M39 as well, see #8
2026-01-11 21:11:55 +09:00
[ ] 26bc8fdda5 fix: Solve some missed multi-soundbank song errors
> missed some rarer cases, as long as soundbank is alphanumeric, it should work now
> affected 2dx soundbanks like 01004 and non-digit ones like 28102
2026-01-06 00:53:31 +09:00
[ ] f13c9d0e50 refactor/code: Change hooks::soundbank::snd_bank::resampler_ to user definable
> add support for `extra.sndb_resampler` in linux.json
> revert default resampler back to `bmswac_resampler_s16`
> backward support for 0.2.x
2025-12-22 22:50:50 +09:00
[ ] aa9ea9204b refactor: Enable spicetools logging for bmsound-wine by default
> optional bmsound-wine API until v0.3.0
2025-12-21 09:05:47 +09:00
[ ] ea5d566218 feat: Test spicetools logging bmsound-wine side
> broken commit
> route bmsound messages back to spicetools log
2025-12-20 21:23:22 +09:00
[ ] 56e8e7456b refactor: Cleanup develhook logic
> refactor hooks::devel to work better with hooks::wmisc
> minor naming consistency changes
2025-12-17 00:00:40 +09:00
[ ] 7f63b77ab3 refactor: Unify string hashing
> moved to util/utils.h
2025-12-15 23:53:18 +09:00
[ ] 42689377e6 feat: Resolve long load times before get ready for IIDX
> see #4
> partial improvements for result screen hang, see #2
2025-12-14 23:17:40 +09:00
[ ] 3f7dcd72bb code: Minor changes
> related to previous commit
2025-11-19 20:36:10 +09:00
[ ] e15c689061 feat: Resolve low framerate during song select for IIDX32+
> caused by bad code around thumbnail fetching logic including file checks in every draw call, see #4
> added `hooks::wmisc` for linux specific minor performance fixes and similar
2025-11-19 08:13:37 +09:00
[ ] 6561ea9230 refactor: Add support for LDJ-20250825
> account for changes in IIDX32 onward affecting `hooks::soundbank`
> `hooks::soundbank` should support any ambiguous IIDX32/33 datacodes again
2025-11-12 00:40:42 +09:00
[ ] 2b3071c9a1 Merge branch 'spice2x' into spice22x 2025-11-11 06:00:36 +09:00
[ ] 04fc516d47 Update to spice2x-25-10-07 (completed)
> adjust run configurations
> adjust cmake.sh
> adjust .gitignore
2025-11-11 05:50:32 +09:00
[ ] 652ad41845 Update to spice2x-25-10-07 (pre-apply)
> broken commit
2025-11-11 04:57:26 +09:00
[ ] 2c188f2ac4 refactor: Minor changes
> use bmswac_resampler_s16_ad temporarily (reduces load times)
> fix hook name
2025-11-11 04:33:40 +09:00
[ ] 7b5d86fe65 fix: Fix header size assert
> probably wont ever matter, change for consistency to use `adpcm_ms` header size instead
2025-06-04 22:15:35 +09:00
[ ] 127d07ed31 refactor: Ambiguous game version support for hooks::soundbank
> user supplied sndbhook offset through `prop/linux.json`
> fix tests
2025-06-02 23:34:42 +09:00
[ ] 7ceeda8bca refactor: Handle optional experimental code better
> introduce `hooks::devel` to aggregate debug specific code
> fix support for IIDX31 (target LDJ-010 instead)
2025-06-02 21:34:28 +09:00
[ ] cd709f0a55 fix: Support per difficulty soundbanks
> related to #1
2025-05-30 23:11:47 +09:00
[ ] 07249ebb1c feat: Add support for IIDX25-26 to transcoder
> added support for adpcm_ms wav codec required by these versions
> related to #1
2025-05-29 02:41:23 +09:00
[ ] f0855d9b53 code: Cleanups around added code
> related to #1
2025-05-28 00:27:46 +09:00
[ ] 0b6986e12c fix: stop_subsystems() missing some log messages
> related to #1
2025-05-27 23:30:45 +09:00
[ ] d72f4b780a refactor: Make assertsz_asf() resampler aware
> resampling free conversion support (results in sound data errors, game only expects s16le for wavs)
> related to #1
2025-05-27 23:11:02 +09:00
[ ] 5075efc6dc refactor: Enable unreachable cleanup logic in main()
> refactor `main_implemntation()` cleanup to run once on `launcher::shutdown()`, not sure why it was defined like that (unreachable code, at least for iidx)
> not thread safe, but probably not needed (no obvious failures as well)
> related to #1
2025-05-27 23:08:16 +09:00
[ ] 88a19a8811 refactor/build: Sanitize MF logic
> revert linking to MF libraries on build
> move MF init to be used for tests only
> fix 32-bit builds
> related to #1
2025-05-27 20:49:49 +09:00
[ ] 4a45f4d9c4 refactor: Add transcoding support for more styles
> some styles still won't work due to SOUND DATA CREATE ERROR
> related to #1
2025-05-26 23:14:57 +09:00
[ ] a34014876a feat: Add avs_fs_umount()
> only added for avs 2.17.3.0
> change `avs_fs_mount()` return type to avs_file_t (should be avs_handle_t to begin with)
> related to #1
2025-05-26 20:26:00 +09:00
[ ] 74ef5237ad feat: Integrate linux-side avcodec support
> support only IIDX30
> working full wma=>wav transcoding pipeline with ifs support
> missing unmounting behaviour (will add later)
> depends on latest bmsound_wine for avcodec transcoding
> related to #1
2025-05-25 21:05:24 +09:00
[ ] 933dcf72a9 feat: Add iidx soundbank hook
> mockup using MF decoder (which by itself is broken, it seems)
> working .s3p unpacking and .2dx packing
> use avs filesystem (read only)
> soundbank patching disabled (missing some logic)
> unity games may be broken (linker)
> related to #1
2025-05-22 23:53:19 +09:00
[ ] 8a5f1b5017 build: Fix merge inconsistencies
> revert no longer needed fixes
> update clion configs
2025-05-09 02:56:04 +09:00
[ ] c70e12ff07 Merge branch 'spice2x' into spice22x 2025-05-09 02:54:57 +09:00
[ ] 359716b850 Update to spice2x-25-04-25 (completed)
> adjust run configurations
> adjust cmake.sh
2025-05-09 01:38:46 +09:00
[ ] c94de456b5 Update to spice2x-25-04-25 (pre-apply)
> broken commit
2025-05-07 00:25:31 +09:00
[ ] e72da8ad5f build: Fix building for spice2x-20230929
> resolve third party libraries not building
> force include stdint.h in place of cstdint missing from windows.h now
> to be reverted after updates to spice2x master
2025-05-06 23:52:24 +09:00
[ ] 3da7aa5be0 feat: Update pipewire backend to support the latest bmsound_wine
> minimal necessary implementation for `loopback_signal` bmsound_wine profile
2025-05-05 03:16:01 +09:00
[ ] 40a755173a Merge branch 'spice2x' into spice22x 2023-10-24 20:25:56 +09:00
[ ] 04dee88276 Update to spice2x-23-09-29
> omitted mostly useless path length warning
2023-10-24 20:24:09 +09:00
[ ] 572cf9d12c refactor: Update pipewire backend to the latest version
> support for linux config
> some cleanup around bmsw API usage
2023-10-19 02:49:48 +09:00
[ ] 6c53848ea4 feat: First working pipewire endpoint
> uses `T_NOTIF_SPICE` experimental endpoint on bmsound-pw's side
> latency should be on par with wasapi shared?
2023-10-09 01:55:17 +09:00
[ ] 32f4f1871d feat: Add global access for selected Game object
> separated commit in case this functionality is already provided through other means and needs to be reverted
> probably target for rehaul in the future for better/less invasive approach
2023-10-02 23:56:13 +09:00
[ ] f6f62e0e9d feat: Add pipewire backend
> only for testing current bmsound_wine implementation wine-side
2023-10-02 23:51:45 +09:00
[ ] 15cd228b8d feat: Dummy none backend
> to run games with no sound
2023-09-28 20:21:06 +09:00
[ ] 5951a550bb refactor: Bad attempt of fixing waveout
> tried to unify use of hooks::audio::FORMAT between backends
2023-09-26 05:20:02 +09:00
[ ] a0e44f9e4b build: Add .idea project files tracking 2023-09-26 05:09:15 +09:00
[ ] 90ebf58554 build: Add .idea project files tracking 2023-09-25 00:31:06 +09:00
[ ] a3f2d56a9e build: Add cmake.sh wrapper 2023-09-20 04:06:02 +09:00
[ ] d03cca1fe2 Update to spice2x-23-06-08 2023-09-20 02:09:17 +09:00
smpn2 8957d9d446 Apply spicetools-22-10-21 2022-12-16 23:19:02 +09:00
smpn2 6c914266d9 chore: CRLF to LF 2022-12-16 23:18:35 +09:00
smpn2 b7a60638a4 Let us start here, from square one. No, from Zero! 2022-09-30 22:45:51 +09:00
smpn2 14215af0d1 Initial commit 2022-09-30 22:44:38 +09:00
80 changed files with 3411 additions and 237 deletions
+14 -1
View File
@@ -1,7 +1,6 @@
.vscode/* .vscode/*
# JetBrains IDEs # JetBrains IDEs
.idea
.idea_modules .idea_modules
*.iws *.iws
@@ -11,3 +10,17 @@ cmake-build*
# user config # user config
build_all.local.sh build_all.local.sh
build_docker.local.sh build_docker.local.sh
# output from build script
bin/*
dist/*
build/*.dll
external/cv2pdb/*
.ccache/*
# Visual Studio
.vs
out
CMakeSettings.json
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Unnecessary plugin data
/markdown-navigator.xml
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSharedSettings">
<configurations>
<configuration PROFILE_NAME="Release_x64[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x64/Release" CONFIG_NAME="Release" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake&quot;" />
<configuration PROFILE_NAME="Release_x86[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x86/Release" CONFIG_NAME="Release" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake&quot;" />
<configuration PROFILE_NAME="Debug_x64[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x64/Debug" CONFIG_NAME="Debug" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake&quot;" />
<configuration PROFILE_NAME="Debug_x86[WIN_32]" ENABLED="true" GENERATION_DIR="bin/build/x86/Debug" CONFIG_NAME="Debug" TOOLCHAIN_NAME="MinGW [Crosscompile]" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DCMAKE_TOOLCHAIN_FILE=&quot;external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake&quot;" />
</configurations>
</component>
</project>
+72
View File
@@ -0,0 +1,72 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ConstantConditionsOC" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyBroadExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="2.7" />
<item index="1" class="java.lang.String" itemvalue="3.7" />
<item index="2" class="java.lang.String" itemvalue="3.8" />
<item index="3" class="java.lang.String" itemvalue="3.9" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyDefaultArgumentInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E266" />
<option value="E701" />
<option value="E265" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N806" />
<option value="N803" />
<option value="N802" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyTypeCheckerInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnusedLocalInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ShellCheck" enabled="true" level="ERROR" enabled_by_default="true">
<shellcheck_settings value="SC2164" />
</inspection_tool>
<inspection_tool class="Simplify" enabled="false" level="WARNING" enabled_by_default="false">
<option name="clangTidyCheckOptions">
<list>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyConstantConditions" />
<option name="optionValue" value="1" />
</ClangTidyCheckOption>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyIfWithReturn" />
<option name="optionValue" value="0" />
</ClangTidyCheckOption>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyEqualToTrueFalse" />
<option name="optionValue" value="1" />
</ClangTidyCheckOption>
<ClangTidyCheckOption>
<option name="optionName" value="clion-simplify.SimplifyTernaryWithConstantBranch" />
<option name="optionValue" value="1" />
</ClangTidyCheckOption>
</list>
</option>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownEnhProjectSettings">
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
</project>
+71
View File
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FlexmarkProjectSettings">
<FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false">
<flexmarkSectionLanguages>
<option name="1" value="Markdown" />
<option name="2" value="HTML" />
<option name="3" value="flexmark-ast:1" />
</flexmarkSectionLanguages>
</FlexmarkHtmlSettings>
</component>
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" lastLayoutSetsDefault="false">
<PanelProvider>
<provider providerId="com.vladsch.md.nav.editor.swing.html.panel" providerName="Default - Swing" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0">
<PegdownExtensions>
<option name="ATXHEADERSPACE" value="true" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" />
<option name="RELAXEDHRULES" value="true" />
<option name="STRIKETHROUGH" value="true" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
</PegdownExtensions>
<ParserOptions>
<option name="COMMONMARK_LISTS" value="true" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="PRODUCTION_SPEC_PARSER" value="true" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="false" addAnchorLinks="false" anchorLinksWrapText="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" defaultUrlTitle="false" migratedPlantUml="true" migratedAnchorLinks="true" plantUmlConversion="0">
<GeneratorProvider>
<provider providerId="com.vladsch.md.nav.editor.text.html.generator" providerName="Unmodified HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
<fencedCodeConversions>
<option name="c4plantuml" value="NONE" />
<option name="ditaa" value="NONE" />
<option name="erd" value="NONE" />
<option name="graphviz" value="NONE" />
<option name="latex" value="KATEX" />
<option name="math" value="KATEX" />
<option name="mermaid" value="NONE" />
<option name="nomnoml" value="NONE" />
<option name="plantuml" value="NONE" />
<option name="puml" value="NONE" />
<option name="svgbob" value="NONE" />
<option name="umlet" value="NONE" />
<option name="vega" value="NONE" />
<option name="vegalite" value="NONE" />
<option name="wavedrom" value="NONE" />
</fencedCodeConversions>
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.md.nav.editor.text.html.css" providerName="No Stylesheet" />
</StylesheetProvider>
<ScriptProviders />
<cssText />
<cssUriHistory />
</CssSettings>
</component>
</project>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/spicetools.iml" filepath="$PROJECT_DIR$/.idea/spicetools.iml" />
</modules>
</component>
</project>
+17
View File
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Clion[Init]" type="ShConfigurationType" activateToolWindowBeforeRun="false">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/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$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</component>
+23
View File
@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Debug_x64]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="true" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_spice64" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Debug_x64[WIN_32]" />
</method>
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Debug_x86]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="true" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_cfg" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Debug_x86[WIN_32]" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_spice" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Debug_x86[WIN_32]" />
</method>
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Extra_x64]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_stubs_nvEncodeAPI64" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_stubs_nvcuda" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_stubs_nvcuvid" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
</method>
</configuration>
</component>
+23
View File
@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Extra_x86]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_stubs_cpusbxpkm" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_spice_laa" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
</method>
</configuration>
</component>
+23
View File
@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Release_x64]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_spice64" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x64[WIN_32]" />
</method>
</configuration>
</component>
+24
View File
@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Deploy[Release_x86]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="deploy all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Clion[Init]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="spicetools_cfg" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
<option name="RunConfigurationTask" enabled="false" run_configuration_name="spicetools_spice" run_configuration_type="CMakeRunConfiguration" run_configuration_target="CMakeBuildProfile:Release_x86[WIN_32]" />
</method>
</configuration>
</component>
+25
View File
@@ -0,0 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Distribute[Release]" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/cmake.sh" />
<option name="SCRIPT_OPTIONS" value="distribute all" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs>
<env name="_G_clean" value="false" />
<env name="_G_debug" value="false" />
</envs>
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Release_x64]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Release_x86]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Extra_x64]" run_configuration_type="ShConfigurationType" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Deploy[Extra_x86]" run_configuration_type="ShConfigurationType" />
</method>
</configuration>
</component>
+9
View File
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_cfg" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_cfg" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+9
View File
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_spice" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_spice" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+9
View File
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_spice64" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_spice64" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt64" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld64" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+9
View File
@@ -0,0 +1,9 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_spice_laa" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_spice_laa" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kbt" />
<option name="CMake.Explicit.Target" predefinedTargetName="spicetools_stubs_kld" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_cpusbxpkm" type="CMakeRunConfiguration" factoryName="Application" folderName="32" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" 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" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_stubs_nvEncodeAPI64" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_nvcuda" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_stubs_nvcuda" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetools_stubs_nvcuvid" type="CMakeRunConfiguration" factoryName="Application" folderName="64" activateToolWindowBeforeRun="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="spicetools" TARGET_NAME="spicetools_stubs_nvcuvid" CONFIG_NAME="Release_x64[WIN_32]" RUN_PATH="/bin/true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CIDR" type="CPP_MODULE" version="4" />
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+22 -7
View File
@@ -124,8 +124,6 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
else() else()
# warnings as errors
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
# warnings # warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff
@@ -273,7 +271,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
######### #########
@@ -525,6 +536,11 @@ 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
@@ -747,7 +763,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 +776,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 "")
@@ -789,14 +805,14 @@ 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)
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")
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1) target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
@@ -840,7 +856,6 @@ add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
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)
+5
View File
@@ -41,6 +41,7 @@ namespace avs {
AVS_FS_CLOSE_T avs_fs_close {}; AVS_FS_CLOSE_T avs_fs_close {};
AVS_FS_DUMP_MOUNTPOINT_T avs_fs_dump_mountpoint {}; AVS_FS_DUMP_MOUNTPOINT_T avs_fs_dump_mountpoint {};
AVS_FS_MOUNT_T avs_fs_mount {}; AVS_FS_MOUNT_T avs_fs_mount {};
AVS_FS_UMOUNT_T avs_fs_umount {};
AVS_FS_COPY_T avs_fs_copy {}; AVS_FS_COPY_T avs_fs_copy {};
AVS_FS_FSTAT_T avs_fs_fstat {}; AVS_FS_FSTAT_T avs_fs_fstat {};
AVS_FS_LSTAT_T avs_fs_lstat {}; AVS_FS_LSTAT_T avs_fs_lstat {};
@@ -927,6 +928,7 @@ namespace avs {
.avs_fs_close = "XCgsqzn0000055", .avs_fs_close = "XCgsqzn0000055",
.avs_fs_dump_mountpoint = "XCgsqzn0000068", .avs_fs_dump_mountpoint = "XCgsqzn0000068",
.avs_fs_mount = "XCgsqzn000004b", .avs_fs_mount = "XCgsqzn000004b",
.avs_fs_umount = "XCgsqzn000004d",
.avs_fs_fstat = "XCgsqzn0000062", .avs_fs_fstat = "XCgsqzn0000062",
.avs_fs_lstat = "XCgsqzn0000063", .avs_fs_lstat = "XCgsqzn0000063",
.avs_fs_lseek = "XCgsqzn000004f", .avs_fs_lseek = "XCgsqzn000004f",
@@ -1018,6 +1020,7 @@ namespace avs {
.avs_fs_close = "XCgsqzn0000055", .avs_fs_close = "XCgsqzn0000055",
.avs_fs_dump_mountpoint = "XCgsqzn0000068", .avs_fs_dump_mountpoint = "XCgsqzn0000068",
.avs_fs_mount = "XCgsqzn000004b", .avs_fs_mount = "XCgsqzn000004b",
.avs_fs_umount = "XCgsqzn000004d",
.avs_fs_fstat = "XCgsqzn0000062", .avs_fs_fstat = "XCgsqzn0000062",
.avs_fs_lstat = "XCgsqzn0000063", .avs_fs_lstat = "XCgsqzn0000063",
.avs_fs_lseek = "XCgsqzn000004f", .avs_fs_lseek = "XCgsqzn000004f",
@@ -1601,6 +1604,8 @@ namespace avs {
DLL_INSTANCE, IMPORTS[ver].avs_fs_dump_mountpoint); DLL_INSTANCE, IMPORTS[ver].avs_fs_dump_mountpoint);
avs_fs_mount = libutils::try_proc<AVS_FS_MOUNT_T>( avs_fs_mount = libutils::try_proc<AVS_FS_MOUNT_T>(
DLL_INSTANCE, IMPORTS[ver].avs_fs_mount); DLL_INSTANCE, IMPORTS[ver].avs_fs_mount);
avs_fs_umount = libutils::try_proc<AVS_FS_UMOUNT_T>(
DLL_INSTANCE, IMPORTS[ver].avs_fs_umount);
avs_fs_lstat = libutils::try_proc<AVS_FS_LSTAT_T>( avs_fs_lstat = libutils::try_proc<AVS_FS_LSTAT_T>(
DLL_INSTANCE, IMPORTS[ver].avs_fs_lstat); DLL_INSTANCE, IMPORTS[ver].avs_fs_lstat);
+5 -1
View File
@@ -338,6 +338,7 @@ namespace avs {
const char *avs_fs_close = "?"; const char *avs_fs_close = "?";
const char *avs_fs_dump_mountpoint = "?"; const char *avs_fs_dump_mountpoint = "?";
const char *avs_fs_mount = "?"; const char *avs_fs_mount = "?";
const char *avs_fs_umount = "?";
const char *avs_fs_fstat = "?"; const char *avs_fs_fstat = "?";
const char *avs_fs_lstat = "?"; const char *avs_fs_lstat = "?";
const char *avs_fs_lseek = "?"; const char *avs_fs_lseek = "?";
@@ -513,9 +514,12 @@ namespace avs {
typedef int (*AVS_FS_DUMP_MOUNTPOINT_T)(void); typedef int (*AVS_FS_DUMP_MOUNTPOINT_T)(void);
extern AVS_FS_DUMP_MOUNTPOINT_T avs_fs_dump_mountpoint; extern AVS_FS_DUMP_MOUNTPOINT_T avs_fs_dump_mountpoint;
typedef int (*AVS_FS_MOUNT_T)(const char *mountpoint, const char *fsroot, const char *fstype, void *data); typedef avs_file_t (*AVS_FS_MOUNT_T)(const char *mountpoint, const char *fsroot, const char *fstype, void *data);
extern AVS_FS_MOUNT_T avs_fs_mount; extern AVS_FS_MOUNT_T avs_fs_mount;
typedef void (*AVS_FS_UMOUNT_T)(avs_file_t mount);
extern AVS_FS_UMOUNT_T avs_fs_umount;
typedef int (*AVS_FS_COPY_T)(const char *sname, const char *dname); typedef int (*AVS_FS_COPY_T)(const char *sname, const char *dname);
extern AVS_FS_COPY_T avs_fs_copy; extern AVS_FS_COPY_T avs_fs_copy;
+10
View File
@@ -0,0 +1,10 @@
#ifndef RCDATA
#define RCDATA 10
#endif
#ifdef SPICE64
DMO_BLOB RCDATA "dsdmo.x86_64.dll"
#else
DMO_BLOB RCDATA "dsdmo.i686.dll"
#endif
+1
View File
@@ -6,6 +6,7 @@
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"> <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<dpiAware>true</dpiAware> <dpiAware>true</dpiAware>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">Legacy</activeCodePage>
</asmv3:windowsSettings> </asmv3:windowsSettings>
</asmv3:application> </asmv3:application>
<dependency> <dependency>
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
: ${_G_buildir:="bin/build"}
: ${_G_bindir:="bin"}
: ${_G_debug:=false}
: ${_G_clean:=false}
# Hidden build all temporary environment for sourcing
<<__BUILDALLINIT__
# Auto-config for build_all.sh
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse --short HEAD || echo "none")
BUILD_TYPE=$({ $_G_debug && echo Debug; } || echo Release)
OUTDIR="$_G_bindir/${BUILD_TYPE}"
OUTDIR_EXTRAS="$OUTDIR/extras"
DIST_FOLDER="$_G_bindir/Distribution"
DIST_NAME="${GIT_BRANCH}-${GIT_HEAD}.zip"
DIST_COMMENT="$(date +%Y)/$(date +%m)/$(date +%d):$(git rev-parse HEAD)"
DIST_ENABLE=0
UPX_ENABLE="$DIST_ENABLE"
IGNORE_CACHE=0
INCLUDE_SRC=0
UPX_FLAGS="-q --best --lzma --compress-exports=0"
CLEAN_BUILD=$({ $_G_clean && echo 1; } || echo 0)
DEBUG=$({ $_G_debug && echo 1; } || echo 0)
TOOLCHAIN_32="external/cmake-various/toolchain/toolchain-mingw_nix-win-x86.cmake"
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_spice spicetools_spice_laa spicetools_stubs_cpusbxpkm"
BUILDDIR_32="$_G_buildir/x86/${BUILD_TYPE}"
TOOLCHAIN_64="external/cmake-various/toolchain/toolchain-mingw_nix-win-x64.cmake"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_spice64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda"
BUILDDIR_64="$_G_buildir/x64/${BUILD_TYPE}"
__BUILDALLINIT__
run_build_all() {
# Init build_all.sh
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
source <(head -n +20 ./build_all.sh)
# Run build_all.sh
source <(tail -n +80 ./build_all.sh | head -n -3 | sed -e 's~mkdir -p ${OUTDIR}/src~~' -e '/linux/d')
}
run_deploy_all() {
# Init build_all.sh
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
source <(head -n +20 ./build_all.sh)
# Run deploy in build_all.sh
set +e
source <(tail -n +182 ./build_all.sh | head -n -3 | sed -e 's~mkdir -p ${OUTDIR}/src~~' -e '/linux/d')
}
run_distribute_all() {
# Init build_all.sh
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
source <(head -n +20 ./build_all.sh)
# Run deploy in build_all.sh
source <(tail -n +182 ./build_all.sh | head -n -3 | sed -e 's~mkdir -p ${OUTDIR}/src~~' -e '/linux/d')
# Use old distribute logic
echo "Building dist..."
mkdir -p ${DIST_FOLDER}
rm -rf ${DIST_FOLDER}/${DIST_NAME}
pushd ${OUTDIR} > /dev/null
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} . -z <<< "$DIST_COMMENT"
popd > /dev/null
}
run_clion_init() {
# Init cmake.sh only
source <(sed -n '/__BUILDALLINIT__/{:a;n;/__BUILDALLINIT__/q;p;$!ba}' "$0")
# Clion specific first-time setup
}
# Sanity checks
[ -d "external/cmake-various/toolchain" ] || { echo "Toolchain not found" ; exit 1; }
[ "$(md5sum ./build_all.sh | cut -c -32)" = "5c5d7152a1bff96962223458fdf690a2" ] || { echo "Version mismatch for build_all.sh " ; exit 1; }
# Run requested task
if [[ $(type -t "run_${1}_${2}") == function ]]; then
echo "Starting task: 'run_${1}_${2}'"
"run_${1}_${2}"
else
echo "Invalid task: 'run_${1}_${2}'"
exit 1
fi
+1
View File
@@ -7,6 +7,7 @@ RUN pacman --noconfirm -Syu git \
ninja \ ninja \
cmake \ cmake \
unzip \ unzip \
wget \
mingw-w64-crt \ mingw-w64-crt \
mingw-w64-winpthreads \ mingw-w64-winpthreads \
mingw-w64-gcc \ mingw-w64-gcc \
+62
View File
@@ -31,8 +31,65 @@
#include <thread> #include <thread>
#include "util/utils.h" #include "util/utils.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/libutils.h"
#include "hooks/sleephook.h" #include "hooks/sleephook.h"
//_INFO: Lazy load winscard, not tested on actual hardware
struct Winscard
{
decltype(&::SCardEstablishContext) SCardEstablishContext;
decltype(&::SCardListReadersA) SCardListReadersA;
decltype(&::SCardFreeMemory) SCardFreeMemory;
decltype(&::SCardGetStatusChangeA) SCardGetStatusChangeA;
decltype(&::SCardConnectA) SCardConnectA;
decltype(&::SCardTransmit) SCardTransmit;
decltype(&::SCardStatusA) SCardStatusA;
decltype(&::SCardDisconnect) SCardDisconnect;
decltype(::g_rgSCardT0Pci)* g_rgSCardT0Pci;
decltype(::g_rgSCardT1Pci)* g_rgSCardT1Pci;
inline static Winscard *instance = nullptr;
static HMODULE attach()
{
if (instance) return instance->lib_;
instance = new Winscard();
return instance->lib_;
}
private:
HMODULE lib_;
Winscard()
{
#define GETPROC(name) (name) = reinterpret_cast<decltype(name)>(GetProcAddress(lib_, #name))
if ((lib_ = libutils::try_library("winscard.dll")))
{
GETPROC(SCardEstablishContext);
GETPROC(SCardListReadersA);
GETPROC(SCardFreeMemory);
GETPROC(SCardGetStatusChangeA);
GETPROC(SCardConnectA);
GETPROC(SCardTransmit);
GETPROC(SCardStatusA);
GETPROC(SCardDisconnect);
GETPROC(g_rgSCardT0Pci);
GETPROC(g_rgSCardT1Pci);
}
#undef GETPROC
}
};
#define SCardEstablishContext Winscard::instance->SCardEstablishContext
#define SCardListReadersA Winscard::instance->SCardListReadersA
#define SCardFreeMemory Winscard::instance->SCardFreeMemory
#define SCardGetStatusChangeA Winscard::instance->SCardGetStatusChangeA
#define SCardConnectA Winscard::instance->SCardConnectA
#define SCardTransmit Winscard::instance->SCardTransmit
#define SCardStatusA Winscard::instance->SCardStatusA
#define SCardDisconnect Winscard::instance->SCardDisconnect
#define g_rgSCardT0Pci (*Winscard::instance->g_rgSCardT0Pci)
#define g_rgSCardT1Pci (*Winscard::instance->g_rgSCardT1Pci)
#define MAX_APDU_SIZE 255 #define MAX_APDU_SIZE 255
#define SCARD_POLL_INTERVAL_MS 100 #define SCARD_POLL_INTERVAL_MS 100
@@ -419,6 +476,11 @@ int scard_threadmain() {
} }
void scard_threadstart() { void scard_threadstart() {
if(!Winscard::attach())
{
log_warning("scard", "Support disabled due to missing winscard.dll");
return;
}
std::thread t(scard_threadmain); std::thread t(scard_threadmain);
t.detach(); t.detach();
} }
+4
View File
@@ -5,6 +5,10 @@ games::Game::Game(std::string name) {
this->name = name; this->name = name;
} }
const char *games::Game::title() {
return this->name.c_str();
}
void games::Game::attach() { void games::Game::attach() {
log_info("game", "attach: {}", name); log_info("game", "attach: {}", name);
} }
+1
View File
@@ -15,6 +15,7 @@ namespace games {
// where the main magic will happen // where the main magic will happen
virtual void attach(); virtual void attach();
virtual const char *title();
// optional // optional
virtual void pre_attach(); virtual void pre_attach();
+47
View File
@@ -31,6 +31,7 @@ namespace games::gitadora {
// settings // settings
bool TWOCHANNEL = false; bool TWOCHANNEL = false;
bool DISABLE_FRAME_LIMITER = false;
std::optional<unsigned int> CAB_TYPE = std::nullopt; std::optional<unsigned int> CAB_TYPE = std::nullopt;
bool P1_LEFTY = false; bool P1_LEFTY = false;
bool P2_LEFTY = false; bool P2_LEFTY = false;
@@ -65,6 +66,46 @@ namespace games::gitadora {
return CreateDirectoryA(lpPathName, lpSecurityAttributes); return CreateDirectoryA(lpPathName, lpSecurityAttributes);
} }
// libshare-pj paces mainloop with separate 12 ms and 16 ms waits. these waits
// interfere with the game's normal display synchronization on modern Windows
// and can hold a nominal 60 FPS game near 58 FPS. locate the instruction
// sequences at runtime so the fix does not depend on per-version file offsets.
static void disable_mainloop_frame_limiter(HMODULE sharepj_module) {
if (!sharepj_module) {
return;
}
// both limiters have the same shape, only the millisecond target xx differs
// (0Ch for the 12 ms limiter, 10h for the 16 ms one):
//
// 48 83 F8 xx: cmp rax, xx; compare elapsed frame time with the target
// 73 10: jae +10h; skip the wait once the target has elapsed
// B9 xx 00 00 00: mov ecx, xx; load the target
// 48 2B C8: sub rcx, rax; calculate the remaining wait time
// 74 06: je +6h; skip the following six-byte Sleep call if no wait remains
//
// changing jae (73h) to jmp (EBh) makes each block always take its existing
// skip path, which bypasses only the associated Sleep call and leaves the 10h
// branch displacement and every other wait untouched.
const auto limiter_12ms_disabled = replace_pattern(
sharepj_module,
"4883F80C7310B90C000000482BC87406",
"????????EB??????????????????????", 0, 0);
const auto limiter_16ms_disabled = replace_pattern(
sharepj_module,
"4883F8107310B910000000482BC87406",
"????????EB??????????????????????", 0, 0);
if (!limiter_12ms_disabled || !limiter_16ms_disabled) {
log_fatal(
"gitadora",
"failed to disable libshare-pj mainloop frame limiter (-gdnoframelimiter), ensure patch is not already applied");
return;
}
log_info("gitadora", "successfully disabled libshare-pj mainloop frame limiter (-gdnoframelimiter)");
}
#endif #endif
/* /*
@@ -687,6 +728,12 @@ namespace games::gitadora {
HMODULE system_module = libutils::try_module("libsystem.dll"); HMODULE system_module = libutils::try_module("libsystem.dll");
// patches // patches
#ifdef SPICE64
if (DISABLE_FRAME_LIMITER && !is_arena_model()) {
disable_mainloop_frame_limiter(sharepj_module);
}
#endif
detour::inline_hook((void *) eam_network_detected_ip_change, libutils::try_proc( detour::inline_hook((void *) eam_network_detected_ip_change, libutils::try_proc(
sharepj_module, "eam_network_detected_ip_change")); sharepj_module, "eam_network_detected_ip_change"));
detour::inline_hook((void *) eam_network_settings_conflict, libutils::try_proc( detour::inline_hook((void *) eam_network_settings_conflict, libutils::try_proc(
+1
View File
@@ -13,6 +13,7 @@ namespace games::gitadora {
// settings // settings
extern bool TWOCHANNEL; extern bool TWOCHANNEL;
extern bool DISABLE_FRAME_LIMITER;
extern std::optional<unsigned int> CAB_TYPE; extern std::optional<unsigned int> CAB_TYPE;
extern bool P1_LEFTY; extern bool P1_LEFTY;
extern bool P2_LEFTY; extern bool P2_LEFTY;
+6 -18
View File
@@ -58,7 +58,7 @@ namespace games::iidx {
// settings // settings
bool FLIP_CAMS = false; bool FLIP_CAMS = false;
std::optional<bool> DISABLE_CAMS; cab_camera_access_mode CAB_CAMERA_ACCESS = cab_camera_access_mode::automatic;
bool TDJ_CAMERA = false; bool TDJ_CAMERA = false;
bool TDJ_CAMERA_PREFER_16_9 = true; bool TDJ_CAMERA_PREFER_16_9 = true;
bool TDJ_MODE = false; bool TDJ_MODE = false;
@@ -424,10 +424,7 @@ namespace games::iidx {
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE); "RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
// check if cam hook should be enabled // check if cam hook should be enabled
if (!DISABLE_CAMS.has_value()) { if (CAB_CAMERA_ACCESS == cab_camera_access_mode::legacy) {
log_fatal("iidx", "assertion failure - DISABLE_CAMS not set during attach");
}
if (!DISABLE_CAMS.value()) {
init_legacy_camera_hook(FLIP_CAMS); init_legacy_camera_hook(FLIP_CAMS);
} }
@@ -454,20 +451,11 @@ namespace games::iidx {
SetEnvironmentVariable("SCREEN_MODE", SCREEN_MODE.value().c_str()); SetEnvironmentVariable("SCREEN_MODE", SCREEN_MODE.value().c_str());
} }
// check for cab camera access for the second time (first time was in launcher.cpp) // auto with iidx module means turn off camera (non-cab use)
// this time, we are inside -iidx module hook, which means the user is likely NOT on a cab if (CAB_CAMERA_ACCESS == cab_camera_access_mode::automatic) {
// therefore, start with cams OFF by default, and allow user to forcibly override to ON
if (!games::iidx::DISABLE_CAMS.has_value()) {
games::iidx::DISABLE_CAMS = true;
if (options->at(launcher::Options::IIDXCabCamAccess).is_active() &&
options->at(launcher::Options::IIDXCabCamAccess).value_text() == "on") {
games::iidx::DISABLE_CAMS = false;
}
if (games::iidx::DISABLE_CAMS.value()) {
log_misc("iidx", "CONNECT_CAMERA env var set to 0"); log_misc("iidx", "CONNECT_CAMERA env var set to 0");
SetEnvironmentVariable("CONNECT_CAMERA", "0"); SetEnvironmentVariable("CONNECT_CAMERA", "0");
} }
}
// windowed subscreen, enabled by default, unless turned off by user // windowed subscreen, enabled by default, unless turned off by user
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool() && TDJ_MODE) { if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool() && TDJ_MODE) {
@@ -502,8 +490,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",
+8 -1
View File
@@ -11,6 +11,13 @@
namespace games::iidx { namespace games::iidx {
enum class cab_camera_access_mode {
automatic,
off,
on,
legacy,
};
enum class iidx_aio_emulation_state { enum class iidx_aio_emulation_state {
unknown, unknown,
bi2a_com2, bi2a_com2,
@@ -20,7 +27,7 @@ namespace games::iidx {
// settings // settings
extern bool FLIP_CAMS; extern bool FLIP_CAMS;
extern std::optional<bool> DISABLE_CAMS; extern cab_camera_access_mode CAB_CAMERA_ACCESS;
extern bool TDJ_CAMERA; extern bool TDJ_CAMERA;
extern bool TDJ_CAMERA_PREFER_16_9; extern bool TDJ_CAMERA_PREFER_16_9;
extern std::optional<std::string> TDJ_CAMERA_OVERRIDE; extern std::optional<std::string> TDJ_CAMERA_OVERRIDE;
+2 -2
View File
@@ -353,8 +353,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
View File
@@ -15,6 +15,8 @@ namespace hooks::audio {
enum class Backend { enum class Backend {
Asio, Asio,
WaveOut, WaveOut,
Pipewire,
None
}; };
// surround-to-stereo downmix algorithm // surround-to-stereo downmix algorithm
@@ -64,6 +66,10 @@ namespace hooks::audio {
return Backend::Asio; return Backend::Asio;
} else if (_stricmp(value, "waveout") == 0) { } else if (_stricmp(value, "waveout") == 0) {
return Backend::WaveOut; return Backend::WaveOut;
} else if (_stricmp(value, "pipewire") == 0) {
return Backend::Pipewire;
} else if (_stricmp(value, "none") == 0) {
return Backend::None;
} }
return std::nullopt; return std::nullopt;
@@ -10,6 +10,8 @@
#include "hooks/audio/backends/wasapi/util.h" #include "hooks/audio/backends/wasapi/util.h"
#include "hooks/audio/implementations/asio.h" #include "hooks/audio/implementations/asio.h"
#include "hooks/audio/implementations/wave_out.h" #include "hooks/audio/implementations/wave_out.h"
#include "hooks/audio/implementations/pipewire.h"
#include "hooks/audio/implementations/none.h"
//#include "util/co_task_mem_ptr.h" //#include "util/co_task_mem_ptr.h"
#include "util/utils.h" #include "util/utils.h"
@@ -78,6 +80,12 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
case hooks::audio::Backend::WaveOut: case hooks::audio::Backend::WaveOut:
backend = new WaveOutBackend(); backend = new WaveOutBackend();
break; break;
case hooks::audio::Backend::Pipewire:
backend = new PipewireBackend();
break;
case hooks::audio::Backend::None:
backend = new NoneBackend();
break;
default: default:
break; break;
} }
@@ -75,6 +75,9 @@ HRESULT STDMETHODCALLTYPE DummyIAudioClient::Initialize(
log_info("audio::wasapi", "IAudioClient::Initialize hook hit"); log_info("audio::wasapi", "IAudioClient::Initialize hook hit");
print_format(ShareMode, StreamFlags, hnsBufferDuration, hnsPeriodicity, pFormat); print_format(ShareMode, StreamFlags, hnsBufferDuration, hnsPeriodicity, pFormat);
log_info("audio::wasapi", "IAudioClient::Initialize forwarding format");
copy_wave_format(&hooks::audio::FORMAT, pFormat);
HRESULT ret = this->backend->on_initialize( HRESULT ret = this->backend->on_initialize(
&ShareMode, &ShareMode,
&StreamFlags, &StreamFlags,
@@ -132,7 +132,7 @@ static SampleType convert_asio_sample_type(AsioSampleType type) {
} }
} }
AsioBackend::AsioBackend() { AsioBackend::AsioBackend() : format_(hooks::audio::FORMAT) {
this->asio_thread = std::thread([this]() { this->asio_thread = std::thread([this]() {
std::unique_lock<std::mutex> lock_handle(this->asio_thread_state_lock); std::unique_lock<std::mutex> lock_handle(this->asio_thread_state_lock);
@@ -777,7 +777,6 @@ HRESULT AsioBackend::on_initialize(
{ {
AsioError result; AsioError result;
copy_wave_format(&this->format_, pFormat);
memcpy(&this->last_checked_format, &this->format_, sizeof(this->format_)); memcpy(&this->last_checked_format, &this->format_, sizeof(this->format_));
if (!this->asio_thread_initialized) { if (!this->asio_thread_initialized) {
@@ -144,7 +144,7 @@ private:
SampleType asio_sample_type = SampleType::UNSUPPORTED; SampleType asio_sample_type = SampleType::UNSUPPORTED;
std::atomic_bool started = false; std::atomic_bool started = false;
WAVEFORMATEXTENSIBLE format_ {}; const WAVEFORMATEXTENSIBLE &format_;
WAVEFORMATEXTENSIBLE last_checked_format {}; WAVEFORMATEXTENSIBLE last_checked_format {};
//std::vector<BYTE> last_sound_buffer; //std::vector<BYTE> last_sound_buffer;
@@ -0,0 +1,99 @@
#include "none.h"
#include "hooks/audio/audio.h"
#include "hooks/audio/backends/wasapi/audio_client.h"
const WAVEFORMATEXTENSIBLE &NoneBackend::format() const noexcept {
return format_;
}
HRESULT NoneBackend::on_initialize(
AUDCLNT_SHAREMODE *ShareMode,
DWORD *StreamFlags,
REFERENCE_TIME *hnsBufferDuration,
REFERENCE_TIME *hnsPeriodicity,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) noexcept
{
*ShareMode = AUDCLNT_SHAREMODE_SHARED;
*StreamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
AUDCLNT_STREAMFLAGS_RATEADJUST |
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM |
AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
*hnsBufferDuration = 100000;
*hnsPeriodicity = 100000;
log_info("audio::none", "on_initialize");
return S_OK;
}
HRESULT NoneBackend::on_get_buffer_size(uint32_t *buffer_frames) noexcept {
*buffer_frames = 0;
return S_OK;
}
HRESULT NoneBackend::on_get_stream_latency(REFERENCE_TIME *latency) noexcept {
*latency = 100000;
return S_OK;
}
HRESULT NoneBackend::on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept {
padding_frames = 0;
return S_OK;
}
HRESULT NoneBackend::on_is_format_supported(
AUDCLNT_SHAREMODE *ShareMode,
const WAVEFORMATEX *pFormat,
WAVEFORMATEX **ppClosestMatch) noexcept
{
// only accept 44.1 kHz, stereo, 16-bits per channel
if (*ShareMode == AUDCLNT_SHAREMODE_EXCLUSIVE &&
pFormat->nChannels == 2 &&
pFormat->nSamplesPerSec == 44100 &&
pFormat->wBitsPerSample == 16)
{
return S_OK;
}
return AUDCLNT_E_UNSUPPORTED_FORMAT;
}
HRESULT NoneBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept {
return E_NOTIMPL;
}
HRESULT NoneBackend::on_get_device_period(
REFERENCE_TIME *default_device_period,
REFERENCE_TIME *minimum_device_period)
{
*default_device_period = 10000;
*minimum_device_period = 10000;
return S_OK;
}
HRESULT NoneBackend::on_start() noexcept {
return S_OK;
}
HRESULT NoneBackend::on_stop() noexcept {
return S_OK;
}
HRESULT NoneBackend::on_set_event_handle(HANDLE *event_handle) {
*event_handle = CreateEvent(nullptr, true, false, nullptr);
return S_OK;
}
HRESULT NoneBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) {
static BYTE buf[10000];
*ppData = buf;
return S_OK;
}
HRESULT NoneBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) {
return S_OK;
}
NoneBackend::NoneBackend() : format_(hooks::audio::FORMAT)
{
}
@@ -0,0 +1,48 @@
#pragma once
#include "backend.h"
struct NoneBackend final : AudioBackend {
public:
explicit NoneBackend();
~NoneBackend() final = default;
[[nodiscard]] const WAVEFORMATEXTENSIBLE &format() const noexcept override;
HRESULT on_initialize(
AUDCLNT_SHAREMODE *ShareMode,
DWORD *StreamFlags,
REFERENCE_TIME *hnsBufferDuration,
REFERENCE_TIME *hnsPeriodicity,
const WAVEFORMATEX *pFormat,
LPCGUID AudioSessionGuid) noexcept override;
HRESULT on_get_buffer_size(uint32_t *buffer_frames) noexcept override;
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) noexcept override;
HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept override;
HRESULT on_is_format_supported(
AUDCLNT_SHAREMODE *ShareMode,
const WAVEFORMATEX *pFormat,
WAVEFORMATEX **ppClosestMatch) noexcept override;
HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept override;
HRESULT on_get_device_period(
REFERENCE_TIME *default_device_period,
REFERENCE_TIME *minimum_device_period) override;
HRESULT on_start() noexcept override;
HRESULT on_stop() noexcept override;
HRESULT on_set_event_handle(HANDLE *event_handle) override;
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) override;
HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) override;
private:
const WAVEFORMATEXTENSIBLE &format_;
BYTE *active_sound_buffer = nullptr;
};
@@ -0,0 +1,300 @@
#include <winternl.h>
#include "pipewire.h"
#include "hooks/audio/audio.h"
#include "hooks/audio/backends/wasapi/audio_client.h"
#include "util/libutils.h"
#include "launcher/launcher.h"
#include "hooks/audio/util.h"
#include "hooks/audio/backends/wasapi/util.h"
/*
... ShareMode : AUDCLNT_SHAREMODE_EXCLUSIVE <- backend to reimplement, THIS is the audio engine, GAME is client https://learn.microsoft.com/en-us/windows/win32/coreaudio/audclnt-streamflags-xxx-constants
... StreamFlags : AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST <- first is the whole reason for relay_handle_ requirement, second flag not tested
... hnsBufferDuration : 10000 <- wasapi people inventing new time units, 1ms (since 1unit==100ns, 10000*0.0001ms)
... hnsPeriodicity : 10000 <- same as above (1ms)
... nChannels : 2 <- channel_count
... nSamplesPerSec : 44100 <- bitrate
... nAvgBytesPerSec : 176400 <- raw buffer size per second (bitrate * stride)(bytes)
... nBlockAlign : 4 <- stride (bytes)
... wBitsPerSample : 16 <- sample size (bits)
... wValidBitsPerSample : 16 <- same as above
... dwChannelMask : SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT <- position config
_INFO: Code pipeline
INIT: on_is_format_supported()?->on_initialize()->on_set_event_handle()-:(LOOPx1):->on_start()
LOOP: if relay_handle_: on_get_buffer_size()->on_get_buffer()->..->on_release_buffer()
DEINIT: on_stop()
_REV: POSSIBLE ALTERNATIVES:
Check if the stream is driving. The stream needs to have the
* PW_STREAM_FLAG_DRIVER set. When the stream is driving,
* pw_stream_trigger_process() needs to be called when data is
* available (output) or needed (input). Since 0.3.34
bool pw_stream_is_driving(struct pw_stream *stream);
* */
/* Imports/Exports (refer to bmsound-wine.dll.spec) */
typedef void (*BmswConfigInit_t)(const char *);
typedef void (*BmswExperimentalForceProfile_t)(const char *);
typedef int(*BmswClientFormatIsSupported_t)(DWORD, DWORD, DWORD, void *);
typedef int(*BmswClientFormatPeriodFPC_t)(void *);
typedef REFERENCE_TIME(*BmswClientFormatPeriodWRT_t)(void *);
typedef void *(*BmswClientCreate_t)(const char *, void *, void *);
typedef int(*BmswClientStart_t)(void *);
typedef int(*BmswClientStop_t)(void *);
typedef int(*BmswClientDestroy_t)(void *);
typedef unsigned char *(*BmswClientGetBuffer_t)(void *, uint32_t);
typedef int(*BmswClientReleaseBuffer_t)(void *, uint32_t);
typedef int(*BmswClientAwaitBuffer_t)(void *);
typedef void (*BmswClientUpdateCallback_t)(void *, void *, void *);
static BmswConfigInit_t BmswConfigInit;
[[maybe_unused]] static BmswExperimentalForceProfile_t BmswExperimentalForceProfile;
static BmswClientFormatIsSupported_t BmswClientFormatIsSupported;
static BmswClientFormatPeriodFPC_t BmswClientFormatPeriodFPC;
static BmswClientFormatPeriodWRT_t BmswClientFormatPeriodWRT;
static BmswClientCreate_t BmswClientCreate;
static BmswClientStart_t BmswClientStart;
static BmswClientStop_t BmswClientStop;
static BmswClientDestroy_t BmswClientDestroy;
static BmswClientGetBuffer_t BmswClientGetBuffer;
static BmswClientReleaseBuffer_t BmswClientReleaseBuffer;
static BmswClientAwaitBuffer_t BmswClientAwaitBuffer;
[[maybe_unused]] static BmswClientUpdateCallback_t BmswClientUpdateCallback;
static HMODULE bmsw_ = nullptr;
/* Audio init (unless specified otherwise, run once at start) */
// Reports to game whether each requested audio format is available for device (first success return will be used)
HRESULT PipewireBackend::on_is_format_supported(AUDCLNT_SHAREMODE *ShareMode, const WAVEFORMATEX *pFormat, WAVEFORMATEX **ppClosestMatch) noexcept
{
// Format reporting and filtering
log_info("audio::pipewire", "{}", __FUNCTION__);
log_misc("audio::pipewire", "Checking backend support for {} channels, {} Hz, {}-bit",
pFormat->nChannels,
pFormat->nSamplesPerSec,
pFormat->wBitsPerSample);
// IIDX? will always request format that was last checked through this function
if (*ShareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) return AUDCLNT_E_UNSUPPORTED_FORMAT;
// Request format support from real backend (only accepts 44.1 kHz, stereo, 16-bits per channel for now)
return BmswClientFormatIsSupported(pFormat->nSamplesPerSec, pFormat->nChannels, pFormat->wBitsPerSample, nullptr) == 0 ? S_OK : AUDCLNT_E_UNSUPPORTED_FORMAT;
}
// Populate hnsPeriodicity, _INFO: runs before on_initialize, requires configured client stream data
HRESULT PipewireBackend::on_get_device_period(REFERENCE_TIME *default_device_period, REFERENCE_TIME *minimum_device_period)
{
log_info("audio::pipewire", "{}", __FUNCTION__);
*default_device_period = wrt_;
*minimum_device_period = wrt_;
return S_OK;
}
// This expects to be run once on_is_format_supported() succeeds, initializes backend based on passed arguments (and updates them if needed)
HRESULT PipewireBackend::on_initialize(AUDCLNT_SHAREMODE *ShareMode, DWORD *StreamFlags, REFERENCE_TIME *hnsBufferDuration, REFERENCE_TIME *hnsPeriodicity, const WAVEFORMATEX *pFormat, LPCGUID AudioSessionGuid) noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
// Initialize pipewire client (without starting the thread)
client_ = BmswClientCreate(GAME_INSTANCE->title(), nullptr, nullptr);
notif_ = std::thread(PipewireBackend::notif_poll, this);
if (!client_)
log_fatal("audio::pipewire", "Client could not be initialized");
log_info("audio::pipewire", "Client initialized: '{}'", fmt::ptr(client_));
// Adjust WASAPI configuration visible to game (passed arguments are populated and should match that of last on_is_format_supported call)
*hnsBufferDuration = wrt_;
*hnsPeriodicity = wrt_;
//_TODO: Init info
log_info("audio::pipewire", "Device Info:");
log_info("audio::pipewire", "... hnsBufferDuration : {}", *hnsBufferDuration);
log_info("audio::pipewire", "... hnsPeriodicity : {}", *hnsPeriodicity);
return S_OK;
}
// This takes ownership over shared event handle exclusive to AUDCLNT_STREAMFLAGS_EVENTCALLBACK _INFO: first loop iteration runs directly after this, before on_start() call
HRESULT PipewireBackend::on_set_event_handle(HANDLE *event_handle)
{
log_info("audio::pipewire", "{}", __FUNCTION__);
// Take over WASAPI's owned handle pre-initialized by client and replace with always off dummy, cleaning up previously owned handle
if (relay_handle_) CloseHandle(relay_handle_);
relay_handle_ = *event_handle;
*event_handle = CreateEvent(nullptr, true, false, nullptr);
//BmswClientUpdateCallback(client_, (void *) 0, relay_handle_);
return S_OK;
ULONG ntbuf_size = sizeof(OBJECT_NAME_INFORMATION) + 1024;
char ntbuf[ntbuf_size];
OBJECT_NAME_INFORMATION *ntinfo = (OBJECT_NAME_INFORMATION *) ntbuf;
NTSTATUS status = NtQueryObject(*event_handle, ObjectNameInformation, ntinfo, ntbuf_size, &ntbuf_size);
if (NT_SUCCESS(status))
{
// possible to rename anonymous handle to access by name
}
}
HRESULT PipewireBackend::on_start() noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
BmswClientStart(client_);
return S_OK;
}
PipewireBackend::PipewireBackend() : relay_handle_(CreateEvent(nullptr, true, false, nullptr)), format_(hooks::audio::FORMAT), client_(nullptr), notif_state_(-1)
{
log_info("audio::pipewire", "{}", __FUNCTION__);
// Initialize bmsound-wine.dll once
if (!bmsw_ && (bmsw_ = libutils::try_library(MODULE_PATH / "bmsound-wine.dll")))
{
BmswConfigInit = (BmswConfigInit_t) GetProcAddress(bmsw_, "BmswConfigInit");
BmswExperimentalForceProfile = (BmswExperimentalForceProfile_t) GetProcAddress(bmsw_, "BmswExperimentalForceProfile");
BmswClientFormatIsSupported = (BmswClientFormatIsSupported_t) GetProcAddress(bmsw_, "BmswClientFormatIsSupported");
BmswClientFormatPeriodWRT = (BmswClientFormatPeriodWRT_t) GetProcAddress(bmsw_, "BmswClientFormatPeriodWRT");
BmswClientFormatPeriodFPC = (BmswClientFormatPeriodFPC_t) GetProcAddress(bmsw_, "BmswClientFormatPeriodFPC");
BmswClientCreate = (BmswClientCreate_t) GetProcAddress(bmsw_, "BmswClientCreate");
BmswClientStart = (BmswClientStart_t) GetProcAddress(bmsw_, "BmswClientStart");
BmswClientStop = (BmswClientStop_t) GetProcAddress(bmsw_, "BmswClientStop");
BmswClientDestroy = (BmswClientDestroy_t) GetProcAddress(bmsw_, "BmswClientDestroy");
BmswClientGetBuffer = (BmswClientGetBuffer_t) GetProcAddress(bmsw_, "BmswClientGetBuffer");
BmswClientReleaseBuffer = (BmswClientReleaseBuffer_t) GetProcAddress(bmsw_, "BmswClientReleaseBuffer");
BmswClientAwaitBuffer = (BmswClientAwaitBuffer_t) GetProcAddress(bmsw_, "BmswClientAwaitBuffer");
BmswClientUpdateCallback = (BmswClientUpdateCallback_t) GetProcAddress(bmsw_, "BmswClientUpdateCallback");
// Load config
BmswConfigInit("prop/linux.json");
//BmswExperimentalForceProfile("notif_spice");
}
if (bmsw_)
{
// Sync config
wrt_ = BmswClientFormatPeriodWRT(nullptr); //_INFO: wrt affects reported latency (1:100ns)
fpc_ = BmswClientFormatPeriodFPC(nullptr);
return;
}
log_fatal("audio::pipewire", "Library not found: '{}'", (MODULE_PATH / "bmsound-wine.dll").string());
}
/* Audio callback loop (reacts to state of relay_handle_, most likely separate thread */
// _REV: Synchronizing client and backend with nanotime may be necessary (bmsound-pw endpoint dependant)
inline static void callback_notify(void *self)
{
//log_info("audio::pipewire", "{}", __FUNCTION__);
auto *self_ = (PipewireBackend *) self;
//std::this_thread::sleep_for(std::chrono::nanoseconds(10 * 1000 * 1000 - 227));
if (!SetEvent(self_->relay_handle_)) // has to be called for game-side audio loop to continue
{
DWORD last_error = GetLastError();
log_warning("audio::pipewire", "SetEvent failed({}): {}",
last_error,
std::system_category().message(last_error));
}
}
void PipewireBackend::notif_poll(PipewireBackend *self)
{
self->notif_state_ = BmswClientAwaitBuffer(self->client_);
while (self->notif_state_ == 1)
{
BmswClientAwaitBuffer(self->client_);
callback_notify(self);
}
self->notif_state_ = -1;
}
// Amount in frames of data we may handle at once (which should always end up being what gets send by client, unless overrun happens)
HRESULT PipewireBackend::on_get_buffer_size(uint32_t *buffer_frames) noexcept
{
static int iterc = -1;
if (iterc == -1)
{
log_info("audio::pipewire", "{}, frames: {} (INITIAL HIT)", __FUNCTION__, fpc_);
iterc = 0;
}
*buffer_frames = fpc_;
return S_OK;
}
// Wants a raw stream buffer to be assigned into *ppData, this stream will have x amount of sound frames stored into it by a client and assumes exclusive access until next on_release_buffer
HRESULT PipewireBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData)
{
static int iterc = -1;
if (iterc == -1)
{
log_info("audio::pipewire", "{}, frames: {} (INITIAL HIT)", __FUNCTION__, num_frames_requested);
}
iterc++;
if (iterc > 999999)
{
log_info("audio::pipewire", "on_get_buffer, frames: {} (HIT {})", num_frames_requested, iterc);
iterc = 0;
}
*ppData = BmswClientGetBuffer(client_, num_frames_requested);
return S_OK;
}
// This releases access to buffer from last on_get_buffer and implies x amount of frames being valid data to be streamed
HRESULT PipewireBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags)
{
static int iterc = -1;
if (iterc == -1)
{
log_info("audio::pipewire", "{}, frames: {} (INITIAL HIT)", __FUNCTION__, num_frames_written);
iterc = 0;
}
BmswClientReleaseBuffer(client_, num_frames_written);
if (notif_state_ == -1) callback_notify(this);
return S_OK;
}
/* Audio deinit (unless specified otherwise, run once at termination) */
HRESULT PipewireBackend::on_stop() noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
BmswClientStop(client_);
return S_OK;
}
PipewireBackend::~PipewireBackend()
{
log_info("audio::pipewire", "~PipewireBackend");
if (notif_state_ == 1)
{
notif_state_ = 0;
notif_.join();
if (notif_state_ != -1)
{
log_warning("audio::pipewire", "Errors during thread cleanup: '{}'", (const int) notif_state_);
}
}
if (client_) BmswClientDestroy(client_);
if (relay_handle_) CloseHandle(relay_handle_);
}
/* Unsorted (unknown callers, should be considered as unimplemented/untested) *///_BUG: does this need implementation?
const WAVEFORMATEXTENSIBLE &PipewireBackend::format() const noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
return format_;
}
HRESULT PipewireBackend::on_get_stream_latency(REFERENCE_TIME *latency) noexcept
{
// log_info("audio::pipewire", "{}", __FUNCTION__);
*latency = BmswClientFormatPeriodWRT(client_);
return S_OK;
}
HRESULT PipewireBackend::on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept
{
// log_info("audio::pipewire", "{}", __FUNCTION__);
padding_frames = 0;
return S_OK;
}
HRESULT PipewireBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept
{
log_info("audio::pipewire", "{}", __FUNCTION__);
return E_NOTIMPL;
}
@@ -0,0 +1,38 @@
#pragma once
#include "backend.h"
#include <thread>
struct PipewireBackend final : AudioBackend
{
public:
HANDLE relay_handle_;
explicit PipewireBackend();
~PipewireBackend() final;
[[nodiscard]] const WAVEFORMATEXTENSIBLE &format() const noexcept override;
HRESULT on_initialize(AUDCLNT_SHAREMODE *ShareMode, DWORD *StreamFlags, REFERENCE_TIME *hnsBufferDuration, REFERENCE_TIME *hnsPeriodicity, const WAVEFORMATEX *pFormat, LPCGUID AudioSessionGuid) noexcept override;
HRESULT on_get_buffer_size(uint32_t *buffer_frames) noexcept override;
HRESULT on_get_stream_latency(REFERENCE_TIME *latency) noexcept override;
HRESULT on_get_current_padding(std::optional<uint32_t> &padding_frames) noexcept override;
HRESULT on_is_format_supported(AUDCLNT_SHAREMODE *ShareMode, const WAVEFORMATEX *pFormat, WAVEFORMATEX **ppClosestMatch) noexcept override;
HRESULT on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept override;
HRESULT on_get_device_period(REFERENCE_TIME *default_device_period, REFERENCE_TIME *minimum_device_period) override;
HRESULT on_start() noexcept override;
HRESULT on_stop() noexcept override;
HRESULT on_set_event_handle(HANDLE *event_handle) override;
HRESULT on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) override;
HRESULT on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) override;
static void notif_poll(PipewireBackend *self);
private:
int fpc_;
REFERENCE_TIME wrt_;
const WAVEFORMATEXTENSIBLE &format_;
void *client_;
std::thread notif_;
volatile int notif_state_;
};
@@ -3,27 +3,35 @@
#include "hooks/audio/audio.h" #include "hooks/audio/audio.h"
#include "hooks/audio/backends/wasapi/audio_client.h" #include "hooks/audio/backends/wasapi/audio_client.h"
#include "hooks/audio/backends/wasapi/defs.h" #include "hooks/audio/backends/wasapi/defs.h"
#include "hooks/audio/util.h"
#include "hooks/audio/buffer.h"
#include "util/precise_timer.h" #include "util/precise_timer.h"
static REFERENCE_TIME WASAPI_TARGET_REFTIME = TARGET_REFTIME; static REFERENCE_TIME WASAPI_TARGET_REFTIME = TARGET_REFTIME;
HRESULT WaveOutBackend::init(uint32_t buffer_size) { HRESULT WaveOutBackend::init(uint32_t buffer_size) {
auto &format = hooks::audio::FORMAT.Format; MMRESULT ret;
format.wFormatTag = WAVE_FORMAT_PCM;
log_info("audio::wave_out", "initializing waveOut backend with {} channels, {} Hz, {}-bit", if (format_.Format.nSamplesPerSec == 0)
format.nChannels, {
format.nSamplesPerSec, log_warning("audio::wave_out", "format_ condition race");
format.wBitsPerSample); return static_cast<HRESULT>(MMSYSERR_ERROR);
log_info("audio::wave_out", "... nBlockAlign : {} bytes", format.nBlockAlign); }
log_info("audio::wave_out", "... nAvgBytesPerSec : {} bytes", format.nAvgBytesPerSec); hooks::audio::FORMAT.Format.wFormatTag = WAVE_FORMAT_PCM;
log_info("audio::wave_out", "initializing waveOut backend with {} channels, {} Hz, {}-bit, {} format",
format_.Format.nChannels,
format_.Format.nSamplesPerSec,
format_.Format.wBitsPerSample,
format_.Format.wFormatTag);
log_info("audio::wave_out", "... nBlockAlign : {} bytes", format_.Format.nBlockAlign);
log_info("audio::wave_out", "... nAvgBytesPerSec : {} bytes", format_.Format.nAvgBytesPerSec);
log_info("audio::wave_out", "... buffer reftime : {} ms", WASAPI_TARGET_REFTIME / 10000.f); log_info("audio::wave_out", "... buffer reftime : {} ms", WASAPI_TARGET_REFTIME / 10000.f);
log_info("audio::wave_out", "... buffer count : {} buffers", _countof(this->hdrs)); log_info("audio::wave_out", "... buffer count : {} buffers", _countof(this->hdrs));
MMRESULT ret = waveOutOpen( ret = waveOutOpen(
&this->handle, &this->handle,
WAVE_MAPPER, WAVE_MAPPER,
reinterpret_cast<const WAVEFORMATEX *>(&hooks::audio::FORMAT.Format), reinterpret_cast<const WAVEFORMATEX *>(&format_.Format),
reinterpret_cast<DWORD_PTR>(this->dispatcher_event), reinterpret_cast<DWORD_PTR>(this->dispatcher_event),
reinterpret_cast<DWORD_PTR>(nullptr), reinterpret_cast<DWORD_PTR>(nullptr),
CALLBACK_EVENT); CALLBACK_EVENT);
@@ -36,6 +44,7 @@ HRESULT WaveOutBackend::init(uint32_t buffer_size) {
} }
// initialize buffers // initialize buffers
log_info("audio::wave_out", "... device handle : {}", fmt::ptr(this->handle));
for (auto &hdr : this->hdrs) { for (auto &hdr : this->hdrs) {
memset(&hdr, 0, sizeof(hdr)); memset(&hdr, 0, sizeof(hdr));
hdr.lpData = new char[buffer_size] {}; hdr.lpData = new char[buffer_size] {};
@@ -71,7 +80,7 @@ HRESULT WaveOutBackend::init(uint32_t buffer_size) {
} }
const WAVEFORMATEXTENSIBLE &WaveOutBackend::format() const noexcept { const WAVEFORMATEXTENSIBLE &WaveOutBackend::format() const noexcept {
return hooks::audio::FORMAT; return format_;
} }
HRESULT WaveOutBackend::on_initialize( HRESULT WaveOutBackend::on_initialize(
@@ -90,6 +99,8 @@ HRESULT WaveOutBackend::on_initialize(
*hnsBufferDuration = WASAPI_TARGET_REFTIME; *hnsBufferDuration = WASAPI_TARGET_REFTIME;
*hnsPeriodicity = WASAPI_TARGET_REFTIME; *hnsPeriodicity = WASAPI_TARGET_REFTIME;
log_info("audio::wave_out", "on_initialize");
// this backend only supports stereo audio // this backend only supports stereo audio
if (pFormat->nChannels > 2) { if (pFormat->nChannels > 2) {
return AUDCLNT_E_UNSUPPORTED_FORMAT; return AUDCLNT_E_UNSUPPORTED_FORMAT;
@@ -116,7 +127,7 @@ HRESULT WaveOutBackend::on_get_current_padding(std::optional<uint32_t> &padding_
} }
} }
auto frames = static_cast<uint32_t>(queued_bytes / hooks::audio::FORMAT.Format.nBlockAlign); auto frames = static_cast<uint32_t>(queued_bytes / format_.Format.nBlockAlign);
//log_info("audio::wave_out", "queued_bytes = {}, frames = {}", queued_bytes, frames); //log_info("audio::wave_out", "queued_bytes = {}, frames = {}", queued_bytes, frames);
padding_frames = frames; padding_frames = frames;
@@ -137,6 +148,7 @@ HRESULT WaveOutBackend::on_is_format_supported(
return S_OK; return S_OK;
} }
//return waveOutOpen(nullptr, WAVE_MAPPER, reinterpret_cast<const WAVEFORMATEX *>(&format_.Format), NULL, NULL, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR ? S_OK : AUDCLNT_E_UNSUPPORTED_FORMAT;
return AUDCLNT_E_UNSUPPORTED_FORMAT; return AUDCLNT_E_UNSUPPORTED_FORMAT;
} }
HRESULT WaveOutBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept { HRESULT WaveOutBackend::on_get_mix_format(WAVEFORMATEX **pp_device_format) noexcept {
@@ -167,17 +179,20 @@ HRESULT WaveOutBackend::on_set_event_handle(HANDLE *event_handle) {
} }
HRESULT WaveOutBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) { HRESULT WaveOutBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppData) {
auto buffer_size = hooks::audio::FORMAT.Format.nBlockAlign * num_frames_requested; size_t buffer_size = format_.Format.nBlockAlign * num_frames_requested;
if (!this->initialized) { if (!this->initialized) {
this->init(buffer_size); this->init(buffer_size);
} }
const size_t converted_size = required_buffer_size(num_frames_requested, format_.Format.nChannels, SampleType::SINT_16);
const size_t max_size = std::max(buffer_size, converted_size);
// wait for a free slot // wait for a free slot
WaitForSingleObject(this->dispatcher_event, INFINITE); WaitForSingleObject(this->dispatcher_event, INFINITE);
// allocate temporary sound buffer // allocate temporary sound buffer
this->active_sound_buffer = reinterpret_cast<BYTE *>(CoTaskMemAlloc(buffer_size)); this->active_sound_buffer = reinterpret_cast<BYTE *>(CoTaskMemAlloc(max_size));
// hand the buffer to the callee // hand the buffer to the callee
*ppData = this->active_sound_buffer; *ppData = this->active_sound_buffer;
@@ -224,3 +239,7 @@ HRESULT WaveOutBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwF
return S_OK; return S_OK;
} }
WaveOutBackend::WaveOutBackend() : format_(hooks::audio::FORMAT)
{
}
@@ -10,6 +10,7 @@
struct WaveOutBackend final : AudioBackend { struct WaveOutBackend final : AudioBackend {
public: public:
explicit WaveOutBackend();
~WaveOutBackend() final = default; ~WaveOutBackend() final = default;
HRESULT init(uint32_t buffer_size); HRESULT init(uint32_t buffer_size);
@@ -49,6 +50,7 @@ public:
private: private:
WrappedIAudioClient *client; WrappedIAudioClient *client;
const WAVEFORMATEXTENSIBLE &format_;
bool initialized = false; bool initialized = false;
HANDLE relay_event = nullptr; HANDLE relay_event = nullptr;
HANDLE dispatcher_event = nullptr; HANDLE dispatcher_event = nullptr;
+1 -6
View File
@@ -179,11 +179,6 @@ static int avs_fs_lstat(const char *name, struct avs::core::avs_stat *st) {
return 1; return 1;
} }
if (!hooks::avs::config::DISABLE_CACHE && (st != nullptr) &&
avs::game::is_model("LDJ") && is_ldj_cacheable_file(name)) {
return cached_avs_fs_lstat(name, st);
}
const auto value = avs::core::avs_fs_lstat(name, st); const auto value = avs::core::avs_fs_lstat(name, st);
if (!is_spam_file(name)) { if (!is_spam_file(name)) {
WRAP_DEBUG_FMT("name: {}", name); WRAP_DEBUG_FMT("name: {}", name);
@@ -251,7 +246,7 @@ static avs::core::avs_file_t avs_fs_opendir(const char *path) {
return value; return value;
} }
static int avs_fs_mount(const char *mountpoint, const char *fsroot, const char *fstype, void *data) { static avs::core::avs_file_t avs_fs_mount(const char *mountpoint, const char *fsroot, const char *fstype, void *data) {
// avs redirection breaks ongaku paradise // avs redirection breaks ongaku paradise
if (mountpoint == nullptr || if (mountpoint == nullptr ||
fsroot == nullptr || fsroot == nullptr ||
+224
View File
@@ -0,0 +1,224 @@
#include "develhook.h"
#include "util/logging.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/utils.h"
#include "util/sigscan.h"
#include "avs/core.h"
#include "avs/game.h"
#include "hooks/wmischook.h"
#include "external/stackwalker/stackwalker.h"
#include <vector>
#include <string>
#include <sstream>
namespace hooks::devel
{
static class Thumbnail : public Routine
{
public:
using Routine::Routine;
void entry_main() override
{
log_warning("hooks::devel::thumbnail", "Forcing thumbnail patch..");
hooks::wmisc::apply_performance_hacks(avs::game::DLL_INSTANCE, "FORCE", "thumbnail");
}
} thumbnail_("wmisc_thum");
static class Msacm : public Routine
{
public:
using Routine::Routine;
void entry_main() override
{
log_warning("hooks::devel::msacm", "Forcing msacm patch..");
hooks::wmisc::apply_performance_hacks(avs::game::DLL_INSTANCE, "FORCE", "msacm");
}
} msacm_("wmisc_acm");
static class DmoEffector : public Routine
{
public:
using Routine::Routine;
void entry_main() override
{
log_warning("hooks::devel::msacm", "Forcing msacm patch..");
hooks::wmisc::apply_performance_hacks(avs::game::DLL_INSTANCE, "FORCE", "dmoeffector");
}
} dmoeffector_("wmisc_dmoeffector");
static class Wmisc : public Routine
{
public:
using Routine::Routine;
void attach() override
{
log_warning("hooks::devel::wmisc", "Disabling auto-patching..");
hooks::wmisc::skip_patching_by_model(true);
}
void entry_main() override
{
// Reenable logging
hooks::wmisc::apply_performance_hacks(avs::game::DLL_INSTANCE, "FORCE", "bmsound");
}
} wmisc_("disable_auto_wmisc");
static class Avs_fs : public Routine
{
private:
static constexpr char needle_[] = ""; // set to "" to print all
inline static avs::core::AVS_FS_LSTAT_T avs_fs_lstat_;
inline static avs::core::AVS_FS_MOUNT_T avs_fs_mount_;
inline static avs::core::AVS_FS_OPEN_T avs_fs_open_;
static int on_avs_fs_lstat(const char *path, struct avs::core::avs_stat *stat)
{
if (strstr(path, needle_))
{
log_info("hooks::devel", "{}({})", __FUNCTION__, path);
}
return avs_fs_lstat_(path, stat);
}
static avs::core::avs_file_t on_avs_fs_mount(const char *mountpoint, const char *fsroot, const char *fstype, void *data)
{
if (strstr(fsroot, needle_))
{
log_info("hooks::devel", "{}({}=>{}) @{} :nullptr data?{} ", __FUNCTION__, fsroot, mountpoint, fstype, data == nullptr);
}
return avs_fs_mount_(mountpoint, fsroot, fstype, data);
}
static avs::core::avs_file_t on_avs_fs_open(const char *name, uint16_t mode, int flags)
{
if (strstr(name, needle_))
{
log_info("hooks::devel", "{}({}::{}) @{}", __FUNCTION__, name, mode, flags);
}
return avs_fs_open_(name, mode, flags);
}
public:
using Routine::Routine;
void entry_main() override
{
detour::trampoline(reinterpret_cast<void *>(avs::core::avs_fs_lstat), reinterpret_cast<void *>(on_avs_fs_lstat), reinterpret_cast<void **>(&avs_fs_lstat_));
detour::trampoline(reinterpret_cast<void *>(avs::core::avs_fs_mount), reinterpret_cast<void *>(on_avs_fs_mount), reinterpret_cast<void **>(&avs_fs_mount_));
detour::trampoline(reinterpret_cast<void *>(avs::core::avs_fs_open), reinterpret_cast<void *>(on_avs_fs_open), reinterpret_cast<void **>(&avs_fs_open_));
}
} avs_fs_("avs_fs_detour");
static class CMoviePlayer : public Routine
{
private:
typedef void (*GetWmvChunk_t)(uint8_t *param_1);
inline static GetWmvChunk_t GetWmvChunk_;
static void on_get_wmv_chunk_(uint8_t *param_1)
{
static FILE *fp = nullptr;
if (!fp)
{
fp = fopen("data/movie/testbg2_fhd.wmv", "rb");
if (!fp)
log_warning("hooks::devel::CMoviePlayer", "Missing wmv file");
}
// Read from file (skip AVS)
if (fp)
{
int chunksz = *(uint32_t *) (param_1 + 0x10);
log_misc("hooks::devel::CMoviePlayer", "Reading {} bytes", chunksz);
int readsz = fread((void *) *(intptr_t *) (param_1 + 0x08), chunksz, 1, fp);
*(int *) (param_1 + 0x14) = readsz;
if(readsz < chunksz)
{
fclose(fp);
fp = nullptr;
}
}
}
public:
using Routine::Routine;
void entry_main() override
{
log_warning("hooks::devel", "Test code for SDVX5::CMoviePlayer only");
intptr_t TX_ThumExists = find_pattern_from(avs::game::DLL_INSTANCE, "40534883ec20448b4110488bd9488b51088b09ff150000000089431485c0791d448bc0488d1500000000488d0d000000004883c4205b48ff25000000004883c4205bc3", "XXXXXXXXXXXXXXXXXXXXX????XXXXXXXXXXXXX????XXX????XXXXXXXX????XXXXXX", 0, 0, 0);
if (!detour::trampoline_try(reinterpret_cast<void *>(TX_ThumExists), reinterpret_cast<void *>(on_get_wmv_chunk_), reinterpret_cast<void **>(&GetWmvChunk_)))
{
log_fatal("hooks::devel", "Hook failed");
}
}
} cmovieplayer_("wmisc_cmovieplayer");
std::vector<std::string> from_env(const char *str)
{
std::vector<std::string> r;
std::stringstream ss(getenv(str) ? getenv(str) : "");
std::string flag;
while (std::getline(ss, flag, ';'))
{
r.push_back(flag);
}
return r;
}
static std::vector<Routine *> &routines()
{
static std::vector<Routine *> routines_;
if (routines_.empty())
{
//Default routines
routines_.push_back(&avs_fs_);
routines_.push_back(&thumbnail_);
routines_.push_back(&msacm_);
routines_.push_back(&wmisc_);
routines_.push_back(&cmovieplayer_);
routines_.push_back(&dmoeffector_);
}
return routines_;
}
Routine *active_routine(const char *name)
{
unsigned int hash = to_hash(name);
for (auto &routine: routines())
{
if (routine->hash() == hash)
return routine;
}
return nullptr;
}
void bind_routine(Routine *routine)
{
routines().push_back(routine);
}
void log_environment()
{
log_warning("hooks::devel", "WINEDLLOVERRIDES: '{}'", getenv("WINEDLLOVERRIDES") ? getenv("WINEDLLOVERRIDES") : "null");
}
void entry_main()
{
log_environment();
const auto rnames = from_env("SPICE_DEVEL");
if (!rnames.empty()) log_info("hooks::devel", "Run subroutines..");
for (const auto &routinename: from_env("SPICE_DEVEL"))
{
Routine *routine = active_routine(routinename.c_str());
if (routine) routine->entry_main();
}
}
void attach()
{
const auto rnames = from_env("SPICE_DEVEL");
if (!rnames.empty()) log_info("hooks::devel", "Attach subroutines..");
for (const auto &rname: rnames)
{
Routine *routine = active_routine(rname.c_str());
if (routine) routine->attach();
}
}
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <cstdint>
#include "util/utils.h"
namespace hooks::devel
{
//_INFO: use attach()/entry_main() to run task before game::attach()/game::entry_main()
class Routine
{
private:
unsigned int hash_;
public:
[[nodiscard]] inline unsigned int constexpr hash() const { return hash_; }
virtual void attach() {}
virtual void entry_main() {}
explicit Routine(const char *name) : hash_(to_hash(name)) {}
virtual ~Routine() = default;
};
Routine *active_routine(const char *name);
void bind_routine(Routine *routine);
void attach();
void entry_main();
}
@@ -1,5 +1,6 @@
#include "d3d9_backend.h" #include "d3d9_backend.h"
#include <atomic>
#include <cassert> #include <cassert>
#include <memory> #include <memory>
#include <thread> #include <thread>
@@ -113,6 +114,12 @@ static Direct3DCreate9On12Ex_t Direct3DCreate9On12Ex_orig = nullptr;
static bool ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = false; static bool ATTEMPTED_SUB_SWAP_CHAIN_ACQUIRE = false;
static IDirect3DSwapChain9 *SUB_SWAP_CHAIN = nullptr; static IDirect3DSwapChain9 *SUB_SWAP_CHAIN = nullptr;
// main and subscreen presents may occur on different threads.
static std::atomic_bool SUBSCREEN_PRESENTED_SINCE_LAST_MAIN = false;
// do not mistake the fallback Present below for a game-originated Present.
static thread_local bool SUBSCREEN_FORCE_REDRAW_IN_PROGRESS = false;
static void graphics_d3d9_ldj_init_sub_screen( static void graphics_d3d9_ldj_init_sub_screen(
IDirect3DDevice9Ex *device, IDirect3DDevice9Ex *device,
D3DPRESENT_PARAMETERS *present_params, D3DPRESENT_PARAMETERS *present_params,
@@ -1445,6 +1452,12 @@ IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen() {
return surface; return surface;
} }
void graphics_d3d9_notify_subscreen_present() {
if (SUBSCREEN_FORCE_REDRAW && !SUBSCREEN_FORCE_REDRAW_IN_PROGRESS) {
SUBSCREEN_PRESENTED_SINCE_LAST_MAIN.store(true, std::memory_order_relaxed);
}
}
static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) { static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
// iidx/sdvx // iidx/sdvx
int swapchain = 1; int swapchain = 1;
@@ -1472,8 +1485,16 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
// //
// early versions of popn HC needs this as well, but not on by default as it can cause // early versions of popn HC needs this as well, but not on by default as it can cause
// graphical glitches on some GPUs // graphical glitches on some GPUs
if (GRAPHICS_WINDOWED || SUBSCREEN_FORCE_REDRAW) { //
// treat forced redraw as a fallback so it does not duplicate a successful game present.
const bool force_redraw = SUBSCREEN_FORCE_REDRAW &&
!SUBSCREEN_PRESENTED_SINCE_LAST_MAIN.exchange(false, std::memory_order_relaxed);
if (GRAPHICS_WINDOWED || force_redraw) {
SUBSCREEN_FORCE_REDRAW_IN_PROGRESS = true;
SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0); SUB_SWAP_CHAIN->Present(nullptr, nullptr, nullptr, nullptr, 0);
SUBSCREEN_FORCE_REDRAW_IN_PROGRESS = false;
} }
} }
} }
@@ -15,6 +15,8 @@ void graphics_d3d9_on_present(
IDirect3DDevice9 *device, IDirect3DDevice9 *device,
IDirect3DDevice9 *wrapped_device); IDirect3DDevice9 *wrapped_device);
void graphics_d3d9_notify_subscreen_present();
IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen(); IDirect3DSurface9 *graphics_d3d9_ldj_get_sub_screen();
struct WrappedIDirect3D9 : IDirect3D9Ex { struct WrappedIDirect3D9 : IDirect3D9Ex {
@@ -134,6 +134,10 @@ ULONG STDMETHODCALLTYPE WrappedIDirect3DDevice9::Release() {
this->main_swapchain->Release(); this->main_swapchain->Release();
this->main_swapchain = nullptr; this->main_swapchain = nullptr;
} }
if (this->implicit_sub_swapchain) {
this->implicit_sub_swapchain->Release();
this->implicit_sub_swapchain = nullptr;
}
for (auto &sc : this->sub_swapchain) { for (auto &sc : this->sub_swapchain) {
if (sc) { if (sc) {
sc->Release(); sc->Release();
@@ -487,6 +491,24 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::GetSwapChain(
fake_sub_swapchain[0]->AddRef(); fake_sub_swapchain[0]->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[0]); *ppSwapChain = static_cast<IDirect3DSwapChain9 *>(fake_sub_swapchain[0]);
graphics_screens_register(iSwapChain);
return D3D_OK;
} else if (SUBSCREEN_FORCE_REDRAW) {
// store implicit sub swap chain
if (!implicit_sub_swapchain) {
IDirect3DSwapChain9 *real_swapchain = nullptr;
HRESULT ret = pReal->GetSwapChain(iSwapChain, &real_swapchain);
if (FAILED(ret)) {
return ret;
}
implicit_sub_swapchain = new WrappedIDirect3DSwapChain9(this, real_swapchain);
implicit_sub_swapchain->should_run_hooks = false;
}
implicit_sub_swapchain->AddRef();
*ppSwapChain = static_cast<IDirect3DSwapChain9 *>(implicit_sub_swapchain);
graphics_screens_register(iSwapChain); graphics_screens_register(iSwapChain);
return D3D_OK; return D3D_OK;
} }
@@ -262,6 +262,7 @@ struct WrappedIDirect3DDevice9 : IDirect3DDevice9Ex {
std::atomic_ulong refs = 1; std::atomic_ulong refs = 1;
WrappedIDirect3DSwapChain9 *main_swapchain = nullptr; WrappedIDirect3DSwapChain9 *main_swapchain = nullptr;
WrappedIDirect3DSwapChain9 *implicit_sub_swapchain = nullptr;
WrappedIDirect3DSwapChain9 *sub_swapchain[3] = { nullptr, nullptr, nullptr }; WrappedIDirect3DSwapChain9 *sub_swapchain[3] = { nullptr, nullptr, nullptr };
FakeIDirect3DSwapChain9 *fake_sub_swapchain[3] = { nullptr, nullptr, nullptr }; FakeIDirect3DSwapChain9 *fake_sub_swapchain[3] = { nullptr, nullptr, nullptr };
@@ -96,6 +96,12 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DSwapChain9::Present(const RECT *pSourc
pDirtyRegion, pDirtyRegion,
dwFlags); dwFlags);
// a successful game subscreen present suppresses the next forced fallback present.
if (SUCCEEDED(result) &&
(this == pDev->implicit_sub_swapchain || this == pDev->sub_swapchain[0])) {
graphics_d3d9_notify_subscreen_present();
}
// Some drivers report S_PRESENT_MODE_CHANGED after Windows moves the portrait SMALL // Some drivers report S_PRESENT_MODE_CHANGED after Windows moves the portrait SMALL
// head into the requested exclusive mode, leaving both group heads black. Toggle SMALL // head into the requested exclusive mode, leaving both group heads black. Toggle SMALL
// through another supported mode and restore it once, then retry the interrupted MAIN // through another supported mode and restore it once, then retry the interrupted MAIN
+45
View File
@@ -13,7 +13,9 @@
#include "avs/game.h" #include "avs/game.h"
#include "games/iidx/iidx.h" #include "games/iidx/iidx.h"
#include "games/gitadora/gitadora.h" #include "games/gitadora/gitadora.h"
#include "games/popn/popn.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "util/deferlog.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"
@@ -29,6 +31,7 @@ static decltype(GetLocaleInfoEx) *GetLocaleInfoEx_orig = nullptr;
#ifdef SPICE64 #ifdef SPICE64
static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr; static decltype(GetSystemDefaultLCID) *GetSystemDefaultLCID_orig = nullptr;
static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr; static decltype(IsDBCSLeadByte) *IsDBCSLeadByte_orig = nullptr;
static decltype(IsDBCSLeadByteEx) *IsDBCSLeadByteEx_orig = nullptr;
static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr; static decltype(WideCharToMultiByte) *WideCharToMultiByte_orig = nullptr;
static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr; static decltype(GetLocaleInfoA) *GetLocaleInfoA_orig = nullptr;
static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr; static decltype(GetThreadLocale) *GetThreadLocale_orig = nullptr;
@@ -182,9 +185,30 @@ static BOOL WINAPI IsDBCSLeadByte_hook (
BYTE TestChar BYTE TestChar
) )
{ {
if (IsDBCSLeadByteEx_orig) {
return IsDBCSLeadByteEx_orig(CODEPAGE_SHIFT_JIS, TestChar);
}
return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar); return IsDBCSLeadByteEx(CODEPAGE_SHIFT_JIS, TestChar);
} }
static BOOL WINAPI IsDBCSLeadByteEx_hook(
UINT CodePage,
BYTE TestChar)
{
switch (CodePage) {
case CP_ACP:
case CP_THREAD_ACP:
CodePage = CODEPAGE_SHIFT_JIS;
break;
default:
break;
}
return IsDBCSLeadByteEx_orig(CodePage, TestChar);
}
static static
int int
WINAPI WINAPI
@@ -250,6 +274,18 @@ GetLocaleInfoA_hook(
void hooks::lang::early_init() { void hooks::lang::early_init() {
log_info("hooks::lang", "early initialization"); log_info("hooks::lang", "early initialization");
const auto native_code_page = GetACP();
if (native_code_page == CP_UTF8) {
log_warning(
"hooks::lang",
"Windows is using UTF-8 as the system code page; "
"some games may render text incorrectly or behave unexpectedly");
deferredlogs::defer_error_messages({
"Windows is using UTF-8 as the system code page",
" some games may render text incorrectly or behave unexpectedly"});
}
// hooking these two functions fixes the jubeat mojibake // hooking these two functions fixes the jubeat mojibake
detour::trampoline_try("kernel32.dll", "GetACP", GetACP_hook, &GetACP_orig); detour::trampoline_try("kernel32.dll", "GetACP", GetACP_hook, &GetACP_orig);
detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig); detour::trampoline_try("kernel32.dll", "GetOEMCP", GetOEMCP_hook, &GetOEMCP_orig);
@@ -315,6 +351,15 @@ void hooks::lang::early_init() {
WideCharToMultiByte_hook, WideCharToMultiByte_hook,
&WideCharToMultiByte_orig); &WideCharToMultiByte_orig);
} }
if (games::popn::is_pikapika_model() && native_code_page == CP_UTF8) {
detour::trampoline_try(
"kernel32.dll",
"IsDBCSLeadByteEx",
IsDBCSLeadByteEx_hook,
&IsDBCSLeadByteEx_orig);
}
#endif #endif
} }
+976
View File
@@ -0,0 +1,976 @@
#include "sndbhook.h"
#include "hooks/develhook.h"
#include "util/logging.h"
#include "util/memutils.h"
#include "util/sigscan.h"
#include "util/detour.h"
#include "util/libutils.h"
#include "util/utils.h"
#include "avs/core.h"
#include <codecvt>
#include <cstring>
// This implementation outsources libavcodec from host ffmpeg (so that linking ffmpeg stack with spicetools isn't required)
namespace hooks::soundbank::avcodec
{
enum pcm_resampler : int8_t
{
bmswac_resampler_none = -1,
bmswac_resampler_u8,
bmswac_resampler_s16,
bmswac_resampler_s16_ad,
bmswac_resampler_f32
};
typedef enum pcm_resampler pcm_resampler_t;
typedef void (*BmswConfigInit_t)(const char *path);
typedef int (*BmswSymlink_t)(const char *srcwpath, const char *dstwpath, int canonic);
typedef uint64_t (*BmswTranscoderEnumValidSoundbanksAddr_t)();
typedef pcm_resampler (*BmswTranscoderResampler_t)();
typedef int (*BmswTranscoderAsfToWav_t)(const uint8_t *srcasf, uint32_t srcsz, uint8_t **dstwav, uint32_t *dstsz, pcm_resampler_t resampler);
static BmswConfigInit_t BmswConfigInit;
static BmswSymlink_t BmswSymlink;
static BmswTranscoderEnumValidSoundbanksAddr_t BmswTranscoderEnumValidSoundbanksAddr;
static BmswTranscoderResampler_t BmswTranscoderResampler;
static BmswTranscoderAsfToWav_t BmswTranscoderAsfToWav;
static HMODULE bmsw_ = nullptr;
bool init()
{
log_info("hooks::soundbank::avcodec", "{}", __FUNCTION__);
// Initialize bmsound-wine.dll once
if (bmsw_)
return true;
if ((bmsw_ = libutils::try_library(MODULE_PATH / "bmsound-wine.dll")))
{
BmswConfigInit = (BmswConfigInit_t) GetProcAddress(bmsw_, "BmswConfigInit");
BmswSymlink = (BmswSymlink_t) GetProcAddress(bmsw_, "BmswSymlink");
BmswTranscoderEnumValidSoundbanksAddr = (BmswTranscoderEnumValidSoundbanksAddr_t) GetProcAddress(bmsw_, "BmswTranscoderEnumValidSoundbanksAddr");
BmswTranscoderResampler = (BmswTranscoderResampler_t) GetProcAddress(bmsw_, "BmswTranscoderResampler");
BmswTranscoderAsfToWav = (BmswTranscoderAsfToWav_t) GetProcAddress(bmsw_, "BmswTranscoderAsfToWav");
//_REM: 0.2.x support (old behaviour), remove on 0.3.0
if (!BmswTranscoderEnumValidSoundbanksAddr || !BmswTranscoderResampler)
{
BmswTranscoderEnumValidSoundbanksAddr = (BmswTranscoderEnumValidSoundbanksAddr_t) GetProcAddress(bmsw_, "BmswEnumValidSoundbanksAddr");
BmswTranscoderResampler = []() -> pcm_resampler
{
return bmswac_resampler_s16_ad;
};
}
// Load config
BmswConfigInit("prop/linux.json");
return true;
}
log_warning("hooks::soundbank", "Library not found: '{}'", (MODULE_PATH / "bmsound-wine.dll").string());
return false;
}
void deinit()
{
log_info("hooks::soundbank::avcodec", "{}", __FUNCTION__);
}
}
// Replicates wine MF wma=>wav playback bug 1:1, follows https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/multimedia/mediafoundation/AudioClip/main.cpp
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mfobjects.h>
#include <shlwapi.h>
namespace hooks::soundbank::mf_broken
{
typedef HRESULT (*MFCreateMFByteStreamOnStream_t)(IStream *, IMFByteStream **);
typedef HRESULT (*MFCreateSourceReaderFromByteStream_t)(IMFByteStream *, IMFAttributes *, IMFSourceReader **);
typedef HRESULT (*MFStartup_t)(ULONG, DWORD);
typedef HRESULT (*MFShutdown_t)();
typedef IStream *(*SHCreateMemStream_t)(const BYTE *, UINT);
MFCreateMFByteStreamOnStream_t MFCreateMFByteStreamOnStream;
MFCreateSourceReaderFromByteStream_t MFCreateSourceReaderFromByteStream;
MFStartup_t MFStartup;
MFShutdown_t MFShutdown;
SHCreateMemStream_t SHCreateMemStream;
struct wav_hdr
{
char sig_riff[4] = {'R', 'I', 'F', 'F'};
uint32_t filesz = -1;
char sig_wave[4] = {'W', 'A', 'V', 'E'};
char sig_fmt[4] = {'f', 'm', 't', ' '};
uint32_t chunksize = 16;
uint16_t format = 1;
uint16_t channels = 2;
uint32_t samplerate = 44100;
uint32_t byterate = 44100 * 2 * 2;
uint16_t stride = 2 * 2;
uint16_t bitdepth = 16;
char sig_data[4] = {'d', 'a', 't', 'a'};
uint32_t datasz = -1;
};
HRESULT asf_unpack_pcm_s16le(BYTE *dst, IMFSourceReader *srcreader, uint32_t szmax, uint32_t *sz)
{
HRESULT hr = S_OK;
size_t srcpos = 0;
size_t srcchunk = 0;
BYTE *audiobuf = nullptr;
IMFSample *samples = nullptr;
IMFMediaBuffer *mediabuf = nullptr;
while (true)
{
DWORD flags = 0;
// Get next samples chunk, in memcpy capable format
hr = srcreader->ReadSample((DWORD) MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &flags, nullptr, &samples);
if (FAILED(hr))
{
log_warning("hooks::soundbank", "ReadSample() failure, skipping..");
break;
}
if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
{
log_warning("hooks::soundbank", "Unsupported WAVE format, skipping..");
samples->Release();
break;
}
if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
{
break;
}
if (samples == nullptr)
{
log_warning("hooks::soundbank", "Missing samples");
continue;
}
hr = samples->ConvertToContiguousBuffer(&mediabuf);
if (FAILED(hr))
{
log_warning("hooks::soundbank", "ConvertToContiguousBuffer() failure, skipping..");
samples->Release();
break;
}
// Store to destination (szmax as overflow check)
mediabuf->Lock(&audiobuf, nullptr, reinterpret_cast<DWORD *>(&srcchunk));
if (szmax - srcpos < srcchunk)
{
srcchunk = (signed int) szmax - srcpos;
}
memcpy(dst + srcpos, audiobuf, srcchunk);
srcpos += srcchunk;
hr = mediabuf->Unlock();
// Necessary per samples chunk
audiobuf = nullptr;
samples->Release();
mediabuf->Release();
if (srcpos >= szmax)
{
break;
}
}
*sz = srcpos;
return hr;
}
HRESULT asf_to_wav(const BYTE *srcasf, uint32_t srcsz, BYTE **dstwav, uint32_t *dstsz)
{
HRESULT hr = S_OK;
// Reuse raw asf container into a byte stream https://stackoverflow.com/questions/46493191/create-imfbytestream-from-byte-array
IStream *shstream = SHCreateMemStream(srcasf, srcsz);
IMFByteStream *bstream;
MFCreateMFByteStreamOnStream(shstream, &bstream);
// Create preconfigured from source buffer reader
IMFSourceReader *srcreader;
hr = MFCreateSourceReaderFromByteStream(bstream, nullptr, &srcreader);
// Setup wav metadata/header (s16le@44.1kHz 2ch)
UINT32 a, b, c;
IMFMediaType *mediatype;
PROPVARIANT duration;
srcreader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, &mediatype);
srcreader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &duration);
#ifdef _WIN64
mediatype->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &a);
mediatype->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &b);
mediatype->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &c);
#else
a = 2;
b = 44100;
c = 16;
#endif
wav_hdr dsthdr = {
.channels=static_cast<uint16_t>(a),
.samplerate=b,
.bitdepth=static_cast<uint16_t>(c)
};
// ASF reader => PCM data
if (!*dstwav) *dstwav = (BYTE *) malloc(size_t(((double) duration.uhVal.QuadPart / 10000000.0 * dsthdr.samplerate + 4096) * dsthdr.bitdepth * dsthdr.channels) + sizeof(struct wav_hdr));
asf_unpack_pcm_s16le(*dstwav + sizeof(struct wav_hdr), srcreader, 0xFFFFFFFF, dstsz); //_INFO: overflow handled elsewhere
// Finalize and store header
dsthdr.datasz = *dstsz;
dsthdr.filesz = *dstsz + sizeof(struct wav_hdr) - 8;
memcpy(*dstwav, &dsthdr, sizeof(struct wav_hdr));
*dstsz += sizeof(wav_hdr);
// Cleanup without releasing srcasf
PropVariantClear(&duration);
mediatype->Release();
srcreader->Release();
bstream->Close();
shstream->Release();
return hr;
}
bool init()
{
HMODULE mf_ = libutils::try_library("mf.dll");
HMODULE mfreadwrite_ = libutils::try_library("mfreadwrite.dll");
HMODULE mfplat_ = libutils::try_library("mfplat.dll");
HMODULE shlwapi_ = libutils::try_library("shlwapi.dll");
if (!mf_ || !mfreadwrite_ || !mfplat_ || !shlwapi_) return false;
// Required by linker
MFCreateMFByteStreamOnStream = (MFCreateMFByteStreamOnStream_t) GetProcAddress(mfplat_, "MFCreateMFByteStreamOnStream");
MFStartup = (MFStartup_t) GetProcAddress(mfplat_, "MFStartup");
MFShutdown = (MFShutdown_t) GetProcAddress(mfplat_, "MFShutdown");
MFCreateSourceReaderFromByteStream = (MFCreateSourceReaderFromByteStream_t) GetProcAddress(mfreadwrite_, "MFCreateSourceReaderFromByteStream");
SHCreateMemStream = (SHCreateMemStream_t) GetProcAddress(shlwapi_, "SHCreateMemStream");
if (!MFCreateMFByteStreamOnStream || !MFCreateSourceReaderFromByteStream || !MFStartup || !MFShutdown || FAILED(MFStartup(MF_VERSION, MFSTARTUP_FULL)))
return false;
return true;
}
void deinit()
{
MFShutdown();
}
}
namespace hooks::soundbank
{
constexpr char SNDPATHFMT[] = "%05d/%05d";
constexpr size_t SNDPATHFMTMAX = sizeof(SNDPATHFMT) - 1;
constexpr size_t MNTPATHMAX = sizeof("/sdAABBB/AABBB/AABBBC_pre.2dx");
constexpr size_t AVSPATHMAX = sizeof("/data/sound/AABBB/AABBBC_pre.2dx");
constexpr uint8_t CACHEMAX = 8;
constexpr int operator "" _i(long double x) { return static_cast<int>(x); }
typedef struct snd_bank snd_bank;
typedef struct snd_s3p snd_s3p;
typedef struct snd_2dx snd_2dx;
typedef void *(*EnumValidSoundbanks_t)(void *, int32_t, int32_t);
struct snd_2dx
{
public:
struct bank
{
struct voice_hdr
{
char sig[4];
uint32_t fbegin;
uint32_t sz;
int16_t unk0;
int16_t type;
int16_t unk1;
int16_t volume;
int32_t unk2;
[[nodiscard]] constexpr BYTE *wave() noexcept
{
return ((BYTE *) this) + this->fbegin;
}
};
struct voice_ptr
{
uint32_t fbegin;
[[nodiscard]] constexpr void sz(voice_hdr *val) noexcept
{
(this + 1)->fbegin = this->fbegin + val->fbegin + val->sz; // see constructor
}
[[nodiscard]] constexpr size_t sz() const noexcept
{
return (this + 1)->fbegin > 0 ? (this + 1)->fbegin - this->fbegin : 0; // see constructor
}
};
struct
{
char name[16];
uint32_t sz;
uint32_t voice_cnt;
char unk[48];
} header;
voice_ptr table;
[[nodiscard]] constexpr size_t sz() const noexcept
{
return (&table + header.voice_cnt - 1)->fbegin + (&table + header.voice_cnt - 1)->sz();
}
};
[[nodiscard]] static constexpr size_t assertsz_asf(size_t sz, int8_t resampler = 1) noexcept
{
resampler = (resampler > 1 || resampler == -1) ? 2 : 1; // resampler ratio multiplier (see pcm_resampler enum, original data is float)
return static_cast<size_t>(static_cast<double>(sz) * 14.8) * resampler + WAVHDRMAX; // 0.wma is consistent 180 therefore VBR Q90, sometimes 160 though, but samples are all over the place
}
[[nodiscard]] constexpr const decltype(bank::header) *header() const noexcept
{
return &this->bank_->header;
}
[[nodiscard]] constexpr bank::voice_ptr *voice_ptr(size_t i) const noexcept
{
return &bank_->table + i;
}
[[nodiscard]] constexpr bank::voice_hdr *voice(size_t i) const noexcept
{
return (bank::voice_hdr *) ((char *) this->bank_ + this->voice_ptr(i)->fbegin);
}
[[nodiscard]] constexpr size_t voice_emplace(size_t assertsz) noexcept //_INFO: assertsz guaranteed until serialize(true), overridden by voice_ptr::sz(v), compute value with assertsz_*()
{
// memory space assertion
if (this->voice_ptr(this->bank_->header.voice_cnt)->fbegin + assertsz >= banksz_)
{
log_info("hooks::soundbank", "snd_2dx::voice_emplace({}=>{})", banksz_, banksz_ + assertsz + ALLOCSIZE);
banksz_ += assertsz + ALLOCSIZE;
bank_ = (bank *) realloc(bank_, banksz_);
}
// voice_ptr updating works through voice_ptr::sz()
//_REV: table and header.sz resizing event if voice_cnt == 4077, as voice_ptr::sz() needs to read 1 ahead
// voice_hdr setup
bank::voice_hdr *voice_hdr = this->voice(this->bank_->header.voice_cnt);
std::copy("2DX9", &"2DX9"[4], voice_hdr->sig);
voice_hdr->fbegin = sizeof(bank::voice_hdr);
voice_hdr->sz = 0;
voice_hdr->unk0 = (bank_->header.name[1] - '0') % 2 == 1 ? 0x3231 : 0x3230; // 0x3230/0x3231 for even/odd style
voice_hdr->type = this->bank_->header.voice_cnt == 0 ? 0 : -1; // special case for Voice[0]
voice_hdr->unk1 = 0x40;
voice_hdr->volume = 1;
voice_hdr->unk2 = 0x0;
return this->bank_->header.voice_cnt++;
}
static snd_2dx *unpack(char *refbank) = delete;
static snd_2dx *pack(int id);
bank *serialize(bool trim);
~snd_2dx();
private:
static constexpr size_t WAVHDRMAX = 78;
static constexpr size_t ALLOCSIZE = 4 * 1024 * 1024;
struct bank *bank_;
size_t banksz_;
explicit snd_2dx(uint8_t *bindbank);
};
snd_2dx::snd_2dx(uint8_t *bindbank) : bank_(reinterpret_cast<struct bank *>(bindbank)), banksz_(0)
{
if (bank_)
return;
// Necessary bank header initialization
banksz_ = ALLOCSIZE;
bank_ = (bank *) malloc(ALLOCSIZE);
memset(&bank_->header, 0, sizeof(bank_->header) + 4078 * sizeof(bank::voice_ptr)); // initializes bank::voice_ptr::sz and bank::header.unk
bank_->header.name[0] = '\0';
bank_->header.voice_cnt = 0;
bank_->header.sz = sizeof(bank_->header) + 4078 * sizeof(bank::voice_ptr); // 4078 keysounds limit (16KB header), refer to voice_emplace()
// Necessary voice pointer table initialization (refer to voice_ptr::sz())
(&bank_->table)[0].fbegin = bank_->header.sz;
}
snd_2dx *snd_2dx::pack(int id)
{
snd_2dx *r = new snd_2dx(nullptr);
snprintf(r->bank_->header.name, sizeof(r->bank_->header.name), "%05d.2dx", id);
return r;
}
snd_2dx::bank *snd_2dx::serialize(bool trim)
{
if (trim)
{
//_REV: prune voice_ptr for unused voice slots + move data segment up adjusting voice_ptr->fbegin
}
return bank_;
}
snd_2dx::~snd_2dx()
{
free(bank_);
}
struct snd_s3p
{
public:
struct bank
{
struct voice_hdr
{
char sig[4]; // S3V0
uint32_t fbegin;
uint32_t sz;
uint8_t unk[20];
[[nodiscard]] constexpr BYTE *asf() const noexcept
{
return ((BYTE *) this) + this->fbegin;
}
};
struct voice_ptr
{
uint32_t fbegin;
uint32_t sz;
};
struct
{
const char sig[4]; // S3P0
uint32_t voice_cnt;
} header;
voice_ptr table;
};
[[nodiscard]] constexpr const decltype(bank::header) *header() const noexcept
{
return &this->bank_->header;
}
[[nodiscard]] constexpr bank::voice_ptr *voice_ptr(size_t i) const noexcept
{
return &bank_->table + i;
}
[[nodiscard]] constexpr bank::voice_hdr *voice(size_t i) const noexcept
{
return (bank::voice_hdr *) ((char *) this->bank_ + this->voice_ptr(i)->fbegin);
}
static snd_s3p *unpack(uint8_t *refbank);
static snd_s3p *pack(int id) = delete;
bank *serialize(bool trim) = delete;
~snd_s3p();
private:
struct bank *bank_;
size_t banksz_; //_TODO: keep track of allocated memory
explicit snd_s3p(uint8_t *bindbank);
};
snd_s3p::snd_s3p(uint8_t *bindbank) : bank_(reinterpret_cast<struct bank *>(bindbank)), banksz_(0)
{
}
snd_s3p::~snd_s3p()
{
free(bank_);
}
snd_s3p *snd_s3p::unpack(uint8_t *refbank)
{
return refbank ? new snd_s3p(refbank) : nullptr;
}
struct snd_bank
{
public:
static snd_bank *new_instance(int id);
static void flush_cache();
static void pop_cache();
static snd_bank *get_bank(int id, const char suffix = '0');
static uint8_t *read_bank_any(const char *avspath, const char *ext, size_t *sz);
static bool has_format(const char *avspath, const char *ext);
static avs::core::avs_file_t map_ifs(char *avspath);
static void dump(const char *path, const uint8_t *src, size_t sz);
int id;
char avspath[AVSPATHMAX];
void cache_bank_any(const uint8_t *sndbuf, size_t sz);
[[nodiscard]] constexpr bool is_cached() const noexcept
{
return uid_ != UID_INVALID;
}
[[nodiscard]] constexpr const char *sndpath() const noexcept
{
const char *it = avspath + AVSPATHMAX;
size_t c = 0;
while (it >= avspath && c < 2)
{
it--;
if (*it == '/') c++;
}
it++;
if (strlen(it) > SNDPATHFMTMAX)
return SNDPATHFMT;
return it;
}
inline static avcodec::pcm_resampler_t resampler() noexcept
{
return resampler_;
}
private:
static constexpr avs::core::avs_file_t E_NOT_FOUND = 0x80070002;
static constexpr uint8_t UID_INVALID = 0x00;
static snd_bank *cache_;
static size_t cachesz_;
static avcodec::pcm_resampler_t resampler_;
friend void hooks::soundbank::init(HINSTANCE hmodule, const char *model, const char *ext);
uint8_t uid_;
uint32_t mnt_;
snd_bank *next_;
snd_bank();
~snd_bank();
};
void snd_bank::dump(const char *path, const uint8_t *src, size_t sz)
{
FILE *fh = fopen(path, "wb");
fwrite(src, sz, 1, fh);
fclose(fh);
}
snd_bank *snd_bank::new_instance(int id)
{
snd_bank *prev, *instance;
// Latest entry should always be head
instance = new snd_bank();
instance->id = id;
instance->next_ = cache_;
cache_ = instance;
cachesz_ += 1;
// Clean cache (last entry indexing target excluded, cache limit reached)
if (instance->next_ && instance->next_->uid_ == UID_INVALID)
{
log_info("hooks::soundbank", "Cache prune");
prev = instance->next_;
instance->next_ = prev->next_;
log_info("hooks::soundbank", "Removing {}::Bank[CACHE]::{}", prev->id, (uint32_t) prev->uid_);
delete prev;
cachesz_ -= 1;
}
if (cachesz_ > CACHEMAX)
{
log_info("hooks::soundbank", "Cache full");
for (uint8_t i = 0; i < CACHEMAX; i += 2)
{
prev = nullptr;
instance = cache_;
while (instance && instance->next_)
{
prev = instance;
instance = instance->next_;
}
prev->next_ = nullptr;
delete instance;
cachesz_ -= 1;
}
}
return cache_;
}
void snd_bank::cache_bank_any(const uint8_t *sndbuf, size_t sz)
{
// Update struct using next free cache uid
if (uid_ == UID_INVALID)
{
snd_bank *it = cache_;
while (it)
{
if (uid_ == UID_INVALID || (uid_ == it->uid_ && it != this))
{
uid_--;
it = cache_;
continue;
}
it = it->next_;
}
}
snprintf(this->avspath, sizeof(this->avspath), "data/sound/system/%02x", (unsigned int) uid_);
// Store file
char path[AVSPATHMAX];
snprintf(path, sizeof(path), "%s.2dx", this->avspath);
log_info("hooks::soundbank", "Storing as intermediate soundbank at '{}'", (const char *) path);
dump(path, sndbuf, sz);
}
snd_bank::snd_bank() : id(0), avspath(""), uid_(UID_INVALID), mnt_(E_NOT_FOUND), next_(nullptr)
{
}
snd_bank::~snd_bank()
{
if (uid_ != UID_INVALID)
{
char path[AVSPATHMAX];
snprintf(path, sizeof(path), "data/sound/system/%02x.2dx", uid_);
log_info("hooks::soundbank", "{}::Bank[CACHE]::Purge '{}'", this->id, (const char *) path);
remove(path);
}
if (mnt_ != E_NOT_FOUND)
{
log_info("hooks::soundbank", "{}::Bank[CACHE]::Purge mount(0x{:08x})", this->id, mnt_);
avs::core::avs_fs_umount(mnt_);
}
}
snd_bank *snd_bank::get_bank(int id, const char suffix)
{
// Find in cache (latest=>oldest)
id = id + ((suffix - '0') * 1e6_i);
for (snd_bank *it = cache_; it; it = it->next_)
if (it->id == id) return it;
// Find in avs (has valid s3p/.2dx resource at data/sound/AABBB/AABBB.2dx)
snd_bank *bank = new_instance(id);
snprintf(bank->avspath, AVSPATHMAX, "data/sound/%05d", (id % 1e6_i));
if (has_format(bank->avspath, ".ifs"))
bank->mnt_ = map_ifs(bank->avspath);
snprintf(bank->avspath + strlen(bank->avspath), 7, "/%05d", (id % 1e6_i));
if (id / 1e6_i) snprintf(bank->avspath + strlen(bank->avspath), 2, "%c", '0' + (id / 1e6_i));
if (!(has_format(bank->avspath, ".s3p") || has_format(bank->avspath, ".2dx")))
{
pop_cache();
bank = nullptr;
}
return bank;
}
void snd_bank::flush_cache()
{
snd_bank *prev;
while (cache_)
{
prev = cache_;
cache_ = cache_->next_;
delete prev;
}
}
void snd_bank::pop_cache()
{
snd_bank *prev = cache_;
cache_ = cache_->next_;
delete prev;
}
uint8_t *snd_bank::read_bank_any(const char *avspath, const char *ext, size_t *sz)
{
constexpr uint16_t O_RDONLY = 1; // rw:12 consistent across versions with s3p support
uint8_t *r = nullptr;
char path[AVSPATHMAX];
snprintf(path, sizeof(path), "%s%s", avspath, ext);
avs::core::avs_file_t fd = avs::core::avs_fs_open(path, O_RDONLY, 420);
if (fd != E_NOT_FOUND)
{
{
avs::core::avs_stat st; // NOLINT(cppcoreguidelines-pro-type-member-init)
avs::core::avs_fs_fstat(fd, &st);
*sz = st.filesize;
}
r = (uint8_t *) malloc(*sz);
if (r) avs::core::avs_fs_read(fd, r, *sz);
avs::core::avs_fs_close(fd);
}
return r;
}
bool snd_bank::has_format(const char *avspath, const char *ext)
{
char path[AVSPATHMAX] = "";
snprintf(path, sizeof(path), "%s%s", avspath, ext);
avs::core::avs_stat st; // NOLINT(cppcoreguidelines-pro-type-member-init)
bool r = static_cast<bool>(avs::core::avs_fs_lstat(path, &st)); // lstat should be faster than open
log_info("hooks::soundbank", "'{}' exists? => {}", path, r);
return static_cast<bool>(r);
}
avs::core::avs_file_t snd_bank::map_ifs(char *avspath)
{
// Map */${name}.ifs => /sc${name}
char ifspath[AVSPATHMAX];
char mntpath[MNTPATHMAX];
const char *it = avspath + strlen(avspath);
while (it >= avspath && *it != '/')
{
it--;
}
it++;
snprintf(ifspath, sizeof(ifspath), "%s.ifs", avspath);
snprintf(mntpath, sizeof(mntpath), "sc%s", it);
avs::core::avs_file_t r = avs::core::avs_fs_mount(mntpath, ifspath, "imagefs", nullptr);
// Finalize avspath
if (r != E_NOT_FOUND)
{
snprintf(mntpath + strlen(mntpath), strlen(it) + 2, "/%s", it);
log_info("hooks::soundbank", "'{}' => '{}'", avspath, mntpath);
strcpy(avspath, mntpath);
}
return r;
}
static struct offset_t
{
intptr_t RD_05d_s3p; // %05d/%05d.s3p
intptr_t RD_05d_c_s3p; // %05d/%05d%c.s3p
intptr_t RD_05d_2dx; // %05d/%05d.2dx
intptr_t RD_05d_c_2dx; // %05d/%05d%c.2dx
intptr_t TX_EnumValidSoundbanks; // enumerates above databank paths, simple exist check (lstat()/OPEN -> CLOSE_NOWRITE only)
intptr_t DAT_song_titlel;
intptr_t DAT_song_artistl;
intptr_t DAT_song_id;
bool is_lstr_wchar;
memutils::VProtectGuard *guard;
} offset_;
snd_bank *snd_bank::cache_ = nullptr;
size_t snd_bank::cachesz_ = 0;
avcodec::pcm_resampler_t snd_bank::resampler_ = avcodec::bmswac_resampler_s16;
static EnumValidSoundbanks_t EnumValidSoundbanks;
void *on_enum_valid_soundbanks(void *song, int32_t difficulty, int32_t unk2)
{
// Keysounds populated into CtrlSound::Bank[2][X] = [0:s3p][1:2dx][2:_pre.2dx], always uses lowest i databank for resource confirmed valid by this function
// Struct memory layout 1:1 with data/info/(0/1)/music_*.bin
snd_bank *keysounds = nullptr;
const char *titlel = (const char *) song + offset_.DAT_song_titlel;
const char *artistl = (const char *) song + offset_.DAT_song_artistl;
const int32_t id = *(int32_t *) ((const char *) song + offset_.DAT_song_id);
const char suffix = *((const char *) song + offset_.DAT_song_id + 8 + difficulty);
if (offset_.is_lstr_wchar)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
log_info("hooks::soundbank", "Processing s3p->2dx transcoding routine for [{}]({} - {})",
id,
converter.to_bytes(std::wstring(reinterpret_cast<const wchar_t *>(artistl), wcslen(reinterpret_cast<const wchar_t *>(artistl)))),
converter.to_bytes(std::wstring(reinterpret_cast<const wchar_t *>(titlel), wcslen(reinterpret_cast<const wchar_t *>(titlel))))
);
}
else
log_info("hooks::soundbank", "Processing s3p->2dx transcoding routine for [{}]({} - {})", id, artistl, titlel);
if (!(keysounds = snd_bank::get_bank(id, suffix)))
{
log_warning("hooks::soundbank", "soundbank::get_bank() was 0x0, this shouldn't happen..");
memcpy(reinterpret_cast<void *>(offset_.RD_05d_2dx), "%05d/%05d", SNDPATHFMTMAX);
return EnumValidSoundbanks(song, difficulty, unk2);
}
else if (!snd_bank::has_format(keysounds->avspath, ".2dx"))
{
// Get s3p and process into s3p soundbank for intermediate wma voice storage
size_t szs3p;
uint8_t *bufs3p = snd_bank::read_bank_any(keysounds->avspath, ".s3p", &szs3p);
snd_s3p *snds3p = snd_s3p::unpack(bufs3p);
// Process wma voices into wav voices stored directly into 2dx soundbank
size_t i, j;
snd_2dx *snd2dx = snd_2dx::pack(id);
for (i = 0; i < snds3p->header()->voice_cnt; i++)
{
// WMA=>WAV transcode
snd_s3p::bank::voice_hdr *voicesrc = snds3p->voice(i);
j = snd2dx->voice_emplace(snd_2dx::assertsz_asf(voicesrc->sz));
snd_2dx::bank::voice_hdr *voicedst = snd2dx->voice(j);
BYTE *voicedst_wave = voicedst->wave();
avcodec::BmswTranscoderAsfToWav(voicesrc->asf(), voicesrc->sz, &voicedst_wave, &voicedst->sz, snd_bank::resampler());
snd2dx->voice_ptr(j)->sz(voicedst); // updates voice_ptr table, do not skip
}
// Output to cache (reuse output file, while in valid cache)
snd_2dx::bank *buf2dx = snd2dx->serialize(false);
keysounds->cache_bank_any(reinterpret_cast<const uint8_t *>(buf2dx), buf2dx->sz());
//_REV: symlink system/uid_.2dx -> /tmp/uid_.2dx
}
// Resume execution (force %05.2dx branch)
const char *t = keysounds->sndpath();
memcpy(reinterpret_cast<void *>(offset_.RD_05d_2dx), t, SNDPATHFMTMAX);
log_info("hooks::soundbank", "RD_05d_2dx set to '{}'", reinterpret_cast<const char *>(offset_.RD_05d_2dx));
log_info("hooks::soundbank", "Processing completed");
*((char *) song + offset_.DAT_song_id + 8 + difficulty) = keysounds->is_cached() ? '0' : suffix;
void *r = EnumValidSoundbanks(song, difficulty, unk2);
*((char *) song + offset_.DAT_song_id + 8 + difficulty) = suffix;
return r;
}
static class Tests : public hooks::devel::Routine
{
public:
using Routine::Routine;
void entry_main() override
{
size_t szs3p;
BYTE *wav = nullptr;
uint32_t wavsz = 0;
char avspath[AVSPATHMAX];
avs::core::avs_file_t avsmnt = 0;
char sig[5] = " ";
int id = 25073;
if (!mf_broken::init())
{
log_warning("hooks::soundbank", "MF initialization failed, skipping..");
}
// Filesystem tests, s3p unpacking
snprintf(avspath, sizeof(avspath), "data/sound/%05d", id);
if (snd_bank::has_format(avspath, ".ifs")) avsmnt = snd_bank::map_ifs(avspath);
snprintf(avspath + strlen(avspath), 7, "/%05d", id);
snd_bank::has_format(avspath, ".s3p");
snd_bank::has_format(avspath, ".2dx");
snd_bank::has_format(avspath, "_pre.2dx");
uint8_t *bufs3p = snd_bank::read_bank_any(avspath, ".s3p", &szs3p);
snd_s3p *snds3p = snd_s3p::unpack(bufs3p);
if (avsmnt) avs::core::avs_fs_umount(avsmnt);
// S3P => WMA => WAV => 2DX pipeline
if (snds3p)
{
// S3P/S3V metadata
memcpy(sig, snds3p->header()->sig, 4);
log_info("hooks::soundbank", "{}::Bank[S3P]::Signature {}", id, sig);
log_info("hooks::soundbank", "{}::Bank[S3P]::Size {}", id, szs3p);
log_info("hooks::soundbank", "{}::Bank[S3P]::VoiceCount {}", id, snds3p->header()->voice_cnt);
log_info("hooks::soundbank", "{}::Bank[S3P]::Voice[0]::Size {}", id, snds3p->voice_ptr(0)->sz);
memcpy(sig, snds3p->voice(0)->sig, 4);
log_info("hooks::soundbank", "{}::Bank[S3P]::Voice[0]::Signature {}", id, sig);
snd_bank::dump("raw_wma.wma", snds3p->voice(0)->asf(), snds3p->voice(0)->sz);
// MF codec (tainted PCM data, bad)
mf_broken::asf_to_wav(snds3p->voice(0)->asf(), snds3p->voice(0)->sz, &wav, &wavsz);
snd_bank::dump("mf_wav.wav", wav, wavsz);
free(wav);
wav = nullptr;
wavsz = 0;
// AV codec (1:1 unmodified PCM data, other than resampling if enabled)
wav = (BYTE *) malloc(snd_2dx::assertsz_asf(snds3p->voice(0)->sz, avcodec::bmswac_resampler_f32));
avcodec::BmswTranscoderAsfToWav(snds3p->voice(0)->asf(), snds3p->voice(0)->sz, &wav, &wavsz, avcodec::bmswac_resampler_f32);
snd_bank::dump("ac_wav.wav", wav, wavsz);
free(wav);
wav = nullptr;
wavsz = 0;
// S3P=>2DX (max 4076 voices)
size_t i, j;
snd_2dx *snd2dx = snd_2dx::pack(id);
log_info("hooks::soundbank", "{}::Bank[2DX]::Name {}", id, snd2dx->header()->name);
for (i = 0; i < snds3p->header()->voice_cnt; i++)
{
// WMA=>WAV transcode
snd_s3p::bank::voice_hdr *voicesrc = snds3p->voice(i);
j = snd2dx->voice_emplace(snd_2dx::assertsz_asf(voicesrc->sz));
snd_2dx::bank::voice_hdr *voicedst = snd2dx->voice(j);
BYTE *voicedst_wave = voicedst->wave();
//mf_broken::asf_to_wav(voicesrc->asf(), voicesrc->sz, &voicedst_wave, &voicedst->sz);
avcodec::BmswTranscoderAsfToWav(voicesrc->asf(), voicesrc->sz, &voicedst_wave, &voicedst->sz, snd_bank::resampler());
snd2dx->voice_ptr(j)->sz(voicedst); // updates voice_ptr table, do not skip
}
// Valid 2DX (trim is not necessary)
log_info("hooks::soundbank", "{}::Bank[2DX]::Dumping", id);
snd_2dx::bank *buf2dx = snd2dx->serialize(false);
snd_bank::dump("ac_soundbank.2dx", reinterpret_cast<const uint8_t *>(buf2dx), buf2dx->sz());
}
else
{
log_info("hooks::soundbank", "Tests failed");
}
// Caching and intermediate 2dx storage detection
EnumValidSoundbanks_t t = EnumValidSoundbanks;
EnumValidSoundbanks = [](void *, int32_t, int32_t) -> void * { return nullptr; };
char *tmp = (char *) malloc(0x3b8 + strlen("00000000") + 1);
int arr[6] = {25073, 24016, 26057, 25073, 26087, 29095};
strcpy(tmp, "SJISTITLE");
strcpy(tmp + 0xc0, "SJISARTIST");
strcpy(tmp + 0x3b8, "00000000");
for (int it: arr)
{
*(int32_t *) (tmp + 0x3b0) = it;
on_enum_valid_soundbanks(tmp, 3, 0);
}
free(tmp);
EnumValidSoundbanks = t;
snd_bank::flush_cache();
mf_broken::deinit();
}
} tests_("sndb_test");
void init(HINSTANCE hmodule, const char *model, const char *ext)
{
if (to_hash(model) != to_hash("LDJ")) return;
if (false && IsDebuggerPresent())
{
log_warning("hooks::soundbank", "Debugger detected, skipping..");
return;
}
if (!avcodec::init())
{
log_warning("hooks::soundbank", "AVCodec initialization failed, skipping..");
return;
}
// Override .data (per datacode)
long extl = strtol(ext, nullptr, 10);
if (extl < 2019100800)
{
offset_.DAT_song_titlel = 0x00;
offset_.DAT_song_artistl = 0xc0;
offset_.DAT_song_id = 0x1c8;
offset_.is_lstr_wchar = false;
snd_bank::resampler_ = avcodec::bmswac_resampler_s16_ad;
}
else if (extl < 2024082700)
{
offset_.DAT_song_titlel = 0x00;
offset_.DAT_song_artistl = 0xc0;
offset_.DAT_song_id = 0x3b0;
offset_.is_lstr_wchar = false;
}
else
{
offset_.DAT_song_titlel = 0x00;
offset_.DAT_song_artistl = 0x1c0;
offset_.DAT_song_id = 0x67c;
offset_.is_lstr_wchar = true;
}
// Override .text (pattern match)
offset_.TX_EnumValidSoundbanks = patch_pipeline(
hmodule,
[](HMODULE hmodule, intptr_t addr) { return find_asm_leacstr_from(hmodule, R8, "%05d/%05d.2dx"); },
[](HMODULE hmodule, intptr_t addr) { return find_pattern_from(hmodule, "CCCCCCCCCCCCCCCC", "XXXXXXXX", 8, 0, addr, true); }
);
snd_bank::resampler_ = avcodec::BmswTranscoderResampler() == avcodec::bmswac_resampler_none ? snd_bank::resampler_ : avcodec::BmswTranscoderResampler();
offset_.TX_EnumValidSoundbanks = avcodec::BmswTranscoderEnumValidSoundbanksAddr() == 0x0 ? offset_.TX_EnumValidSoundbanks : (intptr_t) hmodule + (int64_t) avcodec::BmswTranscoderEnumValidSoundbanksAddr();
if (!offset_.TX_EnumValidSoundbanks)
{
log_warning("hooks::soundbank", "Unsupported game version({}), skipping..", ext);
return;
}
log_info("hooks::soundbank", "Supported game version({})", ext);
// Override .rdata (pattern match)
offset_.RD_05d_s3p = replace_pattern(hmodule, "253035642f253035642e73337000", "003035642f253035642e73337000", 0, 0);
offset_.RD_05d_c_s3p = replace_pattern(hmodule, "253035642f2530356425632e73337000", "003035642f2530356425632e73337000", 0, 0);
offset_.RD_05d_2dx = replace_pattern(hmodule, "253035642F253035642E32647800", "73797374656D2F30302E32647800", 0, 0);
offset_.RD_05d_c_2dx = replace_pattern(hmodule, "253035642f2530356425632e32647800", "253035642f2530356425632e32647800", 0, 0);
if (!(offset_.TX_EnumValidSoundbanks && offset_.RD_05d_s3p && offset_.RD_05d_c_s3p && offset_.RD_05d_2dx && offset_.RD_05d_c_2dx))
{
log_warning("hooks::soundbank", "Could not find valid offsets, skipping..");
return;
}
// Setup on_enum_valid_ksbd and optional tests
log_info("hooks::soundbank", "Hooking EnumValidSoundbanks(0x{:x})", offset_.TX_EnumValidSoundbanks);
offset_.guard = new memutils::VProtectGuard((void *) offset_.RD_05d_2dx, SNDPATHFMTMAX);
detour::trampoline(reinterpret_cast<void *>(offset_.TX_EnumValidSoundbanks), reinterpret_cast<void *>(on_enum_valid_soundbanks), reinterpret_cast<void **>(&EnumValidSoundbanks));
hooks::devel::bind_routine(&tests_);
log_info("hooks::soundbank", "Soundbank preprocessor ready");
}
void deinit(HINSTANCE hmodule)
{
log_info("hooks::soundbank", "Releasing soundbank preprocessor");
snd_bank::flush_cache();
delete offset_.guard;
avcodec::deinit();
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <windows.h>
namespace hooks::soundbank
{
void init(HINSTANCE hmodule, const char *model, const char *ext);
void deinit(HINSTANCE hmodule);
}
+267
View File
@@ -0,0 +1,267 @@
#include "wmischook.h"
#include "util/logging.h"
#include "util/detour.h"
#include "util/sigscan.h"
#include "util/libutils.h"
#include "util/utils.h"
#include <mmsystem.h>
#include <mutex>
namespace hooks::wmisc::msacm
{
typedef MMRESULT (__stdcall *acmFormatSuggest_t)(HANDLE had, LPWAVEFORMATEX pwfxSrc, LPWAVEFORMATEX pwfxDst, DWORD cbwfxDst, DWORD fdwSuggest);
static acmFormatSuggest_t acmFormatSuggest;
static uint64_t hash_waveformatex(const WAVEFORMATEX *pwfx, DWORD fdwSuggest)
{
//_INFO: no clashes, likely faster than hashing sizeof(WAVEFORMATEX) buffer
uint64_t h = 0;
h ^= (uint64_t) pwfx->nSamplesPerSec << 0;
h ^= (uint64_t) pwfx->nAvgBytesPerSec << 1;
h ^= (uint64_t) pwfx->nBlockAlign << 2;
h ^= (uint64_t) pwfx->wBitsPerSample << 3;
h ^= (uint64_t) pwfx->cbSize << 4;
h ^= (uint64_t) fdwSuggest << 5;
h ^= (uint64_t) pwfx->wFormatTag << 6;
h ^= (uint64_t) pwfx->nChannels << 7;
return h;
}
MMRESULT __stdcall on_acmFormatSuggest(HANDLE had, LPWAVEFORMATEX pwfxSrc, LPWAVEFORMATEX pwfxDst, DWORD cbwfxDst, DWORD fdwSuggest)
{
static std::unordered_map<uint64_t, std::vector<char>> acmformats;
static std::mutex mutex;
// Find cached formats
size_t hash = hash_waveformatex(pwfxSrc, fdwSuggest);
{
std::lock_guard lock(mutex);
auto it = acmformats.find(hash);
if (it != acmformats.end())
{
// Copy cached result
memcpy(pwfxDst, it->second.data(), std::min(cbwfxDst, (DWORD) (sizeof(WAVEFORMATEX) + ((WAVEFORMATEX *) it->second.data())->cbSize)));
return MMSYSERR_NOERROR;
}
}
// Cache unique formats
log_info("hooks::wmisc::msacm", "Forwarding acmFormatSuggest({}) request due to cache miss", reinterpret_cast<void *>(hash));
MMRESULT r = acmFormatSuggest(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
if (r == MMSYSERR_NOERROR)
{
std::lock_guard lock(mutex);
acmformats[hash].resize(sizeof(WAVEFORMATEX) + pwfxDst->cbSize);
memcpy(acmformats[hash].data(), pwfxDst, sizeof(WAVEFORMATEX) + pwfxDst->cbSize);
}
return r;
}
void attach(HINSTANCE hmodule)
{
// Override acmFormatSuggest
HMODULE msacm = nullptr;
if ((msacm = libutils::try_library("msacm32.dll")))
{
acmFormatSuggest = (acmFormatSuggest_t) GetProcAddress(msacm, "acmFormatSuggest");
}
log_info("hooks::wmisc::msacm", "Hooking acmFormatSuggest({})", reinterpret_cast<void *>(acmFormatSuggest));
detour::trampoline_try(reinterpret_cast<void *>(acmFormatSuggest), reinterpret_cast<void *>(on_acmFormatSuggest), reinterpret_cast<void **>(&acmFormatSuggest));
}
}
namespace hooks::wmisc::thumbnail
{
typedef bool (*ThumExists_t)(void *titleUTF_getter, int32_t id, char flag);
static ThumExists_t ThumExists;
bool on_thum_exists_(void *titleUTF_getter, int32_t id, char flag)
{
static std::unordered_map<int32_t, bool> thumids;
auto it = thumids.find(id);
if (it != thumids.end())
{
return it->second;
}
else
{
// log_misc("hooks::wmisc::thumbnail", "Caching state for [{}]", id);
it = thumids.insert({id, ThumExists(titleUTF_getter, id, flag)}).first;
}
return it->second;
}
void attach(HINSTANCE hmodule)
{
// Reverse lookup function head (PUSH RDI) for ThumExists() specific signature, quietly fail if invalid
intptr_t TX_ThumExists = patch_pipeline(
hmodule,
[](HMODULE hmodule, intptr_t addr) { return find_pattern_from(hmodule, "83f801750c4084ff750732c0", "XXXXXXXXXXXX", 0, 0, 0); },
[](HMODULE hmodule, intptr_t addr) { return find_pattern_from(hmodule, "4057", "XX", 0, 0, addr, true); }
);
log_info("hooks::wmisc::thumbnail", "Hooking ThumExists({})", reinterpret_cast<void *>(TX_ThumExists));
detour::trampoline_try(reinterpret_cast<void *>(TX_ThumExists), reinterpret_cast<void *>(on_thum_exists_), reinterpret_cast<void **>(&ThumExists));
}
}
namespace hooks::wmisc::bmsound
{
typedef void (*BmswLogInit_t)(void *cb);
extern "C" void __cdecl on_log_msg(char level, const char *scope, const char *msg)
{
switch (level)
{
case 'I':
log_info(scope, "{}", msg);
break;
case 'W':
log_warning(scope, "{}", msg);
break;
case 'E':
log_fatal(scope, "{}", msg);
break;
case 'M':
default:
log_misc(scope, "{}", msg);
break;
}
}
void attach(HINSTANCE hmodule)
{
BmswLogInit_t BmswLogInit = nullptr;
HMODULE bmsw;
if ((bmsw = libutils::try_library(MODULE_PATH / "bmsound-wine.dll")))
{
BmswLogInit = (BmswLogInit_t) GetProcAddress(bmsw, "BmswLogInit");
}
if (BmswLogInit) //_REM: keep 0.2.x backward compatibility until 0.3.0
{
BmswLogInit(reinterpret_cast<void *>(on_log_msg));
}
}
}
namespace hooks::wmisc::cmovieplayer
{
typedef void (*GetWmvChunk_t)(uint8_t *unk);
static GetWmvChunk_t GetWmvChunk_;
void on_get_wmv_chunk_(uint8_t *unk)
{
size_t chunksz = *(uint32_t *) (unk + 0x10);
void *readbuf = (void *) *(intptr_t *) (unk + 0x08);
int *readsz = (int *) (unk + 0x14);
//_REV: For now simply disable broken video stream
*readsz = 0;
}
void attach(HINSTANCE hmodule)
{
// Remove crashing movie
intptr_t TX_ThumExists = find_pattern_from(hmodule, "40534883ec20448b4110488bd9488b51088b09ff150000000089431485c0791d448bc0488d1500000000488d0d000000004883c4205b48ff25000000004883c4205bc3", "XXXXXXXXXXXXXXXXXXXXX????XXXXXXXXXXXXX????XXX????XXXXXXXX????XXXXXX", 0, 0, 0);
log_info("hooks::wmisc::cmovieplayer", "Hooking GetWmvChunk({})", reinterpret_cast<void *>(TX_ThumExists));
if (detour::trampoline_try(reinterpret_cast<void *>(TX_ThumExists), reinterpret_cast<void *>(on_get_wmv_chunk_), reinterpret_cast<void **>(&GetWmvChunk_)))
{
log_warning("hooks::wmisc::cmovieplayer", "Video playback for wmv won't work");
}
}
}
namespace hooks::wmisc::dmoeffector
{
typedef HRESULT (STDAPICALLTYPE *DllRegisterServer_t)();
HMODULE LoadLibraryRsrc(const char *rsrcname) {
// Unpack library blob
HRSRC hrsrc = FindResourceA(nullptr, rsrcname, RT_RCDATA);
void* rsrcdata = LockResource(LoadResource(nullptr, hrsrc));
DWORD rsrcsz = SizeofResource(nullptr, hrsrc);
// _REV: could use tmpfs instead if bmsound_wine provides tempfile wrapper in future
// Store as temporary file (no direct way to in-memory load without involving custom loaders like MemoryModule)
char tempPath[MAX_PATH];
char tempFile[MAX_PATH];
GetTempPathA(MAX_PATH, tempPath);
GetTempFileNameA(tempPath, rsrcname, 0, tempFile);
MoveFileExA(tempFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); // wine blocks DeleteFileA operations on LoadLibraryA filepaths
FILE *fh = fopen(tempFile, "wb");
fwrite(rsrcdata, rsrcsz, 1, fh);
fclose(fh);
// Map temporary library buffer
HMODULE hmodule = LoadLibraryA(tempFile);
return hmodule;
}
void attach(HINSTANCE hmodule)
{
HMODULE libdmo = LoadLibraryRsrc("DMO_BLOB");
if(libdmo)
{
log_info("hooks::wmisc::dmoeffector", "Registering COM server for DMO({})", reinterpret_cast<void *>(libdmo));
auto DllRegisterServer = (DllRegisterServer_t)GetProcAddress(libdmo, "DllRegisterServer");
if(DllRegisterServer && SUCCEEDED(DllRegisterServer())) return;
}
log_warning("hooks::wmisc::dmoeffector", "Loading embedded dsdmo.dll failed, error code: {}", GetLastError());
}
}
namespace hooks::wmisc
{
static bool patchmodel_ = true;
void skip_patching_by_model(bool val)
{
patchmodel_ = !val;
}
void apply_performance_hacks(HINSTANCE hmodule, const char *model, const char *ext)
{
if (!patchmodel_ && strcmp(model, "FORCE") != 0) return;
log_info("hooks::wmisc", "Enabling performance patches for {}-{}", model, ext);
long extl = strtol(ext, nullptr, 10);
switch (to_hash(model))
{
case to_hash("LDJ"):
dmoeffector::attach(hmodule);
thumbnail::attach(hmodule);
msacm::attach(hmodule);
bmsound::attach(hmodule);
break;
case to_hash("M39"):
msacm::attach(hmodule);
bmsound::attach(hmodule);
break;
case to_hash("KFC"):
dmoeffector::attach(hmodule);
msacm::attach(hmodule);
bmsound::attach(hmodule);
if (extl > 2019020600 && extl < 2021011800) // SDVX5
{
cmovieplayer::attach(hmodule);
}
break;
case to_hash("FORCE"):
switch (to_hash(ext))
{
case to_hash("thumbnail"):
thumbnail::attach(hmodule);
break;
case to_hash("msacm"):
msacm::attach(hmodule);
break;
case to_hash("bmsound"):
bmsound::attach(hmodule);
break;
case to_hash("cmovieplayer"):
cmovieplayer::attach(hmodule);
break;
case to_hash("dmoeffector"):
dmoeffector::attach(hmodule);
break;
default:
break;
}
break;
default:
break;
}
}
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <windows.h>
namespace hooks::wmisc
{
void skip_patching_by_model(bool val);
void apply_performance_hacks(HINSTANCE hmodule, const char *model, const char *ext);
}
+46 -21
View File
@@ -5,7 +5,6 @@
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include <assert.h>
#include <shlwapi.h> #include <shlwapi.h>
#include <windows.h> #include <windows.h>
#include <cfg/configurator.h> #include <cfg/configurator.h>
@@ -72,6 +71,7 @@
#include "games/mfg/mfg.h" #include "games/mfg/mfg.h"
#include "games/pc/pc.h" #include "games/pc/pc.h"
#include "games/museca/museca.h" #include "games/museca/museca.h"
#include "hooks/develhook.h"
#include "hooks/avshook.h" #include "hooks/avshook.h"
#include "hooks/audio/audio.h" #include "hooks/audio/audio.h"
#include "hooks/audio/asio_proxy.h" #include "hooks/audio/asio_proxy.h"
@@ -85,6 +85,8 @@
#include "hooks/networkhook.h" #include "hooks/networkhook.h"
#include "hooks/icmphook_net.h" #include "hooks/icmphook_net.h"
#include "hooks/unisintrhook.h" #include "hooks/unisintrhook.h"
#include "hooks/sndbhook.h"
#include "hooks/wmischook.h"
#include "launcher/launcher.h" #include "launcher/launcher.h"
#include "launcher/logger.h" #include "launcher/logger.h"
#include "launcher/signal.h" #include "launcher/signal.h"
@@ -145,7 +147,7 @@ std::string LOG_FILE_PATH = "";
int LAUNCHER_ARGC = 0; int LAUNCHER_ARGC = 0;
char **LAUNCHER_ARGV = nullptr; char **LAUNCHER_ARGV = nullptr;
std::unique_ptr<std::vector<Option>> LAUNCHER_OPTIONS; std::unique_ptr<std::vector<Option>> LAUNCHER_OPTIONS;
games::Game *GAME_INSTANCE = nullptr;
std::mutex CARD_OVERRIDES_LOCK; std::mutex CARD_OVERRIDES_LOCK;
std::string CARD_OVERRIDES[2]; std::string CARD_OVERRIDES[2];
@@ -524,22 +526,25 @@ int main_implementation(int argc, char *argv[]) {
games::iidx::FLIP_CAMS = true; games::iidx::FLIP_CAMS = true;
} }
// IIDX CONNECT_CAMERA logic here for cases where user is running without -iidx module // Resolve the IIDX camera policy here so it also applies without the -iidx module.
// for now, we assume that user may be running on a cab const auto &cab_camera_access = options[launcher::Options::IIDXCabCamAccess];
// (games::iidx::DISABLE_CAMS starts out as false unless user overrides) if (cab_camera_access.is_active()) {
// we will check again in IIDX module with a different default const auto value = cab_camera_access.value_text();
assert(!games::iidx::DISABLE_CAMS.has_value()); if (value == "off") {
if (options[launcher::Options::IIDXDisableCameras].value_bool()) { games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::off;
games::iidx::DISABLE_CAMS = true; } else if (value == "on") {
games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::on;
} else if (value == "legacy") {
games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::legacy;
} }
if (options[launcher::Options::IIDXCabCamAccess].is_active() && }
options[launcher::Options::IIDXCabCamAccess].value_text() == "off") {
games::iidx::DISABLE_CAMS = true; if (options[launcher::Options::IIDXDisableCameras].value_bool()) {
games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::off;
} }
if (options[launcher::Options::IIDXCamHook].value_bool()) { if (options[launcher::Options::IIDXCamHook].value_bool()) {
games::iidx::TDJ_CAMERA = true; games::iidx::TDJ_CAMERA = true;
// Disable legacy behaviour to avoid conflict games::iidx::CAB_CAMERA_ACCESS = games::iidx::cab_camera_access_mode::off;
games::iidx::DISABLE_CAMS = true;
} }
// CONNECT_CAMERA env var will be set once logging is enabled // CONNECT_CAMERA env var will be set once logging is enabled
@@ -644,6 +649,9 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::LoadGitaDoraModule].value_bool()) { if (options[launcher::Options::LoadGitaDoraModule].value_bool()) {
attach_gitadora = true; attach_gitadora = true;
} }
if (options[launcher::Options::GitaDoraDisableFrameLimiter].value_bool()) {
games::gitadora::DISABLE_FRAME_LIMITER = true;
}
if (options[launcher::Options::GitaDoraCabinetType].is_active()) { if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32(); games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32();
} }
@@ -1714,14 +1722,13 @@ int main_implementation(int argc, char *argv[]) {
GRAPHICS_FS_ORIENTATION_SWAP = true; GRAPHICS_FS_ORIENTATION_SWAP = true;
} }
// apply an explicit off outside of the -iidx module so it also works on cabinets.
// for cab usage - set environment variables (outside of -iidx module) if (games::iidx::CAB_CAMERA_ACCESS == games::iidx::cab_camera_access_mode::off &&
if (games::iidx::DISABLE_CAMS.has_value() &&
games::iidx::DISABLE_CAMS.value() &&
!cfg::CONFIGURATOR_STANDALONE) { !cfg::CONFIGURATOR_STANDALONE) {
log_misc("launcher::iidx", "CONNECT_CAMERA env var set to 0"); log_misc("launcher::iidx", "CONNECT_CAMERA env var set to 0");
SetEnvironmentVariable("CONNECT_CAMERA", "0"); SetEnvironmentVariable("CONNECT_CAMERA", "0");
} }
if (games::iidx::SOUND_OUTPUT_DEVICE.has_value() && if (games::iidx::SOUND_OUTPUT_DEVICE.has_value() &&
games::iidx::SOUND_OUTPUT_DEVICE.value() != "auto" && games::iidx::SOUND_OUTPUT_DEVICE.value() != "auto" &&
!cfg::CONFIGURATOR_STANDALONE) { !cfg::CONFIGURATOR_STANDALONE) {
@@ -2382,6 +2389,7 @@ int main_implementation(int argc, char *argv[]) {
avs::core::HEAP_SIZE = 536870912; // 512MB avs::core::HEAP_SIZE = 536870912; // 512MB
games.push_back(new games::pc::PCGame()); games.push_back(new games::pc::PCGame());
} }
GAME_INSTANCE = games.back();
// apply user heap size, if defined // apply user heap size, if defined
if (user_heap_size > 0) { if (user_heap_size > 0) {
@@ -2531,11 +2539,18 @@ int main_implementation(int argc, char *argv[]) {
// load game // load game
avs::game::load_dll(); avs::game::load_dll();
// devel attach
hooks::devel::attach();
// attach games // attach games
for (auto game : games) { for (auto game : games) {
game->attach(); game->attach();
} }
// wine fixes
hooks::wmisc::apply_performance_hacks(avs::game::DLL_INSTANCE, avs::game::MODEL, avs::game::EXT);
hooks::soundbank::init(avs::game::DLL_INSTANCE, avs::game::MODEL, avs::game::EXT);
#if SPICE64 && !SPICE_XP #if SPICE64 && !SPICE_XP
if (!cfg::CONFIGURATOR_STANDALONE) { if (!cfg::CONFIGURATOR_STANDALONE) {
if (games::iidx::TDJ_CAMERA) { if (games::iidx::TDJ_CAMERA) {
@@ -2747,13 +2762,15 @@ int main_implementation(int argc, char *argv[]) {
game->post_attach(); game->post_attach();
} }
// game start // cleanup procedure
log_info("launcher", "calling game entry"); launcher::on_shutdown = [&]() {
avs::game::entry_main();
// clear presence // clear presence
richpresence::shutdown(); richpresence::shutdown();
// cleanup wine fixes
hooks::soundbank::deinit(avs::game::DLL_INSTANCE);
// detach games // detach games
for (auto game : games) { for (auto game : games) {
game->detach(); game->detach();
@@ -2842,6 +2859,14 @@ int main_implementation(int argc, char *argv[]) {
#if SPICE64 && !SPICE_XP #if SPICE64 && !SPICE_XP
games::iidx::camera_release(); games::iidx::camera_release();
#endif #endif
};
// devel start
hooks::devel::entry_main();
// game start
log_info("launcher", "calling game entry");
avs::game::entry_main();
// shutdown // shutdown
log_warning("launcher", "end"); log_warning("launcher", "end");
+2
View File
@@ -7,6 +7,7 @@
#include <windows.h> #include <windows.h>
#include "cfg/option.h" #include "cfg/option.h"
#include "games/game.h"
namespace rawinput { namespace rawinput {
class RawInputManager; class RawInputManager;
@@ -21,6 +22,7 @@ extern std::string LOG_FILE_PATH;
extern int LAUNCHER_ARGC; extern int LAUNCHER_ARGC;
extern char **LAUNCHER_ARGV; extern char **LAUNCHER_ARGV;
extern std::unique_ptr<std::vector<Option>> LAUNCHER_OPTIONS; extern std::unique_ptr<std::vector<Option>> LAUNCHER_OPTIONS;
extern games::Game *GAME_INSTANCE;
extern std::unique_ptr<api::Controller> API_CONTROLLER; extern std::unique_ptr<api::Controller> API_CONTROLLER;
extern std::unique_ptr<rawinput::RawInputManager> RI_MGR; extern std::unique_ptr<rawinput::RawInputManager> RI_MGR;
+26 -8
View File
@@ -640,17 +640,21 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
}, },
{ {
// IIDXCabCamAccess // IIDXCabCamAccess
.title = "IIDX Use Official AC Cams", .title = "IIDX Official AC Camera Access",
.name = "iidxcabcams", .name = "iidxcabcams",
.desc = "For IIDX25+, allow direct access to cameras from real arcade cabinets. " .desc = "Controls how the game accesses USB cameras for IIDX 25+.\n\n"
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto.", "auto (default): use [off] when the IIDX module is enabled; otherwise, use [on].\n\n"
"on: game discovers and directly accesses cameras; requires official cameras on correct USB ports.\n\n"
"legacy: for IIDX 25/26 only; allow camera access with emulated discovery.\n\n"
"off: prevent game from accessing USB cameras.",
.type = OptionType::Enum, .type = OptionType::Enum,
.game_name = "Beatmania IIDX", .game_name = "Beatmania IIDX",
.category = "Cab Peripherals", .category = "Cab Peripherals",
.elements = { .elements = {
{"auto", ""}, {"auto", ""},
{"on", ""},
{"legacy", ""},
{"off", ""}, {"off", ""},
{"on", ""}
}, },
}, },
{ {
@@ -977,7 +981,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.display_name = "sdvxsubredraw", .display_name = "sdvxsubredraw",
.aliases= "sdvxsubredraw", .aliases= "sdvxsubredraw",
.desc = "Check if submonitor in fullscreen mode doesn't update every frame; " .desc = "Check if submonitor in fullscreen mode doesn't update every frame; "
"this option forces subscreen to redraw every frame.", "this option presents the subscreen when the game does not.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Sound Voltex", .game_name = "Sound Voltex",
.category = "Advanced Game Options", .category = "Advanced Game Options",
@@ -1171,7 +1175,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.title = "Pop'n Music PikaPika Subscreen Force Redraw", .title = "Pop'n Music PikaPika Subscreen Force Redraw",
.name = "popnsubredraw", .name = "popnsubredraw",
.desc = "Check if submonitor in fullscreen mode appears stuck; " .desc = "Check if submonitor in fullscreen mode appears stuck; "
"this option forces subscreen to redraw every frame.", "this option presents the subscreen when the game does not.",
.type = OptionType::Bool, .type = OptionType::Bool,
.game_name = "Pop'n Music", .game_name = "Pop'n Music",
.category = "Advanced Game Options", .category = "Advanced Game Options",
@@ -1202,6 +1206,19 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Game Options", .category = "Game Options",
.quick_setting_category = "Game", .quick_setting_category = "Game",
}, },
{
.title = "GitaDora Disable Frame Limiter (EXPERIMENTAL)",
.name = "gdnoframelimiter",
.desc = "Only for GITADORA to GALAXY WAVE; not for XG series or Arena Model.\n\n"
"Game engine's built-in frame limiter can result in unstable frame pacing "
"(around 56-58 FPS) on modern Windows. Turning this on disables that and "
"forces the game to rely on V-Sync instead, resulting in a stable FPS.\n\n"
"Ensure your monitors are set to 60Hz.",
.type = OptionType::Bool,
.game_name = "GitaDora",
.category = "Game Options",
.quick_setting_category = "Game",
},
{ {
.title = "GitaDora Cabinet Type", .title = "GitaDora Cabinet Type",
.name = "gdcabtype", .name = "gdcabtype",
@@ -2091,7 +2108,9 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.category = "Audio Conversion", .category = "Audio Conversion",
.elements = { .elements = {
{"asio", "ASIO"}, {"asio", "ASIO"},
{"waveout", "broken, do not use"} {"waveout", "broken, do not use"},
{"pipewire", "Pipewire (Linux)"},
{"none", "None"}
}, },
}, },
{ {
@@ -2166,7 +2185,6 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
{"25", "+25 dB"}, {"25", "+25 dB"},
{"30", "+30 dB"}, {"30", "+30 dB"},
}, },
.quick_setting_category = "Audio",
}, },
{ {
// AudioResample // AudioResample
+1
View File
@@ -114,6 +114,7 @@ namespace launcher {
LoadHelloPopnMusicModule, LoadHelloPopnMusicModule,
LoadGitaDoraModule, LoadGitaDoraModule,
GitaDoraTwoChannelAudio, GitaDoraTwoChannelAudio,
GitaDoraDisableFrameLimiter,
GitaDoraCabinetType, GitaDoraCabinetType,
GitaDoraLefty, GitaDoraLefty,
GitaDoraWailHold, GitaDoraWailHold,
+10 -3
View File
@@ -19,6 +19,8 @@
namespace launcher { namespace launcher {
std::function<void()> on_shutdown = []() {};
void stop_subsystems() { void stop_subsystems() {
// note that it is possible for stop_subsystems to be called multiple times // note that it is possible for stop_subsystems to be called multiple times
// (e.g., crashing, and then closing the window) // (e.g., crashing, and then closing the window)
@@ -37,9 +39,6 @@ namespace launcher {
// before shutting down logger, dump any deferred log messages // before shutting down logger, dump any deferred log messages
deferredlogs::dump_to_logger(); deferredlogs::dump_to_logger();
// flush/stop logger
logger::stop();
// stop ea server // stop ea server
easrv_shutdown(); easrv_shutdown();
@@ -58,6 +57,14 @@ namespace launcher {
// unload nvapi and free library (if loaded) // unload nvapi and free library (if loaded)
nvapi::unload(); nvapi::unload();
// dynamic main cleanup _BUG: not thread safe, required?
on_shutdown();
on_shutdown = []() {};
// flush/stop logger
logger::stop();
} }
void kill(UINT exit_code) { void kill(UINT exit_code) {
+2
View File
@@ -2,8 +2,10 @@
#include <windows.h> #include <windows.h>
#include <stdlib.h> #include <stdlib.h>
#include <functional>
namespace launcher { namespace launcher {
extern std::function<void()> on_shutdown;
void stop_subsystems(); void stop_subsystems();
void kill(UINT exit_code = EXIT_FAILURE); void kill(UINT exit_code = EXIT_FAILURE);
void shutdown(UINT exit_code = EXIT_SUCCESS); void shutdown(UINT exit_code = EXIT_SUCCESS);
+91
View File
@@ -0,0 +1,91 @@
// enable touch functions - set version to windows 7
// mingw otherwise doesn't load touch stuff
#define _WIN32_WINNT 0x0601
#include "wintouchemu.h"
#include "util/detour.h"
#include "util/logging.h"
#define TOUCH_SIMULATE_FAT_FINGERS 0
#define TOUCH_DEBUG_VERBOSE 0
#if TOUCH_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
namespace nativetouchhook {
static decltype(GetTouchInputInfo) *GetTouchInputInfo_orig = nullptr;
static void strip_contact_size(PTOUCHINPUT point) {
#if TOUCH_SIMULATE_FAT_FINGERS
point->dwMask |= 0x004;
point->cxContact = 80 * 100;
point->cyContact = 60 * 100;
#endif
// most monitors do not set TOUCHEVENTFMASK_CONTACTAREA, but for
// monitors that do set it, IIDX can get very confused (SDVX is not
// affected)
//
// while the test menu and the touch "glow" seem to work properly,
// interacting with subscreen menu items or entering PIN becomes
// very unpredictable
//
// to fix this, simply remove the contact area width and height
//
// note: test menu > I/O > touch test gives 5 numbers:
// n: x, y, w, h
// where
// n is the nth touch input since boot
// x, y are coordinates (center of finger)
// w, h are contact width and height
//
// when TOUCHEVENTFMASK_CONTACTAREA is not set, w/h will
// automatically be seen as 1x1, which works perfectly fine
log_debug(
"touch::native",
"[{}, {}] dwMask = 0x{:x}, cxContact = {}, cyContact = {}",
point->x / 100,
point->y / 100,
point->dwMask,
point->cxContact,
point->cyContact);
point->dwMask &= ~(0x004ul); // clear TOUCHEVENTFMASK_CONTACTAREA
point->cxContact = 0;
point->cyContact = 0;
}
static BOOL WINAPI GetTouchInputInfoHook(
HTOUCHINPUT hTouchInput, UINT cInputs, PTOUCHINPUT pInputs, int cbSize) {
// call the original fist
const auto result = GetTouchInputInfo_orig(hTouchInput, cInputs, pInputs, cbSize);
if (result == 0) {
return result;
}
for (size_t i = 0; i < cInputs; i++) {
PTOUCHINPUT point = &pInputs[i];
strip_contact_size(point);
}
return result;
}
void hook(HMODULE module) {
GetTouchInputInfo_orig = detour::iat_try("GetTouchInputInfo", GetTouchInputInfoHook, module);
if (GetTouchInputInfo_orig != nullptr) {
log_misc("touch::native", "GetTouchInputInfo hooked");
}
}
}
+6
View File
@@ -0,0 +1,6 @@
#include <windows.h>
namespace nativetouchhook {
void hook(HMODULE module);
}
+2 -1
View File
@@ -660,7 +660,8 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
if (logical_max > field_max) { if (logical_max > field_max) {
log_info( log_info(
"rawinput", "rawinput",
"value cap {} LogicalMax exceeds bit width, fixing it up: {} -> {}", "device {}: value cap {} LogicalMax exceeds bit width, fixing it up: {} -> {}",
new_device.name,
value_cap_num, value_cap_num,
value_caps.LogicalMax, value_caps.LogicalMax,
field_max field_max
+2 -3
View File
@@ -394,11 +394,10 @@ namespace nativetouch::inject {
// preserve native registration and attach mouse injection to the touch window // preserve native registration and attach mouse injection to the touch window
static BOOL WINAPI RegisterTouchWindowHook(HWND window, ULONG flags) { static BOOL WINAPI RegisterTouchWindowHook(HWND window, ULONG flags) {
// for TDJ in windowed subscreen mode, the main game window is // TDJ handles touches on the main window, including contacts that the
// target for delivering touch messages, not the subscreen // dedicated subscreen forwards there
if (GRAPHICS_IIDX_WSUB && window != TDJ_SUBSCREEN_WINDOW) { if (GRAPHICS_IIDX_WSUB && window != TDJ_SUBSCREEN_WINDOW) {
touch_delivery_window.store(window, std::memory_order_release); touch_delivery_window.store(window, std::memory_order_release);
return TRUE;
} }
// call original // call original
+57
View File
@@ -0,0 +1,57 @@
// set version to Windows 8 to enable Windows 8 touch functions
#define _WIN32_WINNT 0x0602
#include "touch_indicators.h"
#include "util/libutils.h"
#include "util/logging.h"
static HINSTANCE USER32_INSTANCE = nullptr;
typedef BOOL (WINAPI *SetWindowFeedbackSetting_t)(HWND, FEEDBACK_TYPE, DWORD, UINT32, const VOID *);
static SetWindowFeedbackSetting_t pSetWindowFeedbackSetting = nullptr;
void disable_touch_indicators(HWND hwnd) {
if (USER32_INSTANCE == nullptr) {
USER32_INSTANCE = libutils::load_library("user32.dll");
}
if (USER32_INSTANCE == nullptr) {
return;
}
if (pSetWindowFeedbackSetting == nullptr) {
pSetWindowFeedbackSetting = libutils::try_proc<SetWindowFeedbackSetting_t>(
USER32_INSTANCE, "SetWindowFeedbackSetting");
}
if (pSetWindowFeedbackSetting == nullptr) {
return;
}
log_info("touch_indicators", "disable visual feedback for touch events for HWND={}", fmt::ptr(hwnd));
BOOL enabled = FALSE;
pSetWindowFeedbackSetting(
hwnd,
FEEDBACK_TOUCH_CONTACTVISUALIZATION,
0, sizeof(enabled), &enabled);
pSetWindowFeedbackSetting(
hwnd,
FEEDBACK_TOUCH_TAP,
0, sizeof(enabled), &enabled);
pSetWindowFeedbackSetting(
hwnd,
FEEDBACK_TOUCH_DOUBLETAP,
0, sizeof(enabled), &enabled);
pSetWindowFeedbackSetting(
hwnd,
FEEDBACK_TOUCH_PRESSANDHOLD,
0, sizeof(enabled), &enabled);
pSetWindowFeedbackSetting(
hwnd,
FEEDBACK_TOUCH_RIGHTTAP,
0, sizeof(enabled), &enabled);
pSetWindowFeedbackSetting(
hwnd,
FEEDBACK_GESTURE_PRESSANDTAP,
0, sizeof(enabled), &enabled);
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <windows.h>
void disable_touch_indicators(HWND hwnd);
+157 -122
View File
@@ -9,187 +9,145 @@
#include "util/memutils.h" #include "util/memutils.h"
#include "util/utils.h" #include "util/utils.h"
intptr_t find_pattern(std::vector<uint8_t> &data, intptr_t base, const uint8_t *pattern, intptr_t find_pattern(std::span<const unsigned char> data, intptr_t base, const uint8_t *pattern,
const char *mask, intptr_t offset, intptr_t usage) const char *mask, intptr_t offset, intptr_t usage, bool reverse)
{ {
return find_pattern_from(data, base, pattern, mask, offset, usage, 0, reverse);
}
// build pattern intptr_t find_pattern(const std::vector<uint8_t> &data, intptr_t base, const uint8_t *pattern,
std::vector<std::pair<uint8_t, bool>> pattern_vector; const char *mask, intptr_t offset, intptr_t usage, bool reverse)
size_t mask_size = strlen(mask); {
for (size_t i = 0; i < mask_size; i++) { return find_pattern_from(std::span<const unsigned char>(data), base, pattern, mask, offset, usage, 0, reverse);
pattern_vector.emplace_back(pattern[i], mask[i] == 'X');
}
// the scan loop
auto data_begin = data.begin();
auto cur_usage = 0;
while (true) {
// search for the pattern
auto search_result = std::search(data_begin, data.end(), pattern_vector.begin(), pattern_vector.end(),
[&](uint8_t c, std::pair<uint8_t, bool> pat) {
return (!pat.second) || c == pat.first;
});
// check for a match
if (search_result != data.end()) {
// return the result if we hit the usage count
if (cur_usage == usage) {
return (std::distance(data.begin(), search_result) + base) + offset;
}
// increment the found count
++cur_usage;
data_begin = ++search_result;
} else {
break;
}
}
return 0;
} }
intptr_t find_pattern(HMODULE module, const uint8_t *pattern, const char *mask, intptr_t find_pattern(HMODULE module, const uint8_t *pattern, const char *mask,
intptr_t offset, intptr_t result_usage) intptr_t offset, intptr_t result_usage, bool reverse)
{ {
// get module information return find_pattern_from(module, pattern, mask, offset, result_usage, 0, reverse);
MODULEINFO module_info {};
if (!GetModuleInformation(GetCurrentProcess(), module, &module_info, sizeof(module_info))) {
return 0;
}
auto size = static_cast<size_t>(module_info.SizeOfImage);
try {
// copy data
std::vector<uint8_t> data(size);
memcpy(data.data(), module_info.lpBaseOfDll, size);
// find pattern
return find_pattern(
data,
reinterpret_cast<intptr_t>(module_info.lpBaseOfDll),
pattern,
mask,
offset,
result_usage);
} catch (const std::bad_alloc &e) {
log_warning("sigscan", "failed to allocate buffer of size {} for image data", size);
return false;
}
} }
intptr_t find_pattern(HMODULE module, const std::string &pattern, const char *mask, intptr_t find_pattern(HMODULE module, const std::string &pattern, const char *mask,
intptr_t offset, intptr_t result_usage) intptr_t offset, intptr_t result_usage, bool reverse)
{ {
std::string pattern_str(pattern); return find_pattern_from(module, pattern, mask, offset, result_usage, 0, reverse);
auto pattern_bin = std::make_unique<uint8_t[]>(pattern.length() / 2);
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
log_warning("sigscan", "hex2bin failed");
return false;
}
return find_pattern(module, pattern_bin.get(), mask, offset, result_usage);
} }
/// ///
intptr_t find_pattern_from(std::vector<uint8_t> &data, intptr_t base, const uint8_t *pattern, intptr_t find_pattern_from(std::span<const unsigned char> data, intptr_t base, const uint8_t *pattern,
const char *mask, intptr_t offset, intptr_t usage, intptr_t start_from) const char *mask, intptr_t offset, intptr_t usage, intptr_t start_from, bool reverse)
{ {
// build pattern // build pattern
std::vector<std::pair<uint8_t, bool>> pattern_vector; std::vector<std::pair<uint8_t, bool>> pattern_vector;
size_t mask_size = strlen(mask); size_t mask_size = strlen(mask);
for (size_t i = 0; i < mask_size; i++) { for (size_t i = 0; i < mask_size; i++) {
pattern_vector.emplace_back(pattern[i], mask[i] == 'X'); pattern_vector.emplace_back(pattern[i], mask[i] == 'X');
} }
if (reverse) std::reverse(pattern_vector.begin(), pattern_vector.end());
// the scan loop // the scan loop
auto data_begin = data.begin(); auto on_search = [&](uint8_t c, std::pair<uint8_t, bool> pat)
std::advance(data_begin, start_from); {
auto cur_usage = 0;
while (true) {
// search for the pattern
auto search_result = std::search(data_begin, data.end(), pattern_vector.begin(), pattern_vector.end(),
[&](uint8_t c, std::pair<uint8_t, bool> pat) {
return (!pat.second) || c == pat.first; return (!pat.second) || c == pat.first;
}); };
auto cur_usage = 0;
if (!reverse)
{
auto data_begin = data.begin() + start_from;
while (true)
{
// search for the pattern
auto search_result = std::search(data_begin, data.end(), pattern_vector.begin(), pattern_vector.end(), on_search);
// check for a match // check for a match
if (search_result != data.end()) { if (search_result != data.end())
{
// return the result if we hit the usage count // return the result if we hit the usage count
if (cur_usage == usage) { if (cur_usage == usage)
{
return (std::distance(data.begin(), search_result) + base) + offset; return (std::distance(data.begin(), search_result) + base) + offset;
} }
// increment the found count // increment the found count
++cur_usage; ++cur_usage;
data_begin = ++search_result; data_begin = ++search_result;
} else {
break;
} }
else break;
}
}
else
{
auto data_begin = std::make_reverse_iterator(start_from ? data.begin() + start_from + 1 : data.end());
while(true)
{
// search for the pattern
auto search_result = std::search(data_begin, data.rend(), pattern_vector.begin(), pattern_vector.end(), on_search);
// check for a match
if (search_result != data.rend())
{
// return the result if we hit the usage count
if (cur_usage == usage)
{
return (std::distance(data.begin(), search_result.base()) - pattern_vector.size() + base) + offset;
}
// increment the found count
++cur_usage;
data_begin = ++search_result;
}
else break;
}
} }
return 0; return 0;
} }
intptr_t find_pattern_from(HMODULE module, const uint8_t *pattern, const char *mask, intptr_t find_pattern_from(const std::vector<uint8_t> &data, intptr_t base, const uint8_t *pattern,
intptr_t offset, intptr_t result_usage, intptr_t start_from) const char *mask, intptr_t offset, intptr_t usage, intptr_t start_from, bool reverse)
{ {
// get module information return find_pattern_from(std::span<const unsigned char>(data), base, pattern, mask, offset, usage, start_from, reverse);
MODULEINFO module_info {}; }
if (!GetModuleInformation(GetCurrentProcess(), module, &module_info, sizeof(module_info))) {
return 0;
}
auto size = static_cast<size_t>(module_info.SizeOfImage); intptr_t find_pattern_from(HMODULE module, const uint8_t *pattern, const char *mask,
intptr_t offset, intptr_t result_usage, intptr_t start_from, bool reverse)
try { {
intptr_t base;
// copy data auto data = get_module_data(module, &base);
std::vector<uint8_t> data(size);
memcpy(data.data(), module_info.lpBaseOfDll, size);
// find pattern
return find_pattern_from( return find_pattern_from(
data, data,
reinterpret_cast<intptr_t>(module_info.lpBaseOfDll), base,
pattern, pattern,
mask, mask,
offset, offset,
result_usage, result_usage,
start_from); start_from,
} catch (const std::bad_alloc &e) { reverse);
log_warning("sigscan", "failed to allocate buffer of size {} for image data", size);
return false;
}
} }
intptr_t find_pattern_from(HMODULE module, const std::string &pattern, const char *mask, intptr_t find_pattern_from(HMODULE module, const std::string &pattern, const char *mask,
intptr_t offset, intptr_t result_usage, intptr_t start_from) intptr_t offset, intptr_t result_usage, intptr_t start_from, bool reverse)
{ {
std::string pattern_str(pattern);
auto pattern_bin = std::make_unique<uint8_t[]>(pattern.length() / 2); auto pattern_bin = std::make_unique<uint8_t[]>(pattern.length() / 2);
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) { if (!hex2bin(pattern.c_str(), pattern_bin.get())) {
log_warning("sigscan", "hex2bin failed"); log_warning("sigscan", "hex2bin failed");
return false; return false;
} }
return find_pattern_from(module, pattern_bin.get(), mask, offset, result_usage, start_from); return find_pattern_from(module, pattern_bin.get(), mask, offset, result_usage, start_from, reverse);
} }
intptr_t replace_pattern(HMODULE module, const uint8_t *pattern, const char *mask, intptr_t offset, intptr_t replace_pattern_from(HMODULE module, const uint8_t *pattern, const char *mask, intptr_t offset,
intptr_t usage, const uint8_t *replace_data, const char *replace_mask) intptr_t usage, intptr_t start_from, const uint8_t *replace_data, const char *replace_mask, bool reverse)
{ {
// find result // find result
auto result = find_pattern(module, pattern, mask, offset, usage); auto result = find_pattern_from(module, pattern, mask, offset, usage, start_from, reverse);
// check result // check result
if (!result) { if (!result) {
@@ -211,8 +169,8 @@ intptr_t replace_pattern(HMODULE module, const uint8_t *pattern, const char *mas
return result; return result;
} }
intptr_t replace_pattern(HMODULE module, const std::string &signature, intptr_t replace_pattern_from(HMODULE module, const std::string &signature,
const std::string &replacement, intptr_t offset, intptr_t usage) const std::string &replacement, intptr_t offset, intptr_t usage, intptr_t start_from, bool reverse)
{ {
// build pattern // build pattern
std::string pattern_str(signature); std::string pattern_str(signature);
@@ -261,17 +219,31 @@ intptr_t replace_pattern(HMODULE module, const std::string &signature,
} }
// do the replacement // do the replacement
return replace_pattern( return replace_pattern_from(
module, module,
pattern_bin.get(), pattern_bin.get(),
signature_mask.c_str(), signature_mask.c_str(),
offset, offset,
usage, usage,
start_from,
replace_data_bin.get(), replace_data_bin.get(),
replace_mask.c_str() replace_mask.c_str(),
reverse
); );
} }
intptr_t replace_pattern(HMODULE module, const std::string &signature,
const std::string &replacement, intptr_t offset, intptr_t usage, bool reverse)
{
return replace_pattern_from(module, signature, replacement, offset, usage, 0, reverse);
}
intptr_t replace_pattern(HMODULE module, const uint8_t *pattern, const char *mask, intptr_t offset,
intptr_t usage, const uint8_t *replace_data, const char *replace_mask, bool reverse)
{
return replace_pattern_from(module, pattern, mask, offset, usage, 0, replace_data, replace_mask, reverse);
}
bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point) { bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point) {
std::ifstream file(dll_path, std::ios::binary); std::ifstream file(dll_path, std::ios::binary);
if (!file) { if (!file) {
@@ -304,3 +276,66 @@ bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_dat
return true; return true;
} }
intptr_t find_asm_leacstr_from(HMODULE module, reg_t reg, const char *cstr)
{
// get module information
intptr_t base;
auto data = get_module_data(module, &base);
const unsigned char *modbegp = data.data();
const unsigned char *modendp = data.data() + data.size();
// search cstr
const unsigned char *cstr_addr = std::search(
modbegp,
modendp,
reinterpret_cast<const unsigned char *>(cstr),
reinterpret_cast<const unsigned char *>(cstr) + strlen(cstr)
);
if (cstr_addr == modendp)
{
return 0;
}
#if defined(__i386__)
// find LEA REG, [abs32]
if (reg.t != GPR_32) return 0;
unsigned char asmop[6] = { 0x8D, reg.v, 0x00, 0x00, 0x00, 0x00};
int32_t dist32 = reinterpret_cast<int32_t>(cstr_addr);
memcpy(asmop + (sizeof(asmop) - 4), &dist32, 4);
return reinterpret_cast<intptr_t>(std::search(
modbegp,
modendp,
asmop,
asmop + sizeof(asmop)
)) % reinterpret_cast<intptr_t>(modendp);
#elif defined(__x86_64__)
// find LEA REG, [RIP + rel32]
unsigned char rex = reg.t == GPR_EXT ? 0x4C : 0x48;
unsigned char asmop[7] = {rex, 0x8D, reg.v, 0x00, 0x00, 0x00, 0x00};
for (const unsigned char *addr = modbegp; addr < modendp - sizeof(asmop); addr++)
{
auto dist32 = static_cast<int32_t>(reinterpret_cast<intptr_t>(cstr_addr) - reinterpret_cast<intptr_t>(addr + sizeof(asmop)));
memcpy(asmop + (sizeof(asmop) - 4), &dist32, 4);
if (memcmp(addr, asmop, sizeof(asmop)) == 0) return reinterpret_cast<intptr_t>(addr);
}
#endif
return 0;
}
std::span<const unsigned char> get_module_data(HMODULE module, intptr_t *base)
{
// get module information
size_t sz = 0;
MODULEINFO module_info {};
if (GetModuleInformation(GetCurrentProcess(), module, &module_info, sizeof(module_info))) {
sz = static_cast<size_t>(module_info.SizeOfImage);
}
// wrap data
std::span<const unsigned char> data(static_cast<const unsigned char*>(module_info.lpBaseOfDll), sz);
*base = reinterpret_cast<intptr_t>(module_info.lpBaseOfDll);
return data;
}
+119 -10
View File
@@ -5,39 +5,63 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include <filesystem> #include <filesystem>
#include <span>
#include "windows.h" #include "windows.h"
#include "psapi.h" #include "psapi.h"
intptr_t find_pattern( intptr_t find_pattern(
std::vector<unsigned char> &data, std::span<const unsigned char> data,
intptr_t base, intptr_t base,
const unsigned char *pattern, const unsigned char *pattern,
const char *mask, const char *mask,
intptr_t offset, intptr_t offset,
intptr_t usage); intptr_t usage,
bool reverse = false);
intptr_t find_pattern(
const std::vector<unsigned char> &data,
intptr_t base,
const unsigned char *pattern,
const char *mask,
intptr_t offset,
intptr_t usage,
bool reverse = false);
intptr_t find_pattern( intptr_t find_pattern(
HMODULE module, HMODULE module,
const unsigned char *pattern, const unsigned char *pattern,
const char *mask, const char *mask,
intptr_t offset, intptr_t offset,
intptr_t usage); intptr_t usage,
bool reverse = false);
intptr_t find_pattern( intptr_t find_pattern(
HMODULE module, HMODULE module,
const std::string &pattern, const std::string &pattern,
const char *mask, const char *mask,
intptr_t offset, intptr_t offset,
intptr_t result_usage); intptr_t result_usage,
bool reverse = false);
intptr_t find_pattern_from( intptr_t find_pattern_from(
std::vector<unsigned char> &data, std::span<const unsigned char> data,
intptr_t base, intptr_t base,
const unsigned char *pattern, const unsigned char *pattern,
const char *mask, const char *mask,
intptr_t offset, intptr_t offset,
intptr_t usage, intptr_t usage,
intptr_t start_from); intptr_t start_from,
bool reverse = false);
intptr_t find_pattern_from(
const std::vector<unsigned char> &data,
intptr_t base,
const unsigned char *pattern,
const char *mask,
intptr_t offset,
intptr_t usage,
intptr_t start_from,
bool reverse = false);
intptr_t find_pattern_from( intptr_t find_pattern_from(
HMODULE module, HMODULE module,
@@ -45,7 +69,8 @@ intptr_t find_pattern_from(
const char *mask, const char *mask,
intptr_t offset, intptr_t offset,
intptr_t usage, intptr_t usage,
intptr_t start_from); intptr_t start_from,
bool reverse = false);
intptr_t find_pattern_from( intptr_t find_pattern_from(
HMODULE module, HMODULE module,
@@ -53,7 +78,8 @@ intptr_t find_pattern_from(
const char *mask, const char *mask,
intptr_t offset, intptr_t offset,
intptr_t result_usage, intptr_t result_usage,
intptr_t start_from); intptr_t start_from,
bool reverse = false);
intptr_t replace_pattern( intptr_t replace_pattern(
HMODULE module, HMODULE module,
@@ -62,13 +88,96 @@ intptr_t replace_pattern(
intptr_t offset, intptr_t offset,
intptr_t usage, intptr_t usage,
const unsigned char *replace_data, const unsigned char *replace_data,
const char *replace_mask); const char *replace_mask,
bool reverse = false);
intptr_t replace_pattern( intptr_t replace_pattern(
HMODULE module, HMODULE module,
const std::string &signature, const std::string &signature,
const std::string &replacement, const std::string &replacement,
intptr_t offset, intptr_t offset,
intptr_t usage); intptr_t usage,
bool reverse = false);
intptr_t replace_pattern_from(
HMODULE module,
const unsigned char *pattern,
const char *mask,
intptr_t offset,
intptr_t usage,
intptr_t start_from,
const unsigned char *replace_data,
const char *replace_mask,
bool reverse = false);
intptr_t replace_pattern_from(
HMODULE module,
const std::string &signature,
const std::string &replacement,
intptr_t offset,
intptr_t usage,
intptr_t start_from,
bool reverse = false);
bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point); bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point);
union reg_t{
struct{
unsigned char t;
unsigned char v;
};
unsigned short s;
constexpr reg_t(unsigned char t, unsigned char v) : s(v << 8 | t) {}
constexpr reg_t(unsigned short s) : s(s) {}
};
enum REGTYPE : unsigned char
{
GPR_32,
GPR_64,
GPR_EXT
};
enum REG : unsigned short
{
EAX = reg_t(GPR_32, 0x05).s,
ECX = reg_t(GPR_32, 0x0D).s,
EDX = reg_t(GPR_32, 0x15).s,
EBX = reg_t(GPR_32, 0x1D).s,
ESP = reg_t(GPR_32, 0x25).s,
EBP = reg_t(GPR_32, 0x2D).s,
ESI = reg_t(GPR_32, 0x35).s,
EDI = reg_t(GPR_32, 0x3D).s,
RAX = reg_t(GPR_64, 0x05).s,
RCX = reg_t(GPR_64, 0x0D).s,
RDX = reg_t(GPR_64, 0x15).s,
RBX = reg_t(GPR_64, 0x1D).s,
RSP = reg_t(GPR_64, 0x25).s,
RBP = reg_t(GPR_64, 0x2D).s,
RSI = reg_t(GPR_64, 0x35).s,
RDI = reg_t(GPR_64, 0x3D).s,
R8 = reg_t(GPR_EXT, 0x05).s,
R9 = reg_t(GPR_EXT, 0x0D).s,
R10 = reg_t(GPR_EXT, 0x15).s,
R11 = reg_t(GPR_EXT, 0x1D).s,
R12 = reg_t(GPR_EXT, 0x25).s,
R13 = reg_t(GPR_EXT, 0x2D).s,
R14 = reg_t(GPR_EXT, 0x35).s,
R15 = reg_t(GPR_EXT, 0x3D).s
};
intptr_t find_asm_leacstr_from(
HMODULE module,
reg_t reg,
const char *cstr);
std::span<const unsigned char> get_module_data(HMODULE hmodule, intptr_t *base);
template <typename... Funcs>
intptr_t patch_pipeline(HMODULE module, Funcs&&... cbs) {
intptr_t base;
if(!get_module_data(module, &base).size()) return 0;
intptr_t addr = base; //_REV: needs implementing module updating logic in case of traversing between libraries
((addr && (addr = std::forward<Funcs>(cbs)(module, addr - base))), ...); // stop on first mismatch
return addr;
}
+5
View File
@@ -28,6 +28,11 @@ static const char HEX_LOOKUP_UPPERCASE[16] = {
const char *inet_ntop(short af, const void *src, char *dst, DWORD size); const char *inet_ntop(short af, const void *src, char *dst, DWORD size);
static inline constexpr unsigned int to_hash(const char *str, int h = 0)
{
return !str[h] ? 5381 : (to_hash(str, h + 1) * 33) ^ str[h];
}
// make a window's native title bar follow the Windows dark/light app theme. // make a window's native title bar follow the Windows dark/light app theme.
// pass force_dark=true to always use the dark caption regardless of the system theme. // pass force_dark=true to always use the dark caption regardless of the system theme.
void set_window_dark_titlebar(HWND hWnd, bool force_dark = false); void set_window_dark_titlebar(HWND hWnd, bool force_dark = false);