From 44f3559e8272b88149c5fda09a795d45a1980ae7 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 21 Dec 2024 20:45:47 +0200 Subject: [PATCH 1/2] [d3d9] Refactor the D3D9ShaderValidator implementation --- src/d3d9/d3d9_shader_validator.cpp | 205 +++++++++++++++++++++++++++++ src/d3d9/d3d9_shader_validator.h | 185 +------------------------- src/d3d9/meson.build | 1 + 3 files changed, 213 insertions(+), 178 deletions(-) create mode 100644 src/d3d9/d3d9_shader_validator.cpp diff --git a/src/d3d9/d3d9_shader_validator.cpp b/src/d3d9/d3d9_shader_validator.cpp new file mode 100644 index 00000000000..a4f983e53ba --- /dev/null +++ b/src/d3d9/d3d9_shader_validator.cpp @@ -0,0 +1,205 @@ +#include "d3d9_shader_validator.h" + +namespace dxvk { + + HRESULT STDMETHODCALLTYPE D3D9ShaderValidator::QueryInterface(REFIID riid, void** ppvObject) { + if (ppvObject == nullptr) + return E_POINTER; + + *ppvObject = ref(this); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE D3D9ShaderValidator::Begin( + D3D9ShaderValidatorCallback pCallback, + void* pUserData, + DWORD Unknown) { + if (unlikely(m_state != D3D9ShaderValidatorState::Begin)) { + return ErrorCallback(nullptr, -1, 0, + D3D9ShaderValidatorMessage::BeginOutOfOrder, + "IDirect3DShaderValidator9::Begin called out of order. ::End must be called first."); + } + + m_callback = pCallback; + m_userData = pUserData; + m_state = D3D9ShaderValidatorState::ValidatingHeader; + + return D3D_OK; + } + + + HRESULT STDMETHODCALLTYPE D3D9ShaderValidator::Instruction( + const char* pFile, + UINT Line, + const DWORD* pdwInst, + DWORD cdw) { + if (unlikely(pdwInst == nullptr || !cdw)) { + return ErrorCallback(pFile, Line, 0, + D3D9ShaderValidatorMessage::InstructionNullArgs, + "IDirect3DShaderValidator9::Instruction called with NULL == pdwInst or 0 == cdw."); + } + + if (unlikely(m_state == D3D9ShaderValidatorState::Begin)) { + return ErrorCallback(pFile, Line, 0, + D3D9ShaderValidatorMessage::InstructionOutOfOrder, + "IDirect3DShaderValidator9::Instruction called out of order. ::Begin must be called first."); + } else if (unlikely(m_state == D3D9ShaderValidatorState::EndOfShader)) { + return ErrorCallback(pFile, Line, 0, + D3D9ShaderValidatorMessage::InstructionEndOfShader, + "IDirect3DShaderValidator9::Instruction called out of order. After end token there should be no more instructions. Call ::End next."); + } else if (unlikely(m_state == D3D9ShaderValidatorState::Error)) { + return E_FAIL; + } + + if (m_state == D3D9ShaderValidatorState::ValidatingHeader) + return ValidateHeader(pFile, Line, pdwInst, cdw); + + DxsoCodeIter pdwInstIter{ reinterpret_cast(pdwInst) }; + bool isEndToken = !m_ctx->decodeInstruction(pdwInstIter); + const DxsoInstructionContext instContext = m_ctx->getInstructionContext(); + + if (isEndToken) + return ValidateEndToken(pFile, Line, pdwInst, cdw); + + // TODO: DxsoDecodeContext::decodeInstructionLength() does not currently appear + // to return the correct token length in many cases, and as such dwordLength + // will not be equal to cdw in many situations that are expected to pass validation + // + /*Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: opcode ", instContext.instruction.opcode)); + // + 1 to account for the opcode... + uint32_t dwordLength = instContext.instruction.tokenLength + 1; + Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: cdw ", cdw)); + Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: dwordLength ", dwordLength)); + if (unlikely(cdw != dwordLength)) { + return ErrorCallback(pFile, Line, 0x2, + D3D9ShaderValidatorMessage::BadInstructionLength, + str::format("Instruction length specified for instruction (", cdw, ") does not match the token count encountered (", dwordLength, "). Aborting validation.")); + }*/ + + // a maximum of 10 inputs are supported with PS 3.0 (validation required by The Void) + if (m_isPixelShader && m_majorVersion == 3) { + switch (instContext.instruction.opcode) { + case DxsoOpcode::Comment: + case DxsoOpcode::Def: + case DxsoOpcode::DefB: + case DxsoOpcode::DefI: + break; + + default: + // Iterate over register tokens. Bit 31 of register tokens is always 1. + for (uint32_t instNum = 1; pdwInst[instNum] & 0x80000000; instNum++) { + DWORD regType = ((pdwInst[instNum] & D3DSP_REGTYPE_MASK) >> D3DSP_REGTYPE_SHIFT) + | ((pdwInst[instNum] & D3DSP_REGTYPE_MASK2) >> D3DSP_REGTYPE_SHIFT2); + DWORD regIndex = pdwInst[instNum] & D3DSP_REGNUM_MASK; + + if (unlikely(regType == static_cast(DxsoRegisterType::Input) && regIndex >= 10)) { + Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: Found register index ", regIndex)); + return ErrorCallback(pFile, Line, 0x2, + instContext.instruction.opcode == DxsoOpcode::Dcl ? + D3D9ShaderValidatorMessage::BadInputRegisterDeclaration : + D3D9ShaderValidatorMessage::BadInputRegister, + "IDirect3DShaderValidator9::Instruction: Invalid number of PS input registers specified. Aborting validation."); + } + } + } + } + + return D3D_OK; + } + + + HRESULT STDMETHODCALLTYPE D3D9ShaderValidator::End() { + if (unlikely(m_state == D3D9ShaderValidatorState::Error)) { + return E_FAIL; + } else if (unlikely(m_state == D3D9ShaderValidatorState::Begin)) { + return ErrorCallback(nullptr, 0, 0, + D3D9ShaderValidatorMessage::EndOutOfOrder, + "IDirect3DShaderValidator9::End called out of order. Call to ::Begin, followed by calls to ::Instruction must occur first."); + } else if (unlikely(m_state != D3D9ShaderValidatorState::EndOfShader)) { + return ErrorCallback(nullptr, 0, 0, + D3D9ShaderValidatorMessage::MissingEndToken, + "IDirect3DShaderValidator9::End: Shader missing end token."); + } + + m_state = D3D9ShaderValidatorState::Begin; + m_isPixelShader = false; + m_majorVersion = 0; + m_minorVersion = 0; + m_callback = nullptr; + m_userData = nullptr; + m_ctx = nullptr; + + return D3D_OK; + } + + + HRESULT D3D9ShaderValidator::ValidateHeader(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw) { + if (unlikely(cdw != 1)) { + return ErrorCallback(pFile, Line, 0x6, + D3D9ShaderValidatorMessage::BadVersionTokenLength, + "IDirect3DShaderValidator9::Instruction: Bad version token. DWORD count > 1 given. Expected DWORD count to be 1 for version token."); + } + + DxsoReader reader = { reinterpret_cast(pdwInst) }; + uint32_t headerToken = reader.readu32(); + uint32_t shaderType = headerToken & 0xffff0000; + DxsoProgramType programType; + + if (shaderType == 0xffff0000) { // Pixel Shader + programType = DxsoProgramTypes::PixelShader; + m_isPixelShader = true; + } else if (shaderType == 0xfffe0000) { // Vertex Shader + programType = DxsoProgramTypes::VertexShader; + m_isPixelShader = false; + } else { + return ErrorCallback(pFile, Line, 0x6, + D3D9ShaderValidatorMessage::BadVersionTokenType, + "IDirect3DShaderValidator9::Instruction: Bad version token. It indicates neither a pixel shader nor a vertex shader."); + } + + m_majorVersion = (headerToken >> 8) & 0xff; + m_minorVersion = headerToken & 0xff; + m_ctx = std::make_unique(DxsoProgramInfo{ programType, m_minorVersion, m_majorVersion }); + m_state = D3D9ShaderValidatorState::ValidatingInstructions; + + const char* shaderTypeOutput = m_isPixelShader ? "PS" : "VS"; + Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: Validating ", + shaderTypeOutput, " version ", m_majorVersion, ".", m_minorVersion)); + + return D3D_OK; + } + + + HRESULT D3D9ShaderValidator::ValidateEndToken(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw) { + // Reached the end token. + if (unlikely(cdw != 1)) { + return ErrorCallback(pFile, Line, 0x6, + D3D9ShaderValidatorMessage::BadEndToken, + "IDirect3DShaderValidator9::Instruction: Bad end token. DWORD count > 1 given. Expected DWORD count to be 1 for end token."); + } + + m_state = D3D9ShaderValidatorState::EndOfShader; + + return D3D_OK; + } + + + HRESULT D3D9ShaderValidator::ErrorCallback( + const char* pFile, + UINT Line, + DWORD Unknown, + D3D9ShaderValidatorMessage MessageID, + const std::string& Message) { + if (m_callback) + m_callback(pFile, Line, Unknown, MessageID, Message.c_str(), m_userData); + + // TODO: Consider switching this to debug, once we're + // confident the implementation doesn't cause any issues + Logger::warn(Message); + + m_state = D3D9ShaderValidatorState::Error; + + return E_FAIL; + } + +} \ No newline at end of file diff --git a/src/d3d9/d3d9_shader_validator.h b/src/d3d9/d3d9_shader_validator.h index 0b2d311ca93..eff743dc1ad 100644 --- a/src/d3d9/d3d9_shader_validator.h +++ b/src/d3d9/d3d9_shader_validator.h @@ -61,204 +61,33 @@ namespace dxvk { public: - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) { - if (ppvObject == nullptr) - return E_POINTER; - - *ppvObject = ref(this); - return S_OK; - } - + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); HRESULT STDMETHODCALLTYPE Begin( D3D9ShaderValidatorCallback pCallback, void* pUserData, - DWORD Unknown) { - if (unlikely(m_state != D3D9ShaderValidatorState::Begin)) { - return ErrorCallback(nullptr, -1, 0, - D3D9ShaderValidatorMessage::BeginOutOfOrder, - "IDirect3DShaderValidator9::Begin called out of order. ::End must be called first."); - } - - m_callback = pCallback; - m_userData = pUserData; - m_state = D3D9ShaderValidatorState::ValidatingHeader; - - return D3D_OK; - } - + DWORD Unknown); HRESULT STDMETHODCALLTYPE Instruction( const char* pFile, UINT Line, const DWORD* pdwInst, - DWORD cdw) { - if (unlikely(pdwInst == nullptr || !cdw)) { - return ErrorCallback(pFile, Line, 0, - D3D9ShaderValidatorMessage::InstructionNullArgs, - "IDirect3DShaderValidator9::Instruction called with NULL == pdwInst or 0 == cdw."); - } - - if (unlikely(m_state == D3D9ShaderValidatorState::Begin)) { - return ErrorCallback(pFile, Line, 0, - D3D9ShaderValidatorMessage::InstructionOutOfOrder, - "IDirect3DShaderValidator9::Instruction called out of order. ::Begin must be called first."); - } else if (unlikely(m_state == D3D9ShaderValidatorState::EndOfShader)) { - return ErrorCallback(pFile, Line, 0, - D3D9ShaderValidatorMessage::InstructionEndOfShader, - "IDirect3DShaderValidator9::Instruction called out of order. After end token there should be no more instructions. Call ::End next."); - } else if (unlikely(m_state == D3D9ShaderValidatorState::Error)) { - return E_FAIL; - } - - if (m_state == D3D9ShaderValidatorState::ValidatingHeader) - return ValidateHeader(pFile, Line, pdwInst, cdw); - - DxsoCodeIter pdwInstIter{ reinterpret_cast(pdwInst) }; - bool isEndToken = !m_ctx->decodeInstruction(pdwInstIter); - const DxsoInstructionContext instContext = m_ctx->getInstructionContext(); - - if (isEndToken) - return ValidateEndToken(pFile, Line, pdwInst, cdw); - - // TODO: DxsoDecodeContext::decodeInstructionLength() does not currently appear - // to return the correct token length in many cases, and as such dwordLength - // will not be equal to cdw in many situations that are expected to pass validation - // - /*Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: opcode ", instContext.instruction.opcode)); - // + 1 to account for the opcode... - uint32_t dwordLength = instContext.instruction.tokenLength + 1; - Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: cdw ", cdw)); - Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: dwordLength ", dwordLength)); - if (unlikely(cdw != dwordLength)) { - return ErrorCallback(pFile, Line, 0x2, - D3D9ShaderValidatorMessage::BadInstructionLength, - str::format("Instruction length specified for instruction (", cdw, ") does not match the token count encountered (", dwordLength, "). Aborting validation.")); - }*/ - - // a maximum of 10 inputs are supported with PS 3.0 (validation required by The Void) - if (m_isPixelShader && m_majorVersion == 3) { - switch (instContext.instruction.opcode) { - case DxsoOpcode::Comment: - case DxsoOpcode::Def: - case DxsoOpcode::DefB: - case DxsoOpcode::DefI: - break; - - default: - // Iterate over register tokens. Bit 31 of register tokens is always 1. - for (uint32_t instNum = 1; pdwInst[instNum] & 0x80000000; instNum++) { - DWORD regType = ((pdwInst[instNum] & D3DSP_REGTYPE_MASK) >> D3DSP_REGTYPE_SHIFT) - | ((pdwInst[instNum] & D3DSP_REGTYPE_MASK2) >> D3DSP_REGTYPE_SHIFT2); - DWORD regIndex = pdwInst[instNum] & D3DSP_REGNUM_MASK; - - if (unlikely(regType == static_cast(DxsoRegisterType::Input) && regIndex >= 10)) { - return ErrorCallback(pFile, Line, 0x2, - instContext.instruction.opcode == DxsoOpcode::Dcl ? - D3D9ShaderValidatorMessage::BadInputRegisterDeclaration : - D3D9ShaderValidatorMessage::BadInputRegister, - "IDirect3DShaderValidator9::Instruction: Invalid number of PS input registers specified. Aborting validation."); - } - } - } - } - - return D3D_OK; - } + DWORD cdw); - - HRESULT STDMETHODCALLTYPE End() { - if (unlikely(m_state == D3D9ShaderValidatorState::Error)) { - return E_FAIL; - } else if (unlikely(m_state == D3D9ShaderValidatorState::Begin)) { - return ErrorCallback(nullptr, 0, 0, - D3D9ShaderValidatorMessage::EndOutOfOrder, - "IDirect3DShaderValidator9::End called out of order. Call to ::Begin, followed by calls to ::Instruction must occur first."); - } else if (unlikely(m_state != D3D9ShaderValidatorState::EndOfShader)) { - return ErrorCallback(nullptr, 0, 0, - D3D9ShaderValidatorMessage::MissingEndToken, - "IDirect3DShaderValidator9::End: Shader missing end token."); - } - - m_state = D3D9ShaderValidatorState::Begin; - m_isPixelShader = false; - m_majorVersion = 0; - m_minorVersion = 0; - m_callback = nullptr; - m_userData = nullptr; - m_ctx = nullptr; - - return D3D_OK; - } + HRESULT STDMETHODCALLTYPE End(); private: - HRESULT ValidateHeader(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw) { - if (unlikely(cdw != 1)) { - return ErrorCallback(pFile, Line, 0x6, - D3D9ShaderValidatorMessage::BadVersionTokenLength, - "IDirect3DShaderValidator9::Instruction: Bad version token. DWORD count > 1 given. Expected DWORD count to be 1 for version token."); - } - - DxsoReader reader = { reinterpret_cast(pdwInst) }; - uint32_t headerToken = reader.readu32(); - uint32_t shaderType = headerToken & 0xffff0000; - DxsoProgramType programType; - - if (shaderType == 0xffff0000) { // Pixel Shader - programType = DxsoProgramTypes::PixelShader; - m_isPixelShader = true; - } else if (shaderType == 0xfffe0000) { // Vertex Shader - programType = DxsoProgramTypes::VertexShader; - m_isPixelShader = false; - } else { - return ErrorCallback(pFile, Line, 0x6, - D3D9ShaderValidatorMessage::BadVersionTokenType, - "IDirect3DShaderValidator9::Instruction: Bad version token. It indicates neither a pixel shader nor a vertex shader."); - } - - m_majorVersion = (headerToken >> 8) & 0xff; - m_minorVersion = headerToken & 0xff; - m_ctx = std::make_unique(DxsoProgramInfo{ programType, m_minorVersion, m_majorVersion }); - m_state = D3D9ShaderValidatorState::ValidatingInstructions; - - const char* shaderTypeOutput = m_isPixelShader ? "PS" : "VS"; - Logger::debug(str::format("IDirect3DShaderValidator9::Instruction: Validating ", - shaderTypeOutput, " version ", m_majorVersion, ".", m_minorVersion)); + HRESULT ValidateHeader(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw); - return D3D_OK; - } - - HRESULT ValidateEndToken(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw) { - // Reached the end token. - if (unlikely(cdw != 1)) { - return ErrorCallback(pFile, Line, 0x6, - D3D9ShaderValidatorMessage::BadEndToken, - "IDirect3DShaderValidator9::Instruction: Bad end token. DWORD count > 1 given. Expected DWORD count to be 1 for end token."); - } - - m_state = D3D9ShaderValidatorState::EndOfShader; - - return D3D_OK; - } + HRESULT ValidateEndToken(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw); HRESULT ErrorCallback( const char* pFile, UINT Line, DWORD Unknown, D3D9ShaderValidatorMessage MessageID, - const std::string& Message) { - if (m_callback) - m_callback(pFile, Line, Unknown, MessageID, Message.c_str(), m_userData); - - // TODO: Consider switching this to debug, once we're - // confident the implementation doesn't cause any issues - Logger::warn(Message); - - m_state = D3D9ShaderValidatorState::Error; - - return E_FAIL; - } + const std::string& Message); bool m_isPixelShader = false; uint32_t m_majorVersion = 0; diff --git a/src/d3d9/meson.build b/src/d3d9/meson.build index 8a3d78d2f5b..2f459468ace 100644 --- a/src/d3d9/meson.build +++ b/src/d3d9/meson.build @@ -30,6 +30,7 @@ d3d9_src = [ 'd3d9_shader.cpp', 'd3d9_vertex_declaration.cpp', 'd3d9_query.cpp', + 'd3d9_shader_validator.cpp', 'd3d9_multithread.cpp', 'd3d9_options.cpp', 'd3d9_stateblock.cpp', From 3537475a7d74d7e1590a84e6789e5accc3f63306 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sun, 22 Dec 2024 00:18:40 +0200 Subject: [PATCH 2/2] [util] Add a config option for shader input count validations --- src/d3d9/d3d9_interface.cpp | 3 +++ src/d3d9/d3d9_options.cpp | 1 + src/d3d9/d3d9_options.h | 3 +++ src/d3d9/d3d9_shader_validator.cpp | 9 ++++++--- src/d3d9/d3d9_shader_validator.h | 6 ++++++ src/util/config/config.cpp | 5 +++++ 6 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/d3d9/d3d9_interface.cpp b/src/d3d9/d3d9_interface.cpp index 46ec5d65401..6f2a39295c6 100644 --- a/src/d3d9/d3d9_interface.cpp +++ b/src/d3d9/d3d9_interface.cpp @@ -4,6 +4,7 @@ #include "d3d9_caps.h" #include "d3d9_device.h" #include "d3d9_bridge.h" +#include "d3d9_shader_validator.h" #include "../util/util_singleton.h" @@ -67,6 +68,8 @@ namespace dxvk { SetProcessDPIAware(); } #endif + + D3D9ShaderValidator::SetValidateShaderInputCount(m_d3d9Options.validateShaderInputCount); } diff --git a/src/d3d9/d3d9_options.cpp b/src/d3d9/d3d9_options.cpp index e535d610c5e..de7d9b40c0d 100644 --- a/src/d3d9/d3d9_options.cpp +++ b/src/d3d9/d3d9_options.cpp @@ -76,6 +76,7 @@ namespace dxvk { this->clampNegativeLodBias = config.getOption ("d3d9.clampNegativeLodBias", false); this->countLosableResources = config.getOption ("d3d9.countLosableResources", true); this->reproducibleCommandStream = config.getOption ("d3d9.reproducibleCommandStream", false); + this->validateShaderInputCount = config.getOption ("d3d9.validateShaderInputCount", false); // D3D8 options this->drefScaling = config.getOption ("d3d8.scaleDref", 0); diff --git a/src/d3d9/d3d9_options.h b/src/d3d9/d3d9_options.h index 7339f8cbb2f..743a5b675be 100644 --- a/src/d3d9/d3d9_options.h +++ b/src/d3d9/d3d9_options.h @@ -153,6 +153,9 @@ namespace dxvk { /// can negatively affect performance. bool reproducibleCommandStream; + // Validate shader input count for PS 3.0 in D3D9ShaderValidator + bool validateShaderInputCount; + /// Enable depth texcoord Z (Dref) scaling (D3D8 quirk) int32_t drefScaling; }; diff --git a/src/d3d9/d3d9_shader_validator.cpp b/src/d3d9/d3d9_shader_validator.cpp index a4f983e53ba..866622ed535 100644 --- a/src/d3d9/d3d9_shader_validator.cpp +++ b/src/d3d9/d3d9_shader_validator.cpp @@ -77,7 +77,7 @@ namespace dxvk { }*/ // a maximum of 10 inputs are supported with PS 3.0 (validation required by The Void) - if (m_isPixelShader && m_majorVersion == 3) { + if (s_validateShaderInputCount && m_isPixelShader && m_majorVersion == 3) { switch (instContext.instruction.opcode) { case DxsoOpcode::Comment: case DxsoOpcode::Def: @@ -193,8 +193,6 @@ namespace dxvk { if (m_callback) m_callback(pFile, Line, Unknown, MessageID, Message.c_str(), m_userData); - // TODO: Consider switching this to debug, once we're - // confident the implementation doesn't cause any issues Logger::warn(Message); m_state = D3D9ShaderValidatorState::Error; @@ -202,4 +200,9 @@ namespace dxvk { return E_FAIL; } + + // s_validateShaderInputCount will be parsed and set appropriately based + // on config options whenever a D3D9InterfaceEx type object is created + bool D3D9ShaderValidator::s_validateShaderInputCount = false; + } \ No newline at end of file diff --git a/src/d3d9/d3d9_shader_validator.h b/src/d3d9/d3d9_shader_validator.h index eff743dc1ad..2df6df457da 100644 --- a/src/d3d9/d3d9_shader_validator.h +++ b/src/d3d9/d3d9_shader_validator.h @@ -76,6 +76,10 @@ namespace dxvk { HRESULT STDMETHODCALLTYPE End(); + static void SetValidateShaderInputCount (bool value) { + s_validateShaderInputCount = value; + } + private: HRESULT ValidateHeader(const char* pFile, UINT Line, const DWORD* pdwInst, DWORD cdw); @@ -89,6 +93,8 @@ namespace dxvk { D3D9ShaderValidatorMessage MessageID, const std::string& Message); + static bool s_validateShaderInputCount; + bool m_isPixelShader = false; uint32_t m_majorVersion = 0; uint32_t m_minorVersion = 0; diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp index 95b15e3abd5..c2fcbe56306 100644 --- a/src/util/config/config.cpp +++ b/src/util/config/config.cpp @@ -1011,6 +1011,11 @@ namespace dxvk { { R"(\\DS\.exe$)", {{ { "d3d9.textureMemory", "0" }, }} }, + /* The Void - Crashes in several locations * + * without shader input count validations */ + { R"(\\The Void\\bin\\win32\\Game\.exe$)", {{ + { "d3d9.validateShaderInputCount", "True" }, + }} }, /**********************************************/ /* D3D8 GAMES */