Browse Source

Use the Microsoft provided GameInputCreate() function (#15797)

It does better version checking and has better compatibility.

This also fixes a crash in GameInputRedist.dll when attempting to load v3 when v2 is installed on the system. In this case, a thread is created in GameInputCreate() which is not cleaned up when the object is released, and can crash later with a NULL pointer dereference.
Sam Lantinga 12 hours ago
parent
commit
36f621842b

+ 13 - 7
.github/workflows/create-test-plan.py

@@ -12,7 +12,6 @@ from typing import Optional
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
-WINDOWS_GAMEINPUT_VERSION = "v3.3.195.0 "
 
 
 class AppleArch(Enum):
 class AppleArch(Enum):
     Aarch64 = "aarch64"
     Aarch64 = "aarch64"
@@ -222,7 +221,8 @@ class JobDetails:
     msys2_packages: list[str] = dataclasses.field(default_factory=list)
     msys2_packages: list[str] = dataclasses.field(default_factory=list)
     cygwin_packages: list[str] = dataclasses.field(default_factory=list)
     cygwin_packages: list[str] = dataclasses.field(default_factory=list)
     werror: bool = True
     werror: bool = True
-    microsoft_gameinput_version: str = ""
+    microsoft_gameinput: bool = False
+    microsoft_gameinput_arch: str = ""
     msvc_vcvars_arch: str = ""
     msvc_vcvars_arch: str = ""
     msvc_vcvars_sdk: str = ""
     msvc_vcvars_sdk: str = ""
     msvc_project: str = ""
     msvc_project: str = ""
@@ -293,7 +293,8 @@ class JobDetails:
             "android-mk": self.android_mk,
             "android-mk": self.android_mk,
             "werror": self.werror,
             "werror": self.werror,
             "sudo": self.sudo,
             "sudo": self.sudo,
-            "microsoft-gameinput-version": self.microsoft_gameinput_version,
+            "microsoft-gameinput": self.microsoft_gameinput,
+            "microsoft-gameinput-arch": self.microsoft_gameinput_arch,
             "msvc-vcvars-arch": self.msvc_vcvars_arch,
             "msvc-vcvars-arch": self.msvc_vcvars_arch,
             "msvc-vcvars-sdk": self.msvc_vcvars_sdk,
             "msvc-vcvars-sdk": self.msvc_vcvars_sdk,
             "msvc-project": self.msvc_project,
             "msvc-project": self.msvc_project,
@@ -445,9 +446,14 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool, ctest_args
                         job.setup_libusb_arch = "x86"
                         job.setup_libusb_arch = "x86"
                     case MsvcArch.X64:
                     case MsvcArch.X64:
                         job.setup_libusb_arch = "x64"
                         job.setup_libusb_arch = "x64"
-            job.microsoft_gameinput_version = WINDOWS_GAMEINPUT_VERSION
-            job.cflags.append("-I$GAMEINPUT_INCLUDE")
-            job.cxxflags.append("-I$GAMEINPUT_INCLUDE")
+                job.microsoft_gameinput = True
+                match spec.msvc_arch:
+                    case MsvcArch.X64:
+                        job.microsoft_gameinput_arch = "x64"
+                    case MsvcArch.Arm64:
+                        job.microsoft_gameinput_arch = "arm64"
+                job.cflags.append("-I$GAMEINPUT_INCLUDE")
+                job.cxxflags.append("-I$GAMEINPUT_INCLUDE")
         case SdlPlatform.Linux:
         case SdlPlatform.Linux:
             if spec.name.startswith("Ubuntu"):
             if spec.name.startswith("Ubuntu"):
                 assert spec.os.value.startswith("ubuntu-")
                 assert spec.os.value.startswith("ubuntu-")
@@ -775,7 +781,7 @@ def spec_to_job(spec: JobSpec, key: str, trackmem_symbol_names: bool, ctest_args
                 job.msys2_packages.append(f"{msys2_env}-clang-tools-extra")
                 job.msys2_packages.append(f"{msys2_env}-clang-tools-extra")
             if job.ccache:
             if job.ccache:
                 job.msys2_packages.append(f"{msys2_env}-ccache")
                 job.msys2_packages.append(f"{msys2_env}-ccache")
-            job.microsoft_gameinput_version = WINDOWS_GAMEINPUT_VERSION
+            job.microsoft_gameinput = True
             job.cflags.append("-I$GAMEINPUT_INCLUDE")
             job.cflags.append("-I$GAMEINPUT_INCLUDE")
             job.cxxflags.append("-I$GAMEINPUT_INCLUDE")
             job.cxxflags.append("-I$GAMEINPUT_INCLUDE")
         case SdlPlatform.Cygwin:
         case SdlPlatform.Cygwin:

+ 6 - 7
.github/workflows/generic.yml

@@ -173,14 +173,12 @@ jobs:
             done
             done
           done
           done
       - name: 'Set up Microsoft.GameInput headers'
       - name: 'Set up Microsoft.GameInput headers'
-        if: ${{ matrix.platform.microsoft-gameinput-version != '' }}
+        if: ${{ !!matrix.platform.microsoft-gameinput }}
         run: |
         run: |
-          python build-scripts/download-gameinput-headers.py \
-            --version ${{ matrix.platform.microsoft-gameinput-version }} \
-            -o $HOME/gameinput
-          echo "GAMEINPUT_INCLUDE=$(cygpath -w "$HOME/gameinput")" >>$GITHUB_ENV
-          echo "INCLUDE=$INCLUDE;$(cygpath -w "$HOME/gameinput")" >>$GITHUB_ENV
-
+          python build-scripts/download-gameinput-sdk.py -o $HOME/gameinput
+          echo "GAMEINPUT_INCLUDE=$(cygpath -w "$HOME/gameinput/include")" >>$GITHUB_ENV
+          echo "INCLUDE=$(cygpath -w "$HOME/gameinput/include");$INCLUDE" >>$GITHUB_ENV
+          ${{ (!!matrix.platform.microsoft-gameinput-arch && format('echo "LIB=$(cygpath -w "$HOME/gameinput/lib/{0}");$LIB" >>$GITHUB_ENV', matrix.platform.microsoft-gameinput-arch)) || '' }}
       - name: 'Calculate ccache key'
       - name: 'Calculate ccache key'
         if: ${{ matrix.platform.ccache }}
         if: ${{ matrix.platform.ccache }}
         id: prepare-restore-ccache
         id: prepare-restore-ccache
@@ -439,6 +437,7 @@ jobs:
           path: |
           path: |
             build/dist/SDL3*
             build/dist/SDL3*
             build/include*
             build/include*
+            build/CMakeFiles/CMakeConfigureLog.yaml
       - name: 'Upload minidumps'
       - name: 'Upload minidumps'
         uses: actions/upload-artifact@v7
         uses: actions/upload-artifact@v7
         continue-on-error: true
         continue-on-error: true

+ 33 - 3
CMakeLists.txt

@@ -938,7 +938,7 @@ if(SDL_ASSEMBLY)
         }]==] COMPILER_SUPPORTS_ARMSVE2)
         }]==] COMPILER_SUPPORTS_ARMSVE2)
       if(COMPILER_SUPPORTS_ARMSVE2)
       if(COMPILER_SUPPORTS_ARMSVE2)
         # IMPORTANT: As not all AArch64 processors support SVE2, we only
         # IMPORTANT: As not all AArch64 processors support SVE2, we only
