From 329b0e8f3ad233d360cc9d744249d219cc2d4057 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 30 Jul 2025 15:32:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8=20Windows=20=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E4=BC=98=E5=85=88=E4=BD=BF=E7=94=A8=E6=B3=A8=E5=86=8C=E8=A1=A8?= =?UTF-8?q?=E6=8E=A2=E6=B5=8B=20GPU=20(#4145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/util/platform/windows/WinReg.java | 21 +++ .../platform/windows/WindowsGPUDetector.java | 147 ++++++++++++++++++ .../windows/WindowsHardwareDetector.java | 50 +----- .../util/platform/windows/WinRegTest.java | 12 +- 4 files changed, 179 insertions(+), 51 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsGPUDetector.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java index afb15bd64..006716050 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java @@ -25,6 +25,7 @@ import com.sun.jna.ptr.PointerByReference; import org.jackhuang.hmcl.util.platform.NativeUtils; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -175,6 +176,26 @@ public abstract class WinReg { return lpData.getWideString(0L); } + case WinConstants.REG_MULTI_SZ: { + String str = new String(lpData.getByteArray(0L, cbData), StandardCharsets.UTF_16LE); + + var result = new ArrayList(); + for (int start = 0; start < str.length(); ) { + final int end = str.indexOf('\0', start); + if (end < 0) { + result.add(str.substring(start)); + break; + } else if (end == start && end == str.length() - 1) { + // The terminator of the array + break; + } + + result.add(str.substring(start, end)); + start = end + 1; + } + + return result.toArray(new String[0]); + } default: throw new RuntimeException("Unknown reg type: " + type); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsGPUDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsGPUDetector.java new file mode 100644 index 000000000..67ba6d15f --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsGPUDetector.java @@ -0,0 +1,147 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * 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 . + */ +package org.jackhuang.hmcl.util.platform.windows; + +import org.jackhuang.hmcl.util.KeyValuePairUtils; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.SystemUtils; +import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard; +import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.*; +import java.util.regex.Pattern; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +final class WindowsGPUDetector { + + private static GraphicsCard.Type fromDacType(String adapterDACType) { + if (StringUtils.isBlank(adapterDACType) + || "Internal".equalsIgnoreCase(adapterDACType) + || "InternalDAC".equalsIgnoreCase(adapterDACType)) { + return GraphicsCard.Type.Integrated; + } else { + return GraphicsCard.Type.Discrete; + } + } + + private static List detectByCim() { + try { + String getCimInstance = OperatingSystem.SYSTEM_VERSION.startsWith("6.1") + ? "Get-WmiObject" + : "Get-CimInstance"; + + List> videoControllers = SystemUtils.run(Arrays.asList( + "powershell.exe", + "-NoProfile", + "-Command", + String.join(" | ", + getCimInstance + " -Class Win32_VideoController", + "Select-Object Name,AdapterCompatibility,DriverVersion,AdapterDACType", + "Format-List" + )), + inputStream -> KeyValuePairUtils.loadList(new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET)))); + + ArrayList cards = new ArrayList<>(videoControllers.size()); + for (Map videoController : videoControllers) { + String name = videoController.get("Name"); + String adapterCompatibility = videoController.get("AdapterCompatibility"); + String driverVersion = videoController.get("DriverVersion"); + String adapterDACType = videoController.get("AdapterDACType"); + + if (StringUtils.isNotBlank(name)) { + cards.add(GraphicsCard.builder().setName(GraphicsCard.cleanName(name)) + .setVendor(HardwareVendor.of(adapterCompatibility)) + .setDriverVersion(driverVersion) + .setType(fromDacType(adapterDACType)) + .build() + ); + } + } + + return cards; + } catch (Throwable e) { + LOG.warning("Failed to get graphics card info", e); + return List.of(); + } + } + + private static @Nullable String regValueToString(Object object) { + if (object == null) { + return null; + } else if (object instanceof String[]) { + return String.join(" ", (String[]) object); + } else { + return object.toString(); + } + } + + private static List detectByRegistry(WinReg reg) { + final WinReg.HKEY hkey = WinReg.HKEY.HKEY_LOCAL_MACHINE; + final String displayDevices = "SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\"; + final Pattern graphicsCardPattern = Pattern.compile("\\\\[0-9]+\\\\?$"); + + var result = new ArrayList(); + for (String subkey : reg.querySubKeys(hkey, displayDevices)) { + if (!graphicsCardPattern.matcher(subkey).find()) + continue; + + String name = regValueToString(reg.queryValue(hkey, subkey, "HardwareInformation.AdapterString")); + String vendor = regValueToString(reg.queryValue(hkey, subkey, "ProviderName")); + String driverVersion = regValueToString(reg.queryValue(hkey, subkey, "DriverVersion")); + String dacType = regValueToString(reg.queryValue(hkey, subkey, "HardwareInformation.DacType")); + + GraphicsCard.Builder builder = GraphicsCard.builder(); + if (name != null) + builder.setName(GraphicsCard.cleanName(name)); + if (vendor != null) + builder.setVendor(HardwareVendor.of(vendor)); + if (driverVersion != null) + builder.setDriverVersion(driverVersion); + if (dacType != null) + builder.setType(fromDacType(dacType)); + result.add(builder.build()); + } + return result; + } + + static @Nullable List detect() { + try { + WinReg reg = WinReg.INSTANCE; + if (reg != null) { + List res = detectByRegistry(reg); + if (!res.isEmpty()) + return res; + } + return detectByCim(); + } catch (Throwable e) { + LOG.warning("Failed to get graphics cards", e); + return null; + } + } + + private WindowsGPUDetector() { + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsHardwareDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsHardwareDetector.java index 19cd7694e..53fdd4ad5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsHardwareDetector.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsHardwareDetector.java @@ -17,19 +17,13 @@ */ package org.jackhuang.hmcl.util.platform.windows; -import org.jackhuang.hmcl.util.KeyValuePairUtils; -import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.platform.NativeUtils; import org.jackhuang.hmcl.util.platform.OperatingSystem; -import org.jackhuang.hmcl.util.platform.SystemUtils; import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor; import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard; import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector; -import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor; import org.jetbrains.annotations.Nullable; -import java.io.BufferedReader; -import java.io.InputStreamReader; import java.util.*; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -50,49 +44,7 @@ public final class WindowsHardwareDetector extends HardwareDetector { public List detectGraphicsCards() { if (!OperatingSystem.isWindows7OrLater()) return null; - - try { - String getCimInstance = OperatingSystem.SYSTEM_VERSION.startsWith("6.1") - ? "Get-WmiObject" - : "Get-CimInstance"; - - List> videoControllers = SystemUtils.run(Arrays.asList( - "powershell.exe", - "-NoProfile", - "-Command", - String.join(" | ", - getCimInstance + " -Class Win32_VideoController", - "Select-Object Name,AdapterCompatibility,DriverVersion,AdapterDACType", - "Format-List" - )), - inputStream -> KeyValuePairUtils.loadList(new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET)))); - - ArrayList cards = new ArrayList<>(videoControllers.size()); - for (Map videoController : videoControllers) { - String name = videoController.get("Name"); - String adapterCompatibility = videoController.get("AdapterCompatibility"); - String driverVersion = videoController.get("DriverVersion"); - String adapterDACType = videoController.get("AdapterDACType"); - - if (StringUtils.isNotBlank(name)) { - cards.add(GraphicsCard.builder().setName(GraphicsCard.cleanName(name)) - .setVendor(HardwareVendor.of(adapterCompatibility)) - .setDriverVersion(driverVersion) - .setType(StringUtils.isBlank(adapterDACType) - || "Internal".equalsIgnoreCase(adapterDACType) - || "InternalDAC".equalsIgnoreCase(adapterDACType) - ? GraphicsCard.Type.Integrated - : GraphicsCard.Type.Discrete) - .build() - ); - } - } - - return cards; - } catch (Throwable e) { - LOG.warning("Failed to get graphics card info", e); - return Collections.emptyList(); - } + return WindowsGPUDetector.detect(); } @Override diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WinRegTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WinRegTest.java index 897fea5f2..63605bdfa 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WinRegTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WinRegTest.java @@ -23,10 +23,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; -import java.util.Arrays; import java.util.Collections; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -48,6 +48,9 @@ public final class WinRegTest { }; private static final byte[] TEST_DATA = new byte[128]; + private static final String[] TEST_STRING_ARRAY = { + "str0", "str1", "str2" + }; static { for (int i = 0; i < TEST_DATA.length; i++) { @@ -65,6 +68,8 @@ public final class WinRegTest { Advapi32Util.registrySetStringValue(ROOT_KEY, key, "SZ", "Hello World!"); Advapi32Util.registrySetIntValue(ROOT_KEY, key, "DWORD", 0xCAFEBABE); Advapi32Util.registrySetLongValue(ROOT_KEY, key, "QWORD", 0xCAFEBABEL); + Advapi32Util.registrySetStringArray(ROOT_KEY, key, "REG_MULTI_SZ", TEST_STRING_ARRAY); + Advapi32Util.registrySetStringArray(ROOT_KEY, key, "REG_MULTI_SZ_EMPTY", new String[0]); for (String subkey : SUBKEYS) { if (!Advapi32Util.registryCreateKey(ROOT_KEY, key, subkey)) @@ -94,6 +99,9 @@ public final class WinRegTest { assertEquals("Hello World!", reg.queryValue(hkey, key, "SZ")); assertEquals(0xCAFEBABE, reg.queryValue(hkey, key, "DWORD")); assertEquals(0xCAFEBABEL, reg.queryValue(hkey, key, "QWORD")); + assertArrayEquals(TEST_STRING_ARRAY, (Object[]) reg.queryValue(hkey, key, "REG_MULTI_SZ")); + assertArrayEquals(new String[0], (Object[]) reg.queryValue(hkey, key, "REG_MULTI_SZ_EMPTY")); + assertNull(reg.queryValue(hkey, key, "UNKNOWN")); assertNull(reg.queryValue(hkey, KEY_BASE + "\\" + "NOT_EXIST", "UNKNOWN")); } @@ -103,7 +111,7 @@ public final class WinRegTest { WinReg.HKEY hkey = WinReg.HKEY.HKEY_CURRENT_USER; WinReg reg = WinReg.INSTANCE; - assertEquals(Arrays.asList(SUBKEYS).stream().map(it -> key + "\\" + it).collect(Collectors.toList()), + assertEquals(Stream.of(SUBKEYS).map(it -> key + "\\" + it).collect(Collectors.toList()), reg.querySubKeys(hkey, key).stream().sorted().collect(Collectors.toList())); for (String subkey : SUBKEYS) { assertEquals(Collections.emptyList(), reg.querySubKeys(hkey, key + "\\" + subkey));