Skip to content

Fall back to DLL presence for vcruntime detection - #239

Merged
frankea merged 2 commits into
frankea:mainfrom
slevin-7:vcruntime-dll-fallback
Sep 1, 2026
Merged

Fall back to DLL presence for vcruntime detection#239
frankea merged 2 commits into
frankea:mainfrom
slevin-7:vcruntime-dll-fallback

Conversation

@slevin-7

@slevin-7 slevin-7 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Third of the three changes agreed in #233.

The winetricks.log entry for a verb is only written once the verb's installer exits, and the vc_redist installer is known to hang under wine after installing successfully. That leaves the runtime fully in place while verb-based detection keeps reporting "Not Installed": the state my bottle was in.

As discussed:

  • scoped to the vcruntime definition
  • the filesystem is only probed when the verbs are missing from the log; a bottle whose log already says installed is never touched
  • both msvcp140.dll and vcruntime140.dll must be present in system32 or syswow64 (same directory)
  • the result is reported with heuristic confidence, so the UI can distinguish it from log-backed detection

The WhiskyKit tests include the regression case from the issue (DLLs present, log entry missing → detected as installed) plus the scoping and DLL-presence variants.

Includes a CHANGELOG entry.

@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 30.43478% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
Whisky/Utils/DependencyManager.swift 0.00% 16 Missing ⚠️

📢 Thoughts on this report? Let us know!

@frankea frankea left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scoping and gating are exactly as agreed, the tests are good, and the app-target change compiles and reads cleanly. The premise is the problem, and I only found it by probing real prefixes.

Wine ships msvcp140.dll and vcruntime140.dll as builtin PE DLLs and wineboot copies them into every prefix. A fresh prefix booted by the stock engine, with winetricks never run, has all four files in system32 and syswow64 carrying the "Wine builtin DLL" stub. Running your detectsInstallation against a bottle in that state returns true, so on the merged tree every bottle whose log lacks vcrun2019 flips to Installed (heuristic), and DependencyConfigSection hides the Install button for installed dependencies. Users would lose the ability to install the runtime from the panel, and the post-install verification would report success regardless of outcome. The unit tests pass because they create empty files with those names, which is the ambiguous case.

It gets more subtle: on a bottle here where a real redist did run (via a game installer), vcruntime140.dll and mfc140.dll are Microsoft's, but msvcp140.dll is still the Wine builtin. winetricks documents that setup refuses to replace it. So "non-builtin msvcp140" would be wrong in the other direction.

Fix direction: use winetricks' own marker. The vcrun2019 verb sets installed_file1 to system32/mfc140.dll (Libraries/winetricks:13360), which Wine does not ship, is absent in fresh prefixes, and is present only after the x64 redist really ran. Probe for that (and mfc140u.dll if you want to match the verb's second check) instead of the two CRT DLLs. If you would rather keep a DLL-signature approach, rejecting files whose DOS stub contains "Wine builtin DLL" or "Wine placeholder DLL" also works, but the marker is simpler and matches what winetricks itself trusts.

When you push the rework, rebase onto main first: #237 landed a changelog entry at the same spot as yours, keep both. Tests should cover the fresh-prefix case explicitly (builtin CRT DLLs present, no mfc140.dll, must report not installed).

…e detection