-        #            attach the following compilation option to SVE 
+        #            attach the following compilation option to SVE
         #            dedicated source files.
         #            dedicated source files.
         set(SVE2_MARCH_FLAG "-march=armv8-a+sve2")
         set(SVE2_MARCH_FLAG "-march=armv8-a+sve2")
         set(HAVE_ARMSVE2 TRUE)
         set(HAVE_ARMSVE2 TRUE)
@@ -2313,12 +2313,42 @@ elseif(WINDOWS OR CYGWIN)
     static __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2 *s2;
     static __x_ABI_CWindows_CGaming_CInput_CIGamepadStatics2 *s2;
     int main(int argc, char **argv) { return 0; }" HAVE_WINDOWS_GAMING_INPUT_H
     int main(int argc, char **argv) { return 0; }" HAVE_WINDOWS_GAMING_INPUT_H
   )
   )
+
   check_cxx_source_compiles("
   check_cxx_source_compiles("
-    #include <stdbool.h>
-    #define COBJMACROS
     #include <gameinput.h>
     #include <gameinput.h>
     int main(int argc, char **argv) { return 0; }" HAVE_GAMEINPUT_H
     int main(int argc, char **argv) { return 0; }" HAVE_GAMEINPUT_H
   )
   )
+  if(HAVE_GAMEINPUT_H)
+    set(SDL_GAMEINPUT_DYNAMIC 1)
+    find_library(GAMEINPUT_LIB NAMES "gameinput.lib")
+    if(GAMEINPUT_LIB)
+      cmake_push_check_state()
+      list(APPEND CMAKE_REQUIRED_LIBRARIES "${GAMEINPUT_LIB}")
+      check_cxx_source_compiles("
+        #include <windows.h>
+        #include <gameinput.h>
+        #if defined(GAMEINPUT_API_VERSION) && GAMEINPUT_API_VERSION > 0
+        #define STR_JOIN2(A, B) A##B
+        #define STR_JOIN(A, B) STR_JOIN2(A, B)
+        using namespace GameInput::STR_JOIN(v, GAMEINPUT_API_VERSION);
+        #endif
+        int main(int argc, char **argv) {
+         IGameInput *gameInput;
+         HRESULT hr = GameInputCreate(&gameInput);
+         if (SUCCEEDED(hr)) {
+          gameInput->Release();
+         }
+         (void) argc; (void) argv;
+         return 0;
+        }" HAVE_GAMEINPUT_LIB
+      )
+      if(HAVE_GAMEINPUT_LIB)
+        sdl_link_dependency(gameinput LIBS gameinput.lib PKG_CONFIG_LINK_OPTIONS -lgameinput)
+        set(SDL_GAMEINPUT_DYNAMIC 0)
+      endif()
+    endif()
+  endif()
+
   check_include_file(dxgi1_5.h HAVE_DXGI1_5_H)
   check_include_file(dxgi1_5.h HAVE_DXGI1_5_H)
   check_include_file(dxgi1_6.h HAVE_DXGI1_6_H)
   check_include_file(dxgi1_6.h HAVE_DXGI1_6_H)
   check_include_file(tpcshrd.h HAVE_TPCSHRD_H)
   check_include_file(tpcshrd.h HAVE_TPCSHRD_H)

