diff --git a/CMakeLists.txt b/CMakeLists.txt index 7966387..a01c5e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,6 @@ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") add_compile_definitions(WIN32_LEAN_AND_MEAN NOMINMAX _CRT_SECURE_NO_WARNINGS) include(cmake/Detours.cmake) -include(cmake/DxWrapper.cmake) set(SACRED_RE_CXX_FLAGS /W4 /permissive- /utf-8) @@ -33,12 +32,10 @@ add_library(sacred_re SHARED src/game/engine/cmdline.cpp src/mods/borderless.cpp src/mods/widescreen.cpp - src/mods/dxwrapper.cpp ) target_include_directories(sacred_re PRIVATE src) target_compile_options(sacred_re PRIVATE ${SACRED_RE_CXX_FLAGS}) -target_link_libraries(sacred_re PRIVATE detours ddraw) -sacred_re_install_dxwrapper(sacred_re) +target_link_libraries(sacred_re PRIVATE detours) # --- sacred_re.exe: launches sacred.exe with sacred_re.dll injected ---------- add_executable(sacred_re_launcher WIN32 src/launcher/main.cpp) diff --git a/cmake/DxWrapper.cmake b/cmake/DxWrapper.cmake deleted file mode 100644 index fb17ab8..0000000 --- a/cmake/DxWrapper.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# dxwrapper (zlib; github.com/elishacloud/dxwrapper): translates the game's -# DirectDraw 7 / Direct3D 7 to Direct3D 9, lifting D3D7's 2048x2048 render -# target limit. Pinned release, verified by hash; installed next to -# sacred_re.dll in dxwrapper/ and loaded by src/mods/dxwrapper.cpp, so the -# game folder stays untouched. -include(FetchContent) - -# Some CMake builds (e.g. WinLibs/MinGW) ship curl without a CA store, so -# HTTPS downloads fail certificate verification. Fall back to Git for -# Windows' bundle when nothing else is configured. -if(NOT CMAKE_TLS_CAINFO AND NOT DEFINED ENV{SSL_CERT_FILE}) - file(TO_CMAKE_PATH "$ENV{ProgramFiles}" _program_files) - foreach(_bundle "${_program_files}/Git/mingw64/etc/ssl/certs/ca-bundle.crt" - "${_program_files}/Git/mingw64/ssl/certs/ca-bundle.crt") - if(EXISTS "${_bundle}") - set(CMAKE_TLS_CAINFO "${_bundle}") - break() - endif() - endforeach() -endif() - -FetchContent_Declare(dxwrapper_bin - URL https://github.com/elishacloud/dxwrapper/releases/download/v1.8.8600.25/dxwrapper.zip - URL_HASH SHA256=3894368ebce69f348d08e189c2f0818c49aa3b18ad01e06348743bf6a29af97a - DOWNLOAD_EXTRACT_TIMESTAMP TRUE -) -FetchContent_MakeAvailable(dxwrapper_bin) - -function(sacred_re_install_dxwrapper target) - set(_dest "$/dxwrapper") - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "${_dest}" - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${dxwrapper_bin_SOURCE_DIR}/Stub/ddraw.dll" - "${dxwrapper_bin_SOURCE_DIR}/dxwrapper.dll" - "${dxwrapper_bin_SOURCE_DIR}/License.txt" - "${CMAKE_SOURCE_DIR}/config/dxwrapper.ini" - "${_dest}" - VERBATIM) -endfunction() diff --git a/config/dxwrapper.ini b/config/dxwrapper.ini deleted file mode 100644 index 9e19c4c..0000000 --- a/config/dxwrapper.ini +++ /dev/null @@ -1,152 +0,0 @@ -;; sacred_re: dxwrapper settings (copied next to dxwrapper.dll at build time). -;; Only change from the release defaults: Dd7to9 = 1 (DirectDraw/D3D7 -> D3D9). -;; Config file for DirectX DLL Wrapper -;; -;; For details, see: https://github.com/elishacloud/dxwrapper/wiki/Configuration -;; -[General] -RealDllPath = AUTO -WrapperMode = AUTO -LoadCustomDllPath = -ExcludeProcess = -IncludeProcess = -RunProcess = -WaitForProcess = 0 -DisableLogging = 0 - -[Plugins] -LoadPlugins = 0 -LoadFromScriptsOnly = 0 - -[Compatibility] -Dd7to9 = 1 -D3d8to9 = 0 -D3d9to9Ex = 0 -D3d9on12 = 0 -DDrawCompat = 0 -Dinputto8 = 0 -DisableGameUX = 0 -EnableDdrawWrapper = 0 -EnableD3d9Wrapper = 0 -EnableDinput8Wrapper = 0 -EnableDsoundWrapper = 0 -DisableGDIGammaRamp = 0 -EnableOpenDialogHook = 0 -WinVersionLie = off -WinVersionLieSP = 0 -ForceKeyboardLayout = 0 -FixPerfCounterUptime = 0 -HandleExceptions = 0 -SingleProcAffinity = 0 -ProcAffinityMask = 0 - -[DDrawCompat] -DDrawCompat20 = 0 -DDrawCompat21 = 0 -DDrawCompat32 = 0 -DDrawCompatDisableGDIHook = 0 -DDrawCompatNoProcAffinity = 0 - -[ddraw] -DdrawOverrideBitMode = 0 -DdrawUseDirect3D9Caps = 0 - -[Dd7to9] -DdrawAutoFrameSkip = 0 -DdrawEmulateSurface = 0 -DdrawEmulateLock = 0 -DdrawUseShadowSurface = 0 -DdrawKeepAllInterfaceCache = 0 -DdrawAllowMultiSampling = 0 -DdrawForceMipMapAutoGen = 0 -DdrawClampVertexZDepth = 0 -DdrawEnableByteAlignment = 0 -DdrawFixByteAlignment = 0 -DdrawIntroVideoFix = 0 -DdrawFilterActivateApp = 0 -DdrawRemoveScanlines = 0 -DdrawRemoveInterlacing = 0 -DdrawReadFromGDI = 0 -DdrawWriteToGDI = 0 -DdrawLimitTextureFormats = 0 -DdrawLimitDisplayModeCount = 0 -DdrawCustomWidth = 0 -DdrawCustomHeight = 0 -DdrawLinearTextureFilter = 0 -DdrawUseNativeResolution = 0 -DdrawOverrideWidth = 0 -DdrawOverrideHeight = 0 -DdrawOverrideStencilFormat = 0 -DdrawIntegerScalingClamp = 0 -DdrawMaintainAspectRatio = 0 - -[d3d9] -AnisotropicFiltering = 0 -AntiAliasing = 0 -EnableMultisamplingATOC = 0 -EnvironmentCubeMapFix = 0 -ForceMipMapUsage = 0 -DepthBiasFactor = 0 -DepthBiasDropOffValue = 0 -EnableVSync = 0 -ForceVsyncMode = 0 -ShowFPSCounter = 0 -OverrideRefreshRate = 0 -LimitRefreshRates = 0 -LimitDisplayModeCount = 0 -CustomDisplayWidth = 0 -CustomDisplayHeight = 0 -LimitPerFrameFPS = 0 -EnableWindowMode = 0 -WindowModeBorder = 0 -WindowModeGammaShader = 0 -DisplayBrightness = 0 -DisplayContrast = 0 -SetInitialWindowPosition = 0 -InitialWindowPositionLeft = 0 -InitialWindowPositionTop = 0 -FullscreenWindowMode = 0 -HideWindowFocusChanges = 0 -EnableCursorClip = 0 -ForceExclusiveFullscreen = 0 -ForceMixedVertexProcessing = 0 -ForceSystemMemVertexCache = 0 -UseShadowBackbuffer = 0 -OverrideStencilFormat = 0 -FlipEx = 0 -SetSwapEffectShim = 0 -DisableMaxWindowedMode = 0 -GraphicsHybridAdapter = 0 - -[FullScreen] -FullScreen = 0 -ForceWindowResize = 0 -WaitForWindowChanges = 0 - -[dinput8] -DeviceLookupCacheTime = 0 -FilterNonActiveInput = 0 -InvertForceDirection = 0 -FixHighFrequencyMouse = 0 -MouseMovementFactor = 0 -MouseMovementFactorX = 0 -MouseMovementFactorY = 0 -MouseMovementPadding = 0 -MouseMovementPaddingX = 0 -MouseMovementPaddingY = 0 - -[dsound] -Num2DBuffers = 0 -Num3DBuffers = 0 -ForceCertification = 0 -ForceExclusiveMode = 0 -ForceSoftwareMixing = 0 -ForceHardwareMixing = 0 -ForceHQ3DSoftMixing = 0 -ForceNonStaticBuffers = 0 -ForceVoiceManagement = 0 -ForcePrimaryBufferFormat = 0 -PrimaryBufferBits = 16 -PrimaryBufferSamples = 44100 -PrimaryBufferChannels = 2 -AudioClipDetection = 0 diff --git a/docs/widescreen.md b/docs/widescreen.md index 49da91b..a58082c 100644 --- a/docs/widescreen.md +++ b/docs/widescreen.md @@ -76,9 +76,12 @@ presents only that area, scaled to the monitor. Windows' D3D7 runtime refuses `IDirect3D7::CreateDevice` on any render target wider or taller than 2048 (`DDERR_INVALIDOBJECT`, 0x88760082), measured with a standalone test: 2048x1440 works, 2049x1440 / 2560x1080 / -3440x1440 fail. Native ultrawide therefore needs dxwrapper (`Dd7to9`, -D3D7 -> D3D9), see `src/mods/dxwrapper.cpp`; without it the render size is -clamped to 2048. +3440x1440 fail. The default render size (768 high, e.g. 1835x768) stays +below it; larger sizes are clamped to 2048. (A D3D7 -> D3D9 wrapper, +dxwrapper, lifted the limit but was dropped again in commit history once the +render size no longer needed it; it also required fixing the device info GUID +pointer at +4 of SetDisplayMode's first argument, which its enumeration left +invalid.) ## Renderer diff --git a/src/mods/borderless.cpp b/src/mods/borderless.cpp index e45a1c7..b303ce5 100644 --- a/src/mods/borderless.cpp +++ b/src/mods/borderless.cpp @@ -33,7 +33,6 @@ #include "game/engine/input.h" #include "game/render/dx_driver7.h" -#include "mods/dxwrapper.h" #include "mods/widescreen.h" #include "re/config.h" #include "game/addresses.h" @@ -168,7 +167,6 @@ RE_HOOK(addr::MainWindowProc, MainWindowProc); int __fastcall DxDriver7_SetDisplayMode(DxDriver7* self, void* edx, void* deviceCaps, const DxDisplayMode* mode, uint32_t unknown) { - mods::dxwrapper::FixDeviceInfo(deviceCaps); if (!Enabled()) { return DxDriver7_SetDisplayMode_original(self, edx, deviceCaps, mode, unknown); } @@ -197,7 +195,14 @@ int __fastcall DxDriver7_Flip(DxDriver7* self, void* edx) { self->blitSource = {0, 0, layout.gameWidth, layout.gameHeight}; self->blitDest = layout.screen; } - return DxDriver7_Flip_original(self, edx); + const int result = DxDriver7_Flip_original(self, edx); + static std::atomic failures = 0; + if (result != 0 && failures++ < 5) { + re::Log("borderless: present failed (0x%08x), source %ldx%ld -> dest (%ld,%ld)-(%ld,%ld)", + static_cast(result), self->blitSource.right, self->blitSource.bottom, self->blitDest.left, + self->blitDest.top, self->blitDest.right, self->blitDest.bottom); + } + return result; } void __cdecl GetMouseClientPos(HWND window, POINT* position) { diff --git a/src/mods/dxwrapper.cpp b/src/mods/dxwrapper.cpp deleted file mode 100644 index 1883447..0000000 --- a/src/mods/dxwrapper.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// Route the game's DirectDraw 7 / Direct3D 7 through dxwrapper (Dd7to9: -// translation to Direct3D 9). Windows' own D3D7 runtime cannot render to -// surfaces larger than 2048x2048, which the widescreen mod needs. -// -// The build puts dxwrapper's ddraw.dll stub, dxwrapper.dll and our -// config/dxwrapper.ini into dxwrapper/ next to sacred_re.dll; the game folder -// is not touched. sacred.exe imports three functions from DDRAW.dll; we hook -// them and forward to the wrapper, which is loaded on the first call (never -// from DllMain). Calls made while the wrapper is running (it may use the real -// DirectDraw itself) go to the original functions. -// -// sacred_re.ini: -// [renderer] -// DxWrapper=1 ; 0 = Windows' DirectDraw (render size limited to 2048) - -#include "mods/dxwrapper.h" - -#include -#include -#include - -#include -#include - -#include "re/config.h" -#include "re/hook.h" -#include "re/log.h" - -namespace mods::dxwrapper { -namespace { - -HMODULE g_wrapper = nullptr; -bool g_loadAttempted = false; -thread_local int g_insideWrapper = 0; - -std::wstring WrapperPath() { - return re::ModuleDirectory() + L"dxwrapper\\ddraw.dll"; -} - -bool Enabled() { - return re::ConfigInt("renderer", "DxWrapper", 1) != 0; -} - -FARPROC WrapperFunction(const char* name) { - if (!g_loadAttempted) { - g_loadAttempted = true; - const std::wstring path = WrapperPath(); - g_wrapper = LoadLibraryW(path.c_str()); - if (g_wrapper) { - re::Log("dxwrapper: loaded %ls", path.c_str()); - } else { - re::Log("dxwrapper: could not load %ls (error %lu), using Windows' DirectDraw", path.c_str(), - GetLastError()); - } - } - return g_wrapper ? GetProcAddress(g_wrapper, name) : nullptr; -} - -// Calls `function` from the wrapper if possible, else `original`. -template -HRESULT Forward(const char* name, Function original, Args... args) { - if (g_insideWrapper == 0 && Enabled()) { - if (auto wrapped = reinterpret_cast(WrapperFunction(name))) { - ++g_insideWrapper; - const HRESULT result = wrapped(args...); - --g_insideWrapper; - return result; - } - } - return original(args...); -} - -} // namespace - -bool IsActive() { - if (!Enabled()) { - return false; - } - if (g_loadAttempted) { - return g_wrapper != nullptr; - } - return GetFileAttributesW(WrapperPath().c_str()) != INVALID_FILE_ATTRIBUTES; -} - -void FixDeviceInfo(void* deviceInfo) { - auto* bytes = static_cast(deviceInfo); - const uint32_t guidAddress = static_cast(reinterpret_cast(bytes + 0x3f4)); - uint32_t guidPointer; - memcpy(&guidPointer, bytes + 4, 4); - if (guidPointer != guidAddress) { - re::Log("dxwrapper: device info GUID pointer was 0x%08x, set to 0x%08x", guidPointer, guidAddress); - memcpy(bytes + 4, &guidAddress, 4); - } -} - -HRESULT WINAPI DirectDrawCreateExViaWrapper(GUID* guid, LPVOID* directDraw, REFIID iid, IUnknown* outer); -HRESULT WINAPI DirectDrawCreateViaWrapper(GUID* guid, LPDIRECTDRAW* directDraw, IUnknown* outer); -HRESULT WINAPI DirectDrawEnumerateExAViaWrapper(LPDDENUMCALLBACKEXA callback, LPVOID context, DWORD flags); - -RE_HOOK_API(DirectDrawCreateEx, DirectDrawCreateExViaWrapper); -RE_HOOK_API(DirectDrawCreate, DirectDrawCreateViaWrapper); -RE_HOOK_API(DirectDrawEnumerateExA, DirectDrawEnumerateExAViaWrapper); - -HRESULT WINAPI DirectDrawCreateExViaWrapper(GUID* guid, LPVOID* directDraw, REFIID iid, IUnknown* outer) { - return Forward("DirectDrawCreateEx", DirectDrawCreateExViaWrapper_original, guid, directDraw, iid, outer); -} - -HRESULT WINAPI DirectDrawCreateViaWrapper(GUID* guid, LPDIRECTDRAW* directDraw, IUnknown* outer) { - return Forward("DirectDrawCreate", DirectDrawCreateViaWrapper_original, guid, directDraw, outer); -} - -HRESULT WINAPI DirectDrawEnumerateExAViaWrapper(LPDDENUMCALLBACKEXA callback, LPVOID context, DWORD flags) { - return Forward("DirectDrawEnumerateExA", DirectDrawEnumerateExAViaWrapper_original, callback, context, flags); -} - -} // namespace mods::dxwrapper diff --git a/src/mods/dxwrapper.h b/src/mods/dxwrapper.h deleted file mode 100644 index fb3defd..0000000 --- a/src/mods/dxwrapper.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -namespace mods::dxwrapper { - -// True when the game's DirectDraw calls go to dxwrapper (Direct3D 9), which -// has no 2048x2048 render target limit. -bool IsActive(); - -// The device info the game passes to DxDriver7::SetDisplayMode keeps a pointer -// to its own device GUID at +4 (the GUID itself is at +0x3f4); the game uses -// it for EnumZBufferFormats. Under dxwrapper the game's device enumeration -// leaves garbage there (0x3c0) and dxwrapper crashes dereferencing it. -// Restores the pointer Windows' DirectDraw would have produced. -void FixDeviceInfo(void* deviceInfo); - -} // namespace mods::dxwrapper diff --git a/src/mods/widescreen.cpp b/src/mods/widescreen.cpp index 26b9465..371cfb9 100644 --- a/src/mods/widescreen.cpp +++ b/src/mods/widescreen.cpp @@ -1,5 +1,5 @@ -// Widescreen: render the world 1:1 at the monitor's resolution, showing more -// of it, instead of scaling a 1024x768 image. +// Widescreen: show more of the world horizontally on wide monitors while +// everything keeps its original on-screen size. // // The world view sizes itself from zoom * 1024.0 / 768.0 (float constants that // the UI also reads) and projects 3D models with an ortho camera of @@ -15,8 +15,8 @@ // ; 0 = monitor height, i.e. native 1:1 (everything smaller) // Width=0 ; 0 = Height * monitor aspect ratio // -// Windows' Direct3D 7 limits the render size to 2048x2048 unless the dxwrapper -// mod (Direct3D 9) is active; the borderless mod centres a smaller image 1:1. +// Windows' Direct3D 7 cannot render to targets larger than 2048x2048, so the +// render size is clamped to that (only reachable with Height=0 / large Width). // // Experimental: the UI is still drawn at the top-left 1024x768, and creatures // are only active within the original distance. @@ -32,7 +32,6 @@ #include "game/addresses.h" #include "game/ui/ui_manager.h" -#include "mods/dxwrapper.h" #include "re/config.h" #include "re/hook.h" #include "re/log.h" @@ -246,7 +245,7 @@ void ApplyRenderSize(game::DxDisplayMode& mode, HWND window) { // wider or taller than 2048 pixels (CreateDevice fails with // DDERR_INVALIDOBJECT); anything larger needs a different renderer. constexpr int kMaxD3D7Surface = 2048; - if (!mods::dxwrapper::IsActive() && (width > kMaxD3D7Surface || height > kMaxD3D7Surface)) { + if (width > kMaxD3D7Surface || height > kMaxD3D7Surface) { re::Log("widescreen: %dx%d exceeds the Direct3D 7 limit, clamping to %d", width, height, kMaxD3D7Surface); width = std::min(width, kMaxD3D7Surface); height = std::min(height, kMaxD3D7Surface); diff --git a/tools/game-input.ps1 b/tools/game-input.ps1 index 64ee291..f6d3f3f 100644 --- a/tools/game-input.ps1 +++ b/tools/game-input.ps1 @@ -19,13 +19,27 @@ Add-Type -Namespace SacredRe -Name Input -MemberDefinition @' $script:Root = Split-Path $PSScriptRoot +# The game pauses its start-up until its window is active, and Windows does +# not hand the foreground to a window started from a background process; a +# synthetic Alt press lifts that restriction for SetForegroundWindow. +function Focus-Sacred($Game) { + $Game.Refresh() + if ($Game.MainWindowHandle -eq [IntPtr]::Zero) { return $false } + [SacredRe.Input]::keybd_event(0x12, 0, 0, [UIntPtr]::Zero) + [SacredRe.Input]::keybd_event(0x12, 0, 2, [UIntPtr]::Zero) + [SacredRe.Input]::SetForegroundWindow($Game.MainWindowHandle) +} + function Start-Sacred([string]$Arguments = "log", [int]$WaitSeconds = 18) { if (Get-Process sacred -ErrorAction SilentlyContinue) { throw "sacred.exe is already running" } $null = Start-Process (Join-Path $script:Root "build\x86-debug\sacred_re.exe") -ArgumentList $Arguments - Start-Sleep $WaitSeconds - $game = Get-Process sacred -ErrorAction SilentlyContinue | Select-Object -First 1 - if (-not $game) { throw "sacred.exe did not start (or crashed)" } - [SacredRe.Input]::SetForegroundWindow($game.MainWindowHandle) | Out-Null + $game = $null + for ($i = 0; $i -lt $WaitSeconds * 4; $i++) { + Start-Sleep -Milliseconds 250 + if (-not $game) { $game = Get-Process sacred -ErrorAction SilentlyContinue | Select-Object -First 1 } + if ($game) { Focus-Sacred $game | Out-Null } + } + if (-not $game -or $game.HasExited) { throw "sacred.exe did not start (or crashed)" } $game }