启动时在日志中记录 CPU 信息 (#3914)
This commit is contained in:
@@ -650,8 +650,8 @@ public final class JavaManager {
|
|||||||
if (reg == null)
|
if (reg == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (String java : reg.queryKeys(hkey, location)) {
|
for (String java : reg.querySubKeys(hkey, location)) {
|
||||||
if (!reg.queryKeys(hkey, java).contains(java + "\\MSI"))
|
if (!reg.querySubKeys(hkey, java).contains(java + "\\MSI"))
|
||||||
continue;
|
continue;
|
||||||
Object home = reg.queryValue(hkey, java, "JavaHome");
|
Object home = reg.queryValue(hkey, java, "JavaHome");
|
||||||
if (home instanceof String) {
|
if (home instanceof String) {
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ import java.lang.ref.WeakReference;
|
|||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -411,22 +410,6 @@ public final class FXUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String which(String command) {
|
|
||||||
String path = System.getenv("PATH");
|
|
||||||
if (path == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
for (String item : path.split(OperatingSystem.PATH_SEPARATOR)) {
|
|
||||||
try {
|
|
||||||
Path program = Paths.get(item, command);
|
|
||||||
if (Files.isExecutable(program))
|
|
||||||
return program.toRealPath().toString();
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void showFileInExplorer(Path file) {
|
public static void showFileInExplorer(Path file) {
|
||||||
String path = file.toAbsolutePath().toString();
|
String path = file.toAbsolutePath().toString();
|
||||||
|
|
||||||
@@ -435,7 +418,7 @@ public final class FXUtils {
|
|||||||
openCommands = new String[]{"explorer.exe", "/select,", path};
|
openCommands = new String[]{"explorer.exe", "/select,", path};
|
||||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||||
openCommands = new String[]{"/usr/bin/open", "-R", path};
|
openCommands = new String[]{"/usr/bin/open", "-R", path};
|
||||||
else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && which("dbus-send") != null)
|
else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && SystemUtils.which("dbus-send") != null)
|
||||||
openCommands = new String[]{
|
openCommands = new String[]{
|
||||||
"dbus-send",
|
"dbus-send",
|
||||||
"--print-reply",
|
"--print-reply",
|
||||||
@@ -501,10 +484,10 @@ public final class FXUtils {
|
|||||||
}
|
}
|
||||||
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
|
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
|
||||||
for (String browser : linuxBrowsers) {
|
for (String browser : linuxBrowsers) {
|
||||||
String path = which(browser);
|
Path path = SystemUtils.which(browser);
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
try {
|
try {
|
||||||
Runtime.getRuntime().exec(new String[]{browser, link});
|
Runtime.getRuntime().exec(new String[]{path.toString(), link});
|
||||||
return;
|
return;
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.java;
|
package org.jackhuang.hmcl.java;
|
||||||
|
|
||||||
import kala.compress.archivers.ArchiveEntry;
|
import kala.compress.archivers.ArchiveEntry;
|
||||||
import org.jackhuang.hmcl.util.KeyValuePairProperties;
|
import org.jackhuang.hmcl.util.KeyValuePairUtils;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
@@ -33,6 +33,7 @@ import java.io.InputStreamReader;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Glavo
|
* @author Glavo
|
||||||
@@ -60,7 +61,7 @@ public final class JavaInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static JavaInfo fromReleaseFile(BufferedReader reader) throws IOException {
|
public static JavaInfo fromReleaseFile(BufferedReader reader) throws IOException {
|
||||||
KeyValuePairProperties properties = KeyValuePairProperties.load(reader);
|
Map<String, String> properties = KeyValuePairUtils.loadProperties(reader);
|
||||||
String osName = properties.get("OS_NAME");
|
String osName = properties.get("OS_NAME");
|
||||||
String osArch = properties.get("OS_ARCH");
|
String osArch = properties.get("OS_ARCH");
|
||||||
String vendor = properties.get("IMPLEMENTOR");
|
String vendor = properties.get("IMPLEMENTOR");
|
||||||
|
|||||||
@@ -19,25 +19,34 @@ package org.jackhuang.hmcl.util;
|
|||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Glavo
|
* @author Glavo
|
||||||
*/
|
*/
|
||||||
public final class KeyValuePairProperties extends LinkedHashMap<String, String> {
|
public final class KeyValuePairUtils {
|
||||||
public static KeyValuePairProperties load(Path file) throws IOException {
|
public static Map<String, String> loadProperties(Path file) throws IOException {
|
||||||
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
||||||
return load(reader);
|
return loadProperties(reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyValuePairProperties load(BufferedReader reader) throws IOException {
|
public static Map<String, String> loadProperties(BufferedReader reader) throws IOException {
|
||||||
KeyValuePairProperties result = new KeyValuePairProperties();
|
try {
|
||||||
|
return loadProperties(reader.lines().iterator());
|
||||||
|
} catch (UncheckedIOException e) {
|
||||||
|
throw e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> loadProperties(Iterator<String> lineIterator) {
|
||||||
|
Map<String, String> result = new LinkedHashMap<>();
|
||||||
|
while (lineIterator.hasNext()) {
|
||||||
|
String line = lineIterator.next();
|
||||||
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
if (line.startsWith("#"))
|
if (line.startsWith("#"))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -91,4 +100,78 @@ public final class KeyValuePairProperties extends LinkedHashMap<String, String>
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> loadPairs(Path file) throws IOException {
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
||||||
|
return loadPairs(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> loadPairs(BufferedReader reader) throws IOException {
|
||||||
|
try {
|
||||||
|
return loadPairs(reader.lines().iterator());
|
||||||
|
} catch (UncheckedIOException e) {
|
||||||
|
throw e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> loadPairs(Iterator<String> lineIterator) {
|
||||||
|
Map<String, String> result = new LinkedHashMap<>();
|
||||||
|
while (lineIterator.hasNext()) {
|
||||||
|
String line = lineIterator.next();
|
||||||
|
|
||||||
|
int idx = line.indexOf(':');
|
||||||
|
if (idx > 0) {
|
||||||
|
String name = line.substring(0, idx).trim();
|
||||||
|
String value = line.substring(idx + 1).trim();
|
||||||
|
result.put(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Map<String, String>> loadList(Path file) throws IOException {
|
||||||
|
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
||||||
|
return loadList(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Map<String, String>> loadList(BufferedReader reader) throws IOException {
|
||||||
|
try {
|
||||||
|
return loadList(reader.lines().iterator());
|
||||||
|
} catch (UncheckedIOException e) {
|
||||||
|
throw e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Map<String, String>> loadList(Iterator<String> lineIterator) {
|
||||||
|
ArrayList<Map<String, String>> result = new ArrayList<>();
|
||||||
|
Map<String, String> current = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
while (lineIterator.hasNext()) {
|
||||||
|
String line = lineIterator.next();
|
||||||
|
int idx = line.indexOf(':');
|
||||||
|
|
||||||
|
if (idx < 0) {
|
||||||
|
if (!current.isEmpty()) {
|
||||||
|
result.add(current);
|
||||||
|
current = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = line.substring(0, idx).trim();
|
||||||
|
String value = line.substring(idx + 1).trim();
|
||||||
|
|
||||||
|
current.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!current.isEmpty())
|
||||||
|
result.add(current);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyValuePairUtils() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,13 @@ public final class StringUtils {
|
|||||||
return !isBlank(str);
|
return !isBlank(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String capitalizeFirst(String str) {
|
||||||
|
if (str == null || str.isEmpty())
|
||||||
|
return str;
|
||||||
|
|
||||||
|
return Character.toUpperCase(str.charAt(0)) + str.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
public static String substringBeforeLast(String str, char delimiter) {
|
public static String substringBeforeLast(String str, char delimiter) {
|
||||||
return substringBeforeLast(str, delimiter, str);
|
return substringBeforeLast(str, delimiter, str);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util.platform;
|
package org.jackhuang.hmcl.util.platform;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.util.KeyValuePairProperties;
|
import org.jackhuang.hmcl.util.KeyValuePairUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
|
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
|
||||||
import org.jackhuang.hmcl.util.platform.windows.WinTypes;
|
import org.jackhuang.hmcl.util.platform.windows.WinTypes;
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ public enum OperatingSystem {
|
|||||||
Path osReleaseFile = Paths.get("/etc/os-release");
|
Path osReleaseFile = Paths.get("/etc/os-release");
|
||||||
if (Files.exists(osReleaseFile)) {
|
if (Files.exists(osReleaseFile)) {
|
||||||
try {
|
try {
|
||||||
osRelease = KeyValuePairProperties.load(osReleaseFile);
|
osRelease = KeyValuePairUtils.loadProperties(osReleaseFile);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace(System.err);
|
e.printStackTrace(System.err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,13 @@
|
|||||||
package org.jackhuang.hmcl.util.platform;
|
package org.jackhuang.hmcl.util.platform;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.util.DataSizeUnit;
|
import org.jackhuang.hmcl.util.DataSizeUnit;
|
||||||
|
import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
|
||||||
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
||||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
||||||
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
|
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
|
||||||
import org.jackhuang.hmcl.util.platform.linux.LinuxHardwareDetector;
|
import org.jackhuang.hmcl.util.platform.linux.LinuxHardwareDetector;
|
||||||
import org.jackhuang.hmcl.util.platform.macos.MacOSHardwareDetector;
|
import org.jackhuang.hmcl.util.platform.macos.MacOSHardwareDetector;
|
||||||
import org.jackhuang.hmcl.util.platform.windows.WindowsHardwareDetector;
|
import org.jackhuang.hmcl.util.platform.windows.*;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -47,12 +48,18 @@ public final class SystemInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final long TOTAL_MEMORY = DETECTOR.getTotalMemorySize();
|
public static final long TOTAL_MEMORY = DETECTOR.getTotalMemorySize();
|
||||||
|
public static final @Nullable CentralProcessor CENTRAL_PROCESSOR = DETECTOR.detectCentralProcessor();
|
||||||
public static final @Nullable List<GraphicsCard> GRAPHICS_CARDS = DETECTOR.detectGraphicsCards();
|
public static final @Nullable List<GraphicsCard> GRAPHICS_CARDS = DETECTOR.detectGraphicsCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void initialize() {
|
public static void initialize() {
|
||||||
StringBuilder builder = new StringBuilder("System Info:");
|
StringBuilder builder = new StringBuilder("System Info:");
|
||||||
|
|
||||||
|
// CPU
|
||||||
|
CentralProcessor cpu = getCentralProcessor();
|
||||||
|
if (cpu != null)
|
||||||
|
builder.append("\n - CPU: ").append(cpu);
|
||||||
|
|
||||||
// Graphics Card
|
// Graphics Card
|
||||||
List<GraphicsCard> graphicsCards = getGraphicsCards();
|
List<GraphicsCard> graphicsCards = getGraphicsCards();
|
||||||
if (graphicsCards != null) {
|
if (graphicsCards != null) {
|
||||||
@@ -108,6 +115,10 @@ public final class SystemInfo {
|
|||||||
return Long.max(0, totalMemorySize - getFreeMemorySize());
|
return Long.max(0, totalMemorySize - getFreeMemorySize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @Nullable CentralProcessor getCentralProcessor() {
|
||||||
|
return Holder.CENTRAL_PROCESSOR;
|
||||||
|
}
|
||||||
|
|
||||||
public static @Nullable List<GraphicsCard> getGraphicsCards() {
|
public static @Nullable List<GraphicsCard> getGraphicsCards() {
|
||||||
return Holder.GRAPHICS_CARDS;
|
return Holder.GRAPHICS_CARDS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,47 @@
|
|||||||
package org.jackhuang.hmcl.util.platform;
|
package org.jackhuang.hmcl.util.platform;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||||
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
public final class SystemUtils {
|
public final class SystemUtils {
|
||||||
private SystemUtils() {}
|
private SystemUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable Path which(String command) {
|
||||||
|
String path = System.getenv("PATH");
|
||||||
|
if (path == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (String item : path.split(OperatingSystem.PATH_SEPARATOR)) {
|
||||||
|
try {
|
||||||
|
Path program = Paths.get(item, command);
|
||||||
|
if (Files.isExecutable(program))
|
||||||
|
return program.toRealPath();
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static int callExternalProcess(String... command) throws IOException, InterruptedException {
|
public static int callExternalProcess(String... command) throws IOException, InterruptedException {
|
||||||
return callExternalProcess(Arrays.asList(command));
|
return callExternalProcess(Arrays.asList(command));
|
||||||
@@ -43,6 +75,34 @@ public final class SystemUtils {
|
|||||||
return managedProcess.getProcess().waitFor();
|
return managedProcess.getProcess().waitFor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T run(List<String> command, ExceptionalFunction<InputStream, T, ?> convert) throws Exception {
|
||||||
|
File nul = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||||
|
? new File("NUL")
|
||||||
|
: new File("/dev/null");
|
||||||
|
|
||||||
|
Process process = new ProcessBuilder(command)
|
||||||
|
.redirectInput(nul)
|
||||||
|
.redirectError(nul)
|
||||||
|
.start();
|
||||||
|
try {
|
||||||
|
InputStream inputStream = process.getInputStream();
|
||||||
|
CompletableFuture<T> future = CompletableFuture.supplyAsync(
|
||||||
|
Lang.wrap(() -> convert.apply(inputStream)),
|
||||||
|
Schedulers.io());
|
||||||
|
|
||||||
|
if (!process.waitFor(15, TimeUnit.SECONDS))
|
||||||
|
throw new TimeoutException();
|
||||||
|
|
||||||
|
if (process.exitValue() != 0)
|
||||||
|
throw new IOException("Bad exit code: " + process.exitValue());
|
||||||
|
|
||||||
|
return future.get();
|
||||||
|
} finally {
|
||||||
|
if (process.isAlive())
|
||||||
|
process.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean supportJVMAttachment() {
|
public static boolean supportJVMAttachment() {
|
||||||
return JavaRuntime.CURRENT_VERSION >= 9 && Thread.currentThread().getContextClassLoader().getResource("com/sun/tools/attach/VirtualMachine.class") != null;
|
return JavaRuntime.CURRENT_VERSION >= 9 && Thread.currentThread().getContextClassLoader().getResource("com/sun/tools/attach/VirtualMachine.class") != null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* 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.hardware;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Glavo
|
||||||
|
*/
|
||||||
|
public final class CentralProcessor {
|
||||||
|
private final String name;
|
||||||
|
private final @Nullable HardwareVendor vendor;
|
||||||
|
private final @Nullable Cores cores;
|
||||||
|
|
||||||
|
private CentralProcessor(String name, @Nullable HardwareVendor vendor, @Nullable Cores cores) {
|
||||||
|
this.name = name;
|
||||||
|
this.vendor = vendor;
|
||||||
|
this.cores = cores;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable HardwareVendor getVendor() {
|
||||||
|
return vendor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Cores getCores() {
|
||||||
|
return cores;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder(128);
|
||||||
|
if (cores != null && cores.packages > 1)
|
||||||
|
builder.append(cores.packages).append(" x ");
|
||||||
|
|
||||||
|
builder.append(name);
|
||||||
|
|
||||||
|
if (cores != null) {
|
||||||
|
builder.append(" (");
|
||||||
|
builder.append(cores.physical).append(" Cores");
|
||||||
|
if (cores.logical > 0 && cores.logical != cores.physical)
|
||||||
|
builder.append(" / ").append(cores.logical).append(" Threads");
|
||||||
|
builder.append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Cores {
|
||||||
|
public final int physical;
|
||||||
|
public final int logical;
|
||||||
|
public final int packages;
|
||||||
|
|
||||||
|
public Cores(int logical) {
|
||||||
|
this(logical, logical, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cores(int physical, int logical, int packages) {
|
||||||
|
this.physical = physical;
|
||||||
|
this.logical = logical;
|
||||||
|
this.packages = packages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Cores[physical=%d, logical=%d, packages=%d]", physical, logical, packages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private String name;
|
||||||
|
private @Nullable HardwareVendor vendor;
|
||||||
|
private @Nullable Cores cores;
|
||||||
|
|
||||||
|
public CentralProcessor build() {
|
||||||
|
String name = this.name;
|
||||||
|
if (name == null) {
|
||||||
|
if (vendor != null)
|
||||||
|
name = vendor + " Processor";
|
||||||
|
else
|
||||||
|
name = "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CentralProcessor(name, vendor, cores);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HardwareVendor getVendor() {
|
||||||
|
return vendor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setVendor(HardwareVendor vendor) {
|
||||||
|
this.vendor = vendor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cores getCores() {
|
||||||
|
return cores;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setCores(Cores cores) {
|
||||||
|
this.cores = cores;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* 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.hardware;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
|
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||||
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Glavo
|
||||||
|
*/
|
||||||
|
final class FastFetchUtils {
|
||||||
|
private FastFetchUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T get(String type, TypeToken<T> resultType) {
|
||||||
|
Path fastfetch = SystemUtils.which("fastfetch");
|
||||||
|
if (fastfetch == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String json;
|
||||||
|
try {
|
||||||
|
json = SystemUtils.run(Arrays.asList(fastfetch.toString(), "--structure", type, "--format", "json"),
|
||||||
|
inputStream -> IOUtils.readFullyAsString(inputStream, OperatingSystem.NATIVE_CHARSET));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warning("Failed to get result from fastfetch", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<Result<T>> list = JsonUtils.GSON.fromJson(json, JsonUtils.listTypeOf(Result.typeOf(resultType)));
|
||||||
|
|
||||||
|
Result<T> result;
|
||||||
|
if (list == null
|
||||||
|
|| list.size() != 1
|
||||||
|
|| (result = list.get(0)) == null
|
||||||
|
|| !type.equalsIgnoreCase(result.type)
|
||||||
|
|| result.result == null) {
|
||||||
|
throw new IOException("Illegal output format");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.result;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warning("Failed to parse fastfetch output: " + json, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static @Nullable CentralProcessor detectCentralProcessor() {
|
||||||
|
CPUInfo cpuInfo = get("CPU", TypeToken.get(CPUInfo.class));
|
||||||
|
|
||||||
|
if (cpuInfo == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
CentralProcessor.Builder builder = new CentralProcessor.Builder()
|
||||||
|
.setName(cpuInfo.cpu)
|
||||||
|
.setVendor(HardwareVendor.of(cpuInfo.vendor));
|
||||||
|
|
||||||
|
if (cpuInfo.cores != null) {
|
||||||
|
try {
|
||||||
|
String physical = cpuInfo.cores.get("physical");
|
||||||
|
String logical = cpuInfo.cores.get("logical");
|
||||||
|
|
||||||
|
int cores = physical != null ? Integer.parseInt(physical) : 0;
|
||||||
|
int threads = logical != null ? Integer.parseInt(logical) : 0;
|
||||||
|
int packages = Integer.max(cpuInfo.packages, 1);
|
||||||
|
|
||||||
|
if (cores > 0 && threads == 0)
|
||||||
|
threads = cores;
|
||||||
|
else if (threads > 0 && cores == 0)
|
||||||
|
cores = threads;
|
||||||
|
|
||||||
|
builder.setCores(new CentralProcessor.Cores(cores, threads, packages));
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
static @Nullable List<GraphicsCard> detectGraphicsCards() {
|
||||||
|
List<GPUInfo> gpuInfos = get("GPU", JsonUtils.listTypeOf(GPUInfo.class));
|
||||||
|
if (gpuInfos == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
ArrayList<GraphicsCard> result = new ArrayList<>(gpuInfos.size());
|
||||||
|
for (GPUInfo gpuInfo : gpuInfos) {
|
||||||
|
if (gpuInfo == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
GraphicsCard.Builder builder = new GraphicsCard.Builder()
|
||||||
|
.setName(gpuInfo.name)
|
||||||
|
.setVendor(HardwareVendor.of(gpuInfo.vendor))
|
||||||
|
.setDriver(gpuInfo.driver);
|
||||||
|
|
||||||
|
if ("Discrete".equalsIgnoreCase(gpuInfo.type))
|
||||||
|
builder.setType(GraphicsCard.Type.Discrete);
|
||||||
|
else if ("Integrated".equalsIgnoreCase(gpuInfo.type))
|
||||||
|
builder.setType(GraphicsCard.Type.Integrated);
|
||||||
|
|
||||||
|
result.add(builder.build());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static final class Result<T> {
|
||||||
|
static <T> TypeToken<Result<T>> typeOf(TypeToken<T> type) {
|
||||||
|
return (TypeToken<Result<T>>) TypeToken.getParameterized(Result.class, type.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
String type;
|
||||||
|
T result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CPUInfo {
|
||||||
|
String cpu;
|
||||||
|
String vendor;
|
||||||
|
int packages;
|
||||||
|
Map<String, String> cores;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class GPUInfo {
|
||||||
|
String name;
|
||||||
|
String vendor;
|
||||||
|
String type;
|
||||||
|
String driver;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util.platform.hardware;
|
package org.jackhuang.hmcl.util.platform.hardware;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -32,12 +31,12 @@ public final class GraphicsCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final @Nullable Vendor vendor;
|
private final @Nullable HardwareVendor vendor;
|
||||||
private final @Nullable Type type;
|
private final @Nullable Type type;
|
||||||
private final @Nullable String driver;
|
private final @Nullable String driver;
|
||||||
private final @Nullable String driverVersion;
|
private final @Nullable String driverVersion;
|
||||||
|
|
||||||
private GraphicsCard(String name, @Nullable Vendor vendor, @Nullable Type type, @Nullable String driver, @Nullable String driverVersion) {
|
private GraphicsCard(String name, @Nullable HardwareVendor vendor, @Nullable Type type, @Nullable String driver, @Nullable String driverVersion) {
|
||||||
this.name = Objects.requireNonNull(name);
|
this.name = Objects.requireNonNull(name);
|
||||||
this.vendor = vendor;
|
this.vendor = vendor;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -49,7 +48,7 @@ public final class GraphicsCard {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Vendor getVendor() {
|
public @Nullable HardwareVendor getVendor() {
|
||||||
return vendor;
|
return vendor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,132 +67,6 @@ public final class GraphicsCard {
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Vendor {
|
|
||||||
public static final Vendor INTEL = new Vendor("Intel");
|
|
||||||
public static final Vendor NVIDIA = new Vendor("NVIDIA");
|
|
||||||
public static final Vendor AMD = new Vendor("AMD");
|
|
||||||
public static final Vendor APPLE = new Vendor("Apple");
|
|
||||||
public static final Vendor QUALCOMM = new Vendor("Qualcomm");
|
|
||||||
public static final Vendor MTK = new Vendor("MTK");
|
|
||||||
public static final Vendor VMWARE = new Vendor("VMware");
|
|
||||||
public static final Vendor PARALLEL = new Vendor("Parallel");
|
|
||||||
public static final Vendor MICROSOFT = new Vendor("Microsoft");
|
|
||||||
public static final Vendor MOORE_THREADS = new Vendor("Moore Threads");
|
|
||||||
public static final Vendor BROADCOM = new Vendor("Broadcom");
|
|
||||||
public static final Vendor IMG = new Vendor("Imagination");
|
|
||||||
public static final Vendor LOONGSON = new Vendor("Loongson");
|
|
||||||
public static final Vendor JINGJIA_MICRO = new Vendor("Jingjia Micro");
|
|
||||||
public static final Vendor HUAWEI = new Vendor("Huawei");
|
|
||||||
public static final Vendor ZHAOXIN = new Vendor("Zhaoxin");
|
|
||||||
|
|
||||||
public static @Nullable Vendor getKnown(String name) {
|
|
||||||
if (name == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
String lower = name.toLowerCase(Locale.ROOT);
|
|
||||||
if (lower.startsWith("intel")) return INTEL;
|
|
||||||
if (lower.startsWith("nvidia")) return NVIDIA;
|
|
||||||
if (lower.startsWith("advanced micro devices")
|
|
||||||
|| (lower.startsWith("amd") && !(lower.length() > 3 && Character.isAlphabetic(lower.charAt(3)))))
|
|
||||||
return AMD;
|
|
||||||
if (lower.equals("brcm") || lower.startsWith("broadcom")) return BROADCOM;
|
|
||||||
if (lower.startsWith("mediatek")) return MTK;
|
|
||||||
if (lower.startsWith("qualcomm")) return QUALCOMM;
|
|
||||||
if (lower.startsWith("apple")) return APPLE;
|
|
||||||
if (lower.startsWith("microsoft")) return MICROSOFT;
|
|
||||||
if (lower.startsWith("imagination") || lower.equals("img")) return IMG;
|
|
||||||
|
|
||||||
if (lower.startsWith("loongson")) return LOONGSON;
|
|
||||||
if (lower.startsWith("moore threads")) return MOORE_THREADS;
|
|
||||||
if (lower.startsWith("jingjia")) return JINGJIA_MICRO;
|
|
||||||
if (lower.startsWith("huawei")) return HUAWEI;
|
|
||||||
if (lower.startsWith("zhaoxin")) return ZHAOXIN;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Contract("null -> null; !null -> !null")
|
|
||||||
public static Vendor of(String name) {
|
|
||||||
if (name == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Vendor known = getKnown(name);
|
|
||||||
return known != null ? known : new Vendor(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @Nullable Vendor ofId(int vendorId) {
|
|
||||||
// https://devicehunt.com/all-pci-vendors
|
|
||||||
switch (vendorId) {
|
|
||||||
case 0x106b:
|
|
||||||
return APPLE;
|
|
||||||
case 0x1002:
|
|
||||||
case 0x1022:
|
|
||||||
case 0x1dd8: // AMD Pensando Systems
|
|
||||||
case 0x1924: // AMD Solarflare
|
|
||||||
return AMD;
|
|
||||||
case 0x8086:
|
|
||||||
case 0x8087:
|
|
||||||
case 0x03e7:
|
|
||||||
return INTEL;
|
|
||||||
case 0x0955:
|
|
||||||
case 0x10de:
|
|
||||||
case 0x12d2:
|
|
||||||
return NVIDIA;
|
|
||||||
case 0x1ed5:
|
|
||||||
return MOORE_THREADS;
|
|
||||||
case 0x168c:
|
|
||||||
case 0x5143:
|
|
||||||
return QUALCOMM;
|
|
||||||
case 0x14c3:
|
|
||||||
return MTK;
|
|
||||||
case 0x15ad:
|
|
||||||
return VMWARE;
|
|
||||||
case 0x1ab8:
|
|
||||||
return PARALLEL;
|
|
||||||
case 0x1414:
|
|
||||||
return MICROSOFT;
|
|
||||||
case 0x182f:
|
|
||||||
case 0x14e4:
|
|
||||||
return BROADCOM;
|
|
||||||
case 0x0014:
|
|
||||||
return LOONGSON;
|
|
||||||
case 0x0731:
|
|
||||||
return JINGJIA_MICRO;
|
|
||||||
case 0x19e5:
|
|
||||||
return HUAWEI;
|
|
||||||
case 0x1d17:
|
|
||||||
return ZHAOXIN;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
public Vendor(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
return o instanceof Vendor && name.equals(((Vendor) o).name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return name.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
Integrated,
|
Integrated,
|
||||||
Discrete
|
Discrete
|
||||||
@@ -201,14 +74,19 @@ public final class GraphicsCard {
|
|||||||
|
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
private String name;
|
private String name;
|
||||||
private Vendor vendor;
|
private HardwareVendor vendor;
|
||||||
private Type type;
|
private Type type;
|
||||||
private String driver;
|
private String driver;
|
||||||
private String driverVersion;
|
private String driverVersion;
|
||||||
|
|
||||||
public GraphicsCard build() {
|
public GraphicsCard build() {
|
||||||
if (name == null)
|
String name = this.name;
|
||||||
throw new IllegalStateException("Name not set");
|
if (name == null) {
|
||||||
|
if (vendor != null)
|
||||||
|
name = vendor + " Graphics";
|
||||||
|
else
|
||||||
|
name = "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
return new GraphicsCard(name, vendor, type, driver, driverVersion);
|
return new GraphicsCard(name, vendor, type, driver, driverVersion);
|
||||||
}
|
}
|
||||||
@@ -222,11 +100,11 @@ public final class GraphicsCard {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vendor getVendor() {
|
public HardwareVendor getVendor() {
|
||||||
return vendor;
|
return vendor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setVendor(Vendor vendor) {
|
public Builder setVendor(HardwareVendor vendor) {
|
||||||
this.vendor = vendor;
|
this.vendor = vendor;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,12 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("ALL")
|
@SuppressWarnings("ALL")
|
||||||
public class HardwareDetector {
|
public class HardwareDetector {
|
||||||
|
public @Nullable CentralProcessor detectCentralProcessor() {
|
||||||
|
return FastFetchUtils.detectCentralProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable List<GraphicsCard> detectGraphicsCards() {
|
public @Nullable List<GraphicsCard> detectGraphicsCards() {
|
||||||
return null;
|
return FastFetchUtils.detectGraphicsCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTotalMemorySize() {
|
public long getTotalMemorySize() {
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* 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.hardware;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public final class HardwareVendor {
|
||||||
|
public static final HardwareVendor INTEL = new HardwareVendor("Intel");
|
||||||
|
public static final HardwareVendor NVIDIA = new HardwareVendor("NVIDIA");
|
||||||
|
public static final HardwareVendor AMD = new HardwareVendor("AMD");
|
||||||
|
public static final HardwareVendor APPLE = new HardwareVendor("Apple");
|
||||||
|
public static final HardwareVendor ARM = new HardwareVendor("ARM");
|
||||||
|
public static final HardwareVendor QUALCOMM = new HardwareVendor("Qualcomm");
|
||||||
|
public static final HardwareVendor MTK = new HardwareVendor("MTK");
|
||||||
|
public static final HardwareVendor VMWARE = new HardwareVendor("VMware");
|
||||||
|
public static final HardwareVendor PARALLEL = new HardwareVendor("Parallel");
|
||||||
|
public static final HardwareVendor MICROSOFT = new HardwareVendor("Microsoft");
|
||||||
|
public static final HardwareVendor MOORE_THREADS = new HardwareVendor("Moore Threads");
|
||||||
|
public static final HardwareVendor BROADCOM = new HardwareVendor("Broadcom");
|
||||||
|
public static final HardwareVendor IMG = new HardwareVendor("Imagination");
|
||||||
|
public static final HardwareVendor LOONGSON = new HardwareVendor("Loongson");
|
||||||
|
public static final HardwareVendor JINGJIA_MICRO = new HardwareVendor("Jingjia Micro");
|
||||||
|
public static final HardwareVendor HUAWEI = new HardwareVendor("Huawei");
|
||||||
|
public static final HardwareVendor ZHAOXIN = new HardwareVendor("Zhaoxin");
|
||||||
|
public static final HardwareVendor SAMSUNG = new HardwareVendor("Samsung");
|
||||||
|
public static final HardwareVendor MARVELL = new HardwareVendor("Marvell");
|
||||||
|
public static final HardwareVendor AMPERE = new HardwareVendor("Ampere");
|
||||||
|
public static final HardwareVendor ROCKCHIP = new HardwareVendor("Rockchip");
|
||||||
|
|
||||||
|
// RISC-V
|
||||||
|
public static final HardwareVendor THEAD = new HardwareVendor("T-Head");
|
||||||
|
public static final HardwareVendor STARFIVE = new HardwareVendor("StarFive");
|
||||||
|
public static final HardwareVendor ESWIN = new HardwareVendor("ESWIN");
|
||||||
|
public static final HardwareVendor SPACEMIT = new HardwareVendor("SpacemiT");
|
||||||
|
|
||||||
|
public static @Nullable HardwareVendor getKnown(String name) {
|
||||||
|
if (name == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String lower = name.toLowerCase(Locale.ROOT);
|
||||||
|
if (lower.startsWith("intel") || lower.startsWith("genuineintel")) return INTEL;
|
||||||
|
if (lower.startsWith("nvidia")) return NVIDIA;
|
||||||
|
if (lower.startsWith("advanced micro devices")
|
||||||
|
|| lower.startsWith("authenticamd")
|
||||||
|
|| (lower.startsWith("amd") && !(lower.length() > 3 && Character.isAlphabetic(lower.charAt(3)))))
|
||||||
|
return AMD;
|
||||||
|
if (lower.equals("brcm") || lower.startsWith("broadcom")) return BROADCOM;
|
||||||
|
if (lower.startsWith("mediatek")) return MTK;
|
||||||
|
if (lower.equals("qcom") || lower.startsWith("qualcomm")) return QUALCOMM;
|
||||||
|
if (lower.startsWith("apple")) return APPLE;
|
||||||
|
if (lower.startsWith("microsoft")) return MICROSOFT;
|
||||||
|
if (lower.startsWith("imagination") || lower.equals("img")) return IMG;
|
||||||
|
if (lower.startsWith("loongson")) return LOONGSON;
|
||||||
|
if (lower.startsWith("moore threads")) return MOORE_THREADS;
|
||||||
|
if (lower.startsWith("jingjia")) return JINGJIA_MICRO;
|
||||||
|
if (lower.startsWith("huawei") || lower.startsWith("hisilicon")) return HUAWEI;
|
||||||
|
if (lower.startsWith("zhaoxin")) return ZHAOXIN;
|
||||||
|
if (lower.startsWith("marvell")) return MARVELL;
|
||||||
|
if (lower.startsWith("samsung")) return SAMSUNG;
|
||||||
|
if (lower.startsWith("ampere")) return AMPERE;
|
||||||
|
if (lower.startsWith("rockchip")) return ROCKCHIP;
|
||||||
|
if (lower.startsWith("thead") || lower.startsWith("t-head")) return THEAD;
|
||||||
|
if (lower.startsWith("starfive")) return STARFIVE;
|
||||||
|
if (lower.startsWith("eswin")) return ESWIN;
|
||||||
|
if (lower.startsWith("spacemit")) return SPACEMIT;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract("null -> null; !null -> !null")
|
||||||
|
public static HardwareVendor of(String name) {
|
||||||
|
if (name == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
HardwareVendor known = getKnown(name);
|
||||||
|
return known != null ? known : new HardwareVendor(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable HardwareVendor ofPciVendorId(int vendorId) {
|
||||||
|
// https://devicehunt.com/all-pci-vendors
|
||||||
|
switch (vendorId) {
|
||||||
|
case 0x106b:
|
||||||
|
return APPLE;
|
||||||
|
case 0x1002:
|
||||||
|
case 0x1022:
|
||||||
|
case 0x1dd8: // AMD Pensando Systems
|
||||||
|
case 0x1924: // AMD Solarflare
|
||||||
|
return AMD;
|
||||||
|
case 0x8086:
|
||||||
|
case 0x8087:
|
||||||
|
case 0x03e7:
|
||||||
|
return INTEL;
|
||||||
|
case 0x0955:
|
||||||
|
case 0x10de:
|
||||||
|
case 0x12d2:
|
||||||
|
return NVIDIA;
|
||||||
|
case 0x1ed5:
|
||||||
|
return MOORE_THREADS;
|
||||||
|
case 0x168c:
|
||||||
|
case 0x5143:
|
||||||
|
return QUALCOMM;
|
||||||
|
case 0x14c3:
|
||||||
|
return MTK;
|
||||||
|
case 0x15ad:
|
||||||
|
return VMWARE;
|
||||||
|
case 0x1ab8:
|
||||||
|
return PARALLEL;
|
||||||
|
case 0x1414:
|
||||||
|
return MICROSOFT;
|
||||||
|
case 0x182f:
|
||||||
|
case 0x14e4:
|
||||||
|
return BROADCOM;
|
||||||
|
case 0x0014:
|
||||||
|
return LOONGSON;
|
||||||
|
case 0x0731:
|
||||||
|
return JINGJIA_MICRO;
|
||||||
|
case 0x19e5:
|
||||||
|
return HUAWEI;
|
||||||
|
case 0x1d17:
|
||||||
|
return ZHAOXIN;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable HardwareVendor ofArmImplementerId(int implementerId) {
|
||||||
|
// https://github.com/util-linux/util-linux/blob/0a21358af3e50fcb13a9bf3702779f11a4739667/sys-utils/lscpu-arm.c#L301
|
||||||
|
switch (implementerId) {
|
||||||
|
case 0x41:
|
||||||
|
return ARM;
|
||||||
|
case 0x42:
|
||||||
|
return BROADCOM;
|
||||||
|
case 0x48:
|
||||||
|
return HUAWEI;
|
||||||
|
case 0x4e:
|
||||||
|
return NVIDIA;
|
||||||
|
case 0x51:
|
||||||
|
return QUALCOMM;
|
||||||
|
case 0x53:
|
||||||
|
return SAMSUNG;
|
||||||
|
case 0x56:
|
||||||
|
return MARVELL;
|
||||||
|
case 0x61:
|
||||||
|
return APPLE;
|
||||||
|
case 0x69:
|
||||||
|
return INTEL;
|
||||||
|
case 0x6D:
|
||||||
|
return MICROSOFT;
|
||||||
|
case 0xc0:
|
||||||
|
return AMPERE;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public HardwareVendor(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof HardwareVendor && name.equals(((HardwareVendor) o).name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* 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.linux;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.util.KeyValuePairUtils;
|
||||||
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
|
import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
|
||||||
|
import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Glavo
|
||||||
|
* @see <a href="https://github.com/fastfetch-cli/fastfetch/blob/e13eb05073297aa6f1dc244ab3414e98170a43e1/src/detection/cpu/cpu_linux.c">cpu_linux.c</a>
|
||||||
|
*/
|
||||||
|
final class LinuxCPUDetector {
|
||||||
|
|
||||||
|
private static final String CPUINFO_PATH = "/proc/cpuinfo";
|
||||||
|
|
||||||
|
private static TreeMap<Integer, Map<String, String>> loadCPUInfo() {
|
||||||
|
try {
|
||||||
|
List<Map<String, String>> list = KeyValuePairUtils.loadList(Paths.get(CPUINFO_PATH));
|
||||||
|
TreeMap<Integer, Map<String, String>> result = new TreeMap<>();
|
||||||
|
for (Map<String, String> map : list) {
|
||||||
|
String id = map.get("processor");
|
||||||
|
if (id != null) {
|
||||||
|
result.put(Integer.parseInt(id), map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warning("Failed to load /proc/cpuinfo", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://asahilinux.org/docs/hw/soc/soc-codenames/
|
||||||
|
private static String appleCodeToName(int code) {
|
||||||
|
switch (code) {
|
||||||
|
case 8103:
|
||||||
|
return "Apple M1";
|
||||||
|
case 6000:
|
||||||
|
return "Apple M1 Pro";
|
||||||
|
case 6001:
|
||||||
|
return "Apple M1 Max";
|
||||||
|
case 6002:
|
||||||
|
return "Apple M1 Ultra";
|
||||||
|
case 8112:
|
||||||
|
return "Apple M2";
|
||||||
|
case 6020:
|
||||||
|
return "Apple M2 Pro";
|
||||||
|
case 6021:
|
||||||
|
return "Apple M2 Max";
|
||||||
|
case 6022:
|
||||||
|
return "Apple M2 Ultra";
|
||||||
|
case 8122:
|
||||||
|
return "Apple M3";
|
||||||
|
case 6030:
|
||||||
|
return "Apple M3 Pro";
|
||||||
|
case 6031:
|
||||||
|
case 6034:
|
||||||
|
return "Apple M3 Max";
|
||||||
|
case 8132:
|
||||||
|
return "Apple M4";
|
||||||
|
case 6040:
|
||||||
|
return "Apple M4 Pro";
|
||||||
|
case 6041:
|
||||||
|
return "Apple M4 Max";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void detectName(CentralProcessor.Builder builder, TreeMap<Integer, Map<String, String>> cpuInfo) {
|
||||||
|
// assert !cpuInfo.isEmpty();
|
||||||
|
Map<String, String> firstCore = cpuInfo.firstEntry().getValue();
|
||||||
|
|
||||||
|
String modelName = firstCore.get("model name");
|
||||||
|
if (modelName == null)
|
||||||
|
modelName = firstCore.get("Model Name");
|
||||||
|
if (modelName == null)
|
||||||
|
modelName = firstCore.get("cpu model");
|
||||||
|
|
||||||
|
if (modelName != null) {
|
||||||
|
builder.setName(modelName);
|
||||||
|
builder.setVendor(HardwareVendor.of(firstCore.get("vendor_id")));
|
||||||
|
|
||||||
|
if (builder.getVendor() == null && modelName.startsWith("Loongson"))
|
||||||
|
builder.setVendor(HardwareVendor.LOONGSON);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Path compatiblePath = Paths.get("/proc/device-tree/compatible");
|
||||||
|
if (Files.isRegularFile(compatiblePath)) {
|
||||||
|
// device-vendor,device-model\0soc-vendor,soc-model\0
|
||||||
|
String[] data = FileUtils.readText(compatiblePath).split("\0");
|
||||||
|
|
||||||
|
for (int i = data.length - 1; i >= 0; i--) {
|
||||||
|
String device = data[i];
|
||||||
|
int idx = device.indexOf(',');
|
||||||
|
if (idx <= 0 || idx >= device.length() - 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String vendor = device.substring(0, idx);
|
||||||
|
String model = device.substring(idx + 1);
|
||||||
|
|
||||||
|
if (model.startsWith("generic-"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
builder.setVendor(HardwareVendor.getKnown(vendor));
|
||||||
|
if (builder.getVendor() == null)
|
||||||
|
builder.setVendor(HardwareVendor.of(StringUtils.capitalizeFirst(vendor)));
|
||||||
|
|
||||||
|
if (builder.getVendor() == HardwareVendor.APPLE) {
|
||||||
|
// https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/apple
|
||||||
|
if (model.matches("t[0-9]+"))
|
||||||
|
builder.setName(appleCodeToName(Integer.parseInt(model.substring(1))));
|
||||||
|
|
||||||
|
if (builder.getName() == null)
|
||||||
|
builder.setName("Apple Silicon " + model);
|
||||||
|
} else if (builder.getVendor() == HardwareVendor.QUALCOMM) {
|
||||||
|
// https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/qcom
|
||||||
|
if (model.startsWith("x"))
|
||||||
|
builder.setName("Qualcomm Snapdragon X Elite " + model.toUpperCase(Locale.ROOT));
|
||||||
|
} else if (builder.getVendor() == HardwareVendor.BROADCOM)
|
||||||
|
// Raspberry Pi
|
||||||
|
builder.setName("Broadcom " + model.toUpperCase(Locale.ROOT));
|
||||||
|
|
||||||
|
if (builder.getName() == null)
|
||||||
|
builder.setName(builder.getVendor() + " " + model.toUpperCase(Locale.ROOT));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warning("Failed to detect CPU name from /proc/device-tree/compatible", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void detectCores(CentralProcessor.Builder builder, TreeMap<Integer, Map<String, String>> cpuInfo) {
|
||||||
|
// assert !cpuInfo.isEmpty();
|
||||||
|
|
||||||
|
int logical = cpuInfo.size();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, String> firstCore = cpuInfo.firstEntry().getValue();
|
||||||
|
if (firstCore.containsKey("cpu cores") && firstCore.containsKey("physical id")) {
|
||||||
|
TreeMap<Integer, Integer> cpuCores = new TreeMap<>();
|
||||||
|
TreeSet<Integer> physicalIds = new TreeSet<>();
|
||||||
|
|
||||||
|
for (Map<String, String> core : cpuInfo.values()) {
|
||||||
|
int cores = Integer.parseInt(core.get("cpu cores"));
|
||||||
|
int physicalId = Integer.parseInt(core.get("physical id"));
|
||||||
|
|
||||||
|
cpuCores.put(physicalId, cores);
|
||||||
|
physicalIds.add(physicalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
int physical = 0;
|
||||||
|
for (Integer value : cpuCores.values()) {
|
||||||
|
physical += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setCores(new CentralProcessor.Cores(physical, logical, physicalIds.size()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warning("Failed to detect CPU cores", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can check /sys/devices/system/cpu, but I don't think it's necessary.
|
||||||
|
builder.setCores(new CentralProcessor.Cores(logical));
|
||||||
|
}
|
||||||
|
|
||||||
|
static @Nullable CentralProcessor detect() {
|
||||||
|
TreeMap<Integer, Map<String, String>> cpuInfo = loadCPUInfo();
|
||||||
|
if (cpuInfo == null || cpuInfo.isEmpty()) return null;
|
||||||
|
|
||||||
|
CentralProcessor.Builder builder = new CentralProcessor.Builder();
|
||||||
|
detectName(builder, cpuInfo);
|
||||||
|
if (builder.getName() == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
detectCores(builder, cpuInfo);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinuxCPUDetector() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,8 +21,10 @@ import org.glavo.pci.ids.PCIIDsDatabase;
|
|||||||
import org.glavo.pci.ids.model.Device;
|
import org.glavo.pci.ids.model.Device;
|
||||||
import org.glavo.pci.ids.model.Vendor;
|
import org.glavo.pci.ids.model.Vendor;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
||||||
|
import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
@@ -52,7 +54,7 @@ final class LinuxGPUDetector {
|
|||||||
private static final Pattern PCI_DEVICE_PATTERN =
|
private static final Pattern PCI_DEVICE_PATTERN =
|
||||||
Pattern.compile("(?<pciDomain>\\p{XDigit}+):(?<pciBus>\\p{XDigit}+):(?<pciDevice>\\p{XDigit}+)\\.(?<pciFunc>\\p{XDigit}+)");
|
Pattern.compile("(?<pciDomain>\\p{XDigit}+):(?<pciBus>\\p{XDigit}+):(?<pciDevice>\\p{XDigit}+)\\.(?<pciFunc>\\p{XDigit}+)");
|
||||||
private static final Pattern OF_DEVICE_PATTERN =
|
private static final Pattern OF_DEVICE_PATTERN =
|
||||||
Pattern.compile("of:NgpuT[^C]*C(?<compatible>.*)");
|
Pattern.compile("of:N(img)?gpuT[^C]*C(?<compatible>.*)");
|
||||||
|
|
||||||
private static PCIIDsDatabase getPCIIDsDatabase() {
|
private static PCIIDsDatabase getPCIIDsDatabase() {
|
||||||
SoftReference<PCIIDsDatabase> databaseWeakReference = LinuxGPUDetector.databaseCache;
|
SoftReference<PCIIDsDatabase> databaseWeakReference = LinuxGPUDetector.databaseCache;
|
||||||
@@ -136,12 +138,11 @@ final class LinuxGPUDetector {
|
|||||||
int pciDevice = Integer.parseInt(matcher.group("pciDevice"), 16);
|
int pciDevice = Integer.parseInt(matcher.group("pciDevice"), 16);
|
||||||
int pciFunc = Integer.parseInt(matcher.group("pciFunc"), 16);
|
int pciFunc = Integer.parseInt(matcher.group("pciFunc"), 16);
|
||||||
|
|
||||||
builder.setVendor(GraphicsCard.Vendor.ofId(vendorId));
|
builder.setVendor(HardwareVendor.ofPciVendorId(vendorId));
|
||||||
|
|
||||||
detectDriver(builder, deviceDir);
|
detectDriver(builder, deviceDir);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (builder.getVendor() == GraphicsCard.Vendor.AMD) {
|
if (builder.getVendor() == HardwareVendor.AMD) {
|
||||||
Path hwmon = deviceDir.resolve("hwmon");
|
Path hwmon = deviceDir.resolve("hwmon");
|
||||||
try (Stream<Path> subDirs = Files.list(hwmon)) {
|
try (Stream<Path> subDirs = Files.list(hwmon)) {
|
||||||
for (Path subDir : Lang.toIterable(subDirs)) {
|
for (Path subDir : Lang.toIterable(subDirs)) {
|
||||||
@@ -173,7 +174,7 @@ final class LinuxGPUDetector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (builder.getVendor() == GraphicsCard.Vendor.INTEL) {
|
} else if (builder.getVendor() == HardwareVendor.INTEL) {
|
||||||
builder.setType(pciDevice == 20 ? GraphicsCard.Type.Integrated : GraphicsCard.Type.Discrete);
|
builder.setType(pciDevice == 20 ? GraphicsCard.Type.Integrated : GraphicsCard.Type.Discrete);
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
@@ -185,7 +186,7 @@ final class LinuxGPUDetector {
|
|||||||
Vendor vendor = database.findVendor(vendorId);
|
Vendor vendor = database.findVendor(vendorId);
|
||||||
if (vendor != null) {
|
if (vendor != null) {
|
||||||
if (builder.getVendor() == null)
|
if (builder.getVendor() == null)
|
||||||
builder.setVendor(GraphicsCard.Vendor.of(vendor.getName()));
|
builder.setVendor(HardwareVendor.of(vendor.getName()));
|
||||||
|
|
||||||
if (builder.getName() == null) {
|
if (builder.getName() == null) {
|
||||||
Device device = vendor.getDevices().get(deviceId);
|
Device device = vendor.getDevices().get(deviceId);
|
||||||
@@ -223,13 +224,13 @@ final class LinuxGPUDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (builder.getType() == null) {
|
if (builder.getType() == null) {
|
||||||
if (builder.getVendor() == GraphicsCard.Vendor.NVIDIA) {
|
if (builder.getVendor() == HardwareVendor.NVIDIA) {
|
||||||
if (builder.getName().startsWith("GeForce")
|
if (builder.getName().startsWith("GeForce")
|
||||||
|| builder.getName().startsWith("Quadro")
|
|| builder.getName().startsWith("Quadro")
|
||||||
|| builder.getName().startsWith("Tesla"))
|
|| builder.getName().startsWith("Tesla"))
|
||||||
builder.setType(GraphicsCard.Type.Discrete);
|
builder.setType(GraphicsCard.Type.Discrete);
|
||||||
|
|
||||||
} else if (builder.getVendor() == GraphicsCard.Vendor.MOORE_THREADS) {
|
} else if (builder.getVendor() == HardwareVendor.MOORE_THREADS) {
|
||||||
if (builder.getName().startsWith("MTT "))
|
if (builder.getName().startsWith("MTT "))
|
||||||
builder.setType(GraphicsCard.Type.Discrete);
|
builder.setType(GraphicsCard.Type.Discrete);
|
||||||
}
|
}
|
||||||
@@ -248,15 +249,25 @@ final class LinuxGPUDetector {
|
|||||||
String compatible = matcher.group("compatible");
|
String compatible = matcher.group("compatible");
|
||||||
int idx = compatible.indexOf(',');
|
int idx = compatible.indexOf(',');
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
builder.setName(compatible.trim());
|
String name = compatible.trim().toUpperCase(Locale.ROOT);
|
||||||
|
if (name.equals("IMG-GPU")) // Fucking Imagination
|
||||||
|
builder.setVendor(HardwareVendor.IMG);
|
||||||
|
else
|
||||||
|
builder.setName(name);
|
||||||
} else {
|
} else {
|
||||||
String vendorName = compatible.substring(0, idx).trim();
|
String vendorName = compatible.substring(0, idx).trim();
|
||||||
GraphicsCard.Vendor vendor = GraphicsCard.Vendor.getKnown(vendorName);
|
HardwareVendor vendor = HardwareVendor.getKnown(vendorName);
|
||||||
if (vendor == null)
|
if (vendor == null)
|
||||||
vendor = new GraphicsCard.Vendor(vendorName.toUpperCase(Locale.ROOT));
|
vendor = new HardwareVendor(StringUtils.capitalizeFirst(vendorName));
|
||||||
|
|
||||||
builder.setName(vendor + " " + compatible.substring(idx + 1).trim());
|
|
||||||
builder.setVendor(vendor);
|
builder.setVendor(vendor);
|
||||||
|
|
||||||
|
String name = compatible.substring(idx + 1).trim().toUpperCase(Locale.ROOT);
|
||||||
|
if (vendor == HardwareVendor.IMG) {
|
||||||
|
if (!name.equals("GPU"))
|
||||||
|
builder.setName(vendor + " " + name);
|
||||||
|
} else
|
||||||
|
builder.setName(vendor + " " + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setType(GraphicsCard.Type.Integrated);
|
builder.setType(GraphicsCard.Type.Integrated);
|
||||||
@@ -265,7 +276,7 @@ final class LinuxGPUDetector {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<GraphicsCard> detectAll() {
|
static List<GraphicsCard> detect() {
|
||||||
Path drm = Paths.get("/sys/class/drm");
|
Path drm = Paths.get("/sys/class/drm");
|
||||||
if (!Files.isDirectory(drm))
|
if (!Files.isDirectory(drm))
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
@@ -304,7 +315,6 @@ final class LinuxGPUDetector {
|
|||||||
LOG.warning("Failed to get graphics card info", e);
|
LOG.warning("Failed to get graphics card info", e);
|
||||||
} finally {
|
} finally {
|
||||||
databaseCache = null;
|
databaseCache = null;
|
||||||
System.gc();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableList(cards);
|
return Collections.unmodifiableList(cards);
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
package org.jackhuang.hmcl.util.platform.linux;
|
package org.jackhuang.hmcl.util.platform.linux;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
|
||||||
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
||||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -37,11 +39,18 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
|||||||
*/
|
*/
|
||||||
public final class LinuxHardwareDetector extends HardwareDetector {
|
public final class LinuxHardwareDetector extends HardwareDetector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable CentralProcessor detectCentralProcessor() {
|
||||||
|
if (OperatingSystem.CURRENT_OS != OperatingSystem.LINUX)
|
||||||
|
return null;
|
||||||
|
return LinuxCPUDetector.detect();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GraphicsCard> detectGraphicsCards() {
|
public List<GraphicsCard> detectGraphicsCards() {
|
||||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.LINUX)
|
if (OperatingSystem.CURRENT_OS != OperatingSystem.LINUX)
|
||||||
return null;
|
return null;
|
||||||
return LinuxGPUDetector.detectAll();
|
return LinuxGPUDetector.detect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Path MEMINFO = Paths.get("/proc/meminfo");
|
private static final Path MEMINFO = Paths.get("/proc/meminfo");
|
||||||
|
|||||||
@@ -20,24 +20,21 @@ package org.jackhuang.hmcl.util.platform.macos;
|
|||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.util.KeyValuePairUtils;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
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.GraphicsCard;
|
||||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
||||||
|
import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
@@ -46,34 +43,68 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
|||||||
*/
|
*/
|
||||||
public final class MacOSHardwareDetector extends HardwareDetector {
|
public final class MacOSHardwareDetector extends HardwareDetector {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable CentralProcessor detectCentralProcessor() {
|
||||||
|
if (OperatingSystem.CURRENT_OS != OperatingSystem.OSX)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, String> values = SystemUtils.run(Arrays.asList("/usr/sbin/sysctl", "machdep.cpu"),
|
||||||
|
inputStream -> KeyValuePairUtils.loadProperties(
|
||||||
|
new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET))));
|
||||||
|
|
||||||
|
String brandString = values.get("machdep.cpu.brand_string");
|
||||||
|
String coreCount = values.get("machdep.cpu.core_count");
|
||||||
|
String threadCount = values.get("machdep.cpu.thread_count");
|
||||||
|
String coresPerPackage = values.get("machdep.cpu.cores_per_package");
|
||||||
|
|
||||||
|
CentralProcessor.Builder builder = new CentralProcessor.Builder();
|
||||||
|
|
||||||
|
if (brandString != null) {
|
||||||
|
builder.setName(brandString);
|
||||||
|
|
||||||
|
String lower = brandString.toLowerCase(Locale.ROOT);
|
||||||
|
if (lower.startsWith("apple"))
|
||||||
|
builder.setVendor(HardwareVendor.APPLE);
|
||||||
|
else if (lower.startsWith("intel"))
|
||||||
|
builder.setVendor(HardwareVendor.INTEL);
|
||||||
|
} else
|
||||||
|
builder.setName("Unknown");
|
||||||
|
|
||||||
|
if (coreCount != null || threadCount != null) {
|
||||||
|
int cores = coreCount != null ? Integer.parseInt(coreCount) : 0;
|
||||||
|
int threads = threadCount != null ? Integer.parseInt(threadCount) : 0;
|
||||||
|
int coresPerPackageCount = coresPerPackage != null ? Integer.parseInt(coresPerPackage) : 0;
|
||||||
|
|
||||||
|
if (cores > 0 && threads == 0)
|
||||||
|
threads = cores;
|
||||||
|
else if (threads > 0 && cores == 0)
|
||||||
|
cores = threads;
|
||||||
|
|
||||||
|
int packages = 1;
|
||||||
|
if (cores > 0 && coresPerPackageCount > 0)
|
||||||
|
packages = Integer.max(cores / coresPerPackageCount, 1);
|
||||||
|
|
||||||
|
builder.setCores(new CentralProcessor.Cores(cores, threads, packages));
|
||||||
|
} else
|
||||||
|
builder.setCores(new CentralProcessor.Cores(Runtime.getRuntime().availableProcessors()));
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warning("Failed to get CPU info", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GraphicsCard> detectGraphicsCards() {
|
public List<GraphicsCard> detectGraphicsCards() {
|
||||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.OSX)
|
if (OperatingSystem.CURRENT_OS != OperatingSystem.OSX)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Process process = null;
|
|
||||||
String json = null;
|
String json = null;
|
||||||
try {
|
try {
|
||||||
File devNull = new File("/dev/null");
|
json = SystemUtils.run(Arrays.asList("/usr/sbin/system_profiler", "SPDisplaysDataType", "-json"),
|
||||||
|
inputStream -> IOUtils.readFullyAsString(inputStream, OperatingSystem.NATIVE_CHARSET));
|
||||||
Process finalProcess = process = new ProcessBuilder("/usr/sbin/system_profiler",
|
|
||||||
"SPDisplaysDataType",
|
|
||||||
"-json")
|
|
||||||
.redirectInput(devNull)
|
|
||||||
.redirectError(devNull)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
CompletableFuture<String> future = CompletableFuture.supplyAsync(Lang.wrap(() ->
|
|
||||||
IOUtils.readFullyAsString(finalProcess.getInputStream(), OperatingSystem.NATIVE_CHARSET)),
|
|
||||||
Schedulers.io());
|
|
||||||
|
|
||||||
if (!process.waitFor(15, TimeUnit.SECONDS))
|
|
||||||
throw new TimeoutException();
|
|
||||||
|
|
||||||
if (process.exitValue() != 0)
|
|
||||||
throw new IOException("Bad exit code: " + process.exitValue());
|
|
||||||
|
|
||||||
json = future.get();
|
|
||||||
|
|
||||||
JsonObject object = JsonUtils.GSON.fromJson(json, JsonObject.class);
|
JsonObject object = JsonUtils.GSON.fromJson(json, JsonObject.class);
|
||||||
JsonArray spDisplaysDataType = object.getAsJsonArray("SPDisplaysDataType");
|
JsonArray spDisplaysDataType = object.getAsJsonArray("SPDisplaysDataType");
|
||||||
@@ -98,7 +129,7 @@ public final class MacOSHardwareDetector extends HardwareDetector {
|
|||||||
.setName(model.getAsString());
|
.setName(model.getAsString());
|
||||||
|
|
||||||
if (vendor != null)
|
if (vendor != null)
|
||||||
builder.setVendor(GraphicsCard.Vendor.of(StringUtils.removePrefix(vendor.getAsString(), "sppci_vendor_")));
|
builder.setVendor(HardwareVendor.of(StringUtils.removePrefix(vendor.getAsString(), "sppci_vendor_")));
|
||||||
|
|
||||||
GraphicsCard.Type type = GraphicsCard.Type.Integrated;
|
GraphicsCard.Type type = GraphicsCard.Type.Integrated;
|
||||||
if (bus != null) {
|
if (bus != null) {
|
||||||
@@ -114,9 +145,6 @@ public final class MacOSHardwareDetector extends HardwareDetector {
|
|||||||
return Collections.unmodifiableList(cards);
|
return Collections.unmodifiableList(cards);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (process != null && process.isAlive())
|
|
||||||
process.destroy();
|
|
||||||
|
|
||||||
LOG.warning("Failed to get graphics card info" + (json != null ? ": " + json : ""), e);
|
LOG.warning("Failed to get graphics card info" + (json != null ? ": " + json : ""), e);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util.platform.windows;
|
package org.jackhuang.hmcl.util.platform.windows;
|
||||||
|
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
import com.sun.jna.win32.StdCallLibrary;
|
import com.sun.jna.win32.StdCallLibrary;
|
||||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||||
|
|
||||||
@@ -53,4 +55,9 @@ public interface Kernel32 extends StdCallLibrary {
|
|||||||
* @see <a href="https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex">GlobalMemoryStatusEx function</a>
|
* @see <a href="https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex">GlobalMemoryStatusEx function</a>
|
||||||
*/
|
*/
|
||||||
boolean GlobalMemoryStatusEx(WinTypes.MEMORYSTATUSEX lpBuffer);
|
boolean GlobalMemoryStatusEx(WinTypes.MEMORYSTATUSEX lpBuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex">GetLogicalProcessorInformationEx function</a>
|
||||||
|
*/
|
||||||
|
boolean GetLogicalProcessorInformationEx(int relationshipType, Pointer buffer, IntByReference returnedLength);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,19 @@ public interface WinConstants {
|
|||||||
long HKEY_DYN_DATA = 0x80000006L;
|
long HKEY_DYN_DATA = 0x80000006L;
|
||||||
long HKEY_CURRENT_USER_LOCAL_SETTINGS = 0x80000007L;
|
long HKEY_CURRENT_USER_LOCAL_SETTINGS = 0x80000007L;
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/windows/win32/api/winnls/ne-winnls-sysgeoclass
|
||||||
int GEOCLASS_NATION = 16;
|
int GEOCLASS_NATION = 16;
|
||||||
int GEOCLASS_REGION = 14;
|
int GEOCLASS_REGION = 14;
|
||||||
int GEOCLASS_ALL = 0;
|
int GEOCLASS_ALL = 0;
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/windows/win32/api/winnt/ne-winnt-logical_processor_relationship
|
||||||
|
int RelationProcessorCore = 0;
|
||||||
|
int RelationNumaNode = 1;
|
||||||
|
int RelationCache = 2;
|
||||||
|
int RelationProcessorPackage = 3;
|
||||||
|
int RelationGroup = 4;
|
||||||
|
int RelationProcessorDie = 5;
|
||||||
|
int RelationNumaNodeEx = 6;
|
||||||
|
int RelationProcessorModule = 7;
|
||||||
|
int RelationAll = 0xffff;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,20 @@ public abstract class WinReg {
|
|||||||
|
|
||||||
public abstract Object queryValue(HKEY root, String key, String valueName);
|
public abstract Object queryValue(HKEY root, String key, String valueName);
|
||||||
|
|
||||||
public abstract List<String> queryKeys(HKEY root, String key);
|
public abstract List<String> querySubKeyNames(HKEY root, String key);
|
||||||
|
|
||||||
|
public List<String> querySubKeys(HKEY root, String key) {
|
||||||
|
List<String> list = querySubKeyNames(root, key);
|
||||||
|
if (list.isEmpty())
|
||||||
|
return list;
|
||||||
|
|
||||||
|
if (!(list instanceof ArrayList))
|
||||||
|
list = new ArrayList<>(list);
|
||||||
|
|
||||||
|
String prefix = key.endsWith("\\") ? key : key + "\\";
|
||||||
|
list.replaceAll(str -> prefix + str);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
private static final class JNAWinReg extends WinReg {
|
private static final class JNAWinReg extends WinReg {
|
||||||
|
|
||||||
@@ -176,14 +189,13 @@ public abstract class WinReg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> queryKeys(HKEY root, String key) {
|
public List<String> querySubKeyNames(HKEY root, String key) {
|
||||||
PointerByReference phkKey = new PointerByReference();
|
PointerByReference phkKey = new PointerByReference();
|
||||||
if (advapi32.RegOpenKeyExW(root.toPointer(), new WString(key), 0, WinConstants.KEY_READ, phkKey) != WinConstants.ERROR_SUCCESS)
|
if (advapi32.RegOpenKeyExW(root.toPointer(), new WString(key), 0, WinConstants.KEY_READ, phkKey) != WinConstants.ERROR_SUCCESS)
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|
||||||
Pointer hkey = phkKey.getValue();
|
Pointer hkey = phkKey.getValue();
|
||||||
try {
|
try {
|
||||||
String prefix = key.endsWith("\\") ? key : key + "\\";
|
|
||||||
ArrayList<String> res = new ArrayList<>();
|
ArrayList<String> res = new ArrayList<>();
|
||||||
int maxKeyLength = 256;
|
int maxKeyLength = 256;
|
||||||
try (Memory lpName = new Memory(maxKeyLength * 2)) {
|
try (Memory lpName = new Memory(maxKeyLength * 2)) {
|
||||||
@@ -194,7 +206,7 @@ public abstract class WinReg {
|
|||||||
lpcchName.setValue(maxKeyLength);
|
lpcchName.setValue(maxKeyLength);
|
||||||
int status = advapi32.RegEnumKeyExW(hkey, i, lpName, lpcchName, null, null, null, null);
|
int status = advapi32.RegEnumKeyExW(hkey, i, lpName, lpcchName, null, null, null, null);
|
||||||
if (status == WinConstants.ERROR_SUCCESS) {
|
if (status == WinConstants.ERROR_SUCCESS) {
|
||||||
res.add(prefix + lpName.getWideString(0L));
|
res.add(lpName.getWideString(0L));
|
||||||
i++;
|
i++;
|
||||||
} else {
|
} else {
|
||||||
if (status != WinConstants.ERROR_NO_MORE_ITEMS)
|
if (status != WinConstants.ERROR_NO_MORE_ITEMS)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.util.platform.windows;
|
package org.jackhuang.hmcl.util.platform.windows;
|
||||||
|
|
||||||
import com.sun.jna.*;
|
import com.sun.jna.*;
|
||||||
|
import com.sun.jna.ptr.LongByReference;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -87,4 +88,111 @@ public interface WinTypes {
|
|||||||
"ullTotalVirtual", "ullAvailVirtual", "ullAvailExtendedVirtual");
|
"ullTotalVirtual", "ullAvailVirtual", "ullAvailExtendedVirtual");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class GROUP_AFFINITY extends Structure {
|
||||||
|
public LongByReference mask;
|
||||||
|
public short group;
|
||||||
|
public short[] reserved = new short[3];
|
||||||
|
|
||||||
|
public GROUP_AFFINITY(Pointer memory) {
|
||||||
|
super(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GROUP_AFFINITY() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList(
|
||||||
|
"mask", "group", "reserved"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-processor_group_info">PROCESSOR_GROUP_INFO structure</a>
|
||||||
|
*/
|
||||||
|
final class PROCESSOR_GROUP_INFO extends Structure {
|
||||||
|
public byte maximumProcessorCount;
|
||||||
|
public byte activeProcessorCount;
|
||||||
|
public byte[] reserved = new byte[38];
|
||||||
|
public LongByReference activeProcessorMask;
|
||||||
|
|
||||||
|
public PROCESSOR_GROUP_INFO(Pointer memory) {
|
||||||
|
super(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PROCESSOR_GROUP_INFO() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("maximumProcessorCount", "activeProcessorCount", "reserved", "activeProcessorMask");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-processor_relationship">PROCESSOR_RELATIONSHIP structure</a>
|
||||||
|
*/
|
||||||
|
final class PROCESSOR_RELATIONSHIP extends Structure {
|
||||||
|
|
||||||
|
public byte flags;
|
||||||
|
public byte efficiencyClass;
|
||||||
|
public byte[] reserved = new byte[20];
|
||||||
|
public short groupCount;
|
||||||
|
public GROUP_AFFINITY[] groupMask = new GROUP_AFFINITY[1];
|
||||||
|
|
||||||
|
public PROCESSOR_RELATIONSHIP() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public PROCESSOR_RELATIONSHIP(Pointer memory) {
|
||||||
|
super(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("flags", "efficiencyClass", "reserved", "groupCount", "groupMask");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read() {
|
||||||
|
readField("groupCount");
|
||||||
|
if (groupCount != groupMask.length) {
|
||||||
|
groupMask = new GROUP_AFFINITY[groupCount];
|
||||||
|
}
|
||||||
|
super.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-group_relationship">GROUP_RELATIONSHIP structure</a>
|
||||||
|
*/
|
||||||
|
final class GROUP_RELATIONSHIP extends Structure {
|
||||||
|
public short maximumGroupCount;
|
||||||
|
public short activeGroupCount;
|
||||||
|
public byte[] reserved = new byte[20];
|
||||||
|
public PROCESSOR_GROUP_INFO[] groupInfo = new PROCESSOR_GROUP_INFO[1];
|
||||||
|
|
||||||
|
public GROUP_RELATIONSHIP() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public GROUP_RELATIONSHIP(Pointer memory) {
|
||||||
|
super(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("maximumGroupCount", "activeGroupCount", "reserved", "groupInfo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read() {
|
||||||
|
readField("activeGroupCount");
|
||||||
|
if (activeGroupCount != groupInfo.length)
|
||||||
|
groupInfo = new PROCESSOR_GROUP_INFO[activeGroupCount];
|
||||||
|
super.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.sun.jna.Memory;
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
|
||||||
|
import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Glavo
|
||||||
|
*/
|
||||||
|
final class WindowsCPUDetector {
|
||||||
|
|
||||||
|
private static void detectName(CentralProcessor.Builder builder, WinReg reg) {
|
||||||
|
Object name = reg.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString");
|
||||||
|
Object vendor = reg.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "VendorIdentifier");
|
||||||
|
|
||||||
|
if (name instanceof String)
|
||||||
|
builder.setName((String) name);
|
||||||
|
|
||||||
|
if (vendor instanceof String)
|
||||||
|
builder.setVendor(HardwareVendor.of((String) vendor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void detectCores(CentralProcessor.Builder builder, Kernel32 kernel32) {
|
||||||
|
int coresLogical = 0;
|
||||||
|
int coresPhysical = 0;
|
||||||
|
int packages = 0;
|
||||||
|
|
||||||
|
IntByReference length = new IntByReference();
|
||||||
|
if (!kernel32.GetLogicalProcessorInformationEx(WinConstants.RelationAll, null, length) && length.getValue() == 0)
|
||||||
|
throw new AssertionError("Failed to get logical processor information length: " + kernel32.GetLastError());
|
||||||
|
|
||||||
|
try (Memory pProcessorInfo = new Memory(Integer.toUnsignedLong(length.getValue()))) {
|
||||||
|
if (!kernel32.GetLogicalProcessorInformationEx(WinConstants.RelationAll, pProcessorInfo, length))
|
||||||
|
throw new AssertionError("Failed to get logical processor information length: " + kernel32.GetLastError());
|
||||||
|
|
||||||
|
for (long offset = 0L; offset < pProcessorInfo.size(); ) {
|
||||||
|
int relationship = pProcessorInfo.getInt(offset);
|
||||||
|
long size = Integer.toUnsignedLong(pProcessorInfo.getInt(offset + 4L));
|
||||||
|
|
||||||
|
if (relationship == WinConstants.RelationGroup) {
|
||||||
|
WinTypes.GROUP_RELATIONSHIP groupRelationship = new WinTypes.GROUP_RELATIONSHIP(pProcessorInfo.share(offset + 8L, size - 8L));
|
||||||
|
groupRelationship.read();
|
||||||
|
|
||||||
|
int activeGroupCount = Short.toUnsignedInt(groupRelationship.activeGroupCount);
|
||||||
|
for (int i = 0; i < activeGroupCount; i++) {
|
||||||
|
coresLogical += Short.toUnsignedInt(groupRelationship.groupInfo[i].maximumProcessorCount);
|
||||||
|
}
|
||||||
|
} else if (relationship == WinConstants.RelationProcessorCore)
|
||||||
|
coresPhysical++;
|
||||||
|
else if (relationship == WinConstants.RelationProcessorPackage)
|
||||||
|
packages++;
|
||||||
|
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setCores(new CentralProcessor.Cores(coresPhysical, coresLogical, packages));
|
||||||
|
}
|
||||||
|
|
||||||
|
static @Nullable CentralProcessor detect() {
|
||||||
|
WinReg reg = WinReg.INSTANCE;
|
||||||
|
Kernel32 kernel32 = Kernel32.INSTANCE;
|
||||||
|
if (reg == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
CentralProcessor.Builder builder = new CentralProcessor.Builder();
|
||||||
|
detectName(builder, reg);
|
||||||
|
if (kernel32 != null)
|
||||||
|
detectCores(builder, kernel32);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WindowsCPUDetector() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,21 +17,20 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.util.platform.windows;
|
package org.jackhuang.hmcl.util.platform.windows;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.util.KeyValuePairUtils;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
|
||||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
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.GraphicsCard;
|
||||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
||||||
|
import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.InputStreamReader;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
@@ -40,31 +39,11 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
|||||||
*/
|
*/
|
||||||
public final class WindowsHardwareDetector extends HardwareDetector {
|
public final class WindowsHardwareDetector extends HardwareDetector {
|
||||||
|
|
||||||
private static List<Map<String, String>> parsePowerShellFormatList(Iterable<String> lines) {
|
@Override
|
||||||
ArrayList<Map<String, String>> result = new ArrayList<>();
|
public @Nullable CentralProcessor detectCentralProcessor() {
|
||||||
Map<String, String> current = new LinkedHashMap<>();
|
if (!OperatingSystem.isWindows7OrLater())
|
||||||
|
return null;
|
||||||
for (String line : lines) {
|
return WindowsCPUDetector.detect();
|
||||||
int idx = line.indexOf(':');
|
|
||||||
|
|
||||||
if (idx < 0) {
|
|
||||||
if (!current.isEmpty()) {
|
|
||||||
result.add(current);
|
|
||||||
current = new LinkedHashMap<>();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String key = line.substring(0, idx).trim();
|
|
||||||
String value = line.substring(idx + 1).trim();
|
|
||||||
|
|
||||||
current.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!current.isEmpty())
|
|
||||||
result.add(current);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -72,39 +51,21 @@ public final class WindowsHardwareDetector extends HardwareDetector {
|
|||||||
if (!OperatingSystem.isWindows7OrLater())
|
if (!OperatingSystem.isWindows7OrLater())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Process process = null;
|
|
||||||
String list = null;
|
|
||||||
try {
|
try {
|
||||||
File nul = new File("NUL");
|
|
||||||
|
|
||||||
String getCimInstance = OperatingSystem.SYSTEM_VERSION.startsWith("6.1")
|
String getCimInstance = OperatingSystem.SYSTEM_VERSION.startsWith("6.1")
|
||||||
? "Get-WmiObject"
|
? "Get-WmiObject"
|
||||||
: "Get-CimInstance";
|
: "Get-CimInstance";
|
||||||
|
|
||||||
Process finalProcess = process = new ProcessBuilder("powershell.exe",
|
List<Map<String, String>> videoControllers = SystemUtils.run(Arrays.asList(
|
||||||
|
"powershell.exe",
|
||||||
"-Command",
|
"-Command",
|
||||||
String.join(" | ",
|
String.join(" | ",
|
||||||
getCimInstance + " -Class Win32_VideoController",
|
getCimInstance + " -Class Win32_VideoController",
|
||||||
"Select-Object Name,AdapterCompatibility,DriverVersion,AdapterDACType",
|
"Select-Object Name,AdapterCompatibility,DriverVersion,AdapterDACType",
|
||||||
"Format-List"
|
"Format-List"
|
||||||
))
|
)),
|
||||||
.redirectInput(nul)
|
inputStream -> KeyValuePairUtils.loadList(new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET))));
|
||||||
.redirectError(nul)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
CompletableFuture<String> future = CompletableFuture.supplyAsync(Lang.wrap(() ->
|
|
||||||
IOUtils.readFullyAsString(finalProcess.getInputStream(), OperatingSystem.NATIVE_CHARSET)),
|
|
||||||
Schedulers.io());
|
|
||||||
|
|
||||||
if (!process.waitFor(15, TimeUnit.SECONDS))
|
|
||||||
throw new TimeoutException();
|
|
||||||
|
|
||||||
if (process.exitValue() != 0)
|
|
||||||
throw new IOException("Bad exit code: " + process.exitValue());
|
|
||||||
|
|
||||||
list = future.get();
|
|
||||||
|
|
||||||
List<Map<String, String>> videoControllers = parsePowerShellFormatList(Arrays.asList(list.split("\\R")));
|
|
||||||
ArrayList<GraphicsCard> cards = new ArrayList<>(videoControllers.size());
|
ArrayList<GraphicsCard> cards = new ArrayList<>(videoControllers.size());
|
||||||
for (Map<String, String> videoController : videoControllers) {
|
for (Map<String, String> videoController : videoControllers) {
|
||||||
String name = videoController.get("Name");
|
String name = videoController.get("Name");
|
||||||
@@ -114,7 +75,7 @@ public final class WindowsHardwareDetector extends HardwareDetector {
|
|||||||
|
|
||||||
if (StringUtils.isNotBlank(name)) {
|
if (StringUtils.isNotBlank(name)) {
|
||||||
cards.add(GraphicsCard.builder().setName(name)
|
cards.add(GraphicsCard.builder().setName(name)
|
||||||
.setVendor(GraphicsCard.Vendor.of(adapterCompatibility))
|
.setVendor(HardwareVendor.of(adapterCompatibility))
|
||||||
.setDriverVersion(driverVersion)
|
.setDriverVersion(driverVersion)
|
||||||
.setType(StringUtils.isBlank(adapterDACType)
|
.setType(StringUtils.isBlank(adapterDACType)
|
||||||
|| "Internal".equalsIgnoreCase(adapterDACType)
|
|| "Internal".equalsIgnoreCase(adapterDACType)
|
||||||
@@ -128,9 +89,7 @@ public final class WindowsHardwareDetector extends HardwareDetector {
|
|||||||
|
|
||||||
return cards;
|
return cards;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (process != null && process.isAlive())
|
LOG.warning("Failed to get graphics card info", e);
|
||||||
process.destroy();
|
|
||||||
LOG.warning("Failed to get graphics card info" + (list != null ? ": " + list : ""), e);
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -12,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
/**
|
/**
|
||||||
* @author Glavo
|
* @author Glavo
|
||||||
*/
|
*/
|
||||||
public final class KeyValuePairPropertiesTest {
|
public final class KeyValuePairUtilsTest {
|
||||||
@Test
|
@Test
|
||||||
public void test() throws IOException {
|
public void test() throws IOException {
|
||||||
String content = "#test: key0=value0\n \n" +
|
String content = "#test: key0=value0\n \n" +
|
||||||
@@ -20,7 +21,7 @@ public final class KeyValuePairPropertiesTest {
|
|||||||
"key2=\"value2\"\n" +
|
"key2=\"value2\"\n" +
|
||||||
"key3=\"\\\" \\n\"\n";
|
"key3=\"\\\" \\n\"\n";
|
||||||
|
|
||||||
KeyValuePairProperties properties = KeyValuePairProperties.load(new BufferedReader(new StringReader(content)));
|
Map<String, String> properties = KeyValuePairUtils.loadProperties(new BufferedReader(new StringReader(content)));
|
||||||
|
|
||||||
assertEquals(Lang.mapOf(
|
assertEquals(Lang.mapOf(
|
||||||
pair("key1", "value1"),
|
pair("key1", "value1"),
|
||||||
@@ -99,15 +99,15 @@ public final class WinRegTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testQueryKeys() {
|
public void testQuerySubKeys() {
|
||||||
WinReg.HKEY hkey = WinReg.HKEY.HKEY_CURRENT_USER;
|
WinReg.HKEY hkey = WinReg.HKEY.HKEY_CURRENT_USER;
|
||||||
WinReg reg = WinReg.INSTANCE;
|
WinReg reg = WinReg.INSTANCE;
|
||||||
|
|
||||||
assertEquals(Arrays.asList(SUBKEYS).stream().map(it -> key + "\\" + it).collect(Collectors.toList()),
|
assertEquals(Arrays.asList(SUBKEYS).stream().map(it -> key + "\\" + it).collect(Collectors.toList()),
|
||||||
reg.queryKeys(hkey, key).stream().sorted().collect(Collectors.toList()));
|
reg.querySubKeys(hkey, key).stream().sorted().collect(Collectors.toList()));
|
||||||
for (String subkey : SUBKEYS) {
|
for (String subkey : SUBKEYS) {
|
||||||
assertEquals(Collections.emptyList(), reg.queryKeys(hkey, key + "\\" + subkey));
|
assertEquals(Collections.emptyList(), reg.querySubKeys(hkey, key + "\\" + subkey));
|
||||||
}
|
}
|
||||||
assertEquals(Collections.emptyList(), reg.queryKeys(hkey, key + "\\NOT_EXIST"));
|
assertEquals(Collections.emptyList(), reg.querySubKeys(hkey, key + "\\NOT_EXIST"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user