+ 6 - 6
VisualC-GDK/SDL/SDL.vcxproj

@@ -132,7 +132,7 @@
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     </ResourceCompile>
     <Link>
     <Link>
-      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;vcruntimed.lib;msvcrtd.lib;ucrtd.lib;msvcprtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>gameinput.lib;setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;vcruntimed.lib;msvcrtd.lib;ucrtd.lib;msvcprtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <SubSystem>Windows</SubSystem>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
@@ -161,7 +161,7 @@
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     </ResourceCompile>
     <Link>
     <Link>
-      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;d3d12_xs.lib;uuid.lib;vcruntimed.lib;msvcrtd.lib;ucrtd.lib;msvcprtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>gameinput.lib;setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;d3d12_xs.lib;uuid.lib;vcruntimed.lib;msvcrtd.lib;ucrtd.lib;msvcprtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <SubSystem>Windows</SubSystem>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
@@ -199,7 +199,7 @@
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     </ResourceCompile>
     <Link>
     <Link>
-      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;d3d12_x.lib;uuid.lib;vcruntimed.lib;msvcrtd.lib;ucrtd.lib;msvcprtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>gameinput.lib;setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;d3d12_x.lib;uuid.lib;vcruntimed.lib;msvcrtd.lib;ucrtd.lib;msvcprtd.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <SubSystem>Windows</SubSystem>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
@@ -238,7 +238,7 @@
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     </ResourceCompile>
     <Link>
     <Link>
-      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;vcruntime.lib;msvcrt.lib;ucrt.lib;msvcprt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>gameinput.lib;setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;vcruntime.lib;msvcrt.lib;ucrt.lib;msvcprt.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <SubSystem>Windows</SubSystem>
       <OptimizeReferences>true</OptimizeReferences>
       <OptimizeReferences>true</OptimizeReferences>
@@ -268,7 +268,7 @@
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     </ResourceCompile>
     <Link>
     <Link>
-      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;d3d12_xs.lib;uuid.lib;vcruntime.lib;msvcrt.lib;ucrt.lib;msvcprt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>gameinput.lib;setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;d3d12_xs.lib;uuid.lib;vcruntime.lib;msvcrt.lib;ucrt.lib;msvcprt.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <SubSystem>Windows</SubSystem>
       <OptimizeReferences>true</OptimizeReferences>
       <OptimizeReferences>true</OptimizeReferences>
@@ -307,7 +307,7 @@
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     </ResourceCompile>
     <Link>
     <Link>
-      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;d3d12_x.lib;uuid.lib;vcruntime.lib;msvcrt.lib;ucrt.lib;msvcprt.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>gameinput.lib;setupapi.lib;winmm.lib;imm32.lib;version.lib;xgameruntime.lib;d3d12_x.lib;uuid.lib;vcruntime.lib;msvcrt.lib;ucrt.lib;msvcprt.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <SubSystem>Windows</SubSystem>
       <OptimizeReferences>true</OptimizeReferences>
       <OptimizeReferences>true</OptimizeReferences>

+ 9 - 3
build-scripts/build-release.py

@@ -175,7 +175,7 @@ class VisualStudio:
         assert msbuild_path.is_file(), "MSBuild.exe does not exist"
         assert msbuild_path.is_file(), "MSBuild.exe does not exist"
         return msbuild_path
         return msbuild_path
 
 
-    def build(self, arch_platform: VsArchPlatformConfig, projects: list[Path], include_paths: list[str]):
+    def build(self, arch_platform: VsArchPlatformConfig, projects: list[Path], include_paths: list[str], lib_paths: list[str]):
         assert projects, "Need at least one project to build"
         assert projects, "Need at least one project to build"
 
 
         vsdev_cmd_str = f"\"{self.vsdevcmd}\" -arch={arch_platform.arch}"
         vsdev_cmd_str = f"\"{self.vsdevcmd}\" -arch={arch_platform.arch}"
@@ -183,7 +183,10 @@ class VisualStudio:
         include_contents = "%INCLUDE%"
         include_contents = "%INCLUDE%"
         if include_paths:
         if include_paths:
             include_contents = f"{';'.join(str(p) for p in include_paths)};" + include_contents
             include_contents = f"{';'.join(str(p) for p in include_paths)};" + include_contents
-        bat_contents = textwrap.dedent(f"{vsdev_cmd_str} && set INCLUDE={include_contents} && {msbuild_cmd_str}")
+        lib_contents = "%LIB%"
+        if lib_paths:
+            lib_contents = f"{';'.join(str(p) for p in lib_paths)};" + lib_contents
+        bat_contents = textwrap.dedent(f"{vsdev_cmd_str} && set INCLUDE={include_contents} && set LIB={lib_paths} && {msbuild_cmd_str}")
         bat_path = Path(tempfile.gettempdir()) / "cmd.bat"
         bat_path = Path(tempfile.gettempdir()) / "cmd.bat"
         with bat_path.open("w") as f:
         with bat_path.open("w") as f:
             f.write(bat_contents)
             f.write(bat_contents)
