Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83111b83f2 | ||
|
|
420e337c8c | ||
|
|
028e43c959 | ||
|
|
ef01acca6d | ||
|
|
110d5f5630 | ||
|
|
94497a073d | ||
|
|
ff540a95ee | ||
|
|
ff6a32cdc3 | ||
|
|
969644635a | ||
|
|
a053736e95 | ||
|
|
879f82c462 |
No files matched your search
@@ -66,6 +66,7 @@ dependencies {
|
||||
}
|
||||
|
||||
embedResources(libs.authlib.injector)
|
||||
embedResources(libs.lwjgl.unsafe.agent)
|
||||
}
|
||||
|
||||
fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes)
|
||||
@@ -155,6 +156,7 @@ val hmclProperties = buildList {
|
||||
add("hmcl.microsoft.auth.id" to microsoftAuthId)
|
||||
add("hmcl.curseforge.apikey" to curseForgeApiKey)
|
||||
add("hmcl.authlib-injector.version" to libs.authlib.injector.get().version!!)
|
||||
add("hmcl.lwjgl-unsafe-agent.version" to libs.lwjgl.unsafe.agent.get().version!!)
|
||||
}
|
||||
|
||||
val hmclPropertiesFile = layout.buildDirectory.file("hmcl.properties")
|
||||
|
||||
@@ -21,12 +21,16 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.launch.DefaultLauncher;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.util.NativePatcher;
|
||||
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
@@ -150,4 +154,60 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
generateOptionsTxt();
|
||||
super.makeLaunchScript(scriptFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendJvmArgs(CommandBuilder result) {
|
||||
super.appendJvmArgs(result);
|
||||
|
||||
if (config().getAllowAutoAgent()
|
||||
&& !options.isNoGeneratedJVMArgs()
|
||||
&& !options.isNoGeneratedOptimizingJVMArgs()
|
||||
&& NativePatcher.needPatchMemoryUtil(version, options.getJava().getParsedVersion())) {
|
||||
LOG.info("Attempting to patch game with lwjgl-unsafe-agent");
|
||||
try {
|
||||
result.add("-javaagent:" + extractLwjglUnsafeAgent());
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to extract lwjgl-unsafe-agent", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Path extractLwjglUnsafeAgent() throws IOException {
|
||||
String agentVersion = JarUtils.getAttribute("hmcl.lwjgl-unsafe-agent.version", null);
|
||||
if (agentVersion == null) {
|
||||
throw new IOException("Missing hmcl.lwjgl-unsafe-agent.version attribute");
|
||||
}
|
||||
|
||||
Library library = new Library(new Artifact("org.glavo", "lwjgl-unsafe-agent", agentVersion));
|
||||
String fileName = library.getArtifact().getFileName();
|
||||
|
||||
Path agentPath = repository.getLibraryFile(version, library).toAbsolutePath().normalize();
|
||||
if (agentPath.toString().contains("=")) {
|
||||
throw new IOException("Invalid library path: " + agentPath);
|
||||
}
|
||||
|
||||
byte[] bytes;
|
||||
try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/" + fileName)) {
|
||||
if (input == null) {
|
||||
throw new IOException("/assets/" + fileName + " not found");
|
||||
}
|
||||
|
||||
bytes = input.readAllBytes();
|
||||
}
|
||||
|
||||
if (Files.isRegularFile(agentPath)) {
|
||||
try {
|
||||
if (Files.size(agentPath) == bytes.length) {
|
||||
return agentPath;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to check size of " + agentPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
Files.createDirectories(agentPath.getParent());
|
||||
FileUtils.saveSafely(agentPath, output -> output.write(bytes));
|
||||
return agentPath;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,6 +65,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static javafx.application.Platform.runLater;
|
||||
import static javafx.application.Platform.setImplicitExit;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.Lang.resolveException;
|
||||
@@ -75,6 +76,8 @@ import static org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java;
|
||||
|
||||
public final class LauncherHelper {
|
||||
|
||||
private static final String LWJGL_3_4_1_TIP = "lwjgl3.4.1-ffm";
|
||||
|
||||
private final Profile profile;
|
||||
private Account account;
|
||||
private final String selectedVersion;
|
||||
@@ -197,6 +200,28 @@ public final class LauncherHelper {
|
||||
);
|
||||
}).withStage("launch.state.dependencies")
|
||||
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
|
||||
.thenComposeAsync(() -> {
|
||||
if (config().getAllowAutoAgent()
|
||||
|| setting.isNoJVMArgs()
|
||||
|| setting.isNoOptimizingJVMArgs()
|
||||
|| Boolean.TRUE.equals(config().getShownTips().get(LWJGL_3_4_1_TIP))
|
||||
|| !NativePatcher.needPatchMemoryUtil(version.get(), javaVersionRef.get().getParsedVersion())) {
|
||||
return Task.completed(null);
|
||||
} else {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
runInFX(() -> {
|
||||
Controllers.confirm(i18n("launch.advice.lwjgl_3_4_1"), i18n("launch.advice.lwjgl_3_4_1.title"), MessageType.QUESTION, () -> {
|
||||
config().getShownTips().put(LWJGL_3_4_1_TIP, true);
|
||||
config().setAllowAutoAgent(true);
|
||||
future.complete(null);
|
||||
}, () -> {
|
||||
config().getShownTips().put(LWJGL_3_4_1_TIP, true);
|
||||
future.complete(null);
|
||||
});
|
||||
});
|
||||
return Task.fromCompletableFuture(future);
|
||||
}
|
||||
})
|
||||
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions.Builder launchOptionsBuilder = repository.getLaunchOptions(
|
||||
|
||||
@@ -689,6 +689,21 @@ public final class Config extends ObservableSetting {
|
||||
this.disableAutoGameOptions.set(disableAutoGameOptions);
|
||||
}
|
||||
|
||||
@SerializedName("allowAutoAgent")
|
||||
private final BooleanProperty allowAutoAgent = new SimpleBooleanProperty(false);
|
||||
|
||||
public BooleanProperty allowAutoAgentProperty() {
|
||||
return allowAutoAgent;
|
||||
}
|
||||
|
||||
public boolean getAllowAutoAgent() {
|
||||
return allowAutoAgent.get();
|
||||
}
|
||||
|
||||
public void setAllowAutoAgent(boolean allowAutoAgent) {
|
||||
this.allowAutoAgent.set(allowAutoAgent);
|
||||
}
|
||||
|
||||
// Accounts
|
||||
|
||||
@SerializedName("authlibInjectorServers")
|
||||
|
||||
@@ -30,9 +30,6 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
public class LineButton extends LineButtonBase {
|
||||
private static final String DEFAULT_STYLE_CLASS = "line-button";
|
||||
|
||||
private static final int IDX_TRAILING_TEXT = IDX_TRAILING;
|
||||
private static final int IDX_TRAILING_ICON = IDX_TRAILING + 1;
|
||||
|
||||
public static LineButton createNavigationButton() {
|
||||
var button = new LineButton();
|
||||
button.setTrailingIcon(SVG.ARROW_FORWARD);
|
||||
@@ -50,7 +47,14 @@ public class LineButton extends LineButtonBase {
|
||||
|
||||
public LineButton() {
|
||||
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
container.setMouseTransparent(true);
|
||||
}
|
||||
|
||||
protected int getTrailingTextIndex() {
|
||||
return IDX_TRAILING;
|
||||
}
|
||||
|
||||
protected int getTrailingIconIndex() {
|
||||
return getTrailingTextIndex() + 1;
|
||||
}
|
||||
|
||||
private ObjectProperty<EventHandler<ActionEvent>> onAction;
|
||||
@@ -110,12 +114,13 @@ public class LineButton extends LineButtonBase {
|
||||
trailingTextLabel = new Label();
|
||||
trailingTextLabel.getStyleClass().add("trailing-label");
|
||||
trailingTextLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
||||
trailingTextLabel.setMouseTransparent(true);
|
||||
}
|
||||
trailingTextLabel.setText(message);
|
||||
setNode(IDX_TRAILING_TEXT, trailingTextLabel);
|
||||
setNode(getTrailingTextIndex(), trailingTextLabel);
|
||||
} else if (trailingTextLabel != null) {
|
||||
trailingTextLabel.setText("");
|
||||
setNode(IDX_TRAILING_TEXT, null);
|
||||
setNode(getTrailingTextIndex(), null);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -149,7 +154,7 @@ public class LineButton extends LineButtonBase {
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
setNode(IDX_TRAILING_ICON, get());
|
||||
setNode(getTrailingIconIndex(), get());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -171,6 +176,7 @@ public class LineButton extends LineButtonBase {
|
||||
public void setTrailingIcon(SVG rightIcon, double size) {
|
||||
Node rightIconNode = rightIcon.createIcon(size);
|
||||
rightIconNode.getStyleClass().add("trailing-icon");
|
||||
rightIconNode.setMouseTransparent(true);
|
||||
setTrailingIcon(rightIconNode);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ public abstract class LineComponent extends StackPane implements NoPaddingCompon
|
||||
this.container = new HBox(SPACING);
|
||||
container.getStyleClass().add("line-component-container");
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
container.setPickOnBounds(true);
|
||||
|
||||
this.titleLabel = new Label();
|
||||
titleLabel.getStyleClass().add("title-label");
|
||||
|
||||
@@ -43,7 +43,7 @@ import java.util.function.Function;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;
|
||||
|
||||
/// @author Glavo
|
||||
public final class LineSelectButton<T> extends LineButton {
|
||||
public class LineSelectButton<T> extends LineButton {
|
||||
|
||||
private static final String DEFAULT_STYLE_CLASS = "line-select-button";
|
||||
private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
|
||||
|
||||
@@ -18,18 +18,18 @@
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
@@ -69,13 +69,9 @@ import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class SettingsPage extends ScrollPane {
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final ToggleGroup updateChannelGroup;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final InvalidationListener updateListener;
|
||||
|
||||
@@ -122,82 +118,59 @@ public final class SettingsPage extends ScrollPane {
|
||||
settingsPane.getContent().add(sponsorPane);
|
||||
}
|
||||
|
||||
ObjectProperty<UpdateChannel> updateChannel;
|
||||
{
|
||||
ComponentSublist updatePane = new ComponentSublist();
|
||||
|
||||
JFXButton updateButton = new JFXButton();
|
||||
updateButton.setGraphic(SVG.UPDATE.createIcon(20));
|
||||
updateButton.setOnAction(e -> onUpdate());
|
||||
updateButton.setPadding(Insets.EMPTY);
|
||||
FXUtils.installFastTooltip(updateButton, i18n("update.tooltip"));
|
||||
|
||||
var updatePane = new LineSelectButton<UpdateChannel>() {
|
||||
|
||||
{
|
||||
getStyleClass().add("update-pane");
|
||||
setNode(IDX_TRAILING, updateButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTrailingTextIndex() {
|
||||
return LineComponent.IDX_TRAILING + 1;
|
||||
}
|
||||
};
|
||||
updateChannel = updatePane.valueProperty();
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setHasSubtitle(true);
|
||||
updatePane.setValue(UpdateChannel.getChannel());
|
||||
|
||||
final Label lblUpdate;
|
||||
final Label lblUpdateSub;
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
updatePane.setConverter(channel -> i18n("update.channel." + channel.channelName));
|
||||
updatePane.setItems(List.of(UpdateChannel.STABLE, UpdateChannel.DEVELOPMENT));
|
||||
updatePane.setDescriptionConverter(channel -> i18n("update.note." + channel.channelName));
|
||||
|
||||
lblUpdate = new Label(i18n("update"));
|
||||
lblUpdate.getStyleClass().add("title-label");
|
||||
lblUpdateSub = new Label();
|
||||
lblUpdateSub.getStyleClass().add("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(lblUpdate, lblUpdateSub);
|
||||
updatePane.setHeaderLeft(headerLeft);
|
||||
}
|
||||
final StringProperty lblUpdateSubProperty = updatePane.subtitleProperty();
|
||||
|
||||
{
|
||||
JFXButton btnUpdate = FXUtils.newToggleButton4(SVG.UPDATE, 20);
|
||||
btnUpdate.setOnAction(e -> onUpdate());
|
||||
FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
|
||||
updateListener = any -> {
|
||||
btnUpdate.setVisible(UpdateChecker.isOutdated());
|
||||
boolean outdated = UpdateChecker.isOutdated();
|
||||
|
||||
updateButton.setVisible(outdated);
|
||||
updateButton.setManaged(outdated);
|
||||
updatePane.pseudoClassStateChanged(PseudoClass.getPseudoClass("active"), outdated);
|
||||
|
||||
if (UpdateChecker.isOutdated()) {
|
||||
lblUpdateSub.setText(i18n("update.newest_version", UpdateChecker.getLatestVersion().getVersion()));
|
||||
lblUpdateSub.getStyleClass().setAll("update-label");
|
||||
|
||||
lblUpdate.setText(i18n("update.found"));
|
||||
lblUpdate.getStyleClass().setAll("update-label");
|
||||
lblUpdateSubProperty.set(i18n("update.newest_version", UpdateChecker.getLatestVersion().getVersion()));
|
||||
} else if (UpdateChecker.isCheckingUpdate()) {
|
||||
lblUpdateSub.setText(i18n("update.checking"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll("title-label");
|
||||
lblUpdateSubProperty.set(i18n("update.checking"));
|
||||
} else {
|
||||
lblUpdateSub.setText(i18n("update.latest"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll("title-label");
|
||||
lblUpdateSubProperty.set(i18n("update.latest"));
|
||||
}
|
||||
};
|
||||
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
updateListener.invalidated(null);
|
||||
|
||||
updatePane.setHeaderRight(btnUpdate);
|
||||
}
|
||||
|
||||
{
|
||||
VBox content = new VBox(12);
|
||||
content.setPadding(new Insets(8, 0, 0, 0));
|
||||
|
||||
updateChannelGroup = new ToggleGroup();
|
||||
|
||||
JFXRadioButton chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable"));
|
||||
chkUpdateStable.setUserData(UpdateChannel.STABLE);
|
||||
chkUpdateStable.setToggleGroup(updateChannelGroup);
|
||||
|
||||
JFXRadioButton chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev"));
|
||||
chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateDev.setToggleGroup(updateChannelGroup);
|
||||
|
||||
Label noteWrapper = new Label(i18n("update.note"));
|
||||
VBox.setMargin(noteWrapper, new Insets(8, 0, 0, 0));
|
||||
|
||||
content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);
|
||||
|
||||
updatePane.getContent().add(content);
|
||||
}
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
@@ -207,8 +180,6 @@ public final class SettingsPage extends ScrollPane {
|
||||
previewPane.setSubtitle(i18n("update.preview.subtitle"));
|
||||
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());
|
||||
|
||||
ObjectProperty<UpdateChannel> updateChannel = selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class);
|
||||
updateChannel.set(UpdateChannel.getChannel());
|
||||
InvalidationListener checkUpdateListener = e -> {
|
||||
UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());
|
||||
};
|
||||
@@ -289,6 +260,15 @@ public final class SettingsPage extends ScrollPane {
|
||||
settingsPane.getContent().add(disableAutoGameOptionsPane);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton allowAutoAgentPane = new LineToggleButton();
|
||||
allowAutoAgentPane.setTitle(i18n("settings.launcher.allow_auto_agent"));
|
||||
allowAutoAgentPane.setSubtitle(i18n("settings.launcher.allow_auto_agent.subtitle"));
|
||||
allowAutoAgentPane.selectedProperty().bindBidirectional(config().allowAutoAgentProperty());
|
||||
|
||||
settingsPane.getContent().add(allowAutoAgentPane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane debugPane = new BorderPane();
|
||||
|
||||
|
||||
+25
-25
@@ -22,7 +22,7 @@ import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.mod.DataPack;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -44,45 +44,45 @@ import java.util.regex.Pattern;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> implements WorldManagePage.WorldRefreshable {
|
||||
public final class DataPackListPage extends ListPageBase<DataPackListPageSkin.DataPackInfoObject> implements WorldManagePage.WorldRefreshable {
|
||||
private final World world;
|
||||
private final Datapack datapack;
|
||||
private final DataPack dataPack;
|
||||
final BooleanProperty readOnly;
|
||||
|
||||
public DatapackListPage(WorldManagePage worldManagePage) {
|
||||
public DataPackListPage(WorldManagePage worldManagePage) {
|
||||
world = worldManagePage.getWorld();
|
||||
datapack = new Datapack(world.getFile().resolve("datapacks"));
|
||||
setItems(MappedObservableList.create(datapack.getPacks(), DatapackListPageSkin.DatapackInfoObject::new));
|
||||
dataPack = new DataPack(world.getFile().resolve("datapacks"));
|
||||
setItems(MappedObservableList.create(dataPack.getPacks(), DataPackListPageSkin.DataPackInfoObject::new));
|
||||
readOnly = worldManagePage.readOnlyProperty();
|
||||
FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)),
|
||||
this::installMultiDatapack, this::refresh);
|
||||
this::installMultiDataPack, this::refresh);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void installMultiDatapack(List<Path> datapackPath) {
|
||||
datapackPath.forEach(this::installSingleDatapack);
|
||||
private void installMultiDataPack(List<Path> dataPackPath) {
|
||||
dataPackPath.forEach(this::installSingleDataPack);
|
||||
if (readOnly.get()) {
|
||||
Controllers.showToast(i18n("datapack.reload.toast"));
|
||||
}
|
||||
}
|
||||
|
||||
private void installSingleDatapack(Path datapack) {
|
||||
private void installSingleDataPack(Path dataPack) {
|
||||
try {
|
||||
this.datapack.installPack(datapack, world.getGameVersion());
|
||||
this.dataPack.installPack(dataPack, world.getGameVersion());
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
LOG.warning("Unable to parse datapack file " + datapack, e);
|
||||
LOG.warning("Unable to parse datapack file " + dataPack, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new DatapackListPageSkin(this);
|
||||
return new DataPackListPageSkin(this);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
setLoading(true);
|
||||
Task.runAsync(datapack::loadFromDir)
|
||||
Task.runAsync(dataPack::loadFromDir)
|
||||
.withRunAsync(Schedulers.javafx(), () -> setLoading(false))
|
||||
.start();
|
||||
}
|
||||
@@ -94,18 +94,18 @@ public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.Da
|
||||
List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
|
||||
if (res != null) {
|
||||
installMultiDatapack(res);
|
||||
installMultiDataPack(res);
|
||||
}
|
||||
|
||||
datapack.loadFromDir();
|
||||
dataPack.loadFromDir();
|
||||
}
|
||||
|
||||
void removeSelected(ObservableList<DatapackListPageSkin.DatapackInfoObject> selectedItems) {
|
||||
void removeSelected(ObservableList<DataPackListPageSkin.DataPackInfoObject> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)
|
||||
.map(DataPackListPageSkin.DataPackInfoObject::getPackInfo)
|
||||
.forEach(pack -> {
|
||||
try {
|
||||
datapack.deletePack(pack);
|
||||
dataPack.deletePack(pack);
|
||||
} catch (IOException e) {
|
||||
// Fail to remove mods if the game is running or the datapack is absent.
|
||||
LOG.warning("Failed to delete datapack \"" + pack.getId() + "\"", e);
|
||||
@@ -113,23 +113,23 @@ public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.Da
|
||||
});
|
||||
}
|
||||
|
||||
void enableSelected(ObservableList<DatapackListPageSkin.DatapackInfoObject> selectedItems) {
|
||||
void enableSelected(ObservableList<DataPackListPageSkin.DataPackInfoObject> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)
|
||||
.map(DataPackListPageSkin.DataPackInfoObject::getPackInfo)
|
||||
.forEach(pack -> pack.setActive(true));
|
||||
}
|
||||
|
||||
void disableSelected(ObservableList<DatapackListPageSkin.DatapackInfoObject> selectedItems) {
|
||||
void disableSelected(ObservableList<DataPackListPageSkin.DataPackInfoObject> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)
|
||||
.map(DataPackListPageSkin.DataPackInfoObject::getPackInfo)
|
||||
.forEach(pack -> pack.setActive(false));
|
||||
}
|
||||
|
||||
void openDataPackFolder() {
|
||||
FXUtils.openFolder(datapack.getPath());
|
||||
FXUtils.openFolder(dataPack.getPath());
|
||||
}
|
||||
|
||||
@NotNull Predicate<DatapackListPageSkin.DatapackInfoObject> updateSearchPredicate(String queryString) {
|
||||
@NotNull Predicate<DataPackListPageSkin.DataPackInfoObject> updateSearchPredicate(String queryString) {
|
||||
if (queryString.isBlank()) {
|
||||
return dataPack -> true;
|
||||
}
|
||||
+16
-16
@@ -42,7 +42,7 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.mod.DataPack;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -67,7 +67,7 @@ import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
|
||||
private final TransitionPane toolbarPane;
|
||||
private final HBox searchBar;
|
||||
@@ -75,8 +75,8 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
private final HBox selectingToolbar;
|
||||
InvalidationListener updateBarByStateWeakListener;
|
||||
|
||||
private final JFXListView<DatapackInfoObject> listView;
|
||||
private final FilteredList<DatapackInfoObject> filteredList;
|
||||
private final JFXListView<DataPackInfoObject> listView;
|
||||
private final FilteredList<DataPackInfoObject> filteredList;
|
||||
|
||||
private final BooleanProperty isSearching = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty isSelecting = new SimpleBooleanProperty(false);
|
||||
@@ -85,7 +85,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
private static final AtomicInteger lastShiftClickIndex = new AtomicInteger(-1);
|
||||
final Consumer<Integer> toggleSelect;
|
||||
|
||||
DatapackListPageSkin(DatapackListPage skinnable) {
|
||||
DataPackListPageSkin(DataPackListPage skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
StackPane pane = new StackPane();
|
||||
@@ -181,7 +181,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
ComponentList.setVgrow(center, Priority.ALWAYS);
|
||||
center.loadingProperty().bind(skinnable.loadingProperty());
|
||||
|
||||
listView.setCellFactory(x -> new DatapackInfoListCell(listView, getSkinnable().readOnly));
|
||||
listView.setCellFactory(x -> new DataPackInfoListCell(listView, getSkinnable().readOnly));
|
||||
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
this.listView.setItems(filteredList);
|
||||
|
||||
@@ -216,13 +216,13 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
static class DatapackInfoObject extends RecursiveTreeObject<DatapackInfoObject> {
|
||||
static class DataPackInfoObject extends RecursiveTreeObject<DataPackInfoObject> {
|
||||
private final BooleanProperty activeProperty;
|
||||
private final Datapack.Pack packInfo;
|
||||
private final DataPack.Pack packInfo;
|
||||
|
||||
private SoftReference<CompletableFuture<Image>> iconCache;
|
||||
|
||||
DatapackInfoObject(Datapack.Pack packInfo) {
|
||||
DataPackInfoObject(DataPack.Pack packInfo) {
|
||||
this.packInfo = packInfo;
|
||||
this.activeProperty = packInfo.activeProperty();
|
||||
}
|
||||
@@ -235,7 +235,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
return packInfo.getDescription().toString();
|
||||
}
|
||||
|
||||
Datapack.Pack getPackInfo() {
|
||||
DataPack.Pack getPackInfo() {
|
||||
return packInfo;
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<DatapackInfoObject>> current) {
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<DataPackInfoObject>> current) {
|
||||
SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;
|
||||
CompletableFuture<Image> imageFuture;
|
||||
if (iconCache != null && (imageFuture = iconCache.get()) != null) {
|
||||
@@ -286,7 +286,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
imageContainer.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
imageFuture.thenAcceptAsync(image -> {
|
||||
if (current != null) {
|
||||
ObjectProperty<DatapackInfoObject> infoObjectProperty = current.get();
|
||||
ObjectProperty<DataPackInfoObject> infoObjectProperty = current.get();
|
||||
if (infoObjectProperty == null || infoObjectProperty.get() != this) {
|
||||
// The current ListCell has already switched to another object
|
||||
return;
|
||||
@@ -298,13 +298,13 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
private final class DatapackInfoListCell extends MDListCell<DatapackInfoObject> {
|
||||
private final class DataPackInfoListCell extends MDListCell<DataPackInfoObject> {
|
||||
final JFXCheckBox checkBox = new JFXCheckBox();
|
||||
ImageContainer imageContainer = new ImageContainer(32);
|
||||
final TwoLineListItem content = new TwoLineListItem();
|
||||
BooleanProperty booleanProperty;
|
||||
|
||||
DatapackInfoListCell(JFXListView<DatapackInfoObject> listView, BooleanProperty isReadOnlyProperty) {
|
||||
DataPackInfoListCell(JFXListView<DataPackInfoObject> listView, BooleanProperty isReadOnlyProperty) {
|
||||
super(listView);
|
||||
|
||||
HBox container = new HBox(8);
|
||||
@@ -326,7 +326,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(DatapackInfoObject dataItem, boolean empty) {
|
||||
protected void updateControl(DataPackInfoObject dataItem, boolean empty) {
|
||||
if (empty) return;
|
||||
content.setTitle(dataItem.getTitle());
|
||||
content.setSubtitle(dataItem.getSubtitle());
|
||||
@@ -338,7 +338,7 @@ final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
public void handleSelect(DatapackInfoListCell cell, MouseEvent mouseEvent) {
|
||||
public void handleSelect(DataPackInfoListCell cell, MouseEvent mouseEvent) {
|
||||
if (cell.isEmpty()) {
|
||||
mouseEvent.consume();
|
||||
return;
|
||||
@@ -27,16 +27,8 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
@@ -54,11 +46,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
@@ -66,14 +54,7 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -313,25 +294,55 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
for (String gameVersion : control.versions.keys().stream()
|
||||
.sorted(Collections.reverseOrder(GameVersionNumber::compare))
|
||||
.toList()) {
|
||||
List<RemoteMod.Version> versions = control.versions.get(gameVersion);
|
||||
if (versions == null || versions.isEmpty()) {
|
||||
continue;
|
||||
final class Versions {
|
||||
static ComponentSublist createSublist(DownloadPage control, String title, List<String> target) {
|
||||
var sublist = new ComponentSublist(() -> {
|
||||
ArrayList<ModItem> items = new ArrayList<>();
|
||||
for (String gv : target) {
|
||||
List<RemoteMod.Version> versions = control.versions.get(gv);
|
||||
if (versions != null) {
|
||||
for (RemoteMod.Version v : versions) {
|
||||
items.add(new ModItem(control.addon, v, control));
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
sublist.setTitle(title);
|
||||
sublist.getStyleClass().add("no-padding");
|
||||
|
||||
return sublist;
|
||||
}
|
||||
|
||||
var sublist = new ComponentSublist(() -> {
|
||||
ArrayList<ModItem> items = new ArrayList<>(versions.size());
|
||||
for (RemoteMod.Version v : versions) {
|
||||
items.add(new ModItem(control.addon, v, control));
|
||||
}
|
||||
return items;
|
||||
});
|
||||
sublist.getStyleClass().add("no-padding");
|
||||
sublist.setTitle("Minecraft " + gameVersion);
|
||||
final List<String> releases = new ArrayList<>();
|
||||
final List<String> snapshots = new ArrayList<>();
|
||||
}
|
||||
|
||||
list.getContent().add(sublist);
|
||||
TreeMap<GameVersionNumber, Versions> map = new TreeMap<>(Collections.reverseOrder());
|
||||
for (String version : control.versions.keys()) {
|
||||
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(version);
|
||||
GameVersionNumber releaseOfSnapshot = GameVersionNumber.getReleaseOfSnapshot(gameVersion);
|
||||
GameVersionNumber releaseVersion = Objects.requireNonNullElse(releaseOfSnapshot, gameVersion);
|
||||
|
||||
Versions lists = map.computeIfAbsent(releaseVersion, v -> new Versions());
|
||||
|
||||
if (releaseOfSnapshot == null) {
|
||||
lists.releases.add(version);
|
||||
} else {
|
||||
lists.snapshots.add(version);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<GameVersionNumber, Versions> entry : map.entrySet()) {
|
||||
GameVersionNumber gameVersion = entry.getKey();
|
||||
Versions versions = entry.getValue();
|
||||
|
||||
if (!versions.releases.isEmpty())
|
||||
list.getContent().add(Versions.createSublist(control, i18n("addon.download.title.release", gameVersion), versions.releases));
|
||||
|
||||
if (!versions.snapshots.isEmpty())
|
||||
list.getContent().add(Versions.createSublist(control, i18n("addon.download.title.snapshot", gameVersion), versions.snapshots));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
private final TabHeader header = new TabHeader(transitionPane);
|
||||
private final TabHeader.Tab<WorldInfoPage> worldInfoTab = new TabHeader.Tab<>("worldInfoPage");
|
||||
private final TabHeader.Tab<WorldBackupsPage> worldBackupsTab = new TabHeader.Tab<>("worldBackupsPage");
|
||||
private final TabHeader.Tab<DatapackListPage> datapackTab = new TabHeader.Tab<>("datapackListPage");
|
||||
private final TabHeader.Tab<DataPackListPage> dataPackTab = new TabHeader.Tab<>("dataPackListPage");
|
||||
|
||||
public WorldManagePage(World world, Profile profile, String instanceId) {
|
||||
this.world = world;
|
||||
@@ -86,7 +86,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
|
||||
worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
|
||||
worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
|
||||
datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
|
||||
dataPackTab.setNodeSupplier(() -> new DataPackListPage(this));
|
||||
|
||||
this.state = new SimpleObjectProperty<>(new State(i18n("world.manage.title", StringUtils.parseColorEscapes(world.getWorldName())), null, true, true, true));
|
||||
|
||||
@@ -218,9 +218,9 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
tabBar.addNavigationDrawerTab(getSkinnable().header, getSkinnable().worldInfoTab, i18n("world.info"), SVG.INFO, SVG.INFO_FILL)
|
||||
.addNavigationDrawerTab(getSkinnable().header, getSkinnable().worldBackupsTab, i18n("world.backup"), SVG.ARCHIVE, SVG.ARCHIVE_FILL);
|
||||
|
||||
if (getSkinnable().world.supportDatapacks()) {
|
||||
getSkinnable().header.getTabs().add(getSkinnable().datapackTab);
|
||||
tabBar.addNavigationDrawerTab(getSkinnable().header, getSkinnable().datapackTab, i18n("world.datapack"), SVG.EXTENSION, SVG.EXTENSION_FILL);
|
||||
if (getSkinnable().world.supportDataPacks()) {
|
||||
getSkinnable().header.getTabs().add(getSkinnable().dataPackTab);
|
||||
tabBar.addNavigationDrawerTab(getSkinnable().header, getSkinnable().dataPackTab, i18n("world.datapack"), SVG.EXTENSION, SVG.EXTENSION_FILL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,21 +31,27 @@ public final class ChunkBaseApp {
|
||||
private static final GameVersionNumber MIN_END_CITY_VERSION = GameVersionNumber.asGameVersion("1.13");
|
||||
|
||||
private static final String[] SEED_MAP_GAME_VERSIONS = {
|
||||
"1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21", "1.20",
|
||||
"1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14", "1.13",
|
||||
"1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13", "1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] STRONGHOLD_FINDER_GAME_VERSIONS = {
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.16", "1.13", "1.9", "1.7"
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13", "1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] NETHER_FORTRESS_GAME_VERSIONS = {
|
||||
"1.18", "1.16", "1.7"
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13", "1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] END_CITY_GAME_VERSIONS = {
|
||||
"1.19", "1.13"
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13"
|
||||
};
|
||||
|
||||
public static boolean isSupported(@NotNull World world) {
|
||||
|
||||
@@ -63,6 +63,16 @@ public final class NativePatcher {
|
||||
});
|
||||
}
|
||||
|
||||
// https://github.com/LWJGL/lwjgl3/issues/1111
|
||||
public static boolean needPatchMemoryUtil(Version version, int javaVersion) {
|
||||
return javaVersion >= 25 && javaVersion <= 26 && version.getLibraries().stream().anyMatch(library ->
|
||||
"org.lwjgl".equals(library.getGroupId())
|
||||
&& "lwjgl".equals(library.getArtifactId())
|
||||
&& "3.4.1".equals(library.getVersion())
|
||||
&& library.getClassifier() == null
|
||||
);
|
||||
}
|
||||
|
||||
public static Version patchNative(DefaultGameRepository repository,
|
||||
Version version, String gameVersion,
|
||||
JavaRuntime javaVersion,
|
||||
|
||||
@@ -1924,3 +1924,7 @@
|
||||
.line-toggle-button .jfx-toggle-button {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.update-pane:active .subtitle-label {
|
||||
-fx-text-fill: -monet-tertiary;
|
||||
}
|
||||
@@ -167,6 +167,9 @@ account.skin.upload.failed=Failed to upload skin.
|
||||
account.skin.invalid_skin=Invalid skin file.
|
||||
account.username=Username
|
||||
|
||||
addon.download.title.release=Minecraft %s
|
||||
addon.download.title.snapshot=Minecraft %s (Snapshots)
|
||||
|
||||
archive.author=Author(s)
|
||||
archive.date=Publish Date
|
||||
archive.file.name=File Name
|
||||
@@ -397,7 +400,7 @@ extension.ps1=Windows PowerShell Script
|
||||
extension.sh=Shell Script
|
||||
extension.command=macOS Shell Script
|
||||
|
||||
extension.datapack=Datapack Archive
|
||||
extension.datapack=Data Pack Archive
|
||||
extension.mod=Mod File
|
||||
extension.modloader.installer=Mod Loader Installer
|
||||
extension.resourcepack=Resource Pack Archive
|
||||
@@ -702,7 +705,7 @@ game.version=Game Instance
|
||||
|
||||
help=Help
|
||||
help.doc=Hello Minecraft! Launcher Documentation
|
||||
help.detail=For datapack and modpack makers.
|
||||
help.detail=For data pack and modpack makers.
|
||||
|
||||
input.email=The username must be an email address.
|
||||
input.number=The input must be numbers.
|
||||
@@ -808,6 +811,8 @@ launch.advice.forge37_0_60=Forge versions prior to 37.0.60 are not compatible wi
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or later versions.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions prior to 1.8.0_51. Please install the latest Java 8 version.
|
||||
launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later. Please use Java 8 instead.
|
||||
launch.advice.lwjgl_3_4_1=The current game version has a known issue that may cause stuttering in the game.\nHMCL can automatically optimize the game performance (using a Java Agent).\nDo you allow HMCL to automatically optimize the game?\nIf you encounter issues, you can disable the "Allow HMCL to modify the game" option in "Settings → General".
|
||||
launch.advice.lwjgl_3_4_1.title=Allow HMCL to automatically optimize the game?
|
||||
launch.advice.modded_java=Some mods may not be compatible with newer Java versions. It is recommended to use Java %1$s to launch Minecraft %2$s.
|
||||
launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge.
|
||||
launch.advice.newer_java=You are using an older Java version to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
|
||||
@@ -990,7 +995,7 @@ modrinth.category.colored-lighting=Colored Lighting
|
||||
modrinth.category.combat=Combat
|
||||
modrinth.category.core-shaders=Core Shaders
|
||||
modrinth.category.cursed=Cursed
|
||||
modrinth.category.datapack=Datapack
|
||||
modrinth.category.datapack=Data Pack
|
||||
modrinth.category.decoration=Decoration
|
||||
modrinth.category.economy=Economy
|
||||
modrinth.category.entities=Entities
|
||||
@@ -1133,11 +1138,11 @@ nbt.open.failed=Failed to open file
|
||||
nbt.save.failed=Failed to save file
|
||||
nbt.title=View File - %s
|
||||
|
||||
datapack=Datapacks
|
||||
datapack=Data Packs
|
||||
datapack.add=Add
|
||||
datapack.add.title=Choose datapack archive you want to add
|
||||
datapack.reload.toast=Minecraft is running, please use the /reload command to reload the data pack
|
||||
datapack.title=World [%s] - Datapacks
|
||||
datapack.add.title=Choose data pack archive you want to add
|
||||
datapack.reload.toast=Minecraft is running. Use the /reload command to reload data packs
|
||||
datapack.title=World [%s] - Data Packs
|
||||
|
||||
web.failed=Failed to load page
|
||||
web.open_in_browser=Do you want to open this address in a browser:\n%s
|
||||
@@ -1167,7 +1172,7 @@ world.duplicate.failed.empty_name=Name cannot be empty
|
||||
world.duplicate.failed.invalid_name=Name contains invalid characters
|
||||
world.duplicate.failed=Failed to duplicate the world
|
||||
world.duplicate.success.toast=Successfully duplicated the world
|
||||
world.datapack=Datapacks
|
||||
world.datapack=Data Packs
|
||||
world.datetime=Last played on %s
|
||||
world.delete=Delete the World
|
||||
world.delete.failed=Failed to delete world.\n%s
|
||||
@@ -1417,6 +1422,8 @@ settings.game.working_directory.hint=Enable the "Isolated" option in "Working Di
|
||||
settings.icon=Icon
|
||||
|
||||
settings.launcher=Launcher Settings
|
||||
settings.launcher.allow_auto_agent=Allow HMCL to modify the game
|
||||
settings.launcher.allow_auto_agent.subtitle=Allow HMCL to modify the game by attaching a Java Agent to improve the gaming experience.
|
||||
settings.launcher.appearance=Appearance
|
||||
settings.launcher.brightness=Theme Mode
|
||||
settings.launcher.brightness.auto=Follow System Settings
|
||||
@@ -1602,7 +1609,8 @@ update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
update.bubble.title=Update Available: %s
|
||||
update.bubble.subtitle=Click here to update
|
||||
update.note=Beta and Nightly channels may have more features or fixes, but they also come with more potential problems.
|
||||
update.note.stable=Suitable for users who prioritize software stability.\nNew features are added to the stable version only after thorough testing.
|
||||
update.note.dev=Suitable for users who want to experience new features first.\nThe development version includes the latest features and bug fixes, but may also have more issues due to insufficient testing.
|
||||
update.latest=This is the latest version
|
||||
update.no_browser=Cannot open in system browser. But we copied the link to your clipboard, and you can open it manually.
|
||||
update.tooltip=Update
|
||||
|
||||
@@ -1593,7 +1593,6 @@ update.found=تحديث متاح!
|
||||
update.newest_version=أحدث إصدار: %s
|
||||
update.bubble.title=تحديث متاح: %s
|
||||
update.bubble.subtitle=انقر هنا للتحديث
|
||||
update.note=قد تحتوي القنوات التجريبية والليلية على مزيد من الميزات والإصلاحات، لكنها تأتي أيضاً بمزيد من المشاكل المحتملة.
|
||||
update.latest=هذا هو أحدث إصدار
|
||||
update.no_browser=لا يمكن الفتح في متصفح النظام. لكننا نسخنا الرابط إلى حافظتك، ويمكنك فتحه يدوياً.
|
||||
update.tooltip=تحديث
|
||||
|
||||
@@ -1402,7 +1402,6 @@ update.found=¡Actualización disponible!
|
||||
update.newest_version=Última versión: %s
|
||||
update.bubble.title=Actualización disponible: %s
|
||||
update.bubble.subtitle=Haga clic aquí para actualizar
|
||||
update.note=Advertencia: Las versiones beta y las versiones nocturnas pueden tener más funciones o correcciones, pero también tienen más problemas potenciales.
|
||||
update.latest=Esta es la última versión.
|
||||
update.no_browser=No se puede abrir en el navegador del sistema. Pero, hemos copiado el enlace a su portapapeles y puede abrirlo manualmente.
|
||||
update.tooltip=Actualización
|
||||
|
||||
@@ -912,7 +912,6 @@ update.found=アップデートが利用可能です!
|
||||
update.newest_version=最新バージョン:%s
|
||||
update.bubble.title=利用可能な更新:%s
|
||||
update.bubble.subtitle=更新するにはここをクリックしてください
|
||||
update.note=警告:ベータ版には、より多くの機能とバグ修正が含まれている可能性がありますが、エラーが発生する可能性もあります。
|
||||
update.latest=これは最新バージョンです。
|
||||
update.no_browser=ブラウザを開くことができません。リンクがクリップボードにコピーされました。ブラウザのアドレスバーに貼り付けて更新します。
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -579,6 +579,8 @@ launch.advice.forge37_0_60=鍛三七點〇點五九與舊者弗兼爪哇十七
|
||||
launch.advice.java8_1_13=礦藝一點一三以晉但行於爪哇八與晉者。宜行以爪哇八與晉者。
|
||||
launch.advice.java8_51_1_13=爪哇之前一點八點〇之五一者或崩礦藝一點一三。宜新爪哇於一點八點〇之五一以晉,然後復啟。
|
||||
launch.advice.java9=礦藝之前一點一三而有改囊者弗見兼於爪哇九與晉者。宜啟以爪哇八。
|
||||
launch.advice.lwjgl_3_4_1=戲之是版有已知之謬,行戲之時或致頓滯。\nHMCL 可假爪哇 Agent 自優其戲,以弭是患。君其許 HMCL 自優其戲乎?\n誠有謬,可至於「置設 → 貫用」,廢「許改戲」之項。
|
||||
launch.advice.lwjgl_3_4_1.title=君其許 HMCL 自優其戲乎?
|
||||
launch.advice.modded_java=改囊或弗見兼於爪哇之新者。宜以爪哇 %s 啟礦藝 %s。
|
||||
launch.advice.modlauncher8=鍛版之所行弗兼於爪哇之所行。宜新鍛。
|
||||
launch.advice.newer_java=余識君之啟戲以舊爪哇也,蓋改囊或是以崩戲也。宜新爪哇於八,然後復啟。
|
||||
@@ -1127,6 +1129,8 @@ settings.game.working_directory.hint=夫于「運徑」擇「例獨」者,可
|
||||
settings.icon=戲徵
|
||||
|
||||
settings.launcher=啟者置設
|
||||
settings.launcher.allow_auto_agent=許改戲
|
||||
settings.launcher.allow_auto_agent.subtitle=許 HMCL 假附加爪哇 Agent 以改戲,而優其戲感。
|
||||
settings.launcher.appearance=外觀
|
||||
settings.launcher.common_path.tooltip=啟者置戲資與府庫於是。倘戲有成案,則弗取公庫之案。
|
||||
settings.launcher.debug=勘誤
|
||||
@@ -1223,7 +1227,6 @@ update.found=尋得新者
|
||||
update.newest_version=至新:%s
|
||||
update.bubble.title=尋得更新:%s
|
||||
update.bubble.subtitle=是擊以晉
|
||||
update.note=开發版與預版,虽增功、修阙,然亦恐生他患。
|
||||
update.latest=當版至新
|
||||
update.no_browser=無啟瀏覽器。址固鈔訖,君可自粘之以啟頁。
|
||||
update.tooltip=迭更
|
||||
|
||||
@@ -1392,7 +1392,6 @@ update.found=Доступно обновление!
|
||||
update.newest_version=Последняя версия: %s
|
||||
update.bubble.title=Доступно обновление: %s
|
||||
update.bubble.subtitle=Кликните здесь для обновления
|
||||
update.note=Предупреждение: Бета-версии могут иметь больше функциональных возможностей и исправлений ошибок, но также и больше возможных ошибок.
|
||||
update.latest=Это последняя версия.
|
||||
update.no_browser=Не удалось открыть браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.
|
||||
update.tooltip=Обновить
|
||||
|
||||
@@ -1343,7 +1343,6 @@ update.found=Доступне оновлення!
|
||||
update.newest_version=Остання версія: %s
|
||||
update.bubble.title=Доступне оновлення: %s
|
||||
update.bubble.subtitle=Натисніть тут, щоб оновити
|
||||
update.note=Канали Бета та Нічний можуть мати більше функцій або виправлень, але вони також мають більше потенційних проблем.
|
||||
update.latest=Це остання версія
|
||||
update.no_browser=Не вдається відкрити в системному браузері. Але ми скопіювали посилання до вашого буфера обміну, і ви можете відкрити його вручну.
|
||||
update.tooltip=Оновити
|
||||
|
||||
@@ -164,6 +164,9 @@ account.skin.upload.failed=外觀上傳失敗
|
||||
account.skin.invalid_skin=無法識別的外觀檔案
|
||||
account.username=使用者名稱
|
||||
|
||||
addon.download.title.release=Minecraft %s
|
||||
addon.download.title.snapshot=Minecraft %s (快照)
|
||||
|
||||
archive.author=作者
|
||||
archive.date=發布日期
|
||||
archive.file.name=檔案名稱
|
||||
@@ -612,6 +615,8 @@ launch.advice.forge37_0_60=Forge 37.0.59 及更低版本與 Java 17 不相容。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 及更高版本僅支援 Java 8 或更高版本。請使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議你到 https://java.com 安裝最新版的 Java 8。
|
||||
launch.advice.java9=低於 (包含) 1.13 的有安裝模組的 Minecraft 版本不支援 Java 9 或更高版本。請使用 Java 8。
|
||||
launch.advice.lwjgl_3_4_1=當前遊戲版本存在已知問題,可能在遊戲中發生卡頓。\nHMCL 可以 (透過 Java Agent) 自動最佳化遊戲解決此問題。是否允許 HMCL 自動最佳化遊戲?\n如遇到問題,你可以在「設定 → 一般」中關閉「允許 HMCL 修改遊戲」選項。
|
||||
launch.advice.lwjgl_3_4_1.title=是否允許 HMCL 自動最佳化遊戲?
|
||||
launch.advice.modded_java=部分模組可能與高版本 Java 不相容。建議使用 Java %s 啟動 Minecraft %s。
|
||||
launch.advice.modlauncher8=你所使用的 Forge 版本與目前使用的 Java 不相容。請更新 Forge。
|
||||
launch.advice.newer_java=偵測到你正在使用舊版本 Java 啟動遊戲,這可能導致部分模組引發遊戲崩潰。建議更新至 Java 8 後再次啟動。
|
||||
@@ -1202,6 +1207,8 @@ settings.game.working_directory.hint=在「執行路徑」選項中選取「各
|
||||
settings.icon=遊戲圖示
|
||||
|
||||
settings.launcher=啟動器設定
|
||||
settings.launcher.allow_auto_agent=允許 HMCL 修改遊戲
|
||||
settings.launcher.allow_auto_agent.subtitle=允許 HMCL 透過附加 Java Agent 修改遊戲以改善遊戲體驗。
|
||||
settings.launcher.appearance=外觀
|
||||
settings.launcher.brightness=主題模式
|
||||
settings.launcher.brightness.auto=跟隨系統設定
|
||||
@@ -1383,7 +1390,8 @@ update.found=發現到更新
|
||||
update.newest_version=最新版本為:%s
|
||||
update.bubble.title=發現更新:%s
|
||||
update.bubble.subtitle=點選此處進行升級
|
||||
update.note=開發版與預覽版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
|
||||
update.note.stable=適合優先追求軟體穩定性的使用者使用。\n新功能在經過充分測試後才會被添加到穩定版中。
|
||||
update.note.dev=適合希望優先體驗新功能的使用者使用。\n開發版會包含最新功能和錯誤修復,但未經充分測試,可能會存在更多問題。
|
||||
update.latest=目前版本為最新版本
|
||||
update.no_browser=無法開啟瀏覽器。網址已經複製到剪貼簿了,你可以手動複製網址開啟頁面。
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -166,6 +166,9 @@ account.skin.upload.failed=皮肤上传失败
|
||||
account.skin.invalid_skin=无法识别的皮肤文件
|
||||
account.username=用户名
|
||||
|
||||
addon.download.title.release=Minecraft %s
|
||||
addon.download.title.snapshot=Minecraft %s (快照)
|
||||
|
||||
archive.author=作者
|
||||
archive.date=发布日期
|
||||
archive.file.name=文件名
|
||||
@@ -617,6 +620,8 @@ launch.advice.forge37_0_60=Forge 37.0.59 及更低版本与 Java 17 不兼容。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 及更高版本只能在 Java 8 或更高版本上运行。请使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低于 1.8.0_51 的 Java 版本可能会导致 Minecraft 1.13 崩溃。建议更新 Java 至 1.8.0_51 或更高版本后再次启动。
|
||||
launch.advice.java9=低于 1.13 的有安装模组的 Minecraft 版本与 Java 9 或更高版本不兼容。请使用 Java 8。
|
||||
launch.advice.lwjgl_3_4_1=当前游戏版本存在已知问题,可能在游戏中发生卡顿。\nHMCL 可以 (通过 Java Agent) 自动优化游戏解决此问题。是否允许 HMCL 自动优化游戏?\n如遇到问题,你可以在“设置 → 通用”中关闭“允许 HMCL 修改游戏”选项。
|
||||
launch.advice.lwjgl_3_4_1.title=是否允许 HMCL 自动优化游戏?
|
||||
launch.advice.modded_java=部分模组可能与高版本 Java 不兼容。建议使用 Java %s 启动 Minecraft %s。
|
||||
launch.advice.modlauncher8=你所使用的 Forge 版本与当前使用的 Java 不兼容。请更新 Forge。
|
||||
launch.advice.newer_java=检测到你正在使用旧版本 Java 启动游戏,这可能导致部分模组引发游戏崩溃。建议更新至 Java 8 后再次启动。
|
||||
@@ -1207,6 +1212,8 @@ settings.game.working_directory.hint=在“版本隔离”中选择“各实例
|
||||
settings.icon=游戏图标
|
||||
|
||||
settings.launcher=启动器设置
|
||||
settings.launcher.allow_auto_agent=允许 HMCL 修改游戏
|
||||
settings.launcher.allow_auto_agent.subtitle=允许 HMCL 通过附加 Java Agent 修改游戏以改善游戏体验。
|
||||
settings.launcher.appearance=外观
|
||||
settings.launcher.brightness=主题模式
|
||||
settings.launcher.brightness.auto=跟随系统设置
|
||||
@@ -1388,7 +1395,8 @@ update.found=发现更新
|
||||
update.newest_version=最新版本为:%s
|
||||
update.bubble.title=发现更新:%s
|
||||
update.bubble.subtitle=点击此处进行升级
|
||||
update.note=开发版与预览版包含更多的功能以及错误修复,但也可能会包含其他的问题。
|
||||
update.note.stable=适合优先追求软件稳定性的用户使用。\n新功能在经过充分测试后才会被添加到稳定版中。
|
||||
update.note.dev=适合希望优先体验新功能的用户使用。\n包含最新功能和错误修复,但未经充分测试,可能会存在更多问题。
|
||||
update.latest=当前版本为最新版本
|
||||
update.no_browser=无法打开浏览器。网址已经复制到剪贴板,你可以手动粘贴网址打开页面。
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -222,7 +222,7 @@ public final class World {
|
||||
return isLocked(getSessionLockFile());
|
||||
}
|
||||
|
||||
public boolean supportDatapacks() {
|
||||
public boolean supportDataPacks() {
|
||||
return getGameVersion() != null && getGameVersion().isAtLeast("1.13", "17w43a");
|
||||
}
|
||||
|
||||
|
||||
+24
-24
@@ -39,14 +39,14 @@ import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class Datapack {
|
||||
public class DataPack {
|
||||
private static final String DISABLED_EXT = "disabled";
|
||||
private static final String ZIP_EXT = "zip";
|
||||
|
||||
private final Path path;
|
||||
private final ObservableList<Pack> packs = FXCollections.observableArrayList();
|
||||
|
||||
public Datapack(Path path) {
|
||||
public DataPack(Path path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ public class Datapack {
|
||||
return packs;
|
||||
}
|
||||
|
||||
public static void installPack(Path sourceDatapackPath, Path targetDatapackDirectory, GameVersionNumber gameVersionNumber) throws IOException {
|
||||
public static void installPack(Path sourceDataPackPath, Path targetDataPackDirectory, GameVersionNumber gameVersionNumber) throws IOException {
|
||||
boolean containsMultiplePacks;
|
||||
Set<String> packs = new HashSet<>();
|
||||
try (FileSystem fs = CompressingUtils.readonly(sourceDatapackPath).setAutoDetectEncoding(true).build()) {
|
||||
Path datapacks = fs.getPath("datapacks");
|
||||
try (FileSystem fs = CompressingUtils.readonly(sourceDataPackPath).setAutoDetectEncoding(true).build()) {
|
||||
Path dataPacks = fs.getPath("datapacks");
|
||||
Path mcmeta = fs.getPath("pack.mcmeta");
|
||||
|
||||
if (Files.exists(datapacks)) {
|
||||
if (Files.exists(dataPacks)) {
|
||||
containsMultiplePacks = true;
|
||||
} else if (Files.exists(mcmeta)) {
|
||||
containsMultiplePacks = false;
|
||||
@@ -74,14 +74,14 @@ public class Datapack {
|
||||
}
|
||||
|
||||
if (containsMultiplePacks) {
|
||||
try (Stream<Path> s = Files.list(datapacks)) {
|
||||
try (Stream<Path> s = Files.list(dataPacks)) {
|
||||
packs = s.map(FileUtils::getNameWithoutExtension).collect(Collectors.toSet());
|
||||
}
|
||||
} else {
|
||||
packs.add(FileUtils.getNameWithoutExtension(sourceDatapackPath));
|
||||
packs.add(FileUtils.getNameWithoutExtension(sourceDataPackPath));
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDatapackDirectory)) {
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDataPackDirectory)) {
|
||||
for (Path dir : stream) {
|
||||
String packName = FileUtils.getName(dir);
|
||||
if (FileUtils.getExtension(dir).equals(DISABLED_EXT)) {
|
||||
@@ -100,9 +100,9 @@ public class Datapack {
|
||||
}
|
||||
|
||||
if (!containsMultiplePacks) {
|
||||
FileUtils.copyFile(sourceDatapackPath, targetDatapackDirectory.resolve(FileUtils.getName(sourceDatapackPath)));
|
||||
FileUtils.copyFile(sourceDataPackPath, targetDataPackDirectory.resolve(FileUtils.getName(sourceDataPackPath)));
|
||||
} else {
|
||||
new Unzipper(sourceDatapackPath, targetDatapackDirectory)
|
||||
new Unzipper(sourceDataPackPath, targetDataPackDirectory)
|
||||
.setReplaceExistentFile(true)
|
||||
.setSubDirectory("/datapacks/")
|
||||
.unzip();
|
||||
@@ -113,14 +113,14 @@ public class Datapack {
|
||||
&& gameVersionNumber.compareTo("26.1-snapshot-6") >= 0;
|
||||
|
||||
if (useNewResourcePath) {
|
||||
Files.createDirectories(targetDatapackDirectory.getParent().resolve("resourcepacks"));
|
||||
targetResourceZipPath = targetDatapackDirectory.getParent().resolve("resourcepacks/resources.zip");
|
||||
Files.createDirectories(targetDataPackDirectory.getParent().resolve("resourcepacks"));
|
||||
targetResourceZipPath = targetDataPackDirectory.getParent().resolve("resourcepacks/resources.zip");
|
||||
} else {
|
||||
targetResourceZipPath = targetDatapackDirectory.getParent().resolve("resources.zip");
|
||||
targetResourceZipPath = targetDataPackDirectory.getParent().resolve("resources.zip");
|
||||
}
|
||||
|
||||
try (FileSystem outputResourcesZipFS = CompressingUtils.createWritableZipFileSystem(targetResourceZipPath);
|
||||
FileSystem inputPackZipFS = CompressingUtils.createReadOnlyZipFileSystem(sourceDatapackPath)) {
|
||||
FileSystem inputPackZipFS = CompressingUtils.createReadOnlyZipFileSystem(sourceDataPackPath)) {
|
||||
Path resourcesZip = inputPackZipFS.getPath("resources.zip");
|
||||
if (Files.isRegularFile(resourcesZip)) {
|
||||
Path tempResourcesFile = Files.createTempFile("hmcl", ".zip");
|
||||
@@ -229,14 +229,14 @@ public class Datapack {
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Pack> parsePack(Path datapackPath, boolean isDirectory, String name, Path mcmetaPath) {
|
||||
private Optional<Pack> parsePack(Path dataPackPath, boolean isDirectory, String name, Path mcmetaPath) {
|
||||
try {
|
||||
PackMcMeta mcMeta = JsonUtils.fromNonNullJson(Files.readString(mcmetaPath), PackMcMeta.class);
|
||||
return Optional.of(new Pack(datapackPath, isDirectory, name, mcMeta.pack().description(), this));
|
||||
return Optional.of(new Pack(dataPackPath, isDirectory, name, mcMeta.pack().description(), this));
|
||||
} catch (JsonParseException e) {
|
||||
LOG.warning("Invalid pack.mcmeta format in " + datapackPath, e);
|
||||
LOG.warning("Invalid pack.mcmeta format in " + dataPackPath, e);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("IO error reading " + datapackPath, e);
|
||||
LOG.warning("IO error reading " + dataPackPath, e);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -248,14 +248,14 @@ public class Datapack {
|
||||
private final BooleanProperty activeProperty;
|
||||
private final String id;
|
||||
private final LocalModFile.Description description;
|
||||
private final Datapack parentDatapack;
|
||||
private final DataPack parentDataPack;
|
||||
|
||||
public Pack(Path path, boolean isDirectory, String id, LocalModFile.Description description, Datapack parentDatapack) {
|
||||
public Pack(Path path, boolean isDirectory, String id, LocalModFile.Description description, DataPack parentDataPack) {
|
||||
this.path = path;
|
||||
this.isDirectory = isDirectory;
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
this.parentDatapack = parentDatapack;
|
||||
this.parentDataPack = parentDataPack;
|
||||
|
||||
this.statusFile = initializeStatusFile(path, isDirectory);
|
||||
this.activeProperty = initializeActiveProperty();
|
||||
@@ -313,8 +313,8 @@ public class Datapack {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Datapack getParentDatapack() {
|
||||
return parentDatapack;
|
||||
public DataPack getParentDataPack() {
|
||||
return parentDataPack;
|
||||
}
|
||||
|
||||
public BooleanProperty activeProperty() {
|
||||
@@ -86,4 +86,9 @@ public final class SettingsMap {
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SettingsMap" + map;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.util.versioning;
|
||||
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -189,6 +190,32 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
return buildDebugString().toString();
|
||||
}
|
||||
|
||||
public static @Nullable GameVersionNumber getReleaseOfSnapshot(GameVersionNumber gameVersion) {
|
||||
if (gameVersion instanceof Release release) {
|
||||
if (release.getEaType() == Release.ReleaseType.GA) {
|
||||
return null;
|
||||
}
|
||||
if (release.getPatch() > 0) {
|
||||
return asGameVersion(release.getMajor() + "." + release.getMinor() + "." + release.getPatch());
|
||||
} else {
|
||||
return asGameVersion(release.getMajor() + "." + release.getMinor());
|
||||
}
|
||||
}
|
||||
|
||||
if (gameVersion instanceof LegacySnapshot snapshot) {
|
||||
String[] defaultVersions = Versions.DEFAULT_GAME_VERSIONS;
|
||||
for (int i = defaultVersions.length - 1; i >= 0; i--) {
|
||||
Release gaRelease = (Release) asGameVersion(defaultVersions[i]);
|
||||
|
||||
if (gaRelease.compareToSnapshot(snapshot) > 0) {
|
||||
return gaRelease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final class Old extends GameVersionNumber {
|
||||
static Old parse(String value) {
|
||||
if (value.isEmpty())
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.asGameVersion;
|
||||
import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.getReleaseOfSnapshot;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
@@ -507,4 +508,14 @@ public final class GameVersionNumberTest {
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", true));
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetReleaseOfSnapshot() {
|
||||
assertEquals(asGameVersion("1.21.11"), getReleaseOfSnapshot(asGameVersion("25w45a")));
|
||||
assertEquals(asGameVersion("1.21.11"), getReleaseOfSnapshot(asGameVersion("25w45a_unobfuscated")));
|
||||
assertEquals(asGameVersion("1.21.11"), getReleaseOfSnapshot(asGameVersion("1.21.11-pre3")));
|
||||
assertEquals(asGameVersion("26.1"), getReleaseOfSnapshot(asGameVersion("26.1-snapshot-9")));
|
||||
assertNull(getReleaseOfSnapshot(asGameVersion("26.1")));
|
||||
assertNull(getReleaseOfSnapshot(asGameVersion("20w14infinite")));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
[versions]
|
||||
hmclauncher = "3.7.0.1"
|
||||
jetbrains-annotations = "26.1.0"
|
||||
kala-compress = "1.27.1-1"
|
||||
kala-compress = "1.27.1-3"
|
||||
simple-png-javafx = "0.3.0"
|
||||
gson = "2.13.2"
|
||||
toml4j = "0.7.2"
|
||||
@@ -9,15 +9,16 @@ xz = "1.11"
|
||||
lz4 = "1.10.4.1"
|
||||
fx-gson = "5.0.0"
|
||||
constant-pool-scanner = "1.2"
|
||||
hello-nbt = "0.2.0"
|
||||
hello-nbt = "0.3.0"
|
||||
nanohttpd = "2.3.1"
|
||||
jsoup = "1.21.2"
|
||||
chardet = "2.5.0"
|
||||
twelvemonkeys = "3.13.0"
|
||||
twelvemonkeys = "3.13.1"
|
||||
jna = "5.18.1"
|
||||
pci-ids = "0.4.0"
|
||||
java-info = "1.0"
|
||||
authlib-injector = "1.2.7"
|
||||
lwjgl-unsafe-agent = "1.0"
|
||||
monet-fx = "0.4.0"
|
||||
terracotta = "0.4.2"
|
||||
nayuki-qrcodegen = "1.8.0"
|
||||
@@ -51,6 +52,7 @@ jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" }
|
||||
pci-ids = { module = "org.glavo:pci-ids", version.ref = "pci-ids" }
|
||||
java-info = { module = "org.glavo:java-info", version.ref = "java-info" }
|
||||
authlib-injector = { module = "org.glavo.hmcl:authlib-injector", version.ref = "authlib-injector" }
|
||||
lwjgl-unsafe-agent = { module = "org.glavo:lwjgl-unsafe-agent", version.ref = "lwjgl-unsafe-agent" }
|
||||
monet-fx = { module = "org.glavo:MonetFX", version.ref = "monet-fx" }
|
||||
nayuki-qrcodegen = { module = "io.nayuki:qrcodegen", version.ref = "nayuki-qrcodegen" }
|
||||
|
||||
|
||||
Reference in new issue
Block a user