使用 JNA 调用本机 API (#3890)
This commit is contained in:
@@ -15,5 +15,7 @@ dependencies {
|
||||
api(libs.nanohttpd)
|
||||
api(libs.jsoup)
|
||||
api(libs.chardet)
|
||||
api(libs.jna)
|
||||
|
||||
compileOnlyApi(libs.jetbrains.annotations)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Platform;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class NativeUtils {
|
||||
public static final boolean USE_JNA = useJNA();
|
||||
|
||||
public static <T extends Library> @Nullable T load(String name, Class<T> interfaceClass) {
|
||||
return load(name, interfaceClass, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public static <T extends Library> @Nullable T load(String name, Class<T> interfaceClass, Map<String, ?> options) {
|
||||
if (USE_JNA) {
|
||||
try {
|
||||
return Native.load(name, interfaceClass, options);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
LOG.warning("Failed to load native library: " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean useJNA() {
|
||||
String backend = System.getProperty("hmcl.native.backend");
|
||||
if (backend == null || "auto".equalsIgnoreCase(backend)) {
|
||||
try {
|
||||
if (Platform.isWindows()) {
|
||||
String osVersion = System.getProperty("os.version");
|
||||
|
||||
// Requires Windows 7 or later (6.1+)
|
||||
// https://learn.microsoft.com/windows/win32/sysinfo/operating-system-version
|
||||
if (osVersion == null || osVersion.startsWith("5.") || osVersion.equals("6.0"))
|
||||
return false;
|
||||
|
||||
// Currently we only need to use JNA on Windows
|
||||
Native.getDefaultStringEncoding();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Throwable ignored) {
|
||||
return false;
|
||||
}
|
||||
} else if ("jna".equalsIgnoreCase(backend)) {
|
||||
// Ensure JNA is available
|
||||
Native.getDefaultStringEncoding();
|
||||
return true;
|
||||
} else if ("none".equalsIgnoreCase(backend))
|
||||
return false;
|
||||
else
|
||||
throw new Error("Unsupported native backend: " + backend);
|
||||
}
|
||||
|
||||
private NativeUtils() {
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@
|
||||
package org.jackhuang.hmcl.util.platform;
|
||||
|
||||
import org.jackhuang.hmcl.util.KeyValuePairProperties;
|
||||
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinTypes;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -29,10 +31,7 @@ import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -159,47 +158,68 @@ public enum OperatingSystem {
|
||||
if (CURRENT_OS == WINDOWS) {
|
||||
String versionNumber = null;
|
||||
int buildNumber = -1;
|
||||
int codePage = -1;
|
||||
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"cmd", "ver"});
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), NATIVE_CHARSET))) {
|
||||
Matcher matcher = Pattern.compile("(?<version>[0-9]+\\.[0-9]+\\.(?<build>[0-9]+)(\\.[0-9]+)?)]$")
|
||||
.matcher(reader.readLine().trim());
|
||||
Kernel32 kernel32 = Kernel32.INSTANCE;
|
||||
|
||||
if (matcher.find()) {
|
||||
versionNumber = matcher.group("version");
|
||||
buildNumber = Integer.parseInt(matcher.group("build"));
|
||||
}
|
||||
}
|
||||
process.destroy();
|
||||
} catch (Throwable ignored) {
|
||||
// Get Windows version number
|
||||
if (kernel32 != null) {
|
||||
WinTypes.OSVERSIONINFOEXW osVersionInfo = new WinTypes.OSVERSIONINFOEXW();
|
||||
if (kernel32.GetVersionExW(osVersionInfo)) {
|
||||
int majorVersion = osVersionInfo.dwMajorVersion;
|
||||
int minorVersion = osVersionInfo.dwMinorVersion;
|
||||
|
||||
buildNumber = osVersionInfo.dwBuildNumber;
|
||||
versionNumber = majorVersion + "." + minorVersion + "." + buildNumber;
|
||||
} else
|
||||
System.err.println("Failed to obtain OS version number (" + kernel32.GetLastError() + ")");
|
||||
}
|
||||
|
||||
if (versionNumber == null) {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"cmd", "ver"});
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), NATIVE_CHARSET))) {
|
||||
Matcher matcher = Pattern.compile("(?<version>[0-9]+\\.[0-9]+\\.(?<build>[0-9]+)(\\.[0-9]+)?)]$")
|
||||
.matcher(reader.readLine().trim());
|
||||
|
||||
if (matcher.find()) {
|
||||
versionNumber = matcher.group("version");
|
||||
buildNumber = Integer.parseInt(matcher.group("build"));
|
||||
}
|
||||
}
|
||||
process.destroy();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (versionNumber == null)
|
||||
versionNumber = System.getProperty("os.version");
|
||||
|
||||
// Get Code Page
|
||||
|
||||
if (kernel32 != null)
|
||||
codePage = kernel32.GetACP();
|
||||
else {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"chcp.com"});
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), NATIVE_CHARSET))) {
|
||||
Matcher matcher = Pattern.compile("(?<cp>[0-9]+)$")
|
||||
.matcher(reader.readLine().trim());
|
||||
|
||||
if (matcher.find()) {
|
||||
codePage = Integer.parseInt(matcher.group("cp"));
|
||||
}
|
||||
}
|
||||
process.destroy();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
String osName = System.getProperty("os.name");
|
||||
|
||||
// Java 17 or earlier recognizes Windows 11 as Windows 10
|
||||
if (osName.equals("Windows 10") && buildNumber >= 22000) {
|
||||
if (osName.equals("Windows 10") && buildNumber >= 22000)
|
||||
osName = "Windows 11";
|
||||
}
|
||||
|
||||
int codePage = -1;
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"chcp.com"});
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), NATIVE_CHARSET))) {
|
||||
Matcher matcher = Pattern.compile("(?<cp>[0-9]+)$")
|
||||
.matcher(reader.readLine().trim());
|
||||
|
||||
if (matcher.find()) {
|
||||
codePage = Integer.parseInt(matcher.group("cp"));
|
||||
}
|
||||
}
|
||||
process.destroy();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
SYSTEM_NAME = osName;
|
||||
SYSTEM_VERSION = versionNumber;
|
||||
@@ -443,4 +463,5 @@ public enum OperatingSystem {
|
||||
|
||||
public static final PhysicalMemoryStatus INVALID = new PhysicalMemoryStatus(0, -1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.win32.StdCallLibrary;
|
||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public interface Kernel32 extends StdCallLibrary {
|
||||
|
||||
Kernel32 INSTANCE = NativeUtils.USE_JNA && com.sun.jna.Platform.isWindows()
|
||||
? NativeUtils.load("kernel32", Kernel32.class)
|
||||
: null;
|
||||
|
||||
/**
|
||||
* @see <a href="https://learn.microsoft.com/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror">GetLastError function</a>
|
||||
*/
|
||||
int GetLastError();
|
||||
|
||||
/**
|
||||
* @see <a href="https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-getversionexw">GetVersionExW function</a>
|
||||
*/
|
||||
boolean GetVersionExW(WinTypes.OSVERSIONINFOEXW lpVersionInfo);
|
||||
|
||||
/**
|
||||
* @see <a href="https://learn.microsoft.com/windows/win32/api/winnls/nf-winnls-getacp">GetACP function</a>
|
||||
*/
|
||||
int GetACP();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public interface WinConstants {
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/sysinfo/registry-key-security-and-access-rights
|
||||
int KEY_READ = 0x20019;
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/sysinfo/predefined-keys
|
||||
long HKEY_CLASSES_ROOT = 0x80000000L;
|
||||
long HKEY_CURRENT_USER = 0x80000001L;
|
||||
long HKEY_LOCAL_MACHINE = 0x80000002L;
|
||||
long HKEY_USERS = 0x80000003L;
|
||||
long HKEY_PERFORMANCE_DATA = 0x80000004L;
|
||||
long HKEY_PERFORMANCE_TEXT = 0x80000050L;
|
||||
long HKEY_PERFORMANCE_NLSTEXT = 0x80000060L;
|
||||
long HKEY_CURRENT_CONFIG = 0x80000005L;
|
||||
long HKEY_DYN_DATA = 0x80000006L;
|
||||
long HKEY_CURRENT_USER_LOCAL_SETTINGS = 0x80000007L;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public interface WinTypes {
|
||||
/**
|
||||
* @see <a href="https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-osversioninfoexw">OSVERSIONINFOEXW structure</a>
|
||||
*/
|
||||
final class OSVERSIONINFOEXW extends Structure {
|
||||
public int dwOSVersionInfoSize;
|
||||
public int dwMajorVersion;
|
||||
public int dwMinorVersion;
|
||||
public int dwBuildNumber;
|
||||
public int dwPlatformId;
|
||||
public char[] szCSDVersion;
|
||||
public short wServicePackMajor;
|
||||
public short wServicePackMinor;
|
||||
public short wSuiteMask;
|
||||
public byte wProductType;
|
||||
public byte wReserved;
|
||||
|
||||
public OSVERSIONINFOEXW() {
|
||||
szCSDVersion = new char[128];
|
||||
dwOSVersionInfoSize = size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFieldOrder() {
|
||||
return Arrays.asList(
|
||||
"dwOSVersionInfoSize",
|
||||
"dwMajorVersion", "dwMinorVersion", "dwBuildNumber",
|
||||
"dwPlatformId",
|
||||
"szCSDVersion",
|
||||
"wServicePackMajor", "wServicePackMinor",
|
||||
"wSuiteMask", "wProductType",
|
||||
"wReserved"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user