@@ -1218,9 +1221,12 @@ class Releaser:
     def _build_msvc_msbuild(self, arch_platform: VsArchPlatformConfig, vs: VisualStudio):
     def _build_msvc_msbuild(self, arch_platform: VsArchPlatformConfig, vs: VisualStudio):
         platform_context = self.get_context(arch_platform.extra_context())
         platform_context = self.get_context(arch_platform.extra_context())
         include_paths = []
         include_paths = []
+        lib_paths = []
         for dep in self.release_info.get("dependencies", {}):#release_info["msvc"].get("dependencies", {}).items():
         for dep in self.release_info.get("dependencies", {}):#release_info["msvc"].get("dependencies", {}).items():
             if "command" in self.release_info["dependencies"][dep]:
             if "command" in self.release_info["dependencies"][dep]:
                 include_paths.append(self.deps_path / dep / "include")
                 include_paths.append(self.deps_path / dep / "include")
+                lib_paths.append(self.deps_path / dep / "lib")
+                lib_paths.append(self.deps_path / dep / "lib" / arch_platform.arch)
                 continue
                 continue
             depinfo = self.release_info["msvc"]["dependencies"][dep]
             depinfo = self.release_info["msvc"]["dependencies"][dep]
             msvc_zip = self.deps_path / glob.glob(depinfo["artifact"], root_dir=self.deps_path)[0]
             msvc_zip = self.deps_path / glob.glob(depinfo["artifact"], root_dir=self.deps_path)[0]
@@ -1270,7 +1276,7 @@ class Releaser:
                 shutil.copy(src=src, dst=dir_b_props)
                 shutil.copy(src=src, dst=dir_b_props)
 
 
         with self.section_printer.group(f"Build {arch_platform.arch} VS binary"):
         with self.section_printer.group(f"Build {arch_platform.arch} VS binary"):
-            vs.build(arch_platform=arch_platform, projects=projects, include_paths=include_paths)
+            vs.build(arch_platform=arch_platform, projects=projects, include_paths=include_paths, lib_paths=lib_paths)
 
 
         if self.dry:
         if self.dry:
             for b in built_paths:
             for b in built_paths:

+ 12 - 9
build-scripts/download-gameinput-headers.py → build-scripts/download-gameinput-sdk.py

@@ -7,24 +7,27 @@ import urllib.request
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
-def download_headers(tag: str, lowercase: bool, output: Path):
+DEFAULT_GAMEINPUT_VERSION = "v3.3.195.0"
+
+def download_sdk(tag: str, lowercase: bool, output: Path):
     base_url = f"https://raw.githubusercontent.com/microsoftconnect/GameInput/refs/tags/{tag}/"
     base_url = f"https://raw.githubusercontent.com/microsoftconnect/GameInput/refs/tags/{tag}/"
     url_relpaths = (
     url_relpaths = (
         "include/GameInput.h",
         "include/GameInput.h",
         "include/v0/GameInput.h",
         "include/v0/GameInput.h",
         "include/v1/GameInput.h",
         "include/v1/GameInput.h",
         "include/v2/GameInput.h",
         "include/v2/GameInput.h",
+        "lib/arm64/GameInput.lib",
+        "lib/x64/GameInput.lib",
     )
     )
-    remove_prefix = "include/"
     for url_relpath in url_relpaths:
     for url_relpath in url_relpaths:
         url = base_url + url_relpath
         url = base_url + url_relpath
-        local_relpath = url_relpath.removeprefix(remove_prefix)
+        local_relpath = url_relpath
         if lowercase:
         if lowercase:
             local_relpath = local_relpath.lower()
             local_relpath = local_relpath.lower()
         local_path = output / local_relpath
         local_path = output / local_relpath
 
 
         local_dirpath = local_path.parent
         local_dirpath = local_path.parent
-        local_dirpath.mkdir(parents=False, exist_ok=True)
+        local_dirpath.mkdir(parents=True, exist_ok=True)
 
 
         logger.info("Downloading %s to %s...", url, local_path)
         logger.info("Downloading %s to %s...", url, local_path)
         urllib.request.urlretrieve(url, local_path)
         urllib.request.urlretrieve(url, local_path)