The winetricks.log entry for a verb is only written once the verb's
installer exits, and the vc_redist installer is known to hang under
wine after installing successfully. The runtime can therefore be fully
in place while verb-based detection keeps reporting "Not Installed"
(frankea#233).

When (and only when) the vcruntime verbs are missing from the log,
probe for winetricks' own installed-file marker for vcrun2019,
system32/mfc140.dll, and report the dependency installed with heuristic
confidence. Wine ships msvcp140.dll and vcruntime140.dll as builtins in
every fresh prefix, so those cannot serve as the signal; mfc140.dll is
only ever placed by a real redist install. Bottles whose log already
says installed are never probed. Scoped to the vcruntime definition.
@slevin-7
slevin-7 force-pushed the vcruntime-dll-fallback branch from 1b1411b to 186ecbb Compare August 29, 2026 18:47
@slevin-7

Copy link
Copy Markdown
Contributor Author

Reworked as suggested — thanks for probing the real prefixes, that builtin detail would have bitten hard.

The probe now keys on winetricks' own installed-file marker for vcrun2019: system32/mfc140.dll (installed_file1, Libraries/winetricks). Wine doesn't ship it as a builtin, so it's absent in fresh prefixes and only appears after the x64 redist actually ran. msvcp140/vcruntime140 are gone from the check entirely, and it's system32-only to mirror winetricks.

Tests updated accordingly; the new key case is the one you described — a fresh prefix carrying the Wine-builtin msvcp140.dll and vcruntime140.dll in system32 and syswow64 must not count as installed. Scoping and gating are unchanged (vcruntime only, probe only when the log lacks the verb).

One drive-by in the CHANGELOG: a stray ||||||| parent of a96a2bb8 conflict marker had ended up committed in the Fixed section on main; since this PR touches the lines right around it, I removed the marker line (the #237 entry below it is kept as-is). Happy to drop that hunk if you'd rather fix it separately.

@slevin-7
slevin-7 requested a review from frankea August 30, 2026 15:34
@dappermint

Copy link
Copy Markdown
Contributor

the marker file is right, the directory is wrong on win64 prefixes.

mfc140.dll is a good marker, i checked it against two real bottles: absent in a clean one, present in one that has had the runtime installed, and strings shows no Wine builtin DLL in it, unlike msvcp140/vcruntime140 which do carry it in a fresh prefix. so the rework is keyed on the right file.

the problem is which directory winetricks actually writes it to. installed_file1 expands W_SYSTEM32_DLLS_WIN, and on a win64 prefix that is C:\windows\syswow64, not system32 (Libraries/winetricks:4674). system32 only on win32. whisky creates win64 bottles by default, so on the common case this probe looks in the directory winetricks does not use.

that also collides with the failure in #233 specifically. vcrun2019 installs the x86 redist first, then the x64 half, and the reported symptom is the installer hanging. a bottle where the x86 half landed and the x64 half hung ends up with syswow64/mfc140.dll and no system32/mfc140.dll, which is exactly the partially-installed state this fallback is meant to catch. testMarkerInSyswow64AloneDoesNotCount currently asserts we should call that one not installed.

on my win64 bottle with the runtime in place, mfc140.dll is in both directories, so accepting either would not cost accuracy here:

17A3732D (runtime installed)  mfc140 sys32:Y syswow64:Y
780CB9F9 (clean)              mfc140 sys32:n syswow64:n

suggest probing either directory and inverting that test, or keying the path off the bottle's arch if you would rather mirror winetricks exactly. if you targeted the x64 half on purpose then say so and i will drop it, but the doc comment reads as though system32 is winetricks' own path, and on win64 it is not.

@frankea

frankea commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Confirming @dappermint's read, and owning the source of the confusion: the system32 pointer came from my review. I cited installed_file1 as "system32/mfc140.dll (Libraries/winetricks:13360)" reading the variable name literally; on a win64 prefix W_SYSTEM32_DLLS_WIN expands to C:\windows\syswow64 (winetricks:4675, system32 only on win32 at :4756). The doc comment inherited my misreading, so that part is on me, not this PR.

Direction, though: keep the system32 probe, and make it deliberate. On win64 the x64 redist writes its mfc140.dll into system32, so the probe as written keys on the x64 half's real payload rather than winetricks' bookkeeping marker, and the bottles above show it matches on full installs (both directories populated). The syswow64-alone state is the one worth being strict about: that is the #233 partial install (x86 landed, x64 hung), and calling it installed would hide the Install button exactly where a rerun (unattended and forced since #237/#244) is the repair path, and would let post-install verification report success after a hung x64 half. On win32 bottles system32 is the x86 location anyway, so the probe stays correct there.

So: no directory change needed, and testMarkerInSyswow64AloneDoesNotCount stays as is. Please just rewrite the doc comment to state the real semantics (x64 payload location on win64, winetricks' own marker directory on win32; syswow64-alone means partial and must stay repairable) so the next reader does not trip over the same variable name I did. With that, this is ready.

The comments claimed system32/mfc140.dll is winetricks' own
installed_file1 marker. That reading only holds on win32 prefixes;
on win64 W_SYSTEM32_DLLS_WIN expands to syswow64, and the system32
file is the x64 redist's payload. The probe and the tests are
unchanged; the docs now state the intent: syswow64-alone is the frankea#233
partial install (x86 landed, x64 hung) and must stay repairable.
@slevin-7

slevin-7 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Doc comments rewritten in 49988e4 — probe and tests untouched. system32 is now described as what it actually is: the x64 redist's payload location on win64 (winetricks' own marker directory only on win32, where the two coincide), and syswow64-alone is called out as the #233 partial state that has to stay repairable. The same misreading had leaked into the CHANGELOG entry and a comment in DependencyManager, so those lines got the same fix.
@dappermint the bottle data was exactly what this needed — good catch on the expansion.

@frankea frankea left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-verified the rework hands-on: simulated the merge on main in a scratch worktree, built the app target, and ran the full kit suite (267 tests in 36 suites pass, VCRuntimeFallbackTests 6/6 isolated as well, swiftlint strict and pinned swiftformat both clean). 49988e4 is doc and changelog wording only; the probe and tests are byte-identical to the previously reviewed state.

The logic checks out end to end: the probe keys on system32/mfc140.dll alone (msvcp140/vcruntime140 survive only in comments), fires only for the vcruntime definition when the verb log actually reports it missing, reports at heuristic confidence, and the syswow64-only partial state from #233 stays "Not Installed" so a repair remains possible.

One housekeeping note: the branch predates the 3.7.0 release commits, so the changelog entry lands inside the 3.7.0 section when merged; I'll move it up to Unreleased on main right after. Nothing for you to do.

Thanks for the fast turnarounds on both reworks and for confirming against the real stuck bottle.

@frankea
frankea merged commit 9bd1e6e into frankea:main Sep 1, 2026
9 checks passed
frankea added a commit that referenced this pull request Sep 1, 2026
The #239 branch predated the 3.7.0 release commits, so the squash merge
placed its entry inside the 3.7.0 section. The change ships in the next
release, not 3.7.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants