Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c59ed3ccf | ||
|
|
43d8d96608 | ||
|
|
26a6a7d883 | ||
|
|
2e9c237367 | ||
|
|
99d2816ea7 | ||
|
|
7c3853b0e6 | ||
|
|
53aecf38e0 | ||
|
|
41fab0a781 | ||
|
|
3df35aa373 | ||
|
|
543eaa4f66 | ||
|
|
3a851b7f1a | ||
|
|
089ae1e313 | ||
|
|
bfe5802d02 | ||
|
|
3ec6c97380 | ||
|
|
e376da208d | ||
|
|
3b02c5e4e0 | ||
|
|
1bcc79c415 | ||
|
|
9e2cb25d01 | ||
|
|
7e820ba2a5 | ||
|
|
b079940cd9 | ||
|
|
593ee24429 | ||
|
|
f7a91bf10c | ||
|
|
dad7ac706f | ||
|
|
13c48ce78c | ||
|
|
8ecbba1003 | ||
|
|
8bf6a9ec9d | ||
|
|
64092a4bd4 | ||
|
|
8e6bcd969e | ||
|
|
6777dd7ea7 | ||
|
|
f16885568e | ||
|
|
ab109bcfcc | ||
|
|
17f71eaa21 | ||
|
|
3a294e354a |
No files matched your search
@@ -23,8 +23,10 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
@@ -55,14 +57,25 @@ public final class Launcher extends Application {
|
||||
primaryStage.setResizable(false);
|
||||
primaryStage.setScene(Controllers.getScene());
|
||||
|
||||
thread(() -> {
|
||||
try {
|
||||
UpdateChecker.checkUpdate();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to check for update", e);
|
||||
}
|
||||
UpdateChecker.updateChannelProperty().addListener(observable -> {
|
||||
thread(() -> {
|
||||
try {
|
||||
UpdateChecker.checkUpdate();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to check for update", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
UpdateChecker.updateChannelProperty().bind(Bindings.createStringBinding(() -> {
|
||||
switch (ConfigHolder.config().getUpdateChannel()) {
|
||||
case DEVELOPMENT:
|
||||
return UpdateChecker.CHANNEL_DEV;
|
||||
default:
|
||||
return UpdateChecker.CHANNEL_STABLE;
|
||||
}
|
||||
}, ConfigHolder.config().updateChannelProperty()));
|
||||
|
||||
primaryStage.show();
|
||||
} catch (Throwable e) {
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
|
||||
@@ -21,8 +21,8 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -40,16 +40,22 @@ import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
public final class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("java.net.useSystemProxies", "true");
|
||||
|
||||
checkJavaFX();
|
||||
checkDirectoryPath();
|
||||
checkDSTRootCAX3();
|
||||
checkConfigPermission();
|
||||
|
||||
if (UpdateHandler.processArguments(args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigHolder.init();
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
} catch (IOException e) {
|
||||
showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
|
||||
}
|
||||
|
||||
Launcher.main(args);
|
||||
}
|
||||
|
||||
@@ -92,22 +98,6 @@ public final class Main {
|
||||
showWarningAndContinue(i18n("fatal.missing_dst_root_ca_x3"));
|
||||
}
|
||||
|
||||
private static void checkConfigPermission() {
|
||||
Path config = ConfigHolder.CONFIG_PATH;
|
||||
if (Files.exists(config)) {
|
||||
if (Files.isReadable(config) && Files.isWritable(config)) {
|
||||
// we are able to read & write the existent config
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (Files.isWritable(config.getParent())) {
|
||||
// we are able to create a new config
|
||||
return;
|
||||
}
|
||||
}
|
||||
showErrorAndExit(i18n("fatal.config_access_denied", config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
|
||||
@@ -65,6 +65,8 @@ public final class LauncherHelper {
|
||||
if (account == null)
|
||||
throw new IllegalArgumentException("No account");
|
||||
|
||||
Logging.LOG.info("Launching game version: " + selectedVersion);
|
||||
|
||||
GameRepository repository = profile.getRepository();
|
||||
|
||||
Version version = repository.getResolvedVersion(selectedVersion);
|
||||
@@ -388,15 +390,18 @@ public final class LauncherHelper {
|
||||
logs.removeFirst();
|
||||
|
||||
if (setting.isShowLogs()) {
|
||||
Lang.invoke(() -> {
|
||||
try {
|
||||
latch.await();
|
||||
logWindow.waitForLoaded();
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> logWindow.logLine(log, level));
|
||||
}
|
||||
|
||||
if (!lwjgl && log.contains("LWJGL Version: ") || !detectWindow) {
|
||||
if (!lwjgl && (log.contains("LWJGL Version: ") || !detectWindow)) {
|
||||
lwjgl = true;
|
||||
switch (visibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class Accounts {
|
||||
return type2factory.get(accountType(account));
|
||||
}
|
||||
|
||||
private static String accountId(Account account) {
|
||||
static String accountId(Account account) {
|
||||
return account.getUsername() + ":" + account.getCharacter();
|
||||
}
|
||||
|
||||
@@ -138,20 +138,19 @@ public final class Accounts {
|
||||
accounts.addListener(onInvalidating(Accounts::updateAccountStorages));
|
||||
}
|
||||
|
||||
static Map<Object, Object> getAccountStorage(Account account) {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", accountType(account));
|
||||
return storage;
|
||||
}
|
||||
|
||||
private static void updateAccountStorages() {
|
||||
// don't update the underlying storage before data loading is completed
|
||||
// otherwise it might cause data loss
|
||||
if (!initialized)
|
||||
return;
|
||||
// update storage
|
||||
config().getAccountStorages().setAll(
|
||||
accounts.stream()
|
||||
.map(account -> {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", accountType(account));
|
||||
return storage;
|
||||
})
|
||||
.collect(toList()));
|
||||
config().getAccountStorages().setAll(accounts.stream().map(Accounts::getAccountStorage).collect(toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -149,6 +149,9 @@ public final class Config implements Cloneable, Observable {
|
||||
@SerializedName("authlibInjectorServers")
|
||||
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
|
||||
@SerializedName("updateChannel")
|
||||
private ObjectProperty<EnumUpdateChannel> updateChannel = new SimpleObjectProperty<>(EnumUpdateChannel.STABLE);
|
||||
|
||||
@SerializedName("_version")
|
||||
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
|
||||
|
||||
@@ -435,4 +438,15 @@ public final class Config implements Cloneable, Observable {
|
||||
return authlibInjectorServers;
|
||||
}
|
||||
|
||||
public EnumUpdateChannel getUpdateChannel() {
|
||||
return updateChannel.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EnumUpdateChannel> updateChannelProperty() {
|
||||
return updateChannel;
|
||||
}
|
||||
|
||||
public void setUpdateChannel(EnumUpdateChannel updateChannel) {
|
||||
this.updateChannel.set(updateChannel);
|
||||
}
|
||||
}
|
||||
@@ -29,15 +29,17 @@ import java.util.logging.Level;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
|
||||
public final class ConfigHolder {
|
||||
|
||||
private ConfigHolder() {}
|
||||
|
||||
public static final String CONFIG_FILENAME = "hmcl.json";
|
||||
public static final Path CONFIG_PATH = Paths.get(CONFIG_FILENAME).toAbsolutePath();
|
||||
public static final String CONFIG_FILENAME_LINUX = ".hmcl.json";
|
||||
|
||||
private static Path configLocation;
|
||||
private static Config configInstance;
|
||||
private static boolean newlyCreated;
|
||||
|
||||
@@ -55,35 +57,64 @@ public final class ConfigHolder {
|
||||
return newlyCreated;
|
||||
}
|
||||
|
||||
public synchronized static void init() {
|
||||
public synchronized static void init() throws IOException {
|
||||
if (configInstance != null) {
|
||||
throw new IllegalStateException("Configuration is already loaded");
|
||||
}
|
||||
|
||||
configLocation = locateConfig();
|
||||
configInstance = loadConfig();
|
||||
configInstance.addListener(source -> saveConfig());
|
||||
configInstance.addListener(source -> markConfigDirty());
|
||||
|
||||
Settings.init();
|
||||
|
||||
if (newlyCreated) {
|
||||
saveConfig();
|
||||
|
||||
// hide the config file on windows
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
try {
|
||||
Files.setAttribute(configLocation, "dos:hidden", true);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to set hidden attribute of " + configLocation, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Files.isWritable(configLocation)) {
|
||||
// the config cannot be saved
|
||||
// throw up the error now to prevent further data loss
|
||||
throw new IOException("Config at " + configLocation + " is not writable");
|
||||
}
|
||||
}
|
||||
|
||||
private static Config loadConfig() {
|
||||
if (Files.exists(CONFIG_PATH)) {
|
||||
private static Path locateConfig() {
|
||||
Path config = Paths.get(CONFIG_FILENAME);
|
||||
if (Files.isRegularFile(config))
|
||||
return config;
|
||||
|
||||
Path dotConfig = Paths.get(CONFIG_FILENAME_LINUX);
|
||||
if (Files.isRegularFile(dotConfig))
|
||||
return dotConfig;
|
||||
|
||||
// create new
|
||||
return OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? config : dotConfig;
|
||||
}
|
||||
|
||||
private static Config loadConfig() throws IOException {
|
||||
if (Files.exists(configLocation)) {
|
||||
try {
|
||||
String content = new String(Files.readAllBytes(CONFIG_PATH), UTF_8);
|
||||
String content = new String(Files.readAllBytes(configLocation), UTF_8);
|
||||
Config deserialized = Config.fromJson(content);
|
||||
if (deserialized == null) {
|
||||
LOG.info("Config is empty");
|
||||
} else {
|
||||
Map<?, ?> raw = new Gson().fromJson(content, Map.class);
|
||||
upgradeConfig(deserialized, raw);
|
||||
ConfigUpgrader.upgradeConfig(deserialized, raw);
|
||||
return deserialized;
|
||||
}
|
||||
} catch (IOException | JsonParseException e) {
|
||||
LOG.log(Level.WARNING, "Something went wrong when loading config.", e);
|
||||
} catch (JsonParseException e) {
|
||||
LOG.log(Level.WARNING, "Malformed config.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,30 +123,22 @@ public final class ConfigHolder {
|
||||
return new Config();
|
||||
}
|
||||
|
||||
static void saveConfig() {
|
||||
private static void saveConfig() throws IOException {
|
||||
LOG.info("Saving config");
|
||||
try {
|
||||
Files.write(CONFIG_PATH, configInstance.toJson().getBytes(UTF_8));
|
||||
} catch (IOException ex) {
|
||||
LOG.log(Level.SEVERE, "Failed to save config", ex);
|
||||
Files.write(configLocation, configInstance.toJson().getBytes(UTF_8));
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.SEVERE, "Failed to save config", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is for the compatibility with old HMCL 3.x as well as HMCL 2.x.
|
||||
* @param deserialized deserialized config settings
|
||||
* @param rawJson raw json structure of the config settings without modification
|
||||
* @return {@code deserialized}
|
||||
*/
|
||||
private static void upgradeConfig(Config deserialized, Map<?, ?> rawJson) {
|
||||
// Following is for the compatibility with HMCL 2.x
|
||||
if (!rawJson.containsKey("commonDirType"))
|
||||
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
|
||||
if (!rawJson.containsKey("backgroundType"))
|
||||
deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
|
||||
if (!rawJson.containsKey("hasProxy"))
|
||||
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
|
||||
if (!rawJson.containsKey("hasProxyAuth"))
|
||||
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
|
||||
static void markConfigDirty() {
|
||||
// TODO: change this to async
|
||||
try {
|
||||
saveConfig();
|
||||
} catch (IOException ignored) {
|
||||
// ignore it as it has been logged
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
final class ConfigUpgrader {
|
||||
private ConfigUpgrader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is for the compatibility with old HMCL 3.x as well as HMCL 2.x.
|
||||
*
|
||||
* @param deserialized deserialized config settings
|
||||
* @param rawJson raw json structure of the config settings without modification
|
||||
*/
|
||||
static void upgradeConfig(Config deserialized, Map<?, ?> rawJson) {
|
||||
upgradeV2(deserialized, rawJson);
|
||||
upgradeV3(deserialized, rawJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade configuration of HMCL 2.x
|
||||
*
|
||||
* @param deserialized deserialized config settings
|
||||
* @param rawJson raw json structure of the config settings without modification
|
||||
*/
|
||||
private static void upgradeV2(Config deserialized, Map<?, ?> rawJson) {
|
||||
// Convert OfflineAccounts whose stored uuid is important.
|
||||
tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> {
|
||||
tryCast(auth.get("offline"), Map.class).ifPresent(offline -> {
|
||||
List<OfflineAccount> accounts = new LinkedList<>();
|
||||
tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
|
||||
((Map<?, ?>) uuidMap).forEach((key, value) -> {
|
||||
OfflineAccount account = OfflineAccountFactory.INSTANCE.create((String) key, UUIDTypeAdapter.fromString((String) value));
|
||||
accounts.add(account);
|
||||
deserialized.getAccountStorages().add(Accounts.getAccountStorage(account));
|
||||
});
|
||||
});
|
||||
|
||||
tryCast(offline.get("IAuthenticator_UserName"), String.class).ifPresent(selected -> {
|
||||
if (!rawJson.containsKey("selectedAccount"))
|
||||
accounts.stream().filter(account -> selected.equals(account.getUsername())).findAny().ifPresent(account ->
|
||||
deserialized.setSelectedAccount(Accounts.accountId(account)));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade configuration of HMCL earlier than 3.1.70
|
||||
*
|
||||
* @param deserialized deserialized config settings
|
||||
* @param rawJson raw json structure of the config settings without modification
|
||||
*/
|
||||
private static void upgradeV3(Config deserialized, Map<?, ?> rawJson) {
|
||||
if (!rawJson.containsKey("commonDirType"))
|
||||
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
|
||||
if (!rawJson.containsKey("backgroundType"))
|
||||
deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
|
||||
if (!rawJson.containsKey("hasProxy"))
|
||||
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
|
||||
if (!rawJson.containsKey("hasProxyAuth"))
|
||||
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
public enum EnumUpdateChannel {
|
||||
STABLE,
|
||||
DEVELOPMENT
|
||||
}
|
||||
@@ -66,7 +66,7 @@ public final class Profile {
|
||||
return globalProperty.get();
|
||||
}
|
||||
|
||||
public void setGlobal(VersionSetting global) {
|
||||
private void setGlobal(VersionSetting global) {
|
||||
if (global == null)
|
||||
global = new VersionSetting();
|
||||
globalProperty.set(global);
|
||||
@@ -175,6 +175,7 @@ public final class Profile {
|
||||
globalProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
useRelativePathProperty.addListener(listener);
|
||||
globalProperty.get().addPropertyChangedListener(listener);
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||
|
||||
@@ -58,7 +58,7 @@ public class Settings {
|
||||
for (Map.Entry<String, Profile> profileEntry : getProfileMap().entrySet()) {
|
||||
profileEntry.getValue().setName(profileEntry.getKey());
|
||||
profileEntry.getValue().nameProperty().setChangedListener(this::profileNameChanged);
|
||||
profileEntry.getValue().addPropertyChangedListener(e -> ConfigHolder.saveConfig());
|
||||
profileEntry.getValue().addPropertyChangedListener(e -> ConfigHolder.markConfigDirty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,8 +88,6 @@ public class Theme {
|
||||
}
|
||||
|
||||
return new String[]{
|
||||
Theme.class.getResource("/css/jfoenix-fonts.css").toExternalForm(),
|
||||
Theme.class.getResource("/css/jfoenix-design.css").toExternalForm(),
|
||||
css,
|
||||
Theme.class.getResource("/assets/css/root.css").toExternalForm()
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
|
||||
@@ -49,7 +50,7 @@ import java.util.Optional;
|
||||
public class AccountPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title;
|
||||
private final ObjectProperty<Runnable> onDelete = new SimpleObjectProperty<>(this, "onDelete");
|
||||
private final VersionListItem item;
|
||||
private final AdvancedListItem item;
|
||||
private final Account account;
|
||||
|
||||
@FXML
|
||||
@@ -73,7 +74,7 @@ public class AccountPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private JFXProgressBar progressBar;
|
||||
|
||||
public AccountPage(Account account, VersionListItem item) {
|
||||
public AccountPage(Account account, AdvancedListItem item) {
|
||||
this.account = account;
|
||||
this.item = item;
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
@@ -163,15 +162,6 @@ public final class Controllers {
|
||||
return pane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link DialogCloseEvent}
|
||||
*/
|
||||
public static void closeDialog(Region content) {
|
||||
if (stage == null) // shut down
|
||||
return;
|
||||
decorator.closeDialog(content);
|
||||
}
|
||||
|
||||
public static void navigate(Node node) {
|
||||
if (decorator.getNowPage() == node)
|
||||
decorator.showPage(null);
|
||||
|
||||
@@ -171,14 +171,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
|
||||
onCloseButtonAction = new SimpleObjectProperty<>(this, "onCloseButtonAction", Launcher::stopApplication);
|
||||
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case OSX:
|
||||
titleContainer.setRight(null);
|
||||
break;
|
||||
default:
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
break;
|
||||
}
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
btnClose.setGraphic(close);
|
||||
btnMin.setGraphic(minus);
|
||||
btnMax.setGraphic(resizeMax);
|
||||
@@ -310,11 +303,22 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private boolean isMaximized() {
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case OSX:
|
||||
Rectangle2D bounds = Screen.getPrimary().getVisualBounds();
|
||||
return primaryStage.getWidth() >= bounds.getWidth() && primaryStage.getHeight() >= bounds.getHeight();
|
||||
default:
|
||||
return primaryStage.isMaximized();
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
@FXML
|
||||
private void onMouseMoved(MouseEvent mouseEvent) {
|
||||
if (!primaryStage.isMaximized() && !primaryStage.isFullScreen() && !maximized) {
|
||||
if (!isMaximized() && !primaryStage.isFullScreen() && !maximized) {
|
||||
if (!primaryStage.isResizable())
|
||||
updateInitMouseValues(mouseEvent);
|
||||
else {
|
||||
@@ -363,7 +367,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
private void onMouseDragged(MouseEvent mouseEvent) {
|
||||
this.isDragging = true;
|
||||
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
|
||||
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress() && !this.primaryStage.isMaximized() && !this.maximized) {
|
||||
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress() && !isMaximized() && !this.maximized) {
|
||||
this.newX = mouseEvent.getScreenX();
|
||||
this.newY = mouseEvent.getScreenY();
|
||||
double deltaX = this.newX - this.initX;
|
||||
@@ -611,18 +615,20 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void closeDialog(Node node) {
|
||||
private void closeDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
dialogPane.pop(node);
|
||||
if (dialog != null) {
|
||||
dialogPane.pop(node);
|
||||
|
||||
if (dialogPane.getChildren().isEmpty()) {
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
if (dialogPane.getChildren().isEmpty()) {
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,11 +49,7 @@ import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.ClassTitle;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
@@ -127,7 +123,7 @@ public final class LeftPaneController {
|
||||
|
||||
// ==== Accounts ====
|
||||
// Missing account item
|
||||
VersionListItem missingAccountItem = new VersionListItem(i18n("account.missing"), i18n("message.unknown"));
|
||||
AdvancedListItem missingAccountItem = new AdvancedListItem(i18n("account.missing"), i18n("message.unknown"));
|
||||
RipplerContainer missingAccountRippler = new RipplerContainer(missingAccountItem);
|
||||
missingAccountItem.setOnSettingsButtonClicked(e -> addNewAccount());
|
||||
missingAccountRippler.setOnMouseClicked(e -> addNewAccount());
|
||||
@@ -162,7 +158,7 @@ public final class LeftPaneController {
|
||||
}
|
||||
|
||||
private RipplerContainer createAccountItem(Account account) {
|
||||
VersionListItem item = new VersionListItem(account.getCharacter(), accountSubtitle(account));
|
||||
AdvancedListItem item = new AdvancedListItem(account.getCharacter(), accountSubtitle(account));
|
||||
RipplerContainer rippler = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
AccountPage accountPage = new AccountPage(account, item);
|
||||
@@ -219,7 +215,7 @@ public final class LeftPaneController {
|
||||
if (node instanceof RipplerContainer && node.getProperties().get("profile") instanceof String) {
|
||||
boolean current = Objects.equals(node.getProperties().get("profile"), profile.getName());
|
||||
((RipplerContainer) node).setSelected(current);
|
||||
((VersionListItem) ((RipplerContainer) node).getContainer()).setGameVersion(current ? i18n("profile.selected") : "");
|
||||
((AdvancedListItem) ((RipplerContainer) node).getContainer()).setSubtitle(current ? i18n("profile.selected") : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -228,7 +224,7 @@ public final class LeftPaneController {
|
||||
private void onProfilesLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
for (Profile profile : Settings.instance().getProfiles()) {
|
||||
VersionListItem item = new VersionListItem(Profiles.getProfileDisplayName(profile));
|
||||
AdvancedListItem item = new AdvancedListItem(Profiles.getProfileDisplayName(profile));
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> Controllers.getDecorator().showPage(new ProfilePage(profile)));
|
||||
ripplerContainer.setOnMouseClicked(e -> Settings.instance().setSelectedProfile(profile));
|
||||
|
||||
@@ -26,6 +26,8 @@ import javafx.scene.layout.BorderPane;
|
||||
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
@@ -54,7 +56,14 @@ public final class ModItem extends BorderPane {
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
modItem.setTitle(info.getFileName());
|
||||
modItem.setSubtitle(info.getName() + ", " + i18n("archive.version") + ": " + info.getVersion() + ", " + i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + i18n("archive.author") + ": " + info.getAuthors());
|
||||
StringBuilder message = new StringBuilder(info.getName());
|
||||
if (StringUtils.isNotBlank(info.getVersion()))
|
||||
message.append(", ").append(i18n("archive.version")).append(": ").append(info.getVersion());
|
||||
if (StringUtils.isNotBlank(info.getGameVersion()))
|
||||
message.append(", ").append(i18n("archive.game_version")).append(": ").append(info.getGameVersion());
|
||||
if (StringUtils.isNotBlank(info.getAuthors()))
|
||||
message.append(", ").append(i18n("archive.author")).append(": ").append(info.getAuthors());
|
||||
modItem.setSubtitle(message.toString());
|
||||
chkEnabled.setSelected(info.isActive());
|
||||
chkEnabled.selectedProperty().addListener((a, b, newValue) ->
|
||||
info.activeProperty().set(newValue));
|
||||
|
||||
@@ -39,6 +39,7 @@ import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.Text;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.FontComboBox;
|
||||
@@ -88,6 +89,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private Label lblUpdateSub;
|
||||
@FXML
|
||||
private Text lblUpdateNote;
|
||||
@FXML
|
||||
private JFXRadioButton chkUpdateStable;
|
||||
@FXML
|
||||
private JFXRadioButton chkUpdateDev;
|
||||
@FXML
|
||||
private JFXButton btnUpdate;
|
||||
@FXML
|
||||
private ScrollPane scroll;
|
||||
@@ -96,7 +103,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private StackPane themeColorPickerContainer;
|
||||
@FXML
|
||||
private JFXCheckBox chkEnableProxy;
|
||||
private JFXCheckBox chkDisableProxy;
|
||||
@FXML
|
||||
private JFXRadioButton chkProxyHttp;
|
||||
@FXML
|
||||
@@ -153,10 +160,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
txtProxyUsername.textProperty().bindBidirectional(config().proxyUserProperty());
|
||||
txtProxyPassword.textProperty().bindBidirectional(config().proxyPassProperty());
|
||||
|
||||
proxyPane.disableProperty().bind(chkEnableProxy.selectedProperty().not());
|
||||
proxyPane.disableProperty().bind(chkDisableProxy.selectedProperty());
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
|
||||
chkEnableProxy.selectedProperty().bindBidirectional(config().hasProxyProperty());
|
||||
chkDisableProxy.setSelected(!config().hasProxy());
|
||||
chkDisableProxy.selectedProperty().addListener(o -> config().setHasProxy(!chkDisableProxy.isSelected()));
|
||||
config().hasProxyProperty().addListener(o -> chkDisableProxy.setSelected(!config().hasProxy()));
|
||||
chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());
|
||||
|
||||
selectedProxyType = new SimpleObjectProperty<Proxy.Type>(Proxy.Type.HTTP) {
|
||||
@@ -212,6 +221,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
|
||||
lblUpdate.setText(i18n("update.found"));
|
||||
lblUpdate.getStyleClass().setAll("update-label");
|
||||
} else if (UpdateChecker.isCheckingUpdate()) {
|
||||
lblUpdateSub.setText(i18n("update.checking"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll();
|
||||
} else {
|
||||
lblUpdateSub.setText(i18n("update.latest"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
@@ -222,7 +237,32 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
};
|
||||
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
updateListener.invalidated(null);
|
||||
|
||||
lblUpdateNote.setWrappingWidth(470);
|
||||
|
||||
ObjectProperty<EnumUpdateChannel> updateChannel = new SimpleObjectProperty<EnumUpdateChannel>() {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
EnumUpdateChannel updateChannel = Objects.requireNonNull(get());
|
||||
chkUpdateDev.setSelected(updateChannel == EnumUpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setSelected(updateChannel == EnumUpdateChannel.STABLE);
|
||||
}
|
||||
};
|
||||
|
||||
ToggleGroup updateChannelGroup = new ToggleGroup();
|
||||
chkUpdateDev.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateDev.setUserData(EnumUpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateStable.setUserData(EnumUpdateChannel.STABLE);
|
||||
updateChannelGroup.getToggles().forEach(
|
||||
toggle -> toggle.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
updateChannel.set((EnumUpdateChannel) toggle.getUserData());
|
||||
}
|
||||
}));
|
||||
updateChannel.bindBidirectional(ConfigHolder.config().updateChannelProperty());
|
||||
// ====
|
||||
|
||||
// ==== Background ====
|
||||
|
||||
@@ -147,7 +147,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
sub = "saves";
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
return;
|
||||
}
|
||||
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
|
||||
}
|
||||
|
||||
+15
-14
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.event.EventHandler;
|
||||
@@ -26,27 +26,28 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public final class VersionListItem extends StackPane {
|
||||
public final class AdvancedListItem extends StackPane {
|
||||
@FXML
|
||||
private StackPane imageViewContainer;
|
||||
@FXML
|
||||
private Label lblVersionName;
|
||||
private Label lblTitle;
|
||||
@FXML
|
||||
private Label lblGameVersion;
|
||||
private Label lblSubtitle;
|
||||
@FXML
|
||||
private ImageView imageView;
|
||||
@FXML private JFXButton btnSettings;
|
||||
|
||||
public VersionListItem(String versionName) {
|
||||
this(versionName, "");
|
||||
public AdvancedListItem(String title) {
|
||||
this(title, "");
|
||||
}
|
||||
|
||||
public VersionListItem(String versionName, String gameVersion) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version-list-item.fxml");
|
||||
public AdvancedListItem(String title, String subtitle) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/advanced-list-item.fxml");
|
||||
|
||||
lblVersionName.setText(versionName);
|
||||
lblGameVersion.setText(gameVersion);
|
||||
lblTitle.setText(title);
|
||||
lblSubtitle.setText(subtitle);
|
||||
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
}
|
||||
@@ -55,12 +56,12 @@ public final class VersionListItem extends StackPane {
|
||||
btnSettings.setOnMouseClicked(handler);
|
||||
}
|
||||
|
||||
public void setVersionName(String versionName) {
|
||||
lblVersionName.setText(versionName);
|
||||
public void setTitle(String title) {
|
||||
lblTitle.setText(title);
|
||||
}
|
||||
|
||||
public void setGameVersion(String gameVersion) {
|
||||
lblGameVersion.setText(gameVersion);
|
||||
public void setSubtitle(String subtitle) {
|
||||
lblSubtitle.setText(subtitle);
|
||||
}
|
||||
|
||||
public void setImage(Image image, Rectangle2D viewport) {
|
||||
@@ -50,10 +50,6 @@ public class ComponentList extends StackPane {
|
||||
}
|
||||
|
||||
public void addChildren(Node node) {
|
||||
if (node instanceof ComponentList) {
|
||||
node.getProperties().put("title", ((ComponentList) node).getTitle());
|
||||
node.getProperties().put("subtitle", ((ComponentList) node).getSubtitle());
|
||||
}
|
||||
StackPane child = new StackPane();
|
||||
child.getChildren().add(new ComponentListCell(node));
|
||||
if (vbox.getChildren().isEmpty())
|
||||
|
||||
@@ -27,6 +27,8 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
@@ -75,31 +77,47 @@ public class ComponentListCell extends StackPane {
|
||||
content.getStyleClass().remove("options-list");
|
||||
content.getStyleClass().add("options-sublist");
|
||||
|
||||
StackPane groupNode = new StackPane();
|
||||
BorderPane groupNode = new BorderPane();
|
||||
groupNode.getStyleClass().add("options-list-item-header");
|
||||
|
||||
Node expandIcon = SVG.expand(Theme.blackFillBinding(), 10, 10);
|
||||
JFXButton expandButton = new JFXButton();
|
||||
expandButton.setGraphic(expandIcon);
|
||||
expandButton.getStyleClass().add("options-list-item-expand-button");
|
||||
StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT);
|
||||
|
||||
VBox labelVBox = new VBox();
|
||||
Label label = new Label();
|
||||
label.textProperty().bind(list.titleProperty());
|
||||
label.setMouseTransparent(true);
|
||||
labelVBox.getChildren().add(label);
|
||||
|
||||
if (list.isHasSubtitle()) {
|
||||
Label subtitleLabel = new Label();
|
||||
subtitleLabel.textProperty().bind(list.subtitleProperty());
|
||||
subtitleLabel.setMouseTransparent(true);
|
||||
subtitleLabel.getStyleClass().add("subtitle-label");
|
||||
labelVBox.getChildren().add(subtitleLabel);
|
||||
if (list instanceof ComponentSublist) {
|
||||
Node leftNode = ((ComponentSublist) list).getHeaderLeft();
|
||||
if (leftNode != null)
|
||||
labelVBox.getChildren().setAll(leftNode);
|
||||
} else {
|
||||
Label label = new Label();
|
||||
label.textProperty().bind(list.titleProperty());
|
||||
label.setMouseTransparent(true);
|
||||
labelVBox.getChildren().add(label);
|
||||
|
||||
if (list.isHasSubtitle()) {
|
||||
Label subtitleLabel = new Label();
|
||||
subtitleLabel.textProperty().bind(list.subtitleProperty());
|
||||
subtitleLabel.setMouseTransparent(true);
|
||||
subtitleLabel.getStyleClass().add("subtitle-label");
|
||||
labelVBox.getChildren().add(subtitleLabel);
|
||||
}
|
||||
}
|
||||
|
||||
StackPane.setAlignment(labelVBox, Pos.CENTER_LEFT);
|
||||
groupNode.getChildren().setAll(labelVBox, expandButton);
|
||||
groupNode.setLeft(labelVBox);
|
||||
|
||||
HBox right = new HBox();
|
||||
if (list instanceof ComponentSublist) {
|
||||
Node rightNode = ((ComponentSublist) list).getHeaderRight();
|
||||
if (rightNode != null)
|
||||
right.getChildren().add(rightNode);
|
||||
}
|
||||
right.getChildren().add(expandButton);
|
||||
groupNode.setRight(right);
|
||||
labelVBox.setAlignment(Pos.CENTER_LEFT);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
VBox container = new VBox();
|
||||
container.setStyle("-fx-padding: 8 0 0 0;");
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Node;
|
||||
|
||||
@DefaultProperty("content")
|
||||
public final class ComponentSublist extends ComponentList {
|
||||
|
||||
private final ObjectProperty<Node> headerLeft = new SimpleObjectProperty<>(this, "headerLeft");
|
||||
private final ObjectProperty<Node> headerRight = new SimpleObjectProperty<>(this, "headerRight");
|
||||
|
||||
public ComponentSublist() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Node getHeaderLeft() {
|
||||
return headerLeft.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> headerLeftProperty() {
|
||||
return headerLeft;
|
||||
}
|
||||
|
||||
public void setHeaderLeft(Node headerLeft) {
|
||||
this.headerLeft.set(headerLeft);
|
||||
}
|
||||
|
||||
public Node getHeaderRight() {
|
||||
return headerRight.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> headerRightProperty() {
|
||||
return headerRight;
|
||||
}
|
||||
|
||||
public void setHeaderRight(Node headerRight) {
|
||||
this.headerRight.set(headerRight);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.JarUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
@@ -28,6 +30,8 @@ import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
@@ -44,6 +48,7 @@ final class LocalRepository {
|
||||
private LocalRepository() {}
|
||||
|
||||
private static Path localStorage = Launcher.HMCL_DIRECTORY.toPath().resolve("hmcl.jar");
|
||||
private static Path hmclVersionJson = Launcher.HMCL_DIRECTORY.toPath().resolve("hmclver.json");
|
||||
|
||||
/**
|
||||
* Gets the current stored executable in local repository.
|
||||
@@ -65,6 +70,14 @@ final class LocalRepository {
|
||||
} else {
|
||||
Files.copy(source, localStorage, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
Optional<LocalVersion> stored = getStored();
|
||||
if (stored.isPresent()) {
|
||||
Map<String, String> json = new HashMap<>();
|
||||
json.put("ver", stored.get().getVersion());
|
||||
json.put("loc", stored.get().getLocation().toString());
|
||||
FileUtils.writeText(hmclVersionJson.toFile(), Constants.GSON.toJson(json));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,17 +23,15 @@ import static org.jackhuang.hmcl.util.VersionNumber.asVersion;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.beans.property.*;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.ImmediateStringProperty;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
|
||||
public final class UpdateChecker {
|
||||
@@ -42,7 +40,7 @@ public final class UpdateChecker {
|
||||
public static final String CHANNEL_STABLE = "stable";
|
||||
public static final String CHANNEL_DEV = "dev";
|
||||
|
||||
private static StringProperty updateChannel = new SimpleStringProperty(CHANNEL_STABLE);
|
||||
private static StringProperty updateChannel = new ImmediateStringProperty(null, "updateChannel", CHANNEL_STABLE);
|
||||
|
||||
private static ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
|
||||
private static BooleanBinding outdated = Bindings.createBooleanBinding(
|
||||
@@ -55,6 +53,7 @@ public final class UpdateChecker {
|
||||
}
|
||||
},
|
||||
latestVersion);
|
||||
private static ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
public static String getUpdateChannel() {
|
||||
return updateChannel.get();
|
||||
@@ -84,22 +83,39 @@ public final class UpdateChecker {
|
||||
return outdated;
|
||||
}
|
||||
|
||||
public static boolean isCheckingUpdate() {
|
||||
return checkingUpdate.get();
|
||||
}
|
||||
|
||||
public static ReadOnlyBooleanProperty checkingUpdateProperty() {
|
||||
return checkingUpdate.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static void checkUpdate() throws IOException {
|
||||
if (!IntegrityChecker.isSelfVerified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String channel = getUpdateChannel();
|
||||
String url = NetworkUtils.withQuery(Metadata.UPDATE_URL, mapOf(
|
||||
pair("version", Metadata.VERSION),
|
||||
pair("channel", channel)));
|
||||
|
||||
RemoteVersion fetched = RemoteVersion.fetch(url);
|
||||
Platform.runLater(() -> {
|
||||
if (channel.equals(getUpdateChannel())) {
|
||||
latestVersion.set(fetched);
|
||||
}
|
||||
JFXUtilities.runInFXAndWait(() -> {
|
||||
checkingUpdate.set(true);
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
String channel = getUpdateChannel();
|
||||
String url = NetworkUtils.withQuery(Metadata.UPDATE_URL, mapOf(
|
||||
pair("version", Metadata.VERSION),
|
||||
pair("channel", channel)));
|
||||
|
||||
RemoteVersion fetched = RemoteVersion.fetch(url);
|
||||
Platform.runLater(() -> {
|
||||
if (channel.equals(getUpdateChannel())) {
|
||||
latestVersion.set(fetched);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
Platform.runLater(() -> checkingUpdate.set(false));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDevelopmentVersion(String version) {
|
||||
|
||||
Binary file not shown.
@@ -1,11 +1,4 @@
|
||||
/*
|
||||
* Apply google Roboto font to all UI components
|
||||
* #5264AE
|
||||
*/
|
||||
|
||||
.root {
|
||||
-fx-base-font-family: "微软雅黑", "Microsoft YaHei", "Roboto";
|
||||
-fx-font-family: -fx-base-font-family;
|
||||
}
|
||||
|
||||
.disabled Label {
|
||||
|
||||
+2
-2
@@ -21,10 +21,10 @@
|
||||
</StackPane>
|
||||
<BorderPane style="-fx-padding: 0 0 0 10;">
|
||||
<top>
|
||||
<Label fx:id="lblVersionName" maxWidth="90" style="-fx-font-size: 15;" textAlignment="JUSTIFY" />
|
||||
<Label fx:id="lblTitle" maxWidth="90" style="-fx-font-size: 15;" textAlignment="JUSTIFY" />
|
||||
</top>
|
||||
<bottom>
|
||||
<Label fx:id="lblGameVersion" maxWidth="90" style="-fx-font-size: 10;" textAlignment="JUSTIFY" />
|
||||
<Label fx:id="lblSubtitle" maxWidth="90" style="-fx-font-size: 10;" textAlignment="JUSTIFY" />
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
</HBox>
|
||||
@@ -6,6 +6,7 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.*?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
@@ -15,21 +16,28 @@
|
||||
<VBox fx:id="rootPane" style="-fx-padding: 20;">
|
||||
<ComponentList fx:id="settingsPane">
|
||||
|
||||
<BorderPane> <!-- Update -->
|
||||
<left>
|
||||
<ComponentSublist fx:id="updatePane" title="%update" hasSubtitle="true">
|
||||
<headerLeft>
|
||||
<VBox>
|
||||
<Label fx:id="lblUpdate" text="%update" BorderPane.alignment="CENTER_LEFT"/>
|
||||
<Label fx:id="lblUpdateSub" styleClass="subtitle-label"/>
|
||||
</VBox>
|
||||
</left>
|
||||
<right>
|
||||
</headerLeft>
|
||||
<headerRight>
|
||||
<JFXButton fx:id="btnUpdate" onMouseClicked="#onUpdate" styleClass="toggle-icon4">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/update.fxml" />
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</headerRight>
|
||||
<VBox spacing="8">
|
||||
<JFXRadioButton fx:id="chkUpdateStable" text="%update.channel.stable" />
|
||||
<JFXRadioButton fx:id="chkUpdateDev" text="%update.channel.dev" />
|
||||
<VBox style="-fx-padding: 10 0 0 0;">
|
||||
<Text fx:id="lblUpdateNote" text="%update.note" />
|
||||
</VBox>
|
||||
</VBox>
|
||||
</ComponentSublist>
|
||||
|
||||
<MultiFileItem fx:id="fileCommonLocation" title="%launcher.common_directory" directory="true" chooserTitle="%launcher.common_directory.choose" hasSubtitle="true" customText="%settings.custom" />
|
||||
|
||||
@@ -55,7 +63,7 @@
|
||||
|
||||
<ComponentList title="%settings.launcher.proxy"> <!-- proxy -->
|
||||
<VBox spacing="10">
|
||||
<JFXCheckBox fx:id="chkEnableProxy" text="%settings.launcher.proxy.enable" />
|
||||
<JFXCheckBox fx:id="chkDisableProxy" text="%settings.launcher.proxy.disable" />
|
||||
<VBox fx:id="proxyPane" style="-fx-padding: 0 0 0 30;">
|
||||
<HBox>
|
||||
<JFXRadioButton fx:id="chkProxyHttp" text="%settings.launcher.proxy.http" />
|
||||
|
||||
@@ -93,7 +93,7 @@ extension.sh=Bash shell
|
||||
|
||||
fatal.missing_javafx=JavaFX is missing.\nIf you are using Java 11 or later, please downgrade to Java 8 or 10.\nIf you are using OpenJDK, please ensure OpenJFX is included.
|
||||
fatal.missing_dst_root_ca_x3=The DST Root CA X3 certificate is missing on the current Java platform.\nYou can still use HMCL, but HMCL will be unable to connect to some sites (such as sites that use certificates issued by Let's Encrypt), which may cause HMCL not to function properly.\nPlease upgrade your Java to 8u101 or later to resolve the problem.
|
||||
fatal.config_access_denied=The configuration at "%s" is not readable or writable.\nPlease grant HMCL read and write access to this file and its parent directory.
|
||||
fatal.config_loading_failure=The configuration is not accessible.\nPlease ensure HMCL has read and write access to "%s" and the files in it.
|
||||
fatal.migration_requires_manual_reboot=The upgrade is about to be completed. Please reopen HMCL.
|
||||
fatal.apply_update_failure=We're sorry that HMCL couldn't finish the upgrade because something went wrong.\nBut you can still manually finish the upgrade by downloading HMCL from %s.\nPlease consider reporting this issue to us.
|
||||
|
||||
@@ -293,7 +293,7 @@ settings.launcher.language=Language
|
||||
settings.launcher.log_font=Log Font
|
||||
settings.launcher.proxy=Proxy
|
||||
settings.launcher.proxy.authentication=Proxy Authentication
|
||||
settings.launcher.proxy.enable=Enable Proxy
|
||||
settings.launcher.proxy.disable=Use system proxies
|
||||
settings.launcher.proxy.host=Host
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=Password
|
||||
@@ -311,9 +311,13 @@ settings.type=Version setting type
|
||||
settings.type.special=Specialized version settings(will not affect other versions)
|
||||
|
||||
update=Update
|
||||
update.channel.dev=Update to development version
|
||||
update.channel.stable=Update to stable version
|
||||
update.checking=Checking for updates
|
||||
update.failed=Failed to perform upgrade
|
||||
update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
update.note=Development version contains more functionality and bug fixes as well as more possible bugs. And this will affect all HMCL installations in your computer.
|
||||
update.latest=This is latest Version.
|
||||
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
|
||||
update.tooltip=Update
|
||||
|
||||
@@ -93,7 +93,7 @@ extension.sh=Bash 腳本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,請降級到 Java 8 或 10。\n如果您使用的是 OpenJDK,請確保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=當前 Java 平台缺少 DST Root CA X3 證書。\n您依然可以使用 HMCL,但將無法連接到部分站點(如使用 Let's Encrypt 證書的站點),這可能會使 HMCL 無法正常工作。\n請將您的 Java 升級到 8u101 以上以解決此問題。
|
||||
fatal.config_access_denied=HMCL 無法讀取或寫入位於 "%s" 的配置文件。\n請授予 HMCL 對該文件及其父目錄的讀寫權限。
|
||||
fatal.config_loading_failure=HMCL 無法加載配置文件。\n請確保 HMCL 對 "%s" 目錄及該目錄下的文件擁有讀寫權限。
|
||||
fatal.migration_requires_manual_reboot=HMCL 即將完成升級,請重新打開 HMCL。
|
||||
fatal.apply_update_failure=我們很抱歉 HMCL 無法自動完成升級,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。\n請考慮向我們反饋該問題。
|
||||
|
||||
@@ -293,7 +293,7 @@ settings.launcher.language=語言
|
||||
settings.launcher.log_font=日誌字體
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=身份驗證
|
||||
settings.launcher.proxy.enable=启用代理
|
||||
settings.launcher.proxy.disable=使用系統代理
|
||||
settings.launcher.proxy.host=主機
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=密碼
|
||||
@@ -311,9 +311,13 @@ settings.type=版本設置類型
|
||||
settings.type.special=單獨版本設置(不會影響到其他版本的設定)
|
||||
|
||||
update=啓動器更新
|
||||
update.channel.dev=更新到開發版
|
||||
update.channel.stable=更新到推薦版本
|
||||
update.checking=正在檢查更新
|
||||
update.failed=更新失敗
|
||||
update.found=發現更新
|
||||
update.newest_version=最新版本爲:%s
|
||||
update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。選擇更新到開發版導致你電腦中所有的HMCL更新至開發版
|
||||
update.latest=當前版本爲最新版本
|
||||
update.no_browser=無法打開瀏覽器,網址已經複製到剪貼板了,您可以手動粘貼網址打開頁面
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -93,7 +93,7 @@ extension.sh=Bash 脚本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,请降级到 Java 8 或 10。\n如果您使用的是 OpenJDK,请确保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 HMCL,但将无法连接到部分站点(如使用 Let's Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
fatal.config_access_denied=HMCL 无法读取或写入位于 "%s" 的配置文件。\n请授予 HMCL 对该文件及其父目录的读写权限。
|
||||
fatal.config_loading_failure=HMCL 无法加载配置文件。\n请确保 HMCL 对 "%s" 目录及该目录下的文件拥有读写权限。
|
||||
fatal.migration_requires_manual_reboot=HMCL 即将完成升级,请重新打开 HMCL。
|
||||
fatal.apply_update_failure=我们很抱歉 HMCL 无法自动完成升级,因为出现了一些问题。\n但你依然可以从 %s 处手动下载 HMCL 来完成升级。\n请考虑向我们反馈该问题。
|
||||
|
||||
@@ -293,7 +293,7 @@ settings.launcher.language=语言
|
||||
settings.launcher.log_font=日志字体
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=身份验证
|
||||
settings.launcher.proxy.enable=启用代理
|
||||
settings.launcher.proxy.disable=使用系统代理
|
||||
settings.launcher.proxy.host=主机
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=密码
|
||||
@@ -311,9 +311,13 @@ settings.type=版本设置类型
|
||||
settings.type.special=单独版本设置(不会影响到其他版本的设定)
|
||||
|
||||
update=启动器更新
|
||||
update.channel.dev=更新到开发版
|
||||
update.channel.stable=更新到推荐版本
|
||||
update.checking=正在检查更新
|
||||
update.failed=更新失败
|
||||
update.found=发现更新
|
||||
update.newest_version=最新版本为:%s
|
||||
update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。选择更新到开发版导致你电脑中所有的HMCL更新至开发版
|
||||
update.latest=当前版本为最新版本
|
||||
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -37,6 +37,10 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
||||
private OfflineAccountFactory() {
|
||||
}
|
||||
|
||||
public OfflineAccount create(String username, UUID uuid) {
|
||||
return new OfflineAccount(username, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineAccount create(CharacterSelector selector, String username, String password, Object additionalData) {
|
||||
return new OfflineAccount(username, getUUIDFromUserName(username));
|
||||
|
||||
@@ -56,11 +56,11 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
|
||||
String serialized = Constants.GSON.toJson(library);
|
||||
|
||||
if (multimap.containsKey(id)) {
|
||||
boolean hasEqualRule = false;
|
||||
boolean duplicate = false;
|
||||
for (Library otherLibrary : multimap.get(id)) {
|
||||
VersionNumber otherNumber = VersionNumber.asVersion(otherLibrary.getVersion());
|
||||
if (CompatibilityRule.equals(library.getRules(), otherLibrary.getRules())) { // rules equal, ignore older version.
|
||||
hasEqualRule = true;
|
||||
boolean flag = true;
|
||||
if (number.compareTo(otherNumber) > 0) { // if this library is newer
|
||||
multimap.removeValue(otherLibrary);
|
||||
multimap.put(id, library);
|
||||
@@ -76,12 +76,20 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
|
||||
multimap.put(id, library);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// for text2speech, which have same library id as well as version number,
|
||||
// but its library and native library does not equal
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasEqualRule) {
|
||||
if (!duplicate) {
|
||||
multimap.put(id, library);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -45,12 +45,4 @@ public class ProcessStoppedEvent extends Event {
|
||||
public ManagedProcess getProcess() {
|
||||
return process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("process", process)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -151,8 +151,9 @@ public class Version implements Comparable<Version>, Validation {
|
||||
}
|
||||
|
||||
protected Version resolve(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {
|
||||
if (inheritsFrom == null)
|
||||
return this;
|
||||
if (inheritsFrom == null) {
|
||||
return this.jar == null ? this.setJar(id) : this;
|
||||
}
|
||||
|
||||
// To maximize the compatibility.
|
||||
if (!resolvedSoFar.add(id)) {
|
||||
|
||||
@@ -258,7 +258,7 @@ public class DefaultLauncher extends Launcher {
|
||||
File nativeFolder = Files.createTempDirectory("minecraft").toFile();
|
||||
|
||||
// To guarantee that when failed to generate launch command line, we will not call pre-launch command
|
||||
List<String> rawCommandLine = generateCommandLine(nativeFolder, true).asList();
|
||||
List<String> rawCommandLine = generateCommandLine(nativeFolder, isEnablingLoggingInfo()).asList();
|
||||
|
||||
decompressNatives(nativeFolder);
|
||||
|
||||
@@ -316,14 +316,17 @@ public class DefaultLauncher extends Launcher {
|
||||
throw new PermissionException();
|
||||
}
|
||||
|
||||
protected boolean isEnablingLoggingInfo() {
|
||||
return version.getLogging() != null && version.getLogging().containsKey(DownloadType.CLIENT)
|
||||
&& !"net.minecraft.launchwrapper.Launch".equals(version.getMainClass());
|
||||
}
|
||||
|
||||
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener) {
|
||||
startMonitors(managedProcess, processListener, true);
|
||||
}
|
||||
|
||||
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) {
|
||||
boolean enablesLoggingInfo = version.getLogging() != null && version.getLogging().containsKey(DownloadType.CLIENT)
|
||||
&& !"net.minecraft.launchwrapper.Launch".equals(version.getMainClass());
|
||||
if (enablesLoggingInfo)
|
||||
if (isEnablingLoggingInfo())
|
||||
startMonitorsWithLoggingInfo(managedProcess, processListener, isDaemon);
|
||||
else
|
||||
startMonitorsWithoutLoggingInfo(managedProcess, processListener, isDaemon);
|
||||
|
||||
@@ -152,14 +152,20 @@ public final class ModInfo implements Comparable<ModInfo> {
|
||||
try {
|
||||
return ForgeModMetadata.fromFile(modFile);
|
||||
} catch (Exception ignore) {
|
||||
description = "May be Forge mod";
|
||||
}
|
||||
|
||||
try {
|
||||
return RiftModMetadata.fromFile(modFile);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
description = "";
|
||||
break;
|
||||
case "litemod":
|
||||
try {
|
||||
return LiteModMetadata.fromFile(modFile);
|
||||
} catch (Exception ignore) {
|
||||
description = "May be LiteLoader mod";
|
||||
description = "LiteLoader Mod";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -268,7 +269,7 @@ public final class MultiMCInstanceConfiguration {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modpackFile.toPath())) {
|
||||
Path root = Files.list(fs.getPath("/")).filter(Files::isDirectory).findAny()
|
||||
.orElseThrow(() -> new IOException("Not a valid MultiMC modpack"));
|
||||
String name = root.normalize().getFileName().toString();
|
||||
String name = StringUtils.removeSuffix(root.normalize().getFileName().toString(), "/");
|
||||
|
||||
Path instancePath = root.resolve("instance.cfg");
|
||||
if (Files.notExists(instancePath))
|
||||
|
||||
@@ -120,7 +120,7 @@ public final class MultiMCModpackInstallTask extends Task {
|
||||
|
||||
if (Files.exists(patches))
|
||||
for (Path patchJson : Files.newDirectoryStream(patches)) {
|
||||
if (patchJson.endsWith(".json")) {
|
||||
if (patchJson.toString().endsWith(".json")) {
|
||||
MultiMCInstancePatch patch = Constants.GSON.fromJson(IOUtils.readFullyAsString(Files.newInputStream(patchJson)), MultiMCInstancePatch.class);
|
||||
List<String> newArguments = new LinkedList<>();
|
||||
for (String arg : patch.getTweakers()) {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Immutable
|
||||
public final class RiftModMetadata {
|
||||
private final String id;
|
||||
private final String name;
|
||||
private final List<String> authors;
|
||||
|
||||
public RiftModMetadata() {
|
||||
this("", "", Collections.emptyList());
|
||||
}
|
||||
|
||||
public RiftModMetadata(String id, String name, List<String> authors) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.authors = authors;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<String> getAuthors() {
|
||||
return authors;
|
||||
}
|
||||
|
||||
public static ModInfo fromFile(File modFile) throws IOException, JsonParseException {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.toPath())) {
|
||||
Path mcmod = fs.getPath("riftmod.json");
|
||||
if (Files.notExists(mcmod))
|
||||
throw new IOException("File " + modFile + " is not a Forge mod.");
|
||||
RiftModMetadata metadata = JsonUtils.fromNonNullJson(IOUtils.readFullyAsString(Files.newInputStream(mcmod)), RiftModMetadata.class);
|
||||
String authors = metadata.getAuthors() == null ? "" : String.join(", ", metadata.getAuthors());
|
||||
return new ModInfo(modFile, metadata.getName(), "",
|
||||
authors, "", "", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,12 @@ public final class CommandBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsing will ignore your manual escaping
|
||||
*
|
||||
* @param args commands
|
||||
* @return this
|
||||
*/
|
||||
public CommandBuilder add(String... args) {
|
||||
for (String s : args)
|
||||
raw.add(new Item(s, true));
|
||||
@@ -89,35 +95,27 @@ public final class CommandBuilder {
|
||||
}
|
||||
|
||||
private static String parseWindows(String s) {
|
||||
if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0)
|
||||
if (s.charAt(0) != '"') {
|
||||
// The argument has not been quoted, add quotes.
|
||||
return '"' + s.replace("\\", "\\\\").replace("\"", "\"\"") + '"';
|
||||
} else if (s.endsWith("\"")) {
|
||||
// The argument has already been quoted.
|
||||
return s;
|
||||
} else {
|
||||
// Unmatched quote for the argument.
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
String escape = " \t\"^&<>|";
|
||||
if (StringUtils.containsOne(s, escape.toCharArray()))
|
||||
// The argument has not been quoted, add quotes.
|
||||
return '"' + s
|
||||
.replace("\\", "\\\\")
|
||||
.replace("\"", "\"\"")
|
||||
+ '"';
|
||||
else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private static String parseBash(String s) {
|
||||
if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0)
|
||||
if (s.charAt(0) != '"') {
|
||||
// The argument has not been quoted, add quotes.
|
||||
return '"' + s.replace("\"", "\\\"") + '"';
|
||||
} else if (s.endsWith("\"")) {
|
||||
// The argument has already been quoted.
|
||||
return s;
|
||||
} else {
|
||||
// Unmatched quote for the argument.
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
else
|
||||
String escaping = " \t\"!#$&'()*,;<=>?[\\]^`{|}~";
|
||||
String escaped = "\"$&`";
|
||||
if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0 || StringUtils.containsOne(s, escaping.toCharArray())) {
|
||||
// The argument has not been quoted, add quotes.
|
||||
for (char ch : escaped.toCharArray())
|
||||
s = s.replace("" + ch, "\\" + ch);
|
||||
return '"' + s + '"';
|
||||
} else
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -19,14 +19,15 @@ package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.spi.FileSystemProvider;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.zip.ZipError;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
/**
|
||||
* Utilities of compressing
|
||||
@@ -35,6 +36,11 @@ import java.util.Optional;
|
||||
*/
|
||||
public final class CompressingUtils {
|
||||
|
||||
private static final FileSystemProvider ZIPFS_PROVIDER = FileSystemProvider.installedProviders().stream()
|
||||
.filter(it -> "jar".equalsIgnoreCase(it.getScheme()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("Zipfs not supported"));
|
||||
|
||||
private CompressingUtils() {
|
||||
}
|
||||
|
||||
@@ -62,7 +68,12 @@ public final class CompressingUtils {
|
||||
env.put("encoding", encoding);
|
||||
if (useTempFile)
|
||||
env.put("useTempFile", true);
|
||||
return FileSystems.newFileSystem(URI.create("jar:" + zipFile.toUri()), env);
|
||||
try {
|
||||
return ZIPFS_PROVIDER.newFileSystem(zipFile, env);
|
||||
} catch (ZipError error) {
|
||||
// Since Java 8 throws ZipError stupidly
|
||||
throw new ZipException(error.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,10 +22,9 @@ import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -38,7 +37,7 @@ import java.util.stream.Stream;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class JavaVersion implements Serializable {
|
||||
public final class JavaVersion {
|
||||
|
||||
private final File binary;
|
||||
private final String longVersion;
|
||||
@@ -67,12 +66,11 @@ public final class JavaVersion implements Serializable {
|
||||
/**
|
||||
* The major version of Java installation.
|
||||
*
|
||||
* @see org.jackhuang.hmcl.util.JavaVersion#JAVA_X
|
||||
* @see org.jackhuang.hmcl.util.JavaVersion#JAVA_11
|
||||
* @see org.jackhuang.hmcl.util.JavaVersion#JAVA_10
|
||||
* @see org.jackhuang.hmcl.util.JavaVersion#JAVA_9
|
||||
* @see org.jackhuang.hmcl.util.JavaVersion#JAVA_8
|
||||
* @see org.jackhuang.hmcl.util.JavaVersion#JAVA_7
|
||||
* @see org.jackhuang.hmcl.util.JavaVersion#JAVA_6
|
||||
* @see org.jackhuang.hmcl.util.JavaVersion#JAVA_5
|
||||
* @see org.jackhuang.hmcl.util.JavaVersion#UNKNOWN
|
||||
*/
|
||||
public int getParsedVersion() {
|
||||
@@ -82,52 +80,44 @@ public final class JavaVersion implements Serializable {
|
||||
private static final Pattern REGEX = Pattern.compile("version \"(?<version>(.*?))\"");
|
||||
|
||||
public static final int UNKNOWN = -1;
|
||||
public static final int JAVA_5 = 50;
|
||||
public static final int JAVA_6 = 60;
|
||||
public static final int JAVA_7 = 70;
|
||||
public static final int JAVA_8 = 80;
|
||||
public static final int JAVA_9 = 90;
|
||||
public static final int JAVA_X = 100;
|
||||
public static final int JAVA_10 = 100;
|
||||
public static final int JAVA_11 = 110;
|
||||
|
||||
private static int parseVersion(String version) {
|
||||
if (version.startsWith("10") || version.startsWith("X"))
|
||||
return JAVA_X;
|
||||
else if (version.contains("1.9.") || version.startsWith("9"))
|
||||
if (version.startsWith("11"))
|
||||
return JAVA_11;
|
||||
else if (version.startsWith("10"))
|
||||
return JAVA_10;
|
||||
else if (version.startsWith("9"))
|
||||
return JAVA_9;
|
||||
else if (version.contains("1.8"))
|
||||
return JAVA_8;
|
||||
else if (version.contains("1.7"))
|
||||
return JAVA_7;
|
||||
else if (version.contains("1.6"))
|
||||
return JAVA_6;
|
||||
else if (version.contains("1.5"))
|
||||
return JAVA_5;
|
||||
else
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
public static JavaVersion fromExecutable(File executable) throws IOException {
|
||||
File actualFile = executable;
|
||||
Platform platform = Platform.BIT_32;
|
||||
String version = null;
|
||||
|
||||
if ("javaw".equals(FileUtils.getNameWithoutExtension(actualFile)))
|
||||
actualFile = new File(actualFile.getAbsoluteFile().getParentFile(), "java");
|
||||
// javaw is only used on windows
|
||||
if ("javaw.exe".equalsIgnoreCase(executable.getName()))
|
||||
executable = new File(executable.getAbsoluteFile().getParentFile(), "java.exe");
|
||||
|
||||
try {
|
||||
Process process = new ProcessBuilder(actualFile.getAbsolutePath(), "-version").start();
|
||||
process.waitFor();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
Matcher m = REGEX.matcher(line);
|
||||
if (m.find())
|
||||
version = m.group("version");
|
||||
if (line.contains("64-Bit"))
|
||||
platform = Platform.BIT_64;
|
||||
}
|
||||
Process process = new ProcessBuilder(executable.getAbsolutePath(), "-version").start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
||||
for (String line; (line = reader.readLine()) != null;) {
|
||||
Matcher m = REGEX.matcher(line);
|
||||
if (m.find())
|
||||
version = m.group("version");
|
||||
if (line.contains("64-Bit"))
|
||||
platform = Platform.BIT_64;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException("Interrupted scanning the java version.", e);
|
||||
}
|
||||
|
||||
if (version == null)
|
||||
@@ -135,24 +125,20 @@ public final class JavaVersion implements Serializable {
|
||||
|
||||
if (parseVersion(version) == UNKNOWN)
|
||||
throw new IOException("Unrecognized Java version " + version);
|
||||
return new JavaVersion(actualFile, version, platform);
|
||||
}
|
||||
|
||||
private static JavaVersion fromKnownExecutable(File file, String version) {
|
||||
return new JavaVersion(file, version, Platform.UNKNOWN);
|
||||
return new JavaVersion(executable, version, platform);
|
||||
}
|
||||
|
||||
public static JavaVersion fromJavaHome(File home) throws IOException {
|
||||
return fromExecutable(getJavaFile(home));
|
||||
return fromExecutable(getExecutable(home));
|
||||
}
|
||||
|
||||
private static File getJavaFile(File home) {
|
||||
File path = new File(home, "bin");
|
||||
File javaw = new File(path, "javae.exe");
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && javaw.isFile())
|
||||
return javaw;
|
||||
else
|
||||
return new File(path, "java"); // Both linux and windows allow this.
|
||||
private static File getExecutable(File javaHome) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
return new File(javaHome, "bin/java.exe");
|
||||
} else {
|
||||
return new File(javaHome, "bin/java");
|
||||
}
|
||||
}
|
||||
|
||||
public static JavaVersion fromCurrentEnvironment() {
|
||||
@@ -160,7 +146,7 @@ public final class JavaVersion implements Serializable {
|
||||
}
|
||||
|
||||
public static final JavaVersion THIS_JAVA = new JavaVersion(
|
||||
getJavaFile(new File(System.getProperty("java.home"))),
|
||||
getExecutable(new File(System.getProperty("java.home"))),
|
||||
System.getProperty("java.version"),
|
||||
Platform.PLATFORM
|
||||
);
|
||||
@@ -168,10 +154,6 @@ public final class JavaVersion implements Serializable {
|
||||
private static List<JavaVersion> JAVAS;
|
||||
private static final CountDownLatch LATCH = new CountDownLatch(1);
|
||||
|
||||
public static Optional<List<JavaVersion>> getJREsImmediately() {
|
||||
return Optional.ofNullable(JAVAS);
|
||||
}
|
||||
|
||||
public static List<JavaVersion> getJREs() throws InterruptedException {
|
||||
if (JAVAS != null)
|
||||
return JAVAS;
|
||||
@@ -194,7 +176,7 @@ public final class JavaVersion implements Serializable {
|
||||
javaVersions = queryMacintosh();
|
||||
break;
|
||||
default:
|
||||
javaVersions = Collections.emptyList();
|
||||
javaVersions = new ArrayList<>();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -214,6 +196,7 @@ public final class JavaVersion implements Serializable {
|
||||
LATCH.countDown();
|
||||
}
|
||||
|
||||
// ==== Linux ====
|
||||
private static List<JavaVersion> queryLinux() throws IOException {
|
||||
Path jvmDir = Paths.get("/usr/lib/jvm");
|
||||
if (Files.isDirectory(jvmDir)) {
|
||||
@@ -234,9 +217,11 @@ public final class JavaVersion implements Serializable {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
// ====
|
||||
|
||||
// ==== OSX ====
|
||||
private static List<JavaVersion> queryMacintosh() throws IOException {
|
||||
LinkedList<JavaVersion> res = new LinkedList<>();
|
||||
List<JavaVersion> res = new ArrayList<>();
|
||||
|
||||
File currentJRE = new File("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home");
|
||||
if (currentJRE.exists())
|
||||
@@ -248,18 +233,32 @@ public final class JavaVersion implements Serializable {
|
||||
|
||||
return res;
|
||||
}
|
||||
// ====
|
||||
|
||||
// ==== Windows ====
|
||||
private static List<JavaVersion> queryWindows() {
|
||||
LinkedList<JavaVersion> res = new LinkedList<>();
|
||||
Lang.ignoringException(() -> res.addAll(queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\")));
|
||||
Lang.ignoringException(() -> res.addAll(queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\")));
|
||||
Lang.ignoringException(() -> res.addAll(queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\")));
|
||||
Lang.ignoringException(() -> res.addAll(queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\")));
|
||||
List<JavaVersion> res = new ArrayList<>();
|
||||
Lang.ignoringException(() -> res.addAll(queryJavaInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\")));
|
||||
Lang.ignoringException(() -> res.addAll(queryJavaInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\")));
|
||||
Lang.ignoringException(() -> res.addAll(queryJavaInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\")));
|
||||
Lang.ignoringException(() -> res.addAll(queryJavaInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\")));
|
||||
return res;
|
||||
}
|
||||
|
||||
private static List<JavaVersion> queryJavaInRegistryKey(String location) throws IOException, InterruptedException {
|
||||
List<JavaVersion> res = new ArrayList<>();
|
||||
for (String java : querySubFolders(location)) {
|
||||
if (!querySubFolders(java).contains(java + "\\MSI")) continue;
|
||||
String home = queryRegisterValue(java, "JavaHome");
|
||||
if (home != null)
|
||||
res.add(fromJavaHome(new File(home)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Registry utilities
|
||||
private static List<String> querySubFolders(String location) throws IOException, InterruptedException {
|
||||
List<String> res = new LinkedList<>();
|
||||
List<String> res = new ArrayList<>();
|
||||
String[] cmd = new String[] { "cmd", "/c", "reg", "query", location };
|
||||
Process process = Runtime.getRuntime().exec(cmd);
|
||||
process.waitFor();
|
||||
@@ -272,16 +271,6 @@ public final class JavaVersion implements Serializable {
|
||||
return res;
|
||||
}
|
||||
|
||||
private static List<JavaVersion> queryRegisterKey(String location) throws IOException, InterruptedException {
|
||||
List<JavaVersion> res = new LinkedList<>();
|
||||
for (String java : querySubFolders(location)) {
|
||||
String home = queryRegisterValue(java, "JavaHome");
|
||||
if (home != null)
|
||||
res.add(fromJavaHome(new File(home)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static String queryRegisterValue(String location, String name) throws IOException, InterruptedException {
|
||||
String[] cmd = new String[] { "cmd", "/c", "reg", "query", location, "/v", name };
|
||||
boolean last = false;
|
||||
@@ -306,4 +295,5 @@ public final class JavaVersion implements Serializable {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// ====
|
||||
}
|
||||
@@ -176,6 +176,13 @@ public final class StringUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean containsOne(String pattern, char... targets) {
|
||||
for (char target : targets)
|
||||
if (pattern.toLowerCase().indexOf(Character.toLowerCase(target)) >= 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<String> tokenize(String str) {
|
||||
if (str == null)
|
||||
return new LinkedList<>();
|
||||
|
||||
Binary file not shown.
@@ -103,7 +103,7 @@
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@@ -116,11 +116,13 @@
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<InlineFunctionExpansion>Disabled</InlineFunctionExpansion>
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@@ -133,12 +135,14 @@
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<InlineFunctionExpansion>Disabled</InlineFunctionExpansion>
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user