@@ -33,12 +36,12 @@ def download_headers(tag: str, lowercase: bool, output: Path):
 def main():
 def main():
     logging.basicConfig(level=logging.INFO)
     logging.basicConfig(level=logging.INFO)
 
 
-    parser = argparse.ArgumentParser(description="Download Microsoft.GameInput headers", allow_abbrev=False)
-    parser.add_argument("--version", required=True, help="GameInput release tag (see https://github.com/microsoftconnect/GameInput/tags)")
-    parser.add_argument("--no-lowercase", action="store_false", dest="lowercase", help="Don't lowercase downloaded headers")
-    parser.add_argument("-o", "--output", type=Path, default=Path.cwd(), help="Headers will be stored here (subdirectories created as ")
+    parser = argparse.ArgumentParser(description="Download Microsoft.GameInput SDK", allow_abbrev=False)
+    parser.add_argument("--version", help="GameInput release tag (see https://github.com/microsoftconnect/GameInput/tags)", default=DEFAULT_GAMEINPUT_VERSION)
+    parser.add_argument("--no-lowercase", action="store_false", dest="lowercase", help="Don't lowercase downloaded files")
+    parser.add_argument("-o", "--output", type=Path, default=Path.cwd(), help="SDK will be stored here (in include and lib subdirectories)")
     args = parser.parse_args()
     args = parser.parse_args()
-    download_headers(tag=args.version, lowercase=args.lowercase, output=args.output)
+    download_sdk(tag=args.version, lowercase=args.lowercase, output=args.output)
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":
     raise SystemExit(main())
     raise SystemExit(main())

+ 1 - 1
build-scripts/release-info.json

@@ -3,7 +3,7 @@
   "remote": "libsdl-org/SDL",
   "remote": "libsdl-org/SDL",
   "dependencies": {
   "dependencies": {
     "gameinput": {
     "gameinput": {
-      "command": "build-scripts/download-gameinput-headers.py -o @<@DEPS_PATH@>@/include --version v3.3.195.0",
+      "command": "build-scripts/download-gameinput-sdk.py -o @<@DEPS_PATH@>@",
       "artifact": "gameinput/include/gameinput.h"
       "artifact": "gameinput/include/gameinput.h"
     }
     }
   },
   },

+ 3 - 1
cmake/sdlcommands.cmake

@@ -65,11 +65,12 @@ endfunction()
 # - LIBS: list of libraries to link to (cmake and pkg-config)
 # - LIBS: list of libraries to link to (cmake and pkg-config)
 # - LINK_OPTIONS: list of link options (also used in pc file, unless PKG_CONFIG_LINK_OPTION is used)
 # - LINK_OPTIONS: list of link options (also used in pc file, unless PKG_CONFIG_LINK_OPTION is used)
 function(sdl_generic_link_dependency ID)
 function(sdl_generic_link_dependency ID)
-  cmake_parse_arguments(ARGS "" "COLLECTOR" "SHARED_TARGETS;STATIC_TARGETS;INCLUDES;PKG_CONFIG_LINK_OPTIONS;PKG_CONFIG_LIBS;PKG_CONFIG_PREFIX;PKG_CONFIG_SPECS;CMAKE_MODULE;LIBS;LINK_OPTIONS" ${ARGN})
+  cmake_parse_arguments(ARGS "" "COLLECTOR" "SHARED_TARGETS;STATIC_TARGETS;INCLUDES;PKG_CONFIG_LINK_OPTIONS;PKG_CONFIG_LIBS;PKG_CONFIG_PREFIX;PKG_CONFIG_SPECS;CMAKE_MODULE;PUBLIC_LIBS;LIBS;LINK_OPTIONS" ${ARGN})
   foreach(target IN LISTS ARGS_SHARED_TARGETS)
   foreach(target IN LISTS ARGS_SHARED_TARGETS)
     if(TARGET ${target})
     if(TARGET ${target})
       target_include_directories(${target} SYSTEM PRIVATE ${ARGS_INCLUDES})
       target_include_directories(${target} SYSTEM PRIVATE ${ARGS_INCLUDES})
       target_link_libraries(${target} PRIVATE ${ARGS_LIBS})
       target_link_libraries(${target} PRIVATE ${ARGS_LIBS})
