在 Windows 平台优先使用注册表探测 GPU (#4145)

This commit is contained in:
Glavo 2025-07-30 15:32:42 +08:00 committed by GitHub
parent 3b17e0a897
commit 329b0e8f3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 179 additions and 51 deletions

View File

@ -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<String>();
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);
}

View File

@ -0,0 +1,147 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
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<GraphicsCard> detectByCim() {
try {
String getCimInstance = OperatingSystem.SYSTEM_VERSION.startsWith("6.1")
? "Get-WmiObject"
: "Get-CimInstance";
List<Map<String, String>> 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<GraphicsCard> cards = new ArrayList<>(videoControllers.size());
for (Map<String, String> 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<GraphicsCard> 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<GraphicsCard>();
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<GraphicsCard> detect() {
try {
WinReg reg = WinReg.INSTANCE;
if (reg != null) {
List<GraphicsCard> 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() {
}
}

View File

@ -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<GraphicsCard> detectGraphicsCards() {
if (!OperatingSystem.isWindows7OrLater())
return null;
try {
String getCimInstance = OperatingSystem.SYSTEM_VERSION.startsWith("6.1")
? "Get-WmiObject"
: "Get-CimInstance";
List<Map<String, String>> 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<GraphicsCard> cards = new ArrayList<>(videoControllers.size());
for (Map<String, String> 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

View File

@ -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));