From 799e9932d4a8dfe5ef90cfe5505a448e71ea2cc1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Jul 2026 01:54:30 +0000 Subject: [PATCH 1/3] Authored by Claude (Opus 4.8): Add diagnostics for D3D11 texture snapshot save failures under Proton Texture snapshotting fails for some D3D11 games under Proton while working for others on the same system. The save path uses the legacy D3DX11SaveTextureToFileW from d3dx11_43.dll, which under Proton resolves to Wine's built-in d3dx11 whose implementation may be a stub or lack support for block-compressed (BC) DDS output, returning E_NOTIMPL (0x80004001). Add logging (no behavior change) to confirm the root cause from a single run: - util::get_module_path(HMODULE): return a loaded module's on-disk path; get_module_name now delegates to it. - d3dx.rs: log which d3dx11 DLL the loader actually resolved, so a Wine system copy can be distinguished from a game-local real d3dx11_43.dll. - hook_snapshot.rs: print the save HRESULT in hex, annotate E_NOTIMPL, and include the DXGI format name (highlights the BC formats). On the first failure per snapshot, probe saving the same resource as PNG to separate a total stub (PNG also fails) from a DDS/compressed-only gap. No interop wire-protocol change, so native code versions are not bumped. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_015Nur4mfwesxS7zUaUX7jGK --- Native/d3dx/src/d3dx.rs | 8 ++++ Native/hook_snapshot/src/hook_snapshot.rs | 53 +++++++++++++++++++++-- Native/util/src/util.rs | 13 +++++- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/Native/d3dx/src/d3dx.rs b/Native/d3dx/src/d3dx.rs index e4aef40f..b69e0a8f 100644 --- a/Native/d3dx/src/d3dx.rs +++ b/Native/d3dx/src/d3dx.rs @@ -119,6 +119,14 @@ pub fn load_lib(mm_root: &Option, device: &DevicePointer) -> Result return Err(HookError::LoadLibFailed(format!("D3DX11 not found in system or {:?}", searched))), Some(handle) => { + // Log exactly which d3dx11 DLL the loader resolved. Under Proton this is + // typically Wine's built-in copy (in the windows system dir), whose + // D3DX11SaveTextureToFileW may be a stub / not support compressed formats. + // A game that ships its own real d3dx11_43.dll will show a game-local path here. + match util::get_module_path(handle) { + Ok(path) => write_log_file(&format!("loaded d3dx11 from: {}", path)), + Err(e) => write_log_file(&format!("loaded d3dx11 (path unavailable: {:?})", e)), + } unsafe { Ok(D3DXFn::DX11(D3DX11Fn { D3DX11SaveTextureToFileW: std::mem::transmute(util::get_proc_address( diff --git a/Native/hook_snapshot/src/hook_snapshot.rs b/Native/hook_snapshot/src/hook_snapshot.rs index e03dc1c5..132cc4b3 100644 --- a/Native/hook_snapshot/src/hook_snapshot.rs +++ b/Native/hook_snapshot/src/hook_snapshot.rs @@ -278,6 +278,28 @@ pub unsafe fn take(devptr:&mut DevicePointer, sd:&mut types::interop::SnapshotDa } } +/// Human-readable name for the DXGI formats we care about when logging texture snapshots. +/// Covers the block-compressed range (the formats that fail to save under Wine's d3dx11) +/// plus a few common uncompressed ones; anything else is reported as "?". +fn dxgi_format_name(fmt: DXGI_FORMAT) -> &'static str { + match fmt { + 0 => "UNKNOWN", + 2 => "R32G32B32A32_FLOAT", + 10 => "R16G16B16A16_FLOAT", + 28 => "R8G8B8A8_UNORM", + 29 => "R8G8B8A8_UNORM_SRGB", + 70 => "BC1_TYPELESS", 71 => "BC1_UNORM", 72 => "BC1_UNORM_SRGB", + 73 => "BC2_TYPELESS", 74 => "BC2_UNORM", 75 => "BC2_UNORM_SRGB", + 76 => "BC3_TYPELESS", 77 => "BC3_UNORM", 78 => "BC3_UNORM_SRGB", + 79 => "BC4_TYPELESS", 80 => "BC4_UNORM", 81 => "BC4_SNORM", + 82 => "BC5_TYPELESS", 83 => "BC5_UNORM", 84 => "BC5_SNORM", + 87 => "B8G8R8A8_UNORM", 88 => "B8G8R8X8_UNORM", 91 => "B8G8R8A8_UNORM_SRGB", + 94 => "BC6H_TYPELESS", 95 => "BC6H_UF16", 96 => "BC6H_SF16", + 97 => "BC7_TYPELESS", 98 => "BC7_UNORM", 99 => "BC7_UNORM_SRGB", + _ => "?", + } +} + unsafe fn save_textures(devtr:&mut DevicePointer, buffers:&Box, snap_dir:&str, snap_prefix:&str) -> Result<()> { // in d3d9 the managed code already did this let device = match devtr { @@ -308,6 +330,8 @@ unsafe fn save_textures(devtr:&mut DevicePointer, buffers:&Box= d3d11bufs.srvs.len() { return Err(HookError::SnapshotFailed(format!("invalid srv index {} of {}", idx, d3d11bufs.srvs.len()))); @@ -345,12 +369,33 @@ unsafe fn save_textures(devtr:&mut DevicePointer, buffers:&Box Vec { } pub fn get_module_name() -> Result { - use std::os::windows::prelude::*; use winapi::um::libloaderapi::*; + let handle = unsafe { GetModuleHandleW(std::ptr::null_mut()) }; + get_module_path(handle) +} + +/// Return the on-disk path for a loaded module handle (as returned by `load_lib`). +/// Useful for logging exactly which DLL the OS loader resolved for a given name, +/// which can differ between the system copy and a game- or app-local copy. +pub fn get_module_path(handle: HMODULE) -> Result { + use std::os::windows::prelude::*; + use winapi::um::libloaderapi::GetModuleFileNameW; + unsafe { let ssize = 65535; let mut mpath: Vec = Vec::with_capacity(ssize); - let handle = GetModuleHandleW(std::ptr::null_mut()); let r = GetModuleFileNameW(handle, mpath.as_mut_ptr(), ssize as DWORD); if r == 0 { return Err(HookError::ModuleNameError(format!( From aa79a1022dbaa28247272c7c4811f8039ad0a8a4 Mon Sep 17 00:00:00 2001 From: John Quigley Date: Fri, 10 Jul 2026 15:56:11 -0400 Subject: [PATCH 2/3] MMLaunch: try some more paths for locating profiles - Fix for when trying to do texconversion under proton --- MMLaunch/ModUtil.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MMLaunch/ModUtil.fs b/MMLaunch/ModUtil.fs index 5510b422..320e5733 100644 --- a/MMLaunch/ModUtil.fs +++ b/MMLaunch/ModUtil.fs @@ -33,8 +33,8 @@ module DdsUtil = type HeaderInfo = { FourCC : string; HasDX10 : bool } - let PathToTexCli = [ "TPlib"; "..\\TPLib"; "..\\..\\TPLib"; "..\\..\\..\\TPLib"; ] - let PathToSnapshotProfiles = [ "SnapshotProfiles"; "..\\SnapshotProfiles"; "..\\..\\SnapshotProfiles"; "..\\..\\..\\SnapshotProfiles" ] + let PathToTexCli = [ "TPlib"; "ModelMod\\TPLib"; "..\\TPLib"; "..\\..\\TPLib"; "..\\..\\..\\TPLib"; ] + let PathToSnapshotProfiles = [ "SnapshotProfiles"; "ModelMod\\SnapshotProfiles"; "..\\SnapshotProfiles"; "..\\..\\SnapshotProfiles"; "..\\..\\..\\SnapshotProfiles" ] let SnapTexFormatFile = "TexFormat.txt" let TexConvName = "texconv.exe" From 8c993efdcd29e114cd8af78caefa67430bf6cd45 Mon Sep 17 00:00:00 2001 From: John Quigley Date: Fri, 10 Jul 2026 16:00:37 -0400 Subject: [PATCH 3/3] Tweak d3dx loading (wine/proton) - On wine proton loading from the system will load their implementation of d3dx which doesn't support snapshotting compressed textures (it does support other formats apparently) - Therefore try loading d3dx from MM's tplib first on wine. On actual windows though we want to prefer the system libraries. - Claude added a png diagnostic when a tex save fails, but I found it unnecessary (it failed too) so I removed it --- Native/d3dx/src/d3dx.rs | 35 ++++++++++++++++------- Native/hook_snapshot/src/hook_snapshot.rs | 18 +----------- Native/util/src/util.rs | 12 ++++++++ 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/Native/d3dx/src/d3dx.rs b/Native/d3dx/src/d3dx.rs index b69e0a8f..e74dd4b5 100644 --- a/Native/d3dx/src/d3dx.rs +++ b/Native/d3dx/src/d3dx.rs @@ -71,14 +71,26 @@ pub fn load_lib(mm_root: &Option, device: &DevicePointer) -> Result { let base_names = vec!["d3dx11_43.dll", "d3dx11_42.dll"]; - // just try loading it first from the system - let mut handle = base_names.iter().find_map(|base_name| { - match util::load_lib(base_name) { - Ok(handle) => Some(handle), - Err(_) => None, - } - }); + + let mut handle:Option = None; + + let load_from_sys = || { + base_names.iter().find_map(|base_name| { + match util::load_lib(base_name) { + Ok(handle) => Some(handle), + Err(_) => None, + } + }) + }; + let mut searched:Vec = vec![]; + + // on actual windows try load from sys first; don't do this on wine because it will load, but + // we'll get wine's partially implemented version of the associated functions (on wine + // prefer the copy stored in TPLib) + if !util::is_wine() { + handle = load_from_sys(); + } if handle.is_none() { // not found so look into tplib dir, try looking for arch specific folder // or file with an _arch suffix in tplib @@ -116,13 +128,14 @@ pub fn load_lib(mm_root: &Option, device: &DevicePointer) -> Result return Err(HookError::LoadLibFailed(format!("D3DX11 not found in system or {:?}", searched))), Some(handle) => { - // Log exactly which d3dx11 DLL the loader resolved. Under Proton this is - // typically Wine's built-in copy (in the windows system dir), whose - // D3DX11SaveTextureToFileW may be a stub / not support compressed formats. - // A game that ships its own real d3dx11_43.dll will show a game-local path here. + // Log exactly which d3dx11 DLL the loader resolved. match util::get_module_path(handle) { Ok(path) => write_log_file(&format!("loaded d3dx11 from: {}", path)), Err(e) => write_log_file(&format!("loaded d3dx11 (path unavailable: {:?})", e)), diff --git a/Native/hook_snapshot/src/hook_snapshot.rs b/Native/hook_snapshot/src/hook_snapshot.rs index 132cc4b3..7e1bb9ba 100644 --- a/Native/hook_snapshot/src/hook_snapshot.rs +++ b/Native/hook_snapshot/src/hook_snapshot.rs @@ -279,8 +279,7 @@ pub unsafe fn take(devptr:&mut DevicePointer, sd:&mut types::interop::SnapshotDa } /// Human-readable name for the DXGI formats we care about when logging texture snapshots. -/// Covers the block-compressed range (the formats that fail to save under Wine's d3dx11) -/// plus a few common uncompressed ones; anything else is reported as "?". +/// Covers the block-compressed range, plus a few common uncompressed ones; anything else is reported as "?". fn dxgi_format_name(fmt: DXGI_FORMAT) -> &'static str { match fmt { 0 => "UNKNOWN", @@ -330,8 +329,6 @@ unsafe fn save_textures(devtr:&mut DevicePointer, buffers:&Box= d3d11bufs.srvs.len() { return Err(HookError::SnapshotFailed(format!("invalid srv index {} of {}", idx, d3d11bufs.srvs.len()))); @@ -371,7 +368,6 @@ unsafe fn save_textures(devtr:&mut DevicePointer, buffers:&Box bool { + unsafe { + let ntdll = GetModuleHandleA(b"ntdll.dll\0".as_ptr() as *const i8); + if ntdll.is_null() { + return false; + } + !GetProcAddress(ntdll, b"wine_get_version\0".as_ptr() as *const i8).is_null() + } +} + pub unsafe fn protect_memory( target: *mut winapi::ctypes::c_void, size: usize,