+      target_link_libraries(${target} PUBLIC ${ARGS_PUBLIC_LIBS})
       target_link_options(${target} PRIVATE ${ARGS_LINK_OPTIONS})
       target_link_options(${target} PRIVATE ${ARGS_LINK_OPTIONS})
     endif()
     endif()
   endforeach()
   endforeach()
@@ -77,6 +78,7 @@ function(sdl_generic_link_dependency ID)
     if(TARGET ${target})
     if(TARGET ${target})
       target_include_directories(${target} SYSTEM PRIVATE ${ARGS_INCLUDES})
       target_include_directories(${target} SYSTEM PRIVATE ${ARGS_INCLUDES})
       target_link_libraries(${target} PRIVATE ${ARGS_LIBS})
       target_link_libraries(${target} PRIVATE ${ARGS_LIBS})
+      target_link_libraries(${target} PUBLIC ${ARGS_PUBLIC_LIBS})
       target_link_options(${target} INTERFACE ${ARGS_LINK_OPTIONS})
       target_link_options(${target} INTERFACE ${ARGS_LINK_OPTIONS})
     endif()
     endif()
   endforeach()
   endforeach()

+ 1 - 0
include/build_config/SDL_build_config.h.cmake

@@ -231,6 +231,7 @@
 #cmakedefine HAVE_XINPUT_H 1
 #cmakedefine HAVE_XINPUT_H 1
 #cmakedefine HAVE_WINDOWS_GAMING_INPUT_H 1
 #cmakedefine HAVE_WINDOWS_GAMING_INPUT_H 1
 #cmakedefine HAVE_GAMEINPUT_H 1
 #cmakedefine HAVE_GAMEINPUT_H 1
+#cmakedefine SDL_GAMEINPUT_DYNAMIC 1
 #cmakedefine HAVE_DXGI_H 1
 #cmakedefine HAVE_DXGI_H 1
 #cmakedefine HAVE_DXGI1_5_H 1
 #cmakedefine HAVE_DXGI1_5_H 1
 #cmakedefine HAVE_DXGI1_6_H 1
 #cmakedefine HAVE_DXGI1_6_H 1

+ 35 - 19
src/core/windows/SDL_gameinput.cpp

@@ -25,6 +25,13 @@
 
 
 #ifdef HAVE_GAMEINPUT_H
 #ifdef HAVE_GAMEINPUT_H
 
 
+#ifndef SDL_GAMEINPUT_DYNAMIC
+#define USE_GAMEINPUT_LIB
+#ifdef _MSC_VER
+#pragma comment(lib, "gameinput.lib")
+#endif
+#endif // !SDL_GAMEINPUT_DYNAMIC
+
 static SDL_SharedObject *g_hGameInputDLL;
 static SDL_SharedObject *g_hGameInputDLL;
 static IGameInput *g_pGameInput;
 static IGameInput *g_pGameInput;
 static int g_nGameInputRefCount;
 static int g_nGameInputRefCount;
