diff --git a/assets/UI/Main.css b/assets/UI/Main.css
index e483f2e7..00bdef2a 100644
--- a/assets/UI/Main.css
+++ b/assets/UI/Main.css
@@ -523,6 +523,15 @@ ProgressBar:indeterminate>.bar {
-fx-background-color: #305890;
}
+#Title-minimize-btn-mac,
+#Title-close-btn-mac {
+ -fx-fill: inherit;
+}
+
+#Title-close-btn-mac {
+ -fx-background-radius: 8px 0 0 0;
+}
+
#Title-close-btn:hover,
#Title-close-btn:focused {
-fx-background-color: #CC2211;
diff --git a/assets/UI/RootModule.fxml b/assets/UI/RootModule.fxml
index e9746a79..7615c11b 100644
--- a/assets/UI/RootModule.fxml
+++ b/assets/UI/RootModule.fxml
@@ -140,41 +140,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/assets/UI/SettingsModule.fxml b/assets/UI/SettingsModule.fxml
index 17cd05dc..70f6ebd7 100644
--- a/assets/UI/SettingsModule.fxml
+++ b/assets/UI/SettingsModule.fxml
@@ -157,6 +157,9 @@
+
+
+
diff --git a/assets/UI/Titlebar.fxml b/assets/UI/Titlebar.fxml
new file mode 100644
index 00000000..b24120f7
--- /dev/null
+++ b/assets/UI/Titlebar.fxml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/icons/macos/close.png b/assets/icons/macos/close.png
new file mode 100644
index 00000000..35e9fe72
Binary files /dev/null and b/assets/icons/macos/close.png differ
diff --git a/assets/icons/macos/minimize.png b/assets/icons/macos/minimize.png
new file mode 100644
index 00000000..4da14e9e
Binary files /dev/null and b/assets/icons/macos/minimize.png differ
diff --git a/core/src/cn/harryh/arkpets/utils/IOUtils.java b/core/src/cn/harryh/arkpets/utils/IOUtils.java
index ef13e784..abe69952 100644
--- a/core/src/cn/harryh/arkpets/utils/IOUtils.java
+++ b/core/src/cn/harryh/arkpets/utils/IOUtils.java
@@ -277,4 +277,36 @@ private static void copyFileToZip(ZipOutputStream zos, File sourceFile, String z
zos.closeEntry();
}
}
+
+
+ public static class CommandUtil {
+ /**
+ * Run a command and get the output.
+ * @param command The command will run.
+ * @param env The environment variable.
+ * @param workdir The working directory.
+ * @throws IOException If I/O error occurs.
+ * @return The command output,Return null if failed.
+ */
+ public static String runCommand(String command, String[] env, File workdir) throws IOException {
+ Runtime runtime = Runtime.getRuntime();
+ Process process = runtime.exec(command, env, workdir);
+ try {
+ process.waitFor();
+ } catch (InterruptedException ignore) {
+ }
+ if (process.exitValue() == 0) {
+ BufferedReader reader = process.inputReader();
+ String line;
+ StringBuilder b = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ b.append(line);
+ }
+ reader.close();
+ return b.toString();
+ } else {
+ return null;
+ }
+ }
+ }
}
diff --git a/desktop/src/cn/harryh/arkpets/ArkHomeFX.java b/desktop/src/cn/harryh/arkpets/ArkHomeFX.java
index eba26adf..facacf15 100644
--- a/desktop/src/cn/harryh/arkpets/ArkHomeFX.java
+++ b/desktop/src/cn/harryh/arkpets/ArkHomeFX.java
@@ -9,6 +9,7 @@
import cn.harryh.arkpets.controllers.ModelsModule;
import cn.harryh.arkpets.controllers.RootModule;
import cn.harryh.arkpets.controllers.SettingsModule;
+import cn.harryh.arkpets.controllers.Titlebar;
import cn.harryh.arkpets.tray.HostTray;
import cn.harryh.arkpets.utils.FXMLHelper;
import cn.harryh.arkpets.utils.FXMLHelper.LoadFXMLResult;
@@ -44,6 +45,7 @@ public class ArkHomeFX extends Application {
public ModelsModule modelsModule;
public BehaviorModule behaviorModule;
public SettingsModule settingsModule;
+ public Titlebar titleBar;
static {
FontsConfig.loadFontsToJavafx();
@@ -60,6 +62,10 @@ public void start(Stage stage) throws Exception {
fxml0.initializeWith(this);
rootModule = (RootModule)fxml0.controller();
body = rootModule.body;
+ LoadFXMLResult fxmlTitlebar = FXMLHelper.loadFXML(getClass().getResource("/UI/Titlebar.fxml"));
+ fxmlTitlebar.initializeWith(this);
+ titleBar = (Titlebar) fxmlTitlebar.controller();
+ fxmlTitlebar.addToNode(rootModule.wrapper4);
// Setup scene and primary stage.
Logger.info("Launcher", "Creating main scene");
@@ -71,7 +77,7 @@ public void start(Stage stage) throws Exception {
stage.setResizable(false);
stage.setScene(scene);
stage.setTitle(desktopTitle);
- rootModule.titleText.setText(desktopTitle);
+ titleBar.titleText.setText(desktopTitle);
// After the stage is shown, do initialization.
stage.show();
diff --git a/desktop/src/cn/harryh/arkpets/DesktopLauncher.java b/desktop/src/cn/harryh/arkpets/DesktopLauncher.java
index 1e3aecac..913a2a16 100644
--- a/desktop/src/cn/harryh/arkpets/DesktopLauncher.java
+++ b/desktop/src/cn/harryh/arkpets/DesktopLauncher.java
@@ -3,6 +3,8 @@
*/
package cn.harryh.arkpets;
+import cn.harryh.arkpets.controllers.Titlebar;
+import cn.harryh.arkpets.envchecker.WinGraphicsEnvCheckTask;
import cn.harryh.arkpets.utils.ArgPending;
import cn.harryh.arkpets.utils.Logger;
import javafx.application.Application;
@@ -60,6 +62,20 @@ protected void process(String command, String addition) {
System.exit(0);
}
};
+ // Change ui style
+ new ArgPending("--ui-style", args) {
+ @Override
+ protected void process(String command, String addition) {
+ Titlebar.forceUiStyle = addition.toLowerCase();
+ }
+ };
+ // Remove NVIDIA settings on uninstall
+ new ArgPending("--remove-nvidia", args) {
+ @Override
+ protected void process(String command, String addition) {
+ new WinGraphicsEnvCheckTask().removeNvidiaSettings();
+ }
+ };
// Disable libdecor to avoid glfw and javafx problem
GLFW.glfwInitHint(GLFW.GLFW_WAYLAND_LIBDECOR, GLFW.GLFW_WAYLAND_DISABLE_LIBDECOR);
// Java FX bootstrap
diff --git a/desktop/src/cn/harryh/arkpets/controllers/RootModule.java b/desktop/src/cn/harryh/arkpets/controllers/RootModule.java
index a154af0f..7457ead5 100644
--- a/desktop/src/cn/harryh/arkpets/controllers/RootModule.java
+++ b/desktop/src/cn/harryh/arkpets/controllers/RootModule.java
@@ -8,7 +8,9 @@
import cn.harryh.arkpets.EmbeddedLauncher;
import cn.harryh.arkpets.concurrent.ProcessPool;
import cn.harryh.arkpets.guitasks.CheckAppUpdateTask;
+import cn.harryh.arkpets.guitasks.CheckEnvironmentTask;
import cn.harryh.arkpets.guitasks.GuiTask;
+import cn.harryh.arkpets.envchecker.EnvCheckTask;
import cn.harryh.arkpets.utils.ArgPending;
import cn.harryh.arkpets.utils.GuiPrefabs;
import cn.harryh.arkpets.utils.Logger;
@@ -19,14 +21,13 @@
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
+import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
-import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.SVGPath;
-import javafx.scene.text.Text;
import javafx.util.Duration;
import java.util.ArrayList;
@@ -58,6 +59,8 @@ public final class RootModule implements Controller {
@FXML
public AnchorPane wrapper3;
@FXML
+ public Pane wrapper4;
+ @FXML
private Pane loadingMask;
@FXML
private Pane splashScreen;
@@ -75,18 +78,8 @@ public final class RootModule implements Controller {
@FXML
public JFXButton launchBtn;
- @FXML
- public AnchorPane titleBar;
- @FXML
- public Text titleText;
- @FXML
- private JFXButton titleMinimizeBtn;
- @FXML
- private JFXButton titleCloseBtn;
-
private ArkHomeFX app;
- private double xOffset;
- private double yOffset;
+ private boolean checkEnd;
@Override
public void initializeWith(ArkHomeFX app) {
@@ -198,59 +191,30 @@ public void exit() {
}, durationFast, durationNormal);
}
- @FXML
- public void titleBarPressed(MouseEvent event) {
- xOffset = event.getSceneX();
- yOffset = event.getSceneY();
- }
-
- @FXML
- public void titleBarDragged(MouseEvent event) {
- app.stage.setX(event.getScreenX() - xOffset);
- app.stage.setY(event.getScreenY() - yOffset);
- }
-
- @FXML
- public void windowMinimize(MouseEvent event) {
- GuiPrefabs.fadeOutWindow(app.stage, durationFast, e -> {
- app.stage.hide();
- app.stage.setIconified(true);
- });
- }
-
- @FXML
- public void windowClose(MouseEvent event) {
- String solidExitTip = (app.config != null && app.config.launcher_solid_exit) ?
- "退出程序将会同时退出已启动的桌宠。" : "退出程序后已启动的桌宠将会保留。";
- GuiPrefabs.Dialogs.createConfirmDialog(body,
- GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_HELP_ALT, GuiPrefabs.COLOR_INFO),
- "确认退出",
- "现在退出 " + appName + " 吗?",
- "根据您的设置," + solidExitTip + "\n使用最小化 [-] 按钮可以隐藏窗口到系统托盘。",
- this::exit).show();
- }
-
private void initLaunchButton() {
+ // Build environment check confirm dialog.
+ JFXDialog dialog = GuiPrefabs.Dialogs.createConfirmDialog(body,
+ GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_HELP_ALT, GuiPrefabs.COLOR_INFO),
+ "环境检查",
+ "首次运行环境检查",
+ "这似乎是你第一次运行 ArkPets,我们需要对您的系统进行一些基本检查以确保桌宠能够正常运行。\n你也可以跳过检查,但可能会导致使用体验下降。",
+ () -> {
+ new CheckEnvironmentTask(app.body,EnvCheckTask.getAvailableTasks(),this::launchArkPets).start();
+ });
+ Node cancel = ((JFXDialogLayout)dialog.getContent()).getActions().get(0);
+ ((JFXButton) cancel).setOnAction(e -> {
+ GuiPrefabs.Dialogs.disposeDialog(dialog);
+ launchArkPets();
+ });
// Set handler for internal start button.
launchBtn.setOnAction(e -> {
// When request to launch ArkPets:
- launchBtn.setDisable(true);
- app.config.save();
- if (app.config.character_asset != null && !app.config.character_asset.isEmpty()) {
- app.popLoading(ev -> {
- try {
- // Do launch ArkPets core.
- startArkPetsCore();
- Thread.sleep(1200);
- // Show handbook in the first-run.
- if (isNewcomer)
- trayExitHandbook.showIfNotShownBefore(app.body);
- } catch (InterruptedException ignored) {
- } finally {
- launchBtn.setDisable(false);
- }
- });
+ if (isNewcomer && !checkEnd) {
+ checkEnd = true;
+ dialog.show();
+ return;
}
+ launchArkPets();
});
}
@@ -297,6 +261,25 @@ protected Boolean call() throws Exception {
ss.start();
}
+ private void launchArkPets() {
+ launchBtn.setDisable(true);
+ app.config.save();
+ if (app.config.character_asset != null && !app.config.character_asset.isEmpty()) {
+ app.popLoading(ev -> {
+ try {
+ // Do launch ArkPets core.
+ startArkPetsCore();
+ Thread.sleep(1200);
+ // Show handbook in the first-run.
+ if (isNewcomer)
+ trayExitHandbook.showIfNotShownBefore(app.body);
+ } catch (InterruptedException ignored) {
+ } finally {
+ launchBtn.setDisable(false);
+ }
+ });
+ }
+ }
private static class TrayExitHandBook extends Handbook {
@Override
public String getTitle() {
diff --git a/desktop/src/cn/harryh/arkpets/controllers/SettingsModule.java b/desktop/src/cn/harryh/arkpets/controllers/SettingsModule.java
index c1fdffa9..04ed22ad 100644
--- a/desktop/src/cn/harryh/arkpets/controllers/SettingsModule.java
+++ b/desktop/src/cn/harryh/arkpets/controllers/SettingsModule.java
@@ -7,9 +7,11 @@
import cn.harryh.arkpets.ArkHomeFX;
import cn.harryh.arkpets.Const;
import cn.harryh.arkpets.guitasks.CheckAppUpdateTask;
+import cn.harryh.arkpets.guitasks.CheckEnvironmentTask;
import cn.harryh.arkpets.guitasks.GuiTask;
-import cn.harryh.arkpets.platform.StartupConfig;
+import cn.harryh.arkpets.startup.StartupConfig;
import cn.harryh.arkpets.platform.WindowSystem;
+import cn.harryh.arkpets.envchecker.EnvCheckTask;
import cn.harryh.arkpets.utils.*;
import cn.harryh.arkpets.utils.GuiComponents.*;
import com.badlogic.gdx.graphics.Color;
@@ -103,12 +105,13 @@ public final class SettingsModule implements Controller {
private JFXCheckBox configWindowToolwindow;
@FXML
private JFXButton configWindowToolwindowHelp;
-
@FXML
private JFXComboBox> configWindowSystem;
@FXML
private JFXButton configWindowSystemHelp;
@FXML
+ private Label runEnvCheck;
+ @FXML
private Label aboutQueryUpdate;
@FXML
private Label aboutVisitWebsite;
@@ -397,6 +400,8 @@ public String getContent() {
};
}
};
+
+ runEnvCheck.setOnMouseClicked(e -> new CheckEnvironmentTask(app.body, EnvCheckTask.getAvailableTasks()).start());
}
private static ArrayList> getWindowSystemItems() {
@@ -578,4 +583,7 @@ protected Boolean call() {
ss.setRestartOnFailure(true);
ss.start();
}
+
+ private void clearData() {
+ }
}
diff --git a/desktop/src/cn/harryh/arkpets/controllers/Titlebar.java b/desktop/src/cn/harryh/arkpets/controllers/Titlebar.java
new file mode 100644
index 00000000..4d8c02b7
--- /dev/null
+++ b/desktop/src/cn/harryh/arkpets/controllers/Titlebar.java
@@ -0,0 +1,147 @@
+package cn.harryh.arkpets.controllers;
+
+import cn.harryh.arkpets.ArkHomeFX;
+import cn.harryh.arkpets.utils.GuiPrefabs;
+import javafx.fxml.FXML;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.text.Text;
+
+import static cn.harryh.arkpets.Const.appName;
+import static cn.harryh.arkpets.Const.durationFast;
+
+
+public class Titlebar implements Controller {
+ @FXML
+ public Text titleText;
+ @FXML
+ public HBox macTitleButtons;
+ @FXML
+ public HBox titleButtons;
+ @FXML
+ public HBox title;
+ @FXML
+ public ImageView titleMacCloseImage;
+ @FXML
+ public ImageView titleMacMinimizeImage;
+
+ public static String forceUiStyle = "";
+
+ private ArkHomeFX app;
+ private double xOffset;
+ private double yOffset;
+ private final Rectangle2D area = new Rectangle2D(0, 0, 16, 16);
+ private final Rectangle2D hoverArea = new Rectangle2D(16, 0, 16, 16);
+ private final Rectangle2D activeArea = new Rectangle2D(32, 0, 16, 16);
+ private final Rectangle2D disableArea = new Rectangle2D(48, 0, 16, 16);
+ private boolean inHBox;
+ private boolean focused;
+
+ @Override
+ public void initializeWith(ArkHomeFX app) {
+ this.app = app;
+ if (forceUiStyle.equals("mac") || com.sun.jna.Platform.isMac()) {
+ initMacTitlebar();
+ } else if (forceUiStyle.equals("win") || com.sun.jna.Platform.isWindows()){
+
+ }
+ }
+
+ @FXML
+ public void titleBarPressed(MouseEvent event) {
+ xOffset = event.getSceneX();
+ yOffset = event.getSceneY();
+ }
+
+ @FXML
+ public void titleBarDragged(MouseEvent event) {
+ app.stage.setX(event.getScreenX() - xOffset);
+ app.stage.setY(event.getScreenY() - yOffset);
+ }
+
+ @FXML
+ public void windowMinimize(MouseEvent event) {
+ GuiPrefabs.fadeOutWindow(app.stage, durationFast, e -> {
+ app.stage.hide();
+ app.stage.setIconified(true);
+ });
+ }
+
+ @FXML
+ public void windowClose(MouseEvent event) {
+ String solidExitTip = (app.config != null && app.config.launcher_solid_exit) ?
+ "退出程序将会同时退出已启动的桌宠。" : "退出程序后已启动的桌宠将会保留。";
+ GuiPrefabs.Dialogs.createConfirmDialog(app.body,
+ GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_HELP_ALT, GuiPrefabs.COLOR_INFO),
+ "确认退出",
+ "现在退出 " + appName + " 吗?",
+ "根据您的设置," + solidExitTip + "\n使用最小化 [-] 按钮可以隐藏窗口到系统托盘。",
+ app.rootModule::exit).show();
+ }
+
+ @FXML
+ public void mouseEnterBtnBoxMac() {
+ if (focused) {
+ titleMacCloseImage.setViewport(hoverArea);
+ titleMacMinimizeImage.setViewport(hoverArea);
+ inHBox = true;
+ }
+ }
+
+ @FXML
+ public void mouseExitBtnBoxMac() {
+ if (focused) {
+ titleMacCloseImage.setViewport(area);
+ titleMacMinimizeImage.setViewport(area);
+ inHBox = false;
+ }
+ }
+
+ @FXML
+ public void closePressedMac() {
+ titleMacCloseImage.setViewport(activeArea);
+ }
+
+ @FXML
+ public void closeReleasedMac() {
+ if (inHBox) {
+ titleMacCloseImage.setViewport(hoverArea);
+ } else {
+ titleMacCloseImage.setViewport(area);
+ }
+ }
+
+ @FXML
+ public void minimizePressedMac() {
+ titleMacMinimizeImage.setViewport(activeArea);
+ }
+
+ @FXML
+ public void minimizeReleasedMac() {
+ if (inHBox) {
+ titleMacMinimizeImage.setViewport(hoverArea);
+ } else {
+ titleMacMinimizeImage.setViewport(area);
+ }
+ }
+
+ private void initMacTitlebar() {
+ AnchorPane.setLeftAnchor(title, 52.0);
+ macTitleButtons.setVisible(true);
+ AnchorPane.setRightAnchor(titleButtons, null);
+ titleButtons.setVisible(false);
+ app.stage.focusedProperty().addListener((e, oldValue, newValue) -> {
+ focused = newValue;
+ if (!newValue) {
+ titleMacCloseImage.setViewport(disableArea);
+ titleMacMinimizeImage.setViewport(disableArea);
+ } else {
+ titleMacCloseImage.setViewport(area);
+ titleMacMinimizeImage.setViewport(area);
+ }
+ });
+ }
+}
diff --git a/desktop/src/cn/harryh/arkpets/envchecker/ConfirmTestCheckTask.java b/desktop/src/cn/harryh/arkpets/envchecker/ConfirmTestCheckTask.java
new file mode 100644
index 00000000..aae4de43
--- /dev/null
+++ b/desktop/src/cn/harryh/arkpets/envchecker/ConfirmTestCheckTask.java
@@ -0,0 +1,43 @@
+package cn.harryh.arkpets.envchecker;
+
+public class ConfirmTestCheckTask extends EnvCheckTask {
+ @Override
+ public String getFailureReason() {
+ return "";
+ }
+
+ @Override
+ public String getFailureDetail() {
+ return "";
+ }
+
+ @Override
+ public boolean tryFix() {
+ return true;
+ }
+
+ @Override
+ public boolean needConfirmFix() {
+ return true;
+ }
+
+ @Override
+ public boolean canFix() {
+ return true;
+ }
+
+ @Override
+ public boolean run() {
+ return false;
+ }
+
+ @Override
+ public String getFixReason() {
+ return "TestConfirm";
+ }
+
+ @Override
+ public String getFixDetail() {
+ return "TestConfirm";
+ }
+}
diff --git a/desktop/src/cn/harryh/arkpets/envchecker/EnvCheckTask.java b/desktop/src/cn/harryh/arkpets/envchecker/EnvCheckTask.java
new file mode 100644
index 00000000..6bf99eee
--- /dev/null
+++ b/desktop/src/cn/harryh/arkpets/envchecker/EnvCheckTask.java
@@ -0,0 +1,46 @@
+package cn.harryh.arkpets.envchecker;
+
+import com.sun.jna.Platform;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public abstract class EnvCheckTask {
+ public abstract String getFailureReason();
+
+ public abstract String getFailureDetail();
+
+ public abstract boolean tryFix();
+
+ public abstract boolean canFix();
+
+ public boolean needConfirmFix() {
+ return false;
+ }
+
+ public String getFixReason() {
+ return "";
+ }
+
+ public String getFixDetail() {
+ return "";
+ }
+
+ public abstract boolean run();
+
+ @Override
+ public String toString() {
+ String name = getClass().getSimpleName();
+ return name.isEmpty() ? getClass().getSuperclass().getSimpleName() : name;
+ }
+
+ public static List getAvailableTasks() {
+ ArrayList list = new ArrayList<>();
+ list.add(new SleepEnvCheckTask(1500));
+ if (Platform.isWindows()) {
+ list.add(new WinGraphicsEnvCheckTask());
+ }
+ return list;
+ }
+}
diff --git a/desktop/src/cn/harryh/arkpets/envchecker/SleepEnvCheckTask.java b/desktop/src/cn/harryh/arkpets/envchecker/SleepEnvCheckTask.java
new file mode 100644
index 00000000..08fc3b4a
--- /dev/null
+++ b/desktop/src/cn/harryh/arkpets/envchecker/SleepEnvCheckTask.java
@@ -0,0 +1,39 @@
+package cn.harryh.arkpets.envchecker;
+
+public class SleepEnvCheckTask extends EnvCheckTask {
+ private final int time;
+
+ public SleepEnvCheckTask(int time) {
+ super();
+ this.time = time;
+ }
+
+ @Override
+ public boolean run() {
+ try {
+ Thread.sleep(time);
+ } catch (Exception ignored) {
+ }
+ return true;
+ }
+
+ @Override
+ public String getFailureReason() {
+ return "";
+ }
+
+ @Override
+ public String getFailureDetail() {
+ return "";
+ }
+
+ @Override
+ public boolean tryFix() {
+ return true;
+ }
+
+ @Override
+ public boolean canFix() {
+ return true;
+ }
+}
diff --git a/desktop/src/cn/harryh/arkpets/envchecker/WinGraphicsEnvCheckTask.java b/desktop/src/cn/harryh/arkpets/envchecker/WinGraphicsEnvCheckTask.java
new file mode 100644
index 00000000..dd43e38b
--- /dev/null
+++ b/desktop/src/cn/harryh/arkpets/envchecker/WinGraphicsEnvCheckTask.java
@@ -0,0 +1,270 @@
+package cn.harryh.arkpets.envchecker;
+
+import cn.harryh.arkpets.utils.IOUtils;
+import cn.harryh.arkpets.utils.Logger;
+import cn.harryh.arkpets.utils.NVAPIWrapper;
+import com.sun.jna.Native;
+import com.sun.jna.NativeLong;
+import com.sun.jna.WString;
+import com.sun.jna.platform.win32.Advapi32;
+import com.sun.jna.platform.win32.Advapi32Util;
+import com.sun.jna.platform.win32.Win32Exception;
+import com.sun.jna.platform.win32.WinReg;
+import com.sun.jna.ptr.IntByReference;
+import com.sun.jna.ptr.PointerByReference;
+
+import java.io.File;
+
+import static com.sun.jna.platform.win32.WinNT.*;
+import static com.sun.jna.platform.win32.WinReg.HKEY_CURRENT_USER;
+
+
+public class WinGraphicsEnvCheckTask extends EnvCheckTask {
+ private String failureReason;
+ private String failureDetail;
+ private FixMode fix;
+
+ private final String launcherPath;
+ private final String javaBin;
+
+ public static final String NVAPI_PROFILE_NAME = "ArkPets";
+
+ private enum FixMode {
+ WIN_SAV, // Windows Power-saving
+ WIN_PERF,
+ NV, // NVIDIA OpenGL GDI and Present method
+ WIN_PERF_NV, // Windows Performance, NVIDIA OpenGL GDI and Present method
+ FAIL // Can't Fix
+ }
+
+ public WinGraphicsEnvCheckTask() {
+ super();
+ javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java.exe";
+ File launcher = new File("ArkPets.exe");
+ if (launcher.exists()) launcherPath = launcher.getAbsolutePath().replaceAll("\"", "\"\"");
+ else launcherPath = javaBin;
+ }
+
+ @Override
+ public String getFailureReason() {
+ return failureReason;
+ }
+
+ @Override
+ public String getFailureDetail() {
+ return failureDetail;
+ }
+
+ @Override
+ public boolean tryFix() {
+ try {
+ switch (fix) {
+ case NV -> setNvidiaGLSettings(launcherPath, javaBin);
+ case WIN_SAV -> {
+ setWinGraphicsCard(launcherPath, false);
+ setWinGraphicsCard(javaBin, false);
+ }
+ case WIN_PERF -> {
+ setWinGraphicsCard(launcherPath, true);
+ setWinGraphicsCard(javaBin, true);
+ }
+ case WIN_PERF_NV -> {
+ setWinGraphicsCard(launcherPath, true);
+ setWinGraphicsCard(javaBin, true);
+ setNvidiaGLSettings(launcherPath, javaBin);
+ }
+ }
+ } catch (Exception e) {
+ Logger.error("System", "Failed to modify graphics settings", e);
+ failureDetail = "自动设置显卡失败";
+ failureReason = "尝试设置显卡时失败,请查看“常见问题解答”中的方法进行设置。";
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean canFix() {
+ return fix != FixMode.FAIL;
+ }
+
+ @Override
+ public boolean run() {
+ String cards = wmicCheck();
+ if (cards != null) {
+ try {
+ if (cards.contains("Intel") && cards.contains("NVIDIA")) {
+ // I+N Hybrid
+ boolean card = checkWinGraphicsCard(launcherPath, false) && checkWinGraphicsCard(javaBin, false);
+ if (!card) {
+ fix = FixMode.WIN_SAV;
+ return false;
+ }
+ return true;
+ } else if (cards.contains("AMD") && cards.contains("NVIDIA")) {
+ // A+N Hybrid
+ boolean card = checkWinGraphicsCard(launcherPath, true) && checkWinGraphicsCard(javaBin, true);
+ boolean nv = checkNvidiaGLSettings();
+ if (card && nv) {
+ return true;
+ }
+ if (!card && !nv) fix = FixMode.WIN_PERF_NV;
+ else if (!card) fix = FixMode.WIN_PERF;
+ else fix = FixMode.NV;
+ return false;
+ } else if (cards.contains("AMD")) {
+ // A+A Hybrid or AMD single,Fail temporary
+ fix = FixMode.FAIL;
+ failureReason = "AMD 显卡警告";
+ failureDetail = "检测到正在使用 AMD 显卡,ArkPets 尚未对 AMD 显卡进行充分测试。\n你仍可以强制运行,但桌宠背景可能会不透明。";
+ return false;
+ } else if (cards.contains("NVIDIA")) {
+ // NVIDIA only
+ boolean status = checkNvidiaGLSettings();
+ if (!status) {
+ fix = FixMode.NV;
+ return false;
+ }
+ return true;
+ } else if (cards.contains("Intel")) {
+ // Intel only
+ return true;
+ } else {
+ // Other card (Virtual,Software,Non-mainstream...)
+ failureReason = "未知显卡警告";
+ failureDetail = "当前可能正在使用特殊显卡(虚拟显卡、软件渲染等),ArkPets 尚未对这类显卡进行测试。\n你仍可以强制运行,但可能会产生未知的问题。";
+ fix = FixMode.FAIL;
+ return false;
+ }
+ } catch (Exception e) {
+ failureReason = "获取显卡信息失败";
+ failureDetail = "当前无法获取显卡信息,请参考“常见问题解答”对显卡进行设置。";
+ fix = FixMode.FAIL;
+ return false;
+ }
+ } else {
+ failureReason = "获取显卡信息失败";
+ failureDetail = "当前无法获取显卡信息,请参考“常见问题解答”对显卡进行设置。";
+ fix = FixMode.FAIL;
+ return false;
+ }
+ }
+
+ private static String wmicCheck() {
+ try {
+ String result = IOUtils.CommandUtil.runCommand("wmic path win32_VideoController get Name", null, null);
+ if (result != null) {
+ return result;
+ } else {
+ Logger.warn("EnvCheck", "Failed to get graphics card info");
+ return null;
+ }
+ } catch (Exception e) {
+ Logger.warn("EnvCheck", "Failed to get graphics card info");
+ return null;
+ }
+ }
+
+ public boolean checkNvidiaGLSettings() {
+ boolean status = false;
+ NVAPIWrapper.NvAPI_Initialize();
+ PointerByReference sess = new PointerByReference();
+ NVAPIWrapper.NvAPI_DRS_CreateSession(sess);
+ NVAPIWrapper.NvAPI_DRS_LoadSettings(sess.getValue());
+ PointerByReference pro = new PointerByReference();
+ try {
+ NVAPIWrapper.NvAPI_DRS_FindProfileByName(sess.getValue(), new WString(NVAPI_PROFILE_NAME), pro);
+ status = true;
+ } catch (Exception ignore) {
+ }
+ NVAPIWrapper.NvAPI_DRS_DestroySession(sess.getValue());
+ NVAPIWrapper.NvAPI_Unload();
+ return status;
+ }
+
+ public void setNvidiaGLSettings(String... path) {
+ NVAPIWrapper.NvAPI_Initialize();
+ PointerByReference sess = new PointerByReference();
+ NVAPIWrapper.NvAPI_DRS_CreateSession(sess);
+ NVAPIWrapper.NvAPI_DRS_LoadSettings(sess.getValue());
+ PointerByReference prof = new PointerByReference();
+ NVAPIWrapper.NVDRS_PROFILE.ByReference profile = new NVAPIWrapper.NVDRS_PROFILE.ByReference();
+ NVAPIWrapper.writeStringToShortArray(NVAPI_PROFILE_NAME, profile.profileName);
+ NVAPIWrapper.NvAPI_DRS_CreateProfile(sess.getValue(), profile, prof);
+ for (String p : path) {
+ NVAPIWrapper.NVDRS_APPLICATION.ByReference app = new NVAPIWrapper.NVDRS_APPLICATION.ByReference();
+ NVAPIWrapper.writeStringToShortArray(p, app.appName);
+ NVAPIWrapper.writeStringToShortArray(p, app.userFriendlyName);
+ NVAPIWrapper.NvAPI_DRS_CreateApplication(sess.getValue(), prof.getValue(), app);
+ }
+ NVAPIWrapper.NVDRS_SETTING.ByReference glSetting = new NVAPIWrapper.NVDRS_SETTING.ByReference();
+ glSetting.settingId = new NativeLong(0x2072C5A3);
+ glSetting.settingType = 0;
+ glSetting.currentValue.u32 = new NativeLong(1);
+ //NVAPIWrapper.NVDRS_SETTING.ByReference dxgiSetting = new NVAPIWrapper.NVDRS_SETTING.ByReference();
+ // todo dxgiSetting.settingId= new NativeLong(0x20D690F8);
+ // todo dxgiSetting.currentValue.u32 =
+ //dxgiSetting.settingType = 0;
+ NVAPIWrapper.NVDRS_SETTING.ByReference optimusSetting = new NVAPIWrapper.NVDRS_SETTING.ByReference();
+ optimusSetting.settingId = new NativeLong(0x10F9DC81);
+ optimusSetting.currentValue.u32 = new NativeLong(1);
+ optimusSetting.settingType = 0;
+ NVAPIWrapper.NvAPI_DRS_SetSetting(sess.getValue(), prof.getValue(), glSetting);
+ //NVAPIWrapper.NvAPI_DRS_SetSetting(sess.getValue(),prof.getValue(),dxgiSetting);
+ NVAPIWrapper.NvAPI_DRS_SetSetting(sess.getValue(), prof.getValue(), optimusSetting);
+ NVAPIWrapper.NvAPI_DRS_SaveSettings(sess.getValue());
+ NVAPIWrapper.NvAPI_DRS_DestroySession(sess.getValue());
+ NVAPIWrapper.NvAPI_Unload();
+ Logger.info("EnvCheck", "Success write NVIDIA GPU settings");
+ }
+
+ public boolean checkWinGraphicsCard(String path, boolean performance) {
+ WinReg.HKEYByReference outKey = new WinReg.HKEYByReference();
+ int winstatus = Advapi32.INSTANCE.RegOpenKeyEx(HKEY_CURRENT_USER,
+ "Software\\Microsoft\\DirectX\\UserGpuPreferences", 0, KEY_READ, outKey);
+ if (winstatus != 0) throw new Win32Exception(winstatus);
+ char[] data = new char[1024];
+ winstatus = Advapi32.INSTANCE.RegQueryValueEx(outKey.getValue(), path, 0,
+ new IntByReference(REG_SZ), data, new IntByReference(1024));
+ if (winstatus != 0) {
+ if (winstatus == 2) return false; // not found, uncertain card.
+ throw new Win32Exception(winstatus);
+ }
+ String value = Native.toString(data);
+ Advapi32.INSTANCE.RegCloseKey(outKey.getValue());
+ if (value.contains("GpuPreference=0;")) return false; // uncertain card.
+ if (value.contains("GpuPreference=1;") && !performance) return true;
+ return value.contains("GpuPreference=2;") && performance;
+ }
+
+ public void setWinGraphicsCard(String path, boolean performance) {
+ WinReg.HKEYByReference outKey = new WinReg.HKEYByReference();
+ int winstatus = Advapi32.INSTANCE.RegOpenKeyEx(HKEY_CURRENT_USER,
+ "Software\\Microsoft\\DirectX\\UserGpuPreferences", 0, KEY_WRITE, outKey);
+ if (winstatus != 0) throw new Win32Exception(winstatus);
+ String value = performance ? "GpuPreference=2;" : "GpuPreference=1;";
+ Advapi32Util.registrySetStringValue(outKey.getValue(), path, value);
+ Advapi32.INSTANCE.RegCloseKey(outKey.getValue());
+ Logger.info("EnvCheck", "Success set GPU to " + (performance ? "performance" : "power-saving") + "mode");
+ }
+
+ public void removeNvidiaSettings() {
+ try {
+ String cards = wmicCheck();
+ if (cards != null && cards.contains("NVIDIA")) {
+ NVAPIWrapper.NvAPI_Initialize();
+ PointerByReference sess = new PointerByReference();
+ NVAPIWrapper.NvAPI_DRS_CreateSession(sess);
+ NVAPIWrapper.NvAPI_DRS_LoadSettings(sess.getValue());
+ PointerByReference pro = new PointerByReference();
+ NVAPIWrapper.NvAPI_DRS_FindProfileByName(sess.getValue(), new WString(NVAPI_PROFILE_NAME), pro);
+ NVAPIWrapper.NvAPI_DRS_DeleteProfile(sess.getValue(), pro.getValue());
+ NVAPIWrapper.NvAPI_DRS_SaveSettings(sess.getValue());
+ NVAPIWrapper.NvAPI_DRS_DestroySession(sess.getValue());
+ NVAPIWrapper.NvAPI_Unload();
+ Logger.info("EnvCheck", "Success remove NVIDIA GPU settings");
+ }
+ } catch (Exception ignore) {
+ }
+ }
+}
diff --git a/desktop/src/cn/harryh/arkpets/guitasks/CheckEnvironmentTask.java b/desktop/src/cn/harryh/arkpets/guitasks/CheckEnvironmentTask.java
new file mode 100644
index 00000000..4c74bee2
--- /dev/null
+++ b/desktop/src/cn/harryh/arkpets/guitasks/CheckEnvironmentTask.java
@@ -0,0 +1,138 @@
+package cn.harryh.arkpets.guitasks;
+
+import cn.harryh.arkpets.envchecker.EnvCheckTask;
+import cn.harryh.arkpets.utils.GuiPrefabs;
+import cn.harryh.arkpets.utils.Logger;
+import javafx.concurrent.Task;
+import javafx.scene.layout.StackPane;
+
+import java.util.List;
+
+
+public class CheckEnvironmentTask extends GuiTask {
+ private String failureReason;
+ private String failureContent;
+ private CheckStatus status;
+ private final List list;
+ private final StackPane parent;
+ private Runnable success;
+
+ private enum CheckStatus {
+ UNKNOWN,
+ SUCCESS,
+ FAILED,
+ NEEDCONFIRM,
+ RUNNING
+ }
+
+ public CheckEnvironmentTask(StackPane parent, List list) {
+ super(parent, GuiTaskStyle.STRICT);
+ this.parent = parent;
+ this.list = list;
+ }
+
+ public CheckEnvironmentTask(StackPane parent, List list, Runnable success) {
+ this(parent, list);
+ this.success = success;
+ }
+
+ @Override
+ protected Task getTask() {
+ return new Task() {
+ @Override
+ protected Boolean call() {
+ failureContent = null;
+ failureReason = null;
+ status = CheckStatus.RUNNING;
+ while (!list.isEmpty()) {
+ EnvCheckTask task = list.get(0);
+ Logger.info("EnvCheck", "Running Check " + task);
+ boolean result = task.run();
+ if (!result) {
+ if (task.canFix()) {
+ if (task.needConfirmFix()) {
+ failureReason = task.getFixReason();
+ failureContent = task.getFixDetail();
+ status = CheckStatus.NEEDCONFIRM;
+ return true;
+ }
+ Logger.info("EnvCheck", "Running Fix " + task);
+ boolean fixResult = task.tryFix();
+ if (!fixResult) {
+ failureReason = task.getFailureReason();
+ failureContent = task.getFailureDetail();
+ status = CheckStatus.FAILED;
+ return true;
+ }
+ } else {
+ failureReason = task.getFailureReason();
+ failureContent = task.getFailureDetail();
+ status = CheckStatus.FAILED;
+ return true;
+ }
+ }
+ list.remove(0);
+ }
+ status = CheckStatus.SUCCESS;
+ return true;
+ }
+ };
+ }
+
+ @Override
+ protected String getHeader() {
+ return "正在检查运行环境";
+ }
+
+ @Override
+ protected String getInitialContent() {
+ return "这可能需要数秒钟";
+ }
+
+ @Override
+ protected void onFailed(Throwable e) {
+ if (style != GuiTaskStyle.HIDDEN)
+ GuiPrefabs.Dialogs.createErrorDialog(parent, e).show();
+
+ }
+
+ @Override
+ protected void onSucceeded(boolean result) {
+ if (style != GuiTaskStyle.HIDDEN) {
+ if (status == CheckStatus.FAILED) {
+ GuiPrefabs.Dialogs.createCommonDialog(parent,
+ GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_WARNING_ALT, GuiPrefabs.COLOR_WARNING),
+ "环境检查警告",
+ failureReason,
+ failureContent,
+ null).show();
+ } else if (status == CheckStatus.NEEDCONFIRM) {
+ GuiPrefabs.Dialogs.createConfirmDialog(parent,
+ GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_HELP_ALT, GuiPrefabs.COLOR_INFO),
+ "环境检查提示",
+ failureReason,
+ failureContent,
+ this::fixAndContinue).show();
+ } else if (status == CheckStatus.SUCCESS && success != null) {
+ success.run();
+ }
+ }
+ }
+
+ private void fixAndContinue() {
+ EnvCheckTask fixtask = list.get(0);
+ Logger.info("EnvCheck", "Running Fix " + fixtask);
+ boolean fixResult = fixtask.tryFix();
+ if (!fixResult) {
+ GuiPrefabs.Dialogs.createCommonDialog(parent,
+ GuiPrefabs.Icons.getIcon(GuiPrefabs.Icons.SVG_WARNING_ALT, GuiPrefabs.COLOR_WARNING),
+ "环境检查警告",
+ fixtask.getFailureReason(),
+ fixtask.getFailureDetail(),
+ null).show();
+ return;
+ }
+ list.remove(0);
+ new CheckEnvironmentTask(parent, list).start();
+ }
+}
diff --git a/core/src/cn/harryh/arkpets/platform/NullStartupConfig.java b/desktop/src/cn/harryh/arkpets/startup/NullStartupConfig.java
similarity index 92%
rename from core/src/cn/harryh/arkpets/platform/NullStartupConfig.java
rename to desktop/src/cn/harryh/arkpets/startup/NullStartupConfig.java
index 450559d0..dc7a7d27 100644
--- a/core/src/cn/harryh/arkpets/platform/NullStartupConfig.java
+++ b/desktop/src/cn/harryh/arkpets/startup/NullStartupConfig.java
@@ -1,7 +1,7 @@
/** Copyright (c) 2022-2024, Harry Huang, Litwak913
* At GPL-3.0 License
*/
-package cn.harryh.arkpets.platform;
+package cn.harryh.arkpets.startup;
public class NullStartupConfig extends StartupConfig {
@Override
diff --git a/core/src/cn/harryh/arkpets/platform/StartupConfig.java b/desktop/src/cn/harryh/arkpets/startup/StartupConfig.java
similarity index 95%
rename from core/src/cn/harryh/arkpets/platform/StartupConfig.java
rename to desktop/src/cn/harryh/arkpets/startup/StartupConfig.java
index 23dd7199..cdcc937b 100644
--- a/core/src/cn/harryh/arkpets/platform/StartupConfig.java
+++ b/desktop/src/cn/harryh/arkpets/startup/StartupConfig.java
@@ -1,7 +1,7 @@
/** Copyright (c) 2022-2024, Harry Huang, Litwak913
* At GPL-3.0 License
*/
-package cn.harryh.arkpets.platform;
+package cn.harryh.arkpets.startup;
import com.sun.jna.Platform;
diff --git a/core/src/cn/harryh/arkpets/platform/WindowsStartupConfig.java b/desktop/src/cn/harryh/arkpets/startup/WindowsStartupConfig.java
similarity index 99%
rename from core/src/cn/harryh/arkpets/platform/WindowsStartupConfig.java
rename to desktop/src/cn/harryh/arkpets/startup/WindowsStartupConfig.java
index 419bf125..181f451e 100644
--- a/core/src/cn/harryh/arkpets/platform/WindowsStartupConfig.java
+++ b/desktop/src/cn/harryh/arkpets/startup/WindowsStartupConfig.java
@@ -1,7 +1,7 @@
/** Copyright (c) 2022-2024, Harry Huang, Litwak913
* At GPL-3.0 License
*/
-package cn.harryh.arkpets.platform;
+package cn.harryh.arkpets.startup;
import cn.harryh.arkpets.utils.IOUtils;
import cn.harryh.arkpets.utils.Logger;
diff --git a/desktop/src/cn/harryh/arkpets/utils/NVAPIWrapper.java b/desktop/src/cn/harryh/arkpets/utils/NVAPIWrapper.java
new file mode 100644
index 00000000..3e69c0ec
--- /dev/null
+++ b/desktop/src/cn/harryh/arkpets/utils/NVAPIWrapper.java
@@ -0,0 +1,194 @@
+package cn.harryh.arkpets.utils;
+
+import com.sun.jna.*;
+import com.sun.jna.ptr.PointerByReference;
+
+
+public class NVAPIWrapper {
+ public static void NvAPI_Initialize() {
+ checkStatus(getFunction(0x150E828).invokeInt(new Object[]{}));
+ }
+
+ public static void NvAPI_DRS_CreateSession(PointerByReference phSession) {
+ checkStatus(getFunction(0x694D52E).invokeInt(new Object[]{phSession}));
+ }
+
+ public static void NvAPI_DRS_DestroySession(Pointer hSession) {
+ checkStatus(getFunction(0xDAD9CFF8).invokeInt(new Object[]{hSession}));
+ }
+
+ public static void NvAPI_DRS_LoadSettings(Pointer hSession) {
+ checkStatus(getFunction(0x375DBD6B).invokeInt(new Object[]{hSession}));
+ }
+
+ public static void NvAPI_DRS_SaveSettings(Pointer hSession) {
+ checkStatus(getFunction(0xFCBC7E14).invokeInt(new Object[]{hSession}));
+ }
+
+ public static void NvAPI_DRS_FindProfileByName(Pointer hSession, WString profileName, PointerByReference phProfile) {
+ checkStatus(getFunction(0x7E4A9A0B).invokeInt(new Object[]{hSession, profileName, phProfile}));
+ }
+
+ public static void NvAPI_Unload() {
+ checkStatus(getFunction(0xD22BDD7E).invokeInt(new Object[]{}));
+ }
+
+ public static void NvAPI_DRS_CreateApplication(Pointer hSession, Pointer hProfile, NVDRS_APPLICATION.ByReference pApplication) {
+ checkStatus(getFunction(0x4347A9DE).invokeInt(new Object[]{hSession, hProfile, pApplication}));
+ }
+
+ public static void NvAPI_DRS_CreateProfile(Pointer hSession, NVDRS_PROFILE.ByReference pProfileInfo, PointerByReference phProfile) {
+ checkStatus(getFunction(0xCC176068).invokeInt(new Object[]{hSession, pProfileInfo, phProfile}));
+ }
+
+ public static void NvAPI_DRS_SetSetting(Pointer hSession, Pointer hProfile, NVDRS_SETTING.ByReference setting) {
+ checkStatus(getFunction(0x577DD202).invokeInt(new Object[]{hSession, hProfile, setting}));
+ }
+
+ public static void NvAPI_DRS_DeleteProfile(Pointer hSession, Pointer hProfile) {
+ checkStatus(getFunction(0x17093206).invokeInt(new Object[]{hSession, hProfile}));
+ }
+
+
+ @Structure.FieldOrder({"version", "settingName", "settingId", "settingType", "settingLocation", "isCurrentPredefined", "isPredefinedValid", "predefinedValue", "currentValue"})
+ public static class NVDRS_SETTING extends Structure {
+ public NativeLong version;
+ public short[] settingName = new short[2048];
+ public NativeLong settingId;
+ public int settingType;
+ public int settingLocation;
+ public NativeLong isCurrentPredefined;
+ public NativeLong isPredefinedValid;
+ public DrsSettingValue predefinedValue;
+ public DrsSettingValue currentValue;
+
+ public NVDRS_SETTING() {
+ super();
+ version = new NativeLong((size() | ((1) << 16)));
+ }
+
+ @Override
+ public void read() {
+ super.read();
+ if (settingType == 0) {
+ predefinedValue.setType(NativeLong.class);
+ currentValue.setType(NativeLong.class);
+ } else if (settingType == 1) {
+ predefinedValue.setType(NVDRS_BINARY_SETTING.class);
+ currentValue.setType(NVDRS_BINARY_SETTING.class);
+ } else if (settingType == 3 || settingType == 4) {
+ predefinedValue.setType(Short[].class);
+ currentValue.setType(Short[].class);
+ }
+ predefinedValue.read();
+ currentValue.read();
+ }
+
+ @Override
+ public void write() {
+ super.write();
+ if (settingType == 0) {
+ predefinedValue.setType(NativeLong.class);
+ currentValue.setType(NativeLong.class);
+ } else if (settingType == 1) {
+ predefinedValue.setType(NVDRS_BINARY_SETTING.class);
+ currentValue.setType(NVDRS_BINARY_SETTING.class);
+ } else if (settingType == 3 || settingType == 4) {
+ predefinedValue.setType(Short.class);
+ currentValue.setType(Short.class);
+ }
+ predefinedValue.write();
+ currentValue.write();
+ }
+
+ public static class ByReference extends NVDRS_SETTING implements Structure.ByReference {
+ }
+ }
+
+ public static class DrsSettingValue extends Union {
+ public NativeLong u32;
+ public NVDRS_BINARY_SETTING binary;
+ public short[] wsz = new short[2048];
+ }
+
+ @Structure.FieldOrder({"valueLength", "valueData"})
+ public static class NVDRS_BINARY_SETTING extends Structure {
+ public NativeLong valueLength;
+ public byte[] valueData = new byte[4096];
+
+ public static class ByReference extends NVDRS_BINARY_SETTING implements Structure.ByReference {
+ }
+ }
+
+ @Structure.FieldOrder({"version", "isPredefined", "appName", "userFriendlyName", "launcher"})
+ public static class NVDRS_APPLICATION extends Structure {
+ public NativeLong version;
+ public NativeLong isPredefined;
+ public short[] appName = new short[2048];
+ public short[] userFriendlyName = new short[2048];
+ public short[] launcher = new short[2048];
+
+ public NVDRS_APPLICATION() {
+ super();
+ version = new NativeLong((size() | ((1) << 16)));
+ }
+
+ public static class ByReference extends NVDRS_APPLICATION implements Structure.ByReference {
+ }
+ }
+
+ @Structure.FieldOrder({"version", "profileName", "gpuSupport", "isPredefined", "numOfApps", "numOfSettings"})
+ public static class NVDRS_PROFILE extends Structure {
+ public NativeLong version;
+ public short[] profileName = new short[2048];
+ public NativeLong gpuSupport;
+
+ public NativeLong isPredefined;
+ public NativeLong numOfApps;
+ public NativeLong numOfSettings;
+
+ public NVDRS_PROFILE() {
+ super();
+ version = new NativeLong((size() | ((1) << 16)));
+ }
+
+ public static class ByReference extends NVDRS_PROFILE implements Structure.ByReference {
+ }
+ }
+
+ private static Function getFunction(int id) {
+ return Function.getFunction(NVAPI.INSTANCE.nvapi_QueryInterface(id));
+ }
+
+ private static void checkStatus(int status) {
+ if (status != 0) {
+ byte[] errmsg = new byte[64];
+ getFunction(0x6C2D048C).invokeInt(new Object[]{status, errmsg});
+ throw new RuntimeException("Failed to execute NVAPI, message: " + Native.toString(errmsg));
+ }
+ }
+
+ private interface NVAPI extends Library {
+ NVAPI INSTANCE = Native.load("nvapi64", NVAPI.class);
+
+ Pointer nvapi_QueryInterface(int id);
+ }
+
+ public static String shortArrayToString(short[] array) {
+ StringBuilder sb = new StringBuilder();
+ for (short value : array) {
+ if (value == 0) {
+ break;
+ }
+ sb.append((char) value);
+ }
+ return sb.toString();
+ }
+
+ public static void writeStringToShortArray(String str, short[] target) {
+ char[] strarr = str.toCharArray();
+ for (int i = 0; i < strarr.length; i++) {
+ target[i] = (short) strarr[i];
+ }
+ }
+}
diff --git a/docs/CmdLine.md b/docs/CmdLine.md
index fb2b077a..03ff5606 100644
--- a/docs/CmdLine.md
+++ b/docs/CmdLine.md
@@ -13,15 +13,16 @@ ArkPets [--direct-start [--load-lib ] [--enable-snapshot]]
[--quiet|--warn|--info|--debug]
```
-| 选项 | 描述 |
-|:----------------------|:--------------------------------------------------------------------------------------|
-| `--direct-start` | 直接启动桌宠而不打开启动器。(v1.5.1+) |
-| `--quiet` | 以`ERROR`日志等级运行,只记录错误信息。(v2.0.0+) |
-| `--warn` | 以`WARN`日志等级运行,记录日志和警告信息。(v2.0.0+) |
-| `--info` | 以`INFO`日志等级运行,此为默认日志等级。(v2.0.0+) |
-| `--debug` | 以`DEBUG`日志等级运行,记录完整的调试信息。(v2.0.0+) |
-| * `--load-lib ` | *调试时使用* 加载外部库文件(例如 [RenderDoc](https://renderdoc.org)),其中 `` 是文件的绝对路径。(v3.5.0+) |
-| * `--enable-snapshot` | *调试时使用* 自动保存预处理阶段的截图用于调试,并启用手动截图功能(按Shift+b保存截图)。(v3.5.0+) |
+| 选项 | 描述 |
+|:-----------------------------|:--------------------------------------------------------------------------------------|
+| `--direct-start` | 直接启动桌宠而不打开启动器。(v1.5.1+) |
+| `--quiet` | 以`ERROR`日志等级运行,只记录错误信息。(v2.0.0+) |
+| `--warn` | 以`WARN`日志等级运行,记录日志和警告信息。(v2.0.0+) |
+| `--info` | 以`INFO`日志等级运行,此为默认日志等级。(v2.0.0+) |
+| `--debug` | 以`DEBUG`日志等级运行,记录完整的调试信息。(v2.0.0+) |
+| `--ui-style ` | *调试时使用* 切换启动器的 UI 样式。(v4.0.0+) |
+| * `--load-lib ` | *调试时使用* 加载外部库文件(例如 [RenderDoc](https://renderdoc.org)),其中 `` 是文件的绝对路径。(v3.5.0+) |
+| * `--enable-snapshot` | *调试时使用* 自动保存预处理阶段的截图用于调试,并启用手动截图功能(按Shift+b保存截图)。(v3.5.0+) |
上表中,标有 * 的选项需要与`--direct-start` 同时使用。