Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40d21bbe24 | ||
|
|
d0be7efca3 | ||
|
|
3bed03c535 | ||
|
|
d295e606a9 | ||
|
|
36afe32146 | ||
|
|
87dbbdd96f | ||
|
|
9b8e67b1b3 | ||
|
|
f5a5e6a6da | ||
|
|
f0466c1d8e | ||
|
|
903ecbb127 | ||
|
|
4c6f3aeb99 | ||
|
|
d6a4b3480f | ||
|
|
5e84e5bcd6 | ||
|
|
6ce7af05b2 | ||
|
|
0d62a12353 | ||
|
|
880aab0ba5 | ||
|
|
0b64e89aaa | ||
|
|
7b6379674b | ||
|
|
55f4b0d8fc | ||
|
|
863c3c9c7f | ||
|
|
1f5fb983b0 | ||
|
|
856d5ec5af | ||
|
|
20a5670876 | ||
|
|
bdf6a58be8 | ||
|
|
7c74dd438f |
No files matched your search
@@ -0,0 +1,65 @@
|
||||
# Default: normalize all text files to LF in the repository and on checkout.
|
||||
# Git auto-detects binary files and leaves them untouched.
|
||||
* text=auto eol=lf
|
||||
|
||||
# ─── Explicitly text – source & build ────────────────────────────────────────
|
||||
*.java text eol=lf
|
||||
*.kt text eol=lf
|
||||
*.kts text eol=lf
|
||||
*.groovy text eol=lf
|
||||
*.gradle text eol=lf
|
||||
|
||||
# ─── Configuration & data ─────────────────────────────────────────────────────
|
||||
*.properties text eol=lf
|
||||
*.toml text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.json text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.csv text eol=lf
|
||||
*.html text eol=lf
|
||||
*.css text eol=lf
|
||||
*.txt text eol=lf
|
||||
*.md text eol=lf
|
||||
*.svg text eol=lf
|
||||
*.MF text eol=lf
|
||||
|
||||
# Gradle dependency-lock files
|
||||
*.lockfile text eol=lf
|
||||
|
||||
# ─── Scripts ──────────────────────────────────────────────────────────────────
|
||||
# Unix shell scripts must always use LF so they stay executable on Linux/macOS.
|
||||
*.sh text eol=lf
|
||||
gradlew text eol=lf
|
||||
|
||||
# CI pipeline scripts
|
||||
Jenkinsfile text eol=lf
|
||||
|
||||
# Windows batch files must keep CRLF so cmd.exe can parse them.
|
||||
*.bat text eol=crlf
|
||||
|
||||
# ─── Binary files ─────────────────────────────────────────────────────────────
|
||||
# Raster / animated images
|
||||
*.png binary
|
||||
*.apng binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.webp binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
|
||||
# Archives & compiled artifacts
|
||||
*.jar binary
|
||||
*.zip binary
|
||||
*.class binary
|
||||
|
||||
# Cryptographic / certificate files
|
||||
*.der binary
|
||||
|
||||
# Minecraft schematic
|
||||
*.litematic binary
|
||||
|
||||
# Compiled / intermediate build artefacts
|
||||
*.bin binary
|
||||
*.probe binary
|
||||
|
||||
@@ -5,6 +5,7 @@ hs_err_pid*
|
||||
*.hprof
|
||||
|
||||
.gradle
|
||||
gradle.properties
|
||||
|
||||
*.lck
|
||||
*.1
|
||||
|
||||
@@ -19,9 +19,3 @@ These rules apply to all Java code written or modified in this repository.
|
||||
- Documentation must use `///` Markdown-style Javadoc comments.
|
||||
- Keep documentation accurate and specific to the actual behavior, constraints, and side effects.
|
||||
- Add concise implementation comments inside complex logic whenever they materially improve readability or explain non-obvious behavior.
|
||||
|
||||
## Gradle
|
||||
|
||||
- When invoking Gradle in this repository, always set `GRADLE_USER_HOME` to the workspace-local `.gradle-user-home` directory.
|
||||
- Prefer commands such as `./gradlew -g .gradle-user-home ...` or the equivalent environment-variable-based configuration.
|
||||
- When running Gradle `test` tasks, use a higher timeout of ten minutes.
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.ui.versions.ModTranslations;
|
||||
@@ -47,33 +46,42 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor
|
||||
return getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder);
|
||||
}
|
||||
|
||||
Set<String> englishSearchFiltersSet = new HashSet<>(INITIAL_CAPACITY);
|
||||
Set<String> englishSearchFiltersSet = new LinkedHashSet<>(INITIAL_CAPACITY);
|
||||
|
||||
int count = 0;
|
||||
for (ModTranslations.Mod mod : ModTranslations.getTranslationsByRepositoryType(getType()).searchMod(searchFilter)) {
|
||||
for (String englishWord : StringUtils.tokenize(StringUtils.isNotBlank(mod.getSubname()) ? mod.getSubname() : mod.getName())) {
|
||||
if (englishSearchFiltersSet.contains(englishWord)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
englishSearchFiltersSet.add(englishWord);
|
||||
String englishSearchFilter = String.join(" ", StringUtils.tokenize(StringUtils.isNotBlank(mod.getSubname()) ? mod.getSubname() : mod.getName()));
|
||||
if (StringUtils.isNotBlank(englishSearchFilter)) {
|
||||
englishSearchFiltersSet.add(englishSearchFilter);
|
||||
}
|
||||
|
||||
count++;
|
||||
if (count >= 3) break;
|
||||
}
|
||||
|
||||
if (englishSearchFiltersSet.isEmpty()) {
|
||||
return getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder);
|
||||
}
|
||||
|
||||
RemoteMod[] searchResultArray = new RemoteMod[pageSize];
|
||||
int totalPages, chineseIndex = 0, englishIndex = pageSize - 1;
|
||||
{
|
||||
SearchResult searchResult = getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder);
|
||||
for (Iterator<RemoteMod> iterator = searchResult.getUnsortedResults().iterator(); iterator.hasNext(); ) {
|
||||
SearchResult searchResult = null;
|
||||
List<RemoteMod> remoteMods = List.of();
|
||||
for (String englishSearchFilter : englishSearchFiltersSet) {
|
||||
searchResult = getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, englishSearchFilter, getBackedRemoteModRepositorySortOrder(), sortOrder);
|
||||
remoteMods = searchResult.getUnsortedResults().toList();
|
||||
if (!remoteMods.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (RemoteMod remoteMod : remoteMods) {
|
||||
if (chineseIndex > englishIndex) {
|
||||
LOG.warning("Too many search results! Are the backed remote mod repository broken? Or are the API broken?");
|
||||
continue;
|
||||
}
|
||||
|
||||
RemoteMod remoteMod = iterator.next();
|
||||
ModTranslations.Mod chineseTranslation = ModTranslations.getTranslationsByRepositoryType(getType()).getModByCurseForgeId(remoteMod.getSlug());
|
||||
if (chineseTranslation != null && !StringUtils.isBlank(chineseTranslation.getName()) && StringUtils.containsChinese(chineseTranslation.getName())) {
|
||||
searchResultArray[chineseIndex++] = remoteMod;
|
||||
@@ -110,8 +118,8 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
|
||||
return getBackedRemoteModRepository().getRemoteVersionByLocalFile(localModFile, file);
|
||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(Path file) throws IOException {
|
||||
return getBackedRemoteModRepository().getRemoteVersionByLocalFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,7 +30,10 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@@ -172,6 +175,7 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
|
||||
public static class Factory implements OAuth.Callback {
|
||||
public final EventManager<GrantDeviceCodeEvent> onGrantDeviceCode = new EventManager<>();
|
||||
public final EventManager<LoginCompletedDeviceCodeEvent> onLoginCompletedDeviceCode = new EventManager<>();
|
||||
public final EventManager<OpenBrowserEvent> onOpenBrowserAuthorizationCode = new EventManager<>();
|
||||
public final EventManager<OpenBrowserEvent> onOpenBrowserDevice = new EventManager<>();
|
||||
|
||||
@@ -199,6 +203,11 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
onGrantDeviceCode.fireEvent(new GrantDeviceCodeEvent(this, userCode, verificationURI));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loginCompletedDeviceCode() {
|
||||
onLoginCompletedDeviceCode.fireEvent(new LoginCompletedDeviceCodeEvent(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openBrowser(OAuth.GrantFlow grantFlow, String url) throws IOException {
|
||||
lastlyOpenedURL = url;
|
||||
@@ -235,6 +244,12 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginCompletedDeviceCodeEvent extends Event {
|
||||
public LoginCompletedDeviceCodeEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
|
||||
public static class OpenBrowserEvent extends Event {
|
||||
private final String url;
|
||||
|
||||
|
||||
@@ -1584,9 +1584,8 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Intelligently determines the popup position to prevent the menu from exceeding screen boundaries.
|
||||
* Supports multi-monitor setups by detecting the current screen where the component is located.
|
||||
* Now handles first-time popup display by forcing layout measurement.
|
||||
* Intelligently determines the popup position to prevent the menu from exceeding screen boundaries,
|
||||
* window boundaries, and clipping containers like ScrollPane.
|
||||
*
|
||||
* @param root the root node to calculate position relative to
|
||||
* @param popupInstance the popup instance to position
|
||||
@@ -1595,8 +1594,10 @@ public final class FXUtils {
|
||||
public static JFXPopup.PopupVPosition determineOptimalPopupPosition(Node root, JFXPopup popupInstance) {
|
||||
// Get the screen bounds in screen coordinates
|
||||
Bounds screenBounds = root.localToScreen(root.getBoundsInLocal());
|
||||
if (screenBounds == null) {
|
||||
return JFXPopup.PopupVPosition.TOP; // Fallback
|
||||
}
|
||||
|
||||
// Convert Bounds to Rectangle2D for getScreensForRectangle method
|
||||
Rectangle2D boundsRect = new Rectangle2D(
|
||||
screenBounds.getMinX(), screenBounds.getMinY(),
|
||||
screenBounds.getWidth(), screenBounds.getHeight()
|
||||
@@ -1607,13 +1608,36 @@ public final class FXUtils {
|
||||
Screen currentScreen = screens.isEmpty() ? Screen.getPrimary() : screens.get(0);
|
||||
Rectangle2D visualBounds = currentScreen.getVisualBounds();
|
||||
|
||||
double screenHeight = visualBounds.getHeight();
|
||||
double screenMinY = visualBounds.getMinY();
|
||||
double itemScreenY = screenBounds.getMinY();
|
||||
// 1. Initial bounds based on the physical Screen limits
|
||||
double clipMinY = visualBounds.getMinY();
|
||||
double clipMaxY = visualBounds.getMaxY();
|
||||
|
||||
// Calculate available space relative to the current screen
|
||||
double availableSpaceAbove = itemScreenY - screenMinY;
|
||||
double availableSpaceBelow = screenMinY + screenHeight - itemScreenY - root.getBoundsInLocal().getHeight();
|
||||
// 2. Constrain by Window (Stage) bounds so it doesn't overflow the application window
|
||||
if (root.getScene() != null && root.getScene().getWindow() != null) {
|
||||
javafx.stage.Window window = root.getScene().getWindow();
|
||||
clipMinY = Math.max(clipMinY, window.getY());
|
||||
clipMaxY = Math.min(clipMaxY, window.getY() + window.getHeight());
|
||||
}
|
||||
|
||||
// 3. Constrain by parent ScrollPanes to handle clipping within the UI layout
|
||||
Node parent = root.getParent();
|
||||
while (parent != null) {
|
||||
if (parent instanceof ScrollPane) {
|
||||
Bounds spBounds = parent.localToScreen(parent.getBoundsInLocal());
|
||||
if (spBounds != null) {
|
||||
clipMinY = Math.max(clipMinY, spBounds.getMinY());
|
||||
clipMaxY = Math.min(clipMaxY, spBounds.getMaxY());
|
||||
}
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
|
||||
double itemScreenY = screenBounds.getMinY();
|
||||
double itemScreenMaxY = screenBounds.getMaxY();
|
||||
|
||||
// Calculate available space relative to the narrowest calculated clipping bounds
|
||||
double availableSpaceAbove = itemScreenY - clipMinY;
|
||||
double availableSpaceBelow = clipMaxY - itemScreenMaxY;
|
||||
|
||||
// Get popup content and ensure it's properly measured
|
||||
Region popupContent = popupInstance.getPopupContent();
|
||||
@@ -1629,8 +1653,7 @@ public final class FXUtils {
|
||||
menuHeight = popupContent.getHeight();
|
||||
if (menuHeight <= 0) {
|
||||
// Fallback: estimate based on number of menu items
|
||||
// Each menu item is roughly 36px height + separators + padding
|
||||
menuHeight = 300; // Conservative estimate for the current menu structure
|
||||
menuHeight = 300;
|
||||
}
|
||||
} else {
|
||||
menuHeight = popupContent.getHeight();
|
||||
@@ -1639,9 +1662,12 @@ public final class FXUtils {
|
||||
// Add some margin for safety
|
||||
menuHeight += 20;
|
||||
|
||||
return (availableSpaceAbove > menuHeight && availableSpaceBelow < menuHeight)
|
||||
? JFXPopup.PopupVPosition.BOTTOM // Show menu below the button, expanding downward
|
||||
: JFXPopup.PopupVPosition.TOP; // Show menu above the button, expanding upward
|
||||
// Logic: if there isn't enough space below, AND there is more space above, open upwards.
|
||||
if (availableSpaceBelow < menuHeight && availableSpaceAbove > availableSpaceBelow) {
|
||||
return JFXPopup.PopupVPosition.BOTTOM; // Show menu above the button, expanding upward
|
||||
} else {
|
||||
return JFXPopup.PopupVPosition.TOP; // Show menu below the button, expanding downward
|
||||
}
|
||||
}
|
||||
|
||||
public static void useJFXContextMenu(TextInputControl control) {
|
||||
|
||||
@@ -60,6 +60,7 @@ public enum SVG {
|
||||
EXPLORE("M12 12Zm0 8q-3.325 0-5.6625-2.3375T4 12Q4 8.675 6.3375 6.3375T12 4q3.325-0 5.6625 2.3375T20 12q0 3.325-2.3375 5.6625T12 20Zm0 2q2.075-0 3.9-.7875t3.175-2.1375q1.35-1.35 2.1375-3.175T22 12q-0-2.075-.7875-3.9T19.075 4.925q-1.35-1.35-3.175-2.1375T12 2q-2.075 0-3.9.7875T4.925 4.925Q3.575 6.275 2.7875 8.1T2 12q0 2.075.7875 3.9T4.925 19.075q1.35 1.35 3.175 2.1375T12 22Zm0-8.5q.625 0 1.0625-.4375T13.5 12t-.4375-1.0625T12 10.5t-1.0625.4375T10.5 12t.4375 1.0625T12 13.5Zm-4.5 3 2-7 7-2-2 7-7 2Z"),
|
||||
EXTENSION("M8.8 21H5Q4.175 21 3.5875 20.4125T3 19V15.2Q4.2 15.2 5.1 14.4375T6 12.5Q6 11.325 5.1 10.5625T3 9.8V6Q3 5.175 3.5875 4.5875T5 4H9Q9 2.95 9.725 2.225T11.5 1.5Q12.55 1.5 13.275 2.225T14 4H18Q18.825 4 19.4125 4.5875T20 6V10Q21.05 10 21.775 10.725T22.5 12.5Q22.5 13.55 21.775 14.275T20 15V19Q20 19.825 19.4125 20.4125T18 21H14.2Q14.2 19.75 13.4125 18.875T11.5 18Q10.375 18 9.5875 18.875T8.8 21ZM5 19H7.125Q7.725 17.35 9.05 16.675T11.5 16Q12.625 16 13.95 16.675T15.875 19H18V13H20Q20.2 13 20.35 12.85T20.5 12.5Q20.5 12.3 20.35 12.15T20 12H18V6H12V4Q12 3.8 11.85 3.65T11.5 3.5Q11.3 3.5 11.15 3.65T11 4V6H5V8.2Q6.35 8.7 7.175 9.875T8 12.5Q8 13.925 7.175 15.1T5 16.8V19ZM11.5 12.5Z"),
|
||||
EXTENSION_FILL("M8.8 21H5q-.825 0-1.4125-.5875T3 19V15.2q1.2 0 2.1-.7625T6 12.5q0-1.175-.9-1.9375T3 9.8V6q0-.825.5875-1.4125T5 4H9q0-1.05.725-1.775T11.5 1.5q1.05 0 1.775.725T14 4h4q.825 0 1.4125.5875T20 6v4q1.05 0 1.775.725T22.5 12.5q0 1.05-.725 1.775T20 15v4q0 .825-.5875 1.4125T18 21H14.2q0-1.25-.7875-2.125T11.5 18q-1.125 0-1.9125.875T8.8 21Z"),
|
||||
FRAME_BUG("M12 19q1.65 0 2.825-1.175T16 15v-4q0-1.65-1.175-2.825T12 7T9.175 8.175T8 11v4q0 1.65 1.175 2.825T12 19m-2-3h4v-2h-4zm0-4h4v-2h-4zm2 9q-1.625 0-3.012-.8T6.8 18H4v-2h2.1q-.075-.5-.088-1T6 14H4v-2h2q0-.5.012-1t.088-1H4V8h2.8q.35-.575.788-1.075T8.6 6.05L7 4.4L8.4 3l2.15 2.15q.7-.225 1.425-.225t1.425.225L15.6 3L17 4.4l-1.65 1.65q.575.375 1.038.862T17.2 8H20v2h-2.1q.075.5.088 1T18 12h2v2h-2q0 .5-.013 1t-.087 1H20v2h-2.8q-.8 1.4-2.187 2.2T12 21M1 6V3q0-.825.588-1.412T3 1h3v2H3v3zm5 17H3q-.825 0-1.412-.587T1 21v-3h2v3h3zm12 0v-2h3v-3h2v3q0 .825-.587 1.413T21 23zm3-17V3h-3V1h3q.825 0 1.413.588T23 3v3z"),
|
||||
FEEDBACK("M12 15Q12.425 15 12.7125 14.7125T13 14Q13 13.575 12.7125 13.2875T12 13Q11.575 13 11.2875 13.2875T11 14Q11 14.425 11.2875 14.7125T12 15ZM11 11H13V5H11V11ZM2 22V4Q2 3.175 2.5875 2.5875T4 2H20Q20.825 2 21.4125 2.5875T22 4V16Q22 16.825 21.4125 17.4125T20 18H6L2 22ZM5.15 16H20V4H4V17.125L5.15 16ZM4 16V4 16Z"),
|
||||
FEEDBACK_FILL("M2 22V4q0-.825.5875-1.4125T4 2H20q.825 0 1.4125.5875T22 4V16q0 .825-.5875 1.4125T20 18H6L2 22Zm10-7q.425 0 .7125-.2875T13 14t-.2875-.7125T12 13t-.7125.2875T11 14t.2875.7125T12 15Zm-1-4h2V5H11v6Z"),
|
||||
FILE_OPEN("M6 22q-0.825 0-1.4125-0.5875T4 20v-16q0-0.825 0.5875-1.4125T6 2h8l6 6v6h-2v-5H13v-5H6v16h9v2H6Zm15.95 0.375L19 19.425v2.225h-2v-5.65h5.65v2h-2.25l2.95 2.95-1.4 1.425Zm-15.95-2.375v-16 16Z"),
|
||||
|
||||
@@ -55,6 +55,7 @@ public abstract class ToolbarListPageSkin<E, P extends ListPageBase<E>> extends
|
||||
toolbar.setAlignment(Pos.CENTER_LEFT);
|
||||
toolbar.setPickOnBounds(false);
|
||||
toolbar.getChildren().setAll(toolbarButtons);
|
||||
FXUtils.setOverflowHidden(toolbar, 8);
|
||||
root.getContent().add(toolbar);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -27,7 +26,6 @@ import javafx.scene.control.Tooltip;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -42,6 +40,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
private final Tooltip tooltip;
|
||||
private final Canvas canvas;
|
||||
private boolean tooltipInstalled;
|
||||
|
||||
private final ObjectProperty<Account> account = new SimpleObjectProperty<Account>() {
|
||||
|
||||
@@ -51,10 +50,10 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
if (account == null) {
|
||||
titleProperty().unbind();
|
||||
subtitleProperty().unbind();
|
||||
tooltip.textProperty().unbind();
|
||||
setTitle(i18n("account.missing"));
|
||||
setSubtitle(i18n("account.missing.add"));
|
||||
tooltip.setText(i18n("account.create"));
|
||||
installTooltip();
|
||||
|
||||
TexturesLoader.unbindAvatar(canvas);
|
||||
TexturesLoader.drawAvatar(canvas, TexturesLoader.getDefaultSkinImage());
|
||||
@@ -62,7 +61,7 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
} else {
|
||||
titleProperty().bind(BindingMapping.of(account, Account::getCharacter));
|
||||
subtitleProperty().bind(accountSubtitle(account));
|
||||
tooltip.textProperty().bind(accountTooltip(account));
|
||||
uninstallTooltip();
|
||||
TexturesLoader.bindAvatar(canvas, account);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +73,6 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
|
||||
public AccountAdvancedListItem(Account account) {
|
||||
tooltip = new Tooltip();
|
||||
FXUtils.installFastTooltip(this, tooltip);
|
||||
|
||||
canvas = new Canvas(32, 32);
|
||||
canvas.setMouseTransparent(true);
|
||||
@@ -103,19 +101,17 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
}
|
||||
}
|
||||
|
||||
private static ObservableValue<String> accountTooltip(Account account) {
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();
|
||||
return Bindings.format("%s (%s) (%s)",
|
||||
BindingMapping.of(account, Account::getCharacter),
|
||||
account.getUsername(),
|
||||
BindingMapping.of(server, AuthlibInjectorServer::getName));
|
||||
} else if (account instanceof YggdrasilAccount) {
|
||||
return Bindings.format("%s (%s)",
|
||||
BindingMapping.of(account, Account::getCharacter),
|
||||
account.getUsername());
|
||||
} else {
|
||||
return BindingMapping.of(account, Account::getCharacter);
|
||||
private void installTooltip() {
|
||||
if (!tooltipInstalled) {
|
||||
FXUtils.installFastTooltip(this, tooltip);
|
||||
tooltipInstalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void uninstallTooltip() {
|
||||
if (tooltipInstalled) {
|
||||
Tooltip.uninstall(this, tooltip);
|
||||
tooltipInstalled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ public final class AccountListPopupMenu extends StackPane {
|
||||
|
||||
for (Account account : Accounts.getAccounts()) {
|
||||
AccountAdvancedListItem item = new AccountAdvancedListItem(account);
|
||||
if (Accounts.getSelectedAccount() == account) item.setActive(true);
|
||||
item.setOnAction(e -> {
|
||||
Accounts.setSelectedAccount(account);
|
||||
if (getScene().getWindow() instanceof JFXPopup popup)
|
||||
|
||||
@@ -124,6 +124,11 @@ public class MicrosoftAccountLoginPane extends JFXDialogLayout implements Dialog
|
||||
step.set(new Step.WaitForScanQrCode(event.getUserCode(), event.getVerificationUri()));
|
||||
}));
|
||||
|
||||
holder.registerWeak(Accounts.OAUTH_CALLBACK.onLoginCompletedDeviceCode, event -> Platform.runLater(() -> {
|
||||
if (step.get() instanceof Step.WaitForScanQrCode)
|
||||
step.set(new Step.DeviceLoginCompleted());
|
||||
}));
|
||||
|
||||
this.step.set(Accounts.OAUTH_CALLBACK.getClientId().isEmpty()
|
||||
? new Step.Init()
|
||||
: new Step.StartAuthorizationCodeLogin());
|
||||
@@ -220,6 +225,13 @@ public class MicrosoftAccountLoginPane extends JFXDialogLayout implements Dialog
|
||||
codeBox.setMaxWidth(USE_PREF_SIZE);
|
||||
|
||||
rootContainer.getChildren().addAll(deviceHint, new Group(qrCode), codeBox);
|
||||
} else if (currentStep instanceof Step.DeviceLoginCompleted) {
|
||||
loginButtonSpinner.setLoading(true);
|
||||
|
||||
var hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
hintPane.setSegment(i18n("account.methods.microsoft.methods.device.hint.completed"));
|
||||
|
||||
rootContainer.getChildren().addAll(hintPane);
|
||||
} else if (currentStep instanceof Step.LoginFailed failed) {
|
||||
btnLogin.setOnAction(e -> this.step.set(new Step.StartAuthorizationCodeLogin()));
|
||||
loginButtonSpinner.setLoading(false);
|
||||
@@ -312,6 +324,10 @@ public class MicrosoftAccountLoginPane extends JFXDialogLayout implements Dialog
|
||||
|
||||
}
|
||||
|
||||
record DeviceLoginCompleted() implements Step {
|
||||
|
||||
}
|
||||
|
||||
record LoginFailed(String message) implements Step {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,13 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_MOD =
|
||||
(downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "mods");
|
||||
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_RESOURCE_PACK =
|
||||
(downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "resourcepacks");
|
||||
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_SHADER =
|
||||
(downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "shaderpacks");
|
||||
|
||||
private final ReadOnlyObjectWrapper<DecoratorPage.State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("download"), -1));
|
||||
private final TabHeader tab;
|
||||
private final TabHeader.Tab<VersionsPage> newGameTab = new TabHeader.Tab<>("newGameTab");
|
||||
@@ -99,9 +106,9 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
page.getActions().add(installLocalModpackButton);
|
||||
return page;
|
||||
}));
|
||||
modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "mods"), true)));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "resourcepacks"), true)));
|
||||
shaderTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofShaderPack((downloadProvider, profile, version, mod, file) -> download(downloadProvider, profile, version, file, "shaderpacks"), true)));
|
||||
modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod(FOR_MOD, true)));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack(FOR_RESOURCE_PACK, true)));
|
||||
shaderTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofShaderPack(FOR_SHADER, true)));
|
||||
worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS)));
|
||||
tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab);
|
||||
|
||||
@@ -213,8 +220,9 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
tab.select(modpackTab, false);
|
||||
}
|
||||
|
||||
public void showResourcepackDownloads() {
|
||||
public DownloadListPage showResourcePackDownloads() {
|
||||
tab.select(resourcePackTab, false);
|
||||
return resourcePackTab.getNode();
|
||||
}
|
||||
|
||||
public DownloadListPage showModDownloads() {
|
||||
|
||||
@@ -187,7 +187,6 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
Controllers.getDownloadPage().showGameDownloads();
|
||||
Controllers.navigate(Controllers.getDownloadPage());
|
||||
});
|
||||
FXUtils.installFastTooltip(downloadItem, i18n("download.hint"));
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
FXUtils.prepareOnMouseEnter(downloadItem, Controllers::prepareDownloadPage);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public class TerracottaControllerPage extends StackPane {
|
||||
if (uninitialized.hasLegacy() && I18n.isUseChinese()) {
|
||||
Object feedback = config().getShownTips().get(FEEDBACK_TIP);
|
||||
if (!(feedback instanceof Number number) || number.intValue() < 1) {
|
||||
Controllers.confirm(i18n("terracotta.feedback.desc"), i18n("terracotta.feedback.title"), () -> {
|
||||
Controllers.confirm(i18n("terracotta.feedback.desc.update"), i18n("terracotta.feedback.title"), () -> {
|
||||
FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK);
|
||||
config().getShownTips().put(FEEDBACK_TIP, 1);
|
||||
}, () -> {
|
||||
|
||||
+11
-11
@@ -18,7 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonFile;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -30,33 +30,33 @@ import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class ModCheckUpdatesTask extends Task<List<LocalModFile.ModUpdate>> {
|
||||
public class AddonCheckUpdatesTask<T extends LocalAddonFile> extends Task<List<LocalAddonFile.AddonUpdate>> {
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final List<Task<LocalModFile.ModUpdate>> dependents;
|
||||
private final List<Task<LocalAddonFile.AddonUpdate>> dependents;
|
||||
|
||||
public ModCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection<LocalModFile> mods) {
|
||||
public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection<T> addons) {
|
||||
this.downloadProvider = downloadProvider;
|
||||
dependents = mods.stream().map(mod ->
|
||||
dependents = addons.stream().map(addon ->
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
LocalModFile.ModUpdate candidate = null;
|
||||
LocalAddonFile.AddonUpdate candidate = null;
|
||||
for (RemoteMod.Type type : RemoteMod.Type.values()) {
|
||||
LocalModFile.ModUpdate update = null;
|
||||
LocalAddonFile.AddonUpdate update = null;
|
||||
try {
|
||||
update = mod.checkUpdates(downloadProvider, gameVersion, type.getRemoteModRepository());
|
||||
update = addon.checkUpdates(downloadProvider, gameVersion, type);
|
||||
} catch (IOException e) {
|
||||
LOG.warning(String.format("Cannot check update for mod %s.", mod.getFileName()), e);
|
||||
LOG.warning(String.format("Cannot check update for addon %s.", addon.getFileName()), e);
|
||||
}
|
||||
if (update == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (candidate == null || candidate.getCandidate().getDatePublished().isBefore(update.getCandidate().getDatePublished())) {
|
||||
if (candidate == null || candidate.targetVersion().getDatePublished().isBefore(update.targetVersion().getDatePublished())) {
|
||||
candidate = update;
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}).setName(mod.getFileName()).setSignificance(TaskSignificance.MAJOR).withCounter("update.checking")
|
||||
}).setName(addon.getFileName()).setSignificance(TaskSignificance.MAJOR).withCounter("update.checking")
|
||||
).toList();
|
||||
|
||||
setStage("update.checking");
|
||||
+68
-54
@@ -29,8 +29,8 @@ import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonFile;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonManager;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -41,10 +41,11 @@ import org.jackhuang.hmcl.ui.construct.JFXCheckBoxTableCell;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.CSVTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -56,50 +57,50 @@ import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
public class AddonUpdatesPage<F extends LocalAddonFile> extends BorderPane implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("mods.check_updates")));
|
||||
|
||||
private final ModManager modManager;
|
||||
private final ObservableList<ModUpdateObject> objects;
|
||||
private final LocalAddonManager<F> localAddonManager;
|
||||
private final ObservableList<AddonUpdateObject> objects;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ModUpdatesPage(ModManager modManager, List<LocalModFile.ModUpdate> updates) {
|
||||
this.modManager = modManager;
|
||||
public AddonUpdatesPage(LocalAddonManager<F> localAddonManager, List<LocalAddonFile.AddonUpdate> updates) {
|
||||
this.localAddonManager = localAddonManager;
|
||||
|
||||
getStyleClass().add("gray-background");
|
||||
|
||||
TableColumn<ModUpdateObject, Boolean> enabledColumn = new TableColumn<>();
|
||||
TableColumn<AddonUpdateObject, Boolean> enabledColumn = new TableColumn<>();
|
||||
var allEnabledBox = new JFXCheckBox();
|
||||
enabledColumn.setStyle("-fx-alignment: CENTER;");
|
||||
enabledColumn.setGraphic(allEnabledBox);
|
||||
enabledColumn.setCellFactory(JFXCheckBoxTableCell.forTableColumn(enabledColumn));
|
||||
setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty);
|
||||
setupCellValueFactory(enabledColumn, AddonUpdateObject::enabledProperty);
|
||||
enabledColumn.setEditable(true);
|
||||
enabledColumn.setMaxWidth(40);
|
||||
enabledColumn.setMinWidth(40);
|
||||
|
||||
TableColumn<ModUpdateObject, String> fileNameColumn = new TableColumn<>(i18n("mods.check_updates.file"));
|
||||
TableColumn<AddonUpdateObject, String> fileNameColumn = new TableColumn<>(i18n("mods.check_updates.file"));
|
||||
fileNameColumn.setPrefWidth(200);
|
||||
setupCellValueFactory(fileNameColumn, ModUpdateObject::fileNameProperty);
|
||||
setupCellValueFactory(fileNameColumn, AddonUpdateObject::fileNameProperty);
|
||||
|
||||
TableColumn<ModUpdateObject, String> currentVersionColumn = new TableColumn<>(i18n("mods.check_updates.current_version"));
|
||||
TableColumn<AddonUpdateObject, String> currentVersionColumn = new TableColumn<>(i18n("mods.check_updates.current_version"));
|
||||
currentVersionColumn.setPrefWidth(200);
|
||||
setupCellValueFactory(currentVersionColumn, ModUpdateObject::currentVersionProperty);
|
||||
setupCellValueFactory(currentVersionColumn, AddonUpdateObject::currentVersionProperty);
|
||||
|
||||
TableColumn<ModUpdateObject, String> targetVersionColumn = new TableColumn<>(i18n("mods.check_updates.target_version"));
|
||||
TableColumn<AddonUpdateObject, String> targetVersionColumn = new TableColumn<>(i18n("mods.check_updates.target_version"));
|
||||
targetVersionColumn.setPrefWidth(200);
|
||||
setupCellValueFactory(targetVersionColumn, ModUpdateObject::targetVersionProperty);
|
||||
setupCellValueFactory(targetVersionColumn, AddonUpdateObject::targetVersionProperty);
|
||||
|
||||
TableColumn<ModUpdateObject, String> sourceColumn = new TableColumn<>(i18n("mods.check_updates.source"));
|
||||
setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty);
|
||||
TableColumn<AddonUpdateObject, String> sourceColumn = new TableColumn<>(i18n("mods.check_updates.source"));
|
||||
setupCellValueFactory(sourceColumn, AddonUpdateObject::sourceProperty);
|
||||
|
||||
objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList()));
|
||||
objects = FXCollections.observableList(updates.stream().map(AddonUpdateObject::new).collect(Collectors.toList()));
|
||||
FXUtils.bindAllEnabled(allEnabledBox.selectedProperty(), objects.stream().map(o -> o.enabled).toArray(BooleanProperty[]::new));
|
||||
|
||||
TableView<ModUpdateObject> table = new TableView<>(objects);
|
||||
TableView<AddonUpdateObject> table = new TableView<>(objects);
|
||||
table.setEditable(true);
|
||||
table.getColumns().setAll(enabledColumn, fileNameColumn, currentVersionColumn, targetVersionColumn, sourceColumn);
|
||||
setMargin(table, new Insets(10, 10, 5, 10));
|
||||
@@ -114,7 +115,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
exportListButton.setOnAction(e -> exportList());
|
||||
|
||||
JFXButton nextButton = FXUtils.newRaisedButton(i18n("mods.check_updates.confirm"));
|
||||
nextButton.setOnAction(e -> updateMods());
|
||||
nextButton.setOnAction(e -> updateFiles());
|
||||
|
||||
JFXButton cancelButton = FXUtils.newRaisedButton(i18n("button.cancel"));
|
||||
cancelButton.setOnAction(e -> fireEvent(new PageCloseEvent()));
|
||||
@@ -125,23 +126,24 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
setBottom(actions);
|
||||
}
|
||||
|
||||
private <T> void setupCellValueFactory(TableColumn<ModUpdateObject, T> column, Function<ModUpdateObject, ObservableValue<T>> mapper) {
|
||||
private <T> void setupCellValueFactory(TableColumn<AddonUpdateObject, T> column, Function<AddonUpdateObject, ObservableValue<T>> mapper) {
|
||||
column.setCellValueFactory(param -> mapper.apply(param.getValue()));
|
||||
}
|
||||
|
||||
private void updateMods() {
|
||||
ModUpdateTask task = new ModUpdateTask(
|
||||
modManager,
|
||||
private void updateFiles() {
|
||||
AddonUpdateTask task = new AddonUpdateTask(
|
||||
localAddonManager.getDirectory(),
|
||||
objects.stream()
|
||||
.filter(o -> o.enabled.get())
|
||||
.map(object -> pair(object.data.getLocalMod(), object.data.getCandidate()))
|
||||
.collect(Collectors.toList()));
|
||||
.filter(AddonUpdateObject::isEnabled)
|
||||
.map(AddonUpdateObject::getData)
|
||||
.toList()
|
||||
);
|
||||
Controllers.taskDialog(
|
||||
task.whenComplete(Schedulers.javafx(), exception -> {
|
||||
fireEvent(new PageCloseEvent());
|
||||
if (!task.getFailedMods().isEmpty()) {
|
||||
if (!task.getFailedAddons().isEmpty()) {
|
||||
Controllers.dialog(i18n("mods.check_updates.failed_download") + "\n" +
|
||||
task.getFailedMods().stream().map(LocalModFile::getFileName).collect(Collectors.joining("\n")),
|
||||
task.getFailedAddons().stream().map(LocalAddonFile::getFileName).collect(Collectors.joining("\n")),
|
||||
i18n("install.failed"),
|
||||
MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
@@ -189,22 +191,22 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
return state;
|
||||
}
|
||||
|
||||
private static final class ModUpdateObject {
|
||||
final LocalModFile.ModUpdate data;
|
||||
private static final class AddonUpdateObject {
|
||||
final LocalAddonFile.AddonUpdate data;
|
||||
final BooleanProperty enabled = new SimpleBooleanProperty();
|
||||
final StringProperty fileName = new SimpleStringProperty();
|
||||
final StringProperty currentVersion = new SimpleStringProperty();
|
||||
final StringProperty targetVersion = new SimpleStringProperty();
|
||||
final StringProperty source = new SimpleStringProperty();
|
||||
|
||||
public ModUpdateObject(LocalModFile.ModUpdate data) {
|
||||
public AddonUpdateObject(LocalAddonFile.AddonUpdate data) {
|
||||
this.data = data;
|
||||
|
||||
enabled.set(!data.getLocalMod().getModManager().isDisabled(data.getLocalMod().getFile()));
|
||||
fileName.set(data.getLocalMod().getFileName());
|
||||
currentVersion.set(data.getCurrentVersion().getVersion());
|
||||
targetVersion.set(data.getCandidate().getVersion());
|
||||
switch (data.getCurrentVersion().getSelf().getType()) {
|
||||
enabled.set(!data.localAddonFile().isDisabled());
|
||||
fileName.set(data.localAddonFile().getFileName());
|
||||
currentVersion.set(data.currentVersion().getVersion());
|
||||
targetVersion.set(data.targetVersion().getVersion());
|
||||
switch (data.currentVersion().getSelf().getType()) {
|
||||
case CURSEFORGE:
|
||||
source.set(i18n("mods.curseforge"));
|
||||
break;
|
||||
@@ -213,6 +215,10 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
public LocalAddonFile.AddonUpdate getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled.get();
|
||||
}
|
||||
@@ -274,30 +280,32 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModUpdateTask extends Task<Void> {
|
||||
public static class AddonUpdateTask extends Task<Void> {
|
||||
private final Collection<Task<?>> dependents;
|
||||
private final List<LocalModFile> failedMods = new ArrayList<>();
|
||||
private final List<LocalAddonFile> failedAddons = new ArrayList<>();
|
||||
|
||||
ModUpdateTask(ModManager modManager, List<Pair<LocalModFile, RemoteMod.Version>> mods) {
|
||||
AddonUpdateTask(Path addonDirectory, List<LocalAddonFile.AddonUpdate> addons) {
|
||||
setStage("mods.check_updates.confirm");
|
||||
getProperties().put("total", mods.size());
|
||||
getProperties().put("total", addons.size());
|
||||
|
||||
this.dependents = new ArrayList<>();
|
||||
for (Pair<LocalModFile, RemoteMod.Version> mod : mods) {
|
||||
LocalModFile local = mod.getKey();
|
||||
RemoteMod.Version remote = mod.getValue();
|
||||
boolean isDisabled = local.getModManager().isDisabled(local.getFile());
|
||||
for (LocalAddonFile.AddonUpdate addon : addons) {
|
||||
LocalAddonFile local = addon.localAddonFile();
|
||||
RemoteMod.Version remote = addon.targetVersion();
|
||||
boolean isDisabled = local.isDisabled();
|
||||
String originalFileName = local.getFile().getFileName().toString();
|
||||
|
||||
dependents.add(Task
|
||||
.runAsync(Schedulers.javafx(), () -> local.setOld(true))
|
||||
.thenComposeAsync(() -> {
|
||||
String fileName = remote.getFile().getFilename();
|
||||
String fileName = addon.useRemoteFileName() ? remote.getFile().getFilename() : originalFileName;
|
||||
if (isDisabled)
|
||||
fileName += ModManager.DISABLED_EXTENSION;
|
||||
fileName = StringUtils.addSuffix(fileName, LocalAddonManager.DISABLED_EXTENSION);
|
||||
|
||||
var task = new FileDownloadTask(
|
||||
remote.getFile().getUrl(),
|
||||
modManager.getModsDirectory().resolve(fileName));
|
||||
addonDirectory.resolve(fileName)
|
||||
);
|
||||
|
||||
task.setName(remote.getName());
|
||||
return task;
|
||||
@@ -307,16 +315,22 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
// restore state if failed
|
||||
local.setOld(false);
|
||||
if (isDisabled)
|
||||
local.disable();
|
||||
failedMods.add(local);
|
||||
local.markDisabled();
|
||||
failedAddons.add(local);
|
||||
} else if (!local.keepOldFiles()) {
|
||||
try {
|
||||
local.delete();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to delete outdated addon: " + local.getFile(), e);
|
||||
}
|
||||
}
|
||||
})
|
||||
.withCounter("mods.check_updates.confirm"));
|
||||
}
|
||||
}
|
||||
|
||||
public List<LocalModFile> getFailedMods() {
|
||||
return failedMods;
|
||||
public List<LocalAddonFile> getFailedAddons() {
|
||||
return failedAddons;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,7 +192,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}).executor(true);
|
||||
}
|
||||
|
||||
protected String getLocalizedCategory(String category) {
|
||||
protected String getLocalizedCategory(String category, Object self) {
|
||||
return repository instanceof ModrinthRemoteModRepository
|
||||
? i18n("modrinth.category." + category)
|
||||
: i18n("curse.category." + category);
|
||||
@@ -204,9 +204,9 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
|
||||
private String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {
|
||||
return StringUtils.repeats(' ', category.indent * 4) +
|
||||
(category.getCategory() == null
|
||||
(category.category() == null
|
||||
? i18n("curse.category.0")
|
||||
: getLocalizedCategory(category.getCategory().getId()));
|
||||
: getLocalizedCategory(category.category().id(), category.category().self()));
|
||||
}
|
||||
|
||||
protected String getLocalizedOfficialPage() {
|
||||
@@ -386,7 +386,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
int pageOffset = control.pageOffset.get();
|
||||
getSkinnable().search(gameVersionField.getSelectionModel().getSelectedItem(),
|
||||
Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())
|
||||
.map(CategoryIndented::getCategory)
|
||||
.map(CategoryIndented::category)
|
||||
.orElse(null),
|
||||
pageOffset == -1 ? 0 : pageOffset,
|
||||
nameField.getText(),
|
||||
@@ -585,7 +585,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
content.getTags().clear();
|
||||
for (String category : item.getCategories()) {
|
||||
if (getSkinnable().shouldDisplayCategory(category))
|
||||
content.addTag(getSkinnable().getLocalizedCategory(category));
|
||||
content.addTag(getSkinnable().getLocalizedCategory(category, null));
|
||||
}
|
||||
iconLoader.load(imageContainer.imageProperty(), item.getIconUrl());
|
||||
setGraphic(wrapper);
|
||||
@@ -597,29 +597,13 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
|
||||
private static class CategoryIndented {
|
||||
private record CategoryIndented(int indent, RemoteModRepository.Category category) {
|
||||
private static final CategoryIndented ALL = new CategoryIndented(0, null);
|
||||
|
||||
private final int indent;
|
||||
private final RemoteModRepository.Category category;
|
||||
|
||||
public CategoryIndented(int indent, RemoteModRepository.Category category) {
|
||||
this.indent = indent;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public int getIndent() {
|
||||
return indent;
|
||||
}
|
||||
|
||||
public RemoteModRepository.Category getCategory() {
|
||||
return category;
|
||||
}
|
||||
}
|
||||
|
||||
private static void resolveCategory(RemoteModRepository.Category category, int indent, List<CategoryIndented> result) {
|
||||
result.add(new CategoryIndented(indent, category));
|
||||
for (RemoteModRepository.Category subcategory : category.getSubcategories()) {
|
||||
for (RemoteModRepository.Category subcategory : category.subcategories()) {
|
||||
resolveCategory(subcategory, indent + 1, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
private final Profile.ProfileVersion version;
|
||||
private final DownloadCallback callback;
|
||||
private final DownloadListPage page;
|
||||
private final RemoteModRepository.Type type;
|
||||
|
||||
private SimpleMultimap<String, RemoteMod.Version, List<RemoteMod.Version>> versions;
|
||||
|
||||
@@ -80,7 +81,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
this.page = page;
|
||||
this.repository = page.repository;
|
||||
this.addon = addon;
|
||||
this.translations = ModTranslations.getTranslationsByRepositoryType(repository.getType());
|
||||
this.type = Objects.requireNonNullElse(addon.getRepositoryType(), repository.getType());
|
||||
this.translations = ModTranslations.getTranslationsByRepositoryType(this.type);
|
||||
this.mod = translations.getModByCurseForgeId(addon.getSlug());
|
||||
this.version = version;
|
||||
this.callback = callback;
|
||||
@@ -159,13 +161,13 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
public void download(RemoteMod mod, RemoteMod.Version file) {
|
||||
if (this.callback == null) {
|
||||
saveAs(mod, file);
|
||||
saveAs(file);
|
||||
} else {
|
||||
this.callback.download(page.getDownloadProvider(), version.getProfile(), version.getVersion(), mod, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveAs(RemoteMod mod, RemoteMod.Version file) {
|
||||
public void saveAs(RemoteMod.Version file) {
|
||||
String extension = StringUtils.substringAfterLast(file.getFile().getFilename(), '.');
|
||||
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
@@ -226,7 +228,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
content.getSubtitleLabel().setWrapText(true);
|
||||
getSkinnable().addon.getCategories().stream()
|
||||
.filter(category -> getSkinnable().page.shouldDisplayCategory(category))
|
||||
.map(category -> getSkinnable().page.getLocalizedCategory(category))
|
||||
.map(category -> getSkinnable().page.getLocalizedCategory(category, null))
|
||||
.forEach(content::addTag);
|
||||
content.getFirstLine().setMinWidth(0);
|
||||
descriptionPane.getChildren().add(content);
|
||||
@@ -282,14 +284,22 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
resolve:
|
||||
for (RemoteMod.Version modVersion : modVersions) {
|
||||
for (ModLoaderType loader : modVersion.getLoaders()) {
|
||||
if (targetLoaders.contains(loader)) {
|
||||
list.getContent().addAll(
|
||||
ComponentList.createComponentListTitle(i18n("mods.download.recommend", gameVersion)),
|
||||
new ModItem(control.addon, modVersion, control)
|
||||
);
|
||||
break resolve;
|
||||
if (getSkinnable().type == RemoteModRepository.Type.MOD) {
|
||||
for (ModLoaderType loader : modVersion.getLoaders()) {
|
||||
if (targetLoaders.contains(loader)) {
|
||||
list.getContent().addAll(
|
||||
ComponentList.createComponentListTitle(i18n("mods.download.recommend", gameVersion)),
|
||||
new ModItem(control.addon, modVersion, control)
|
||||
);
|
||||
break resolve;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
list.getContent().addAll(
|
||||
ComponentList.createComponentListTitle(i18n("mods.download.recommend", gameVersion)),
|
||||
new ModItem(control.addon, modVersion, control)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,7 +374,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
Pair.pair(RemoteMod.DependencyType.BROKEN, "mods.dependency.broken")
|
||||
));
|
||||
|
||||
DependencyModItem(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version, DownloadCallback callback) {
|
||||
DependencyModItem(DownloadListPage page, RemoteMod addon, Profile.ProfileVersion version) {
|
||||
HBox pane = new HBox(8);
|
||||
pane.setPadding(new Insets(0, 8, 0, 8));
|
||||
pane.setAlignment(Pos.CENTER_LEFT);
|
||||
@@ -374,6 +384,14 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
var imageView = new ImageContainer(40);
|
||||
pane.getChildren().setAll(imageView, content);
|
||||
FXUtils.setLimitHeight(this, 60);
|
||||
|
||||
RemoteModRepository.Type type = addon.getRepositoryType();
|
||||
DownloadCallback callback = switch (type) {
|
||||
case MOD -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_MOD;
|
||||
case RESOURCE_PACK -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_RESOURCE_PACK;
|
||||
case SHADER_PACK -> org.jackhuang.hmcl.ui.download.DownloadPage.FOR_SHADER;
|
||||
default -> null; // Dependencies should not be modpacks, worlds or customized stuff
|
||||
};
|
||||
setOnAction((e) -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Controllers.navigate(new DownloadPage(page, addon, version, callback));
|
||||
@@ -381,12 +399,12 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
setNode(IDX_LEADING, pane);
|
||||
|
||||
if (addon != RemoteMod.BROKEN) {
|
||||
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(page.repository.getType()).getModByCurseForgeId(addon.getSlug());
|
||||
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(type).getModByCurseForgeId(addon.getSlug());
|
||||
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : addon.getTitle());
|
||||
content.setSubtitle(addon.getDescription());
|
||||
for (String category : addon.getCategories()) {
|
||||
if (page.shouldDisplayCategory(category))
|
||||
content.addTag(page.getLocalizedCategory(category));
|
||||
content.addTag(page.getLocalizedCategory(category, null));
|
||||
}
|
||||
if (StringUtils.isNotBlank(addon.getIconUrl())) {
|
||||
imageView.imageProperty().bind(FXUtils.newRemoteImage(addon.getIconUrl(), 80, 80, true, true));
|
||||
@@ -473,7 +491,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
private static final class ModVersion extends JFXDialogLayout {
|
||||
public ModVersion(RemoteMod mod, RemoteMod.Version version, DownloadPage selfPage) {
|
||||
RemoteModRepository.Type type = selfPage.repository.getType();
|
||||
RemoteModRepository.Type type = selfPage.type;
|
||||
|
||||
String title = switch (type) {
|
||||
case WORLD -> "world.download.title";
|
||||
@@ -525,7 +543,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (!spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
selfPage.saveAs(mod, version);
|
||||
selfPage.saveAs(version);
|
||||
});
|
||||
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
@@ -569,7 +587,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (dep == RemoteMod.BROKEN) {
|
||||
return;
|
||||
}
|
||||
DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dep, selfPage.version, selfPage.callback);
|
||||
DependencyModItem dependencyModItem = new DependencyModItem(selfPage.page, dep, selfPage.version);
|
||||
dependencies.get(dependency.getType()).add(dependencyModItem);
|
||||
})
|
||||
.setSignificance(Task.TaskSignificance.MINOR));
|
||||
|
||||
@@ -34,7 +34,10 @@ import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
@@ -241,6 +244,8 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
toolbarPane.setContent(toolbarNormal, ContainerAnimations.FADE);
|
||||
|
||||
FXUtils.setOverflowHidden(toolbarPane, 8);
|
||||
|
||||
root.getContent().add(toolbarPane);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
@@ -53,7 +52,10 @@ public final class GameListPopupMenu extends StackPane {
|
||||
double initOffsetX, double initOffsetY,
|
||||
Profile profile, List<Version> versions) {
|
||||
GameListPopupMenu menu = new GameListPopupMenu();
|
||||
menu.getItems().setAll(versions.stream().map(it -> new GameItem(profile, it.getId())).toList());
|
||||
menu.getItems().setAll(versions.stream()
|
||||
.filter(it -> profile.getRepository().hasVersion(it.getId()))
|
||||
.map(it -> new GameItem(profile, it.getId()))
|
||||
.toList());
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
popup.show(owner, vAlign, hAlign, initOffsetX, initOffsetY);
|
||||
}
|
||||
@@ -66,10 +68,11 @@ public final class GameListPopupMenu extends StackPane {
|
||||
this.getStyleClass().add("popup-menu-content");
|
||||
|
||||
listView.setCellFactory(Cell::new);
|
||||
listView.setFixedCellSize(60);
|
||||
|
||||
listView.setFixedCellSize(50);
|
||||
listView.setPrefWidth(300);
|
||||
|
||||
listView.prefHeightProperty().bind(Bindings.size(getItems()).multiply(60).add(2));
|
||||
listView.prefHeightProperty().bind(Bindings.size(getItems()).multiply(50).add(2));
|
||||
|
||||
Label placeholder = new Label(i18n("version.empty"));
|
||||
placeholder.setStyle("-fx-padding: 10px; -fx-text-fill: -monet-on-surface-variant; -fx-font-style: italic;");
|
||||
@@ -94,35 +97,34 @@ public final class GameListPopupMenu extends StackPane {
|
||||
|
||||
public Cell(ListView<GameItem> listView) {
|
||||
this.setPadding(Insets.EMPTY);
|
||||
HBox root = new HBox();
|
||||
|
||||
root.setSpacing(8);
|
||||
root.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
this.imageView = new ImageContainer(32);
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
this.imageView.setMouseTransparent(true);
|
||||
BorderPane.setAlignment(imageView, Pos.CENTER);
|
||||
|
||||
this.content = new TwoLineListItem();
|
||||
this.content.setMouseTransparent(true);
|
||||
FXUtils.onChangeAndOperate(tag, tag -> {
|
||||
content.getTags().clear();
|
||||
if (StringUtils.isNotBlank(tag)) {
|
||||
content.addTag(tag);
|
||||
}
|
||||
});
|
||||
BorderPane.setAlignment(content, Pos.CENTER);
|
||||
root.getChildren().setAll(imageView, content);
|
||||
|
||||
StackPane pane = new StackPane();
|
||||
pane.getChildren().setAll(root);
|
||||
pane.getStyleClass().add("menu-container");
|
||||
root.setMouseTransparent(true);
|
||||
BorderPane container = new BorderPane();
|
||||
container.getStyleClass().add("container");
|
||||
container.setPickOnBounds(false);
|
||||
container.setLeft(imageView);
|
||||
container.setCenter(content);
|
||||
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(pane);
|
||||
FXUtils.onClicked(ripplerContainer, () -> {
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(container);
|
||||
|
||||
StackPane rootPane = new StackPane();
|
||||
rootPane.getStyleClass().add("advanced-list-item");
|
||||
rootPane.getChildren().setAll(ripplerContainer);
|
||||
rootPane.maxWidthProperty().bind(listView.widthProperty().subtract(5));
|
||||
|
||||
FXUtils.onClicked(rootPane, () -> {
|
||||
GameItem item = getItem();
|
||||
if (item != null) {
|
||||
item.getProfile().setSelectedVersion(item.getId());
|
||||
@@ -130,8 +132,8 @@ public final class GameListPopupMenu extends StackPane {
|
||||
popup.hide();
|
||||
}
|
||||
});
|
||||
this.graphic = ripplerContainer;
|
||||
ripplerContainer.maxWidthProperty().bind(listView.widthProperty().subtract(5));
|
||||
|
||||
this.graphic = rootPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+13
-1
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.game.LocalizedRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
@@ -50,6 +51,14 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.RESOURCE_PACK, CurseForgeRemoteModRepository.RESOURCE_PACKS, ModrinthRemoteModRepository.RESOURCE_PACKS);
|
||||
}
|
||||
|
||||
public static DownloadListPage ofCurseForgeResourcePack(DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.RESOURCE_PACK, CurseForgeRemoteModRepository.RESOURCE_PACKS, null);
|
||||
}
|
||||
|
||||
public static DownloadListPage ofModrinthResourcePack(DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.RESOURCE_PACK, null, ModrinthRemoteModRepository.RESOURCE_PACKS);
|
||||
}
|
||||
|
||||
public static DownloadListPage ofShaderPack(DownloadPage.DownloadCallback callback, boolean versionSelection) {
|
||||
var page = new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.SHADER_PACK, CurseForgeRemoteModRepository.SHADERS, ModrinthRemoteModRepository.SHADER_PACKS);
|
||||
page.supportChinese.set(false);
|
||||
@@ -125,7 +134,7 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLocalizedCategory(String category) {
|
||||
protected String getLocalizedCategory(String category, Object self) {
|
||||
if (category.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
@@ -135,6 +144,9 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
return I18n.getResourceBundle().getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
LOG.warning("Cannot find key " + key + " in resource bundle");
|
||||
if (self instanceof CurseAddon.Category curseCategory) {
|
||||
return curseCategory.getName();
|
||||
}
|
||||
return category;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> implements VersionPage.VersionLoadable, PageAware {
|
||||
private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false);
|
||||
@@ -64,7 +64,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
final EnumSet<ModLoaderType> supportedLoaders = EnumSet.noneOf(ModLoaderType.class);
|
||||
|
||||
public ModListPage() {
|
||||
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
||||
FXUtils.applyDragListener(this, it -> ModManager.MOD_EXTENSIONS.contains(FileUtils.getExtension(it).toLowerCase(Locale.ROOT)), mods -> {
|
||||
mods.forEach(it -> {
|
||||
try {
|
||||
modManager.addMod(it);
|
||||
@@ -105,8 +105,8 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
lock.lock();
|
||||
try {
|
||||
modManager.refreshMods();
|
||||
return modManager.getMods().stream().map(ModListPageSkin.ModInfoObject::new).toList();
|
||||
modManager.refresh();
|
||||
return modManager.getLocalFiles().stream().map(ModListPageSkin.ModInfoObject::new).toList();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
} finally {
|
||||
@@ -173,7 +173,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
public void add() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("mods.add.title"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.litemod"));
|
||||
List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
|
||||
if (res == null) return;
|
||||
@@ -240,10 +240,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
Runnable action = () -> Controllers.taskDialog(Task
|
||||
.composeAsync(() -> {
|
||||
Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);
|
||||
if (gameVersion.isPresent()) {
|
||||
return new ModCheckUpdatesTask(DownloadProviders.getDownloadProvider(), gameVersion.get(), mods);
|
||||
}
|
||||
return null;
|
||||
return gameVersion.map(g -> new AddonCheckUpdatesTask<>(DownloadProviders.getDownloadProvider(), g, mods)).orElse(null);
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception instanceof CancellationException) return;
|
||||
@@ -252,7 +249,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
} else if (result.isEmpty()) {
|
||||
Controllers.dialog(i18n("mods.check_updates.empty"));
|
||||
} else {
|
||||
Controllers.navigateForward(new ModUpdatesPage(modManager, result));
|
||||
Controllers.navigateForward(new AddonUpdatesPage<>(modManager, result));
|
||||
}
|
||||
})
|
||||
.withStagesHints("update.checking"),
|
||||
|
||||
@@ -72,7 +72,6 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
@@ -198,6 +197,9 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
else
|
||||
changeToolbar(toolbarSelecting);
|
||||
});
|
||||
|
||||
FXUtils.setOverflowHidden(toolbarPane, 8);
|
||||
|
||||
root.getContent().add(toolbarPane);
|
||||
|
||||
// Clear selection when pressing ESC
|
||||
@@ -274,17 +276,11 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
listView.getItems().clear();
|
||||
|
||||
Predicate<@Nullable String> predicate;
|
||||
if (queryString.startsWith("regex:")) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(queryString.substring("regex:".length()));
|
||||
predicate = s -> s != null && pattern.matcher(s).find();
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Illegal regular expression", e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
String lowerQueryString = queryString.toLowerCase(Locale.ROOT);
|
||||
predicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerQueryString);
|
||||
try {
|
||||
predicate = StringUtils.compileQuery(queryString);
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Illegal regular expression", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we need to search in the background thread?
|
||||
@@ -478,7 +474,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
RemoteModRepository repository = item.getValue();
|
||||
JFXHyperlink button = new JFXHyperlink(i18n(item.getKey()));
|
||||
Task.runAsync(() -> {
|
||||
Optional<RemoteMod.Version> versionOptional = repository.getRemoteVersionByLocalFile(modInfo.getModInfo(), modInfo.getModInfo().getFile());
|
||||
Optional<RemoteMod.Version> versionOptional = repository.getRemoteVersionByLocalFile(modInfo.getModInfo().getFile());
|
||||
if (versionOptional.isPresent()) {
|
||||
RemoteMod remoteMod = repository.getModById(DownloadProviders.getDownloadProvider(), versionOptional.get().getModid());
|
||||
FXUtils.runInFX(() -> {
|
||||
@@ -508,7 +504,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
repository instanceof CurseForgeRemoteModRepository ? HMCLLocalizedDownloadListPage.ofCurseForgeMod(null, false) : HMCLLocalizedDownloadListPage.ofModrinthMod(null, false),
|
||||
remoteMod,
|
||||
new Profile.ProfileVersion(ModListPageSkin.this.getSkinnable().getProfile(), ModListPageSkin.this.getSkinnable().getInstanceId()),
|
||||
(downloadProvider, profile, version, mod, file) -> org.jackhuang.hmcl.ui.download.DownloadPage.download(downloadProvider, profile, version, file, "mods")
|
||||
org.jackhuang.hmcl.ui.download.DownloadPage.FOR_MOD
|
||||
));
|
||||
});
|
||||
button.setDisable(false);
|
||||
|
||||
@@ -0,0 +1,643 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.resourcepack.ResourcePackFile;
|
||||
import org.jackhuang.hmcl.resourcepack.ResourcePackManager;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.ListPageBase;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ResourcePackListPage extends ListPageBase<ResourcePackListPage.ResourcePackInfoObject> implements VersionPage.VersionLoadable {
|
||||
|
||||
private static final String TIP_KEY = "resourcePackWarning";
|
||||
private static @Nullable String getWarning(ResourcePackFile.Compatibility compatibility) {
|
||||
return switch (compatibility) {
|
||||
case TOO_NEW -> i18n("resourcepack.warning.too_new");
|
||||
case TOO_OLD -> i18n("resourcepack.warning.too_old");
|
||||
case INVALID -> i18n("resourcepack.warning.invalid");
|
||||
case MISSING_PACK_META -> i18n("resourcepack.warning.missing_pack_meta");
|
||||
case MISSING_GAME_META -> i18n("resourcepack.warning.missing_game_meta");
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private Profile profile;
|
||||
private String instanceId;
|
||||
|
||||
private Path resourcePackDirectory;
|
||||
private ResourcePackManager resourcePackManager;
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
public ResourcePackListPage() {
|
||||
FXUtils.applyDragListener(this, ResourcePackFile::isFileResourcePack, this::addFiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ResourcePackListPageSkin(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String version) {
|
||||
this.profile = profile;
|
||||
this.instanceId = version;
|
||||
this.resourcePackManager = new ResourcePackManager(profile.getRepository(), version);
|
||||
this.resourcePackDirectory = this.resourcePackManager.getDirectory();
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (resourcePackManager == null) return;
|
||||
setDisable(false);
|
||||
setLoading(true);
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
lock.lock();
|
||||
try {
|
||||
if (!ResourcePackManager.isMcVersionSupported(resourcePackManager.getMinecraftVersion())) return null;
|
||||
resourcePackManager.refresh();
|
||||
return resourcePackManager.arePacksEnabled(resourcePackManager.getLocalFiles().stream())
|
||||
.map(ResourcePackInfoObject::new)
|
||||
.toList();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
if (result != null) {
|
||||
getItems().setAll(result);
|
||||
} else { // Unsupported mc version
|
||||
getItems().clear();
|
||||
setDisable(true);
|
||||
}
|
||||
} else {
|
||||
LOG.warning("Failed to load resource packs", exception);
|
||||
getItems().clear();
|
||||
}
|
||||
setLoading(false);
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void addFiles(List<Path> files) {
|
||||
if (resourcePackManager == null) return;
|
||||
|
||||
List<Path> failures = new ArrayList<>();
|
||||
Task.runAsync(() -> {
|
||||
for (Path file : files) {
|
||||
try {
|
||||
resourcePackManager.importResourcePack(file);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to add resource pack", e);
|
||||
failures.add(file);
|
||||
}
|
||||
}
|
||||
}).withRunAsync(Schedulers.javafx(), () -> {
|
||||
if (!failures.isEmpty()) {
|
||||
StringBuilder failure = new StringBuilder(i18n("resourcepack.add.failed"));
|
||||
for (Path file: failures) {
|
||||
failure.append("\n").append(file.toString());
|
||||
}
|
||||
Controllers.dialog(failure.toString(), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
refresh();
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
public void onAddFiles() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(i18n("resourcepack.add"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("resourcepack"), "*.zip"));
|
||||
List<Path> files = FileUtils.toPaths(fileChooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
if (files != null && !files.isEmpty()) {
|
||||
addFiles(files);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownload() {
|
||||
Controllers.getDownloadPage().showResourcePackDownloads().selectVersion(instanceId);
|
||||
Controllers.navigate(Controllers.getDownloadPage());
|
||||
}
|
||||
|
||||
private void onOpenFolder() {
|
||||
if (resourcePackDirectory != null) {
|
||||
FXUtils.openFolder(resourcePackDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSelectedEnabled(List<ResourcePackInfoObject> selectedItems, boolean enabled) {
|
||||
if (!Boolean.TRUE.equals(ConfigHolder.config().getShownTips().get(TIP_KEY)) && enabled && !selectedItems.stream().map(ResourcePackInfoObject::getFile).allMatch(ResourcePackFile::isCompatible)) {
|
||||
Controllers.confirm(
|
||||
i18n("resourcepack.warning.manipulate"),
|
||||
i18n("message.warning"),
|
||||
MessageDialogPane.MessageType.WARNING,
|
||||
() -> {
|
||||
ConfigHolder.config().getShownTips().put(TIP_KEY, true);
|
||||
setSelectedEnabled(selectedItems, true);
|
||||
}, null);
|
||||
} else {
|
||||
if (resourcePackManager == null) return;
|
||||
if (enabled) {
|
||||
resourcePackManager.enableResourcePacks(selectedItems.stream().map(ResourcePackInfoObject::getFile).toList());
|
||||
} else {
|
||||
resourcePackManager.disableResourcePacks(selectedItems.stream().map(ResourcePackInfoObject::getFile).toList());
|
||||
}
|
||||
for (ResourcePackInfoObject item : selectedItems) {
|
||||
item.enabledProperty().set(enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSelected(List<ResourcePackInfoObject> selectedItems) {
|
||||
try {
|
||||
if (resourcePackManager != null) {
|
||||
if (resourcePackManager.removeResourcePacks(selectedItems.stream().map(ResourcePackInfoObject::getFile).toList())) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(i18n("resourcepack.delete.failed", e.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
LOG.warning("Failed to delete resource packs", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkUpdates(Collection<ResourcePackFile> resourcePacks) {
|
||||
Runnable action = () -> Controllers.taskDialog(Task
|
||||
.composeAsync(() -> {
|
||||
Optional<String> gameVersion = profile.getRepository().getGameVersion(instanceId);
|
||||
return gameVersion.map(g -> new AddonCheckUpdatesTask<>(DownloadProviders.getDownloadProvider(), g, resourcePacks)).orElse(null);
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception != null || result == null) {
|
||||
Controllers.dialog(i18n("mods.check_updates.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR);
|
||||
} else if (result.isEmpty()) {
|
||||
Controllers.dialog(i18n("mods.check_updates.empty"));
|
||||
} else {
|
||||
Controllers.navigateForward(new AddonUpdatesPage<>(resourcePackManager, result));
|
||||
}
|
||||
})
|
||||
.withStagesHints("update.checking"),
|
||||
i18n("mods.check_updates"), TaskCancellationAction.NORMAL);
|
||||
|
||||
if (profile.getRepository().isModpack(instanceId)) {
|
||||
Controllers.confirm(
|
||||
i18n("resourcepack.update_in_modpack.warning"), null,
|
||||
MessageDialogPane.MessageType.WARNING,
|
||||
action, null);
|
||||
} else {
|
||||
action.run();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResourcePackListPageSkin extends SkinBase<ResourcePackListPage> {
|
||||
private final JFXListView<ResourcePackInfoObject> listView;
|
||||
private final JFXTextField searchField = new JFXTextField();
|
||||
|
||||
private final TransitionPane toolbarPane = new TransitionPane();
|
||||
private final HBox searchBar = new HBox();
|
||||
private final HBox toolbarNormal = new HBox();
|
||||
private final HBox toolbarSelecting = new HBox();
|
||||
|
||||
private boolean isSearching;
|
||||
|
||||
private ResourcePackListPageSkin(ResourcePackListPage control) {
|
||||
super(control);
|
||||
|
||||
StackPane pane = new StackPane();
|
||||
pane.setPadding(new Insets(10));
|
||||
pane.getStyleClass().addAll("notice-pane");
|
||||
|
||||
ComponentList root = new ComponentList();
|
||||
root.getStyleClass().add("no-padding");
|
||||
|
||||
listView = new JFXListView<>();
|
||||
|
||||
{
|
||||
|
||||
// Toolbar Selecting
|
||||
toolbarSelecting.getChildren().setAll(
|
||||
createToolbarButton2(i18n("button.remove"), SVG.DELETE_FOREVER, () -> {
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
|
||||
control.removeSelected(listView.getSelectionModel().getSelectedItems());
|
||||
}, null);
|
||||
}),
|
||||
createToolbarButton2(i18n("button.enable"), SVG.CHECK, () ->
|
||||
control.setSelectedEnabled(listView.getSelectionModel().getSelectedItems(), true)),
|
||||
createToolbarButton2(i18n("button.disable"), SVG.CLOSE, () ->
|
||||
control.setSelectedEnabled(listView.getSelectionModel().getSelectedItems(), false)),
|
||||
createToolbarButton2(i18n("mods.check_updates.button"), SVG.UPDATE, () ->
|
||||
control.checkUpdates(
|
||||
listView.getSelectionModel().getSelectedItems().stream().map(ResourcePackInfoObject::getFile).toList()
|
||||
)
|
||||
),
|
||||
createToolbarButton2(i18n("button.select_all"), SVG.SELECT_ALL, () ->
|
||||
listView.getSelectionModel().selectAll()),
|
||||
createToolbarButton2(i18n("button.cancel"), SVG.CANCEL, () ->
|
||||
listView.getSelectionModel().clearSelection())
|
||||
);
|
||||
|
||||
// Search Bar
|
||||
searchBar.setAlignment(Pos.CENTER);
|
||||
searchBar.setPadding(new Insets(0, 5, 0, 5));
|
||||
searchField.setPromptText(i18n("search"));
|
||||
HBox.setHgrow(searchField, Priority.ALWAYS);
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(100));
|
||||
pause.setOnFinished(e -> search());
|
||||
FXUtils.onChange(searchField.textProperty(), newValue -> {
|
||||
pause.setRate(1);
|
||||
pause.playFromStart();
|
||||
});
|
||||
|
||||
JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE,
|
||||
() -> {
|
||||
changeToolbar(toolbarNormal);
|
||||
|
||||
isSearching = false;
|
||||
searchField.clear();
|
||||
Bindings.bindContent(listView.getItems(), getSkinnable().getItems());
|
||||
});
|
||||
|
||||
onEscPressed(searchField, closeSearchBar::fire);
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
// Toolbar Normal
|
||||
toolbarNormal.setAlignment(Pos.CENTER_LEFT);
|
||||
toolbarNormal.setPickOnBounds(false);
|
||||
toolbarNormal.getChildren().setAll(
|
||||
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, control::refresh),
|
||||
createToolbarButton2(i18n("resourcepack.add"), SVG.ADD, control::onAddFiles),
|
||||
createToolbarButton2(i18n("button.reveal_dir"), SVG.FOLDER_OPEN, control::onOpenFolder),
|
||||
createToolbarButton2(i18n("mods.check_updates.button"), SVG.UPDATE, () ->
|
||||
control.checkUpdates(listView.getItems().stream().map(ResourcePackInfoObject::getFile).toList())
|
||||
),
|
||||
createToolbarButton2(i18n("download"), SVG.DOWNLOAD, control::onDownload),
|
||||
createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar))
|
||||
);
|
||||
|
||||
FXUtils.onChangeAndOperate(listView.getSelectionModel().selectedItemProperty(),
|
||||
selectedItem -> {
|
||||
if (selectedItem == null)
|
||||
changeToolbar(isSearching ? searchBar : toolbarNormal);
|
||||
else
|
||||
changeToolbar(toolbarSelecting);
|
||||
});
|
||||
FXUtils.setOverflowHidden(toolbarPane, 8);
|
||||
root.getContent().add(toolbarPane);
|
||||
|
||||
// Clear selection when pressing ESC
|
||||
root.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||
if (e.getCode() == KeyCode.ESCAPE) {
|
||||
if (listView.getSelectionModel().getSelectedItem() != null) {
|
||||
listView.getSelectionModel().clearSelection();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
SpinnerPane center = new SpinnerPane();
|
||||
ComponentList.setVgrow(center, Priority.ALWAYS);
|
||||
center.loadingProperty().bind(control.loadingProperty());
|
||||
|
||||
listView.setCellFactory(x -> new ResourcePackListCell(listView, control));
|
||||
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
Bindings.bindContent(listView.getItems(), control.getItems());
|
||||
|
||||
listView.setOnContextMenuRequested(event -> {
|
||||
ResourcePackInfoObject selectedItem = listView.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem != null && listView.getSelectionModel().getSelectedItems().size() == 1) {
|
||||
listView.getSelectionModel().clearSelection();
|
||||
Controllers.dialog(new ResourcePackInfoDialog(control, selectedItem));
|
||||
}
|
||||
});
|
||||
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
listView.getStyleClass().add("no-horizontal-scrollbar");
|
||||
|
||||
center.setContent(listView);
|
||||
root.getContent().add(center);
|
||||
}
|
||||
|
||||
pane.getChildren().setAll(root);
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
|
||||
private void changeToolbar(HBox newToolbar) {
|
||||
Node oldToolbar = toolbarPane.getCurrentNode();
|
||||
if (newToolbar != oldToolbar) {
|
||||
toolbarPane.setContent(newToolbar, ContainerAnimations.FADE);
|
||||
if (newToolbar == searchBar) {
|
||||
Platform.runLater(searchField::requestFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void search() {
|
||||
isSearching = true;
|
||||
|
||||
Bindings.unbindContent(listView.getItems(), getSkinnable().getItems());
|
||||
|
||||
String queryString = searchField.getText();
|
||||
if (StringUtils.isBlank(queryString)) {
|
||||
listView.getItems().setAll(getSkinnable().getItems());
|
||||
} else {
|
||||
listView.getItems().clear();
|
||||
|
||||
Predicate<@Nullable String> predicate;
|
||||
try {
|
||||
predicate = StringUtils.compileQuery(queryString);
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Illegal regular expression", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we need to search in the background thread?
|
||||
for (ResourcePackInfoObject item : getSkinnable().getItems()) {
|
||||
ResourcePackFile resourcePack = item.getFile();
|
||||
LocalAddonFile.Description description = resourcePack.getDescription();
|
||||
Stream<String> descriptionParts = description == null
|
||||
? Stream.empty()
|
||||
: description.getParts().stream().map(LocalAddonFile.Description.Part::getText);
|
||||
if (predicate.test(resourcePack.getFileNameWithExtension())
|
||||
|| predicate.test(resourcePack.getFileName())
|
||||
|| descriptionParts.anyMatch(predicate)) {
|
||||
listView.getItems().add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResourcePackInfoObject {
|
||||
private final ResourcePackFile file;
|
||||
private final BooleanProperty enabled;
|
||||
|
||||
public ResourcePackInfoObject(Pair<ResourcePackFile, Boolean> pair) {
|
||||
this.file = pair.key();
|
||||
this.enabled = new SimpleBooleanProperty(this, "enabled", pair.value());
|
||||
}
|
||||
|
||||
public ResourcePackFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public BooleanProperty enabledProperty() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
Image getIcon() {
|
||||
Image image = file.getIcon();
|
||||
if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 ||
|
||||
(Math.abs(image.getWidth() - image.getHeight()) >= 1)) {
|
||||
image = FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
|
||||
}
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResourcePackListCell extends MDListCell<ResourcePackInfoObject> {
|
||||
private static final PseudoClass WARNING = PseudoClass.getPseudoClass("warning");
|
||||
|
||||
private final ResourcePackListPage page;
|
||||
|
||||
private final JFXCheckBox checkBox;
|
||||
private final ImageContainer imageContainer = new ImageContainer(24);
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER);
|
||||
private final JFXButton btnInfo = FXUtils.newToggleButton4(SVG.INFO);
|
||||
|
||||
private ResourcePackInfoObject object = null;
|
||||
|
||||
private BooleanProperty booleanProperty = null;
|
||||
|
||||
public ResourcePackListCell(JFXListView<ResourcePackInfoObject> listView, ResourcePackListPage page) {
|
||||
super(listView);
|
||||
this.page = page;
|
||||
|
||||
getStyleClass().add("resource-pack-list-cell");
|
||||
|
||||
HBox root = new HBox(8);
|
||||
root.setPickOnBounds(false);
|
||||
root.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
checkBox = new JFXCheckBox() {
|
||||
@Override
|
||||
public void fire() {
|
||||
if (!Boolean.TRUE.equals(ConfigHolder.config().getShownTips().get(TIP_KEY)) && !isSelected() && object != null && !object.getFile().isCompatible()) {
|
||||
Controllers.confirm(
|
||||
i18n("resourcepack.warning.manipulate"),
|
||||
i18n("message.info"),
|
||||
MessageDialogPane.MessageType.INFO,
|
||||
() -> {
|
||||
super.fire();
|
||||
ConfigHolder.config().getShownTips().put(TIP_KEY, true);
|
||||
}, null);
|
||||
} else {
|
||||
super.fire();
|
||||
}
|
||||
}
|
||||
};
|
||||
checkBox.setOnAction(e -> {
|
||||
if (object != null) object.file.setEnabled(checkBox.isSelected());
|
||||
});
|
||||
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
content.setMouseTransparent(true);
|
||||
|
||||
root.getChildren().setAll(checkBox, imageContainer, content, btnReveal, btnInfo);
|
||||
|
||||
setSelectable();
|
||||
|
||||
StackPane.setMargin(root, new Insets(8));
|
||||
getContainer().getChildren().add(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(ResourcePackInfoObject item, boolean empty) {
|
||||
pseudoClassStateChanged(WARNING, false);
|
||||
|
||||
if (empty || item == null) return;
|
||||
|
||||
this.object = item;
|
||||
ResourcePackFile file = item.getFile();
|
||||
imageContainer.setImage(item.getIcon());
|
||||
|
||||
content.getTags().clear();
|
||||
content.setTitle(file.getFileName());
|
||||
{
|
||||
var description = file.getDescription();
|
||||
content.setSubtitle(description != null ? description.toStringSingleLine() : "");
|
||||
}
|
||||
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(file.getFile()));
|
||||
|
||||
btnInfo.setOnAction(e -> Controllers.dialog(new ResourcePackInfoDialog(this.page, item)));
|
||||
|
||||
if (booleanProperty != null) {
|
||||
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
||||
}
|
||||
checkBox.selectedProperty().bindBidirectional(booleanProperty = item.enabledProperty());
|
||||
|
||||
{
|
||||
var compatibility = file.getCompatibility();
|
||||
if (compatibility != ResourcePackFile.Compatibility.COMPATIBLE) {
|
||||
pseudoClassStateChanged(WARNING, true);
|
||||
content.addTagWarning(getWarning(compatibility));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResourcePackInfoDialog extends JFXDialogLayout {
|
||||
|
||||
ResourcePackInfoDialog(ResourcePackListPage page, ResourcePackInfoObject packInfoObject) {
|
||||
ResourcePackFile pack = packInfoObject.getFile();
|
||||
|
||||
HBox titleContainer = new HBox();
|
||||
titleContainer.setSpacing(8);
|
||||
|
||||
Stage stage = Controllers.getStage();
|
||||
maxWidthProperty().bind(stage.widthProperty().multiply(0.7));
|
||||
|
||||
ImageContainer imageContainer = new ImageContainer(40);
|
||||
imageContainer.setImage(packInfoObject.getIcon());
|
||||
|
||||
TwoLineListItem title = new TwoLineListItem();
|
||||
title.setTitle(pack.getFileName());
|
||||
title.setSubtitle(pack.getFileNameWithExtension());
|
||||
var compatibility = pack.getCompatibility();
|
||||
if (compatibility != ResourcePackFile.Compatibility.COMPATIBLE) {
|
||||
title.addTagWarning(getWarning(compatibility));
|
||||
}
|
||||
|
||||
titleContainer.getChildren().setAll(imageContainer, title);
|
||||
setHeading(titleContainer);
|
||||
|
||||
Label description = new Label(Objects.requireNonNullElse(pack.getDescription(), "").toString());
|
||||
description.setWrapText(true);
|
||||
FXUtils.copyOnDoubleClick(description);
|
||||
|
||||
ScrollPane descriptionPane = new ScrollPane(description);
|
||||
FXUtils.smoothScrolling(descriptionPane);
|
||||
descriptionPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
descriptionPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
|
||||
descriptionPane.setFitToWidth(true);
|
||||
FXUtils.onChange(description.heightProperty(), newVal -> {
|
||||
double maxHeight = stage.getHeight() * 0.5;
|
||||
double targetHeight = Math.min(newVal.doubleValue(), maxHeight);
|
||||
descriptionPane.setPrefViewportHeight(targetHeight);
|
||||
});
|
||||
|
||||
setBody(descriptionPane);
|
||||
|
||||
for (Pair<String, ? extends RemoteModRepository> item : Arrays.asList(
|
||||
pair("mods.curseforge", CurseForgeRemoteModRepository.RESOURCE_PACKS),
|
||||
pair("mods.modrinth", ModrinthRemoteModRepository.RESOURCE_PACKS)
|
||||
)) {
|
||||
RemoteModRepository repository = item.getValue();
|
||||
JFXHyperlink button = new JFXHyperlink(i18n(item.getKey()));
|
||||
Task.runAsync(() -> {
|
||||
Optional<RemoteMod.Version> versionOptional = repository.getRemoteVersionByLocalFile(packInfoObject.getFile().getFile());
|
||||
if (versionOptional.isPresent()) {
|
||||
RemoteMod remoteMod = repository.getModById(DownloadProviders.getDownloadProvider(), versionOptional.get().getModid());
|
||||
FXUtils.runInFX(() -> {
|
||||
button.setOnAction(e -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Controllers.navigate(new DownloadPage(
|
||||
repository instanceof CurseForgeRemoteModRepository
|
||||
? HMCLLocalizedDownloadListPage.ofCurseForgeResourcePack(null, false)
|
||||
: HMCLLocalizedDownloadListPage.ofModrinthResourcePack(null, false),
|
||||
remoteMod,
|
||||
new Profile.ProfileVersion(page.profile, page.instanceId),
|
||||
org.jackhuang.hmcl.ui.download.DownloadPage.FOR_RESOURCE_PACK
|
||||
));
|
||||
});
|
||||
button.setDisable(false);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
button.setDisable(true);
|
||||
getActions().add(button);
|
||||
}
|
||||
|
||||
JFXButton okButton = new JFXButton();
|
||||
okButton.getStyleClass().add("dialog-accept");
|
||||
okButton.setText(i18n("button.ok"));
|
||||
okButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
getActions().add(okButton);
|
||||
|
||||
onEscPressed(this, okButton::fire);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.resourcepack.ResourcepackFile;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ResourcepackListPage extends ListPageBase<ResourcepackListPage.ResourcepackInfoObject> implements VersionPage.VersionLoadable {
|
||||
private Path resourcepackDirectory;
|
||||
|
||||
public ResourcepackListPage() {
|
||||
FXUtils.applyDragListener(this, file -> file.getFileName().toString().endsWith(".zip"), this::addFiles);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ResourcepackListPageSkin(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String version) {
|
||||
this.resourcepackDirectory = profile.getRepository().getResourcepacksDirectory(version);
|
||||
|
||||
try {
|
||||
if (!Files.exists(resourcepackDirectory)) {
|
||||
Files.createDirectories(resourcepackDirectory);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to create resourcepack directory" + resourcepackDirectory, e);
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (resourcepackDirectory == null || !Files.isDirectory(resourcepackDirectory)) return;
|
||||
setLoading(true);
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
try (Stream<Path> stream = Files.list(resourcepackDirectory)) {
|
||||
return stream.sorted(Comparator.comparing(FileUtils::getName))
|
||||
.flatMap(item -> {
|
||||
try {
|
||||
return Stream.of(ResourcepackFile.parse(item)).filter(Objects::nonNull).map(ResourcepackInfoObject::new);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to load resourcepack " + item, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}).whenComplete(Schedulers.javafx(), ((result, exception) -> {
|
||||
if (exception == null) {
|
||||
getItems().setAll(result);
|
||||
} else {
|
||||
LOG.warning("Failed to load resourcepacks", exception);
|
||||
getItems().clear();
|
||||
}
|
||||
setLoading(false);
|
||||
})).start();
|
||||
}
|
||||
|
||||
public void addFiles(List<Path> files) {
|
||||
if (resourcepackDirectory == null) return;
|
||||
|
||||
try {
|
||||
for (Path file : files) {
|
||||
Path target = resourcepackDirectory.resolve(file.getFileName());
|
||||
if (!Files.exists(target)) {
|
||||
Files.copy(file, target);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to add resourcepacks", e);
|
||||
Controllers.dialog(i18n("resourcepack.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void onAddFiles() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(i18n("resourcepack.add.title"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.resourcepack"), "*.zip"));
|
||||
List<Path> files = FileUtils.toPaths(fileChooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
if (files != null && !files.isEmpty()) {
|
||||
addFiles(files);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownload() {
|
||||
Controllers.getDownloadPage().showResourcepackDownloads();
|
||||
Controllers.navigate(Controllers.getDownloadPage());
|
||||
}
|
||||
|
||||
private static final class ResourcepackListPageSkin extends ToolbarListPageSkin<ResourcepackInfoObject, ResourcepackListPage> {
|
||||
|
||||
public ResourcepackListPageSkin(ResourcepackListPage control) {
|
||||
super(control);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(ResourcepackListPage skinnable) {
|
||||
return List.of(
|
||||
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
|
||||
createToolbarButton2(i18n("resourcepack.add"), SVG.ADD, skinnable::onAddFiles),
|
||||
createToolbarButton2(i18n("resourcepack.download"), SVG.DOWNLOAD, skinnable::onDownload)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListCell<ResourcepackInfoObject> createListCell(JFXListView<ResourcepackInfoObject> listView) {
|
||||
return new ResourcepackListCell(listView, getSkinnable());
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResourcepackInfoObject {
|
||||
private final ResourcepackFile file;
|
||||
private WeakReference<Image> iconCache;
|
||||
|
||||
public ResourcepackInfoObject(ResourcepackFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public ResourcepackFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
Image getIcon() {
|
||||
Image image = null;
|
||||
if (iconCache != null && (image = iconCache.get()) != null) {
|
||||
return image;
|
||||
}
|
||||
byte[] iconData = file.getIcon();
|
||||
if (iconData != null) {
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) {
|
||||
image = new Image(inputStream, 64, 64, true, true);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resourcepack icon " + file.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 ||
|
||||
(Math.abs(image.getWidth() - image.getHeight()) >= 1)) {
|
||||
image = FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
|
||||
}
|
||||
iconCache = new WeakReference<>(image);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResourcepackListCell extends MDListCell<ResourcepackInfoObject> {
|
||||
private final ImageContainer imageView = new ImageContainer(32);
|
||||
private final TwoLineListItem content = new TwoLineListItem();
|
||||
private final JFXButton btnReveal = FXUtils.newToggleButton4(SVG.FOLDER_OPEN);
|
||||
private final JFXButton btnDelete = FXUtils.newToggleButton4(SVG.DELETE_FOREVER);
|
||||
private final ResourcepackListPage page;
|
||||
|
||||
public ResourcepackListCell(JFXListView<ResourcepackInfoObject> listView, ResourcepackListPage page) {
|
||||
super(listView);
|
||||
|
||||
this.page = page;
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().add("md-list-cell");
|
||||
root.setPadding(new Insets(8));
|
||||
|
||||
HBox left = new HBox(8);
|
||||
left.setAlignment(Pos.CENTER);
|
||||
left.getChildren().add(imageView);
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
FXUtils.setLimitWidth(left, 48);
|
||||
root.setLeft(left);
|
||||
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
root.setCenter(content);
|
||||
|
||||
HBox right = new HBox(8);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
right.getChildren().setAll(btnReveal, btnDelete);
|
||||
root.setRight(right);
|
||||
|
||||
getContainer().getChildren().add(new RipplerContainer(root));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(ResourcepackListPage.ResourcepackInfoObject item, boolean empty) {
|
||||
if (empty || item == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourcepackFile file = item.getFile();
|
||||
imageView.setImage(item.getIcon());
|
||||
|
||||
content.setTitle(file.getName());
|
||||
LocalModFile.Description description = file.getDescription();
|
||||
content.setSubtitle(description != null ? description.toString() : "");
|
||||
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(file.getPath()));
|
||||
|
||||
btnDelete.setOnAction(event ->
|
||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"),
|
||||
() -> onDelete(file), null));
|
||||
}
|
||||
|
||||
private void onDelete(ResourcepackFile file) {
|
||||
try {
|
||||
if (Files.isDirectory(file.getPath())) {
|
||||
FileUtils.deleteDirectory(file.getPath());
|
||||
} else {
|
||||
Files.delete(file.getPath());
|
||||
}
|
||||
page.refresh();
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(i18n("resourcepack.delete.failed", e.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
LOG.warning("Failed to delete resourcepack", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private final TabHeader.Tab<ModListPage> modListTab = new TabHeader.Tab<>("modListTab");
|
||||
private final TabHeader.Tab<WorldListPage> worldListTab = new TabHeader.Tab<>("worldList");
|
||||
private final TabHeader.Tab<SchematicsPage> schematicsTab = new TabHeader.Tab<>("schematicsTab");
|
||||
private final TabHeader.Tab<ResourcepackListPage> resourcePackTab = new TabHeader.Tab<>("resourcePackTab");
|
||||
private final TabHeader.Tab<ResourcePackListPage> resourcePackTab = new TabHeader.Tab<>("resourcePackTab");
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();
|
||||
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||
@@ -78,7 +78,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
|
||||
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
|
||||
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(ResourcepackListPage::new));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(ResourcePackListPage::new));
|
||||
worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new));
|
||||
schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new));
|
||||
|
||||
@@ -302,7 +302,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
new IconedMenuItem(SVG.WB_SUNNY, i18n("folder.shaderpacks"), () -> control.onBrowse("shaderpacks"), browsePopup),
|
||||
new IconedMenuItem(SVG.SCREENSHOT_MONITOR, i18n("folder.screenshots"), () -> control.onBrowse("screenshots"), browsePopup),
|
||||
new IconedMenuItem(SVG.SETTINGS, i18n("folder.config"), () -> control.onBrowse("config"), browsePopup),
|
||||
new IconedMenuItem(SVG.SCRIPT, i18n("folder.logs"), () -> control.onBrowse("logs"), browsePopup)
|
||||
new IconedMenuItem(SVG.SCRIPT, i18n("folder.logs"), () -> control.onBrowse("logs"), browsePopup),
|
||||
new IconedMenuItem(SVG.FRAME_BUG, i18n("folder.crash-reports"), () -> control.onBrowse("crash-reports"), browsePopup)
|
||||
);
|
||||
|
||||
PopupMenu managementList = new PopupMenu();
|
||||
|
||||
@@ -174,7 +174,7 @@ public final class NativePatcher {
|
||||
if (lwjglVersionChanged) {
|
||||
ModManager modManager = repository.getModManager(version.getId());
|
||||
try {
|
||||
for (LocalModFile mod : modManager.getMods()) {
|
||||
for (LocalModFile mod : modManager.getLocalFiles()) {
|
||||
if ("sodium".equals(mod.getId())) {
|
||||
// https://github.com/CaffeineMC/sodium/issues/2561
|
||||
javaArguments.add("-Dsodium.checks.issue2561=false");
|
||||
|
||||
@@ -269,7 +269,14 @@ public final class SelfDependencyPatcher {
|
||||
AtomicBoolean isCancelled = new AtomicBoolean();
|
||||
AtomicBoolean showDetails = new AtomicBoolean();
|
||||
|
||||
ProgressFrame dialog = new ProgressFrame(i18n("download.javafx"));
|
||||
ProgressFrame dialog;
|
||||
try {
|
||||
dialog = new SwingProgressFrame(i18n("download.javafx"));
|
||||
} catch (HeadlessException e) {
|
||||
LOG.warning("Failed to open dialog", e);
|
||||
dialog = new FakeProgressFrame();
|
||||
}
|
||||
|
||||
dialog.setProgressMaximum(dependencies.size() + 1);
|
||||
dialog.setProgress(count);
|
||||
dialog.setOnCancel(() -> isCancelled.set(true));
|
||||
@@ -296,9 +303,10 @@ public final class SelfDependencyPatcher {
|
||||
DependencyDescriptor dependency = dependencies.get(i);
|
||||
|
||||
final String url = repository.resolveDependencyURL(dependency);
|
||||
ProgressFrame finalDialog = dialog;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dialog.setCurrent(dependency.module);
|
||||
dialog.incrementProgress();
|
||||
finalDialog.setCurrent(dependency.module);
|
||||
finalDialog.incrementProgress();
|
||||
});
|
||||
|
||||
LOG.info("Downloading " + url);
|
||||
@@ -382,14 +390,32 @@ public final class SelfDependencyPatcher {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProgressFrame extends JDialog {
|
||||
public sealed interface ProgressFrame {
|
||||
void setCurrent(String component);
|
||||
|
||||
void setProgressMaximum(int total);
|
||||
|
||||
void setProgress(int n);
|
||||
|
||||
void incrementProgress();
|
||||
|
||||
void setOnCancel(Runnable action);
|
||||
|
||||
void setOnChangeSource(Runnable action);
|
||||
|
||||
void setVisible(boolean visible);
|
||||
|
||||
void dispose();
|
||||
}
|
||||
|
||||
public static final class SwingProgressFrame extends JDialog implements ProgressFrame {
|
||||
|
||||
private final JProgressBar progressBar;
|
||||
private final JLabel progressText;
|
||||
private final JButton btnChangeSource;
|
||||
private final JButton btnCancel;
|
||||
|
||||
public ProgressFrame(String title) {
|
||||
public SwingProgressFrame(String title) {
|
||||
JPanel panel = new JPanel();
|
||||
|
||||
setResizable(false);
|
||||
@@ -453,4 +479,39 @@ public final class SelfDependencyPatcher {
|
||||
btnChangeSource.addActionListener(e -> action.run());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class FakeProgressFrame implements ProgressFrame {
|
||||
|
||||
@Override
|
||||
public void setCurrent(String component) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgressMaximum(int total) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgress(int n) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementProgress() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnCancel(Runnable action) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnChangeSource(Runnable action) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1093,6 +1093,10 @@
|
||||
-fx-background-color: -monet-error-container;
|
||||
}
|
||||
|
||||
.resource-pack-list-cell:warning {
|
||||
-fx-background-color: -monet-error-container;
|
||||
}
|
||||
|
||||
.multi-file-item {
|
||||
-fx-padding: 10 0 10 0;
|
||||
}
|
||||
|
||||
@@ -106,14 +106,14 @@ account.methods.microsoft.birth=How to Change Your Account Birth Date
|
||||
account.methods.microsoft.code=Code (Copied to Clipboard)
|
||||
account.methods.microsoft.close_page=Microsoft account authorization is now completed.\n\
|
||||
\n\
|
||||
There are some extra works for us, but you can safely close this tab for now.
|
||||
There are some extra work for us, but you can safely close this tab for now.
|
||||
account.methods.microsoft.deauthorize=Deauthorize
|
||||
account.methods.microsoft.error.add_family=Please click <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">here</a> to change your account birth date to be over 18 years old, or add your account to a family.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live is not available in your current country/region.
|
||||
account.methods.microsoft.error.country_unavailable=XBOX Live is not available in your current country/region.
|
||||
account.methods.microsoft.error.missing_xbox_account=Please confirm that you have purchased Minecraft: Java Edition. If you have, please click <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a> to create your game profile.
|
||||
account.methods.microsoft.error.no_license=No Minecraft purchase record found. Please click <a href="https://www.xbox.com/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj">here</a> to get the game.\nIf you are using Game Pass, please create your game profile <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a>. If you have already purchased the game, please check if you are logged into the correct account.
|
||||
account.methods.microsoft.error.no_character=You have not created a game profile yet. Please click <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">here</a> to create one.
|
||||
account.methods.microsoft.error.banned=Your account may have been banned by Xbox Live.\n\
|
||||
account.methods.microsoft.error.banned=Your account may have been banned by XBOX Live.\n\
|
||||
You can click <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">here</a> to check the ban status of your account.
|
||||
account.methods.microsoft.error.unknown=Failed to log in, error code: %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=Failed to log in. Please try logging into your account using PASSWORD instead of other login methods.
|
||||
@@ -122,6 +122,7 @@ account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name
|
||||
account.methods.microsoft.hint=Click the "Log in" button to start adding your Microsoft account.
|
||||
account.methods.microsoft.methods.device=Log In with QR Code
|
||||
account.methods.microsoft.methods.device.hint=Scan QR code or visit <a href="%s">%s</a> to complete login, enter <b>%s</b> in the opened page.
|
||||
account.methods.microsoft.methods.device.hint.completed=Microsoft account authorization is now completed. There are some extra work for us, just wait for a while.
|
||||
account.methods.microsoft.methods.browser=Log In via Browser
|
||||
account.methods.microsoft.methods.browser.hint=Click the "Log in" button or <a href="%s">copy the link</a> and paste it into the browser to log in.
|
||||
account.methods.microsoft.manual=<b>If your internet connection is bad, it may cause web pages to load slowly or fail to load altogether.\nYou may try again later or switch to a different internet connection.</b>
|
||||
@@ -186,8 +187,10 @@ button.change_source=Change Download Source
|
||||
button.clear=Clear
|
||||
button.copy_and_exit=Copy and Exit
|
||||
button.delete=Delete
|
||||
button.disable=Disable
|
||||
button.do_not_show_again=Don't show again
|
||||
button.edit=Edit
|
||||
button.enable=Enable
|
||||
button.install=Install
|
||||
button.export=Export
|
||||
button.no=No
|
||||
@@ -241,6 +244,8 @@ curse.category.4472=Tech
|
||||
curse.category.4473=Magic
|
||||
curse.category.5128=Vanilla+
|
||||
curse.category.7418=Horror
|
||||
curse.category.9243=Expert
|
||||
curse.category.10683=RLCraft
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5299=Education
|
||||
@@ -266,7 +271,9 @@ curse.category.419=Magic
|
||||
curse.category.412=Technology
|
||||
curse.category.4557=Redstone
|
||||
curse.category.428=Tinker's Construct
|
||||
# '
|
||||
curse.category.8937=ModJam 2025
|
||||
curse.category.9026=CreativeMode
|
||||
curse.category.10775=Horror
|
||||
curse.category.414=Player Transport
|
||||
curse.category.4486=Lucky Blocks
|
||||
curse.category.432=Buildcraft
|
||||
@@ -298,6 +305,8 @@ curse.category.4558=Redstone
|
||||
curse.category.4843=Automation
|
||||
curse.category.4906=MCreator
|
||||
curse.category.7669=Twilight Forest
|
||||
curse.category.9049=Refined Storage
|
||||
curse.category.10754=Farmer's Delight
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5244=Font Packs
|
||||
@@ -316,6 +325,7 @@ curse.category.404=Animated
|
||||
curse.category.4465=Mod Support
|
||||
curse.category.402=Medieval
|
||||
curse.category.401=Modern
|
||||
curse.category.8939=ModJam 2025
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/17
|
||||
curse.category.4464=Modded World
|
||||
@@ -353,7 +363,6 @@ curse.sort.total_downloads=Total Downloads
|
||||
datetime.format=MMM d, yyyy, h\:mm\:ss a
|
||||
|
||||
download=Download
|
||||
download.hint=Install games and modpacks or download mods, resource packs, shaders, and worlds.
|
||||
download.code.404=File "%s" not found on the remote server.
|
||||
download.content=Addons
|
||||
download.shader=Shaders
|
||||
@@ -466,6 +475,7 @@ file=File
|
||||
folder.config=Configs
|
||||
folder.game=Working Directory
|
||||
folder.logs=Logs
|
||||
folder.crash-reports=Crash Reports
|
||||
folder.mod=Mods
|
||||
folder.resourcepacks=Resource Packs
|
||||
folder.shaderpacks=Shader Packs
|
||||
@@ -1088,11 +1098,11 @@ mods.category=Category
|
||||
mods.channel.alpha=Alpha
|
||||
mods.channel.beta=Beta
|
||||
mods.channel.release=Release
|
||||
mods.check_updates=Mod update process
|
||||
mods.check_updates=File update process
|
||||
mods.check_updates.button=Update
|
||||
mods.check_updates.confirm=Update
|
||||
mods.check_updates.current_version=Current Version
|
||||
mods.check_updates.empty=All mods are up-to-date
|
||||
mods.check_updates.empty=All files are up-to-date
|
||||
mods.check_updates.failed_check=Failed to check for updates.
|
||||
mods.check_updates.failed_download=Failed to download some files.
|
||||
mods.check_updates.file=File
|
||||
@@ -1263,12 +1273,19 @@ repositories.chooser.title=Choose download source for JavaFX
|
||||
|
||||
resourcepack=Resource Packs
|
||||
resourcepack.add=Add
|
||||
resourcepack.add.failed=Failed to add resource packs:
|
||||
resourcepack.add.title=Choose resource pack archive you want to add
|
||||
resourcepack.manage=Resource Packs
|
||||
resourcepack.download=Download
|
||||
resourcepack.add.failed=Failed to add resource pack
|
||||
resourcepack.delete.failed=Failed to delete resource pack
|
||||
resourcepack.download=Download
|
||||
resourcepack.download.title=Download Resource Pack - %1s
|
||||
resourcepack.manage=Resource Packs
|
||||
resourcepack.update_in_modpack.warning=Updating resource packs in a modpack can lead to irreparable results, possibly leading to incorrect textures and sounds. Are you sure you want to update?
|
||||
resourcepack.warning.invalid=Invalid pack meta
|
||||
resourcepack.warning.manipulate=Incompatible resource packs might not work well in game.\n\nAre you sure to enable the resource pack(s)?
|
||||
resourcepack.warning.missing_game_meta=Missing instance meta
|
||||
resourcepack.warning.missing_pack_meta=Missing pack meta
|
||||
resourcepack.warning.too_new=For newer Minecraft
|
||||
resourcepack.warning.too_old=For older Minecraft
|
||||
|
||||
reveal.in_file_manager=Reveal in File Manager
|
||||
|
||||
@@ -1550,7 +1567,8 @@ terracotta.terracotta=Terracotta | Multiplayer
|
||||
terracotta.status=Lobby
|
||||
terracotta.back=Exit
|
||||
terracotta.feedback.title=Fill Out Feedback Form
|
||||
terracotta.feedback.desc=As HMCL updates Multiplayer Core, we hope you can take 10 seconds to fill out the feedback form.
|
||||
terracotta.feedback.desc=We hope you can take 10 seconds to fill out the feedback form.
|
||||
terracotta.feedback.desc.update=As HMCL updates Multiplayer Core, we hope you can take 10 seconds to fill out the feedback form.
|
||||
terracotta.sudo_installing=HMCL must verify your password before installing Multiplayer Core
|
||||
terracotta.difficulty.easiest=Excellent network: almost connected!
|
||||
terracotta.difficulty.simple=Good network: connection may take some time
|
||||
|
||||
@@ -107,11 +107,11 @@ account.methods.microsoft.close_page=اكتمل تفويض حساب Microsoft.\n
|
||||
لا يزال هناك بعض الخطوات من جانبنا، لكن يمكنك إغلاق هذا التبويب بأمان الآن.
|
||||
account.methods.microsoft.deauthorize=إلغاء التفويض
|
||||
account.methods.microsoft.error.add_family=يُرجى النقر <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">هنا</a> لتغيير تاريخ ميلاد حسابك ليكون فوق 18 عاماً، أو إضافة حسابك إلى عائلة.
|
||||
account.methods.microsoft.error.country_unavailable=Xbox Live غير متاح في بلدك/منطقتك الحالية.
|
||||
account.methods.microsoft.error.missing_xbox_account=لا يوجد حساب Xbox مرتبط بحساب Microsoft الخاص بك بعد. يُرجى النقر <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">هنا</a> لربط واحد.
|
||||
account.methods.microsoft.error.country_unavailable=XBOX Live غير متاح في بلدك/منطقتك الحالية.
|
||||
account.methods.microsoft.error.missing_xbox_account=لا يوجد حساب XBOX مرتبط بحساب Microsoft الخاص بك بعد. يُرجى النقر <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">هنا</a> لربط واحد.
|
||||
account.methods.microsoft.error.no_character=يُرجى التأكد من أنك اشتريت Minecraft: Java Edition.\n\
|
||||
إذا اشتريته بالفعل، فقد لا يكون ملف اللعبة قد تم إنشاؤه. يُرجى النقر <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">هنا</a> لإنشاء ملف اللعبة.
|
||||
account.methods.microsoft.error.banned=قد يكون حسابك محظوراً من Xbox Live.\n\
|
||||
account.methods.microsoft.error.banned=قد يكون حسابك محظوراً من XBOX Live.\n\
|
||||
يمكنك النقر <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">هنا</a> للتحقق من حالة الحظر لحسابك.
|
||||
account.methods.microsoft.error.unknown=فشل تسجيل الدخول، رمز الخطأ: %d.
|
||||
account.methods.microsoft.error.wrong_verify_method=فشل تسجيل الدخول. يُرجى محاولة تسجيل الدخول إلى حسابك باستخدام كلمة المرور بدلاً من طرق الدخول الأخرى.
|
||||
@@ -181,8 +181,10 @@ button.change_source=تغيير مصدر التنزيل
|
||||
button.clear=مسح
|
||||
button.copy_and_exit=نسخ والخروج
|
||||
button.delete=حذف
|
||||
button.disable=تعطيل
|
||||
button.do_not_show_again=لا تُظهر مجدداً
|
||||
button.edit=تعديل
|
||||
button.enable=تفعيل
|
||||
button.install=تثبيت
|
||||
button.export=تصدير
|
||||
button.no=لا
|
||||
@@ -344,7 +346,6 @@ curse.sort.total_downloads=إجمالي التنزيلات
|
||||
datetime.format=MMM d, yyyy, h\:mm\:ss a
|
||||
|
||||
download=تنزيل
|
||||
download.hint=ثبّت الألعاب وحزم المودات أو نزّل المودات وحزم الموارد والظلال والعوالم.
|
||||
download.code.404=الملف "%s" غير موجود على الخادم البعيد.
|
||||
download.content=الإضافات
|
||||
download.shader=الظلال
|
||||
@@ -1473,7 +1474,7 @@ terracotta.terracotta=Terracotta | متعدد اللاعبين
|
||||
terracotta.status=الردهة
|
||||
terracotta.back=خروج
|
||||
terracotta.feedback.title=ملء استمارة التقييم
|
||||
terracotta.feedback.desc=مع تحديث HMCL لنواة متعدد اللاعبين، نأمل أن تخصص 10 ثوانٍ لملء استمارة التقييم.
|
||||
terracotta.feedback.desc.update=مع تحديث HMCL لنواة متعدد اللاعبين، نأمل أن تخصص 10 ثوانٍ لملء استمارة التقييم.
|
||||
terracotta.sudo_installing=يجب على HMCL التحقق من كلمة مرورك قبل تثبيت نواة متعدد اللاعبين
|
||||
terracotta.difficulty.easiest=شبكة ممتازة: الاتصال شبه مضمون!
|
||||
terracotta.difficulty.simple=شبكة جيدة: قد يستغرق الاتصال بعض الوقت
|
||||
|
||||
@@ -162,8 +162,10 @@ button.change_source=Cambiar fuente de descarga
|
||||
button.clear=Limpiar
|
||||
button.copy_and_exit=Copiar y salir
|
||||
button.delete=Borrar
|
||||
button.disable=Desactivar
|
||||
button.do_not_show_again=No volver a mostrar
|
||||
button.edit=Editar
|
||||
button.enable=Activar
|
||||
button.install=Instalar
|
||||
button.export=Exportar
|
||||
button.no=No
|
||||
@@ -324,7 +326,6 @@ curse.sort.total_downloads=Descargas totales
|
||||
datetime.format=d MMM yyyy, H\:mm\:ss
|
||||
|
||||
download=Descargar
|
||||
download.hint=Instalar juegos y modpacks o descargar mods, paquetes de recursos, sombreadores y mundos.
|
||||
download.code.404=Archivo no encontrado en el servidor remoto: %s
|
||||
download.content=Complementos
|
||||
download.shader=Sombreadores
|
||||
|
||||
@@ -131,7 +131,9 @@ button.change_source=ダウンロードソースの変更
|
||||
button.clear=クリア
|
||||
button.copy_and_exit=コピーして終了
|
||||
button.delete=削除
|
||||
button.disable=無効にする
|
||||
button.edit=編集
|
||||
button.enable=有効にする
|
||||
button.install=インストール
|
||||
button.export=エクスポート
|
||||
button.no=いいえ
|
||||
@@ -283,7 +285,6 @@ curse.sort.total_downloads=合計ダウンロード数
|
||||
datetime.format=yyyy/MM/dd H:mm:ss
|
||||
|
||||
download=ダウンロード
|
||||
download.hint=ゲームや modpack をインストールするか、mod、リソース パック、マップ、シェーダーをダウンロードします
|
||||
download.code.404=リモートサーバーにファイルが見つかりません:%s
|
||||
download.content=ゲームコンテンツ
|
||||
download.shader=シェーダー
|
||||
|
||||
@@ -322,7 +322,6 @@ curse.sort.total_downloads=引數
|
||||
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
download=引
|
||||
download.hint=裝戯事、改囊集,引改囊、資囊、光影與生界
|
||||
download.code.404=伺服器無案曰:%s\n君可求助於右上之鈕。
|
||||
download.content=戲案
|
||||
download.shader=光影
|
||||
|
||||
@@ -161,7 +161,9 @@ button.change_source=Изменить источник скачивания
|
||||
button.clear=Очистить
|
||||
button.copy_and_exit=Скопировать и выйти
|
||||
button.delete=Удалить
|
||||
button.disable=Отключить
|
||||
button.edit=Изменить
|
||||
button.enable=Включить
|
||||
button.install=Установить
|
||||
button.export=Экспорт
|
||||
button.no=Нет
|
||||
@@ -321,7 +323,6 @@ curse.sort.total_downloads=Скачиваний
|
||||
datetime.format=d MMM yyyy г., HH\:mm\:ss
|
||||
|
||||
download=Скачать
|
||||
download.hint=Установить игры и модпаки или скачать моды, пакеты ресурсов, шейдеры и миры.
|
||||
download.code.404=Файл «%s» не найден на удаленном сервере.
|
||||
download.content=Аддоны
|
||||
download.shader=Шейдеры
|
||||
|
||||
@@ -160,7 +160,9 @@ button.change_source=Змінити джерело завантаження
|
||||
button.clear=Очистити
|
||||
button.copy_and_exit=Копіювати та вийти
|
||||
button.delete=Видалити
|
||||
button.disable=Вимкнути
|
||||
button.edit=Редагувати
|
||||
button.enable=Увімкнути
|
||||
button.install=Встановити
|
||||
button.export=Експортувати
|
||||
button.no=Ні
|
||||
@@ -320,7 +322,6 @@ curse.sort.total_downloads=Всього завантажень
|
||||
datetime.format=dd.MM.yyyy, HH:mm:ss
|
||||
|
||||
download=Завантажити
|
||||
download.hint=Встановіть ігри та модпаки або завантажте моди, пакети ресурсів, шейдери та світи.
|
||||
download.code.404=Файл "%s" не знайдено на віддаленому сервері.
|
||||
download.content=Додатки
|
||||
download.shader=Шейдери
|
||||
|
||||
@@ -110,11 +110,11 @@ account.methods.microsoft.code=程式碼 (已自動複製)
|
||||
account.methods.microsoft.close_page=已完成 Microsoft 帳戶授權。啟動器將自動執行後續步驟。你現在可以關閉本頁面了。
|
||||
account.methods.microsoft.deauthorize=移除應用存取權
|
||||
account.methods.microsoft.error.add_family=請點擊 <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">此處</a> 更改你的帳戶出生日期,使年齡滿 18 歲以上,或將帳戶加入到家庭中。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 Xbox Live 的支援。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的國家或地區不受 XBOX Live 的支援。
|
||||
account.methods.microsoft.error.missing_xbox_account=請確認你已經購買了 Minecraft: Java 版。若已購買,請點擊 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 建立遊戲檔案。
|
||||
account.methods.microsoft.error.no_license=未找到 Minecraft 購買記錄。請點擊 <a href="https://www.xbox.com/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj">此處</a> 購買遊戲。\n若你使用 Game Pass,請在 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 建立遊戲檔案。若已購買,請檢查登入的帳戶是否正確。
|
||||
account.methods.microsoft.error.no_character=你可能未建立遊戲檔案。請點擊 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此處</a> 建立遊戲檔案。
|
||||
account.methods.microsoft.error.banned=你的帳戶可能被 Xbox Live 封禁。\n你可以點擊 <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">此處</a> 按鈕查詢帳戶封禁狀態。
|
||||
account.methods.microsoft.error.banned=你的帳戶可能被 XBOX Live 封禁。\n你可以點擊 <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">此處</a> 按鈕查詢帳戶封禁狀態。
|
||||
account.methods.microsoft.error.unknown=登入失敗。錯誤碼:%d。
|
||||
account.methods.microsoft.error.wrong_verify_method=登入失敗。請在 Microsoft 帳戶登入頁面嘗試使用密碼登入,不要使用其他登入方式。
|
||||
account.methods.microsoft.logging_in=登入中……
|
||||
@@ -122,6 +122,7 @@ account.methods.microsoft.makegameidsettings=建立檔案 / 編輯檔案名稱
|
||||
account.methods.microsoft.hint=點擊「登入」按鈕開始新增 Microsoft 帳戶。
|
||||
account.methods.microsoft.methods.device=掃描 QR Code 登入
|
||||
account.methods.microsoft.methods.device.hint=掃描 QR Code 或訪問 <a href="%s">%s</a>,在開啟的頁面中輸入 <b>%s</b> 完成登入。
|
||||
account.methods.microsoft.methods.device.hint.completed=已完成 Microsoft 帳戶授權。其餘登入步驟將由啟動器自動執行,請稍候。
|
||||
account.methods.microsoft.methods.browser=在瀏覽器中登入
|
||||
account.methods.microsoft.methods.browser.hint=點擊「登入」按鈕或者<a href="%s">複製連結</a>並在瀏覽器中貼上以登入。
|
||||
account.methods.microsoft.manual=<b>若網路環境不佳,可能會導致網頁載入緩慢甚至無法載入,請稍後再試或更換網路環境後再試。</b>
|
||||
@@ -184,9 +185,11 @@ button.change_source=切換下載源
|
||||
button.clear=清除
|
||||
button.copy_and_exit=複製並退出
|
||||
button.delete=刪除
|
||||
button.disable=停用
|
||||
button.do_not_show_again=不再顯示
|
||||
button.edit=編輯
|
||||
button.install=安裝
|
||||
button.enable=啟用
|
||||
button.export=匯出
|
||||
button.no=否
|
||||
button.ok=確定
|
||||
@@ -239,6 +242,8 @@ curse.category.4472=科技
|
||||
curse.category.4473=魔法
|
||||
curse.category.5128=原版增強
|
||||
curse.category.7418=恐怖
|
||||
curse.category.9243=專家
|
||||
curse.category.10683=RLCraft
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5299=教育
|
||||
@@ -263,6 +268,9 @@ curse.category.429=工業 (Industrialcraft)
|
||||
curse.category.419=魔法
|
||||
curse.category.412=科技
|
||||
curse.category.4557=紅石
|
||||
curse.category.8937=ModJam 2025
|
||||
curse.category.9026=創造模式
|
||||
curse.category.10775=恐怖
|
||||
curse.category.428=匠魂
|
||||
curse.category.414=交通運輸
|
||||
curse.category.4486=幸運方塊 (Lucky Blocks)
|
||||
@@ -295,8 +303,10 @@ curse.category.4558=紅石
|
||||
curse.category.4843=自動化
|
||||
curse.category.4906=MCreator
|
||||
curse.category.7669=暮色森林
|
||||
curse.category.9049=精緻儲存 (Refined Storage)
|
||||
curse.category.10754=農夫樂事 (Farmer's Delight)
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5244=字體包
|
||||
curse.category.5193=封包
|
||||
curse.category.399=蒸汽朋克
|
||||
@@ -313,6 +323,7 @@ curse.category.404=動態效果
|
||||
curse.category.4465=模組支援
|
||||
curse.category.402=中世紀風格
|
||||
curse.category.401=現代風格
|
||||
curse.category.8939=ModJam 2025
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/17
|
||||
curse.category.4464=模組
|
||||
@@ -350,7 +361,6 @@ curse.sort.total_downloads=下載量
|
||||
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
download=下載
|
||||
download.hint=安裝遊戲和模組包或下載模組、資源包、光影和世界
|
||||
download.code.404=遠端伺服器沒有需要下載的檔案:%s
|
||||
download.content=遊戲內容
|
||||
download.shader=光影
|
||||
@@ -431,6 +441,7 @@ file=檔案
|
||||
folder.config=模組設定目錄
|
||||
folder.game=實例執行目錄
|
||||
folder.logs=日誌目錄
|
||||
folder.crash-reports=崩潰報告目錄
|
||||
folder.mod=模組目錄
|
||||
folder.resourcepacks=資源包目錄
|
||||
folder.shaderpacks=著色器包目錄
|
||||
@@ -884,11 +895,11 @@ mods.category=類別
|
||||
mods.channel.alpha=Alpha
|
||||
mods.channel.beta=Beta
|
||||
mods.channel.release=Release
|
||||
mods.check_updates=模組更新檢查
|
||||
mods.check_updates=檔案更新檢查
|
||||
mods.check_updates.button=檢查更新
|
||||
mods.check_updates.confirm=更新
|
||||
mods.check_updates.current_version=目前版本
|
||||
mods.check_updates.empty=沒有需要更新的模組
|
||||
mods.check_updates.empty=沒有需要更新的檔案
|
||||
mods.check_updates.failed_check=檢查更新失敗
|
||||
mods.check_updates.failed_download=部分檔案下載失敗
|
||||
mods.check_updates.file=檔案
|
||||
@@ -1055,12 +1066,19 @@ repositories.chooser.title=選取 JavaFX 下載源
|
||||
|
||||
resourcepack=資源包
|
||||
resourcepack.add=新增資源包
|
||||
resourcepack.add.failed=新增資源包失敗:
|
||||
resourcepack.add.title=選取要新增的資源包壓縮檔
|
||||
resourcepack.manage=資源包管理
|
||||
resourcepack.download=下載資源包
|
||||
resourcepack.add.failed=新增資源包失敗
|
||||
resourcepack.delete.failed=刪除資源包失敗
|
||||
resourcepack.download=下載資源包
|
||||
resourcepack.download.title=資源包下載 - %1s
|
||||
resourcepack.manage=資源包管理
|
||||
resourcepack.update_in_modpack.warning=更新模組包中的資源包可能導致模組包損壞,使遊戲紋理和音效異常。你確定要更新嗎?
|
||||
resourcepack.warning.invalid=資源包中繼資料無效
|
||||
resourcepack.warning.manipulate=不相容的資源包在遊戲內可能不會正常執行。\n\n你確定要啟用這些資源包嗎?
|
||||
resourcepack.warning.missing_game_meta=目前實例中繼資料缺失
|
||||
resourcepack.warning.missing_pack_meta=資源包中繼資料缺失
|
||||
resourcepack.warning.too_new=適用於新版本
|
||||
resourcepack.warning.too_old=適用於舊版本
|
||||
|
||||
reveal.in_file_manager=在檔案管理員中查看
|
||||
|
||||
@@ -1340,7 +1358,8 @@ terracotta.terracotta=Terracotta | 陶瓦聯機
|
||||
terracotta.status=聯機大廳
|
||||
terracotta.back=退出
|
||||
terracotta.feedback.title=填寫回饋表
|
||||
terracotta.feedback.desc=在 HMCL 更新聯機核心時,我們歡迎您用 10 秒時間填寫聯機品質回饋收集表。
|
||||
terracotta.feedback.desc=我們歡迎您用 10 秒時間填寫聯機品質回饋收集表。
|
||||
terracotta.feedback.desc.update=在 HMCL 更新聯機核心時,我們歡迎您用 10 秒時間填寫聯機品質回饋收集表。
|
||||
terracotta.sudo_installing=HMCL 需要驗證您的密碼才能安裝線上核心
|
||||
terracotta.difficulty.easiest=目前網路狀況極佳:稍等一下就成功!
|
||||
terracotta.difficulty.simple=目前網路狀況較好:建立連線需要一段時間…
|
||||
|
||||
@@ -111,11 +111,11 @@ account.methods.microsoft.code=代码 (已自动复制)
|
||||
account.methods.microsoft.close_page=已完成微软账户授权。其余登录步骤将由启动器自动执行。你现在可以关闭本页面了。
|
||||
account.methods.microsoft.deauthorize=解除账户授权
|
||||
account.methods.microsoft.error.add_family=请点击 <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">此处</a> 更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 Xbox Live 的支持。
|
||||
account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 XBOX Live 的支持。
|
||||
account.methods.microsoft.error.missing_xbox_account=请确认你已经购买了 Minecraft: Java 版。若已购买,请点击 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.no_license=未找到 Minecraft 购买记录。请点击 <a href="https://www.xbox.com/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj">此处</a> 购买游戏。\n若你使用 Game Pass,请在 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 创建游戏档案。若已购买,请检查登录的账户是否正确。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.no_character=你尚未创建游戏档案。请点击 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.banned=你的账户可能被 Xbox Live 封禁。\n你可以点击 <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">此处</a> 按钮查询账户封禁状态。
|
||||
account.methods.microsoft.error.banned=你的账户可能被 XBOX Live 封禁。\n你可以点击 <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">此处</a> 按钮查询账户封禁状态。
|
||||
account.methods.microsoft.error.unknown=未知问题。错误码:%d。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.error.wrong_verify_method=登录失败。请在微软账户登录页面使用密码登录,不要使用其他登录方式。\n你可以点击右上角帮助按钮进行求助。
|
||||
account.methods.microsoft.logging_in=登录中……
|
||||
@@ -123,6 +123,7 @@ account.methods.microsoft.makegameidsettings=创建档案 / 编辑档案名称
|
||||
account.methods.microsoft.hint=点击“登录”按钮开始添加微软账户。
|
||||
account.methods.microsoft.methods.device=扫描二维码登录
|
||||
account.methods.microsoft.methods.device.hint=扫描二维码或访问 <a href="%s">%s</a>,在打开的页面中输入 <b>%s</b> 完成登录。
|
||||
account.methods.microsoft.methods.device.hint.completed=已完成微软账户授权。其余登录步骤将由启动器自动执行,请稍候。
|
||||
account.methods.microsoft.methods.browser=在浏览器登录
|
||||
account.methods.microsoft.methods.browser.hint=点击“登录”按钮或者<a href="%s">复制链接</a>并在浏览器中粘贴以登录。
|
||||
account.methods.microsoft.manual=<b>若网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。</b>\n\
|
||||
@@ -186,9 +187,11 @@ button.change_source=切换下载源
|
||||
button.clear=清除
|
||||
button.copy_and_exit=复制并退出
|
||||
button.delete=删除
|
||||
button.disable=禁用
|
||||
button.do_not_show_again=不再显示
|
||||
button.edit=修改
|
||||
button.install=安装
|
||||
button.enable=启用
|
||||
button.export=导出
|
||||
button.no=否
|
||||
button.ok=确定
|
||||
@@ -241,6 +244,8 @@ curse.category.4472=科技
|
||||
curse.category.4473=魔法
|
||||
curse.category.5128=原版增强
|
||||
curse.category.7418=恐怖
|
||||
curse.category.9243=专家
|
||||
curse.category.10683=RLCraft
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5299=教育
|
||||
@@ -265,6 +270,9 @@ curse.category.429=工业 (Industrialcraft)
|
||||
curse.category.419=魔法
|
||||
curse.category.412=科技
|
||||
curse.category.4557=红石
|
||||
curse.category.8937=ModJam 2025
|
||||
curse.category.9026=创造模式
|
||||
curse.category.10775=恐怖
|
||||
curse.category.428=匠魂
|
||||
curse.category.414=交通运输
|
||||
curse.category.4486=幸运方块 (Lucky Blocks)
|
||||
@@ -297,6 +305,8 @@ curse.category.4558=红石
|
||||
curse.category.4843=自动化
|
||||
curse.category.4906=MCreator
|
||||
curse.category.7669=暮色森林
|
||||
curse.category.9049=精致存储 (Refined Storage)
|
||||
curse.category.10754=农夫乐事 (Farmer's Delight)
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5244=字体包
|
||||
@@ -315,6 +325,7 @@ curse.category.404=动态效果
|
||||
curse.category.4465=模组支持
|
||||
curse.category.402=中世纪风格
|
||||
curse.category.401=现代风格
|
||||
curse.category.8939=ModJam 2025
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/17
|
||||
curse.category.4464=模组
|
||||
@@ -352,7 +363,6 @@ curse.sort.total_downloads=下载量
|
||||
datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss
|
||||
|
||||
download=下载
|
||||
download.hint=安装游戏和整合包或下载模组、资源包、光影和世界
|
||||
download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。
|
||||
download.content=游戏内容
|
||||
download.shader=光影
|
||||
@@ -435,6 +445,7 @@ file=文件
|
||||
folder.config=配置文件夹
|
||||
folder.game=实例运行文件夹
|
||||
folder.logs=日志文件夹
|
||||
folder.crash-reports=崩溃报告文件夹
|
||||
folder.mod=模组文件夹
|
||||
folder.resourcepacks=资源包文件夹
|
||||
folder.shaderpacks=光影包文件夹
|
||||
@@ -889,11 +900,11 @@ mods.category=类别
|
||||
mods.channel.alpha=快照版本
|
||||
mods.channel.beta=测试版本
|
||||
mods.channel.release=稳定版本
|
||||
mods.check_updates=模组更新检查
|
||||
mods.check_updates=文件更新检查
|
||||
mods.check_updates.button=检查更新
|
||||
mods.check_updates.confirm=更新
|
||||
mods.check_updates.current_version=当前版本
|
||||
mods.check_updates.empty=没有需要更新的模组
|
||||
mods.check_updates.empty=没有需要更新的文件
|
||||
mods.check_updates.failed_check=检查更新失败
|
||||
mods.check_updates.failed_download=部分文件下载失败
|
||||
mods.check_updates.file=文件
|
||||
@@ -1060,12 +1071,19 @@ repositories.chooser.title=选择 JavaFX 下载源
|
||||
|
||||
resourcepack=资源包
|
||||
resourcepack.add=添加资源包
|
||||
resourcepack.add.failed=添加资源包失败:
|
||||
resourcepack.add.title=选择要添加的资源包压缩包
|
||||
resourcepack.manage=资源包管理
|
||||
resourcepack.download=下载资源包
|
||||
resourcepack.add.failed=添加资源包失败
|
||||
resourcepack.delete.failed=删除资源包失败
|
||||
resourcepack.download=下载资源包
|
||||
resourcepack.download.title=资源包下载 - %1s
|
||||
resourcepack.manage=资源包管理
|
||||
resourcepack.update_in_modpack.warning=更新整合包中的资源包可能导致整合包损坏,使游戏纹理和音效异常。你确定要更新吗?
|
||||
resourcepack.warning.invalid=资源包元数据无效
|
||||
resourcepack.warning.manipulate=不兼容的资源包在游戏内可能不会正常运行。\n\n你确定要启用这些资源包吗?
|
||||
resourcepack.warning.missing_game_meta=实例元数据缺失
|
||||
resourcepack.warning.missing_pack_meta=资源包元数据缺失
|
||||
resourcepack.warning.too_new=适用于新版本
|
||||
resourcepack.warning.too_old=适用于旧版本
|
||||
|
||||
reveal.in_file_manager=在文件管理器中查看
|
||||
|
||||
@@ -1345,7 +1363,8 @@ terracotta.terracotta=Terracotta | 陶瓦联机
|
||||
terracotta.status=联机大厅
|
||||
terracotta.back=退出
|
||||
terracotta.feedback.title=填写反馈表
|
||||
terracotta.feedback.desc=在 HMCL 更新联机核心时,我们欢迎您用 10 秒时间填写联机质量反馈收集表。
|
||||
terracotta.feedback.desc=我们欢迎您用 10 秒时间填写联机质量反馈收集表。
|
||||
terracotta.feedback.desc.update=在 HMCL 更新联机核心时,我们欢迎您用 10 秒时间填写联机质量反馈收集表。
|
||||
terracotta.sudo_installing=HMCL 需要验证您的密码才能安装联机核心
|
||||
terracotta.difficulty.easiest=当前网络状态极好:稍等一下就成功!
|
||||
terracotta.difficulty.simple=当前网络状态较好:建立连接需要一段时间……
|
||||
|
||||
@@ -160,6 +160,8 @@ public class OAuth {
|
||||
continue;
|
||||
}
|
||||
|
||||
options.callback.loginCompletedDeviceCode();
|
||||
|
||||
return new Result(tokenResponse.accessToken, tokenResponse.refreshToken);
|
||||
}
|
||||
}
|
||||
@@ -268,6 +270,8 @@ public class OAuth {
|
||||
|
||||
void grantDeviceCode(String userCode, String verificationURI);
|
||||
|
||||
void loginCompletedDeviceCode();
|
||||
|
||||
/**
|
||||
* Open browser
|
||||
*
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
@@ -33,12 +34,15 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -169,7 +173,13 @@ public final class GameLibrariesTask extends Task<Void> {
|
||||
throw new IOException("Cannot fix optifine", e);
|
||||
}
|
||||
}
|
||||
} else if ("org.jackhuang.hmcl".equals(library.getGroupId()) && "mmc-bootstrap".equals(library.getArtifactId())) {
|
||||
try (InputStream input = MaintainTask.class.getResourceAsStream("/assets/game/HMCLMultiMCBootstrap-1.0.jar")) {
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.copy(Objects.requireNonNull(input, "Bundled HMCLMultiMCBootstrap is missing."), file, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDownloadLibrary(gameRepository, version, library, integrityCheck) && (library.hasDownloadURL() || !"optifine".equals(library.getGroupId()))) {
|
||||
dependencies.add(new LibraryDownloadTask(dependencyManager, file, library).withCounter("hmcl.install.libraries"));
|
||||
} else {
|
||||
|
||||
@@ -154,6 +154,11 @@ public class DefaultGameRepository implements GameRepository {
|
||||
return getRunDirectory(id).resolve("mods");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getResourcePackDirectory(String id) {
|
||||
return getRunDirectory(id).resolve("resourcepacks");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getVersionRoot(String id) {
|
||||
return getBaseDirectory().resolve("versions/" + id);
|
||||
@@ -564,8 +569,4 @@ public class DefaultGameRepository implements GameRepository {
|
||||
.append("baseDirectory", baseDirectory)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public Path getResourcepacksDirectory(String id) {
|
||||
return getRunDirectory(id).resolve("resourcepacks");
|
||||
}
|
||||
}
|
||||
@@ -137,6 +137,12 @@ public interface GameRepository extends VersionProvider {
|
||||
/// @return the mods directory
|
||||
Path getModsDirectory(String id);
|
||||
|
||||
/// Get the directory for placing resource packs.
|
||||
///
|
||||
/// @param id instance id
|
||||
/// @return the resource pack directory
|
||||
Path getResourcePackDirectory(String id);
|
||||
|
||||
/**
|
||||
* Get minecraft jar
|
||||
*
|
||||
|
||||
@@ -247,10 +247,10 @@ public class DataPack {
|
||||
private Path statusFile;
|
||||
private final BooleanProperty activeProperty;
|
||||
private final String id;
|
||||
private final LocalModFile.Description description;
|
||||
private final LocalAddonFile.Description description;
|
||||
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, LocalAddonFile.Description description, DataPack parentDataPack) {
|
||||
this.path = path;
|
||||
this.isDirectory = isDirectory;
|
||||
this.id = id;
|
||||
@@ -309,7 +309,7 @@ public class DataPack {
|
||||
return id;
|
||||
}
|
||||
|
||||
public LocalModFile.Description getDescription() {
|
||||
public LocalAddonFile.Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/// Sub-classes should implement `Comparable`
|
||||
public abstract class LocalAddonFile {
|
||||
|
||||
protected LocalAddonFile() {
|
||||
}
|
||||
|
||||
public abstract Path getFile();
|
||||
|
||||
/// Without extension
|
||||
public abstract String getFileName();
|
||||
|
||||
public boolean isDisabled() {
|
||||
return FileUtils.getName(getFile()).endsWith(LocalAddonManager.DISABLED_EXTENSION);
|
||||
}
|
||||
|
||||
public abstract void markDisabled() throws IOException;
|
||||
|
||||
public abstract void setOld(boolean old) throws IOException;
|
||||
|
||||
public abstract boolean keepOldFiles();
|
||||
|
||||
public abstract void delete() throws IOException;
|
||||
|
||||
@Nullable
|
||||
public abstract AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteMod.Type type) throws IOException;
|
||||
|
||||
public record AddonUpdate(
|
||||
LocalAddonFile localAddonFile,
|
||||
RemoteMod.Version currentVersion,
|
||||
RemoteMod.Version targetVersion,
|
||||
boolean useRemoteFileName
|
||||
) {
|
||||
}
|
||||
|
||||
public static class Description {
|
||||
private final List<LocalAddonFile.Description.Part> parts;
|
||||
|
||||
public Description(String text) {
|
||||
this.parts = new ArrayList<>();
|
||||
this.parts.add(new LocalAddonFile.Description.Part(text, "black"));
|
||||
}
|
||||
|
||||
public Description(List<LocalAddonFile.Description.Part> parts) {
|
||||
this.parts = parts;
|
||||
}
|
||||
|
||||
public List<LocalAddonFile.Description.Part> getParts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (LocalAddonFile.Description.Part part : parts) {
|
||||
builder.append(part.text);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public String toStringSingleLine() {
|
||||
return toString().lines().map(String::trim).filter(StringUtils::isNotBlank).collect(Collectors.joining(" | "));
|
||||
}
|
||||
|
||||
public static class Part {
|
||||
private final String text;
|
||||
private final String color;
|
||||
|
||||
public Part(String text) {
|
||||
this(text, "");
|
||||
}
|
||||
|
||||
public Part(String text, String color) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
this.color = Objects.requireNonNull(color);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public abstract class LocalAddonManager<T extends LocalAddonFile> {
|
||||
|
||||
public static final String DISABLED_EXTENSION = ".disabled";
|
||||
public static final String OLD_EXTENSION = ".old";
|
||||
|
||||
public static String getLocalAddonName(Path file) {
|
||||
return StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION, OLD_EXTENSION);
|
||||
}
|
||||
|
||||
protected final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
protected final Set<@NotNull T> localFiles = new LinkedHashSet<>();
|
||||
|
||||
protected final GameRepository repository;
|
||||
protected final String id;
|
||||
|
||||
public LocalAddonManager(GameRepository gameRepository, String versionId) {
|
||||
this.repository = gameRepository;
|
||||
this.id = versionId;
|
||||
}
|
||||
|
||||
public GameRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public String getInstanceId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public abstract Path getDirectory();
|
||||
|
||||
public abstract void refresh() throws IOException;
|
||||
|
||||
public abstract Comparator<T> getComparator();
|
||||
|
||||
public @Unmodifiable List<T> getLocalFiles() throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
return localFiles.stream().sorted(getComparator()).toList();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Path setOld(T modFile, boolean old) throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
Path newPath;
|
||||
if (old) {
|
||||
newPath = backupFile(modFile.getFile());
|
||||
localFiles.remove(modFile);
|
||||
} else {
|
||||
newPath = restoreFile(modFile.getFile());
|
||||
localFiles.add(modFile);
|
||||
}
|
||||
return newPath;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private Path backupFile(Path file) throws IOException {
|
||||
Path newPath = file.resolveSibling(
|
||||
StringUtils.addSuffix(
|
||||
StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION),
|
||||
OLD_EXTENSION
|
||||
)
|
||||
);
|
||||
if (Files.exists(file)) {
|
||||
Files.move(file, newPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
return newPath;
|
||||
}
|
||||
|
||||
private Path restoreFile(Path file) throws IOException {
|
||||
Path newPath = file.resolveSibling(
|
||||
StringUtils.removeSuffix(FileUtils.getName(file), OLD_EXTENSION)
|
||||
);
|
||||
if (Files.exists(file)) {
|
||||
Files.move(file, newPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
return newPath;
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,9 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -36,7 +33,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
public final class LocalModFile extends LocalAddonFile implements Comparable<LocalModFile> {
|
||||
|
||||
private Path file;
|
||||
private final ModManager modManager;
|
||||
@@ -56,6 +53,7 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
}
|
||||
|
||||
public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) {
|
||||
super();
|
||||
this.modManager = modManager;
|
||||
this.mod = mod;
|
||||
this.file = file;
|
||||
@@ -85,7 +83,7 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
}
|
||||
};
|
||||
|
||||
fileName = FileUtils.getNameWithoutExtension(ModManager.getModName(file));
|
||||
fileName = FileUtils.getNameWithoutExtension(LocalAddonManager.getLocalAddonName(file));
|
||||
|
||||
if (isOld()) {
|
||||
mod.getOldFiles().add(this);
|
||||
@@ -102,6 +100,7 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
return mod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getFile() {
|
||||
return file;
|
||||
}
|
||||
@@ -154,6 +153,7 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
activeProperty.set(active);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
@@ -162,6 +162,7 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
return modManager.isOld(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOld(boolean old) throws IOException {
|
||||
file = modManager.setOld(this, old);
|
||||
|
||||
@@ -174,13 +175,27 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
}
|
||||
}
|
||||
|
||||
public void disable() throws IOException {
|
||||
@Override
|
||||
public boolean keepOldFiles() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markDisabled() throws IOException {
|
||||
file = modManager.disableMod(file);
|
||||
}
|
||||
|
||||
public ModUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteModRepository repository) throws IOException {
|
||||
Optional<RemoteMod.Version> currentVersion = repository.getRemoteVersionByLocalFile(this, file);
|
||||
if (!currentVersion.isPresent()) return null;
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteMod.Type type) throws IOException {
|
||||
RemoteModRepository repository = type.getRepoForType(RemoteModRepository.Type.MOD);
|
||||
if (repository == null) return null;
|
||||
Optional<RemoteMod.Version> currentVersion = repository.getRemoteVersionByLocalFile(file);
|
||||
if (currentVersion.isEmpty()) return null;
|
||||
List<RemoteMod.Version> remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().getModid())
|
||||
.filter(version -> version.getGameVersions().contains(gameVersion))
|
||||
.filter(version -> version.getLoaders().contains(getModLoaderType()))
|
||||
@@ -188,7 +203,7 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
.sorted(Comparator.comparing(RemoteMod.Version::getDatePublished).reversed())
|
||||
.toList();
|
||||
if (remoteVersions.isEmpty()) return null;
|
||||
return new ModUpdate(this, currentVersion.get(), remoteVersions.get(0));
|
||||
return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -205,76 +220,4 @@ public final class LocalModFile implements Comparable<LocalModFile> {
|
||||
public int hashCode() {
|
||||
return Objects.hash(getFileName());
|
||||
}
|
||||
|
||||
public static class ModUpdate {
|
||||
private final LocalModFile localModFile;
|
||||
private final RemoteMod.Version currentVersion;
|
||||
private final RemoteMod.Version candidate;
|
||||
|
||||
public ModUpdate(LocalModFile localModFile, RemoteMod.Version currentVersion, RemoteMod.Version candidate) {
|
||||
this.localModFile = localModFile;
|
||||
this.currentVersion = currentVersion;
|
||||
this.candidate = candidate;
|
||||
}
|
||||
|
||||
public LocalModFile getLocalMod() {
|
||||
return localModFile;
|
||||
}
|
||||
|
||||
public RemoteMod.Version getCurrentVersion() {
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
public RemoteMod.Version getCandidate() {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Description {
|
||||
private final List<Part> parts;
|
||||
|
||||
public Description(String text) {
|
||||
this.parts = new ArrayList<>();
|
||||
this.parts.add(new Part(text, "black"));
|
||||
}
|
||||
|
||||
public Description(List<Part> parts) {
|
||||
this.parts = parts;
|
||||
}
|
||||
|
||||
public List<Part> getParts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Part part : parts) {
|
||||
builder.append(part.text);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static class Part {
|
||||
private final String text;
|
||||
private final String color;
|
||||
|
||||
public Part(String text) {
|
||||
this(text, "");
|
||||
}
|
||||
|
||||
public Part(String text, String color) {
|
||||
this.text = Objects.requireNonNull(text);
|
||||
this.color = Objects.requireNonNull(color);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,5 @@ public enum ModLoaderType {
|
||||
FABRIC,
|
||||
QUILT,
|
||||
LITE_LOADER,
|
||||
LEGACY_FABRIC,
|
||||
PACK
|
||||
LEGACY_FABRIC
|
||||
}
|
||||
@@ -35,7 +35,9 @@ import java.util.*;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ModManager {
|
||||
public final class ModManager extends LocalAddonManager<LocalModFile> {
|
||||
public static final List<String> MOD_EXTENSIONS = List.of("jar", "litemod");
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ModMetadataReader {
|
||||
LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException;
|
||||
@@ -50,39 +52,26 @@ public final class ModManager {
|
||||
pair(ForgeNewModMetadata::fromNeoForgeFile, ModLoaderType.NEO_FORGE),
|
||||
pair(ForgeOldModMetadata::fromFile, ModLoaderType.FORGE),
|
||||
pair(FabricModMetadata::fromFile, ModLoaderType.FABRIC),
|
||||
pair(QuiltModMetadata::fromFile, ModLoaderType.QUILT),
|
||||
pair(PackMcMeta::fromFile, ModLoaderType.PACK)
|
||||
pair(QuiltModMetadata::fromFile, ModLoaderType.QUILT)
|
||||
);
|
||||
|
||||
map.put("zip", zipReaders);
|
||||
map.put("jar", zipReaders);
|
||||
map.put("litemod", List.of(pair(LiteModMetadata::fromFile, ModLoaderType.LITE_LOADER)));
|
||||
|
||||
READERS = map;
|
||||
}
|
||||
|
||||
private final GameRepository repository;
|
||||
private final String id;
|
||||
private final TreeSet<LocalModFile> localModFiles = new TreeSet<>();
|
||||
private final HashMap<Pair<String, ModLoaderType>, LocalMod> localMods = new HashMap<>();
|
||||
private LibraryAnalyzer analyzer;
|
||||
|
||||
private boolean loaded = false;
|
||||
|
||||
public ModManager(GameRepository repository, String id) {
|
||||
this.repository = repository;
|
||||
this.id = id;
|
||||
super(repository, id);
|
||||
}
|
||||
|
||||
public GameRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public String getInstanceId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Path getModsDirectory() {
|
||||
@Override
|
||||
public Path getDirectory() {
|
||||
return repository.getModsDirectory(id);
|
||||
}
|
||||
|
||||
@@ -91,12 +80,22 @@ public final class ModManager {
|
||||
}
|
||||
|
||||
public LocalMod getLocalMod(String modId, ModLoaderType modLoaderType) {
|
||||
return localMods.computeIfAbsent(pair(modId, modLoaderType),
|
||||
x -> new LocalMod(x.getKey(), x.getValue()));
|
||||
lock.lock();
|
||||
try {
|
||||
return localMods.computeIfAbsent(pair(modId, modLoaderType),
|
||||
x -> new LocalMod(x.getKey(), x.getValue()));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasMod(String modId, ModLoaderType modLoaderType) {
|
||||
return localMods.containsKey(pair(modId, modLoaderType));
|
||||
lock.lock();
|
||||
try {
|
||||
return localMods.containsKey(pair(modId, modLoaderType));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void addModInfo(Path file) {
|
||||
@@ -161,137 +160,128 @@ public final class ModManager {
|
||||
getLocalMod(fileNameWithoutExtension, ModLoaderType.UNKNOWN),
|
||||
file,
|
||||
fileNameWithoutExtension,
|
||||
new LocalModFile.Description("litemod".equals(extension) ? "LiteLoader Mod" : "")
|
||||
new LocalAddonFile.Description("litemod".equals(extension) ? "LiteLoader Mod" : "")
|
||||
);
|
||||
}
|
||||
|
||||
if (!modInfo.isOld()) {
|
||||
localModFiles.add(modInfo);
|
||||
localFiles.add(modInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshMods() throws IOException {
|
||||
localModFiles.clear();
|
||||
localMods.clear();
|
||||
@Override
|
||||
public void refresh() throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
localFiles.clear();
|
||||
localMods.clear();
|
||||
|
||||
analyzer = LibraryAnalyzer.analyze(getRepository().getResolvedPreservingPatchesVersion(id), null);
|
||||
analyzer = LibraryAnalyzer.analyze(getRepository().getResolvedPreservingPatchesVersion(id), null);
|
||||
|
||||
boolean supportSubfolders = analyzer.has(LibraryAnalyzer.LibraryType.FORGE)
|
||||
|| analyzer.has(LibraryAnalyzer.LibraryType.QUILT);
|
||||
boolean supportSubfolders = analyzer.has(LibraryAnalyzer.LibraryType.FORGE)
|
||||
|| analyzer.has(LibraryAnalyzer.LibraryType.QUILT);
|
||||
|
||||
if (Files.isDirectory(getModsDirectory())) {
|
||||
try (DirectoryStream<Path> modsDirectoryStream = Files.newDirectoryStream(getModsDirectory())) {
|
||||
for (Path subitem : modsDirectoryStream) {
|
||||
if (supportSubfolders && Files.isDirectory(subitem) && !".connector".equalsIgnoreCase(subitem.getFileName().toString())) {
|
||||
try (DirectoryStream<Path> subitemDirectoryStream = Files.newDirectoryStream(subitem)) {
|
||||
for (Path subsubitem : subitemDirectoryStream) {
|
||||
addModInfo(subsubitem);
|
||||
if (Files.isDirectory(getDirectory())) {
|
||||
try (DirectoryStream<Path> modsDirectoryStream = Files.newDirectoryStream(getDirectory())) {
|
||||
for (Path subitem : modsDirectoryStream) {
|
||||
if (supportSubfolders && Files.isDirectory(subitem) && !".connector".equalsIgnoreCase(subitem.getFileName().toString())) {
|
||||
try (DirectoryStream<Path> subitemDirectoryStream = Files.newDirectoryStream(subitem)) {
|
||||
for (Path subsubitem : subitemDirectoryStream) {
|
||||
addModInfo(subsubitem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addModInfo(subitem);
|
||||
}
|
||||
} else {
|
||||
addModInfo(subitem);
|
||||
}
|
||||
}
|
||||
}
|
||||
loaded = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
public @Unmodifiable List<LocalModFile> getMods() throws IOException {
|
||||
if (!loaded)
|
||||
refreshMods();
|
||||
return List.copyOf(localModFiles);
|
||||
@Override
|
||||
public Comparator<LocalModFile> getComparator() {
|
||||
return LocalModFile::compareTo;
|
||||
}
|
||||
|
||||
public @Unmodifiable List<LocalModFile> getLocalFiles() throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
if (!loaded)
|
||||
refresh();
|
||||
return super.getLocalFiles();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void addMod(Path file) throws IOException {
|
||||
if (!isFileNameMod(file))
|
||||
throw new IllegalArgumentException("File " + file + " is not a valid mod file.");
|
||||
|
||||
if (!loaded)
|
||||
refreshMods();
|
||||
lock.lock();
|
||||
try {
|
||||
if (!loaded)
|
||||
refresh();
|
||||
|
||||
Path modsDirectory = getModsDirectory();
|
||||
Files.createDirectories(modsDirectory);
|
||||
Path modsDirectory = getDirectory();
|
||||
Files.createDirectories(modsDirectory);
|
||||
|
||||
Path newFile = modsDirectory.resolve(file.getFileName());
|
||||
FileUtils.copyFile(file, newFile);
|
||||
Path newFile = modsDirectory.resolve(file.getFileName());
|
||||
FileUtils.copyFile(file, newFile);
|
||||
|
||||
addModInfo(newFile);
|
||||
addModInfo(newFile);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeMods(LocalModFile... localModFiles) throws IOException {
|
||||
for (LocalModFile localModFile : localModFiles) {
|
||||
Files.deleteIfExists(localModFile.getFile());
|
||||
localModFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void rollback(LocalModFile from, LocalModFile to) throws IOException {
|
||||
if (!loaded) {
|
||||
throw new IllegalStateException("ModManager Not loaded");
|
||||
}
|
||||
if (!localModFiles.contains(from)) {
|
||||
throw new IllegalStateException("Rolling back an unknown mod " + from.getFileName());
|
||||
}
|
||||
if (from.isOld()) {
|
||||
throw new IllegalArgumentException("Rolling back an old mod " + from.getFileName());
|
||||
}
|
||||
if (!to.isOld()) {
|
||||
throw new IllegalArgumentException("Rolling back to an old path " + to.getFileName());
|
||||
}
|
||||
if (from.getFileName().equals(to.getFileName())) {
|
||||
// We cannot roll back to the mod with the same name.
|
||||
return;
|
||||
}
|
||||
lock.lock();
|
||||
try {
|
||||
if (!loaded) {
|
||||
throw new IllegalStateException("ModManager Not loaded");
|
||||
}
|
||||
if (!localFiles.contains(from)) {
|
||||
throw new IllegalStateException("Rolling back an unknown mod " + from.getFileName());
|
||||
}
|
||||
if (from.isOld()) {
|
||||
throw new IllegalArgumentException("Rolling back an old mod " + from.getFileName());
|
||||
}
|
||||
if (!to.isOld()) {
|
||||
throw new IllegalArgumentException("Rolling back to an old path " + to.getFileName());
|
||||
}
|
||||
if (from.getFileName().equals(to.getFileName())) {
|
||||
// We cannot roll back to the mod with the same name.
|
||||
return;
|
||||
}
|
||||
|
||||
LocalMod mod = Objects.requireNonNull(from.getMod());
|
||||
if (mod != to.getMod()) {
|
||||
throw new IllegalArgumentException("Rolling back mod " + from.getFileName() + " to a different mod " + to.getFileName());
|
||||
}
|
||||
if (!mod.getFiles().contains(from)
|
||||
|| !mod.getOldFiles().contains(to)) {
|
||||
throw new IllegalStateException("LocalMod state corrupt");
|
||||
}
|
||||
LocalMod mod = Objects.requireNonNull(from.getMod());
|
||||
if (mod != to.getMod()) {
|
||||
throw new IllegalArgumentException("Rolling back mod " + from.getFileName() + " to a different mod " + to.getFileName());
|
||||
}
|
||||
if (!mod.getFiles().contains(from)
|
||||
|| !mod.getOldFiles().contains(to)) {
|
||||
throw new IllegalStateException("LocalMod state corrupt");
|
||||
}
|
||||
|
||||
boolean active = from.isActive();
|
||||
from.setActive(true);
|
||||
from.setOld(true);
|
||||
to.setOld(false);
|
||||
to.setActive(active);
|
||||
}
|
||||
|
||||
private Path backupMod(Path file) throws IOException {
|
||||
Path newPath = file.resolveSibling(
|
||||
StringUtils.addSuffix(
|
||||
StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION),
|
||||
OLD_EXTENSION
|
||||
)
|
||||
);
|
||||
if (Files.exists(file)) {
|
||||
Files.move(file, newPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
boolean active = from.isActive();
|
||||
from.setActive(true);
|
||||
from.setOld(true);
|
||||
to.setOld(false);
|
||||
to.setActive(active);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return newPath;
|
||||
}
|
||||
|
||||
private Path restoreMod(Path file) throws IOException {
|
||||
Path newPath = file.resolveSibling(
|
||||
StringUtils.removeSuffix(FileUtils.getName(file), OLD_EXTENSION)
|
||||
);
|
||||
if (Files.exists(file)) {
|
||||
Files.move(file, newPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
return newPath;
|
||||
}
|
||||
|
||||
public Path setOld(LocalModFile modFile, boolean old) throws IOException {
|
||||
Path newPath;
|
||||
if (old) {
|
||||
newPath = backupMod(modFile.getFile());
|
||||
localModFiles.remove(modFile);
|
||||
} else {
|
||||
newPath = restoreMod(modFile.getFile());
|
||||
localModFiles.add(modFile);
|
||||
}
|
||||
return newPath;
|
||||
}
|
||||
|
||||
public Path disableMod(Path file) throws IOException {
|
||||
@@ -314,10 +304,6 @@ public final class ModManager {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public static String getModName(Path file) {
|
||||
return StringUtils.removeSuffix(FileUtils.getName(file), DISABLED_EXTENSION, OLD_EXTENSION);
|
||||
}
|
||||
|
||||
public boolean isOld(Path file) {
|
||||
return FileUtils.getName(file).endsWith(OLD_EXTENSION);
|
||||
}
|
||||
@@ -327,8 +313,8 @@ public final class ModManager {
|
||||
}
|
||||
|
||||
public static boolean isFileNameMod(Path file) {
|
||||
String name = getModName(file);
|
||||
return name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".litemod");
|
||||
String name = getLocalAddonName(file);
|
||||
return MOD_EXTENSIONS.contains(FileUtils.getExtension(name).toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
public static boolean isFileMod(Path modFile) {
|
||||
@@ -353,11 +339,6 @@ public final class ModManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Files.exists(fs.getPath("pack.mcmeta"))) {
|
||||
// resource pack, data pack
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
@@ -365,22 +346,19 @@ public final class ModManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if "mods" directory has mod file named "fileName" no matter the mod is disabled,upgraded or not
|
||||
* Check if "mods" directory has mod file named "fileName" no matter the mod is disabled, upgraded or not
|
||||
*
|
||||
* @param fileName name of the file whose existence is being checked
|
||||
* @return true if the file exists
|
||||
*/
|
||||
public boolean hasSimpleMod(String fileName) {
|
||||
return Files.exists(getModsDirectory().resolve(StringUtils.removeSuffix(fileName, DISABLED_EXTENSION)))
|
||||
|| Files.exists(getModsDirectory().resolve(StringUtils.addSuffix(fileName, DISABLED_EXTENSION)))
|
||||
|| Files.exists(getModsDirectory().resolve(StringUtils.removeSuffix(fileName, OLD_EXTENSION)))
|
||||
|| Files.exists(getModsDirectory().resolve(StringUtils.addSuffix(fileName, OLD_EXTENSION)));
|
||||
return Files.exists(getDirectory().resolve(StringUtils.removeSuffix(fileName, DISABLED_EXTENSION)))
|
||||
|| Files.exists(getDirectory().resolve(StringUtils.addSuffix(fileName, DISABLED_EXTENSION)))
|
||||
|| Files.exists(getDirectory().resolve(StringUtils.removeSuffix(fileName, OLD_EXTENSION)))
|
||||
|| Files.exists(getDirectory().resolve(StringUtils.addSuffix(fileName, OLD_EXTENSION)));
|
||||
}
|
||||
|
||||
public Path getSimpleModPath(String fileName) {
|
||||
return getModsDirectory().resolve(fileName);
|
||||
return getDirectory().resolve(fileName);
|
||||
}
|
||||
|
||||
public static final String DISABLED_EXTENSION = ".disabled";
|
||||
public static final String OLD_EXTENSION = ".old";
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
@@ -41,7 +42,7 @@ public final class RemoteMod {
|
||||
public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository, DownloadProvider downloadProvider) throws IOException {
|
||||
throw new IOException();
|
||||
}
|
||||
});
|
||||
}, RemoteModRepository.Type.MOD);
|
||||
|
||||
private final String slug;
|
||||
private final String author;
|
||||
@@ -51,8 +52,9 @@ public final class RemoteMod {
|
||||
private final String pageUrl;
|
||||
private final String iconUrl;
|
||||
private final IMod data;
|
||||
private final RemoteModRepository.Type repoType;
|
||||
|
||||
public RemoteMod(String slug, String author, String title, String description, List<String> categories, String pageUrl, String iconUrl, IMod data) {
|
||||
public RemoteMod(String slug, String author, String title, String description, List<String> categories, String pageUrl, String iconUrl, IMod data, RemoteModRepository.Type repoType) {
|
||||
this.slug = slug;
|
||||
this.author = author;
|
||||
this.title = title;
|
||||
@@ -61,6 +63,7 @@ public final class RemoteMod {
|
||||
this.pageUrl = pageUrl;
|
||||
this.iconUrl = iconUrl;
|
||||
this.data = data;
|
||||
this.repoType = repoType;
|
||||
}
|
||||
|
||||
public String getSlug() {
|
||||
@@ -95,6 +98,10 @@ public final class RemoteMod {
|
||||
return data;
|
||||
}
|
||||
|
||||
public RemoteModRepository.Type getRepositoryType() {
|
||||
return repoType;
|
||||
}
|
||||
|
||||
public enum VersionType {
|
||||
Release,
|
||||
Beta,
|
||||
@@ -188,17 +195,56 @@ public final class RemoteMod {
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
CURSEFORGE(CurseForgeRemoteModRepository.MODS),
|
||||
MODRINTH(ModrinthRemoteModRepository.MODS);
|
||||
CURSEFORGE(
|
||||
CurseForgeRemoteModRepository.MODS,
|
||||
CurseForgeRemoteModRepository.RESOURCE_PACKS,
|
||||
CurseForgeRemoteModRepository.SHADERS,
|
||||
CurseForgeRemoteModRepository.WORLDS,
|
||||
CurseForgeRemoteModRepository.MODPACKS,
|
||||
CurseForgeRemoteModRepository.CUSTOMIZATIONS
|
||||
),
|
||||
MODRINTH(
|
||||
ModrinthRemoteModRepository.MODS,
|
||||
ModrinthRemoteModRepository.RESOURCE_PACKS,
|
||||
ModrinthRemoteModRepository.SHADER_PACKS,
|
||||
null,
|
||||
ModrinthRemoteModRepository.MODPACKS,
|
||||
null
|
||||
);
|
||||
|
||||
private final RemoteModRepository remoteModRepository;
|
||||
public final RemoteModRepository modRepo;
|
||||
public final RemoteModRepository resourcePackRepo;
|
||||
public final RemoteModRepository shaderPackRepo;
|
||||
public final RemoteModRepository worldRepo;
|
||||
public final RemoteModRepository modpackRepo;
|
||||
public final RemoteModRepository customizationRepo;
|
||||
|
||||
public RemoteModRepository getRemoteModRepository() {
|
||||
return this.remoteModRepository;
|
||||
@Nullable
|
||||
public RemoteModRepository getRepoForType(RemoteModRepository.Type type) {
|
||||
return switch (type) {
|
||||
case MOD -> modRepo;
|
||||
case RESOURCE_PACK -> resourcePackRepo;
|
||||
case SHADER_PACK -> shaderPackRepo;
|
||||
case WORLD -> worldRepo;
|
||||
case MODPACK -> modpackRepo;
|
||||
case CUSTOMIZATION -> customizationRepo;
|
||||
};
|
||||
}
|
||||
|
||||
Type(RemoteModRepository remoteModRepository) {
|
||||
this.remoteModRepository = remoteModRepository;
|
||||
Type(
|
||||
RemoteModRepository modRepo,
|
||||
RemoteModRepository resourcePackRepo,
|
||||
RemoteModRepository shaderPackRepo,
|
||||
RemoteModRepository worldRepo,
|
||||
RemoteModRepository modpackRepo,
|
||||
RemoteModRepository customizationRepo
|
||||
) {
|
||||
this.modRepo = modRepo;
|
||||
this.resourcePackRepo = resourcePackRepo;
|
||||
this.shaderPackRepo = shaderPackRepo;
|
||||
this.worldRepo = worldRepo;
|
||||
this.modpackRepo = modpackRepo;
|
||||
this.customizationRepo = customizationRepo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ public interface RemoteModRepository {
|
||||
SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder)
|
||||
throws IOException;
|
||||
|
||||
Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException;
|
||||
Optional<RemoteMod.Version> getRemoteVersionByLocalFile(Path file) throws IOException;
|
||||
|
||||
RemoteMod getModById(DownloadProvider downloadProvider, String id) throws IOException;
|
||||
|
||||
@@ -102,27 +102,6 @@ public interface RemoteModRepository {
|
||||
|
||||
Stream<Category> getCategories() throws IOException;
|
||||
|
||||
class Category {
|
||||
private final Object self;
|
||||
private final String id;
|
||||
private final List<Category> subcategories;
|
||||
|
||||
public Category(Object self, String id, List<Category> subcategories) {
|
||||
this.self = self;
|
||||
this.id = id;
|
||||
this.subcategories = subcategories;
|
||||
}
|
||||
|
||||
public Object getSelf() {
|
||||
return self;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<Category> getSubcategories() {
|
||||
return subcategories;
|
||||
}
|
||||
record Category(Object self, String id, List<Category> subcategories) {
|
||||
}
|
||||
}
|
||||
@@ -212,7 +212,7 @@ public class CurseAddon implements RemoteMod.IMod {
|
||||
return modRepository.getRemoteVersionsById(downloadProvider, Integer.toString(id));
|
||||
}
|
||||
|
||||
public RemoteMod toMod() {
|
||||
public RemoteMod toMod(RemoteModRepository.Type type) {
|
||||
String iconUrl = "";
|
||||
if (logo != null) {
|
||||
if (StringUtils.isNotBlank(logo.getThumbnailUrl()))
|
||||
@@ -229,7 +229,8 @@ public class CurseAddon implements RemoteMod.IMod {
|
||||
categories.stream().map(category -> Integer.toString(category.getId())).collect(Collectors.toList()),
|
||||
links.websiteUrl,
|
||||
iconUrl,
|
||||
this
|
||||
this,
|
||||
type
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+7
-8
@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.mod.curse;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.MurmurHash2;
|
||||
@@ -117,8 +116,8 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
SEMAPHORE.acquireUninterruptibly();
|
||||
try {
|
||||
int categoryId = 0;
|
||||
if (category != null && category.getSelf() instanceof CurseAddon.Category) {
|
||||
categoryId = ((CurseAddon.Category) category.getSelf()).getId();
|
||||
if (category != null && category.self() instanceof CurseAddon.Category) {
|
||||
categoryId = ((CurseAddon.Category) category.self()).getId();
|
||||
}
|
||||
|
||||
var query = new LinkedHashMap<String, String>();
|
||||
@@ -143,7 +142,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
response = withApiKey(HttpRequest.GET(candidate.toString()))
|
||||
.getJson(Response.typeOf(listTypeOf(CurseAddon.class)));
|
||||
if (searchFilter.isEmpty()) {
|
||||
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
|
||||
return new SearchResult(response.getData().stream().map(addon -> addon.toMod(type)), calculateTotalPages(response, pageSize));
|
||||
}
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
@@ -172,7 +171,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
|
||||
StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();
|
||||
|
||||
return new SearchResult(response.getData().stream().map(CurseAddon::toMod).map(remoteMod -> {
|
||||
return new SearchResult(response.getData().stream().map(addon -> addon.toMod(type)).map(remoteMod -> {
|
||||
String lowerCaseResult = remoteMod.getTitle().toLowerCase(Locale.ROOT);
|
||||
int diff = levCalculator.calc(lowerCaseSearchFilter, lowerCaseResult);
|
||||
|
||||
@@ -183,14 +182,14 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
}
|
||||
|
||||
return pair(remoteMod, diff);
|
||||
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
|
||||
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(addon -> addon.toMod(type)), calculateTotalPages(response, pageSize));
|
||||
} finally {
|
||||
SEMAPHORE.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
|
||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(Path file) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (InputStream stream = Files.newInputStream(file)) {
|
||||
byte[] buf = new byte[1024];
|
||||
@@ -232,7 +231,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
try {
|
||||
Response<CurseAddon> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id))
|
||||
.getJson(Response.typeOf(CurseAddon.class));
|
||||
return response.data.toMod();
|
||||
return response.data.toMod(type);
|
||||
} finally {
|
||||
SEMAPHORE.release();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.mod.modinfo;
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import kala.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonFile;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
@@ -65,7 +66,7 @@ public final class FabricModMetadata {
|
||||
throw new IOException("File " + modFile + " is not a Fabric mod.");
|
||||
FabricModMetadata metadata = JsonUtils.fromNonNullJsonFully(tree.getInputStream(mcmod), FabricModMetadata.class);
|
||||
String authors = metadata.authors == null ? "" : metadata.authors.stream().map(author -> author.name).collect(Collectors.joining(", "));
|
||||
return new LocalModFile(modManager, modManager.getLocalMod(metadata.id, ModLoaderType.FABRIC), modFile, metadata.name, new LocalModFile.Description(metadata.description),
|
||||
return new LocalModFile(modManager, modManager.getLocalMod(metadata.id, ModLoaderType.FABRIC), modFile, metadata.name, new LocalAddonFile.Description(metadata.description),
|
||||
authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "", metadata.icon);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import kala.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonFile;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
@@ -234,7 +235,7 @@ public final class ForgeNewModMetadata {
|
||||
|
||||
ModLoaderType type = analyzeLoader(tomlParseResult, mod.getModId(), modLoaderType);
|
||||
|
||||
return new LocalModFile(modManager, modManager.getLocalMod(mod.getModId(), type), modFile, mod.getDisplayName(), new LocalModFile.Description(mod.getDescription()),
|
||||
return new LocalModFile(modManager, modManager.getLocalMod(mod.getModId(), type), modFile, mod.getDisplayName(), new LocalAddonFile.Description(mod.getDescription()),
|
||||
mod.getAuthors(), jarVersion == null ? mod.getVersion() : mod.getVersion().replace("${file.jarVersion}", jarVersion), "",
|
||||
mod.getDisplayURL(),
|
||||
metadata.getLogoFile());
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import kala.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonFile;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
@@ -156,7 +157,7 @@ public final class ForgeOldModMetadata {
|
||||
authors = String.join(", ", metadata.getAuthorList());
|
||||
if (StringUtils.isBlank(authors))
|
||||
authors = metadata.getCredits();
|
||||
return new LocalModFile(modManager, modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalModFile.Description(metadata.getDescription()),
|
||||
return new LocalModFile(modManager, modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()),
|
||||
authors, metadata.getVersion(), metadata.getGameVersion(),
|
||||
StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url,
|
||||
metadata.getLogoFile());
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.mod.modinfo;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import kala.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonFile;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
@@ -116,7 +117,7 @@ public final class LiteModMetadata {
|
||||
LiteModMetadata metadata = JsonUtils.fromJsonFully(tree.getInputStream(entry), LiteModMetadata.class);
|
||||
if (metadata == null)
|
||||
throw new IOException("Mod " + modFile + " `litemod.json` is malformed.");
|
||||
return new LocalModFile(modManager, modManager.getLocalMod(metadata.getName(), ModLoaderType.LITE_LOADER), modFile, metadata.getName(), new LocalModFile.Description(metadata.getDescription()), metadata.getAuthor(),
|
||||
return new LocalModFile(modManager, modManager.getLocalMod(metadata.getName(), ModLoaderType.LITE_LOADER), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()), metadata.getAuthor(),
|
||||
metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI(), "");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,20 +20,17 @@ package org.jackhuang.hmcl.mod.modinfo;
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import kala.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonFile;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.tree.ZipFileTree;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -42,6 +39,25 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@JsonSerializable
|
||||
public record PackMcMeta(@SerializedName("pack") PackInfo pack) implements Validation {
|
||||
|
||||
private static final Gson LENIENT_GSON = JsonUtils.defaultGsonBuilder().setStrictness(Strictness.LENIENT).create();
|
||||
|
||||
public static PackMcMeta fromNonNullJson(String jsonString) throws JsonParseException {
|
||||
PackMcMeta parsed = LENIENT_GSON.fromJson(jsonString, PackMcMeta.class);
|
||||
if (parsed == null)
|
||||
throw new JsonParseException("Json object cannot be null.");
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public static PackMcMeta fromNonNullJsonFile(Path jsonFile) throws JsonParseException, IOException {
|
||||
try (var reader = Files.newBufferedReader(jsonFile)) {
|
||||
PackMcMeta parsed = LENIENT_GSON.fromJson(reader, PackMcMeta.class);
|
||||
if (parsed == null)
|
||||
throw new JsonParseException("Json object cannot be null.");
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (pack == null)
|
||||
@@ -50,9 +66,10 @@ public record PackMcMeta(@SerializedName("pack") PackInfo pack) implements Valid
|
||||
|
||||
@JsonAdapter(PackInfoDeserializer.class)
|
||||
public record PackInfo(@SerializedName("pack_format") int packFormat,
|
||||
@SerializedName("supported_formats") SupportedFormats supportedFormats,
|
||||
@SerializedName("min_format") PackVersion minPackVersion,
|
||||
@SerializedName("max_format") PackVersion maxPackVersion,
|
||||
@SerializedName("description") LocalModFile.Description description) {
|
||||
@SerializedName("description") LocalAddonFile.Description description) {
|
||||
public PackVersion getEffectiveMinVersion() {
|
||||
return !minPackVersion.isUnspecified() ? minPackVersion : new PackVersion(packFormat, 0);
|
||||
}
|
||||
@@ -62,17 +79,67 @@ public record PackMcMeta(@SerializedName("pack") PackInfo pack) implements Valid
|
||||
}
|
||||
}
|
||||
|
||||
public record SupportedFormats(int min, int max) {
|
||||
|
||||
public static final SupportedFormats UNSPECIFIED = new SupportedFormats(-1, -1);
|
||||
|
||||
public static SupportedFormats fromJson(JsonElement element) {
|
||||
if (element == null || element.isJsonNull()) {
|
||||
return UNSPECIFIED;
|
||||
}
|
||||
|
||||
try {
|
||||
if (element instanceof JsonArray jsonArray) {
|
||||
if (jsonArray.size() == 2 && jsonArray.get(0).isJsonPrimitive() && jsonArray.get(1).isJsonPrimitive()) {
|
||||
return new SupportedFormats(jsonArray.get(0).getAsInt(), jsonArray.get(1).getAsInt());
|
||||
} else {
|
||||
LOG.warning("Supported formats array must have 2 elements, but got " + jsonArray.size());
|
||||
}
|
||||
} else if (element instanceof JsonObject jsonObj) {
|
||||
if (jsonObj.get("min_inclusive").isJsonPrimitive() && jsonObj.get("max_inclusive").isJsonPrimitive()) {
|
||||
return new SupportedFormats(jsonObj.get("min_inclusive").getAsInt(), jsonObj.get("max_inclusive").getAsInt());
|
||||
} else {
|
||||
LOG.warning("Supported formats object must have min_inclusive and max_inclusive properties, but got: " + jsonObj);
|
||||
}
|
||||
}
|
||||
} catch (ClassCastException | NumberFormatException e) {
|
||||
LOG.warning("Failed to parse pack version component as a number. Value: " + element, e);
|
||||
}
|
||||
|
||||
return UNSPECIFIED;
|
||||
}
|
||||
|
||||
public boolean isUnspecified() {
|
||||
return getMin().isUnspecified() || getMax().isUnspecified() || getMin().compareTo(getMax()) > 0;
|
||||
}
|
||||
|
||||
public PackVersion getMin() {
|
||||
return new PackVersion(min, 0);
|
||||
}
|
||||
|
||||
public PackVersion getMax() {
|
||||
return new PackVersion(max, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public record PackVersion(int majorVersion, int minorVersion) implements Comparable<PackVersion> {
|
||||
|
||||
public static final PackVersion UNSPECIFIED = new PackVersion(0, 0);
|
||||
public static final PackVersion UNSPECIFIED = new PackVersion(-1, -1);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
public @NotNull String toString() {
|
||||
if (isUnspecified()) return "UNSPECIFIED";
|
||||
return minorVersion != 0 ? majorVersion + "." + minorVersion : String.valueOf(majorVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PackVersion other) {
|
||||
boolean thisUnspecified = this.isUnspecified();
|
||||
boolean otherUnspecified = other.isUnspecified();
|
||||
if (thisUnspecified && otherUnspecified) return 0;
|
||||
if (thisUnspecified) return -1;
|
||||
if (otherUnspecified) return 1;
|
||||
int majorCompare = Integer.compare(this.majorVersion, other.majorVersion);
|
||||
if (majorCompare != 0) {
|
||||
return majorCompare;
|
||||
@@ -81,28 +148,28 @@ public record PackMcMeta(@SerializedName("pack") PackInfo pack) implements Valid
|
||||
}
|
||||
|
||||
public boolean isUnspecified() {
|
||||
return this.equals(UNSPECIFIED);
|
||||
return this.majorVersion < 0 || this.minorVersion < 0;
|
||||
}
|
||||
|
||||
public static PackVersion fromJson(JsonElement element) throws JsonParseException {
|
||||
public static PackVersion fromJson(JsonElement element, boolean anyMinor) throws JsonParseException {
|
||||
if (element == null || element.isJsonNull()) {
|
||||
return UNSPECIFIED;
|
||||
}
|
||||
|
||||
try {
|
||||
if (element instanceof JsonPrimitive primitive && primitive.isNumber()) {
|
||||
return new PackVersion(element.getAsInt(), 0);
|
||||
return new PackVersion(element.getAsInt(), anyMinor ? Integer.MAX_VALUE : 0);
|
||||
} else if (element instanceof JsonArray jsonArray) {
|
||||
if (jsonArray.size() == 1 && jsonArray.get(0) instanceof JsonPrimitive) {
|
||||
return new PackVersion(jsonArray.get(0).getAsInt(), 0);
|
||||
return new PackVersion(jsonArray.get(0).getAsInt(), anyMinor ? Integer.MAX_VALUE : 0);
|
||||
} else if (jsonArray.size() == 2 && jsonArray.get(0) instanceof JsonPrimitive && jsonArray.get(1) instanceof JsonPrimitive) {
|
||||
return new PackVersion(jsonArray.get(0).getAsInt(), jsonArray.get(1).getAsInt());
|
||||
} else {
|
||||
LOG.warning("Datapack version array must have 1 or 2 elements, but got " + jsonArray.size());
|
||||
LOG.warning("Pack version array must have 1 or 2 elements, but got " + jsonArray.size());
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.warning("Failed to parse datapack version component as a number. Value: " + element, e);
|
||||
LOG.warning("Failed to parse pack version component as a number. Value: " + element, e);
|
||||
}
|
||||
|
||||
return UNSPECIFIED;
|
||||
@@ -111,15 +178,15 @@ public record PackMcMeta(@SerializedName("pack") PackInfo pack) implements Valid
|
||||
|
||||
public static final class PackInfoDeserializer implements JsonDeserializer<PackInfo> {
|
||||
|
||||
private List<LocalModFile.Description.Part> pairToPart(List<Pair<String, String>> lists, String color) {
|
||||
List<LocalModFile.Description.Part> parts = new ArrayList<>();
|
||||
private List<LocalAddonFile.Description.Part> pairToPart(List<Pair<String, String>> lists, String color) {
|
||||
List<LocalAddonFile.Description.Part> parts = new ArrayList<>();
|
||||
for (Pair<String, String> list : lists) {
|
||||
parts.add(new LocalModFile.Description.Part(list.getKey(), list.getValue().isEmpty() ? color : list.getValue()));
|
||||
parts.add(new LocalAddonFile.Description.Part(list.getKey(), list.getValue().isEmpty() ? color : list.getValue()));
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
private void parseComponent(JsonElement element, List<LocalModFile.Description.Part> parts, String parentColor) throws JsonParseException {
|
||||
private void parseComponent(JsonElement element, List<LocalAddonFile.Description.Part> parts, String parentColor) throws JsonParseException {
|
||||
if (parentColor == null) {
|
||||
parentColor = "";
|
||||
}
|
||||
@@ -149,8 +216,8 @@ public record PackMcMeta(@SerializedName("pack") PackInfo pack) implements Valid
|
||||
}
|
||||
}
|
||||
|
||||
private List<LocalModFile.Description.Part> parseDescription(JsonElement json) throws JsonParseException {
|
||||
List<LocalModFile.Description.Part> parts = new ArrayList<>();
|
||||
private List<LocalAddonFile.Description.Part> parseDescription(JsonElement json) throws JsonParseException {
|
||||
List<LocalAddonFile.Description.Part> parts = new ArrayList<>();
|
||||
|
||||
if (json == null || json.isJsonNull()) {
|
||||
return parts;
|
||||
@@ -175,25 +242,13 @@ public record PackMcMeta(@SerializedName("pack") PackInfo pack) implements Valid
|
||||
} else {
|
||||
packFormat = 0;
|
||||
}
|
||||
PackVersion minVersion = PackVersion.fromJson(packInfo.get("min_format"));
|
||||
PackVersion maxVersion = PackVersion.fromJson(packInfo.get("max_format"));
|
||||
SupportedFormats supportedFormats = SupportedFormats.fromJson(packInfo.get("supported_formats"));
|
||||
PackVersion minVersion = PackVersion.fromJson(packInfo.get("min_format"), false);
|
||||
PackVersion maxVersion = PackVersion.fromJson(packInfo.get("max_format"), true);
|
||||
|
||||
List<LocalModFile.Description.Part> parts = parseDescription(packInfo.get("description"));
|
||||
return new PackInfo(packFormat, minVersion, maxVersion, new LocalModFile.Description(parts));
|
||||
List<LocalAddonFile.Description.Part> parts = parseDescription(packInfo.get("description"));
|
||||
return new PackInfo(packFormat, supportedFormats, minVersion, maxVersion, new LocalAddonFile.Description(parts));
|
||||
}
|
||||
}
|
||||
|
||||
public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {
|
||||
ZipArchiveEntry mcmod = tree.getEntry("pack.mcmeta");
|
||||
if (mcmod == null)
|
||||
throw new IOException("File " + modFile + " is not a resource pack.");
|
||||
PackMcMeta metadata = JsonUtils.fromNonNullJsonFully(tree.getInputStream(mcmod), PackMcMeta.class);
|
||||
return new LocalModFile(
|
||||
modManager,
|
||||
modManager.getLocalMod(FileUtils.getNameWithoutExtension(modFile), ModLoaderType.PACK),
|
||||
modFile,
|
||||
FileUtils.getNameWithoutExtension(modFile),
|
||||
metadata.pack.description,
|
||||
"", "", "", "", "");
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.mod.modinfo;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import kala.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonFile;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
@@ -86,7 +87,7 @@ public final class QuiltModMetadata {
|
||||
modManager.getLocalMod(root.quilt_loader.id, ModLoaderType.QUILT),
|
||||
modFile,
|
||||
root.quilt_loader.metadata.name,
|
||||
new LocalModFile.Description(root.quilt_loader.metadata.description),
|
||||
new LocalAddonFile.Description(root.quilt_loader.metadata.description),
|
||||
root.quilt_loader.metadata.contributors.entrySet().stream().map(entry -> String.format("%s (%s)", entry.getKey(), entry.getValue().getAsJsonPrimitive().getAsString())).collect(Collectors.joining(", ")),
|
||||
root.quilt_loader.version,
|
||||
"",
|
||||
|
||||
+2
-2
@@ -78,14 +78,14 @@ public class ModrinthModpackExportTask extends Task<Void> {
|
||||
Optional<RemoteMod.Version> curseForgeVersion = Optional.empty();
|
||||
|
||||
try {
|
||||
modrinthVersion = ModrinthRemoteModRepository.MODS.getRemoteVersionByLocalFile(localModFile, file);
|
||||
modrinthVersion = ModrinthRemoteModRepository.MODS.getRemoteVersionByLocalFile(file);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to get remote file from Modrinth for: " + file, e);
|
||||
}
|
||||
|
||||
if (!info.isSkipCurseForgeRemoteFiles() && CurseForgeRemoteModRepository.isAvailable()) {
|
||||
try {
|
||||
curseForgeVersion = CurseForgeRemoteModRepository.MODS.getRemoteVersionByLocalFile(localModFile, file);
|
||||
curseForgeVersion = CurseForgeRemoteModRepository.MODS.getRemoteVersionByLocalFile(file);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to get remote file from CurseForge for: " + file, e);
|
||||
}
|
||||
|
||||
+29
-15
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.mod.modrinth;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
@@ -37,14 +36,7 @@ import java.net.URI;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -101,13 +93,21 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
private final String projectType;
|
||||
|
||||
private final RemoteModRepository.Type type;
|
||||
|
||||
private ModrinthRemoteModRepository(String projectType) {
|
||||
this.projectType = projectType;
|
||||
this.type = switch (projectType) {
|
||||
case "modpack" -> Type.MODPACK;
|
||||
case "resourcepack" -> Type.RESOURCE_PACK;
|
||||
case "shader" -> Type.SHADER_PACK;
|
||||
default -> Type.MOD;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.MOD;
|
||||
return this.type;
|
||||
}
|
||||
|
||||
private static String convertSortType(SortType sortType) {
|
||||
@@ -142,8 +142,8 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
if (StringUtils.isNotBlank(gameVersion)) {
|
||||
facets.add(Collections.singletonList("versions:" + gameVersion));
|
||||
}
|
||||
if (category != null && StringUtils.isNotBlank(category.getId())) {
|
||||
facets.add(Collections.singletonList("categories:" + category.getId()));
|
||||
if (category != null && StringUtils.isNotBlank(category.id())) {
|
||||
facets.add(Collections.singletonList("categories:" + category.id()));
|
||||
}
|
||||
Map<String, String> query = mapOf(
|
||||
pair("query", searchFilter),
|
||||
@@ -183,7 +183,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
|
||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(Path file) throws IOException {
|
||||
String sha1 = DigestUtils.digestToString("SHA-1", file);
|
||||
|
||||
SEMAPHORE.acquireUninterruptibly();
|
||||
@@ -457,6 +457,12 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
}
|
||||
|
||||
public RemoteMod toMod() {
|
||||
RemoteModRepository.Type type = switch (projectType) {
|
||||
case "modpack" -> RemoteModRepository.Type.MODPACK;
|
||||
case "resourcepack" -> RemoteModRepository.Type.RESOURCE_PACK;
|
||||
case "shader" -> RemoteModRepository.Type.SHADER_PACK;
|
||||
default -> RemoteModRepository.Type.MOD;
|
||||
};
|
||||
return new RemoteMod(
|
||||
slug,
|
||||
"",
|
||||
@@ -465,7 +471,8 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
categories,
|
||||
String.format("https://modrinth.com/%s/%s", projectType, id),
|
||||
iconUrl,
|
||||
this
|
||||
this,
|
||||
type
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -844,6 +851,12 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
}
|
||||
|
||||
public RemoteMod toMod() {
|
||||
RemoteModRepository.Type type = switch (projectType) {
|
||||
case "modpack" -> RemoteModRepository.Type.MODPACK;
|
||||
case "resourcepack" -> RemoteModRepository.Type.RESOURCE_PACK;
|
||||
case "shader" -> RemoteModRepository.Type.SHADER_PACK;
|
||||
default -> RemoteModRepository.Type.MOD;
|
||||
};
|
||||
return new RemoteMod(
|
||||
slug,
|
||||
author,
|
||||
@@ -852,7 +865,8 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
sortDisplayCategories(displayCategories),
|
||||
String.format("https://modrinth.com/%s/%s", projectType, projectId),
|
||||
iconUrl,
|
||||
this
|
||||
this,
|
||||
type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -21,7 +21,7 @@ import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonManager;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
@@ -144,8 +144,8 @@ public class ServerModpackCompletionTask extends Task<Void> {
|
||||
boolean download;
|
||||
|
||||
boolean isModDisabled = modsDirectory.equals(actualPath.getParent()) &&
|
||||
(Files.exists(actualPath.resolveSibling(fileName + ModManager.DISABLED_EXTENSION)) ||
|
||||
Files.exists(actualPath.resolveSibling(fileName + ModManager.OLD_EXTENSION)));
|
||||
(Files.exists(actualPath.resolveSibling(fileName + LocalAddonManager.DISABLED_EXTENSION)) ||
|
||||
Files.exists(actualPath.resolveSibling(fileName + LocalAddonManager.OLD_EXTENSION)));
|
||||
|
||||
if (isModDisabled) {
|
||||
download = false;
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonFile;
|
||||
import org.jackhuang.hmcl.mod.modinfo.PackMcMeta;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public sealed abstract class ResourcePackFile extends LocalAddonFile implements Comparable<ResourcePackFile> permits ResourcePackFolder, ResourcePackZipFile {
|
||||
static ResourcePackFile fromFile(ResourcePackManager manager, Path path) throws IOException {
|
||||
if (isFileResourcePack(path)) {
|
||||
return Files.isRegularFile(path) ? new ResourcePackZipFile(manager, path) : new ResourcePackFolder(manager, path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isFileResourcePack(Path file) {
|
||||
if (Files.isDirectory(file)) return Files.isRegularFile(file.resolve("pack.mcmeta"));
|
||||
if (Files.isRegularFile(file)) return file.toString().toLowerCase(Locale.ROOT).endsWith(".zip");
|
||||
return false;
|
||||
}
|
||||
|
||||
protected final ResourcePackManager manager;
|
||||
protected Path file;
|
||||
protected final String fileName;
|
||||
protected final String fileNameWithExtension;
|
||||
|
||||
private Compatibility compatibility = null;
|
||||
|
||||
protected ResourcePackFile(ResourcePackManager manager, Path file) {
|
||||
super();
|
||||
this.manager = manager;
|
||||
this.file = file;
|
||||
this.fileName = StringUtils.parseColorEscapes(FileUtils.getNameWithoutExtension(file));
|
||||
this.fileNameWithExtension = file.getFileName().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getFileNameWithExtension() {
|
||||
return fileNameWithExtension;
|
||||
}
|
||||
|
||||
public Compatibility getCompatibility() {
|
||||
if (compatibility == null) {
|
||||
compatibility = manager.getCompatibility(this);
|
||||
}
|
||||
return compatibility;
|
||||
}
|
||||
|
||||
public boolean isCompatible() {
|
||||
return getCompatibility() == Compatibility.COMPATIBLE;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return manager.isEnabled(this);
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (enabled) {
|
||||
manager.enableResourcePacks(List.of(this));
|
||||
} else {
|
||||
manager.disableResourcePacks(List.of(this));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOld(boolean old) throws IOException {
|
||||
this.file = manager.setOld(this, old);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean keepOldFiles() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markDisabled() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Contract(pure = true)
|
||||
public abstract PackMcMeta getMeta();
|
||||
|
||||
@Nullable
|
||||
public LocalAddonFile.Description getDescription() {
|
||||
if (getMeta() == null || getMeta().pack() == null) return null;
|
||||
return getMeta().pack().description();
|
||||
}
|
||||
|
||||
// 64*64
|
||||
public abstract @Nullable Image getIcon();
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ResourcePackFile other) {
|
||||
return this.fileNameWithExtension.compareTo(other.fileNameWithExtension);
|
||||
}
|
||||
|
||||
public enum Compatibility {
|
||||
COMPATIBLE,
|
||||
TOO_NEW,
|
||||
TOO_OLD,
|
||||
INVALID,
|
||||
MISSING_PACK_META,
|
||||
MISSING_GAME_META
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.modinfo.PackMcMeta;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
final class ResourcePackFolder extends ResourcePackFile {
|
||||
private final PackMcMeta meta;
|
||||
private final @Nullable Image icon;
|
||||
|
||||
public ResourcePackFolder(ResourcePackManager manager, Path path) {
|
||||
super(manager, path);
|
||||
|
||||
PackMcMeta meta = null;
|
||||
try {
|
||||
meta = PackMcMeta.fromNonNullJsonFile(path.resolve("pack.mcmeta"));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to parse resource pack meta", e);
|
||||
}
|
||||
this.meta = meta;
|
||||
|
||||
byte[] iconData = null;
|
||||
Image iconTemp = null;
|
||||
try {
|
||||
iconData = Files.readAllBytes(path.resolve("pack.png"));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read resource pack icon", e);
|
||||
}
|
||||
if (iconData != null) {
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) {
|
||||
iconTemp = new Image(inputStream, 64, 64, true, true);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resource pack icon", e);
|
||||
}
|
||||
}
|
||||
this.icon = iconTemp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackMcMeta getMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Image getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
FileUtils.deleteDirectory(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteMod.Type type) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.mod.LocalAddonManager;
|
||||
import org.jackhuang.hmcl.mod.modinfo.PackMcMeta;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.tree.ZipFileTree;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionRange;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ResourcePackManager extends LocalAddonManager<ResourcePackFile> {
|
||||
|
||||
private static final List<String> RESOURCE_PACK_VERSION_OLD = List.of(
|
||||
"13w24a", // 1
|
||||
"15w31a", // 2
|
||||
"16w32a", // 3
|
||||
"17w48a", // 4
|
||||
"18w47b" // 5
|
||||
);
|
||||
|
||||
public static final String LEAST_MC_VERSION_RELEASE = "1.6.1";
|
||||
public static final String LEAST_MC_VERSION_SNAPSHOT = "13w24a";
|
||||
public static final String LEAST_MC_VERSION_NEW_OPTIONS_RELEASE = "1.13";
|
||||
public static final String LEAST_MC_VERSION_NEW_OPTIONS_SNAPSHOT = "17w43a";
|
||||
|
||||
public static boolean isMcVersionSupported(@NotNull GameVersionNumber version) {
|
||||
return version.isAtLeast(LEAST_MC_VERSION_RELEASE, LEAST_MC_VERSION_SNAPSHOT);
|
||||
}
|
||||
|
||||
public static boolean isMcVersionSupportsNewOptionsFormat(@NotNull GameVersionNumber version) {
|
||||
return version.isAtLeast(LEAST_MC_VERSION_NEW_OPTIONS_RELEASE, LEAST_MC_VERSION_NEW_OPTIONS_SNAPSHOT);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PackMcMeta.PackVersion getPackVersion(GameVersionNumber minecraftVersion, Path gameJar) {
|
||||
for (int i = 0; i < RESOURCE_PACK_VERSION_OLD.size(); i++) {
|
||||
if (minecraftVersion.compareTo(RESOURCE_PACK_VERSION_OLD.get(i)) < 0) {
|
||||
return i == 0 ? PackMcMeta.PackVersion.UNSPECIFIED : new PackMcMeta.PackVersion(i, 0);
|
||||
}
|
||||
}
|
||||
try (var zipFileTree = new ZipFileTree(CompressingUtils.openZipFile(gameJar))) {
|
||||
String versionJson = zipFileTree.readTextEntry("/version.json");
|
||||
try {
|
||||
var info = JsonUtils.fromNonNullJson(versionJson, GameVersionInfo117.class).packVersionInfo();
|
||||
if (info.resourceMajor() > 64) {
|
||||
return new PackMcMeta.PackVersion(info.resourceMajor(), info.resourceMinor());
|
||||
} else {
|
||||
return new PackMcMeta.PackVersion(info.resource(), 0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load Minecraft resource pack version for 25w31a+", e);
|
||||
}
|
||||
try {
|
||||
return new PackMcMeta.PackVersion(JsonUtils.fromNonNullJson(versionJson, GameVersionInfo114.class).packVersion, 0);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load Minecraft resource pack version for 18w47b+", e);
|
||||
}
|
||||
return PackMcMeta.PackVersion.UNSPECIFIED;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to load Minecraft resource pack version", e);
|
||||
return PackMcMeta.PackVersion.UNSPECIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Contract(pure = true)
|
||||
public static VersionRange<PackMcMeta.PackVersion> getResourcePackVersionRangeOldest(PackMcMeta.PackInfo packInfo) {
|
||||
if (packInfo == null || packInfo.packFormat() <= 0)
|
||||
return VersionRange.empty();
|
||||
else
|
||||
return VersionRange.is(new PackMcMeta.PackVersion(packInfo.packFormat(), 0));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Contract(pure = true)
|
||||
public static VersionRange<PackMcMeta.PackVersion> getResourcePackVersionRangeOld(PackMcMeta.PackInfo packInfo) {
|
||||
if (packInfo == null)
|
||||
return VersionRange.empty();
|
||||
if (packInfo.packFormat() <= 0)
|
||||
return VersionRange.empty();
|
||||
if (packInfo.supportedFormats().isUnspecified()) {
|
||||
return VersionRange.is(new PackMcMeta.PackVersion(packInfo.packFormat(), 0));
|
||||
}
|
||||
return VersionRange.between(packInfo.supportedFormats().getMin(), packInfo.supportedFormats().getMax());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Contract(pure = true)
|
||||
public static VersionRange<PackMcMeta.PackVersion> getResourcePackVersionRangeNew(PackMcMeta.PackInfo packInfo) {
|
||||
if (packInfo == null)
|
||||
return VersionRange.empty();
|
||||
|
||||
boolean packFormatUnspecified = packInfo.packFormat() <= 0;
|
||||
boolean supportedFormatsUnspecified = packInfo.supportedFormats().isUnspecified();
|
||||
|
||||
// See https://zh.minecraft.wiki/w/Pack.mcmeta
|
||||
// Also referring to Minecraft's source code
|
||||
if (packInfo.minPackVersion().isUnspecified() || packInfo.maxPackVersion().isUnspecified()) { // Old format
|
||||
if (!supportedFormatsUnspecified) {
|
||||
PackMcMeta.SupportedFormats supportedFormats = packInfo.supportedFormats();
|
||||
int min = supportedFormats.min();
|
||||
int max = supportedFormats.max();
|
||||
if (max > 64) {
|
||||
return VersionRange.empty();
|
||||
} else {
|
||||
if (packFormatUnspecified)
|
||||
return VersionRange.empty();
|
||||
if (isPackFormatInvalid(min, max, packInfo.packFormat()))
|
||||
return VersionRange.empty();
|
||||
}
|
||||
|
||||
return VersionRange.between(supportedFormats.getMin(), supportedFormats.getMax());
|
||||
} else if (!packFormatUnspecified) {
|
||||
int packFormat = packInfo.packFormat();
|
||||
PackMcMeta.PackVersion packVersion = new PackMcMeta.PackVersion(packFormat, 0);
|
||||
return packFormat > 64 ? VersionRange.empty() : VersionRange.is(packVersion);
|
||||
}
|
||||
} else { // New format
|
||||
int minMajor = packInfo.minPackVersion().majorVersion();
|
||||
int maxMajor = packInfo.maxPackVersion().majorVersion();
|
||||
if (packInfo.minPackVersion().compareTo(packInfo.maxPackVersion()) > 0) {
|
||||
return VersionRange.empty();
|
||||
}
|
||||
if (minMajor > 64) {
|
||||
if (!supportedFormatsUnspecified)
|
||||
return VersionRange.empty();
|
||||
|
||||
if (!packFormatUnspecified && isPackFormatInvalid(minMajor, maxMajor, packInfo.packFormat()))
|
||||
return VersionRange.empty();
|
||||
} else {
|
||||
if (supportedFormatsUnspecified)
|
||||
return VersionRange.empty();
|
||||
PackMcMeta.SupportedFormats supportedFormats = packInfo.supportedFormats();
|
||||
if (supportedFormats.min() != minMajor)
|
||||
return VersionRange.empty();
|
||||
if (supportedFormats.max() != maxMajor && supportedFormats.max() != 64)
|
||||
return VersionRange.empty();
|
||||
if (packFormatUnspecified)
|
||||
return VersionRange.empty();
|
||||
if (isPackFormatInvalid(minMajor, maxMajor, packInfo.packFormat()))
|
||||
return VersionRange.empty();
|
||||
}
|
||||
|
||||
return VersionRange.between(packInfo.minPackVersion(), packInfo.maxPackVersion());
|
||||
}
|
||||
return VersionRange.empty();
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
private static boolean isPackFormatInvalid(int i, int j, int k) {
|
||||
if (k >= i && k <= j) {
|
||||
return k < 15;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static String serializePackList(List<String> packNames) {
|
||||
try {
|
||||
return StringUtils.serializeStringList(packNames);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to serialize resource pack list: \n" + packNames, e);
|
||||
return "[]";
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> deserializePackList(String json) {
|
||||
try {
|
||||
return StringUtils.deserializeStringList(json);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to deserialize resource pack list: \n" + json, e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final Path resourcePackDirectory;
|
||||
private final Path optionsFile;
|
||||
|
||||
private @Nullable GameVersionNumber minecraftVersion;
|
||||
private @Nullable PackMcMeta.PackVersion requiredVersion;
|
||||
private boolean supportsNewOptionsFormat;
|
||||
|
||||
private boolean loaded = false;
|
||||
|
||||
public ResourcePackManager(GameRepository repository, String id) {
|
||||
super(repository, id);
|
||||
this.resourcePackDirectory = this.repository.getResourcePackDirectory(this.id);
|
||||
this.optionsFile = repository.getRunDirectory(id).resolve("options.txt");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<String, String> loadOptions() {
|
||||
getMinecraftVersion();
|
||||
Map<String, String> options = new LinkedHashMap<>();
|
||||
if (!Files.isRegularFile(optionsFile)) return options;
|
||||
try (var stream = Files.lines(optionsFile, StandardCharsets.UTF_8)) {
|
||||
stream.forEach(s -> {
|
||||
if (StringUtils.isNotBlank(s)) {
|
||||
var entry = s.split(":", 2);
|
||||
if (entry.length == 2) {
|
||||
options.put(entry[0], entry[1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read instance options file", e);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private void saveOptions(@NotNull Map<String, String> options) {
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (var entry : options.entrySet()) {
|
||||
sb.append(entry.getKey()).append(":").append(entry.getValue()).append(System.lineSeparator());
|
||||
}
|
||||
FileUtils.saveSafely(optionsFile, sb.toString());
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to save instance options file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GameVersionNumber getMinecraftVersion() {
|
||||
if (minecraftVersion == null) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (minecraftVersion == null) {
|
||||
minecraftVersion = GameVersionNumber.asGameVersion(repository.getGameVersion(id));
|
||||
supportsNewOptionsFormat = isMcVersionSupportsNewOptionsFormat(minecraftVersion);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
return minecraftVersion;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PackMcMeta.PackVersion getRequiredVersion() {
|
||||
if (requiredVersion == null) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (requiredVersion == null) requiredVersion = getPackVersion(getMinecraftVersion(), repository.getVersionJar(id));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
return requiredVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getDirectory() {
|
||||
return resourcePackDirectory;
|
||||
}
|
||||
|
||||
private void addResourcePackInfo(Path file) throws IOException {
|
||||
ResourcePackFile resourcePack = ResourcePackFile.fromFile(this, file);
|
||||
if (resourcePack != null) localFiles.add(resourcePack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
localFiles.clear();
|
||||
|
||||
if (Files.isDirectory(resourcePackDirectory)) {
|
||||
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(resourcePackDirectory)) {
|
||||
for (Path subitem : directoryStream) {
|
||||
try {
|
||||
addResourcePackInfo(subitem);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to load resource pack " + subitem, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loaded = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<ResourcePackFile> getComparator() {
|
||||
return ResourcePackFile::compareTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Unmodifiable List<ResourcePackFile> getLocalFiles() throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
if (!loaded)
|
||||
refresh();
|
||||
return super.getLocalFiles();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void importResourcePack(Path file) throws IOException, IllegalArgumentException {
|
||||
lock.lock();
|
||||
try {
|
||||
if (ResourcePackFile.isFileResourcePack(file)) {
|
||||
if (!loaded)
|
||||
refresh();
|
||||
Files.createDirectories(resourcePackDirectory);
|
||||
|
||||
Path newFile = resourcePackDirectory.resolve(file.getFileName());
|
||||
if (Files.isDirectory(file)) {
|
||||
FileUtils.copyDirectory(file, newFile);
|
||||
} else {
|
||||
FileUtils.copyFile(file, newFile);
|
||||
}
|
||||
|
||||
addResourcePackInfo(newFile);
|
||||
} else {
|
||||
throw new IllegalArgumentException("File '" + file + "' is not a resource pack");
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeResourcePacks(List<ResourcePackFile> resourcePacks) throws IOException {
|
||||
lock.lock();
|
||||
try {
|
||||
boolean modified = disableResourcePacks(resourcePacks);
|
||||
for (ResourcePackFile resourcePack : resourcePacks) {
|
||||
if (resourcePack != null && resourcePack.manager == this) {
|
||||
resourcePack.delete();
|
||||
localFiles.remove(resourcePack);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsResourcePack(List<String> resourcePacks, String packIdOld) {
|
||||
String packId = "file/" + packIdOld;
|
||||
if (supportsNewOptionsFormat) return resourcePacks.contains(packIdOld) || resourcePacks.contains(packId);
|
||||
return resourcePacks.contains(packIdOld);
|
||||
}
|
||||
|
||||
public boolean enableResourcePacks(List<ResourcePackFile> resourcePackFiles) {
|
||||
lock.lock();
|
||||
try {
|
||||
boolean modified = false;
|
||||
Map<String, String> options = loadOptions();
|
||||
List<String> resourcePacks = new ArrayList<>(deserializePackList(options.get("resourcePacks")));
|
||||
List<String> incompatibleResourcePacks = new ArrayList<>(deserializePackList(options.get("incompatibleResourcePacks")));
|
||||
for (var pack : resourcePackFiles) {
|
||||
if (enableResourcePack(pack, resourcePacks, incompatibleResourcePacks)) modified = true;
|
||||
}
|
||||
if (modified) {
|
||||
options.put("resourcePacks", serializePackList(resourcePacks));
|
||||
options.put("incompatibleResourcePacks", serializePackList(incompatibleResourcePacks));
|
||||
saveOptions(options);
|
||||
}
|
||||
return modified;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean enableResourcePack(ResourcePackFile resourcePack, List<String> resourcePacks, List<String> incompatibleResourcePacks) {
|
||||
if (resourcePack.manager != this) return false;
|
||||
String packId = "file/" + resourcePack.getFileNameWithExtension();
|
||||
String packIdOld = resourcePack.getFileNameWithExtension();
|
||||
boolean modified = false;
|
||||
if (!containsResourcePack(resourcePacks, packIdOld)) {
|
||||
resourcePacks.add(supportsNewOptionsFormat ? packId : packIdOld);
|
||||
modified = true;
|
||||
}
|
||||
boolean incompatibleContains = containsResourcePack(incompatibleResourcePacks, packIdOld);
|
||||
boolean incompatible = isIncompatible(resourcePack);
|
||||
if (!incompatibleContains && incompatible) {
|
||||
incompatibleResourcePacks.add(supportsNewOptionsFormat ? packId : packIdOld);
|
||||
modified = true;
|
||||
} else if (incompatibleContains && !incompatible) {
|
||||
incompatibleResourcePacks.removeAll(List.of(packId));
|
||||
incompatibleResourcePacks.removeAll(List.of(packIdOld));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
public boolean disableResourcePacks(List<ResourcePackFile> resourcePackFiles) {
|
||||
lock.lock();
|
||||
try {
|
||||
boolean modified = false;
|
||||
Map<String, String> options = loadOptions();
|
||||
List<String> resourcePacks = new ArrayList<>(deserializePackList(options.get("resourcePacks")));
|
||||
List<String> incompatibleResourcePacks = new ArrayList<>(deserializePackList(options.get("incompatibleResourcePacks")));
|
||||
for (var pack : resourcePackFiles) {
|
||||
if (disableResourcePack(pack, resourcePacks, incompatibleResourcePacks)) modified = true;
|
||||
}
|
||||
if (modified) {
|
||||
options.put("resourcePacks", serializePackList(resourcePacks));
|
||||
options.put("incompatibleResourcePacks", serializePackList(incompatibleResourcePacks));
|
||||
saveOptions(options);
|
||||
}
|
||||
return modified;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean disableResourcePack(ResourcePackFile resourcePack, List<String> resourcePacks, List<String> incompatibleResourcePacks) {
|
||||
if (resourcePack.manager != this) return false;
|
||||
String packId = "file/" + resourcePack.getFileNameWithExtension();
|
||||
String packIdOld = resourcePack.getFileNameWithExtension();
|
||||
boolean modified = false;
|
||||
if (resourcePacks.contains(packId)) {
|
||||
resourcePacks.removeAll(List.of(packId));
|
||||
modified = true;
|
||||
}
|
||||
if (resourcePacks.contains(packIdOld)) {
|
||||
resourcePacks.removeAll(List.of(packIdOld));
|
||||
modified = true;
|
||||
}
|
||||
if (incompatibleResourcePacks.contains(packId)) {
|
||||
incompatibleResourcePacks.removeAll(List.of(packId));
|
||||
modified = true;
|
||||
}
|
||||
if (incompatibleResourcePacks.contains(packIdOld)) {
|
||||
incompatibleResourcePacks.removeAll(List.of(packIdOld));
|
||||
modified = true;
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
private boolean isEnabled(ResourcePackFile pack, List<String> optPacks, List<String> optIncompatiblePacks) {
|
||||
if (pack.manager != this) return false;
|
||||
String packIdOld = pack.getFileNameWithExtension();
|
||||
if (!containsResourcePack(optPacks, packIdOld)) return false;
|
||||
return isIncompatible(pack) == containsResourcePack(optIncompatiblePacks, packIdOld);
|
||||
}
|
||||
|
||||
public boolean isEnabled(ResourcePackFile resourcePack) {
|
||||
lock.lock();
|
||||
try {
|
||||
Map<String, String> options = loadOptions();
|
||||
List<String> optPacks = deserializePackList(options.get("resourcePacks"));
|
||||
List<String> optIncompatiblePacks = deserializePackList(options.get("incompatibleResourcePacks"));
|
||||
return isEnabled(resourcePack, optPacks, optIncompatiblePacks);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Stream<Pair<ResourcePackFile, Boolean>> arePacksEnabled(Stream<ResourcePackFile> resourcePacks) {
|
||||
lock.lock();
|
||||
try {
|
||||
Map<String, String> options = loadOptions();
|
||||
List<String> optPacks = deserializePackList(options.get("resourcePacks"));
|
||||
List<String> optIncompatiblePacks = deserializePackList(options.get("incompatibleResourcePacks"));
|
||||
return resourcePacks.map(pack -> Pair.pair(pack, isEnabled(pack, optPacks, optIncompatiblePacks)));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public ResourcePackFile.Compatibility getCompatibility(@NotNull ResourcePackFile resourcePack) {
|
||||
if (resourcePack.getMeta() == null || resourcePack.getMeta().pack() == null)
|
||||
return ResourcePackFile.Compatibility.MISSING_PACK_META;
|
||||
if (this.getRequiredVersion().isUnspecified())
|
||||
return ResourcePackFile.Compatibility.MISSING_GAME_META;
|
||||
int requiredMajor = getRequiredVersion().majorVersion();
|
||||
VersionRange<PackMcMeta.PackVersion> versionRange;
|
||||
if (requiredMajor > 64)
|
||||
versionRange = getResourcePackVersionRangeNew(resourcePack.getMeta().pack());
|
||||
else if (requiredMajor > 15)
|
||||
versionRange = getResourcePackVersionRangeOld(resourcePack.getMeta().pack());
|
||||
else
|
||||
versionRange = getResourcePackVersionRangeOldest(resourcePack.getMeta().pack());
|
||||
if (versionRange.isEmpty())
|
||||
return ResourcePackFile.Compatibility.INVALID;
|
||||
if (versionRange.getMaximum().compareTo(this.getRequiredVersion()) < 0)
|
||||
return ResourcePackFile.Compatibility.TOO_OLD;
|
||||
if (versionRange.getMinimum().compareTo(this.getRequiredVersion()) > 0)
|
||||
return ResourcePackFile.Compatibility.TOO_NEW;
|
||||
return ResourcePackFile.Compatibility.COMPATIBLE;
|
||||
}
|
||||
|
||||
public boolean isIncompatible(@NotNull ResourcePackFile resourcePack) {
|
||||
return getCompatibility(resourcePack) != ResourcePackFile.Compatibility.COMPATIBLE;
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
private record GameVersionInfo114(@SerializedName("pack_version") int packVersion) {
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
private record GameVersionInfo117(@SerializedName("pack_version") PackVersionInfo117 packVersionInfo) {
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
private record PackVersionInfo117(int resource,
|
||||
@SerializedName("resource_major") int resourceMajor,
|
||||
@SerializedName("resource_minor") int resourceMinor) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.mod.modinfo.PackMcMeta;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
final class ResourcePackZipFile extends ResourcePackFile {
|
||||
private final PackMcMeta meta;
|
||||
private final @Nullable Image icon;
|
||||
|
||||
public ResourcePackZipFile(ResourcePackManager manager, Path path) throws IOException {
|
||||
super(manager, path);
|
||||
|
||||
PackMcMeta metaTemp = null;
|
||||
byte[] iconData = null;
|
||||
|
||||
try (var zipFileTree = CompressingUtils.openZipTree(path)) {
|
||||
try {
|
||||
metaTemp = PackMcMeta.fromNonNullJson(zipFileTree.readTextEntry("/pack.mcmeta"));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to parse resource pack meta", e);
|
||||
}
|
||||
|
||||
var iconEntry = zipFileTree.getEntry("/pack.png");
|
||||
if (iconEntry != null) {
|
||||
try {
|
||||
iconData = zipFileTree.readBinaryEntry(iconEntry);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resource pack icon", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.meta = metaTemp;
|
||||
|
||||
Image iconTemp = null;
|
||||
if (iconData != null) {
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(iconData)) {
|
||||
iconTemp = new Image(inputStream, 64, 64, true, true);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resource pack icon", e);
|
||||
}
|
||||
}
|
||||
this.icon = iconTemp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackMcMeta getMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Image getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteMod.Type type) throws IOException {
|
||||
RemoteModRepository repository = type.getRepoForType(RemoteModRepository.Type.RESOURCE_PACK);
|
||||
if (repository == null) return null;
|
||||
Optional<RemoteMod.Version> currentVersion = repository.getRemoteVersionByLocalFile(file);
|
||||
if (currentVersion.isEmpty()) return null;
|
||||
List<RemoteMod.Version> remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().getModid())
|
||||
.filter(version -> version.getGameVersions().contains(gameVersion))
|
||||
.filter(version -> version.getDatePublished().compareTo(currentVersion.get().getDatePublished()) > 0)
|
||||
.sorted(Comparator.comparing(RemoteMod.Version::getDatePublished).reversed())
|
||||
.toList();
|
||||
if (remoteVersions.isEmpty()) return null;
|
||||
return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
|
||||
public interface ResourcepackFile {
|
||||
@Nullable
|
||||
LocalModFile.Description getDescription();
|
||||
|
||||
String getName();
|
||||
|
||||
Path getPath();
|
||||
|
||||
byte @Nullable [] getIcon();
|
||||
|
||||
static ResourcepackFile parse(Path path) throws IOException {
|
||||
String fileName = path.getFileName().toString();
|
||||
if (Files.isRegularFile(path) && fileName.toLowerCase(Locale.ROOT).endsWith(".zip")) {
|
||||
return new ResourcepackZipFile(path);
|
||||
} else if (Files.isDirectory(path) && Files.exists(path.resolve("pack.mcmeta"))) {
|
||||
return new ResourcepackFolder(path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.modinfo.PackMcMeta;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ResourcepackFolder implements ResourcepackFile {
|
||||
private final Path path;
|
||||
private final LocalModFile.Description description;
|
||||
private final byte @Nullable [] icon;
|
||||
|
||||
public ResourcepackFolder(Path path) {
|
||||
this.path = path;
|
||||
|
||||
LocalModFile.Description description = null;
|
||||
try {
|
||||
description = JsonUtils.fromJsonFile(path.resolve("pack.mcmeta"), PackMcMeta.class).pack().description();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to parse resourcepack meta", e);
|
||||
}
|
||||
|
||||
byte[] icon;
|
||||
try {
|
||||
icon = Files.readAllBytes(path.resolve("pack.png"));
|
||||
} catch (IOException e) {
|
||||
icon = null;
|
||||
LOG.warning("Failed to read resourcepack icon", e);
|
||||
}
|
||||
this.icon = icon;
|
||||
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalModFile.Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @Nullable [] getIcon() {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.modinfo.PackMcMeta;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.tree.ZipFileTree;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class ResourcepackZipFile implements ResourcepackFile {
|
||||
private final Path path;
|
||||
private final byte @Nullable [] icon;
|
||||
private final String name;
|
||||
private final LocalModFile.Description description;
|
||||
|
||||
public ResourcepackZipFile(Path path) throws IOException {
|
||||
this.path = path;
|
||||
LocalModFile.Description description = null;
|
||||
|
||||
byte[] icon = null;
|
||||
|
||||
try (var zipFileTree = new ZipFileTree(CompressingUtils.openZipFile(path))) {
|
||||
try {
|
||||
description = JsonUtils.fromNonNullJson(zipFileTree.readTextEntry("/pack.mcmeta"), PackMcMeta.class).pack().description();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to parse resourcepack meta", e);
|
||||
}
|
||||
|
||||
var iconEntry = zipFileTree.getEntry("/pack.png");
|
||||
if (iconEntry != null) {
|
||||
try (InputStream is = zipFileTree.getInputStream(iconEntry)) {
|
||||
icon = is.readAllBytes();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resourcepack icon", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.icon = icon;
|
||||
this.description = description;
|
||||
|
||||
name = FileUtils.getNameWithoutExtension(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalModFile.Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @Nullable [] getIcon() {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Semaphore;
|
||||
@@ -331,7 +330,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
|
||||
do {
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(currentURI)
|
||||
.timeout(Duration.ofMillis(NetworkUtils.TIME_OUT))
|
||||
.timeout(NetworkUtils.TIMEOUT)
|
||||
.header("User-Agent", NetworkUtils.USER_AGENT);
|
||||
headers.forEach(requestBuilder::header);
|
||||
response = Holder.HTTP_CLIENT.send(requestBuilder.build(), BODY_HANDLER);
|
||||
@@ -739,7 +738,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
boolean useHttp2 = !"false".equalsIgnoreCase(System.getProperty("hmcl.http2"));
|
||||
|
||||
HTTP_CLIENT = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofMillis(NetworkUtils.TIME_OUT))
|
||||
.connectTimeout(NetworkUtils.TIMEOUT)
|
||||
.version(useHttp2 ? HttpClient.Version.HTTP_2 : HttpClient.Version.HTTP_1_1)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -17,11 +17,17 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -254,6 +260,18 @@ public final class StringUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Predicate<@Nullable String> compileQuery(String queryString) throws PatternSyntaxException {
|
||||
Predicate<@Nullable String> predicate;
|
||||
if (queryString.startsWith("regex:")) {
|
||||
Pattern pattern = Pattern.compile(queryString.substring("regex:".length()));
|
||||
predicate = s -> s != null && pattern.matcher(s).find();
|
||||
} else {
|
||||
String lowerQueryString = queryString.toLowerCase(Locale.ROOT);
|
||||
predicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerQueryString);
|
||||
}
|
||||
return predicate;
|
||||
}
|
||||
|
||||
public static boolean containsChinese(String str) {
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char ch = str.charAt(i);
|
||||
@@ -590,6 +608,20 @@ public final class StringUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Turns `List.of("a", "b", "c")` into `["a", "b", "c"]`
|
||||
@Contract(pure = true)
|
||||
public static String serializeStringList(List<String> list) {
|
||||
if (list == null) return "[]";
|
||||
return JsonUtils.UGLY_GSON.toJson(list.stream().filter(Objects::nonNull).toList(), JsonUtils.listTypeOf(String.class).getType());
|
||||
}
|
||||
|
||||
/// Turns `["a", "b", "c"]` into `List.of("a", "b", "c")`
|
||||
@Contract(pure = true)
|
||||
public static List<String> deserializeStringList(String json) {
|
||||
if (json == null || json.isBlank()) return List.of();
|
||||
return JsonUtils.fromNonNullJson(json, JsonUtils.listTypeOf(String.class));
|
||||
}
|
||||
|
||||
public static class LevCalculator {
|
||||
private int[][] lev;
|
||||
|
||||
|
||||
@@ -21,50 +21,81 @@ import com.google.gson.*;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.time.format.*;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Locale;
|
||||
|
||||
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
import static java.time.temporal.ChronoField.*;
|
||||
|
||||
/// Serializes and deserializes [Instant] values for Gson.
|
||||
///
|
||||
/// Serialized values are written as ISO offset date-time strings in the system default time zone,
|
||||
/// truncated to whole seconds. Deserialization accepts the historical US localized date-time
|
||||
/// format, ISO local date-time values with optional offsets, and ISO local date-time values
|
||||
/// interpreted in the system default time zone.
|
||||
///
|
||||
/// @author Glavo
|
||||
public final class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
|
||||
/// Shared adapter instance for registering with Gson.
|
||||
public static final InstantTypeAdapter INSTANCE = new InstantTypeAdapter();
|
||||
|
||||
/// Creates the singleton adapter instance.
|
||||
private InstantTypeAdapter() {
|
||||
}
|
||||
|
||||
/// Serializes an [Instant] to a JSON string in the system default time zone.
|
||||
///
|
||||
/// @param t the instant to serialize
|
||||
/// @param type the requested source type
|
||||
/// @param jsc the Gson serialization context
|
||||
/// @return a JSON primitive containing the serialized instant
|
||||
@Override
|
||||
public JsonElement serialize(Instant t, Type type, JsonSerializationContext jsc) {
|
||||
return new JsonPrimitive(serializeToString(t, ZoneId.systemDefault()));
|
||||
}
|
||||
|
||||
/// Deserializes a JSON string into an [Instant].
|
||||
///
|
||||
/// @param json the JSON element to deserialize
|
||||
/// @param type the requested target type
|
||||
/// @param context the Gson deserialization context
|
||||
/// @return the parsed instant
|
||||
/// @throws JsonParseException if `json` is not a string or cannot be parsed as an instant
|
||||
/// @throws IllegalArgumentException if `type` is not [Instant]
|
||||
@Override
|
||||
public Instant deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (!(json instanceof JsonPrimitive))
|
||||
throw new JsonParseException("The instant should be a string value");
|
||||
else {
|
||||
if (json instanceof JsonPrimitive) {
|
||||
Instant time = deserializeToInstant(json.getAsString());
|
||||
if (type == Instant.class)
|
||||
return time;
|
||||
else
|
||||
throw new IllegalArgumentException(this.getClass() + " cannot be deserialized to " + type);
|
||||
} else {
|
||||
throw new JsonParseException("The instant should be a string value");
|
||||
}
|
||||
}
|
||||
|
||||
/// Formatter for the legacy US localized date-time representation.
|
||||
private static final DateTimeFormatter EN_US_FORMAT = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM)
|
||||
.withLocale(Locale.US)
|
||||
.withZone(ZoneId.systemDefault());
|
||||
|
||||
/// Formatter for ISO local date-time text followed by one of the supported offset forms.
|
||||
private static final DateTimeFormatter ISO_DATE_TIME = new DateTimeFormatterBuilder()
|
||||
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
.optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
|
||||
.optionalStart().appendOffset("+H:MM", "+0:00").optionalEnd()
|
||||
.optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
|
||||
.optionalStart().appendOffset("+HH", "Z").optionalEnd()
|
||||
.optionalStart().appendOffsetId().optionalEnd()
|
||||
.toFormatter();
|
||||
|
||||
/// Parses an instant from one of the supported textual representations.
|
||||
///
|
||||
/// @param string the text to parse
|
||||
/// @return the parsed instant
|
||||
/// @throws JsonParseException if `string` is not in a supported instant format
|
||||
public static Instant deserializeToInstant(String string) {
|
||||
try {
|
||||
return ZonedDateTime.parse(string, EN_US_FORMAT).toInstant();
|
||||
@@ -83,6 +114,13 @@ public final class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDe
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats an instant as an ISO offset date-time string in the given time zone.
|
||||
///
|
||||
/// The output is truncated to whole seconds.
|
||||
///
|
||||
/// @param instant the instant to format
|
||||
/// @param zone the time zone used to render the instant
|
||||
/// @return the formatted instant
|
||||
public static String serializeToString(Instant instant, ZoneId zone) {
|
||||
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.ofInstant(instant, zone).truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
@@ -22,10 +22,14 @@ import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -34,115 +38,486 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author yushijinhun
|
||||
*/
|
||||
/// Utility class providing shared [Gson] instances and convenience methods for
|
||||
/// JSON serialization and deserialization.
|
||||
///
|
||||
/// Two pre-configured [Gson] instances are exposed:
|
||||
/// - [GSON] — pretty-printing instance with all standard HMCL type adapters registered.
|
||||
/// - [UGLY_GSON] — compact (non-pretty) instance with the same adapter set minus
|
||||
/// complex-map-key serialization and the built-in type adapters for [java.time.Instant],
|
||||
/// [java.util.UUID], and [java.nio.file.Path].
|
||||
///
|
||||
/// All `fromJson` / `fromJsonFile` / `fromJsonFully` overloads return `null` when the
|
||||
/// JSON literal `null` is encountered. The `fromNonNull*` variants throw
|
||||
/// [JsonParseException] instead of returning `null`. The `fromMaybeMalformed*` variants
|
||||
/// additionally swallow [com.google.gson.JsonSyntaxException] and return `null` for
|
||||
/// syntactically invalid input.
|
||||
///
|
||||
/// @author yushijinhun
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNullByDefault
|
||||
public final class JsonUtils {
|
||||
|
||||
/// The default shared [Gson] instance.
|
||||
///
|
||||
/// Configured with:
|
||||
/// - Pretty printing enabled.
|
||||
/// - Complex map key serialization enabled.
|
||||
/// - Type adapters for [java.time.Instant], [java.util.UUID], and [java.nio.file.Path].
|
||||
/// - [ValidationTypeAdapterFactory], [LowerCaseEnumTypeAdapterFactory], and
|
||||
/// [JsonTypeAdapterFactory].
|
||||
public static final Gson GSON = defaultGsonBuilder().create();
|
||||
|
||||
/// A compact [Gson] instance without pretty printing and without the extra
|
||||
/// type adapters for [java.time.Instant], [java.util.UUID], and [java.nio.file.Path].
|
||||
///
|
||||
/// Configured with:
|
||||
/// - [JsonTypeAdapterFactory]
|
||||
/// - [ValidationTypeAdapterFactory]
|
||||
/// - [LowerCaseEnumTypeAdapterFactory]
|
||||
public static final Gson UGLY_GSON = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(JsonTypeAdapterFactory.INSTANCE)
|
||||
.registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)
|
||||
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory.INSTANCE)
|
||||
.create();
|
||||
|
||||
/// Not instantiable.
|
||||
private JsonUtils() {
|
||||
}
|
||||
|
||||
public static <T> TypeToken<List<T>> listTypeOf(Class<T> elementType) {
|
||||
/// Returns a [TypeToken] representing `List<T>` parameterized with the given element class.
|
||||
///
|
||||
/// @param <T> the element type
|
||||
/// @param elementType the runtime [Class] of the list element
|
||||
/// @return a [TypeToken] for `List<T>`
|
||||
public static <T extends @UnknownNullability Object> TypeToken<List<T>> listTypeOf(Class<T> elementType) {
|
||||
return (TypeToken<List<T>>) TypeToken.getParameterized(List.class, elementType);
|
||||
}
|
||||
|
||||
public static <T> TypeToken<List<T>> listTypeOf(TypeToken<T> elementType) {
|
||||
/// Returns a [TypeToken] representing `List<T>` parameterized with the given element token.
|
||||
///
|
||||
/// @param <T> the element type
|
||||
/// @param elementType a [TypeToken] describing the element type (may itself be generic)
|
||||
/// @return a [TypeToken] for `List<T>`
|
||||
public static <T extends @UnknownNullability Object> TypeToken<List<T>> listTypeOf(TypeToken<T> elementType) {
|
||||
return (TypeToken<List<T>>) TypeToken.getParameterized(List.class, elementType.getType());
|
||||
}
|
||||
|
||||
public static <K, V> TypeToken<Map<K, V>> mapTypeOf(Class<K> keyType, Class<V> valueType) {
|
||||
/// Returns a [TypeToken] representing `Map<K, V>` parameterized with the given key and
|
||||
/// value classes.
|
||||
///
|
||||
/// @param <K> the key type
|
||||
/// @param <V> the value type
|
||||
/// @param keyType the runtime [Class] of the map key
|
||||
/// @param valueType the runtime [Class] of the map value
|
||||
/// @return a [TypeToken] for `Map<K, V>`
|
||||
public static <K, V extends @UnknownNullability Object> TypeToken<Map<K, V>> mapTypeOf(Class<K> keyType, Class<V> valueType) {
|
||||
return (TypeToken<Map<K, V>>) TypeToken.getParameterized(Map.class, keyType, valueType);
|
||||
}
|
||||
|
||||
public static <K, V> TypeToken<Map<K, V>> mapTypeOf(Class<K> keyType, TypeToken<V> valueType) {
|
||||
/// Returns a [TypeToken] representing `Map<K, V>` parameterized with the given key class
|
||||
/// and value token.
|
||||
///
|
||||
/// @param <K> the key type
|
||||
/// @param <V> the value type
|
||||
/// @param keyType the runtime [Class] of the map key
|
||||
/// @param valueType a [TypeToken] describing the value type (may itself be generic)
|
||||
/// @return a [TypeToken] for `Map<K, V>`
|
||||
public static <K, V extends @UnknownNullability Object> TypeToken<Map<K, V>> mapTypeOf(
|
||||
Class<K> keyType, TypeToken<V> valueType) {
|
||||
return (TypeToken<Map<K, V>>) TypeToken.getParameterized(Map.class, keyType, valueType.getType());
|
||||
}
|
||||
|
||||
public static <T> T fromJsonFile(Path file, Class<T> classOfT) throws IOException {
|
||||
return fromJsonFile(file, TypeToken.get(classOfT));
|
||||
/// Deserializes the JSON string into an object of the given class using the provided [Gson]
|
||||
/// instance.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param json the JSON string to parse
|
||||
/// @param type the target class
|
||||
/// @return the deserialized object, or `null` if the JSON is `null`
|
||||
/// @throws com.google.gson.JsonSyntaxException if `json` is not valid JSON for `type`
|
||||
public static <T> @Nullable T fromJson(Gson gson, String json, Class<T> type) {
|
||||
return gson.<@Nullable T>fromJson(json, type);
|
||||
}
|
||||
|
||||
public static <T> T fromJsonFile(Path file, TypeToken<T> type) throws IOException {
|
||||
/// Deserializes the JSON string into an object of the given class using [GSON].
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param json the JSON string to parse
|
||||
/// @param type the target class
|
||||
/// @return the deserialized object, or `null` if the JSON is `null`
|
||||
/// @throws com.google.gson.JsonSyntaxException if `json` is not valid JSON for `type`
|
||||
public static <T> @Nullable T fromJson(String json, Class<T> type) {
|
||||
return fromJson(GSON, json, type);
|
||||
}
|
||||
|
||||
/// Deserializes the JSON string into an object described by the given [TypeToken] using the
|
||||
/// provided [Gson] instance.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param json the JSON string to parse
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, or `null` if the JSON is `null`
|
||||
/// @throws com.google.gson.JsonSyntaxException if `json` is not valid JSON for `type`
|
||||
public static <T> @Nullable T fromJson(Gson gson, String json, TypeToken<T> type) {
|
||||
return gson.<@Nullable T>fromJson(json, type);
|
||||
}
|
||||
|
||||
/// Deserializes the JSON string into an object described by the given [TypeToken] using
|
||||
/// [GSON].
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param json the JSON string to parse
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, or `null` if the JSON is `null`
|
||||
/// @throws com.google.gson.JsonSyntaxException if `json` is not valid JSON for `type`
|
||||
public static <T> @Nullable T fromJson(String json, TypeToken<T> type) {
|
||||
return fromJson(GSON, json, type);
|
||||
}
|
||||
|
||||
/// Deserializes JSON from a [Reader] into an object of the given class using the provided
|
||||
/// [Gson] instance.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param reader the [Reader] supplying JSON content
|
||||
/// @param type the target class
|
||||
/// @return the deserialized object, or `null` if the JSON is `null`
|
||||
/// @throws com.google.gson.JsonIOException if there is a problem reading from `reader`
|
||||
/// @throws com.google.gson.JsonSyntaxException if the JSON is malformed
|
||||
public static <T> @Nullable T fromJson(Gson gson, Reader reader, Class<T> type) {
|
||||
return gson.<@Nullable T>fromJson(reader, type);
|
||||
}
|
||||
|
||||
/// Deserializes JSON from a [Reader] into an object of the given class using [GSON].
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param reader the [Reader] supplying JSON content
|
||||
/// @param type the target class
|
||||
/// @return the deserialized object, or `null` if the JSON is `null`
|
||||
/// @throws com.google.gson.JsonIOException if there is a problem reading from `reader`
|
||||
/// @throws com.google.gson.JsonSyntaxException if the JSON is malformed
|
||||
public static <T> @Nullable T fromJson(Reader reader, Class<T> type) {
|
||||
return fromJson(GSON, reader, type);
|
||||
}
|
||||
|
||||
/// Deserializes JSON from a [Reader] into an object described by the given [TypeToken]
|
||||
/// using the provided [Gson] instance.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param reader the [Reader] supplying JSON content
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, or `null` if the JSON is `null`
|
||||
/// @throws com.google.gson.JsonIOException if there is a problem reading from `reader`
|
||||
/// @throws com.google.gson.JsonSyntaxException if the JSON is malformed
|
||||
public static <T> @Nullable T fromJson(Gson gson, Reader reader, TypeToken<T> type) {
|
||||
return gson.<@Nullable T>fromJson(reader, type);
|
||||
}
|
||||
|
||||
/// Deserializes JSON from a [Reader] into an object described by the given [TypeToken]
|
||||
/// using [GSON].
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param reader the [Reader] supplying JSON content
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, or `null` if the JSON is `null`
|
||||
/// @throws com.google.gson.JsonIOException if there is a problem reading from `reader`
|
||||
/// @throws com.google.gson.JsonSyntaxException if the JSON is malformed
|
||||
public static <T> @Nullable T fromJson(Reader reader, TypeToken<T> type) {
|
||||
return fromJson(GSON, reader, type);
|
||||
}
|
||||
|
||||
/// Reads and deserializes a JSON file at the given [Path] into an object of the given class
|
||||
/// using the provided [Gson] instance.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param file the path to the JSON file
|
||||
/// @param classOfT the target class
|
||||
/// @return the deserialized object, or `null` if the JSON root is `null`
|
||||
/// @throws IOException if an I/O error occurs reading the file
|
||||
/// @throws com.google.gson.JsonSyntaxException if the file content is not valid JSON for `classOfT`
|
||||
public static <T> @Nullable T fromJsonFile(Gson gson, Path file, Class<T> classOfT) throws IOException {
|
||||
return fromJsonFile(gson, file, TypeToken.get(classOfT));
|
||||
}
|
||||
|
||||
/// Reads and deserializes a JSON file at the given [Path] into an object of the given class
|
||||
/// using [GSON].
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param file the path to the JSON file
|
||||
/// @param classOfT the target class
|
||||
/// @return the deserialized object, or `null` if the JSON root is `null`
|
||||
/// @throws IOException if an I/O error occurs reading the file
|
||||
/// @throws com.google.gson.JsonSyntaxException if the file content is not valid JSON for `classOfT`
|
||||
public static <T> @Nullable T fromJsonFile(Path file, Class<T> classOfT) throws IOException {
|
||||
return fromJsonFile(GSON, file, classOfT);
|
||||
}
|
||||
|
||||
/// Reads and deserializes a JSON file at the given [Path] into an object described by the
|
||||
/// given [TypeToken] using the provided [Gson] instance.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param file the path to the JSON file
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, or `null` if the JSON root is `null`
|
||||
/// @throws IOException if an I/O error occurs reading the file
|
||||
/// @throws com.google.gson.JsonSyntaxException if the file content is not valid JSON for `type`
|
||||
public static <T> @Nullable T fromJsonFile(Gson gson, Path file, TypeToken<T> type) throws IOException {
|
||||
try (var reader = Files.newBufferedReader(file)) {
|
||||
return GSON.fromJson(reader, type.getType());
|
||||
return gson.fromJson(reader, type.getType());
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T fromJsonFully(InputStream json, Class<T> classOfT) throws IOException, JsonParseException {
|
||||
/// Reads and deserializes a JSON file at the given [Path] into an object described by the
|
||||
/// given [TypeToken] using [GSON].
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param file the path to the JSON file
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, or `null` if the JSON root is `null`
|
||||
/// @throws IOException if an I/O error occurs reading the file
|
||||
/// @throws com.google.gson.JsonSyntaxException if the file content is not valid JSON for `type`
|
||||
public static <T> @Nullable T fromJsonFile(Path file, TypeToken<T> type) throws IOException {
|
||||
return fromJsonFile(GSON, file, type);
|
||||
}
|
||||
|
||||
/// Reads and deserializes JSON from an [InputStream] (UTF-8) into an object of the given
|
||||
/// class using [GSON]. The stream is closed after reading.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param json the input stream containing UTF-8 JSON
|
||||
/// @param classOfT the target class
|
||||
/// @return the deserialized object, or `null` if the JSON root is `null`
|
||||
/// @throws IOException if an I/O error occurs reading the stream
|
||||
/// @throws JsonParseException if the JSON is malformed or cannot be deserialized
|
||||
public static <T> @Nullable T fromJsonFully(InputStream json, Class<T> classOfT) throws IOException, JsonParseException {
|
||||
try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {
|
||||
return GSON.fromJson(reader, classOfT);
|
||||
return GSON.<@Nullable T>fromJson(reader, classOfT);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T fromJsonFully(InputStream json, TypeToken<T> type) throws IOException, JsonParseException {
|
||||
/// Reads and deserializes JSON from an [InputStream] (UTF-8) into an object described by
|
||||
/// the given [TypeToken] using [GSON]. The stream is closed after reading.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param json the input stream containing UTF-8 JSON
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, or `null` if the JSON root is `null`
|
||||
/// @throws IOException if an I/O error occurs reading the stream
|
||||
/// @throws JsonParseException if the JSON is malformed or cannot be deserialized
|
||||
public static <T> @Nullable T fromJsonFully(InputStream json, TypeToken<T> type) throws IOException, JsonParseException {
|
||||
try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {
|
||||
return GSON.fromJson(reader, type);
|
||||
return GSON.<@Nullable T>fromJson(reader, type);
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes a JSON string into a non-null object of the given class using the provided
|
||||
/// [Gson] instance. Throws [JsonParseException] if the result would be `null`.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param json the JSON string to parse
|
||||
/// @param classOfT the target class
|
||||
/// @return the deserialized object, never `null`
|
||||
/// @throws JsonParseException if the JSON is `null` or cannot be deserialized
|
||||
public static <T> T fromNonNullJson(Gson gson, String json, Class<T> classOfT) throws JsonParseException {
|
||||
return fromNonNullJson(gson, json, TypeToken.get(classOfT));
|
||||
}
|
||||
|
||||
/// Deserializes a JSON string into a non-null object of the given class using [GSON].
|
||||
/// Throws [JsonParseException] if the result would be `null`.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param json the JSON string to parse
|
||||
/// @param classOfT the target class
|
||||
/// @return the deserialized object, never `null`
|
||||
/// @throws JsonParseException if the JSON is `null` or cannot be deserialized
|
||||
public static <T> T fromNonNullJson(String json, Class<T> classOfT) throws JsonParseException {
|
||||
T parsed = GSON.fromJson(json, classOfT);
|
||||
return fromNonNullJson(GSON, json, classOfT);
|
||||
}
|
||||
|
||||
/// Deserializes a JSON string into a non-null object described by the given [TypeToken]
|
||||
/// using the provided [Gson] instance. Throws [JsonParseException] if the result would be
|
||||
/// `null`.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param json the JSON string to parse
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, never `null`
|
||||
/// @throws JsonParseException if the JSON is `null` or cannot be deserialized
|
||||
public static <T> T fromNonNullJson(Gson gson, String json, TypeToken<T> type) throws JsonParseException {
|
||||
T parsed = fromJson(gson, json, type);
|
||||
if (parsed == null)
|
||||
throw new JsonParseException("Json object cannot be null.");
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/// Deserializes a JSON string into a non-null object described by the given [TypeToken]
|
||||
/// using [GSON]. Throws [JsonParseException] if the result would be `null`.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param json the JSON string to parse
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, never `null`
|
||||
/// @throws JsonParseException if the JSON is `null` or cannot be deserialized
|
||||
public static <T> T fromNonNullJson(String json, TypeToken<T> type) throws JsonParseException {
|
||||
T parsed = GSON.fromJson(json, type);
|
||||
if (parsed == null)
|
||||
throw new JsonParseException("Json object cannot be null.");
|
||||
return parsed;
|
||||
return fromNonNullJson(GSON, json, type);
|
||||
}
|
||||
|
||||
public static <T> T fromNonNullJsonFully(InputStream json, Class<T> classOfT) throws IOException, JsonParseException {
|
||||
try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {
|
||||
T parsed = GSON.fromJson(reader, classOfT);
|
||||
/// Reads and deserializes JSON from an [InputStream] (UTF-8) into a non-null object of the
|
||||
/// given class using the provided [Gson] instance. The stream is closed after reading.
|
||||
/// Throws [JsonParseException] if the result would be `null`.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param inputStream the input stream containing UTF-8 JSON
|
||||
/// @param classOfT the target class
|
||||
/// @return the deserialized object, never `null`
|
||||
/// @throws IOException if an I/O error occurs reading the stream
|
||||
/// @throws JsonParseException if the JSON is `null` or cannot be deserialized
|
||||
public static <T> T fromNonNullJsonFully(Gson gson, InputStream inputStream, Class<T> classOfT) throws IOException, JsonParseException {
|
||||
return fromNonNullJsonFully(gson, inputStream, TypeToken.get(classOfT));
|
||||
}
|
||||
|
||||
/// Reads and deserializes JSON from an [InputStream] (UTF-8) into a non-null object of the
|
||||
/// given class using [GSON]. The stream is closed after reading.
|
||||
/// Throws [JsonParseException] if the result would be `null`.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param inputStream the input stream containing UTF-8 JSON
|
||||
/// @param classOfT the target class
|
||||
/// @return the deserialized object, never `null`
|
||||
/// @throws IOException if an I/O error occurs reading the stream
|
||||
/// @throws JsonParseException if the JSON is `null` or cannot be deserialized
|
||||
public static <T> T fromNonNullJsonFully(InputStream inputStream, Class<T> classOfT) throws IOException, JsonParseException {
|
||||
return fromNonNullJsonFully(GSON, inputStream, classOfT);
|
||||
}
|
||||
|
||||
/// Reads and deserializes JSON from an [InputStream] (UTF-8) into a non-null object
|
||||
/// described by the given [TypeToken] using the provided [Gson] instance. The stream is
|
||||
/// closed after reading. Throws [JsonParseException] if the result would be `null`.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param inputStream the input stream containing UTF-8 JSON
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, never `null`
|
||||
/// @throws IOException if an I/O error occurs reading the stream
|
||||
/// @throws JsonParseException if the JSON is `null` or cannot be deserialized
|
||||
public static <T> T fromNonNullJsonFully(Gson gson, InputStream inputStream, TypeToken<T> type) throws IOException, JsonParseException {
|
||||
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
|
||||
T parsed = fromJson(gson, reader, type);
|
||||
if (parsed == null)
|
||||
throw new JsonParseException("Json object cannot be null.");
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T fromNonNullJsonFully(InputStream json, TypeToken<T> type) throws IOException, JsonParseException {
|
||||
try (InputStreamReader reader = new InputStreamReader(json, StandardCharsets.UTF_8)) {
|
||||
T parsed = GSON.fromJson(reader, type);
|
||||
if (parsed == null)
|
||||
throw new JsonParseException("Json object cannot be null.");
|
||||
return parsed;
|
||||
}
|
||||
/// Reads and deserializes JSON from an [InputStream] (UTF-8) into a non-null object
|
||||
/// described by the given [TypeToken] using [GSON]. The stream is closed after reading.
|
||||
/// Throws [JsonParseException] if the result would be `null`.
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param inputStream the input stream containing UTF-8 JSON
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, never `null`
|
||||
/// @throws IOException if an I/O error occurs reading the stream
|
||||
/// @throws JsonParseException if the JSON is `null` or cannot be deserialized
|
||||
public static <T> T fromNonNullJsonFully(InputStream inputStream, TypeToken<T> type) throws IOException, JsonParseException {
|
||||
return fromNonNullJsonFully(GSON, inputStream, type);
|
||||
}
|
||||
|
||||
public static <T> T fromMaybeMalformedJson(String json, Class<T> classOfT) throws JsonParseException {
|
||||
/// Deserializes a possibly malformed JSON string into an object of the given class using
|
||||
/// the provided [Gson] instance. Returns `null` if the JSON is syntactically invalid
|
||||
/// ([com.google.gson.JsonSyntaxException] is swallowed).
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param json the JSON string to parse
|
||||
/// @param classOfT the target class
|
||||
/// @return the deserialized object, or `null` if the JSON is `null` or syntactically invalid
|
||||
/// @throws JsonParseException if the JSON is well-formed but semantically invalid
|
||||
public static <T> @Nullable T fromMaybeMalformedJson(Gson gson, String json, Class<T> classOfT) throws JsonParseException {
|
||||
return fromMaybeMalformedJson(gson, json, TypeToken.get(classOfT));
|
||||
}
|
||||
|
||||
/// Deserializes a possibly malformed JSON string into an object of the given class using
|
||||
/// [GSON]. Returns `null` if the JSON is syntactically invalid
|
||||
/// ([com.google.gson.JsonSyntaxException] is swallowed).
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param json the JSON string to parse
|
||||
/// @param classOfT the target class
|
||||
/// @return the deserialized object, or `null` if the JSON is `null` or syntactically invalid
|
||||
/// @throws JsonParseException if the JSON is well-formed but semantically invalid
|
||||
public static <T> @Nullable T fromMaybeMalformedJson(String json, Class<T> classOfT) throws JsonParseException {
|
||||
return fromMaybeMalformedJson(GSON, json, classOfT);
|
||||
}
|
||||
|
||||
/// Deserializes a possibly malformed JSON string into an object described by the given
|
||||
/// [TypeToken] using the provided [Gson] instance. Returns `null` if the JSON is
|
||||
/// syntactically invalid ([com.google.gson.JsonSyntaxException] is swallowed).
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param gson the [Gson] instance to use
|
||||
/// @param json the JSON string to parse
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, or `null` if the JSON is `null` or syntactically invalid
|
||||
/// @throws JsonParseException if the JSON is well-formed but semantically invalid
|
||||
public static <T> @Nullable T fromMaybeMalformedJson(Gson gson, String json, TypeToken<T> type) throws JsonParseException {
|
||||
try {
|
||||
return GSON.fromJson(json, classOfT);
|
||||
return gson.fromJson(json, type);
|
||||
} catch (JsonSyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T fromMaybeMalformedJson(String json, TypeToken<T> type) throws JsonParseException {
|
||||
try {
|
||||
return GSON.fromJson(json, type);
|
||||
} catch (JsonSyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
/// Deserializes a possibly malformed JSON string into an object described by the given
|
||||
/// [TypeToken] using [GSON]. Returns `null` if the JSON is syntactically invalid
|
||||
/// ([com.google.gson.JsonSyntaxException] is swallowed).
|
||||
///
|
||||
/// @param <T> the target type
|
||||
/// @param json the JSON string to parse
|
||||
/// @param type a [TypeToken] describing the target type
|
||||
/// @return the deserialized object, or `null` if the JSON is `null` or syntactically invalid
|
||||
/// @throws JsonParseException if the JSON is well-formed but semantically invalid
|
||||
public static <T> @Nullable T fromMaybeMalformedJson(String json, TypeToken<T> type) throws JsonParseException {
|
||||
return fromMaybeMalformedJson(GSON, json, type);
|
||||
}
|
||||
|
||||
public static void writeToJsonFile(Path file, Object value) throws IOException {
|
||||
/// Serializes `value` to JSON and writes it to the file at the given [Path] using [GSON].
|
||||
///
|
||||
/// @param file the path to the output file
|
||||
/// @param value the object to serialize; may be `null`, in which case the JSON `null`
|
||||
/// literal is written
|
||||
/// @throws IOException if an I/O error occurs creating or writing the file
|
||||
public static void writeToJsonFile(Path file, @Nullable Object value) throws IOException {
|
||||
try (var writer = Files.newBufferedWriter(file)) {
|
||||
GSON.toJson(value, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates and returns a pre-configured [GsonBuilder] used to construct [GSON].
|
||||
///
|
||||
/// The builder has the following configuration applied:
|
||||
/// - Complex map key serialization enabled.
|
||||
/// - Pretty printing enabled.
|
||||
/// - [InstantTypeAdapter] registered for [java.time.Instant].
|
||||
/// - [UUIDTypeAdapter] registered for [java.util.UUID].
|
||||
/// - [PathTypeAdapter] registered for [java.nio.file.Path].
|
||||
/// - [ValidationTypeAdapterFactory], [LowerCaseEnumTypeAdapterFactory], and
|
||||
/// [JsonTypeAdapterFactory] registered as type adapter factories.
|
||||
///
|
||||
/// Callers may further customize the returned builder before calling
|
||||
/// [GsonBuilder#create()].
|
||||
///
|
||||
/// @return a new [GsonBuilder] with the default HMCL JSON configuration
|
||||
public static GsonBuilder defaultGsonBuilder() {
|
||||
return new GsonBuilder()
|
||||
.enableComplexMapKeySerialization()
|
||||
|
||||
@@ -23,28 +23,60 @@ import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/// Represents text that may either be locale-independent or stored as values keyed by language tags.
|
||||
///
|
||||
/// The JSON representation accepts either a string or an object. A string is treated as the same text for every
|
||||
/// locale, while an object maps language keys, such as `en`, `zh-Hans`, or `default`, to localized text values.
|
||||
///
|
||||
/// @author Glavo
|
||||
@NotNullByDefault
|
||||
@JsonAdapter(LocalizedText.Adapter.class)
|
||||
@JsonSerializable
|
||||
public final class LocalizedText {
|
||||
/// Locale-independent text used when this instance is not backed by localized values.
|
||||
private final @Nullable String value;
|
||||
|
||||
/// Text values keyed by language keys generated by [LocaleUtils#toLanguageKey(Locale)].
|
||||
private final @Nullable Map<String, String> localizedValues;
|
||||
|
||||
public LocalizedText(String value) {
|
||||
/// Creates a locale-independent text value.
|
||||
///
|
||||
/// When `value` is `null`, [#getText(List)] returns `null` for every candidate locale.
|
||||
///
|
||||
/// @param value the text value, or `null` if the text is absent
|
||||
public LocalizedText(@Nullable String value) {
|
||||
this.value = value;
|
||||
this.localizedValues = null;
|
||||
}
|
||||
|
||||
public LocalizedText(@NotNull Map<String, String> localizedValues) {
|
||||
/// Creates a localized text value backed by language-key mappings.
|
||||
///
|
||||
/// The map is stored directly and is not copied. Later changes to `localizedValues` are visible through this
|
||||
/// instance.
|
||||
///
|
||||
/// @param localizedValues text values keyed by language keys generated by [LocaleUtils#toLanguageKey(Locale)]
|
||||
/// @throws NullPointerException if `localizedValues` is `null`
|
||||
public LocalizedText(Map<String, String> localizedValues) {
|
||||
this.value = null;
|
||||
this.localizedValues = Objects.requireNonNull(localizedValues);
|
||||
}
|
||||
|
||||
public String getText(@NotNull List<Locale> candidates) {
|
||||
/// Returns the best matching text for the given candidate locales.
|
||||
///
|
||||
/// If this instance stores localized values, the candidates are checked in iteration order after conversion with
|
||||
/// [LocaleUtils#toLanguageKey(Locale)]. The first matching value is returned, or `null` if no candidate matches.
|
||||
/// If this instance stores locale-independent text, the stored value is returned without checking `candidates`.
|
||||
///
|
||||
/// @param candidates candidate locales ordered from most preferred to least preferred
|
||||
/// @return the matched text, the locale-independent text, or `null` if no text is available
|
||||
public @Nullable String getText(List<Locale> candidates) {
|
||||
if (localizedValues != null) {
|
||||
for (Locale locale : candidates) {
|
||||
String value = localizedValues.get(LocaleUtils.toLanguageKey(locale));
|
||||
@@ -56,10 +88,17 @@ public final class LocalizedText {
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Gson adapter for the compact localized text JSON representation.
|
||||
static final class Adapter extends TypeAdapter<LocalizedText> {
|
||||
|
||||
/// Reads a localized text value from `null`, a JSON string, or a JSON object.
|
||||
///
|
||||
/// @param jsonReader the reader positioned at the next localized text value
|
||||
/// @return the parsed localized text, or `null` if the next JSON token is `null`
|
||||
/// @throws IOException if reading from `jsonReader` fails
|
||||
/// @throws JsonSyntaxException if the next token is neither `null`, a string, nor an object
|
||||
@Override
|
||||
public LocalizedText read(JsonReader jsonReader) throws IOException {
|
||||
public @Nullable LocalizedText read(JsonReader jsonReader) throws IOException {
|
||||
JsonToken nextToken = jsonReader.peek();
|
||||
if (nextToken == JsonToken.NULL) {
|
||||
return null;
|
||||
@@ -83,8 +122,13 @@ public final class LocalizedText {
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a localized text value as `null`, a JSON string, or a JSON object.
|
||||
///
|
||||
/// @param jsonWriter the writer that receives the JSON representation
|
||||
/// @param localizedText the localized text to write, or `null` to write a JSON `null`
|
||||
/// @throws IOException if writing to `jsonWriter` fails
|
||||
@Override
|
||||
public void write(JsonWriter jsonWriter, LocalizedText localizedText) throws IOException {
|
||||
public void write(JsonWriter jsonWriter, @Nullable LocalizedText localizedText) throws IOException {
|
||||
if (localizedText == null) {
|
||||
jsonWriter.nullValue();
|
||||
} else if (localizedText.localizedValues != null) {
|
||||
|
||||
@@ -99,7 +99,11 @@ public final class FileUtils {
|
||||
}
|
||||
|
||||
public static String getNameWithoutExtension(Path file) {
|
||||
return StringUtils.substringBeforeLast(getName(file), '.');
|
||||
String name = getName(file);
|
||||
if (Files.isDirectory(file)) {
|
||||
return name;
|
||||
}
|
||||
return StringUtils.substringBeforeLast(name, '.');
|
||||
}
|
||||
|
||||
public static String getExtension(String fileName) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -45,7 +46,9 @@ public final class NetworkUtils {
|
||||
|
||||
public static final String PARAMETER_SEPARATOR = "&";
|
||||
public static final String NAME_VALUE_SEPARATOR = "=";
|
||||
public static final int TIME_OUT = 8000;
|
||||
|
||||
public static final Duration TIMEOUT = Duration.ofSeconds(30);
|
||||
public static final int TIMEOUT_MILLIS = (int) TIMEOUT.toMillis();
|
||||
|
||||
private NetworkUtils() {
|
||||
}
|
||||
@@ -167,8 +170,8 @@ public final class NetworkUtils {
|
||||
} catch (IllegalArgumentException | MalformedURLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
connection.setConnectTimeout(TIME_OUT);
|
||||
connection.setReadTimeout(TIME_OUT);
|
||||
connection.setConnectTimeout(TIMEOUT_MILLIS);
|
||||
connection.setReadTimeout(TIMEOUT_MILLIS);
|
||||
if (connection instanceof HttpURLConnection httpConnection) {
|
||||
httpConnection.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
|
||||
httpConnection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
@@ -276,8 +279,8 @@ public final class NetworkUtils {
|
||||
int redirect = 0;
|
||||
while (true) {
|
||||
conn.setUseCaches(useCache);
|
||||
conn.setConnectTimeout(TIME_OUT);
|
||||
conn.setReadTimeout(TIME_OUT);
|
||||
conn.setConnectTimeout(TIMEOUT_MILLIS);
|
||||
conn.setReadTimeout(TIMEOUT_MILLIS);
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
Map<String, List<String>> properties = conn.getRequestProperties();
|
||||
String method = conn.getRequestMethod();
|
||||
|
||||
@@ -50,6 +50,11 @@ public final class VersionRange<T extends Comparable<T>> {
|
||||
return new VersionRange<>(null, maximum);
|
||||
}
|
||||
|
||||
public static <T extends Comparable<T>> VersionRange<T> is(T version) {
|
||||
assert version != null;
|
||||
return new VersionRange<>(version, version);
|
||||
}
|
||||
|
||||
private final T minimum;
|
||||
private final T maximum;
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.jackhuang.hmcl.resourcepack.ResourcePackManager.isMcVersionSupported;
|
||||
import static org.jackhuang.hmcl.resourcepack.ResourcePackManager.isMcVersionSupportsNewOptionsFormat;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ResourcePackManagerTest {
|
||||
|
||||
@Test
|
||||
void testIsMcVersionSupported() {
|
||||
assertTrue(isMcVersionSupported(GameVersionNumber.asGameVersion("26.1-snapshot-1")));
|
||||
assertTrue(isMcVersionSupported(GameVersionNumber.asGameVersion("25w14craftmine")));
|
||||
assertTrue(isMcVersionSupported(GameVersionNumber.asGameVersion("1.21")));
|
||||
assertTrue(isMcVersionSupported(GameVersionNumber.asGameVersion("1.16.5")));
|
||||
assertTrue(isMcVersionSupported(GameVersionNumber.asGameVersion("1.13-pre3")));
|
||||
assertTrue(isMcVersionSupported(GameVersionNumber.asGameVersion("17w48a")));
|
||||
assertTrue(isMcVersionSupported(GameVersionNumber.asGameVersion("13w24a")));
|
||||
assertTrue(isMcVersionSupported(GameVersionNumber.asGameVersion("1.6.1")));
|
||||
|
||||
assertFalse(isMcVersionSupported(GameVersionNumber.asGameVersion("13w23a")));
|
||||
assertFalse(isMcVersionSupported(GameVersionNumber.asGameVersion("1.6")));
|
||||
assertFalse(isMcVersionSupported(GameVersionNumber.asGameVersion("b1.1-1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsMcVersionSupportsNewOptionsFormat() {
|
||||
assertTrue(isMcVersionSupportsNewOptionsFormat(GameVersionNumber.asGameVersion("26.1.1")));
|
||||
assertTrue(isMcVersionSupportsNewOptionsFormat(GameVersionNumber.asGameVersion("25w14craftmine")));
|
||||
assertTrue(isMcVersionSupportsNewOptionsFormat(GameVersionNumber.asGameVersion("1.13")));
|
||||
assertTrue(isMcVersionSupportsNewOptionsFormat(GameVersionNumber.asGameVersion("17w43a")));
|
||||
|
||||
assertFalse(isMcVersionSupportsNewOptionsFormat(GameVersionNumber.asGameVersion("1.12.2")));
|
||||
assertFalse(isMcVersionSupportsNewOptionsFormat(GameVersionNumber.asGameVersion("17w31a")));
|
||||
assertFalse(isMcVersionSupportsNewOptionsFormat(GameVersionNumber.asGameVersion("b1.1-1")));
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,14 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
@@ -47,4 +52,30 @@ public final class StringUtilsTest {
|
||||
assertEquals("a b c", StringUtils.normalizeWhitespaces(" a \t b c "));
|
||||
assertEquals("a b c", StringUtils.normalizeWhitespaces(" a \t b c "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializeStringList() {
|
||||
assertEquals("[]", StringUtils.serializeStringList(null));
|
||||
assertEquals("[]", StringUtils.serializeStringList(List.of()));
|
||||
assertEquals("[]", StringUtils.serializeStringList(Arrays.asList((String) null)));
|
||||
assertEquals("[\"hello\"]", StringUtils.serializeStringList(List.of("hello")));
|
||||
assertEquals("[\"hello\"]", StringUtils.serializeStringList(Arrays.asList("hello", null)));
|
||||
assertEquals("[\"he\\\"llo\"]", StringUtils.serializeStringList(Arrays.asList("he\"llo", null)));
|
||||
assertEquals("[\"hello\",\"world\"]", StringUtils.serializeStringList(List.of("hello", "world")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeserializeStringList() {
|
||||
assertEquals(List.of(), StringUtils.deserializeStringList(null));
|
||||
assertEquals(List.of(), StringUtils.deserializeStringList("[]"));
|
||||
assertEquals(List.of(), StringUtils.deserializeStringList("[ ]"));
|
||||
assertThrows(JsonSyntaxException.class, () -> StringUtils.deserializeStringList("[\"]"));
|
||||
assertThrows(JsonSyntaxException.class, () -> StringUtils.deserializeStringList("[\"he\"llo\"]"));
|
||||
assertThrows(JsonSyntaxException.class, () -> StringUtils.deserializeStringList("\"hello\""));
|
||||
assertEquals(Arrays.asList((String) null), StringUtils.deserializeStringList("[null]"));
|
||||
assertEquals(List.of("hello"), StringUtils.deserializeStringList("[\"hello\"]"));
|
||||
assertEquals(List.of("he\"llo"), StringUtils.deserializeStringList("[\"he\\\"llo\"]"));
|
||||
assertEquals(List.of("hello", "world"), StringUtils.deserializeStringList("[\"hello\",\"world\"]"));
|
||||
assertEquals(List.of("hello", "world"), StringUtils.deserializeStringList("[\"hello\",\n\"world\"]"));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
Exception in thread "main" java.lang.ClassNotFoundException: org.glavo.log4j.patch.agent.Log4jAgent
|
||||
at java.net.URLClassLoader.findClass(Unknown Source)
|
||||
at java.lang.ClassLoader.loadClass(Unknown Source)
|
||||
FATAL ERROR in native method: processing of -javaagent failed
|
||||
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
|
||||
at java.lang.ClassLoader.loadClass(Unknown Source)
|
||||
at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)
|
||||
at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(Unknown Source)
|
||||
Exception in thread "main" java.lang.ClassNotFoundException: org.glavo.log4j.patch.agent.Log4jAgent
|
||||
at java.net.URLClassLoader.findClass(Unknown Source)
|
||||
at java.lang.ClassLoader.loadClass(Unknown Source)
|
||||
FATAL ERROR in native method: processing of -javaagent failed
|
||||
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
|
||||
at java.lang.ClassLoader.loadClass(Unknown Source)
|
||||
at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)
|
||||
at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(Unknown Source)
|
||||
@@ -4,16 +4,16 @@
|
||||
Time: 2024-02-05 22:59:34
|
||||
Description: Initializing game
|
||||
|
||||
java.lang.RuntimeException: RivaTuner Statistics Server (RTSS) is not compatible with Sodium, see here for more details: https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible
|
||||
at me.jellysquid.mods.sodium.client.compatibility.checks.ModuleScanner.checkModules(ModuleScanner.java:36)
|
||||
at net.minecraft.class_1041.handler$cda000$sodium$postWindowCreated(class_1041.java:2572)
|
||||
at net.minecraft.class_1041.<init>(class_1041.java:109)
|
||||
at net.minecraft.class_3682.method_16038(class_3682.java:21)
|
||||
at net.minecraft.class_310.<init>(class_310.java:515)
|
||||
at net.minecraft.client.main.Main.main(Main.java:223)
|
||||
at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:470)
|
||||
at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74)
|
||||
at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23)
|
||||
java.lang.RuntimeException: RivaTuner Statistics Server (RTSS) is not compatible with Sodium, see here for more details: https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible
|
||||
at me.jellysquid.mods.sodium.client.compatibility.checks.ModuleScanner.checkModules(ModuleScanner.java:36)
|
||||
at net.minecraft.class_1041.handler$cda000$sodium$postWindowCreated(class_1041.java:2572)
|
||||
at net.minecraft.class_1041.<init>(class_1041.java:109)
|
||||
at net.minecraft.class_3682.method_16038(class_3682.java:21)
|
||||
at net.minecraft.class_310.<init>(class_310.java:515)
|
||||
at net.minecraft.client.main.Main.main(Main.java:223)
|
||||
at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:470)
|
||||
at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74)
|
||||
at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23)
|
||||
|
||||
|
||||
A detailed walkthrough of the error, its code path and all known details is as follows:
|
||||
|
||||
@@ -1,500 +1,500 @@
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
Caused by: java.net.SocketTimeoutException: Read timed out
|
||||
at sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283) ~[?:?]
|
||||
at sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309) ~[?:?]
|
||||
at sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350) ~[?:?]
|
||||
at sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803) ~[?:?]
|
||||
at java.net.Socket$SocketInputStream.read(Socket.java:966) ~[?:?]
|
||||
at java.io.BufferedInputStream.fill(BufferedInputStream.java:244) ~[?:?]
|
||||
at java.io.BufferedInputStream.read1(BufferedInputStream.java:284) ~[?:?]
|
||||
at java.io.BufferedInputStream.read(BufferedInputStream.java:343) ~[?:?]
|
||||
at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:791) ~[?:?]
|
||||
at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:726) ~[?:?]
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1688) ~[?:?]
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589) ~[?:?]
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getHeaderField(HttpURLConnection.java:3221) ~[?:?]
|
||||
at java.net.URLConnection.getHeaderFieldLong(URLConnection.java:637) ~[?:?]
|
||||
at java.net.URLConnection.getContentLengthLong(URLConnection.java:509) ~[?:?]
|
||||
at xaero.map.misc.Internet.checkModVersion(Internet.java:51) ~[transformed-mod-xaeroworldmap.i0:0/:?]
|
||||
... 17 more
|
||||
[12:16:23] [Render thread/INFO]: Loading Xaero's Minimap - Stage 1/2
|
||||
[12:16:23] [Render thread/WARN]: Static binding violation: PRIVATE @Overwrite method method_23182 in #sodium:sodium.mixins.json:features.item.MixinItemRenderer from mod sodium cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded.
|
||||
[12:16:23] [Render thread/INFO]: [Quilt Command|Client] Networking support is enabled
|
||||
[12:16:23] [Render thread/INFO]: ARRP register - before vanilla
|
||||
[12:16:23] [Render thread/INFO]: ARRP register - after vanilla
|
||||
[12:16:23] [Render thread/INFO]: Backend library: LWJGL version 3.3.1 build 7
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - before vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - after vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - before vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - after vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - before vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - after vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - before user
|
||||
[12:16:24] [Render thread/WARN]: Error loading class: net/minecraft/class_809$class_811 (java.lang.ClassNotFoundException: net/minecraft/class_809$class_811)
|
||||
[12:16:24] [Render thread/ERROR]: Mixin apply for mod enhancedblockentities failed #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> net.minecraft.class_756: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]
|
||||
org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:510) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:453) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[?:?]
|
||||
at net.minecraft.class_310.<init>(class_310.java:573) ~[transformed-mod-minecraft.i0:0/:?]
|
||||
at net.minecraft.client.main.Main.main(Main.java:198) ~[1.19.jar:?]
|
||||
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
|
||||
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
|
||||
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
|
||||
at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: net.minecraft.class_809$class_811
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:1002) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:962) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformLVT(MixinTargetContext.java:563) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:469) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
... 24 more
|
||||
[12:16:24] [Render thread/WARN]: Mixin transformation of net.minecraft.class_756 failed
|
||||
org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:392) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:453) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[?:?]
|
||||
at net.minecraft.class_310.<init>(class_310.java:573) ~[transformed-mod-minecraft.i0:0/:?]
|
||||
at net.minecraft.client.main.Main.main(Main.java:198) ~[1.19.jar:?]
|
||||
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
|
||||
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
|
||||
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
|
||||
at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.MixinApplyError: Mixin [#enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities] from phase [DEFAULT] in config [#enhancedblockentities:enhancedblockentities.mixins.json] FAILED during APPLY
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinError(MixinProcessor.java:638) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinApplyError(MixinProcessor.java:589) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:379) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
... 17 more
|
||||
Caused by: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:510) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
... 17 more
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: net.minecraft.class_809$class_811
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:1002) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:962) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformLVT(MixinTargetContext.java:563) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:469) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
... 17 more
|
||||
---- Minecraft Crash Report ----
|
||||
// Hi. I'm Minecraft, and I'm a crashaholic.
|
||||
|
||||
Time: 2023-04-16 12:16:24
|
||||
Description: Initializing game
|
||||
|
||||
java.lang.RuntimeException: Mixin transformation of net.minecraft.class_756 failed
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:458)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228)
|
||||
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
|
||||
at net.minecraft.class_310.<init>(class_310.java:573)
|
||||
at net.minecraft.client.main.Main.main(Main.java:198)
|
||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527)
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28)
|
||||
Caused by: org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:392)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:453)
|
||||
... 14 more
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.MixinApplyError: Mixin [#enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities] from phase [DEFAULT] in config [#enhancedblockentities:enhancedblockentities.mixins.json] FAILED during APPLY
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinError(MixinProcessor.java:638)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinApplyError(MixinProcessor.java:589)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:379)
|
||||
... 17 more
|
||||
Caused by: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:510)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327)
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421)
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363)
|
||||
... 17 more
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: net.minecraft.class_809$class_811
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:1002)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:962)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformLVT(MixinTargetContext.java:563)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:469)
|
||||
... 24 more
|
||||
|
||||
|
||||
A detailed walkthrough of the error, its code path and all known details is as follows:
|
||||
---------------------------------------------------------------------------------------
|
||||
|
||||
-- Head --
|
||||
Thread: Render thread
|
||||
Stacktrace:
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:458)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228)
|
||||
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
|
||||
at net.minecraft.class_310.<init>(class_310.java:573)
|
||||
|
||||
-- Initialization --
|
||||
Details:
|
||||
Modules:
|
||||
ADVAPI32.dll:Advanced Windows 32 Base API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
COMCTL32.dll:User Experience Controls Library:6.10 (WinBuild.160101.0800):Microsoft Corporation
|
||||
CRYPT32.dll:Crypto API32:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
CRYPTBASE.dll:Base cryptographic API DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
CRYPTSP.dll:Cryptographic Service Provider API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
CoreMessaging.dll:Microsoft CoreMessaging Dll:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
DBGHELP.DLL:Windows Image Helper:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
DEVOBJ.dll:Device Information Set DLL:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
DNSAPI.dll:DNS Client API DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
GDI32.dll:GDI Client DLL:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
GLU32.dll:OpenGL Utility Library DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
IMM32.DLL:Multi-User Windows IMM32 API Client DLL:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
IPHLPAPI.DLL:IP Helper API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
KERNEL32.DLL:Windows NT BASE API Client DLL:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
KERNELBASE.dll:Windows NT BASE API Client DLL:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
MMDevApi.dll:MMDevice API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
MSCTF.dll:MSCTF Server DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
MpOav.dll:IOfficeAntiVirus Module:4.18.2303.8 (WinBuild.160101.0800):Microsoft Corporation
|
||||
NSI.dll:NSI User-mode interface DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
NTASN1.dll:Microsoft ASN.1 API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
OLEAUT32.dll:OLEAUT32.DLL:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
Ole32.dll:Microsoft OLE for Windows:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
OpenAL.dll:Main implementation library:1.21.1:
|
||||
PSAPI.DLL:Process Status Helper:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
Pdh.dll:Windows Performance Data Helper DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
RPCRT4.dll:Remote Procedure Call Runtime:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
SETUPAPI.dll:Windows Setup API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
SHCORE.dll:SHCORE:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
SHELL32.dll:Windows Shell Common Dll:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
UMPDC.dll:User Mode Power Dependency Coordinator:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
USER32.dll:Multi-User Windows USER API Client DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
USERENV.dll:Userenv:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
VCRUNTIME140.dll:Microsoft® C Runtime Library:14.29.30133.0 built by: vcwrkspc:Microsoft Corporation
|
||||
VERSION.dll:Version Checking and File Installation Libraries:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WINHTTP.dll:Windows HTTP Services:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WINMM.dll:MCI API DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WINSTA.dll:Winstation Library:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WINTRUST.dll:Microsoft Trust Verification APIs:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WS2_32.dll:Windows Socket 2.0 32-Bit DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WSOCK32.dll:Windows Socket 32-Bit DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WTSAPI32.dll:Windows Remote Desktop Session Host Server SDK APIs:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
amsi.dll:Anti-Malware Scan Interface:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
bcrypt.dll:Windows Cryptographic Primitives Library:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
bcryptPrimitives.dll:Windows Cryptographic Primitives Library:10.0.22621.1175 (WinBuild.160101.0800):Microsoft Corporation
|
||||
cfgmgr32.dll:Configuration Manager DLL:10.0.22621.746 (WinBuild.160101.0800):Microsoft Corporation
|
||||
clbcatq.dll:COM+ Configuration Catalog:2001.12.10941.16384 (WinBuild.160101.0800):Microsoft Corporation
|
||||
combase.dll:Microsoft COM for Windows:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
cryptnet.dll:Crypto Network Related API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dbgcore.DLL:Windows Core Debugging Helpers:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dhcpcsvc.DLL:DHCP Client Service:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dhcpcsvc6.DLL:DHCPv6 Client:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dinput8.dll:Microsoft DirectInput:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
drvstore.dll:Driver Store API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dwmapi.dll:Microsoft Desktop Window Manager API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dxcore.dll:DXCore:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
fwpuclnt.dll:FWP/IPsec User-Mode API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
gdi32full.dll:GDI Client DLL:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
glfw.dll:GLFW 3.4.0 DLL:3.4.0:GLFW
|
||||
icm32.dll:Microsoft Color Management Module (CMM):10.0.22621.586 (WinBuild.160101.0800):Microsoft Corporation
|
||||
iertutil.dll:Run time utility for Internet Explorer:11.00.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
inputhost.dll:InputHost:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
java.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
java.exe:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
jemalloc.dll
|
||||
jimage.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
jli.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
jna4873420272456839116.dll:JNA native library:6.1.4:Java(TM) Native Access (JNA)
|
||||
jvm.dll:OpenJDK 64-Bit server VM:17.0.1.0:Microsoft
|
||||
kernel.appcore.dll:AppModel API Host:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
lwjgl.dll
|
||||
lwjgl_opengl.dll
|
||||
lwjgl_stb.dll
|
||||
management.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
management_ext.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
msasn1.dll:ASN.1 Runtime APIs:10.0.22621.891 (WinBuild.160101.0800):Microsoft Corporation
|
||||
mscms.dll:Microsoft Color Matching System DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
msvcp140.dll:Microsoft® C Runtime Library:14.29.30133.0 built by: vcwrkspc:Microsoft Corporation
|
||||
msvcp_win.dll:Microsoft® C Runtime Library:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
msvcrt.dll:Windows NT CRT DLL:7.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
mswsock.dll:Microsoft Windows Sockets 2.0 Service Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
napinsp.dll:E-mail Naming Shim Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
ncrypt.dll:Windows NCrypt Router:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
net.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
netutils.dll:Net Win32 API Helpers DLL:10.0.22621.870 (WinBuild.160101.0800):Microsoft Corporation
|
||||
nio.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
nlansp_c.dll:NLA Namespace Service Provider DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
ntdll.dll:NT Layer DLL:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
ntmarta.dll:Windows NT MARTA provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
nvoglv64.dll:NVIDIA Compatible OpenGL ICD:31.0.15.2802:NVIDIA Corporation
|
||||
nvspcap64.dll:NVIDIA Game Proxy:3.26.0.160:NVIDIA Corporation
|
||||
opengl32.dll:OpenGL Client DLL:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
perfos.dll:Windows System Performance Objects DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
pfclient.dll:SysMain Client:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
pnrpnsp.dll:PNRP Name Space Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
powrprof.dll:Power Profile Helper DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
profapi.dll:User Profile Basic API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
rasadhlp.dll:Remote Access AutoDial Helper:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
rsaenh.dll:Microsoft Enhanced Cryptographic Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
sechost.dll:Host for SCM/SDDL/LSA Lookup APIs:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
shlwapi.dll:Shell Light-weight Utility Library:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
srvcli.dll:Server Service Client DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
sunmscapi.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
svml.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
textinputframework.dll:"TextInputFramework.DYNLINK":10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
ucrtbase.dll:Microsoft® C Runtime Library:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
urlmon.dll:OLE32 Extensions for Win32:11.00.22621.746 (WinBuild.160101.0800):Microsoft Corporation
|
||||
uxtheme.dll:Microsoft UxTheme Library:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
vcruntime140_1.dll:Microsoft® C Runtime Library:14.29.30133.0 built by: vcwrkspc:Microsoft Corporation
|
||||
verify.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
win32u.dll:Win32u:10.0.22621.1470 (WinBuild.160101.0800):Microsoft Corporation
|
||||
windows.storage.dll:Microsoft WinRT Storage API:10.0.22621.580 (WinBuild.160101.0800):Microsoft Corporation
|
||||
winrnr.dll:LDAP RnR Provider DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
wintypes.dll:Windows Base Types DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
wldp.dll:Windows Lockdown Policy:10.0.22621.885 (WinBuild.160101.0800):Microsoft Corporation
|
||||
wshbth.dll:Windows Sockets Helper DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
xinput1_4.dll:Microsoft Common Controller API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
zip.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
Stacktrace:
|
||||
at net.minecraft.client.main.Main.main(Main.java:198)
|
||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527)
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28)
|
||||
|
||||
-- System Details --
|
||||
Details:
|
||||
Minecraft Version: 1.19.4
|
||||
Minecraft Version ID: 1.19.4
|
||||
Operating System: Windows 11 (amd64) version 10.0
|
||||
Java Version: 17.0.1, Microsoft
|
||||
Java VM Version: OpenJDK 64-Bit Server VM (mixed mode), Microsoft
|
||||
Memory: 627025632 bytes (597 MiB) / 1577058304 bytes (1504 MiB) up to 6845104128 bytes (6528 MiB)
|
||||
CPUs: 12
|
||||
Processor Vendor: AuthenticAMD
|
||||
Processor Name: AMD Ryzen 5 3600 6-Core Processor
|
||||
Identifier: AuthenticAMD Family 23 Model 113 Stepping 0
|
||||
Microarchitecture: Zen 2
|
||||
Frequency (GHz): 3.60
|
||||
Number of physical packages: 1
|
||||
Number of physical CPUs: 6
|
||||
Number of logical CPUs: 12
|
||||
Graphics card #0 name: NVIDIA GeForce RTX 2060
|
||||
Graphics card #0 vendor: NVIDIA (0x10de)
|
||||
Graphics card #0 VRAM (MB): 4095.00
|
||||
Graphics card #0 deviceId: 0x1f08
|
||||
Graphics card #0 versionInfo: DriverVersion=31.0.15.2802
|
||||
Memory slot #0 capacity (MB): 8192.00
|
||||
Memory slot #0 clockSpeed (GHz): 3.00
|
||||
Memory slot #0 type: DDR4
|
||||
Memory slot #1 capacity (MB): 8192.00
|
||||
Memory slot #1 clockSpeed (GHz): 3.00
|
||||
Memory slot #1 type: DDR4
|
||||
Virtual memory max (MB): 29133.06
|
||||
Virtual memory used (MB): 20495.20
|
||||
Swap memory total (MB): 12800.00
|
||||
Swap memory used (MB): 833.95
|
||||
JVM Flags: 11 total; -Xmx6505m -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump
|
||||
Quilt Mods:
|
||||
| Index | Mod | ID | Version | Plugin | File(s) | Sub-Files | Sub-Files |
|
||||
|------:|---------------------------------------------------|-------------------------------------------------------|-----------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------------------------|
|
||||
| 123 | Advanced XRay (Fabric) | advanced-xray-fabric | 1.19.4-1.4.0-build.15 | quilted_fabric_loader | <mods>\advanced-xray-fabric-1.19.4-1.4.0-build.15.jar | | |
|
||||
| 45 | AppleSkin | appleskin | 2.4.3+mc1.19.4 | quilted_fabric_loader | <mods>\appleskin-fabric-mc1.19.4-2.4.3.jar | | |
|
||||
| 32 | Autofish | autofish | 0.9.8-SNAPSHOT | quilted_fabric_loader | <mods>\Autofish-0.9.8-fabric-mc1.19.3.jar | | |
|
||||
| 19 | Baritone | baritone | 1.5.3-830-g3e249073 | quilted_fabric_loader | <mods>\baritone-api-fabric-1.5.3-830-g3e249073.jar | | |
|
||||
| 8 | CaffeineConfig | caffeineconfig | 1.1.0+1.17 | quilted_fabric_loader | <mods>\sodium-extra-0.4.18+mc1.19.4-build.100.jar | /META-INF/jars/CaffeineConfig-1.1.0+1.17.jar | |
|
||||
| 39 | Cloth Config v9 | cloth-config | 10.0.95 | quilted_fabric_loader | <mods>\appleskin-fabric-mc1.19.4-2.4.3.jar | /META-INF/jars/cloth-config-fabric-10.0.95.jar | |
|
||||
| 33 | CrowdinTranslate | crowdin-translate | 1.4+1.19.3 | quilted_fabric_loader | <mods>\sodium-extra-0.4.18+mc1.19.4-build.100.jar | /META-INF/jars/crowdin-translate-1.4+1.19.3.jar | |
|
||||
| 29 | Dynamic FPS | dynamicfps | 2.2.0 | quilted_fabric_loader | <mods>\dynamic-fps-2.2.0.jar | | |
|
||||
| 26 | Enhanced Block Entities | enhancedblockentities | 0.8+1.19.3 | quilted_fabric_loader | <mods>\enhancedblockentities-0.8+1.19.3.jar | | |
|
||||
| 14 | EntityCulling-Fabric | entityculling | 1.6.2-mc1.19.4 | quilted_fabric_loader | <mods>\entityculling-fabric-1.6.2-mc1.19.4.jar | | |
|
||||
| 81 | Fabric Language Kotlin | fabric-language-kotlin | 1.9.3+kotlin.1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | | |
|
||||
| 28 | Farsight Mod | farsight | 1.19-3.4 | quilted_fabric_loader | <mods>\farsight-fabric-1.19-3.4.jar | | |
|
||||
| 105 | Inventory Profiles Next | inventoryprofilesnext | 1.9.5 | quilted_fabric_loader | <mods>\InventoryProfilesNext-fabric-1.19.4-1.9.5.jar | | |
|
||||
| 13 | Litematica | litematica | 0.14.2 | quilted_fabric_loader | <mods>\litematica-fabric-1.19.4-0.14.2.jar | | |
|
||||
| 103 | Lithium | lithium | 0.11.1 | quilted_fabric_loader | <mods>\lithium-fabric-mc1.19.4-0.11.1.jar | | |
|
||||
| 3 | MaLiLib | malilib | 0.15.3 | quilted_fabric_loader | <mods>\malilib-fabric-1.19.4-0.15.3.jar | | |
|
||||
| 126 | Minecraft | minecraft | 1.19.4 | quilt_loader | <game>\.cache\quilt_loader\remappedJars\minecraft-1.19.4-0.19.0-beta.11\client-intermediary.jar | | |
|
||||
| 20 | MiniHUD | minihud | 0.26.2 | quilted_fabric_loader | <mods>\minihud-fabric-1.19.4-0.26.2.jar | | |
|
||||
| 99 | MobHealthBar | mobhealthbar | 2.1.2 | quilted_fabric_loader | <mods>\mobhealthbar-fabric-1.19.3-2.1.2.jar | | |
|
||||
| 65 | More Culling | moreculling | 1.19.4-0.17.0 | quilted_fabric_loader | <mods>\moreculling-1.19.4-0.17.0.jar | | |
|
||||
| 5 | MoreChatHistory | morechathistory | 1.1.1 | quilted_fabric_loader | <mods>\morechathistory-1.19.1-1.1.1.jar | | |
|
||||
| 62 | OpenJDK 64-Bit Server VM | java | 17 | quilt_loader | <user>\AppData\Roaming\.minecraft\cache\java\java-runtime-beta\windows-x64\java-runtime-beta | | |
|
||||
| 133 | Quilt Advancement API | quilt_advancement | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/advancement-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 35 | Quilt Base API | quilt_base | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/qsl_base-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 92 | Quilt Biome API | quilt_biome | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/biome-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 102 | Quilt Block Content Registry API | quilt_block_content_registry | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/block_content_registry-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 71 | Quilt Block Entity API | quilt_block_entity | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/block_entity-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 75 | Quilt Block Extensions API | quilt_block_extensions | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/block_extensions-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 119 | Quilt Chat API | quilt_chat | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/chat-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 84 | Quilt Client Command API | quilt_client_command | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/client_command-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 111 | Quilt Command API | quilt_command | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/command-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 76 | Quilt Crash Info | quilt_crash_info | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/crash_info-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 86 | Quilt Dimension API | quilt_dimension | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/dimension-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 68 | Quilt Entity API | quilt_entity | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/entity-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 46 | Quilt Entity Events | quilt_entity_events | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/entity_events-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 52 | Quilt Entity Networking API | quilt_entity_networking | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/entity_networking-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 117 | Quilt Entity Rendering API | quilt_entity_rendering | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/entity_rendering-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 63 | Quilt Item Content Registry API | quilt_item_content_registry | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/item_content_registry-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 88 | Quilt Item Extensions API | quilt_item_extensions | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/item_extensions-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 122 | Quilt Item Setting API | quilt_item_setting | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/item_setting-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 51 | Quilt Lifecycle Events | quilt_lifecycle_events | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/lifecycle_events-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 125 | Quilt Loader | quilt_loader | 0.19.0-beta.11 | quilt_loader | <user>\OneDrive\Desktop\.minecraft\libraries\org\quiltmc\quilt-loader\0.19.0-beta.11\quilt-loader-0.19.0-beta.11.jar | | |
|
||||
| 97 | Quilt Multipart Entity API | quilt_entity_multipart | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/multipart-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 50 | Quilt Networking | quilt_networking | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/networking-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 40 | Quilt Point of Interest API | quilt_point_of_interest | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/point_of_interest-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 94 | Quilt Recipe API | quilt_recipe | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/recipe-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 89 | Quilt Registry API | quilt_registry | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/registry-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 60 | Quilt Registry Entry Attachment | quilt_registry_entry_attachment | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/registry_entry_attachment-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 48 | Quilt Resource Loader | quilt_resource_loader | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/resource_loader-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 90 | Quilt Screen API | quilt_screen | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/screen-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 82 | Quilt Status Effect API | quilt_status_effect | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/status_effect-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 96 | Quilt Surface Rule API | quilt_surface_rule | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/surface_rule-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 136 | Quilt Tags API | quilt_tags | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/tags-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 54 | Quilt Testing API | quilt_testing | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/testing-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 124 | Quilt Tooltip API | quilt_tooltip | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/tooltip-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 131 | Quilt Vehicle API | quilt_vehicle | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/vehicle-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 104 | Quilt Villager API | quilt_villager | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/villager-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 138 | Quilted Fabric API | quilted_fabric_api | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | | |
|
||||
| 42 | Quilted Fabric API Base | quilted_fabric_api_base | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-api-base-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 130 | Quilted Fabric API Lookup API (v1) | quilted_fabric_api_lookup_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-api-lookup-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 100 | Quilted Fabric Biome API (v1) | quilted_fabric_biome_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-biome-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 77 | Quilted Fabric Block API (v1) | quilted_fabric_block_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-block-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 101 | Quilted Fabric BlockRenderLayer Registration (v1) | quilted_fabric_blockrenderlayer_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-blockrenderlayer-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 55 | Quilted Fabric Client Tags | quilted_fabric_client_tags_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-client-tags-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 129 | Quilted Fabric Command API (v1) | quilted_fabric_command_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-command-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 121 | Quilted Fabric Command API (v2) | quilted_fabric_command_api_v2 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-command-api-v2-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 67 | Quilted Fabric Commands (v0) | quilted_fabric_commands_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-commands-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 112 | Quilted Fabric Containers (v0) | quilted_fabric_containers_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-containers-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 91 | Quilted Fabric Content Registries (v0) | quilted_fabric_content_registries_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-content-registries-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 47 | Quilted Fabric Convention Tags API (v1) | quilted_fabric_convention_tags_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-convention-tags-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 135 | Quilted Fabric Crash Report Info (v1) | quilted_fabric_crash_report_info_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-crash-report-info-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 37 | Quilted Fabric Data Generation API (v1) | quilted_fabric_data_generation_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-data-generation-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 74 | Quilted Fabric Dimensions API (v1) | quilted_fabric_dimensions_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-dimensions-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 79 | Quilted Fabric Entity Events (v1) | quilted_fabric_entity_events_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-entity-events-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 43 | Quilted Fabric Events Interaction (v0) | quilted_fabric_events_interaction_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-events-interaction-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 108 | Quilted Fabric Events Lifecycle (v0) | quilted_fabric_events_lifecycle_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-events-lifecycle-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 109 | Quilted Fabric Game Rule API (v1) | quilted_fabric_game_rule_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-game-rule-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 44 | Quilted Fabric Item API (v1) | quilted_fabric_item_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-item-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 127 | Quilted Fabric Item Group API (v1) | quilted_fabric_item_group_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-item-group-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 80 | Quilted Fabric Key Binding API (v1) | quilted_fabric_key_binding_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-key-binding-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 132 | Quilted Fabric Key Bindings (v0) | quilted_fabric_keybindings_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-keybindings-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 110 | Quilted Fabric Lifecycle Events (v1) | quilted_fabric_lifecycle_events_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-lifecycle-events-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 49 | Quilted Fabric Loot API (v2) | quilted_fabric_loot_api_v2 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-loot-api-v2-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 78 | Quilted Fabric Loot Tables (v1) | quilted_fabric_loot_tables_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-loot-tables-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 61 | Quilted Fabric Message API (v1) | quilted_fabric_message_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-message-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 64 | Quilted Fabric Mining Level API (v1) | quilted_fabric_mining_level_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-mining-level-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 36 | Quilted Fabric Models (v0) | quilted_fabric_models_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-models-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 134 | Quilted Fabric Networking (v0) | quilted_fabric_networking_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-networking-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 38 | Quilted Fabric Networking API (v1) | quilted_fabric_networking_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-networking-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 87 | Quilted Fabric Object Builder API (v1) | quilted_fabric_object_builder_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-object-builder-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 59 | Quilted Fabric Particles (v1) | quilted_fabric_particles_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-particles-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 69 | Quilted Fabric Recipe API (v1) | quilted_fabric_recipe_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-recipe-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 115 | Quilted Fabric Registry Sync (v0) | quilted_fabric_registry_sync_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-registry-sync-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 58 | Quilted Fabric Renderer - Indigo | quilted_fabric_renderer_indigo | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-renderer-indigo-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 70 | Quilted Fabric Renderer API (v1) | quilted_fabric_renderer_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-renderer-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 85 | Quilted Fabric Renderer Registries (v1) | quilted_fabric_renderer_registries_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-renderer-registries-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 118 | Quilted Fabric Rendering (v0) | quilted_fabric_rendering_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-rendering-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 57 | Quilted Fabric Rendering (v1) | quilted_fabric_rendering_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-rendering-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 116 | Quilted Fabric Rendering Data Attachment (v1) | quilted_fabric_rendering_data_attachment_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-rendering-data-attachment-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 114 | Quilted Fabric Rendering Fluids (v1) | quilted_fabric_rendering_fluids_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-rendering-fluids-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 56 | Quilted Fabric Resource Conditions API (v1) | quilted_fabric_resource_conditions_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-resource-conditions-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 95 | Quilted Fabric Resource Loader (v0) | quilted_fabric_resource_loader_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-resource-loader-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 66 | Quilted Fabric Screen API (v1) | quilted_fabric_screen_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-screen-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 113 | Quilted Fabric Screen Handler API (v1) | quilted_fabric_screen_handler_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-screen-handler-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 53 | Quilted Fabric Sound API (v1) | quilted_fabric_sound_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-sound-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 107 | Quilted Fabric Transfer API (v1) | quilted_fabric_transfer_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-transfer-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 83 | Quilted Fabric Transitive Access Wideners (v1) | quilted_fabric_transitive_access_wideners_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-transitive-access-wideners-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 25 | Reese's Sodium Options | reeses-sodium-options | 1.5.0+mc1.19.4-build.72 | quilted_fabric_loader | <mods>\reeses_sodium_options-1.5.0+mc1.19.4-build.72.jar | | |
|
||||
| 22 | Runtime Resource Pack | advanced_runtime_resource_pack | 0.6.7 | quilted_fabric_loader | <mods>\enhancedblockentities-0.8+1.19.3.jar | /META-INF/jars/arrp-0.6.7.jar | |
|
||||
| 73 | Settxi Gui (YetAnotherConfigLib) | settxi-gui-yacl | 2.10.6 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/settxi-gui-yacl-2.10.6-fabric-1.19.3.jar | |
|
||||
| 93 | Sodium | sodium | 0.4.10+build.24 | quilted_fabric_loader | <mods>\sodium-fabric-mc1.19.4-0.4.10+build.24.jar | | |
|
||||
| 128 | Sodium Extra | sodium-extra | 0.4.18+mc1.19.4-build.100 | quilted_fabric_loader | <mods>\sodium-extra-0.4.18+mc1.19.4-build.100.jar | | |
|
||||
| 137 | SpruceUI | spruceui | 4.1.0+1.19.3 | quilted_fabric_loader | <mods>\enhancedblockentities-0.8+1.19.3.jar | /META-INF/jars/spruceui-4.1.0+1.19.3.jar | |
|
||||
| 30 | Tweakeroo | tweakeroo | 0.16.0 | quilted_fabric_loader | <mods>\tweakeroo-fabric-1.19.4-0.16.0.jar | | |
|
||||
| 106 | Xaero's Minimap | xaerominimap | 23.3.3 | quilted_fabric_loader | <mods>\Xaeros_Minimap_23.3.3_Fabric_1.19.4.jar | | |
|
||||
| 98 | Xaero's World Map | xaeroworldmap | 1.29.5 | quilted_fabric_loader | <mods>\XaerosWorldMap_1.29.5_Fabric_1.19.4.jar | | |
|
||||
| 120 | YetAnotherConfigLib | yet-another-config-lib | 2.4.1 | quilted_fabric_loader | <mods>\YetAnotherConfigLib-2.4.1.jar | | |
|
||||
| 72 | Zoomify | zoomify | 2.9.2 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | | |
|
||||
| 18 | atomicfu-jvm | org_jetbrains_kotlinx_atomicfu-jvm | 0.20.1 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/atomicfu-jvm-0.20.1.jar | |
|
||||
| 15 | cloth-basic-math | cloth-basic-math | 0.6.1 | quilted_fabric_loader | <mods>\moreculling-1.19.4-0.17.0.jar | /META-INF/jars/cloth-config-fabric-9.0.94.jar | /META-INF/jars/basic-math-0.6.1.jar |
|
||||
| 34 | conditional mixin | conditional-mixin | 0.3.2 | quilted_fabric_loader | <mods>\moreculling-1.19.4-0.17.0.jar | /META-INF/jars/conditional-mixin-v0.3.2.jar | |
|
||||
| 27 | jctools-core | com_github_jctools_jctools_jctools-core | v4.0.1 | quilted_fabric_loader | <mods>\farsight-fabric-1.19-3.4.jar | /META-INF/jars/jctools-core-v4.0.1.jar | |
|
||||
| 24 | kotlin-reflect | org_jetbrains_kotlin_kotlin-reflect | 1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlin-reflect-1.8.20.jar | |
|
||||
| 6 | kotlin-stdlib | org_jetbrains_kotlin_kotlin-stdlib | 1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlin-stdlib-1.8.20.jar | |
|
||||
| 0 | kotlin-stdlib-jdk7 | org_jetbrains_kotlin_kotlin-stdlib-jdk7 | 1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlin-stdlib-jdk7-1.8.20.jar | |
|
||||
| 4 | kotlin-stdlib-jdk8 | org_jetbrains_kotlin_kotlin-stdlib-jdk8 | 1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlin-stdlib-jdk8-1.8.20.jar | |
|
||||
| 11 | kotlinx-coroutines-core-jvm | org_jetbrains_kotlinx_kotlinx-coroutines-core-jvm | 1.6.4 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-coroutines-core-jvm-1.6.4.jar | |
|
||||
| 16 | kotlinx-coroutines-jdk8 | org_jetbrains_kotlinx_kotlinx-coroutines-jdk8 | 1.6.4 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-coroutines-jdk8-1.6.4.jar | |
|
||||
| 7 | kotlinx-datetime-jvm | org_jetbrains_kotlinx_kotlinx-datetime-jvm | 0.4.0 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-datetime-jvm-0.4.0.jar | |
|
||||
| 31 | kotlinx-serialization-cbor-jvm | org_jetbrains_kotlinx_kotlinx-serialization-cbor-jvm | 1.5.0 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-serialization-cbor-jvm-1.5.0.jar | |
|
||||
| 1 | kotlinx-serialization-core-jvm | org_jetbrains_kotlinx_kotlinx-serialization-core-jvm | 1.5.0 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-serialization-core-jvm-1.5.0.jar | |
|
||||
| 21 | kotlinx-serialization-json-jvm | org_jetbrains_kotlinx_kotlinx-serialization-json-jvm | 1.5.0 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-serialization-json-jvm-1.5.0.jar | |
|
||||
| 17 | ktoml-core-jvm | com_akuleshov7_ktoml-core-jvm | 0.3.0 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/ktoml-core-jvm-0.3.0.jar | |
|
||||
| 41 | libIPN | libipn | 2.0.4 | quilted_fabric_loader | <mods>\libIPN-fabric-1.19.4-2.0.4.jar | | |
|
||||
| 9 | mixinextras | com_github_llamalad7_mixinextras | 0.1.1 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/mixinextras-0.1.1.jar | |
|
||||
| 23 | settxi-core | dev_isxander_settxi_settxi-core | 2.10.6 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/settxi-core-2.10.6.jar | |
|
||||
| 10 | settxi-gui | dev_isxander_settxi_settxi-gui | 2.10.6 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/settxi-gui-2.10.6.jar | |
|
||||
| 12 | settxi-kotlinx-serialization | dev_isxander_settxi_settxi-kotlinx-serialization | 2.10.6 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/settxi-kotlinx-serialization-2.10.6.jar | |
|
||||
| 2 | toml4j | com_moandjiezana_toml_toml4j | 0.7.2 | quilted_fabric_loader | <mods>\dynamic-fps-2.2.0.jar | /META-INF/jars/toml4j-0.7.2.jar | |
|
||||
|------:|---------------------------------------------------|-------------------------------------------------------|-----------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------------------------|
|
||||
|
||||
Quilted Fabric API: !! WARNING !! This instance is using Fabric API modules re-implemented by QSL. If the issue comes from Quilted Fabric API, DO NOT report to Fabric; report them to Quilt instead!
|
||||
Launched Version: 1.19
|
||||
Backend library: LWJGL version 3.3.1 build 7
|
||||
Backend API: NVIDIA GeForce RTX 2060/PCIe/SSE2 GL version 3.2.0 NVIDIA 528.02, NVIDIA Corporation
|
||||
Window size: <not initialized>
|
||||
GL Caps: Using framebuffer using OpenGL 3.2
|
||||
GL debug messages:
|
||||
Using VBOs: Yes
|
||||
Is Modded: Definitely; Client brand changed to 'quilt'
|
||||
Type: Client (map_client.txt)
|
||||
CPU: 12x AMD Ryzen 5 3600 6-Core Processor
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
Caused by: java.net.SocketTimeoutException: Read timed out
|
||||
at sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283) ~[?:?]
|
||||
at sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309) ~[?:?]
|
||||
at sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350) ~[?:?]
|
||||
at sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803) ~[?:?]
|
||||
at java.net.Socket$SocketInputStream.read(Socket.java:966) ~[?:?]
|
||||
at java.io.BufferedInputStream.fill(BufferedInputStream.java:244) ~[?:?]
|
||||
at java.io.BufferedInputStream.read1(BufferedInputStream.java:284) ~[?:?]
|
||||
at java.io.BufferedInputStream.read(BufferedInputStream.java:343) ~[?:?]
|
||||
at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:791) ~[?:?]
|
||||
at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:726) ~[?:?]
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1688) ~[?:?]
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589) ~[?:?]
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getHeaderField(HttpURLConnection.java:3221) ~[?:?]
|
||||
at java.net.URLConnection.getHeaderFieldLong(URLConnection.java:637) ~[?:?]
|
||||
at java.net.URLConnection.getContentLengthLong(URLConnection.java:509) ~[?:?]
|
||||
at xaero.map.misc.Internet.checkModVersion(Internet.java:51) ~[transformed-mod-xaeroworldmap.i0:0/:?]
|
||||
... 17 more
|
||||
[12:16:23] [Render thread/INFO]: Loading Xaero's Minimap - Stage 1/2
|
||||
[12:16:23] [Render thread/WARN]: Static binding violation: PRIVATE @Overwrite method method_23182 in #sodium:sodium.mixins.json:features.item.MixinItemRenderer from mod sodium cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded.
|
||||
[12:16:23] [Render thread/INFO]: [Quilt Command|Client] Networking support is enabled
|
||||
[12:16:23] [Render thread/INFO]: ARRP register - before vanilla
|
||||
[12:16:23] [Render thread/INFO]: ARRP register - after vanilla
|
||||
[12:16:23] [Render thread/INFO]: Backend library: LWJGL version 3.3.1 build 7
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - before vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - after vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - before vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - after vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - before vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - after vanilla
|
||||
[12:16:24] [Render thread/INFO]: ARRP register - before user
|
||||
[12:16:24] [Render thread/WARN]: Error loading class: net/minecraft/class_809$class_811 (java.lang.ClassNotFoundException: net/minecraft/class_809$class_811)
|
||||
[12:16:24] [Render thread/ERROR]: Mixin apply for mod enhancedblockentities failed #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> net.minecraft.class_756: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]
|
||||
org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:510) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:453) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[?:?]
|
||||
at net.minecraft.class_310.<init>(class_310.java:573) ~[transformed-mod-minecraft.i0:0/:?]
|
||||
at net.minecraft.client.main.Main.main(Main.java:198) ~[1.19.jar:?]
|
||||
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
|
||||
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
|
||||
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
|
||||
at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: net.minecraft.class_809$class_811
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:1002) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:962) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformLVT(MixinTargetContext.java:563) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:469) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
... 24 more
|
||||
[12:16:24] [Render thread/WARN]: Mixin transformation of net.minecraft.class_756 failed
|
||||
org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:392) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:453) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[?:?]
|
||||
at net.minecraft.class_310.<init>(class_310.java:573) ~[transformed-mod-minecraft.i0:0/:?]
|
||||
at net.minecraft.client.main.Main.main(Main.java:198) ~[1.19.jar:?]
|
||||
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
|
||||
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
|
||||
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
|
||||
at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28) ~[quilt-loader-0.19.0-beta.11.jar:?]
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.MixinApplyError: Mixin [#enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities] from phase [DEFAULT] in config [#enhancedblockentities:enhancedblockentities.mixins.json] FAILED during APPLY
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinError(MixinProcessor.java:638) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinApplyError(MixinProcessor.java:589) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:379) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
... 17 more
|
||||
Caused by: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:510) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
... 17 more
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: net.minecraft.class_809$class_811
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:1002) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:962) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformLVT(MixinTargetContext.java:563) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:469) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363) ~[sponge-mixin-0.12.4+mixin.0.8.5.jar:0.12.4+mixin.0.8.5]
|
||||
... 17 more
|
||||
---- Minecraft Crash Report ----
|
||||
// Hi. I'm Minecraft, and I'm a crashaholic.
|
||||
|
||||
Time: 2023-04-16 12:16:24
|
||||
Description: Initializing game
|
||||
|
||||
java.lang.RuntimeException: Mixin transformation of net.minecraft.class_756 failed
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:458)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228)
|
||||
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
|
||||
at net.minecraft.class_310.<init>(class_310.java:573)
|
||||
at net.minecraft.client.main.Main.main(Main.java:198)
|
||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527)
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28)
|
||||
Caused by: org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError: An unexpected critical error was encountered
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:392)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClass(MixinTransformer.java:234)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:202)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:453)
|
||||
... 14 more
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.MixinApplyError: Mixin [#enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities] from phase [DEFAULT] in config [#enhancedblockentities:enhancedblockentities.mixins.json] FAILED during APPLY
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinError(MixinProcessor.java:638)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.handleMixinApplyError(MixinProcessor.java:589)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:379)
|
||||
... 17 more
|
||||
Caused by: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Unexpecteded ClassMetadataNotFoundException whilst transforming the mixin class: [MAIN Applicator Phase -> #enhancedblockentities:enhancedblockentities.mixins.json:BuiltinModelItemRendererMixin from mod enhancedblockentities -> Apply Methods -> (Lnet/minecraft/class_1799;Lnet/minecraft/class_809$class_811;Lnet/minecraft/class_4587;Lnet/minecraft/class_4597;IILorg/spongepowered/asm/mixin/injection/callback/CallbackInfo;)V:handler$zea000$enhancedblockentities$enhanced_bes$renderBeds -> Transform LVT -> var=mode -> desc=Lnet/minecraft/class_809$class_811;]
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:510)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyNormalMethod(MixinApplicatorStandard.java:535)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMethods(MixinApplicatorStandard.java:521)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.applyMixin(MixinApplicatorStandard.java:388)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:327)
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.apply(TargetClassContext.java:421)
|
||||
at org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:403)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinProcessor.applyMixins(MixinProcessor.java:363)
|
||||
... 17 more
|
||||
Caused by: org.spongepowered.asm.mixin.throwables.ClassMetadataNotFoundException: net.minecraft.class_809$class_811
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:1002)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformSingleDescriptor(MixinTargetContext.java:962)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformLVT(MixinTargetContext.java:563)
|
||||
at org.spongepowered.asm.mixin.transformer.MixinTargetContext.transformMethod(MixinTargetContext.java:469)
|
||||
... 24 more
|
||||
|
||||
|
||||
A detailed walkthrough of the error, its code path and all known details is as follows:
|
||||
---------------------------------------------------------------------------------------
|
||||
|
||||
-- Head --
|
||||
Thread: Render thread
|
||||
Stacktrace:
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.getPostMixinClassByteArray(KnotClassDelegate.java:458)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.tryLoadClass(KnotClassDelegate.java:244)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClassOnly(KnotClassDelegate.java:164)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:150)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:228)
|
||||
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
|
||||
at net.minecraft.class_310.<init>(class_310.java:573)
|
||||
|
||||
-- Initialization --
|
||||
Details:
|
||||
Modules:
|
||||
ADVAPI32.dll:Advanced Windows 32 Base API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
COMCTL32.dll:User Experience Controls Library:6.10 (WinBuild.160101.0800):Microsoft Corporation
|
||||
CRYPT32.dll:Crypto API32:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
CRYPTBASE.dll:Base cryptographic API DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
CRYPTSP.dll:Cryptographic Service Provider API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
CoreMessaging.dll:Microsoft CoreMessaging Dll:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
DBGHELP.DLL:Windows Image Helper:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
DEVOBJ.dll:Device Information Set DLL:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
DNSAPI.dll:DNS Client API DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
GDI32.dll:GDI Client DLL:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
GLU32.dll:OpenGL Utility Library DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
IMM32.DLL:Multi-User Windows IMM32 API Client DLL:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
IPHLPAPI.DLL:IP Helper API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
KERNEL32.DLL:Windows NT BASE API Client DLL:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
KERNELBASE.dll:Windows NT BASE API Client DLL:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
MMDevApi.dll:MMDevice API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
MSCTF.dll:MSCTF Server DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
MpOav.dll:IOfficeAntiVirus Module:4.18.2303.8 (WinBuild.160101.0800):Microsoft Corporation
|
||||
NSI.dll:NSI User-mode interface DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
NTASN1.dll:Microsoft ASN.1 API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
OLEAUT32.dll:OLEAUT32.DLL:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
Ole32.dll:Microsoft OLE for Windows:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
OpenAL.dll:Main implementation library:1.21.1:
|
||||
PSAPI.DLL:Process Status Helper:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
Pdh.dll:Windows Performance Data Helper DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
RPCRT4.dll:Remote Procedure Call Runtime:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
SETUPAPI.dll:Windows Setup API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
SHCORE.dll:SHCORE:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
SHELL32.dll:Windows Shell Common Dll:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
UMPDC.dll:User Mode Power Dependency Coordinator:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
USER32.dll:Multi-User Windows USER API Client DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
USERENV.dll:Userenv:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
VCRUNTIME140.dll:Microsoft® C Runtime Library:14.29.30133.0 built by: vcwrkspc:Microsoft Corporation
|
||||
VERSION.dll:Version Checking and File Installation Libraries:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WINHTTP.dll:Windows HTTP Services:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WINMM.dll:MCI API DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WINSTA.dll:Winstation Library:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WINTRUST.dll:Microsoft Trust Verification APIs:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WS2_32.dll:Windows Socket 2.0 32-Bit DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WSOCK32.dll:Windows Socket 32-Bit DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
WTSAPI32.dll:Windows Remote Desktop Session Host Server SDK APIs:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
amsi.dll:Anti-Malware Scan Interface:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
bcrypt.dll:Windows Cryptographic Primitives Library:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
bcryptPrimitives.dll:Windows Cryptographic Primitives Library:10.0.22621.1175 (WinBuild.160101.0800):Microsoft Corporation
|
||||
cfgmgr32.dll:Configuration Manager DLL:10.0.22621.746 (WinBuild.160101.0800):Microsoft Corporation
|
||||
clbcatq.dll:COM+ Configuration Catalog:2001.12.10941.16384 (WinBuild.160101.0800):Microsoft Corporation
|
||||
combase.dll:Microsoft COM for Windows:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
cryptnet.dll:Crypto Network Related API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dbgcore.DLL:Windows Core Debugging Helpers:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dhcpcsvc.DLL:DHCP Client Service:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dhcpcsvc6.DLL:DHCPv6 Client:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dinput8.dll:Microsoft DirectInput:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
drvstore.dll:Driver Store API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dwmapi.dll:Microsoft Desktop Window Manager API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
dxcore.dll:DXCore:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
fwpuclnt.dll:FWP/IPsec User-Mode API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
gdi32full.dll:GDI Client DLL:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
glfw.dll:GLFW 3.4.0 DLL:3.4.0:GLFW
|
||||
icm32.dll:Microsoft Color Management Module (CMM):10.0.22621.586 (WinBuild.160101.0800):Microsoft Corporation
|
||||
iertutil.dll:Run time utility for Internet Explorer:11.00.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
inputhost.dll:InputHost:10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
java.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
java.exe:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
jemalloc.dll
|
||||
jimage.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
jli.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
jna4873420272456839116.dll:JNA native library:6.1.4:Java(TM) Native Access (JNA)
|
||||
jvm.dll:OpenJDK 64-Bit server VM:17.0.1.0:Microsoft
|
||||
kernel.appcore.dll:AppModel API Host:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
lwjgl.dll
|
||||
lwjgl_opengl.dll
|
||||
lwjgl_stb.dll
|
||||
management.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
management_ext.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
msasn1.dll:ASN.1 Runtime APIs:10.0.22621.891 (WinBuild.160101.0800):Microsoft Corporation
|
||||
mscms.dll:Microsoft Color Matching System DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
msvcp140.dll:Microsoft® C Runtime Library:14.29.30133.0 built by: vcwrkspc:Microsoft Corporation
|
||||
msvcp_win.dll:Microsoft® C Runtime Library:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
msvcrt.dll:Windows NT CRT DLL:7.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
mswsock.dll:Microsoft Windows Sockets 2.0 Service Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
napinsp.dll:E-mail Naming Shim Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
ncrypt.dll:Windows NCrypt Router:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
net.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
netutils.dll:Net Win32 API Helpers DLL:10.0.22621.870 (WinBuild.160101.0800):Microsoft Corporation
|
||||
nio.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
nlansp_c.dll:NLA Namespace Service Provider DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
ntdll.dll:NT Layer DLL:10.0.22621.1537 (WinBuild.160101.0800):Microsoft Corporation
|
||||
ntmarta.dll:Windows NT MARTA provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
nvoglv64.dll:NVIDIA Compatible OpenGL ICD:31.0.15.2802:NVIDIA Corporation
|
||||
nvspcap64.dll:NVIDIA Game Proxy:3.26.0.160:NVIDIA Corporation
|
||||
opengl32.dll:OpenGL Client DLL:10.0.22621.1465 (WinBuild.160101.0800):Microsoft Corporation
|
||||
perfos.dll:Windows System Performance Objects DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
pfclient.dll:SysMain Client:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
pnrpnsp.dll:PNRP Name Space Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
powrprof.dll:Power Profile Helper DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
profapi.dll:User Profile Basic API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
rasadhlp.dll:Remote Access AutoDial Helper:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
rsaenh.dll:Microsoft Enhanced Cryptographic Provider:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
sechost.dll:Host for SCM/SDDL/LSA Lookup APIs:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
shlwapi.dll:Shell Light-weight Utility Library:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
srvcli.dll:Server Service Client DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
sunmscapi.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
svml.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
textinputframework.dll:"TextInputFramework.DYNLINK":10.0.22621.1325 (WinBuild.160101.0800):Microsoft Corporation
|
||||
ucrtbase.dll:Microsoft® C Runtime Library:10.0.22621.436 (WinBuild.160101.0800):Microsoft Corporation
|
||||
urlmon.dll:OLE32 Extensions for Win32:11.00.22621.746 (WinBuild.160101.0800):Microsoft Corporation
|
||||
uxtheme.dll:Microsoft UxTheme Library:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
vcruntime140_1.dll:Microsoft® C Runtime Library:14.29.30133.0 built by: vcwrkspc:Microsoft Corporation
|
||||
verify.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
win32u.dll:Win32u:10.0.22621.1470 (WinBuild.160101.0800):Microsoft Corporation
|
||||
windows.storage.dll:Microsoft WinRT Storage API:10.0.22621.580 (WinBuild.160101.0800):Microsoft Corporation
|
||||
winrnr.dll:LDAP RnR Provider DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
wintypes.dll:Windows Base Types DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
wldp.dll:Windows Lockdown Policy:10.0.22621.885 (WinBuild.160101.0800):Microsoft Corporation
|
||||
wshbth.dll:Windows Sockets Helper DLL:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
xinput1_4.dll:Microsoft Common Controller API:10.0.22621.1 (WinBuild.160101.0800):Microsoft Corporation
|
||||
zip.dll:OpenJDK Platform binary:17.0.1.0:Microsoft
|
||||
Stacktrace:
|
||||
at net.minecraft.client.main.Main.main(Main.java:198)
|
||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
||||
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
|
||||
at org.quiltmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:527)
|
||||
at org.quiltmc.loader.impl.launch.knot.Knot.launch(Knot.java:82)
|
||||
at org.quiltmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:28)
|
||||
|
||||
-- System Details --
|
||||
Details:
|
||||
Minecraft Version: 1.19.4
|
||||
Minecraft Version ID: 1.19.4
|
||||
Operating System: Windows 11 (amd64) version 10.0
|
||||
Java Version: 17.0.1, Microsoft
|
||||
Java VM Version: OpenJDK 64-Bit Server VM (mixed mode), Microsoft
|
||||
Memory: 627025632 bytes (597 MiB) / 1577058304 bytes (1504 MiB) up to 6845104128 bytes (6528 MiB)
|
||||
CPUs: 12
|
||||
Processor Vendor: AuthenticAMD
|
||||
Processor Name: AMD Ryzen 5 3600 6-Core Processor
|
||||
Identifier: AuthenticAMD Family 23 Model 113 Stepping 0
|
||||
Microarchitecture: Zen 2
|
||||
Frequency (GHz): 3.60
|
||||
Number of physical packages: 1
|
||||
Number of physical CPUs: 6
|
||||
Number of logical CPUs: 12
|
||||
Graphics card #0 name: NVIDIA GeForce RTX 2060
|
||||
Graphics card #0 vendor: NVIDIA (0x10de)
|
||||
Graphics card #0 VRAM (MB): 4095.00
|
||||
Graphics card #0 deviceId: 0x1f08
|
||||
Graphics card #0 versionInfo: DriverVersion=31.0.15.2802
|
||||
Memory slot #0 capacity (MB): 8192.00
|
||||
Memory slot #0 clockSpeed (GHz): 3.00
|
||||
Memory slot #0 type: DDR4
|
||||
Memory slot #1 capacity (MB): 8192.00
|
||||
Memory slot #1 clockSpeed (GHz): 3.00
|
||||
Memory slot #1 type: DDR4
|
||||
Virtual memory max (MB): 29133.06
|
||||
Virtual memory used (MB): 20495.20
|
||||
Swap memory total (MB): 12800.00
|
||||
Swap memory used (MB): 833.95
|
||||
JVM Flags: 11 total; -Xmx6505m -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32m -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -XX:-DontCompileHugeMethods -XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump
|
||||
Quilt Mods:
|
||||
| Index | Mod | ID | Version | Plugin | File(s) | Sub-Files | Sub-Files |
|
||||
|------:|---------------------------------------------------|-------------------------------------------------------|-----------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------------------------|
|
||||
| 123 | Advanced XRay (Fabric) | advanced-xray-fabric | 1.19.4-1.4.0-build.15 | quilted_fabric_loader | <mods>\advanced-xray-fabric-1.19.4-1.4.0-build.15.jar | | |
|
||||
| 45 | AppleSkin | appleskin | 2.4.3+mc1.19.4 | quilted_fabric_loader | <mods>\appleskin-fabric-mc1.19.4-2.4.3.jar | | |
|
||||
| 32 | Autofish | autofish | 0.9.8-SNAPSHOT | quilted_fabric_loader | <mods>\Autofish-0.9.8-fabric-mc1.19.3.jar | | |
|
||||
| 19 | Baritone | baritone | 1.5.3-830-g3e249073 | quilted_fabric_loader | <mods>\baritone-api-fabric-1.5.3-830-g3e249073.jar | | |
|
||||
| 8 | CaffeineConfig | caffeineconfig | 1.1.0+1.17 | quilted_fabric_loader | <mods>\sodium-extra-0.4.18+mc1.19.4-build.100.jar | /META-INF/jars/CaffeineConfig-1.1.0+1.17.jar | |
|
||||
| 39 | Cloth Config v9 | cloth-config | 10.0.95 | quilted_fabric_loader | <mods>\appleskin-fabric-mc1.19.4-2.4.3.jar | /META-INF/jars/cloth-config-fabric-10.0.95.jar | |
|
||||
| 33 | CrowdinTranslate | crowdin-translate | 1.4+1.19.3 | quilted_fabric_loader | <mods>\sodium-extra-0.4.18+mc1.19.4-build.100.jar | /META-INF/jars/crowdin-translate-1.4+1.19.3.jar | |
|
||||
| 29 | Dynamic FPS | dynamicfps | 2.2.0 | quilted_fabric_loader | <mods>\dynamic-fps-2.2.0.jar | | |
|
||||
| 26 | Enhanced Block Entities | enhancedblockentities | 0.8+1.19.3 | quilted_fabric_loader | <mods>\enhancedblockentities-0.8+1.19.3.jar | | |
|
||||
| 14 | EntityCulling-Fabric | entityculling | 1.6.2-mc1.19.4 | quilted_fabric_loader | <mods>\entityculling-fabric-1.6.2-mc1.19.4.jar | | |
|
||||
| 81 | Fabric Language Kotlin | fabric-language-kotlin | 1.9.3+kotlin.1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | | |
|
||||
| 28 | Farsight Mod | farsight | 1.19-3.4 | quilted_fabric_loader | <mods>\farsight-fabric-1.19-3.4.jar | | |
|
||||
| 105 | Inventory Profiles Next | inventoryprofilesnext | 1.9.5 | quilted_fabric_loader | <mods>\InventoryProfilesNext-fabric-1.19.4-1.9.5.jar | | |
|
||||
| 13 | Litematica | litematica | 0.14.2 | quilted_fabric_loader | <mods>\litematica-fabric-1.19.4-0.14.2.jar | | |
|
||||
| 103 | Lithium | lithium | 0.11.1 | quilted_fabric_loader | <mods>\lithium-fabric-mc1.19.4-0.11.1.jar | | |
|
||||
| 3 | MaLiLib | malilib | 0.15.3 | quilted_fabric_loader | <mods>\malilib-fabric-1.19.4-0.15.3.jar | | |
|
||||
| 126 | Minecraft | minecraft | 1.19.4 | quilt_loader | <game>\.cache\quilt_loader\remappedJars\minecraft-1.19.4-0.19.0-beta.11\client-intermediary.jar | | |
|
||||
| 20 | MiniHUD | minihud | 0.26.2 | quilted_fabric_loader | <mods>\minihud-fabric-1.19.4-0.26.2.jar | | |
|
||||
| 99 | MobHealthBar | mobhealthbar | 2.1.2 | quilted_fabric_loader | <mods>\mobhealthbar-fabric-1.19.3-2.1.2.jar | | |
|
||||
| 65 | More Culling | moreculling | 1.19.4-0.17.0 | quilted_fabric_loader | <mods>\moreculling-1.19.4-0.17.0.jar | | |
|
||||
| 5 | MoreChatHistory | morechathistory | 1.1.1 | quilted_fabric_loader | <mods>\morechathistory-1.19.1-1.1.1.jar | | |
|
||||
| 62 | OpenJDK 64-Bit Server VM | java | 17 | quilt_loader | <user>\AppData\Roaming\.minecraft\cache\java\java-runtime-beta\windows-x64\java-runtime-beta | | |
|
||||
| 133 | Quilt Advancement API | quilt_advancement | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/advancement-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 35 | Quilt Base API | quilt_base | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/qsl_base-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 92 | Quilt Biome API | quilt_biome | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/biome-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 102 | Quilt Block Content Registry API | quilt_block_content_registry | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/block_content_registry-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 71 | Quilt Block Entity API | quilt_block_entity | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/block_entity-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 75 | Quilt Block Extensions API | quilt_block_extensions | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/block_extensions-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 119 | Quilt Chat API | quilt_chat | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/chat-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 84 | Quilt Client Command API | quilt_client_command | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/client_command-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 111 | Quilt Command API | quilt_command | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/command-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 76 | Quilt Crash Info | quilt_crash_info | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/crash_info-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 86 | Quilt Dimension API | quilt_dimension | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/dimension-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 68 | Quilt Entity API | quilt_entity | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/entity-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 46 | Quilt Entity Events | quilt_entity_events | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/entity_events-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 52 | Quilt Entity Networking API | quilt_entity_networking | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/entity_networking-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 117 | Quilt Entity Rendering API | quilt_entity_rendering | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/entity_rendering-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 63 | Quilt Item Content Registry API | quilt_item_content_registry | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/item_content_registry-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 88 | Quilt Item Extensions API | quilt_item_extensions | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/item_extensions-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 122 | Quilt Item Setting API | quilt_item_setting | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/item_setting-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 51 | Quilt Lifecycle Events | quilt_lifecycle_events | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/lifecycle_events-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 125 | Quilt Loader | quilt_loader | 0.19.0-beta.11 | quilt_loader | <user>\OneDrive\Desktop\.minecraft\libraries\org\quiltmc\quilt-loader\0.19.0-beta.11\quilt-loader-0.19.0-beta.11.jar | | |
|
||||
| 97 | Quilt Multipart Entity API | quilt_entity_multipart | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/multipart-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 50 | Quilt Networking | quilt_networking | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/networking-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 40 | Quilt Point of Interest API | quilt_point_of_interest | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/point_of_interest-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 94 | Quilt Recipe API | quilt_recipe | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/recipe-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 89 | Quilt Registry API | quilt_registry | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/registry-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 60 | Quilt Registry Entry Attachment | quilt_registry_entry_attachment | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/registry_entry_attachment-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 48 | Quilt Resource Loader | quilt_resource_loader | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/resource_loader-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 90 | Quilt Screen API | quilt_screen | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/screen-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 82 | Quilt Status Effect API | quilt_status_effect | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/status_effect-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 96 | Quilt Surface Rule API | quilt_surface_rule | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/surface_rule-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 136 | Quilt Tags API | quilt_tags | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/tags-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 54 | Quilt Testing API | quilt_testing | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/testing-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 124 | Quilt Tooltip API | quilt_tooltip | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/tooltip-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 131 | Quilt Vehicle API | quilt_vehicle | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/vehicle-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 104 | Quilt Villager API | quilt_villager | 5.0.0-beta.2+1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/villager-5.0.0-beta.2+1.19.4.jar | |
|
||||
| 138 | Quilted Fabric API | quilted_fabric_api | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | | |
|
||||
| 42 | Quilted Fabric API Base | quilted_fabric_api_base | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-api-base-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 130 | Quilted Fabric API Lookup API (v1) | quilted_fabric_api_lookup_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-api-lookup-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 100 | Quilted Fabric Biome API (v1) | quilted_fabric_biome_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-biome-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 77 | Quilted Fabric Block API (v1) | quilted_fabric_block_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-block-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 101 | Quilted Fabric BlockRenderLayer Registration (v1) | quilted_fabric_blockrenderlayer_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-blockrenderlayer-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 55 | Quilted Fabric Client Tags | quilted_fabric_client_tags_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-client-tags-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 129 | Quilted Fabric Command API (v1) | quilted_fabric_command_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-command-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 121 | Quilted Fabric Command API (v2) | quilted_fabric_command_api_v2 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-command-api-v2-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 67 | Quilted Fabric Commands (v0) | quilted_fabric_commands_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-commands-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 112 | Quilted Fabric Containers (v0) | quilted_fabric_containers_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-containers-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 91 | Quilted Fabric Content Registries (v0) | quilted_fabric_content_registries_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-content-registries-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 47 | Quilted Fabric Convention Tags API (v1) | quilted_fabric_convention_tags_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-convention-tags-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 135 | Quilted Fabric Crash Report Info (v1) | quilted_fabric_crash_report_info_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-crash-report-info-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 37 | Quilted Fabric Data Generation API (v1) | quilted_fabric_data_generation_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-data-generation-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 74 | Quilted Fabric Dimensions API (v1) | quilted_fabric_dimensions_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-dimensions-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 79 | Quilted Fabric Entity Events (v1) | quilted_fabric_entity_events_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-entity-events-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 43 | Quilted Fabric Events Interaction (v0) | quilted_fabric_events_interaction_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-events-interaction-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 108 | Quilted Fabric Events Lifecycle (v0) | quilted_fabric_events_lifecycle_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-events-lifecycle-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 109 | Quilted Fabric Game Rule API (v1) | quilted_fabric_game_rule_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-game-rule-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 44 | Quilted Fabric Item API (v1) | quilted_fabric_item_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-item-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 127 | Quilted Fabric Item Group API (v1) | quilted_fabric_item_group_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-item-group-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 80 | Quilted Fabric Key Binding API (v1) | quilted_fabric_key_binding_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-key-binding-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 132 | Quilted Fabric Key Bindings (v0) | quilted_fabric_keybindings_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-keybindings-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 110 | Quilted Fabric Lifecycle Events (v1) | quilted_fabric_lifecycle_events_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-lifecycle-events-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 49 | Quilted Fabric Loot API (v2) | quilted_fabric_loot_api_v2 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-loot-api-v2-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 78 | Quilted Fabric Loot Tables (v1) | quilted_fabric_loot_tables_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-loot-tables-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 61 | Quilted Fabric Message API (v1) | quilted_fabric_message_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-message-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 64 | Quilted Fabric Mining Level API (v1) | quilted_fabric_mining_level_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-mining-level-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 36 | Quilted Fabric Models (v0) | quilted_fabric_models_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-models-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 134 | Quilted Fabric Networking (v0) | quilted_fabric_networking_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-networking-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 38 | Quilted Fabric Networking API (v1) | quilted_fabric_networking_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-networking-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 87 | Quilted Fabric Object Builder API (v1) | quilted_fabric_object_builder_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-object-builder-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 59 | Quilted Fabric Particles (v1) | quilted_fabric_particles_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-particles-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 69 | Quilted Fabric Recipe API (v1) | quilted_fabric_recipe_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-recipe-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 115 | Quilted Fabric Registry Sync (v0) | quilted_fabric_registry_sync_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-registry-sync-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 58 | Quilted Fabric Renderer - Indigo | quilted_fabric_renderer_indigo | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-renderer-indigo-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 70 | Quilted Fabric Renderer API (v1) | quilted_fabric_renderer_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-renderer-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 85 | Quilted Fabric Renderer Registries (v1) | quilted_fabric_renderer_registries_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-renderer-registries-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 118 | Quilted Fabric Rendering (v0) | quilted_fabric_rendering_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-rendering-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 57 | Quilted Fabric Rendering (v1) | quilted_fabric_rendering_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-rendering-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 116 | Quilted Fabric Rendering Data Attachment (v1) | quilted_fabric_rendering_data_attachment_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-rendering-data-attachment-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 114 | Quilted Fabric Rendering Fluids (v1) | quilted_fabric_rendering_fluids_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-rendering-fluids-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 56 | Quilted Fabric Resource Conditions API (v1) | quilted_fabric_resource_conditions_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-resource-conditions-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 95 | Quilted Fabric Resource Loader (v0) | quilted_fabric_resource_loader_v0 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-resource-loader-v0-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 66 | Quilted Fabric Screen API (v1) | quilted_fabric_screen_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-screen-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 113 | Quilted Fabric Screen Handler API (v1) | quilted_fabric_screen_handler_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-screen-handler-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 53 | Quilted Fabric Sound API (v1) | quilted_fabric_sound_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-sound-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 107 | Quilted Fabric Transfer API (v1) | quilted_fabric_transfer_api_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-transfer-api-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 83 | Quilted Fabric Transitive Access Wideners (v1) | quilted_fabric_transitive_access_wideners_v1 | 6.0.0-beta.3+0.76.0-1.19.4 | quilt_loader | <mods>\qfapi-6.0.0-beta.3_qsl-5.0.0-beta.2_fapi-0.76.0_mc-1.19.4.jar | /META-INF/jars/fabric-transitive-access-wideners-v1-6.0.0-beta.3+0.76.0-1.19.4.jar | |
|
||||
| 25 | Reese's Sodium Options | reeses-sodium-options | 1.5.0+mc1.19.4-build.72 | quilted_fabric_loader | <mods>\reeses_sodium_options-1.5.0+mc1.19.4-build.72.jar | | |
|
||||
| 22 | Runtime Resource Pack | advanced_runtime_resource_pack | 0.6.7 | quilted_fabric_loader | <mods>\enhancedblockentities-0.8+1.19.3.jar | /META-INF/jars/arrp-0.6.7.jar | |
|
||||
| 73 | Settxi Gui (YetAnotherConfigLib) | settxi-gui-yacl | 2.10.6 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/settxi-gui-yacl-2.10.6-fabric-1.19.3.jar | |
|
||||
| 93 | Sodium | sodium | 0.4.10+build.24 | quilted_fabric_loader | <mods>\sodium-fabric-mc1.19.4-0.4.10+build.24.jar | | |
|
||||
| 128 | Sodium Extra | sodium-extra | 0.4.18+mc1.19.4-build.100 | quilted_fabric_loader | <mods>\sodium-extra-0.4.18+mc1.19.4-build.100.jar | | |
|
||||
| 137 | SpruceUI | spruceui | 4.1.0+1.19.3 | quilted_fabric_loader | <mods>\enhancedblockentities-0.8+1.19.3.jar | /META-INF/jars/spruceui-4.1.0+1.19.3.jar | |
|
||||
| 30 | Tweakeroo | tweakeroo | 0.16.0 | quilted_fabric_loader | <mods>\tweakeroo-fabric-1.19.4-0.16.0.jar | | |
|
||||
| 106 | Xaero's Minimap | xaerominimap | 23.3.3 | quilted_fabric_loader | <mods>\Xaeros_Minimap_23.3.3_Fabric_1.19.4.jar | | |
|
||||
| 98 | Xaero's World Map | xaeroworldmap | 1.29.5 | quilted_fabric_loader | <mods>\XaerosWorldMap_1.29.5_Fabric_1.19.4.jar | | |
|
||||
| 120 | YetAnotherConfigLib | yet-another-config-lib | 2.4.1 | quilted_fabric_loader | <mods>\YetAnotherConfigLib-2.4.1.jar | | |
|
||||
| 72 | Zoomify | zoomify | 2.9.2 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | | |
|
||||
| 18 | atomicfu-jvm | org_jetbrains_kotlinx_atomicfu-jvm | 0.20.1 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/atomicfu-jvm-0.20.1.jar | |
|
||||
| 15 | cloth-basic-math | cloth-basic-math | 0.6.1 | quilted_fabric_loader | <mods>\moreculling-1.19.4-0.17.0.jar | /META-INF/jars/cloth-config-fabric-9.0.94.jar | /META-INF/jars/basic-math-0.6.1.jar |
|
||||
| 34 | conditional mixin | conditional-mixin | 0.3.2 | quilted_fabric_loader | <mods>\moreculling-1.19.4-0.17.0.jar | /META-INF/jars/conditional-mixin-v0.3.2.jar | |
|
||||
| 27 | jctools-core | com_github_jctools_jctools_jctools-core | v4.0.1 | quilted_fabric_loader | <mods>\farsight-fabric-1.19-3.4.jar | /META-INF/jars/jctools-core-v4.0.1.jar | |
|
||||
| 24 | kotlin-reflect | org_jetbrains_kotlin_kotlin-reflect | 1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlin-reflect-1.8.20.jar | |
|
||||
| 6 | kotlin-stdlib | org_jetbrains_kotlin_kotlin-stdlib | 1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlin-stdlib-1.8.20.jar | |
|
||||
| 0 | kotlin-stdlib-jdk7 | org_jetbrains_kotlin_kotlin-stdlib-jdk7 | 1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlin-stdlib-jdk7-1.8.20.jar | |
|
||||
| 4 | kotlin-stdlib-jdk8 | org_jetbrains_kotlin_kotlin-stdlib-jdk8 | 1.8.20 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlin-stdlib-jdk8-1.8.20.jar | |
|
||||
| 11 | kotlinx-coroutines-core-jvm | org_jetbrains_kotlinx_kotlinx-coroutines-core-jvm | 1.6.4 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-coroutines-core-jvm-1.6.4.jar | |
|
||||
| 16 | kotlinx-coroutines-jdk8 | org_jetbrains_kotlinx_kotlinx-coroutines-jdk8 | 1.6.4 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-coroutines-jdk8-1.6.4.jar | |
|
||||
| 7 | kotlinx-datetime-jvm | org_jetbrains_kotlinx_kotlinx-datetime-jvm | 0.4.0 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-datetime-jvm-0.4.0.jar | |
|
||||
| 31 | kotlinx-serialization-cbor-jvm | org_jetbrains_kotlinx_kotlinx-serialization-cbor-jvm | 1.5.0 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-serialization-cbor-jvm-1.5.0.jar | |
|
||||
| 1 | kotlinx-serialization-core-jvm | org_jetbrains_kotlinx_kotlinx-serialization-core-jvm | 1.5.0 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-serialization-core-jvm-1.5.0.jar | |
|
||||
| 21 | kotlinx-serialization-json-jvm | org_jetbrains_kotlinx_kotlinx-serialization-json-jvm | 1.5.0 | quilted_fabric_loader | <mods>\fabric-language-kotlin-1.9.3+kotlin.1.8.20.jar | /META-INF/jars/kotlinx-serialization-json-jvm-1.5.0.jar | |
|
||||
| 17 | ktoml-core-jvm | com_akuleshov7_ktoml-core-jvm | 0.3.0 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/ktoml-core-jvm-0.3.0.jar | |
|
||||
| 41 | libIPN | libipn | 2.0.4 | quilted_fabric_loader | <mods>\libIPN-fabric-1.19.4-2.0.4.jar | | |
|
||||
| 9 | mixinextras | com_github_llamalad7_mixinextras | 0.1.1 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/mixinextras-0.1.1.jar | |
|
||||
| 23 | settxi-core | dev_isxander_settxi_settxi-core | 2.10.6 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/settxi-core-2.10.6.jar | |
|
||||
| 10 | settxi-gui | dev_isxander_settxi_settxi-gui | 2.10.6 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/settxi-gui-2.10.6.jar | |
|
||||
| 12 | settxi-kotlinx-serialization | dev_isxander_settxi_settxi-kotlinx-serialization | 2.10.6 | quilted_fabric_loader | <mods>\Zoomify-2.9.2.jar | /META-INF/jars/settxi-kotlinx-serialization-2.10.6.jar | |
|
||||
| 2 | toml4j | com_moandjiezana_toml_toml4j | 0.7.2 | quilted_fabric_loader | <mods>\dynamic-fps-2.2.0.jar | /META-INF/jars/toml4j-0.7.2.jar | |
|
||||
|------:|---------------------------------------------------|-------------------------------------------------------|-----------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------------------------|
|
||||
|
||||
Quilted Fabric API: !! WARNING !! This instance is using Fabric API modules re-implemented by QSL. If the issue comes from Quilted Fabric API, DO NOT report to Fabric; report them to Quilt instead!
|
||||
Launched Version: 1.19
|
||||
Backend library: LWJGL version 3.3.1 build 7
|
||||
Backend API: NVIDIA GeForce RTX 2060/PCIe/SSE2 GL version 3.2.0 NVIDIA 528.02, NVIDIA Corporation
|
||||
Window size: <not initialized>
|
||||
GL Caps: Using framebuffer using OpenGL 3.2
|
||||
GL debug messages:
|
||||
Using VBOs: Yes
|
||||
Is Modded: Definitely; Client brand changed to 'quilt'
|
||||
Type: Client (map_client.txt)
|
||||
CPU: 12x AMD Ryzen 5 3600 6-Core Processor
|
||||
#@!@# Game crashed! Crash report saved to: #@!@# C:\Users\arthu\OneDrive\Desktop\.minecraft\versions\1.19\crash-reports\crash-2023-04-16_12.16.24-client.txt
|
||||
@@ -1,69 +1,69 @@
|
||||
WARNING: Unknown module: cpw.mods.bootstraplauncher specified to --add-exports
|
||||
[authlib-injector] [INFO] Logging file: C:\Users\********\AppData\Roaming\.minecraft\versions\鎮犵劧浜虹敓1.0\authlib-injector.log
|
||||
[authlib-injector] [INFO] Version: 1.2.3
|
||||
[authlib-injector] [INFO] Authentication server: https://littleskin.cn/api/yggdrasil
|
||||
[authlib-injector] [INFO] Httpd is running on port 50187
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.patchy.BlockedServers] with [Constant URL Transformer]
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.authlib.HttpAuthenticationService] with [ConcatenateURL Workaround]
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilEnvironment] with [Constant URL Transformer]
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService] with [Texture Whitelist Transformer]
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.authlib.properties.Property] with [Yggdrasil Public Key Transformer]
|
||||
[16:59:07] [main/INFO]: ModLauncher running: args [--username, butie, --version, 鎮犵劧浜虹敓1.0, --gameDir, C:\Users\********\AppData\Roaming\.minecraft\versions\鎮犵劧浜虹敓1.0, --assetsDir, C:\Users\********\AppData\Roaming\.minecraft\assets, --assetIndex, 1.16, --uuid, 35bbf0e6417f47e78f536c90400c0c8b, --accessToken, ????????, --userType, Mojang, --versionType, PCL2, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.39, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550, --tweakClass, optifine.OptiFineForgeTweaker]
|
||||
[16:59:07] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 17.0.1 by Microsoft
|
||||
[16:59:07] [main/INFO]: OptiFineTransformationService.onLoad
|
||||
[16:59:07] [main/INFO]: OptiFine ZIP file: C:\Users\********\AppData\Roaming\.minecraft\libraries\optifine\OptiFine\1.16.5_HD_U_G8\OptiFine-1.16.5_HD_U_G8.jar
|
||||
[16:59:07] [main/INFO]: Target.PRE_CLASS is available
|
||||
[16:59:07] [main/INFO]: Added Lets Encrypt root certificates as additional trust
|
||||
[16:59:07] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/C:/Users/********/AppData/Roaming/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT
|
||||
[16:59:07] [main/INFO]: OptiFineTransformationService.initialize
|
||||
[16:59:09] [main/INFO]: OptiFineTransformationService.transformers
|
||||
[16:59:09] [main/INFO]: Targets: 311
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: java.lang.reflect.InvocationTargetException
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at java.base/java.lang.reflect.Method.invoke(Method.java:568)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at oolloo.jlw.Wrapper.main(Wrapper.java:51)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: Caused by: java.lang.ExceptionInInitializerError
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.internal.runtime.Context.compile(Context.java:1509)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.internal.runtime.Context.compileScript(Context.java:1449)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.internal.runtime.Context.compileScript(Context.java:759)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:528)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:517)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:395)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:146)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at java.scripting/javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:247)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.coremod.CoreMod.initialize(CoreMod.java:40)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.coremod.CoreModEngine.initialize(CoreModEngine.java:75)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.coremod.CoreModEngine.initializeCoreMods(CoreModEngine.java:69)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.coremod.CoreModProvider.getCoreModTransformers(CoreModProvider.java:17)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.fml.loading.FMLServiceProvider.transformers(FMLServiceProvider.java:142)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.TransformationServiceDecorator.gatherTransformers(TransformationServiceDecorator.java:74)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.TransformationServicesHandler.lambda$initialiseServiceTransformers$6(TransformationServicesHandler.java:101)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at java.base/java.util.HashMap$Values.forEach(HashMap.java:1065)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.TransformationServicesHandler.initialiseServiceTransformers(TransformationServicesHandler.java:101)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:64)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.Launcher.run(Launcher.java:76)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.Launcher.main(Launcher.java:66)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: ... 6 more
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: Caused by: java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: at org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.lambda$getDefineAnonymousClass$0(Context.java:335)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: at org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.getDefineAnonymousClass(Context.java:327)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: at org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.<clinit>(Context.java:317)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: ... 27 more
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: Caused by: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:976)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1117)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:3649)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at java.base/java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:2680)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.lambda$getDefineAnonymousClass$0(Context.java:329)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: ... 30 more
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: Caused by: java.lang.NoSuchMethodError: 'java.lang.Class sun.misc.Unsafe.defineAnonymousClass(java.lang.Class, byte[], java.lang.Object[])'
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: at java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: at java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1085)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1114)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: ... 33 more
|
||||
WARNING: Unknown module: cpw.mods.bootstraplauncher specified to --add-exports
|
||||
[authlib-injector] [INFO] Logging file: C:\Users\********\AppData\Roaming\.minecraft\versions\鎮犵劧浜虹敓1.0\authlib-injector.log
|
||||
[authlib-injector] [INFO] Version: 1.2.3
|
||||
[authlib-injector] [INFO] Authentication server: https://littleskin.cn/api/yggdrasil
|
||||
[authlib-injector] [INFO] Httpd is running on port 50187
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.patchy.BlockedServers] with [Constant URL Transformer]
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.authlib.HttpAuthenticationService] with [ConcatenateURL Workaround]
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilEnvironment] with [Constant URL Transformer]
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService] with [Texture Whitelist Transformer]
|
||||
[authlib-injector] [INFO] Transformed [com.mojang.authlib.properties.Property] with [Yggdrasil Public Key Transformer]
|
||||
[16:59:07] [main/INFO]: ModLauncher running: args [--username, butie, --version, 鎮犵劧浜虹敓1.0, --gameDir, C:\Users\********\AppData\Roaming\.minecraft\versions\鎮犵劧浜虹敓1.0, --assetsDir, C:\Users\********\AppData\Roaming\.minecraft\assets, --assetIndex, 1.16, --uuid, 35bbf0e6417f47e78f536c90400c0c8b, --accessToken, ????????, --userType, Mojang, --versionType, PCL2, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 36.2.39, --fml.mcVersion, 1.16.5, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20210115.111550, --tweakClass, optifine.OptiFineForgeTweaker]
|
||||
[16:59:07] [main/INFO]: ModLauncher 8.1.3+8.1.3+main-8.1.x.c94d18ec starting: java version 17.0.1 by Microsoft
|
||||
[16:59:07] [main/INFO]: OptiFineTransformationService.onLoad
|
||||
[16:59:07] [main/INFO]: OptiFine ZIP file: C:\Users\********\AppData\Roaming\.minecraft\libraries\optifine\OptiFine\1.16.5_HD_U_G8\OptiFine-1.16.5_HD_U_G8.jar
|
||||
[16:59:07] [main/INFO]: Target.PRE_CLASS is available
|
||||
[16:59:07] [main/INFO]: Added Lets Encrypt root certificates as additional trust
|
||||
[16:59:07] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.4 Source=file:/C:/Users/********/AppData/Roaming/.minecraft/libraries/org/spongepowered/mixin/0.8.4/mixin-0.8.4.jar Service=ModLauncher Env=CLIENT
|
||||
[16:59:07] [main/INFO]: OptiFineTransformationService.initialize
|
||||
[16:59:09] [main/INFO]: OptiFineTransformationService.transformers
|
||||
[16:59:09] [main/INFO]: Targets: 311
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: java.lang.reflect.InvocationTargetException
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at java.base/java.lang.reflect.Method.invoke(Method.java:568)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1077]: at oolloo.jlw.Wrapper.main(Wrapper.java:51)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: Caused by: java.lang.ExceptionInInitializerError
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.internal.runtime.Context.compile(Context.java:1509)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.internal.runtime.Context.compileScript(Context.java:1449)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.internal.runtime.Context.compileScript(Context.java:759)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:528)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.api.scripting.NashornScriptEngine.compileImpl(NashornScriptEngine.java:517)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:395)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at org.openjdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:146)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at java.scripting/javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:247)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.coremod.CoreMod.initialize(CoreMod.java:40)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.coremod.CoreModEngine.initialize(CoreModEngine.java:75)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.coremod.CoreModEngine.initializeCoreMods(CoreModEngine.java:69)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.coremod.CoreModProvider.getCoreModTransformers(CoreModProvider.java:17)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at net.minecraftforge.fml.loading.FMLServiceProvider.transformers(FMLServiceProvider.java:142)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.TransformationServiceDecorator.gatherTransformers(TransformationServiceDecorator.java:74)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.TransformationServicesHandler.lambda$initialiseServiceTransformers$6(TransformationServicesHandler.java:101)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at java.base/java.util.HashMap$Values.forEach(HashMap.java:1065)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.TransformationServicesHandler.initialiseServiceTransformers(TransformationServicesHandler.java:101)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:64)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.Launcher.run(Launcher.java:76)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: at cpw.mods.modlauncher.Launcher.main(Launcher.java:66)
|
||||
[16:59:10] [main/INFO]: [java.lang.ThreadGroup:uncaughtException:1086]: ... 6 more
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: Caused by: java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: at org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.lambda$getDefineAnonymousClass$0(Context.java:335)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: at org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.getDefineAnonymousClass(Context.java:327)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: at org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.<clinit>(Context.java:317)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:659]: ... 27 more
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: Caused by: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:976)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1117)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:3649)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at java.base/java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:2680)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: at org.openjdk.nashorn.internal.runtime.Context$AnonymousContextCodeInstaller.lambda$getDefineAnonymousClass$0(Context.java:329)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printStackTrace:682]: ... 30 more
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: Caused by: java.lang.NoSuchMethodError: 'java.lang.Class sun.misc.Unsafe.defineAnonymousClass(java.lang.Class, byte[], java.lang.Object[])'
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: at java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: at java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1085)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1114)
|
||||
[16:59:10] [main/INFO]: [java.lang.Throwable:printEnclosedStackTrace:724]: ... 33 more
|
||||
Exception in thread "main"
|
||||
@@ -15,4 +15,4 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
versionRoot=3.14
|
||||
versionRoot=3.15
|
||||
Vendored
+93
-93
@@ -1,93 +1,93 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
Reference in new issue
Block a user