启动时在日志中记录 CPU 信息 (#3914)
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
package org.jackhuang.hmcl.java;
|
||||
|
||||
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.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
@@ -33,6 +33,7 @@ import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
@@ -60,7 +61,7 @@ public final class JavaInfo {
|
||||
}
|
||||
|
||||
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 osArch = properties.get("OS_ARCH");
|
||||
String vendor = properties.get("IMPLEMENTOR");
|
||||
|
||||
@@ -19,25 +19,34 @@ package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class KeyValuePairProperties extends LinkedHashMap<String, String> {
|
||||
public static KeyValuePairProperties load(Path file) throws IOException {
|
||||
public final class KeyValuePairUtils {
|
||||
public static Map<String, String> loadProperties(Path file) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
||||
return load(reader);
|
||||
return loadProperties(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public static KeyValuePairProperties load(BufferedReader reader) throws IOException {
|
||||
KeyValuePairProperties result = new KeyValuePairProperties();
|
||||
public static Map<String, String> loadProperties(BufferedReader reader) throws IOException {
|
||||
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("#"))
|
||||
continue;
|
||||
|
||||
@@ -91,4 +100,78 @@ public final class KeyValuePairProperties extends LinkedHashMap<String, String>
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
return substringBeforeLast(str, delimiter, str);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
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.WinTypes;
|
||||
|
||||
@@ -225,7 +225,7 @@ public enum OperatingSystem {
|
||||
Path osReleaseFile = Paths.get("/etc/os-release");
|
||||
if (Files.exists(osReleaseFile)) {
|
||||
try {
|
||||
osRelease = KeyValuePairProperties.load(osReleaseFile);
|
||||
osRelease = KeyValuePairUtils.loadProperties(osReleaseFile);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
@@ -18,12 +18,13 @@
|
||||
package org.jackhuang.hmcl.util.platform;
|
||||
|
||||
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.HardwareDetector;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
|
||||
import org.jackhuang.hmcl.util.platform.linux.LinuxHardwareDetector;
|
||||
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 java.util.List;
|
||||
@@ -47,12 +48,18 @@ public final class SystemInfo {
|
||||
}
|
||||
|
||||
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 void initialize() {
|
||||
StringBuilder builder = new StringBuilder("System Info:");
|
||||
|
||||
// CPU
|
||||
CentralProcessor cpu = getCentralProcessor();
|
||||
if (cpu != null)
|
||||
builder.append("\n - CPU: ").append(cpu);
|
||||
|
||||
// Graphics Card
|
||||
List<GraphicsCard> graphicsCards = getGraphicsCards();
|
||||
if (graphicsCards != null) {
|
||||
@@ -108,6 +115,10 @@ public final class SystemInfo {
|
||||
return Long.max(0, totalMemorySize - getFreeMemorySize());
|
||||
}
|
||||
|
||||
public static @Nullable CentralProcessor getCentralProcessor() {
|
||||
return Holder.CENTRAL_PROCESSOR;
|
||||
}
|
||||
|
||||
public static @Nullable List<GraphicsCard> getGraphicsCards() {
|
||||
return Holder.GRAPHICS_CARDS;
|
||||
}
|
||||
|
||||
@@ -18,15 +18,47 @@
|
||||
package org.jackhuang.hmcl.util.platform;
|
||||
|
||||
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.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.io.InputStream;
|
||||
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;
|
||||
|
||||
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 {
|
||||
return callExternalProcess(Arrays.asList(command));
|
||||
@@ -43,6 +75,34 @@ public final class SystemUtils {
|
||||
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() {
|
||||
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;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
@@ -32,12 +31,12 @@ public final class GraphicsCard {
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final @Nullable Vendor vendor;
|
||||
private final @Nullable HardwareVendor vendor;
|
||||
private final @Nullable Type type;
|
||||
private final @Nullable String driver;
|
||||
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.vendor = vendor;
|
||||
this.type = type;
|
||||
@@ -49,7 +48,7 @@ public final class GraphicsCard {
|
||||
return name;
|
||||
}
|
||||
|
||||
public @Nullable Vendor getVendor() {
|
||||
public @Nullable HardwareVendor getVendor() {
|
||||
return vendor;
|
||||
}
|
||||
|
||||
@@ -68,132 +67,6 @@ public final class GraphicsCard {
|
||||
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 {
|
||||
Integrated,
|
||||
Discrete
|
||||
@@ -201,14 +74,19 @@ public final class GraphicsCard {
|
||||
|
||||
public static final class Builder {
|
||||
private String name;
|
||||
private Vendor vendor;
|
||||
private HardwareVendor vendor;
|
||||
private Type type;
|
||||
private String driver;
|
||||
private String driverVersion;
|
||||
|
||||
public GraphicsCard build() {
|
||||
if (name == null)
|
||||
throw new IllegalStateException("Name not set");
|
||||
String name = this.name;
|
||||
if (name == null) {
|
||||
if (vendor != null)
|
||||
name = vendor + " Graphics";
|
||||
else
|
||||
name = "Unknown";
|
||||
}
|
||||
|
||||
return new GraphicsCard(name, vendor, type, driver, driverVersion);
|
||||
}
|
||||
@@ -222,11 +100,11 @@ public final class GraphicsCard {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Vendor getVendor() {
|
||||
public HardwareVendor getVendor() {
|
||||
return vendor;
|
||||
}
|
||||
|
||||
public Builder setVendor(Vendor vendor) {
|
||||
public Builder setVendor(HardwareVendor vendor) {
|
||||
this.vendor = vendor;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,12 @@ import java.util.List;
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
public class HardwareDetector {
|
||||
public @Nullable CentralProcessor detectCentralProcessor() {
|
||||
return FastFetchUtils.detectCentralProcessor();
|
||||
}
|
||||
|
||||
public @Nullable List<GraphicsCard> detectGraphicsCards() {
|
||||
return null;
|
||||
return FastFetchUtils.detectGraphicsCards();
|
||||
}
|
||||
|
||||
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.Vendor;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.ref.SoftReference;
|
||||
@@ -52,7 +54,7 @@ final class LinuxGPUDetector {
|
||||
private static final Pattern PCI_DEVICE_PATTERN =
|
||||
Pattern.compile("(?<pciDomain>\\p{XDigit}+):(?<pciBus>\\p{XDigit}+):(?<pciDevice>\\p{XDigit}+)\\.(?<pciFunc>\\p{XDigit}+)");
|
||||
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() {
|
||||
SoftReference<PCIIDsDatabase> databaseWeakReference = LinuxGPUDetector.databaseCache;
|
||||
@@ -136,12 +138,11 @@ final class LinuxGPUDetector {
|
||||
int pciDevice = Integer.parseInt(matcher.group("pciDevice"), 16);
|
||||
int pciFunc = Integer.parseInt(matcher.group("pciFunc"), 16);
|
||||
|
||||
builder.setVendor(GraphicsCard.Vendor.ofId(vendorId));
|
||||
|
||||
builder.setVendor(HardwareVendor.ofPciVendorId(vendorId));
|
||||
detectDriver(builder, deviceDir);
|
||||
|
||||
try {
|
||||
if (builder.getVendor() == GraphicsCard.Vendor.AMD) {
|
||||
if (builder.getVendor() == HardwareVendor.AMD) {
|
||||
Path hwmon = deviceDir.resolve("hwmon");
|
||||
try (Stream<Path> subDirs = Files.list(hwmon)) {
|
||||
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);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
@@ -185,7 +186,7 @@ final class LinuxGPUDetector {
|
||||
Vendor vendor = database.findVendor(vendorId);
|
||||
if (vendor != null) {
|
||||
if (builder.getVendor() == null)
|
||||
builder.setVendor(GraphicsCard.Vendor.of(vendor.getName()));
|
||||
builder.setVendor(HardwareVendor.of(vendor.getName()));
|
||||
|
||||
if (builder.getName() == null) {
|
||||
Device device = vendor.getDevices().get(deviceId);
|
||||
@@ -223,13 +224,13 @@ final class LinuxGPUDetector {
|
||||
}
|
||||
|
||||
if (builder.getType() == null) {
|
||||
if (builder.getVendor() == GraphicsCard.Vendor.NVIDIA) {
|
||||
if (builder.getVendor() == HardwareVendor.NVIDIA) {
|
||||
if (builder.getName().startsWith("GeForce")
|
||||
|| builder.getName().startsWith("Quadro")
|
||||
|| builder.getName().startsWith("Tesla"))
|
||||
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 "))
|
||||
builder.setType(GraphicsCard.Type.Discrete);
|
||||
}
|
||||
@@ -248,15 +249,25 @@ final class LinuxGPUDetector {
|
||||
String compatible = matcher.group("compatible");
|
||||
int idx = compatible.indexOf(',');
|
||||
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 {
|
||||
String vendorName = compatible.substring(0, idx).trim();
|
||||
GraphicsCard.Vendor vendor = GraphicsCard.Vendor.getKnown(vendorName);
|
||||
HardwareVendor vendor = HardwareVendor.getKnown(vendorName);
|
||||
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);
|
||||
|
||||
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);
|
||||
@@ -265,7 +276,7 @@ final class LinuxGPUDetector {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static List<GraphicsCard> detectAll() {
|
||||
static List<GraphicsCard> detect() {
|
||||
Path drm = Paths.get("/sys/class/drm");
|
||||
if (!Files.isDirectory(drm))
|
||||
return Collections.emptyList();
|
||||
@@ -304,7 +315,6 @@ final class LinuxGPUDetector {
|
||||
LOG.warning("Failed to get graphics card info", e);
|
||||
} finally {
|
||||
databaseCache = null;
|
||||
System.gc();
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(cards);
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
package org.jackhuang.hmcl.util.platform.linux;
|
||||
|
||||
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.HardwareDetector;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -37,11 +39,18 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
*/
|
||||
public final class LinuxHardwareDetector extends HardwareDetector {
|
||||
|
||||
@Override
|
||||
public @Nullable CentralProcessor detectCentralProcessor() {
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.LINUX)
|
||||
return null;
|
||||
return LinuxCPUDetector.detect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GraphicsCard> detectGraphicsCards() {
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.LINUX)
|
||||
return null;
|
||||
return LinuxGPUDetector.detectAll();
|
||||
return LinuxGPUDetector.detect();
|
||||
}
|
||||
|
||||
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.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.KeyValuePairUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
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.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
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 java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
|
||||
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 {
|
||||
|
||||
@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
|
||||
public List<GraphicsCard> detectGraphicsCards() {
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.OSX)
|
||||
return null;
|
||||
|
||||
Process process = null;
|
||||
String json = null;
|
||||
try {
|
||||
File devNull = new File("/dev/null");
|
||||
|
||||
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();
|
||||
json = SystemUtils.run(Arrays.asList("/usr/sbin/system_profiler", "SPDisplaysDataType", "-json"),
|
||||
inputStream -> IOUtils.readFullyAsString(inputStream, OperatingSystem.NATIVE_CHARSET));
|
||||
|
||||
JsonObject object = JsonUtils.GSON.fromJson(json, JsonObject.class);
|
||||
JsonArray spDisplaysDataType = object.getAsJsonArray("SPDisplaysDataType");
|
||||
@@ -98,7 +129,7 @@ public final class MacOSHardwareDetector extends HardwareDetector {
|
||||
.setName(model.getAsString());
|
||||
|
||||
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;
|
||||
if (bus != null) {
|
||||
@@ -114,9 +145,6 @@ public final class MacOSHardwareDetector extends HardwareDetector {
|
||||
return Collections.unmodifiableList(cards);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (process != null && process.isAlive())
|
||||
process.destroy();
|
||||
|
||||
LOG.warning("Failed to get graphics card info" + (json != null ? ": " + json : ""), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
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 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>
|
||||
*/
|
||||
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_CURRENT_USER_LOCAL_SETTINGS = 0x80000007L;
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/winnls/ne-winnls-sysgeoclass
|
||||
int GEOCLASS_NATION = 16;
|
||||
int GEOCLASS_REGION = 14;
|
||||
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 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 {
|
||||
|
||||
@@ -176,14 +189,13 @@ public abstract class WinReg {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> queryKeys(HKEY root, String key) {
|
||||
public List<String> querySubKeyNames(HKEY root, String key) {
|
||||
PointerByReference phkKey = new PointerByReference();
|
||||
if (advapi32.RegOpenKeyExW(root.toPointer(), new WString(key), 0, WinConstants.KEY_READ, phkKey) != WinConstants.ERROR_SUCCESS)
|
||||
return Collections.emptyList();
|
||||
|
||||
Pointer hkey = phkKey.getValue();
|
||||
try {
|
||||
String prefix = key.endsWith("\\") ? key : key + "\\";
|
||||
ArrayList<String> res = new ArrayList<>();
|
||||
int maxKeyLength = 256;
|
||||
try (Memory lpName = new Memory(maxKeyLength * 2)) {
|
||||
@@ -194,7 +206,7 @@ public abstract class WinReg {
|
||||
lpcchName.setValue(maxKeyLength);
|
||||
int status = advapi32.RegEnumKeyExW(hkey, i, lpName, lpcchName, null, null, null, null);
|
||||
if (status == WinConstants.ERROR_SUCCESS) {
|
||||
res.add(prefix + lpName.getWideString(0L));
|
||||
res.add(lpName.getWideString(0L));
|
||||
i++;
|
||||
} else {
|
||||
if (status != WinConstants.ERROR_NO_MORE_ITEMS)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.util.platform.windows;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.ptr.LongByReference;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -87,4 +88,111 @@ public interface WinTypes {
|
||||
"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;
|
||||
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.KeyValuePairUtils;
|
||||
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.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
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;
|
||||
|
||||
@@ -40,31 +39,11 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
*/
|
||||
public final class WindowsHardwareDetector extends HardwareDetector {
|
||||
|
||||
private static List<Map<String, String>> parsePowerShellFormatList(Iterable<String> lines) {
|
||||
ArrayList<Map<String, String>> result = new ArrayList<>();
|
||||
Map<String, String> current = new LinkedHashMap<>();
|
||||
|
||||
for (String line : lines) {
|
||||
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
|
||||
public @Nullable CentralProcessor detectCentralProcessor() {
|
||||
if (!OperatingSystem.isWindows7OrLater())
|
||||
return null;
|
||||
return WindowsCPUDetector.detect();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,39 +51,21 @@ public final class WindowsHardwareDetector extends HardwareDetector {
|
||||
if (!OperatingSystem.isWindows7OrLater())
|
||||
return null;
|
||||
|
||||
Process process = null;
|
||||
String list = null;
|
||||
try {
|
||||
File nul = new File("NUL");
|
||||
|
||||
String getCimInstance = OperatingSystem.SYSTEM_VERSION.startsWith("6.1")
|
||||
? "Get-WmiObject"
|
||||
: "Get-CimInstance";
|
||||
|
||||
Process finalProcess = process = new ProcessBuilder("powershell.exe",
|
||||
"-Command",
|
||||
String.join(" | ",
|
||||
getCimInstance + " -Class Win32_VideoController",
|
||||
"Select-Object Name,AdapterCompatibility,DriverVersion,AdapterDACType",
|
||||
"Format-List"
|
||||
))
|
||||
.redirectInput(nul)
|
||||
.redirectError(nul)
|
||||
.start();
|
||||
List<Map<String, String>> videoControllers = SystemUtils.run(Arrays.asList(
|
||||
"powershell.exe",
|
||||
"-Command",
|
||||
String.join(" | ",
|
||||
getCimInstance + " -Class Win32_VideoController",
|
||||
"Select-Object Name,AdapterCompatibility,DriverVersion,AdapterDACType",
|
||||
"Format-List"
|
||||
)),
|
||||
inputStream -> KeyValuePairUtils.loadList(new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET))));
|
||||
|
||||
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());
|
||||
for (Map<String, String> videoController : videoControllers) {
|
||||
String name = videoController.get("Name");
|
||||
@@ -114,7 +75,7 @@ public final class WindowsHardwareDetector extends HardwareDetector {
|
||||
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
cards.add(GraphicsCard.builder().setName(name)
|
||||
.setVendor(GraphicsCard.Vendor.of(adapterCompatibility))
|
||||
.setVendor(HardwareVendor.of(adapterCompatibility))
|
||||
.setDriverVersion(driverVersion)
|
||||
.setType(StringUtils.isBlank(adapterDACType)
|
||||
|| "Internal".equalsIgnoreCase(adapterDACType)
|
||||
@@ -128,9 +89,7 @@ public final class WindowsHardwareDetector extends HardwareDetector {
|
||||
|
||||
return cards;
|
||||
} catch (Throwable e) {
|
||||
if (process != null && process.isAlive())
|
||||
process.destroy();
|
||||
LOG.warning("Failed to get graphics card info" + (list != null ? ": " + list : ""), e);
|
||||
LOG.warning("Failed to get graphics card info", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -12,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class KeyValuePairPropertiesTest {
|
||||
public final class KeyValuePairUtilsTest {
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
String content = "#test: key0=value0\n \n" +
|
||||
@@ -20,7 +21,7 @@ public final class KeyValuePairPropertiesTest {
|
||||
"key2=\"value2\"\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(
|
||||
pair("key1", "value1"),
|
||||
@@ -99,15 +99,15 @@ public final class WinRegTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryKeys() {
|
||||
public void testQuerySubKeys() {
|
||||
WinReg.HKEY hkey = WinReg.HKEY.HKEY_CURRENT_USER;
|
||||
WinReg reg = WinReg.INSTANCE;
|
||||
|
||||
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) {
|
||||
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