diff --git a/src/d3d8/d3d8_device.cpp b/src/d3d8/d3d8_device.cpp
index 59b4288f4..888f7b9fd 100644
--- a/src/d3d8/d3d8_device.cpp
+++ b/src/d3d8/d3d8_device.cpp
@@ -1368,6 +1368,27 @@ namespace dxvk {
 
     D3D8Texture2D* tex = static_cast<D3D8Texture2D*>(pTexture);
 
+    // Splinter Cell: Force perspective divide when a shadow map is bound to slot 0
+    if (unlikely(m_d3d8Options.shadowPerspectiveDivide && Stage == 0)) {
+      if (tex) {
+        D3DSURFACE_DESC surf;
+        tex->GetLevelDesc(0, &surf);
+        if (isDepthStencilFormat(surf.Format)) {
+          // If we bound a depth texture to stage 0 then we need to set the projected flag for stage 0 and 1
+          // Stage 1 is a non-depth light cookie texture but still requires perspective divide to work
+          GetD3D9()->SetTextureStageState(0, d3d9::D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_PROJECTED);
+          GetD3D9()->SetTextureStageState(1, d3d9::D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_PROJECTED);
+          m_shadowPerspectiveDivide = true;
+        } else if (m_shadowPerspectiveDivide) {
+          // Non-depth texture bound. Game will reset the transform flags to 0 on its own
+          m_shadowPerspectiveDivide = false;
+        }
+      } else if (m_shadowPerspectiveDivide) {
+        // Texture unbound. Game will reset the transform flags to 0 on its own
+        m_shadowPerspectiveDivide = false;
+      }
+    }
+
     if (unlikely(m_textures[Stage] == tex))
       return D3D_OK;
 
@@ -1401,6 +1422,13 @@ namespace dxvk {
           DWORD                    Value) {
     d3d9::D3DSAMPLERSTATETYPE stateType = GetSamplerStateType9(Type);
 
+    if (unlikely(m_d3d8Options.shadowPerspectiveDivide && Type == D3DTSS_TEXTURETRANSFORMFLAGS)) {
+      // Splinter Cell: Ignore requests to change texture transform flags
+      // to 0 while shadow mapping perspective divide mode is enabled
+      if (m_shadowPerspectiveDivide && (Stage == 0 || Stage == 1))
+        return D3D_OK;
+    }
+
     StateChange();
     if (stateType != -1u) {
       // if the type has been remapped to a sampler state type:
diff --git a/src/d3d8/d3d8_device.h b/src/d3d8/d3d8_device.h
index 7ccbd8bae..7b9ad7b34 100644
--- a/src/d3d8/d3d8_device.h
+++ b/src/d3d8/d3d8_device.h
@@ -393,6 +393,8 @@ namespace dxvk {
       m_backBuffers.resize(m_presentParams.BackBufferCount);
 
       m_autoDepthStencil = nullptr;
+
+      m_shadowPerspectiveDivide = false;
     }
 
     inline void RecreateBackBuffersAndAutoDepthStencil() {
@@ -434,6 +436,8 @@ namespace dxvk {
     // Controls fixed-function exclusive mode (no PS support)
     bool                  m_isFixedFunctionOnly = false;
 
+    bool                  m_shadowPerspectiveDivide = false;
+
     D3D8StateBlock*                            m_recorder = nullptr;
     DWORD                                      m_recorderToken = 0;
     DWORD                                      m_token    = 0;
diff --git a/src/d3d8/d3d8_options.h b/src/d3d8/d3d8_options.h
index ead1b572d..17604d41a 100644
--- a/src/d3d8/d3d8_options.h
+++ b/src/d3d8/d3d8_options.h
@@ -42,13 +42,19 @@ namespace dxvk {
     /// it was brought in line with standard D3D9 behavior.
     bool forceLegacyDiscard = false;
 
+    /// Splinter Cell expects shadow map texture coordinates to be perspective divided
+    /// even though D3DTTFF_PROJECTED is never set for any texture coordinates. This flag
+    /// forces that flag for the necessary stages when a depth texture is bound to slot 0
+    bool shadowPerspectiveDivide = false;
+
     D3D8Options() {}
 
     D3D8Options(const Config& config) {
-      auto forceVsDeclStr     = config.getOption<std::string>("d3d8.forceVsDecl",            "");
-      batching                = config.getOption<bool>       ("d3d8.batching",               batching);
-      placeP8InScratch        = config.getOption<bool>       ("d3d8.placeP8InScratch",       placeP8InScratch);
-      forceLegacyDiscard      = config.getOption<bool>       ("d3d8.forceLegacyDiscard",     forceLegacyDiscard);
+      auto forceVsDeclStr     = config.getOption<std::string>("d3d8.forceVsDecl",             "");
+      batching                = config.getOption<bool>       ("d3d8.batching",                batching);
+      placeP8InScratch        = config.getOption<bool>       ("d3d8.placeP8InScratch",        placeP8InScratch);
+      forceLegacyDiscard      = config.getOption<bool>       ("d3d8.forceLegacyDiscard",      forceLegacyDiscard);
+      shadowPerspectiveDivide = config.getOption<bool>       ("d3d8.shadowPerspectiveDivide", shadowPerspectiveDivide);
 
       parseVsDecl(forceVsDeclStr);
     }
diff --git a/src/util/config/config.cpp b/src/util/config/config.cpp
index c36dbabeb..45e3f8c8d 100644
--- a/src/util/config/config.cpp
+++ b/src/util/config/config.cpp
@@ -1199,6 +1199,7 @@ namespace dxvk {
      * Fixes shadow buffers and alt-tab           */
     { R"(\\splintercell\.exe$)", {{
       { "d3d8.scaleDref",                     "24" },
+      { "d3d8.shadowPerspectiveDivide",     "True" },
       { "d3d9.deviceLossOnFocusLoss",       "True" },
     }} },
     /* Trainz v1.3 (2001)                         *