使用 JNA 调用本机 API (#3890)

This commit is contained in:
Glavo
2025-05-04 22:31:50 +08:00
committed by GitHub
parent f283254ca0
commit 1ab7ab0750
9 changed files with 301 additions and 34 deletions

View File

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

View File

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

View File

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

View File

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

View File

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