diff --git a/CMakeLists.txt b/CMakeLists.txt
index e6a43be6ed..8a6df1cc2d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,6 +39,9 @@ include(cmake/compilers.cmake)
# Debug symbol stripping for Release builds (MinGW)
include(cmake/debug_strip.cmake)
+# D3DX dependency verification for NO_D3DX builds (MinGW)
+include(cmake/no_d3dx_verify.cmake)
+
include(FetchContent)
# MinGW-w64 specific configuration
diff --git a/Core/Libraries/Include/Lib/D3DXCompat.h b/Core/Libraries/Include/Lib/D3DXCompat.h
new file mode 100644
index 0000000000..6b0e0260be
--- /dev/null
+++ b/Core/Libraries/Include/Lib/D3DXCompat.h
@@ -0,0 +1,939 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+/**
+ * D3DXCompat.h - D3DX8 compatibility layer using WWMath
+ *
+ * This header provides replacements for D3DX8 math functions using the existing
+ * WWMath library (Westwood Math). This eliminates the need for d3dx8.dll at runtime.
+ *
+ * Usage: Define NO_D3DX before including D3DX8 headers
+ */
+
+#pragma once
+
+#ifdef NO_D3DX
+
+// Prevent min-dx8-sdk headers from defining D3DX functions/types
+// by pre-defining their include guards (Option A: Include Guard Coordination)
+// This allows our compatibility layer to be the sole provider of D3DX functionality
+#ifndef __D3DX8_H__
+#define __D3DX8_H__
+#endif
+
+#ifndef __D3DX8CORE_H__
+#define __D3DX8CORE_H__
+#endif
+
+#ifndef __D3DX8EFFECT_H__
+#define __D3DX8EFFECT_H__
+#endif
+
+#ifndef __D3DX8MATH_H__
+#define __D3DX8MATH_H__
+#endif
+
+#ifndef __D3DX8MATH_INL__
+#define __D3DX8MATH_INL__
+#endif
+
+#ifndef __D3DX8MESH_H__
+#define __D3DX8MESH_H__
+#endif
+
+#ifndef __D3DX8SHAPES_H__
+#define __D3DX8SHAPES_H__
+#endif
+
+#ifndef __D3DX8TEX_H__
+#define __D3DX8TEX_H__
+#endif
+
+// Include D3D8 types
+#include
+#include
+#include
+#include
+#include
+
+//-----------------------------------------------------------------------------
+// D3DX Constants
+//-----------------------------------------------------------------------------
+
+// Default values for D3DX functions
+#ifndef D3DX_DEFAULT
+#define D3DX_DEFAULT ULONG_MAX
+#define D3DX_DEFAULT_FLOAT FLT_MAX
+#endif
+
+// D3DX math constants
+#ifndef D3DX_PI
+#define D3DX_PI ((FLOAT) 3.141592654f)
+#define D3DX_1BYPI ((FLOAT) 0.318309886f)
+#define D3DXToRadian(degree) ((degree) * (D3DX_PI / 180.0f))
+#define D3DXToDegree(radian) ((radian) * (180.0f / D3DX_PI))
+#endif
+
+// D3DX_FILTER flags for texture operations
+#ifndef D3DX_FILTER_NONE
+#define D3DX_FILTER_NONE (1 << 0)
+#define D3DX_FILTER_POINT (2 << 0)
+#define D3DX_FILTER_LINEAR (3 << 0)
+#define D3DX_FILTER_TRIANGLE (4 << 0)
+#define D3DX_FILTER_BOX (5 << 0)
+
+#define D3DX_FILTER_MIRROR_U (1 << 16)
+#define D3DX_FILTER_MIRROR_V (2 << 16)
+#define D3DX_FILTER_MIRROR_W (4 << 16)
+#define D3DX_FILTER_MIRROR (7 << 16)
+#define D3DX_FILTER_DITHER (8 << 16)
+#endif
+
+//-----------------------------------------------------------------------------
+
+// WWMath headers for D3DX math function implementations.
+// CppMacros.h must be included first because vector3.h -> STLUtils.h uses CPP_11.
+// D3DXWrapper.h is force-included via -include flag which runs before
+// precompiled headers, so we cannot rely on PCH here.
+#ifdef __cplusplus
+ #include "Utility/CppMacros.h"
+ #include "vector3.h"
+ #include "vector4.h"
+ #include "matrix3d.h"
+ #include "matrix4.h"
+#endif
+
+// Forward declare D3DX types (we'll define compatibility layer)
+typedef struct D3DXVECTOR3 D3DXVECTOR3;
+typedef struct D3DXVECTOR4 D3DXVECTOR4;
+typedef struct D3DXMATRIX D3DXMATRIX;
+
+// D3DX8 Vector and Matrix types - map to D3D types
+// Note: D3DXVECTOR3 is identical to D3DVECTOR (Direct3D 8 type)
+// Note: D3DXVECTOR4 is a simple {x,y,z,w} structure
+// Note: D3DXMATRIX is identical to D3DMATRIX (Direct3D 8 type)
+
+#ifdef __cplusplus
+
+struct D3DXVECTOR3
+{
+ float x, y, z;
+
+ D3DXVECTOR3() {}
+ D3DXVECTOR3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
+ D3DXVECTOR3(const Vector3& v) : x(v.X), y(v.Y), z(v.Z) {}
+
+ operator Vector3() const { return Vector3(x, y, z); }
+
+ // Array access operator for compatibility
+ float& operator[](int i) { return (&x)[i]; }
+ const float& operator[](int i) const { return (&x)[i]; }
+};
+
+struct D3DXVECTOR4
+{
+ float x, y, z, w;
+
+ D3DXVECTOR4() {}
+ D3DXVECTOR4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) {}
+ D3DXVECTOR4(const Vector4& v) : x(v.X), y(v.Y), z(v.Z), w(v.W) {}
+
+ operator Vector4() const { return Vector4(x, y, z, w); }
+
+ // Conversion to pointer for passing to D3D functions
+ operator const float*() const { return &x; }
+ operator float*() { return &x; }
+
+ // Array access operator for compatibility
+ float& operator[](int i) { return (&x)[i]; }
+ const float& operator[](int i) const { return (&x)[i]; }
+};
+
+struct D3DXMATRIX : public D3DMATRIX
+{
+ D3DXMATRIX() {}
+
+ // Constructor from 16 float values (row-major order)
+ D3DXMATRIX(float m00, float m01, float m02, float m03,
+ float m10, float m11, float m12, float m13,
+ float m20, float m21, float m22, float m23,
+ float m30, float m31, float m32, float m33)
+ {
+ // D3DMATRIX uses _11, _12, ... _44 notation
+ _11 = m00; _12 = m01; _13 = m02; _14 = m03;
+ _21 = m10; _22 = m11; _23 = m12; _24 = m13;
+ _31 = m20; _32 = m21; _33 = m22; _34 = m23;
+ _41 = m30; _42 = m31; _43 = m32; _44 = m33;
+ }
+
+ D3DXMATRIX(const Matrix4x4& m)
+ {
+ // Matrix4x4 and D3DMATRIX have transposed layouts - use proper conversion
+ // See To_D3DMATRIX in matrix4.cpp for reference
+ _11 = m[0][0]; _12 = m[1][0]; _13 = m[2][0]; _14 = m[3][0];
+ _21 = m[0][1]; _22 = m[1][1]; _23 = m[2][1]; _24 = m[3][1];
+ _31 = m[0][2]; _32 = m[1][2]; _33 = m[2][2]; _34 = m[3][2];
+ _41 = m[0][3]; _42 = m[1][3]; _43 = m[2][3]; _44 = m[3][3];
+ }
+
+ operator Matrix4x4() const
+ {
+ // D3DMATRIX and Matrix4x4 have transposed layouts - use proper conversion
+ // See To_Matrix4x4 in matrix4.cpp for reference
+ Matrix4x4 result;
+ result[0][0] = _11; result[0][1] = _21; result[0][2] = _31; result[0][3] = _41;
+ result[1][0] = _12; result[1][1] = _22; result[1][2] = _32; result[1][3] = _42;
+ result[2][0] = _13; result[2][1] = _23; result[2][2] = _33; result[2][3] = _43;
+ result[3][0] = _14; result[3][1] = _24; result[3][2] = _34; result[3][3] = _44;
+ return result;
+ }
+
+ // Casting operators
+ operator float*() { return reinterpret_cast(&_11); }
+ operator const float*() const { return reinterpret_cast(&_11); }
+
+ // operator*= for matrix multiplication (native implementation)
+ D3DXMATRIX& operator*=(const D3DXMATRIX& other)
+ {
+ D3DXMATRIX temp;
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ temp.m[i][j] = m[i][0] * other.m[0][j] +
+ m[i][1] * other.m[1][j] +
+ m[i][2] * other.m[2][j] +
+ m[i][3] * other.m[3][j];
+ }
+ }
+ *this = temp;
+ return *this;
+ }
+};
+
+//=============================================================================
+// D3DX8 Math Functions - Native Compatibility Layer
+//=============================================================================
+
+//-----------------------------------------------------------------------------
+// Vector4 Operations
+//-----------------------------------------------------------------------------
+
+// D3DXVec4Dot - Compute dot product of two 4D vectors
+inline float D3DXVec4Dot(const D3DXVECTOR4* pV1, const D3DXVECTOR4* pV2)
+{
+ if (!pV1 || !pV2)
+ return 0.0f;
+
+ Vector4 v1(pV1->x, pV1->y, pV1->z, pV1->w);
+ Vector4 v2(pV2->x, pV2->y, pV2->z, pV2->w);
+
+ return Vector4::Dot_Product(v1, v2);
+}
+
+// D3DXVec4Transform - Transform 4D vector by 4x4 matrix
+// D3D convention: row vector * matrix, i.e. [x,y,z,w] * M
+inline D3DXVECTOR4* D3DXVec4Transform(
+ D3DXVECTOR4* pOut,
+ const D3DXVECTOR4* pV,
+ const D3DXMATRIX* pM)
+{
+ if (!pOut || !pV || !pM)
+ return pOut;
+
+ // D3D uses row vectors: result = [x,y,z,w] * M
+ float x = pV->x, y = pV->y, z = pV->z, w = pV->w;
+ pOut->x = x * pM->_11 + y * pM->_21 + z * pM->_31 + w * pM->_41;
+ pOut->y = x * pM->_12 + y * pM->_22 + z * pM->_32 + w * pM->_42;
+ pOut->z = x * pM->_13 + y * pM->_23 + z * pM->_33 + w * pM->_43;
+ pOut->w = x * pM->_14 + y * pM->_24 + z * pM->_34 + w * pM->_44;
+
+ return pOut;
+}
+
+//-----------------------------------------------------------------------------
+// Vector3 Operations
+//-----------------------------------------------------------------------------
+
+// D3DXVec3Transform - Transform 3D vector by 4x4 matrix (homogeneous)
+// D3D convention: row vector * matrix, i.e. [x,y,z,1] * M
+inline D3DXVECTOR4* D3DXVec3Transform(
+ D3DXVECTOR4* pOut,
+ const D3DXVECTOR3* pV,
+ const D3DXMATRIX* pM)
+{
+ if (!pOut || !pV || !pM)
+ return pOut;
+
+ // D3D uses row vectors: result = [x,y,z,1] * M
+ float x = pV->x, y = pV->y, z = pV->z;
+ pOut->x = x * pM->_11 + y * pM->_21 + z * pM->_31 + pM->_41;
+ pOut->y = x * pM->_12 + y * pM->_22 + z * pM->_32 + pM->_42;
+ pOut->z = x * pM->_13 + y * pM->_23 + z * pM->_33 + pM->_43;
+ pOut->w = x * pM->_14 + y * pM->_24 + z * pM->_34 + pM->_44;
+
+ return pOut;
+}
+
+//-----------------------------------------------------------------------------
+// Matrix Operations
+//-----------------------------------------------------------------------------
+
+// D3DXMatrixTranspose - Transpose a 4x4 matrix
+inline D3DXMATRIX* D3DXMatrixTranspose(
+ D3DXMATRIX* pOut,
+ const D3DXMATRIX* pM)
+{
+ if (!pOut || !pM)
+ return pOut;
+
+ // Native transpose: out[i][j] = in[j][i]
+ // Use temp to handle in-place transpose (pOut == pM)
+ D3DXMATRIX temp;
+ temp._11 = pM->_11; temp._12 = pM->_21; temp._13 = pM->_31; temp._14 = pM->_41;
+ temp._21 = pM->_12; temp._22 = pM->_22; temp._23 = pM->_32; temp._24 = pM->_42;
+ temp._31 = pM->_13; temp._32 = pM->_23; temp._33 = pM->_33; temp._34 = pM->_43;
+ temp._41 = pM->_14; temp._42 = pM->_24; temp._43 = pM->_34; temp._44 = pM->_44;
+ *pOut = temp;
+
+ return pOut;
+}
+
+// D3DXMatrixInverse - Compute inverse of a 4x4 matrix
+// Returns nullptr if the matrix is singular (determinant near zero).
+inline D3DXMATRIX* D3DXMatrixInverse(
+ D3DXMATRIX* pOut,
+ float* pDeterminant,
+ const D3DXMATRIX* pM)
+{
+ if (!pOut || !pM)
+ return pOut;
+
+ const float* m = static_cast(*pM);
+ float v[16], t[6], det;
+
+ // Calculate pairs for first 8 cofactors
+ t[0] = m[10] * m[15] - m[11] * m[14];
+ t[1] = m[9] * m[15] - m[11] * m[13];
+ t[2] = m[9] * m[14] - m[10] * m[13];
+ t[3] = m[8] * m[15] - m[11] * m[12];
+ t[4] = m[8] * m[14] - m[10] * m[12];
+ t[5] = m[8] * m[13] - m[9] * m[12];
+
+ // Calculate first 4 cofactors
+ v[0] = m[5] * t[0] - m[6] * t[1] + m[7] * t[2];
+ v[4] = -(m[4] * t[0] - m[6] * t[3] + m[7] * t[4]);
+ v[8] = m[4] * t[1] - m[5] * t[3] + m[7] * t[5];
+ v[12] = -(m[4] * t[2] - m[5] * t[4] + m[6] * t[5]);
+
+ // Calculate determinant
+ det = m[0] * v[0] + m[1] * v[4] + m[2] * v[8] + m[3] * v[12];
+
+ if (pDeterminant)
+ *pDeterminant = det;
+
+ // Check for singular matrix
+ if (fabsf(det) < 1e-10f)
+ return nullptr;
+
+ // Calculate pairs for second 8 cofactors
+ t[0] = m[2] * m[7] - m[3] * m[6];
+ t[1] = m[1] * m[7] - m[3] * m[5];
+ t[2] = m[1] * m[6] - m[2] * m[5];
+ t[3] = m[0] * m[7] - m[3] * m[4];
+ t[4] = m[0] * m[6] - m[2] * m[4];
+ t[5] = m[0] * m[5] - m[1] * m[4];
+
+ v[1] = -(m[1] * (m[10] * m[15] - m[11] * m[14]) - m[2] * (m[9] * m[15] - m[11] * m[13]) + m[3] * (m[9] * m[14] - m[10] * m[13]));
+ v[5] = m[0] * (m[10] * m[15] - m[11] * m[14]) - m[2] * (m[8] * m[15] - m[11] * m[12]) + m[3] * (m[8] * m[14] - m[10] * m[12]);
+ v[9] = -(m[0] * (m[9] * m[15] - m[11] * m[13]) - m[1] * (m[8] * m[15] - m[11] * m[12]) + m[3] * (m[8] * m[13] - m[9] * m[12]));
+ v[13] = m[0] * (m[9] * m[14] - m[10] * m[13]) - m[1] * (m[8] * m[14] - m[10] * m[12]) + m[2] * (m[8] * m[13] - m[9] * m[12]);
+
+ v[2] = m[13] * t[0] - m[14] * t[1] + m[15] * t[2];
+ v[6] = -(m[12] * t[0] - m[14] * t[3] + m[15] * t[4]);
+ v[10] = m[12] * t[1] - m[13] * t[3] + m[15] * t[5];
+ v[14] = -(m[12] * t[2] - m[13] * t[4] + m[14] * t[5]);
+
+ v[3] = -(m[9] * t[0] - m[10] * t[1] + m[11] * t[2]);
+ v[7] = m[8] * t[0] - m[10] * t[3] + m[11] * t[4];
+ v[11] = -(m[8] * t[1] - m[9] * t[3] + m[11] * t[5]);
+ v[15] = m[8] * t[2] - m[9] * t[4] + m[10] * t[5];
+
+ // Divide by determinant
+ det = 1.0f / det;
+ float* out = static_cast(*pOut);
+ for (int i = 0; i < 16; i++)
+ out[i] = v[i] * det;
+
+ return pOut;
+}
+
+//-----------------------------------------------------------------------------
+// Utility Functions
+//-----------------------------------------------------------------------------
+
+// D3DXGetErrorStringA - Get error string for D3D error code
+inline HRESULT D3DXGetErrorStringA(HRESULT hr, char* pBuffer, UINT BufferLen)
+{
+ if (!pBuffer || BufferLen == 0)
+ return E_INVALIDARG;
+
+ const char* errorStr = nullptr;
+
+ switch (hr)
+ {
+ case D3D_OK:
+ errorStr = "No error";
+ break;
+ case D3DERR_WRONGTEXTUREFORMAT:
+ errorStr = "Wrong texture format";
+ break;
+ case D3DERR_UNSUPPORTEDCOLOROPERATION:
+ errorStr = "Unsupported color operation";
+ break;
+ case D3DERR_UNSUPPORTEDCOLORARG:
+ errorStr = "Unsupported color argument";
+ break;
+ case D3DERR_UNSUPPORTEDALPHAOPERATION:
+ errorStr = "Unsupported alpha operation";
+ break;
+ case D3DERR_UNSUPPORTEDALPHAARG:
+ errorStr = "Unsupported alpha argument";
+ break;
+ case D3DERR_TOOMANYOPERATIONS:
+ errorStr = "Too many operations";
+ break;
+ case D3DERR_CONFLICTINGTEXTUREFILTER:
+ errorStr = "Conflicting texture filter";
+ break;
+ case D3DERR_UNSUPPORTEDFACTORVALUE:
+ errorStr = "Unsupported factor value";
+ break;
+ case D3DERR_CONFLICTINGRENDERSTATE:
+ errorStr = "Conflicting render state";
+ break;
+ case D3DERR_UNSUPPORTEDTEXTUREFILTER:
+ errorStr = "Unsupported texture filter";
+ break;
+ case D3DERR_CONFLICTINGTEXTUREPALETTE:
+ errorStr = "Conflicting texture palette";
+ break;
+ case D3DERR_DRIVERINTERNALERROR:
+ errorStr = "Driver internal error";
+ break;
+ case D3DERR_NOTFOUND:
+ errorStr = "Not found";
+ break;
+ case D3DERR_MOREDATA:
+ errorStr = "More data";
+ break;
+ case D3DERR_DEVICELOST:
+ errorStr = "Device lost";
+ break;
+ case D3DERR_DEVICENOTRESET:
+ errorStr = "Device not reset";
+ break;
+ case D3DERR_NOTAVAILABLE:
+ errorStr = "Not available";
+ break;
+ case D3DERR_OUTOFVIDEOMEMORY:
+ errorStr = "Out of video memory";
+ break;
+ case D3DERR_INVALIDDEVICE:
+ errorStr = "Invalid device";
+ break;
+ case D3DERR_INVALIDCALL:
+ errorStr = "Invalid call";
+ break;
+ case D3DERR_DRIVERINVALIDCALL:
+ errorStr = "Driver invalid call";
+ break;
+ case E_OUTOFMEMORY:
+ errorStr = "Out of memory";
+ break;
+ default:
+ errorStr = "Unknown error";
+ break;
+ }
+
+ // Copy error string to buffer (ensure null termination)
+ strncpy(pBuffer, errorStr, BufferLen - 1);
+ pBuffer[BufferLen - 1] = '\0';
+
+ return D3D_OK;
+}
+
+// D3DXGetFVFVertexSize - Calculate vertex size from FVF flags
+inline UINT D3DXGetFVFVertexSize(DWORD FVF)
+{
+ UINT size = 0;
+
+ // Position formats - use mask and switch since XYZB* bits overlap XYZ/XYZRHW
+ // (Wine d3dx9_36/mesh.c uses the same approach)
+ switch (FVF & D3DFVF_POSITION_MASK)
+ {
+ case D3DFVF_XYZ: size += 12; break; // 3 floats
+ case D3DFVF_XYZRHW: size += 16; break; // 4 floats (x, y, z, rhw)
+ case D3DFVF_XYZB1: size += 16; break; // 3 floats + 1 blend weight
+ case D3DFVF_XYZB2: size += 20; break; // 3 floats + 2 blend weights
+ case D3DFVF_XYZB3: size += 24; break; // 3 floats + 3 blend weights
+ case D3DFVF_XYZB4: size += 28; break; // 3 floats + 4 blend weights
+ case D3DFVF_XYZB5: size += 32; break; // 3 floats + 5 blend weights
+ default: break;
+ }
+
+ // Normal
+ if (FVF & D3DFVF_NORMAL) size += 12; // 3 floats
+
+ // Point size
+ if (FVF & D3DFVF_PSIZE) size += 4; // 1 float
+
+ // Diffuse color
+ if (FVF & D3DFVF_DIFFUSE) size += 4; // 1 DWORD (D3DCOLOR)
+
+ // Specular color
+ if (FVF & D3DFVF_SPECULAR) size += 4; // 1 DWORD (D3DCOLOR)
+
+ // Texture coordinates
+ UINT texCount = (FVF & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT;
+ for (UINT i = 0; i < texCount; i++)
+ {
+ DWORD texFormat = (FVF >> (16 + i * 2)) & 0x3;
+ switch (texFormat)
+ {
+ case D3DFVF_TEXTUREFORMAT1: size += 4; break; // 1 float
+ case D3DFVF_TEXTUREFORMAT2: size += 8; break; // 2 floats
+ case D3DFVF_TEXTUREFORMAT3: size += 12; break; // 3 floats
+ case D3DFVF_TEXTUREFORMAT4: size += 16; break; // 4 floats
+ default: size += 8; break; // Default to 2 floats
+ }
+ }
+
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+// Matrix Operations (Additional)
+//-----------------------------------------------------------------------------
+
+// D3DXMatrixMultiply - Multiply two matrices
+inline D3DXMATRIX* D3DXMatrixMultiply(
+ D3DXMATRIX* pOut,
+ const D3DXMATRIX* pM1,
+ const D3DXMATRIX* pM2)
+{
+ if (!pOut || !pM1 || !pM2)
+ return pOut;
+
+ // Native implementation - handles aliasing via temp
+ D3DXMATRIX temp;
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ temp.m[i][j] = pM1->m[i][0] * pM2->m[0][j] +
+ pM1->m[i][1] * pM2->m[1][j] +
+ pM1->m[i][2] * pM2->m[2][j] +
+ pM1->m[i][3] * pM2->m[3][j];
+ }
+ }
+ *pOut = temp;
+
+ return pOut;
+}
+
+// D3DXMatrixRotationZ - Create rotation matrix around Z axis
+inline D3DXMATRIX* D3DXMatrixRotationZ(D3DXMATRIX* pOut, float angle)
+{
+ if (!pOut)
+ return pOut;
+
+ float c = cosf(angle);
+ float s = sinf(angle);
+
+ pOut->_11 = c; pOut->_12 = s; pOut->_13 = 0; pOut->_14 = 0;
+ pOut->_21 = -s; pOut->_22 = c; pOut->_23 = 0; pOut->_24 = 0;
+ pOut->_31 = 0; pOut->_32 = 0; pOut->_33 = 1; pOut->_34 = 0;
+ pOut->_41 = 0; pOut->_42 = 0; pOut->_43 = 0; pOut->_44 = 1;
+
+ return pOut;
+}
+
+// D3DXMatrixScaling - Create scaling matrix
+inline D3DXMATRIX* D3DXMatrixScaling(D3DXMATRIX* pOut, float sx, float sy, float sz)
+{
+ if (!pOut)
+ return pOut;
+
+ pOut->_11 = sx; pOut->_12 = 0; pOut->_13 = 0; pOut->_14 = 0;
+ pOut->_21 = 0; pOut->_22 = sy; pOut->_23 = 0; pOut->_24 = 0;
+ pOut->_31 = 0; pOut->_32 = 0; pOut->_33 = sz; pOut->_34 = 0;
+ pOut->_41 = 0; pOut->_42 = 0; pOut->_43 = 0; pOut->_44 = 1;
+
+ return pOut;
+}
+
+// D3DXMatrixTranslation - Create translation matrix
+inline D3DXMATRIX* D3DXMatrixTranslation(D3DXMATRIX* pOut, float x, float y, float z)
+{
+ if (!pOut)
+ return pOut;
+
+ pOut->_11 = 1; pOut->_12 = 0; pOut->_13 = 0; pOut->_14 = 0;
+ pOut->_21 = 0; pOut->_22 = 1; pOut->_23 = 0; pOut->_24 = 0;
+ pOut->_31 = 0; pOut->_32 = 0; pOut->_33 = 1; pOut->_34 = 0;
+ pOut->_41 = x; pOut->_42 = y; pOut->_43 = z; pOut->_44 = 1;
+
+ return pOut;
+}
+
+// D3DXMatrixIdentity - Initialize matrix to identity
+inline D3DXMATRIX* D3DXMatrixIdentity(D3DXMATRIX* pOut)
+{
+ if (!pOut)
+ return pOut;
+
+ pOut->_11 = 1; pOut->_12 = 0; pOut->_13 = 0; pOut->_14 = 0;
+ pOut->_21 = 0; pOut->_22 = 1; pOut->_23 = 0; pOut->_24 = 0;
+ pOut->_31 = 0; pOut->_32 = 0; pOut->_33 = 1; pOut->_34 = 0;
+ pOut->_41 = 0; pOut->_42 = 0; pOut->_43 = 0; pOut->_44 = 1;
+
+ return pOut;
+}
+
+//-----------------------------------------------------------------------------
+// Shader Functions (Precompiled Shaders)
+//-----------------------------------------------------------------------------
+
+// Forward declaration
+struct ID3DXBuffer;
+
+// Precompiled shader bytecode (generated from .psh files)
+namespace D3DXCompat_Shaders {
+
+ // Note: These are simplified PS 1.1 bytecode arrays
+ // Generated by scripts/compile_shaders.py from scripts/shaders/water_shader*.psh files
+ // May need validation/correction for production use
+
+ // River water shader (water_shader1.psh)
+ static constexpr DWORD shader1_bytecode[] = {
+ 0xFFFF0101, 0x00000042, 0x000F0300, 0x00000042,
+ 0x000F0301, 0x00000042, 0x000F0302, 0x00000042,
+ 0x000F0303, 0x00000005, 0x000F0000, 0x000F0100,
+ 0x000F0300, 0x00000005, 0x000F0001, 0x000F0301,
+ 0x000F0302, 0x00000003, 0x00070000, 0x000F0000,
+ 0x000F0303, 0x40000005, 0x00080000, 0x000F0000,
+ 0x000F0303, 0x00000003, 0x00070000, 0x000F0000,
+ 0x000F0001, 0x0000FFFF,
+ };
+
+ // Water with environment mapping (water_shader2.psh)
+ static constexpr DWORD shader2_bytecode[] = {
+ 0xFFFF0101, 0x00000042, 0x000F0300, 0x00000042,
+ 0x000F0301, 0x00000043, 0x000F0302, 0x000F0301,
+ 0x00000005, 0x000F0000, 0x000F0100, 0x000F0300,
+ 0x00000005, 0x00070001, 0x000F0302, 0x000F0200,
+ 0x00000003, 0x00070000, 0x000F0000, 0x000F0001,
+ 0x0000FFFF,
+ };
+
+ // Trapezoid water shader (water_shader3.psh)
+ static constexpr DWORD shader3_bytecode[] = {
+ 0xFFFF0101, 0x00000042, 0x000F0300, 0x00000042,
+ 0x000F0301, 0x00000042, 0x000F0302, 0x00000042,
+ 0x000F0303, 0x00000005, 0x000F0000, 0x000F0100,
+ 0x000F0300, 0x00000004, 0x00070000, 0x000F0301,
+ 0x000F0302, 0x000F0000, 0x00000005, 0x00070000,
+ 0x000F0000, 0x000F0303, 0x0000FFFF,
+ };
+
+} // namespace D3DXCompat_Shaders
+
+// ID3DXBuffer interface definition
+struct ID3DXBuffer
+{
+ virtual HRESULT __stdcall QueryInterface(const IID&, void**) = 0;
+ virtual ULONG __stdcall AddRef() = 0;
+ virtual ULONG __stdcall Release() = 0;
+ virtual void* __stdcall GetBufferPointer() = 0;
+ virtual DWORD __stdcall GetBufferSize() = 0;
+};
+
+// ID3DXBuffer implementation for shader bytecode
+class D3DXShaderBuffer : public ID3DXBuffer
+{
+private:
+ const DWORD* m_pData;
+ DWORD m_Size;
+ mutable LONG m_RefCount;
+
+public:
+ D3DXShaderBuffer(const DWORD* pData, DWORD size)
+ : m_pData(pData), m_Size(size), m_RefCount(1) {}
+
+ virtual ~D3DXShaderBuffer() {}
+
+ // IUnknown methods
+ virtual HRESULT __stdcall QueryInterface(const IID& riid, void** ppvObject)
+ {
+ if (!ppvObject)
+ return E_POINTER;
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ virtual ULONG __stdcall AddRef()
+ {
+ return InterlockedIncrement(&m_RefCount);
+ }
+
+ virtual ULONG __stdcall Release()
+ {
+ LONG ref = InterlockedDecrement(&m_RefCount);
+ if (ref == 0)
+ delete this;
+ return ref;
+ }
+
+ // ID3DXBuffer methods
+ virtual void* __stdcall GetBufferPointer()
+ {
+ return (void*)m_pData;
+ }
+
+ virtual DWORD __stdcall GetBufferSize()
+ {
+ return m_Size;
+ }
+};
+
+// D3DXAssembleShader - Returns precompiled bytecode for known water shaders.
+// Recognizes the three water shaders used by the game and returns precompiled
+// bytecode instead of performing runtime assembly.
+inline HRESULT D3DXAssembleShader(
+ const char* pSrcData,
+ UINT SrcDataLen,
+ DWORD Flags,
+ void* ppConstants,
+ ID3DXBuffer** ppCompiledShader,
+ ID3DXBuffer** ppCompilationErrors)
+{
+ if (!pSrcData || !ppCompiledShader)
+ return D3DERR_INVALIDCALL;
+
+ *ppCompiledShader = nullptr;
+ if (ppCompilationErrors)
+ *ppCompilationErrors = nullptr;
+
+ // Ensure null-terminated copy for safe string operations.
+ // D3DX8 API allows non-null-terminated input bounded by SrcDataLen,
+ // matching Wine's D3DAssemble implementation which also null-terminates.
+ std::string srcStr(pSrcData, SrcDataLen);
+ const char* src = srcStr.c_str();
+
+ // Identify which shader is being assembled by matching key strings.
+
+ // Shader 1: River water (has "+mul r0.a, r0, t3" - co-issued instruction)
+ if (strstr(src, "+mul r0.a") != nullptr)
+ {
+ *ppCompiledShader = new D3DXShaderBuffer(
+ D3DXCompat_Shaders::shader1_bytecode,
+ sizeof(D3DXCompat_Shaders::shader1_bytecode));
+ return S_OK;
+ }
+
+ // Shader 2: Water with env mapping (has "texbem")
+ if (strstr(src, "texbem") != nullptr)
+ {
+ *ppCompiledShader = new D3DXShaderBuffer(
+ D3DXCompat_Shaders::shader2_bytecode,
+ sizeof(D3DXCompat_Shaders::shader2_bytecode));
+ return S_OK;
+ }
+
+ // Shader 3: Trapezoid water (has "mad" instruction)
+ if (strstr(src, "mad") != nullptr)
+ {
+ *ppCompiledShader = new D3DXShaderBuffer(
+ D3DXCompat_Shaders::shader3_bytecode,
+ sizeof(D3DXCompat_Shaders::shader3_bytecode));
+ return S_OK;
+ }
+
+ // Unknown shader - return error.
+ // The game will handle this gracefully and use fallback rendering.
+ return E_FAIL;
+}
+
+#endif // __cplusplus
+
+//-----------------------------------------------------------------------------
+// Texture Functions (Direct3D 8 wrappers and stubs)
+// These don't need WWMath
+//-----------------------------------------------------------------------------
+
+#ifdef __cplusplus
+
+// D3DXCreateTexture - Direct wrapper for IDirect3DDevice8::CreateTexture
+inline HRESULT D3DXCreateTexture(
+ LPDIRECT3DDEVICE8 pDevice,
+ UINT Width,
+ UINT Height,
+ UINT MipLevels,
+ DWORD Usage,
+ D3DFORMAT Format,
+ D3DPOOL Pool,
+ LPDIRECT3DTEXTURE8* ppTexture)
+{
+ if (!pDevice || !ppTexture)
+ return D3DERR_INVALIDCALL;
+
+ // Direct D3D8 call - no D3DX involved!
+ return pDevice->CreateTexture(Width, Height, MipLevels, Usage, Format, Pool, ppTexture);
+}
+
+// D3DXCreateCubeTexture - Direct wrapper for IDirect3DDevice8::CreateCubeTexture
+inline HRESULT D3DXCreateCubeTexture(
+ LPDIRECT3DDEVICE8 pDevice,
+ UINT Size,
+ UINT MipLevels,
+ DWORD Usage,
+ D3DFORMAT Format,
+ D3DPOOL Pool,
+ LPDIRECT3DCUBETEXTURE8* ppCubeTexture)
+{
+ if (!pDevice || !ppCubeTexture)
+ return D3DERR_INVALIDCALL;
+
+ // Direct D3D8 call
+ return pDevice->CreateCubeTexture(Size, MipLevels, Usage, Format, Pool, ppCubeTexture);
+}
+
+// D3DXCreateVolumeTexture - Direct wrapper for IDirect3DDevice8::CreateVolumeTexture
+inline HRESULT D3DXCreateVolumeTexture(
+ LPDIRECT3DDEVICE8 pDevice,
+ UINT Width,
+ UINT Height,
+ UINT Depth,
+ UINT MipLevels,
+ DWORD Usage,
+ D3DFORMAT Format,
+ D3DPOOL Pool,
+ LPDIRECT3DVOLUMETEXTURE8* ppVolumeTexture)
+{
+ if (!pDevice || !ppVolumeTexture)
+ return D3DERR_INVALIDCALL;
+
+ // Direct D3D8 call
+ return pDevice->CreateVolumeTexture(Width, Height, Depth, MipLevels, Usage, Format, Pool, ppVolumeTexture);
+}
+
+// D3DXCreateTextureFromFileExA - Stub (zero callers in codebase)
+// Returns D3DERR_NOTAVAILABLE causing fallback to MissingTexture.
+inline HRESULT D3DXCreateTextureFromFileExA(
+ LPDIRECT3DDEVICE8 pDevice,
+ LPCSTR pSrcFile,
+ UINT Width,
+ UINT Height,
+ UINT MipLevels,
+ DWORD Usage,
+ D3DFORMAT Format,
+ D3DPOOL Pool,
+ DWORD Filter,
+ DWORD MipFilter,
+ D3DCOLOR ColorKey,
+ void* pSrcInfo,
+ PALETTEENTRY* pPalette,
+ LPDIRECT3DTEXTURE8* ppTexture)
+{
+ // NOTE: Zero usage in codebase (verified via grep).
+ // Returning D3DERR_NOTAVAILABLE causes fallback to MissingTexture.
+ // Stub function acceptable for unused functionality.
+ return D3DERR_NOTAVAILABLE;
+}
+
+// D3DXLoadSurfaceFromSurface - Copy surface data using D3D8's native CopyRects
+inline HRESULT D3DXLoadSurfaceFromSurface(
+ LPDIRECT3DSURFACE8 pDestSurface,
+ const PALETTEENTRY* pDestPalette,
+ const RECT* pDestRect,
+ LPDIRECT3DSURFACE8 pSrcSurface,
+ const PALETTEENTRY* pSrcPalette,
+ const RECT* pSrcRect,
+ DWORD Filter,
+ D3DCOLOR ColorKey)
+{
+ if (!pDestSurface || !pSrcSurface)
+ return D3DERR_INVALIDCALL;
+
+ // Get D3D8 device from source surface
+ IDirect3DDevice8* pDevice = nullptr;
+ HRESULT hr = pSrcSurface->GetDevice(&pDevice);
+ if (FAILED(hr))
+ return hr;
+
+ // Convert destination RECT to POINT for CopyRects API
+ POINT destPoint = {0, 0};
+ if (pDestRect) {
+ destPoint.x = pDestRect->left;
+ destPoint.y = pDestRect->top;
+ }
+
+ // Use D3D8's native hardware-accelerated CopyRects
+ // This is the same API used by DX8Wrapper::_Copy_DX8_Rects (14 uses in codebase)
+ hr = pDevice->CopyRects(
+ pSrcSurface,
+ pSrcRect, // Source rect (nullptr = entire surface)
+ pSrcRect ? 1 : 0, // Number of rects (0 = full surface)
+ pDestSurface,
+ pDestRect ? &destPoint : nullptr // Dest point (nullptr = 0,0)
+ );
+
+ pDevice->Release();
+ return hr;
+}
+
+// D3DXFilterTexture - Stub (no-op, textures use pre-existing mipmaps)
+inline HRESULT D3DXFilterTexture(
+ LPDIRECT3DBASETEXTURE8 pTexture,
+ const PALETTEENTRY* pPalette,
+ UINT SrcLevel,
+ DWORD Filter)
+{
+ // Stub: No-op, textures use pre-existing mipmaps
+ return D3D_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Font Functions (Stubs for unused functionality)
+//-----------------------------------------------------------------------------
+
+// Forward declaration for D3DX Font interface
+struct ID3DXFont;
+typedef struct ID3DXFont *LPD3DXFONT;
+
+// D3DXCreateFont - Stub (WorldBuilder only, not in MinGW build scope)
+inline HRESULT D3DXCreateFont(
+ LPDIRECT3DDEVICE8 pDevice,
+ HFONT hFont,
+ LPD3DXFONT* ppFont)
+{
+ // WorldBuilder not in build scope - stub acceptable
+ if (ppFont) *ppFont = nullptr;
+ return D3DERR_NOTAVAILABLE;
+}
+
+#endif // __cplusplus
+
+#endif // NO_D3DX
diff --git a/Core/Libraries/Include/Lib/D3DXWrapper.h b/Core/Libraries/Include/Lib/D3DXWrapper.h
new file mode 100644
index 0000000000..bbcdd2b4ff
--- /dev/null
+++ b/Core/Libraries/Include/Lib/D3DXWrapper.h
@@ -0,0 +1,44 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+/**
+ * D3DXWrapper.h - Conditional D3DX8 include
+ *
+ * Include this file instead of directly.
+ * When NO_D3DX is defined, uses compatibility layer.
+ * Otherwise, uses standard D3DX8 headers.
+ *
+ * NOTE: This header is safe to force-include globally.
+ * For C files, it does nothing when NO_D3DX is defined.
+ */
+
+#pragma once
+
+// Only provide D3DX replacements for C++ files
+// C files that don't use D3DX will be unaffected
+#ifdef __cplusplus
+
+#ifdef NO_D3DX
+ // Use compatibility layer - no DLL dependency
+ #include "D3DXCompat.h"
+#else
+ // Use standard D3DX8 headers - requires d3dx8.dll
+ #include
+#endif
+
+#endif // __cplusplus
diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt
index d8a1a27577..48d6c3f0af 100644
--- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt
+++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt
@@ -248,3 +248,8 @@ target_link_libraries(corei_ww3d2 INTERFACE
core_wwlib
core_wwmath
)
+
+# Apply D3DXWrapper forced include for NO_D3DX builds
+if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE)
+ target_compile_options(corei_ww3d2 INTERFACE ${MINGW_D3DX_WRAPPER_INCLUDE})
+endif()
diff --git a/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt
index dca9eb68fe..667bec3b4d 100644
--- a/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt
+++ b/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt
@@ -93,3 +93,8 @@ target_link_libraries(core_wwmath PRIVATE
# @todo Test its impact and see what to do with the legacy functions.
#add_compile_definitions(core_wwmath PUBLIC ALLOW_TEMPORARIES) # Enables legacy math with "temporaries"
+
+# Apply D3DXWrapper forced include for NO_D3DX builds
+if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE)
+ target_compile_options(core_wwmath PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE})
+endif()
diff --git a/Generals/Code/GameEngine/CMakeLists.txt b/Generals/Code/GameEngine/CMakeLists.txt
index 217d7dcecb..6e29a6a37d 100644
--- a/Generals/Code/GameEngine/CMakeLists.txt
+++ b/Generals/Code/GameEngine/CMakeLists.txt
@@ -1102,3 +1102,8 @@ target_precompile_headers(g_gameengine PRIVATE
[["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6
Include/Precompiled/PreRTS.h
)
+
+# Apply D3DXWrapper forced include for NO_D3DX builds
+if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE)
+ target_compile_options(g_gameengine PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE})
+endif()
diff --git a/Generals/Code/GameEngineDevice/CMakeLists.txt b/Generals/Code/GameEngineDevice/CMakeLists.txt
index ca1022e3e6..7089175ca9 100644
--- a/Generals/Code/GameEngineDevice/CMakeLists.txt
+++ b/Generals/Code/GameEngineDevice/CMakeLists.txt
@@ -208,3 +208,8 @@ target_link_libraries(g_gameenginedevice PUBLIC
corei_gameenginedevice_public
g_gameengine
)
+
+# Apply D3DXWrapper forced include for NO_D3DX builds
+if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE)
+ target_compile_options(g_gameenginedevice PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE})
+endif()
diff --git a/Generals/Code/Main/CMakeLists.txt b/Generals/Code/Main/CMakeLists.txt
index 9e14a4cbe7..7858146d22 100644
--- a/Generals/Code/Main/CMakeLists.txt
+++ b/Generals/Code/Main/CMakeLists.txt
@@ -91,3 +91,8 @@ endif()
if(MINGW AND COMMAND add_debug_strip_target)
add_debug_strip_target(g_generals)
endif()
+
+# Verify no D3DX8 DLL dependency when NO_D3DX is enabled
+if(MINGW AND COMMAND add_no_d3dx_verification)
+ add_no_d3dx_verification(g_generals)
+endif()
diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt
index d6990fc097..c3bd2fd095 100644
--- a/GeneralsMD/Code/GameEngine/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt
@@ -1178,3 +1178,8 @@ target_precompile_headers(z_gameengine PRIVATE
[["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6
Include/Precompiled/PreRTS.h
)
+
+# Apply D3DXWrapper forced include for NO_D3DX builds
+if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE)
+ target_compile_options(z_gameengine PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE})
+endif()
diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt
index db9be4f3cd..66cc73376a 100644
--- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt
@@ -221,3 +221,8 @@ target_link_libraries(z_gameenginedevice PUBLIC
corei_gameenginedevice_public
z_gameengine
)
+
+# Apply D3DXWrapper forced include for NO_D3DX builds
+if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE)
+ target_compile_options(z_gameenginedevice PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE})
+endif()
diff --git a/GeneralsMD/Code/Main/CMakeLists.txt b/GeneralsMD/Code/Main/CMakeLists.txt
index d6518f442b..c12120621e 100644
--- a/GeneralsMD/Code/Main/CMakeLists.txt
+++ b/GeneralsMD/Code/Main/CMakeLists.txt
@@ -80,3 +80,8 @@ endif()
if(MINGW AND COMMAND add_debug_strip_target)
add_debug_strip_target(z_generals)
endif()
+
+# Verify no D3DX8 DLL dependency when NO_D3DX is enabled
+if(MINGW AND COMMAND add_no_d3dx_verification)
+ add_no_d3dx_verification(z_generals)
+endif()
diff --git a/cmake/dx8.cmake b/cmake/dx8.cmake
index dd08f56119..6de0f03e53 100644
--- a/cmake/dx8.cmake
+++ b/cmake/dx8.cmake
@@ -5,3 +5,21 @@ FetchContent_Declare(
)
FetchContent_MakeAvailable(dx8)
+
+# When NO_D3DX is enabled, remove d3dx8d from d3d8lib link libraries
+if(MINGW AND DEFINED MINGW_NO_D3DX AND MINGW_NO_D3DX)
+ # Get current link libraries from d3d8lib
+ get_target_property(DX8_LINK_LIBS d3d8lib INTERFACE_LINK_LIBRARIES)
+
+ if(DX8_LINK_LIBS)
+ # Remove d3dx8d from the list
+ list(REMOVE_ITEM DX8_LINK_LIBS d3dx8d)
+
+ # Set the updated list back
+ set_target_properties(d3d8lib PROPERTIES
+ INTERFACE_LINK_LIBRARIES "${DX8_LINK_LIBS}"
+ )
+
+ message(STATUS "Removed d3dx8d from d3d8lib link libraries (NO_D3DX)")
+ endif()
+endif()
diff --git a/cmake/mingw.cmake b/cmake/mingw.cmake
index c095343055..349720b0a9 100644
--- a/cmake/mingw.cmake
+++ b/cmake/mingw.cmake
@@ -74,17 +74,48 @@ if(MINGW)
# are provided by Dependencies/Utility/Utility/comsupp_compat.h as header-only
# implementations. No library linking required.
- # MinGW-w64 compatibility: Create d3dx8 as an alias to d3dx8d
- # MinGW-w64 only provides libd3dx8d.a (debug library), not libd3dx8.a
- # The min-dx8-sdk (dx8.cmake) handles this correctly via d3d8lib interface target,
- # but for compatibility with direct library references in main executables,
- # we create an alias so that linking to d3dx8 automatically uses d3dx8d
- if(NOT TARGET d3dx8)
- add_library(d3dx8 INTERFACE IMPORTED GLOBAL)
- set_target_properties(d3dx8 PROPERTIES
- INTERFACE_LINK_LIBRARIES "d3dx8d"
- )
- message(STATUS "Created d3dx8 -> d3dx8d alias for MinGW-w64")
+ # MinGW-w64 D3DX8 dependency elimination option
+ # Header conflicts resolved using include guard coordination (Option A)
+ # D3DXCompat.h pre-defines min-dx8-sdk include guards to prevent redefinitions
+ option(MINGW_NO_D3DX "Eliminate D3DX8.dll dependency using WWMath compatibility layer" ON)
+
+ if(MINGW_NO_D3DX)
+ # Use compatibility layer
+ add_compile_definitions(NO_D3DX)
+
+ # Force include our D3DX compatibility wrapper in project files only
+ # (Not in external dependencies like lzhl, gamespy, etc.)
+ # This ensures all D3DX calls go through our replacement layer
+ # Note: Will be applied selectively to targets that need it
+ set(MINGW_D3DX_WRAPPER_INCLUDE "-include;${CMAKE_SOURCE_DIR}/Core/Libraries/Include/Lib/D3DXWrapper.h")
+
+ message(STATUS "MinGW: D3DX8 dependency eliminated (NO_D3DX enabled)")
+ message(STATUS " Using native D3D implementations for math functions")
+ message(STATUS " Using Direct3D 8 API for texture operations")
+ message(STATUS " D3DXWrapper.h available for selective inclusion")
+
+ # Create empty d3dx8 INTERFACE library (like stlport pattern)
+ # This allows unconditional linking to d3dx8 in CMakeLists.txt files
+ if(NOT TARGET d3dx8)
+ add_library(d3dx8 INTERFACE)
+ message(STATUS "Created empty d3dx8 INTERFACE library (NO_D3DX)")
+ endif()
+ else()
+ # Legacy behavior: use D3DX8 with DLL dependency
+ message(STATUS "MinGW: Using D3DX8.dll (NO_D3DX disabled)")
+
+ # MinGW-w64 compatibility: Create d3dx8 as an alias to d3dx8d
+ # MinGW-w64 only provides libd3dx8d.a (debug library), not libd3dx8.a
+ # The min-dx8-sdk (dx8.cmake) handles this correctly via d3d8lib interface target,
+ # but for compatibility with direct library references in main executables,
+ # we create an alias so that linking to d3dx8 automatically uses d3dx8d
+ if(NOT TARGET d3dx8)
+ add_library(d3dx8 INTERFACE IMPORTED GLOBAL)
+ set_target_properties(d3dx8 PROPERTIES
+ INTERFACE_LINK_LIBRARIES "d3dx8d"
+ )
+ message(STATUS "Created d3dx8 -> d3dx8d alias for MinGW-w64")
+ endif()
endif()
message(STATUS "MinGW-w64 configuration complete")
diff --git a/cmake/no_d3dx_verify.cmake b/cmake/no_d3dx_verify.cmake
new file mode 100644
index 0000000000..e8efae5a4e
--- /dev/null
+++ b/cmake/no_d3dx_verify.cmake
@@ -0,0 +1,46 @@
+# D3DX Dependency Verification for NO_D3DX builds
+#
+# Verifies that when NO_D3DX is enabled, executables have no d3dx8*.dll imports.
+# This ensures the compatibility layer is working correctly.
+
+if(MINGW AND MINGW_NO_D3DX)
+ # Find objdump tool for checking DLL imports
+ find_program(MINGW_OBJDUMP
+ NAMES ${CMAKE_CXX_COMPILER_TARGET}-objdump
+ ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-objdump
+ objdump
+ DOC "MinGW objdump tool for verifying DLL imports"
+ )
+
+ if(MINGW_OBJDUMP)
+ message(STATUS "D3DX elimination verification enabled:")
+ message(STATUS " objdump: ${MINGW_OBJDUMP}")
+ set(NO_D3DX_VERIFY_AVAILABLE TRUE)
+ else()
+ message(WARNING "D3DX verification not available - objdump not found")
+ set(NO_D3DX_VERIFY_AVAILABLE FALSE)
+ endif()
+
+ # Function to add D3DX verification to a target
+ #
+ # Checks that the built executable does not import d3dx8*.dll
+ # Fails the build if D3DX dependency is detected
+ #
+ # Usage:
+ # add_no_d3dx_verification(target_name)
+ #
+ function(add_no_d3dx_verification target_name)
+ if(NOT NO_D3DX_VERIFY_AVAILABLE)
+ return()
+ endif()
+
+ add_custom_command(TARGET ${target_name} POST_BUILD
+ COMMAND bash -c
+ "${MINGW_OBJDUMP} -p $ | grep -i 'd3dx8' && echo 'ERROR: D3DX8 DLL dependency detected! NO_D3DX failed.' && exit 1 || echo 'SUCCESS: No D3DX8 DLL dependency (NO_D3DX working)'"
+ COMMENT "Verifying no D3DX8 dependency in ${target_name}"
+ VERBATIM
+ )
+
+ message(STATUS "D3DX elimination verification configured for target: ${target_name}")
+ endfunction()
+endif()
diff --git a/scripts/compile_shaders.py b/scripts/compile_shaders.py
new file mode 100755
index 0000000000..0c69acf8c3
--- /dev/null
+++ b/scripts/compile_shaders.py
@@ -0,0 +1,233 @@
+#!/usr/bin/env python3
+"""
+Pixel Shader 1.1 Assembler - Generates C++ header with precompiled shader bytecode
+
+This script assembles DirectX 8 Pixel Shader 1.1 assembly code into bytecode
+and generates a C++ header file that can be included in the project.
+
+Usage: python3 compile_shaders.py [shader2.psh ...]
+
+Note: This is a simple PS 1.1 assembler that handles the basic instructions
+used in the water shaders. For D3D8 ps.1.1 format.
+"""
+
+import sys
+import struct
+import re
+from pathlib import Path
+
+# PS 1.1 instruction opcodes (D3D8 format)
+PS_OPCODES = {
+ 'tex': 0x42, # TEX instruction
+ 'texbem': 0x43, # TEXBEM instruction
+ 'mul': 0x05, # MUL instruction
+ 'add': 0x03, # ADD instruction
+ 'mad': 0x04, # MAD instruction (multiply-add)
+}
+
+# Register types
+REG_TEMP = 0 # r0-r7 (temporary registers)
+REG_INPUT = 1 # v0-v1 (input color registers)
+REG_CONST = 2 # c0-c7 (constant registers)
+REG_TEXTURE = 3 # t0-t3 (texture registers)
+
+def parse_register(reg_str):
+ """Parse a register string like 'r0', 'v0', 't0', 'c0' and return (type, index, write_mask)"""
+ reg_str = reg_str.strip()
+
+ # Handle write masks like r0.rgb, r0.a
+ write_mask = 0xF # Default: all components (xyzw / rgba)
+ if '.' in reg_str:
+ reg_part, mask_part = reg_str.split('.')
+ if mask_part == 'rgb':
+ write_mask = 0x7 # xyz only
+ elif mask_part == 'a':
+ write_mask = 0x8 # w only
+ elif mask_part == 'rgba':
+ write_mask = 0xF # all
+ reg_str = reg_part
+
+ reg_type = reg_str[0]
+ reg_index = int(reg_str[1:])
+
+ type_map = {'r': REG_TEMP, 'v': REG_INPUT, 'c': REG_CONST, 't': REG_TEXTURE}
+ return (type_map[reg_type], reg_index, write_mask)
+
+def encode_register(reg_type, reg_index, write_mask=0xF):
+ """Encode a register into D3D8 format"""
+ # Simple encoding: type in upper bits, index in lower bits
+ # This is a simplified version - actual D3D8 encoding is more complex
+ return (write_mask << 16) | (reg_type << 8) | reg_index
+
+def assemble_instruction(line):
+ """Assemble a single PS 1.1 instruction into bytecode"""
+ line = line.strip()
+
+ # Skip empty lines and comments
+ if not line or line.startswith(';') or line.startswith('//'):
+ return []
+
+ # Handle co-issue (+instruction)
+ coissue = line.startswith('+')
+ if coissue:
+ line = line[1:].strip()
+
+ # Split instruction and operands
+ parts = re.split(r'[,\s]+', line)
+ instr = parts[0].lower()
+
+ # Handle version declaration
+ if instr == 'ps.1.1':
+ # PS 1.1 version token: 0xFFFF0101
+ return [struct.pack('= 2:
+ reg_type, reg_idx, write_mask = parse_register(parts[1])
+ bytecode.append(struct.pack('= 3:
+ dst_type, dst_idx, dst_mask = parse_register(parts[1])
+ src_type, src_idx, src_mask = parse_register(parts[2])
+ bytecode.append(struct.pack('= 4:
+ dst_type, dst_idx, dst_mask = parse_register(parts[1])
+ src1_type, src1_idx, src1_mask = parse_register(parts[2])
+ src2_type, src2_idx, src2_mask = parse_register(parts[3])
+ bytecode.append(struct.pack('= 5:
+ dst_type, dst_idx, dst_mask = parse_register(parts[1])
+ src1_type, src1_idx, src1_mask = parse_register(parts[2])
+ src2_type, src2_idx, src2_mask = parse_register(parts[3])
+ src3_type, src3_idx, src3_mask = parse_register(parts[4])
+ bytecode.append(struct.pack('
+
+namespace PrecompiledShaders {{
+
+""")
+
+ for i, shader_file in enumerate(shader_files, 1):
+ shader_name = Path(shader_file).stem
+ print(f"Assembling {shader_file}...")
+
+ with open(shader_file, 'r') as sf:
+ shader_text = sf.read()
+
+ bytecode = assemble_shader(shader_text)
+
+ # Write bytecode array
+ f.write(f"// {shader_name} - from {Path(shader_file).name}\n")
+ f.write(f"constexpr DWORD {shader_name}_bytecode[] = {{\n")
+
+ # Write bytecode as hex DWORDs
+ dwords = [bytecode[i:i+4] for i in range(0, len(bytecode), 4)]
+ for j, dword in enumerate(dwords):
+ if len(dword) == 4:
+ value = struct.unpack(' [shader2.psh ...]")
+ print("\nExample:")
+ print(" python3 compile_shaders.py shaders/water_shader1.psh shaders/water_shader2.psh shaders/water_shader3.psh")
+ sys.exit(1)
+
+ shader_files = sys.argv[1:]
+ output_file = "PrecompiledShaders.h"
+
+ # Verify all input files exist
+ for shader_file in shader_files:
+ if not Path(shader_file).exists():
+ print(f"Error: Shader file '{shader_file}' not found")
+ sys.exit(1)
+
+ generate_header(shader_files, output_file)
+ print(f"\nSuccess! Precompiled shaders written to {output_file}")
+ print("Include this header in your D3DXCompat.h or shader loading code.")
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/shaders/water_shader1.psh b/scripts/shaders/water_shader1.psh
new file mode 100644
index 0000000000..a8db099945
--- /dev/null
+++ b/scripts/shaders/water_shader1.psh
@@ -0,0 +1,10 @@
+ps.1.1
+tex t0
+tex t1
+tex t2
+tex t3
+mul r0,v0,t0
+mul r1, t1, t2
+add r0.rgb, r0, t3
++mul r0.a, r0, t3
+add r0.rgb, r0, r1
diff --git a/scripts/shaders/water_shader2.psh b/scripts/shaders/water_shader2.psh
new file mode 100644
index 0000000000..4a480efa59
--- /dev/null
+++ b/scripts/shaders/water_shader2.psh
@@ -0,0 +1,7 @@
+ps.1.1
+tex t0
+tex t1
+texbem t2, t1
+mul r0,v0,t0
+mul r1.rgb,t2,c0
+add r0.rgb, r0, r1
diff --git a/scripts/shaders/water_shader3.psh b/scripts/shaders/water_shader3.psh
new file mode 100644
index 0000000000..19551f94e1
--- /dev/null
+++ b/scripts/shaders/water_shader3.psh
@@ -0,0 +1,8 @@
+ps.1.1
+tex t0
+tex t1
+tex t2
+tex t3
+mul r0,v0,t0
+mad r0.rgb, t1, t2, r0
+mul r0.rgb, r0, t3