@@ -33,6 +40,32 @@ static int g_nGameInputRefCount;
 bool SDL_InitGameInput(IGameInput **ppGameInput)
 bool SDL_InitGameInput(IGameInput **ppGameInput)
 {
 {
     if (g_nGameInputRefCount == 0) {
     if (g_nGameInputRefCount == 0) {
+#ifdef USE_GAMEINPUT_LIB
+        // This is recommended, as Microsoft's GameInputCreate() is robust
+        // and better handles various GameInput installations
+        HRESULT hr = GameInputCreate(&g_pGameInput);
+        if (FAILED(hr)) {
+            return WIN_SetErrorFromHRESULT("GameInputCreate failed", hr);
+        }
+#elif GAMEINPUT_API_VERSION > 0
+        g_hGameInputDLL = SDL_LoadObject("gameinputredist.dll");
+        if (!g_hGameInputDLL) {
+            return false;
+        }
+
+        typedef HRESULT (WINAPI *pfnGameInputInitialize)(REFIID riid, void **ppvObject);
+        pfnGameInputInitialize pGameInputInitialize = (pfnGameInputInitialize)SDL_LoadFunction(g_hGameInputDLL, "GameInputInitialize");
+        if (!pGameInputInitialize) {
+            SDL_UnloadObject(g_hGameInputDLL);
+            return false;
+        }
+
+        HRESULT hr = pGameInputInitialize(IID_IGameInput, (void **)&g_pGameInput);
+        if (FAILED(hr)) {
+            SDL_UnloadObject(g_hGameInputDLL);
+            return WIN_SetErrorFromHRESULT("GameInputInitialize failed", hr);
+        }
+#else
         g_hGameInputDLL = SDL_LoadObject("gameinput.dll");
         g_hGameInputDLL = SDL_LoadObject("gameinput.dll");
         if (!g_hGameInputDLL) {
         if (!g_hGameInputDLL) {
             return false;
             return false;
@@ -45,29 +78,12 @@ bool SDL_InitGameInput(IGameInput **ppGameInput)
             return false;
             return false;
         }
         }
 
 
-        IGameInput *pGameInput = NULL;
-        HRESULT hr = pGameInputCreate(&pGameInput);
+        HRESULT hr = pGameInputCreate(&g_pGameInput);
         if (FAILED(hr)) {
         if (FAILED(hr)) {
             SDL_UnloadObject(g_hGameInputDLL);
             SDL_UnloadObject(g_hGameInputDLL);
             return WIN_SetErrorFromHRESULT("GameInputCreate failed", hr);
             return WIN_SetErrorFromHRESULT("GameInputCreate failed", hr);
         }
         }
-
-#ifdef SDL_PLATFORM_WIN32
-#if GAMEINPUT_API_VERSION >= 1
-        hr = pGameInput->QueryInterface(IID_IGameInput, (void **)&g_pGameInput);
-#else
-        // We require GameInput v1.1 or newer
-        hr = E_NOINTERFACE;
-#endif
-        pGameInput->Release();
-        if (FAILED(hr)) {
-            SDL_UnloadObject(g_hGameInputDLL);
-            return WIN_SetErrorFromHRESULT("GameInput QueryInterface failed", hr);
-        }
-#else
-        // Assume that the version we get is compatible with the current SDK
-        g_pGameInput = pGameInput;
-#endif
+#endif // USE_GAMEINPUT_LIB
     }
     }
     ++g_nGameInputRefCount;
     ++g_nGameInputRefCount;
 
 

+ 5 - 6
src/core/windows/SDL_gameinput.h

@@ -40,12 +40,11 @@
 #define GAMEINPUT_API_VERSION 0
 #define GAMEINPUT_API_VERSION 0
 #endif
 #endif
 
 
-#if GAMEINPUT_API_VERSION == 3
-using namespace GameInput::v3;
-#elif GAMEINPUT_API_VERSION == 2
-using namespace GameInput::v2;
-#elif GAMEINPUT_API_VERSION == 1
-using namespace GameInput::v1;
+#if GAMEINPUT_API_VERSION > 0
+// Use the namespace of the current GameInput version
+#define STR_JOIN2(A, B) A##B
+#define STR_JOIN(A, B) STR_JOIN2(A, B)
+using namespace GameInput::STR_JOIN(v, GAMEINPUT_API_VERSION);
 #endif
 #endif
 
 
 // Default value for SDL_HINT_JOYSTICK_GAMEINPUT
 // Default value for SDL_HINT_JOYSTICK_GAMEINPUT