diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index f16f0cb..9b42691 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -5,16 +5,18 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v2.1.0 + uses: actions/setup-java@v4 with: java-version: 17 distribution: zulu + - name: Grant execute permission for gradlew + run: chmod +x gradlew - name: Build with Gradle run: ./gradlew build - name: Upload build JARs - uses: actions/upload-artifact@v2.3.1 + uses: actions/upload-artifact@v4 with: name: build-jars path: build/libs/* diff --git a/src/api/java/io/github/gaming32/modloadingscreen/api/AvailableFeatures.java b/src/api/java/io/github/gaming32/modloadingscreen/api/AvailableFeatures.java index 580f0e4..8a8f6e0 100644 --- a/src/api/java/io/github/gaming32/modloadingscreen/api/AvailableFeatures.java +++ b/src/api/java/io/github/gaming32/modloadingscreen/api/AvailableFeatures.java @@ -3,58 +3,16 @@ import java.util.StringJoiner; public final class AvailableFeatures { - /** - * @since 1.0.3 - * @see LoadingScreenApi#getFinalEntrypoints - */ @SuppressWarnings("PointlessBitwiseExpression") public static final long FINAL_ENTRYPOINTS = 1L << 0; - - /** - * @since 1.0.3 - * @see LoadingScreenApi#isHeadless - */ public static final long HEADLESS_CHECK = 1L << 1; - - /** - * @since 1.0.3 - * @see LoadingScreenApi#isUsingIpc - */ @Deprecated public static final long IPC_CHECK = 1L << 2; - - /** - * @since 1.0.3 - * @see LoadingScreenApi#getActiveProgressBars - * @see LoadingScreenApi#getProgress - */ public static final long GET_PROGRESS = 1L << 3; - - /** - * @since 1.0.3 - * @see LoadingScreenApi#isOpen - */ public static final long OPEN_CHECK = 1L << 4; - - /** - * @since 1.0.4 - * @see LoadingScreenApi#getCustomProgressBar - * @see CustomProgressBar - */ public static final long CUSTOM_PROGRESS_BARS = 1L << 5; - /** - * All the features that should be available on version 1.0.3. - * - * @since 1.0.3 - */ public static final long V1_0_3 = FINAL_ENTRYPOINTS | HEADLESS_CHECK | IPC_CHECK | GET_PROGRESS | OPEN_CHECK; - - /** - * All the features that should be available on version 1.0.4. - * - * @since 1.0.4 - */ public static final long V1_0_4 = V1_0_3 | CUSTOM_PROGRESS_BARS; private static final long MIN_FEATURE = FINAL_ENTRYPOINTS; @@ -63,37 +21,39 @@ public final class AvailableFeatures { private AvailableFeatures() { } + public static String getTranslationKey(long feature) { + switch ((int) feature) { + case (int) FINAL_ENTRYPOINTS: + return "modloadingscreen.feature.final_entrypoints"; + case (int) HEADLESS_CHECK: + return "modloadingscreen.feature.headless_check"; + case (int) IPC_CHECK: + return "modloadingscreen.feature.ipc_check"; + case (int) GET_PROGRESS: + return "modloadingscreen.feature.get_progress"; + case (int) OPEN_CHECK: + return "modloadingscreen.feature.open_check"; + case (int) CUSTOM_PROGRESS_BARS: + return "modloadingscreen.feature.custom_progress_bars"; + default: + return "modloadingscreen.feature.unknown"; + } + } + public static String toString(long features) { if (Long.bitCount(features) <= 1L) { - switch ((int)features) { - case (int)FINAL_ENTRYPOINTS: - return "FINAL_ENTRYPOINTS"; - case (int)HEADLESS_CHECK: - return "HEADLESS_CHECK"; - case (int)IPC_CHECK: - return "IPC_CHECK"; - case (int)GET_PROGRESS: - return "GET_PROGRESS"; - case (int)OPEN_CHECK: - return "OPEN_CHECK"; - case (int)CUSTOM_PROGRESS_BARS: - return "CUSTOM_PROGRESS_BARS"; - } - return ""; + return getTranslationKey(features); } final StringJoiner sj = new StringJoiner(", "); for (long feature = MIN_FEATURE; feature <= MAX_FEATURE; feature <<= 1) { if (hasFeatures(features, feature)) { - sj.add(toString(feature)); + sj.add(getTranslationKey(feature)); } } return sj.toString(); } - /** - * @see LoadingScreenApi#hasFeatures - */ public static boolean hasFeatures(long supportedFeatures, long requestedFeatures) { return (supportedFeatures & requestedFeatures) != 0; } diff --git a/src/api/java/io/github/gaming32/modloadingscreen/api/CustomProgressBar.java b/src/api/java/io/github/gaming32/modloadingscreen/api/CustomProgressBar.java index 7438595..60fad5c 100644 --- a/src/api/java/io/github/gaming32/modloadingscreen/api/CustomProgressBar.java +++ b/src/api/java/io/github/gaming32/modloadingscreen/api/CustomProgressBar.java @@ -3,26 +3,23 @@ import java.io.Closeable; import java.util.Objects; -/** - * A reference to a custom progress bar. Recommended to be used with try-with-resources. - * - * @see LoadingScreenApi#getCustomProgressBar - */ public final class CustomProgressBar implements Closeable { private final String id; private final boolean isReal; private boolean closed; - private String title; + private String titleKey; + private String[] titleArgs; private int progress; private int minimum; private int maximum; private boolean indeterminate; - CustomProgressBar(String id, boolean isReal, String title, int maximum) { + CustomProgressBar(String id, boolean isReal, String titleKey, int maximum, String... titleArgs) { this.id = id; this.isReal = isReal; - this.title = title; + this.titleKey = titleKey; + this.titleArgs = titleArgs != null ? titleArgs : new String[0]; this.maximum = maximum; } @@ -40,9 +37,6 @@ private void checkClosed() { } } - /** - * Close the progress bar and remove it from the loading screen. - */ @Override public void close() { if (LoadingScreenApi.CUSTOM_PROGRESS_BARS.remove(id) == this) { @@ -50,10 +44,6 @@ public void close() { } } - /** - * Sets the progress of the progress bar. The new progress will be clamped to the {@code [minimum, maximum]} range. - * @param progress The new progress. - */ public void setProgress(int progress) { progress = Math.min(maximum, Math.max(minimum, progress)); checkClosed(); @@ -62,36 +52,19 @@ public void setProgress(int progress) { LoadingScreenApi.customProgressBarOp(id, "progress", Integer.toString(progress)); } - /** - * Gets the current progress of the progress bar. - */ public int getProgress() { return progress; } - /** - * Steps the progress bar forward one step. - * - * @return The new progress of the progress bar, which may be clamped into range. - */ public int step() { return step(1); } - /** - * Steps the progress bar forward {@code n} steps. - * - * @return The new progress of the progress bar, which may be clamped into range. - */ public int step(int n) { setProgress(progress + n); return progress; } - /** - * Sets the maximum value of the progress bar. - * @throws IllegalArgumentException If {@code maximum < minimum} - */ public void setMaximum(int maximum) { if (maximum < minimum) { throw new IllegalArgumentException("maximum may not be less than minimum"); @@ -102,18 +75,10 @@ public void setMaximum(int maximum) { LoadingScreenApi.customProgressBarOp(id, "maximum", Integer.toString(maximum)); } - /** - * Gets the maximum value of the progress bar. - */ public int getMaximum() { return maximum; } - - /** - * Sets the minimum value of the progress bar. - * @throws IllegalArgumentException If {@code minimum > maximum} - */ public void setMinimum(int minimum) { if (minimum > maximum) { throw new IllegalArgumentException("minimum may not be greater than maximum"); @@ -124,36 +89,31 @@ public void setMinimum(int minimum) { LoadingScreenApi.customProgressBarOp(id, "minimum", Integer.toString(minimum)); } - - /** - * Gets the minimum value of the progress bar. - */ public int getMinimum() { return minimum; } - /** - * Sets the display text of the progress bar. - */ - public void setTitle(String title) { - Objects.requireNonNull(title, "title"); + public void setTitle(String titleKey, String... titleArgs) { + Objects.requireNonNull(titleKey, "titleKey"); checkClosed(); - if (title.equals(this.title)) return; - this.title = title; - LoadingScreenApi.customProgressBarOp(id, "title", title); + this.titleKey = titleKey; + this.titleArgs = titleArgs != null ? titleArgs : new String[0]; + String[] opArgs = new String[this.titleArgs.length + 3]; + opArgs[0] = id; + opArgs[1] = "title"; + opArgs[2] = titleKey; + System.arraycopy(this.titleArgs, 0, opArgs, 3, this.titleArgs.length); + LoadingScreenApi.customProgressBarOp(opArgs); + } + + public String getTitleKey() { + return titleKey; } - /** - * Gets the display text of the progress bar. - */ - public String getTitle() { - return title; + public String[] getTitleArgs() { + return titleArgs.clone(); } - /** - * Sets whether the progress bar is indeterminate. If the progress bar is indeterminate, {@link #setProgress} will - * still take effect, but it won't be visible until the progress bar is determinate. - */ public void setIndeterminate(boolean indeterminate) { checkClosed(); if (indeterminate == this.indeterminate) return; @@ -161,17 +121,10 @@ public void setIndeterminate(boolean indeterminate) { LoadingScreenApi.customProgressBarOp(id, "indeterminate", Boolean.toString(indeterminate)); } - /** - * Returns whether the progress bar is currently indeterminate. - */ public boolean isIndeterminate() { return indeterminate; } - /** - * Returns whether the progress bar is a real progress bar that can show up. This returns {@code true} if a version - * of Mod Loading Screen that supports custom progress bars is installed. - */ public boolean isReal() { return isReal; } diff --git a/src/api/java/io/github/gaming32/modloadingscreen/api/LoadingScreenApi.java b/src/api/java/io/github/gaming32/modloadingscreen/api/LoadingScreenApi.java index b82a001..2a3914a 100644 --- a/src/api/java/io/github/gaming32/modloadingscreen/api/LoadingScreenApi.java +++ b/src/api/java/io/github/gaming32/modloadingscreen/api/LoadingScreenApi.java @@ -85,7 +85,7 @@ public final class LoadingScreenApi { try { createCustomProgressBar = lookup.findStatic( alsClass, "createCustomProgressBar", - MethodType.methodType(void.class, String.class, String.class, int.class) + MethodType.methodType(void.class, String.class, String.class, int.class, String[].class) ); customProgressBarOp = lookup.findStatic( alsClass, "customProgressBarOp", @@ -93,8 +93,20 @@ public final class LoadingScreenApi { ); features |= AvailableFeatures.CUSTOM_PROGRESS_BARS; } catch (Exception e) { - createCustomProgressBar = null; - loadFailed(">=1.0.4", AvailableFeatures.CUSTOM_PROGRESS_BARS, e); + try { + createCustomProgressBar = lookup.findStatic( + alsClass, "createCustomProgressBar", + MethodType.methodType(void.class, String.class, String.class, int.class) + ); + customProgressBarOp = lookup.findStatic( + alsClass, "customProgressBarOp", + MethodType.methodType(void.class, String[].class) + ); + features |= AvailableFeatures.CUSTOM_PROGRESS_BARS; + } catch (Exception ex) { + createCustomProgressBar = null; + loadFailed(">=1.0.4", AvailableFeatures.CUSTOM_PROGRESS_BARS, ex); + } } System.out.println("[ModLoadingScreen] API loaded with features: " + AvailableFeatures.toString(features)); @@ -104,8 +116,6 @@ public final class LoadingScreenApi { System.err.println(message); e.printStackTrace(); } else { - // This API could be called with Mod Loading Screen simply absent, in which case this is *not* an error - // condition System.out.println(message); System.out.println("[ModLoadingScreen] This is not an error, because Mod Loading Screen is not installed."); } @@ -159,40 +169,14 @@ private static void loadFailed(String mlsVersionRequired, long feature, Exceptio private LoadingScreenApi() { } - /** - * Returns the features of the API that are available to use, as a bit mask of flags from - * {@link AvailableFeatures}. Any features not available will default to no-op fallback implementations. - * - * @see AvailableFeatures - */ public static long getFeatures() { return FEATURES; } - /** - * Returns whether the bitmask of features is supported. - * - * @param requestedFeatures A bitmask of feature flags from {@link AvailableFeatures} - * - * @return Whether all the requested features are available - * - * @see AvailableFeatures - * @see AvailableFeatures#hasFeatures - */ public static boolean hasFeatures(long requestedFeatures) { return AvailableFeatures.hasFeatures(FEATURES, requestedFeatures); } - /** - * Invokes an entrypoint with a clean API. If Mod Loading Screen is available, its progress will show up in the - * loading screen. - * - * @throws RuntimeException If any entrypoints threw an exception - * - * @apiNote This feature is always available, regardless of the return value of {@link #getFeatures}. - * Calling this without Mod Loading Screen will work always, but just won't show up in the (non-existent) loading - * screen. - */ public static void invokeEntrypoint(String name, Class type, Consumer invoker) throws RuntimeException { if (FABRIC_0_14_23_INVOKE_ENTRYPOINTS != null) { try { @@ -205,9 +189,6 @@ public static void invokeEntrypoint(String name, Class type, Consumer clazz = e.getClass(); while (clazz != null) { if (clazz.getName().equals("org.quiltmc.loader.api.entrypoint.EntrypointException")) { @@ -224,21 +205,6 @@ public static void invokeEntrypoint(String name, Class type, Consumer getFinalEntrypoints() { if (FINAL_ENTRYPOINTS == null) { @@ -251,18 +217,6 @@ public static Set getFinalEntrypoints() { } } - /** - * Returns whether the Mod Loading Screen (and the game in general) is running in a headless environment. If - * {@link #getFeatures} doesn't include {@link AvailableFeatures#HEADLESS_CHECK}, this will return the value of - * {@link GraphicsEnvironment#isHeadless}. - * - * @return {@code true} if running in a headless environment. - * - * @see GraphicsEnvironment#isHeadless - * @see AvailableFeatures#HEADLESS_CHECK - * - * @since 1.0.3 - */ public static boolean isHeadless() { if (IS_HEADLESS == null) { return GraphicsEnvironment.isHeadless(); @@ -274,21 +228,6 @@ public static boolean isHeadless() { } } - /** - * Returns whether IPC is being used for the loading screen, and hasn't been disabled with - * {@code mod-loading-screen.disableIpc}. If {@link #isHeadless} returns {@code true}, this will return - * {@code false}. If {@link #getFeatures} doesn't include {@link AvailableFeatures#IPC_CHECK}, this will return - * {@code false}. - * - * @return {@code true} IPC is being used for the loading screen. - * - * @deprecated Non-IPC may be removed in the future, at which point this will always return the opposite of - * {@link #isHeadless} - * - * @see AvailableFeatures#IPC_CHECK - * - * @since 1.0.3 - */ @Deprecated public static boolean isUsingIpc() { if (ENABLE_IPC == null) { @@ -313,54 +252,17 @@ private static Map getAllProgress() { } } - /** - * Returns an {@link Set} of progress bar names. This will be updated dynamically when bars are updated. If - * {@link #getFeatures} doesn't include {@link AvailableFeatures#GET_PROGRESS}, this will return an empty set. - * - * @apiNote Custom progress bars start with {@code "custom:"}, followed by their ID. If {@link #getFeatures} - * includes {@link AvailableFeatures#CUSTOM_PROGRESS_BARS}, entrypoint progress bars start with - * {@code "entrypoint:"} - * - * @see AvailableFeatures#GET_PROGRESS - * - * @since 1.0.3 - */ @UnmodifiableView public static Set getActiveProgressBars() { final Set bars = getAllProgress().keySet(); return bars == Collections.EMPTY_SET ? bars : Collections.unmodifiableSet(bars); } - /** - * Returns the current progress of a progress bar, or {@code null} if there is no such progress bar. If - * {@link #getFeatures} doesn't include {@link AvailableFeatures#GET_PROGRESS}, this will always return - * {@code null}. - * - * @param barName The name of the progress bar. In the case of entrypoints, this is the name of the entrypoint. - * - * @apiNote Custom progress bars start with {@code "custom:"}, followed by their ID. If {@link #getFeatures} - * includes {@link AvailableFeatures#CUSTOM_PROGRESS_BARS}, entrypoint progress bars start with - * {@code "entrypoint:"} - * - * @see AvailableFeatures#GET_PROGRESS - * - * @since 1.0.3 - */ @Nullable public static Integer getProgress(@NotNull String barName) { return getAllProgress().get(barName); } - /** - * Returns whether a loading screen is currently active. If {@link #getFeatures} doesn't include - * {@link AvailableFeatures#OPEN_CHECK}, this will always return {@code false}. - * - * @return {@code true} if there is a loading screen open. - * - * @see AvailableFeatures#OPEN_CHECK - * - * @since 1.0.3 - */ public static boolean isOpen() { if (IS_OPEN == null) { return false; @@ -372,35 +274,32 @@ public static boolean isOpen() { } } - /** - * Creates a custom progress bar. - * @param title The title of the progress bar. This is the full string to display. - * @param max The maximum value of the progress bar. It will not be removed automatically when this is - * reached. - * @return The {@link CustomProgressBar} reference to the progress bar. - */ - public static CustomProgressBar getCustomProgressBar(@NotNull String id, @NotNull String title, int max) { + public static CustomProgressBar getCustomProgressBar(@NotNull String id, @NotNull String titleKey, int max, String... titleArgs) { CustomProgressBar bar = CUSTOM_PROGRESS_BARS.get(id); if (bar == null) { - CUSTOM_PROGRESS_BARS.put(id, bar = createCustomProgressBar(id, title, max)); + CUSTOM_PROGRESS_BARS.put(id, bar = createCustomProgressBar(id, titleKey, max, titleArgs)); } - bar.setTitle(title); + bar.setTitle(titleKey, titleArgs); bar.setMaximum(max); return bar; } - private static CustomProgressBar createCustomProgressBar(String id, String title, int max) { + private static CustomProgressBar createCustomProgressBar(String id, String titleKey, int max, String... titleArgs) { Objects.requireNonNull(id, "id"); - Objects.requireNonNull(title, "title"); + Objects.requireNonNull(titleKey, "titleKey"); if (CREATE_CUSTOM_PROGRESS_BAR == null) { - return new CustomProgressBar(id, false, title, max); + return new CustomProgressBar(id, false, titleKey, max, titleArgs); } try { - CREATE_CUSTOM_PROGRESS_BAR.invokeExact(id, title, max); + if (CREATE_CUSTOM_PROGRESS_BAR.type().parameterCount() == 4) { + CREATE_CUSTOM_PROGRESS_BAR.invokeExact(id, titleKey, max, titleArgs != null ? titleArgs : new String[0]); + } else { + CREATE_CUSTOM_PROGRESS_BAR.invokeExact(id, titleKey, max); + } } catch (Throwable t) { rethrow(t); } - return new CustomProgressBar(id, true, title, max); + return new CustomProgressBar(id, true, titleKey, max, titleArgs); } static void customProgressBarOp(String... args) { diff --git a/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java b/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java index 8c5a56b..e85ce60 100644 --- a/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java +++ b/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java @@ -13,27 +13,35 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.GraphicsEnvironment; +import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static io.github.gaming32.modloadingscreen.MlsTransformers.ACTUAL_LOADING_SCREEN; @@ -48,7 +56,6 @@ public class ActualLoadingScreen { !IS_IPC_CLIENT && !IS_HEADLESS && !Boolean.getBoolean("mod-loading-screen.disableIpc"); public static final String[] SUPPORTED_BACKGROUND_FORMATS = {"gif", "jpeg", "png", "xbm"}; - // Unlike progressBars, this is populated on both the IPC client and IPC server, allowing it to be used from the API public static final Map progress = new LinkedHashMap<>(); private static final Map progressBars = new LinkedHashMap<>(); private static JFrame dialog; @@ -59,12 +66,67 @@ public class ActualLoadingScreen { private static Thread memoryThread; private static boolean titleSet; - static boolean runningOnQuilt; // Accessed from EarlyLoadingAgent + static boolean runningOnQuilt; + private static String gameVersion; private static Path configDir; private static boolean enableMemoryDisplay = true; + private static final Map TRANSLATIONS = new HashMap<>(); + + public static void loadTranslations() { + TRANSLATIONS.clear(); + loadLangFile("en_us"); + final String locale = Locale.getDefault().toString().toLowerCase(Locale.ROOT); + if (!locale.equals("en_us")) { + loadLangFile(locale); + } + } + + private static void loadLangFile(String langCode) { + final InputStream is = ClassLoader.getSystemResourceAsStream("assets/mod-loading-screen/lang/" + langCode + ".json"); + if (is == null) return; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + final Pattern pattern = Pattern.compile("^\\s*\"([^\"]+)\"\\s*:\\s*\"(.*)\",?\\s*$"); + String line; + while ((line = reader.readLine()) != null) { + final Matcher matcher = pattern.matcher(line); + if (matcher.matches()) { + String val = matcher.group(2).replace("\\\"", "\"").replace("\\\\", "\\").replace("\\n", "\n"); + TRANSLATIONS.put(matcher.group(1), val); + } + } + } catch (Exception ignored) { + } + } + + public static String translate(String key, Object... args) { + String pattern = TRANSLATIONS.getOrDefault(key, key); + if (args == null || args.length == 0) { + return pattern; + } + try { + return String.format(pattern, args); + } catch (Exception e) { + try { + return MessageFormat.format(pattern, args); + } catch (Exception ex) { + return pattern; + } + } + } + + public static String getLoaderName() { + return runningOnQuilt ? translate("modloadingscreen.loader.quilt") : translate("modloadingscreen.loader.fabric"); + } + + private static String getLoaderIconName() { + return runningOnQuilt ? "icon-quilt.png" : "icon-fabric.png"; + } + public static void startLoadingScreen(boolean fabricReady) { + loadTranslations(); + final Path gameDir = fabricReady ? FabricLoader.getInstance().getGameDir() : Paths.get(".").toAbsolutePath(); final Path runDir = gameDir.resolve(".cache/mod-loading-screen"); @@ -89,14 +151,18 @@ public static void startLoadingScreen(boolean fabricReady) { if (IS_IPC_CLIENT) { runningOnQuilt = Boolean.getBoolean("mlsipc.quilt"); + gameVersion = System.getProperty("mlsipc.gameVersion"); configDir = Paths.get(System.getProperty("mlsipc.config")); } else { if (fabricReady) { runningOnQuilt = FabricLoader.getInstance().isModLoaded("quilt_loader"); + gameVersion = FabricLoader.getInstance().getModContainer("minecraft") + .map(c -> c.getMetadata().getVersion().getFriendlyString()) + .orElse(null); configDir = FabricLoader.getInstance().getConfigDir().resolve("mod-loading-screen"); } else { - // This code is certainly something. I don't have any ideas for improvement, though. - runningOnQuilt = System.getProperty("java.class.path").contains("quilt-loader"); + final String classpath = System.getProperty("java.class.path"); + runningOnQuilt = classpath.contains("quilt-loader"); configDir = Paths.get("config/mod-loading-screen").toAbsolutePath(); } } @@ -145,6 +211,7 @@ public static void startLoadingScreen(boolean fabricReady) { System.getProperty("java.home") + "/bin/java", "-Dmlsipc.present=true", "-Dmlsipc.quilt=" + runningOnQuilt, + "-Dmlsipc.gameVersion=" + (gameVersion != null ? gameVersion : ""), "-Dmlsipc.config=" + configDir, "-cp", mlsJarPath + File.pathSeparator + flatlafDestPath, ACTUAL_LOADING_SCREEN.replace('/', '.') @@ -173,24 +240,19 @@ public static void startLoadingScreen(boolean fabricReady) { UIManager.getDefaults().put("ProgressBar.selectionForeground", new Color(255, 255, 255)); dialog = new JFrame(); - if (fabricReady) { - setFabricTitle(); - } else { - dialog.setTitle(runningOnQuilt ? "Loading Quilt Loader" : "Loading Fabric Loader"); - } + updateTitleWithGameVersion(); dialog.setResizable(false); try { - dialog.setIconImage(ImageIO.read(findImageUrl("icon", "icon.png", ImageIO.getReaderFileSuffixes()))); + dialog.setIconImage(ImageIO.read(findImageUrl("icon", getLoaderIconName(), ImageIO.getReaderFileSuffixes()))); } catch (Exception e) { - println("Failed to load icon.png", e); + println("Failed to load icon", e); } ImageIcon background; try { - background = new ImageIcon(findImageUrl( - "background", runningOnQuilt ? "quilt-banner.png" : "aof4.png", SUPPORTED_BACKGROUND_FORMATS - )); + String defaultBanner = runningOnQuilt ? "quilt-banner.png" : "aof4.png"; + background = new ImageIcon(findImageUrl("background", defaultBanner, SUPPORTED_BACKGROUND_FORMATS)); } catch (Exception e) { println("Failed to load background.png", e); background = null; @@ -249,13 +311,25 @@ private static void loadConfig() { private static URL findImageUrl( String prefix, String defaultFilename, String[] formats ) throws MalformedURLException { + URL res = ClassLoader.getSystemResource("assets/mod-loading-screen/" + defaultFilename); + if (res != null) return res; + + res = ClassLoader.getSystemResource("assets/mod-loading-screen/icon.png"); + if (res != null) return res; + + final Path specificConfigPath = configDir.resolve(defaultFilename); + if (Files.isRegularFile(specificConfigPath)) { + return specificConfigPath.toUri().toURL(); + } + for (final String format : formats) { final Path path = configDir.resolve(prefix + '.' + format); if (Files.isRegularFile(path)) { return path.toUri().toURL(); } } - return ClassLoader.getSystemResource("assets/mod-loading-screen/" + defaultFilename); + + return null; } private static void startMemoryThread() { @@ -276,19 +350,36 @@ private static void startMemoryThread() { memoryThread.start(); } + public static void setGameVersion(String version) { + if (version == null || version.isEmpty()) return; + gameVersion = version; + sendIpc(7, version); + updateTitleWithGameVersion(); + } + + private static void updateTitleWithGameVersion() { + if (gameVersion != null && !gameVersion.isEmpty()) { + setTitle(translate("modloadingscreen.title.loading_loader_game", getLoaderName(), gameVersion)); + } else { + setTitle(translate("modloadingscreen.title.loading_loader", getLoaderName())); + } + } + public static void setTitleFromMetadata(String id, String name, String version) { + if (id != null && (id.equals("minecraft") || id.equalsIgnoreCase("game"))) { + setGameVersion(version); + return; + } if (titleSet || IGNORED_BUILTIN.contains(id)) return; titleSet = true; - setTitle("Loading " + name + ' ' + version); + setTitle(translate("modloadingscreen.title.loading_mod", name, version)); } private static void setFabricTitle() { - for (final ModContainer container : FabricLoader.getInstance().getAllMods()) { - final ModMetadata m = container.getMetadata(); - if (!m.getType().equals("builtin")) continue; - setTitleFromMetadata(m.getId(), m.getName(), m.getVersion().getFriendlyString()); - if (titleSet) break; - } + FabricLoader.getInstance().getModContainer("minecraft").ifPresent(container -> { + setGameVersion(container.getMetadata().getVersion().getFriendlyString()); + }); + updateTitleWithGameVersion(); } private static void setTitle(String title) { @@ -378,16 +469,21 @@ public static void maybeCloseAfter(String type) { close(); } - public static void createCustomProgressBar(String id, String title, int max) { + public static void createCustomProgressBar(String id, String titleKey, int max, String... titleArgs) { final String fullId = "custom:" + id; progress.put(fullId, 0); - if (sendIpc(4, id, title, Integer.toString(max))) return; + String[] ipcArgs = new String[titleArgs.length + 3]; + ipcArgs[0] = id; + ipcArgs[1] = titleKey; + ipcArgs[2] = Integer.toString(max); + System.arraycopy(titleArgs, 0, ipcArgs, 3, titleArgs.length); + if (sendIpc(4, ipcArgs)) return; if (dialog == null) return; final JProgressBar progressBar = new JProgressBar(0, max); progressBar.setStringPainted(true); - progressBar.setString(title); + progressBar.setString(translate(titleKey, (Object[]) titleArgs)); progressBars.put(fullId, progressBar); label.add(progressBar, BorderLayout.SOUTH, 1); label.revalidate(); @@ -427,7 +523,9 @@ public static void customProgressBarOp(String... args) { progressBar.setMinimum(Integer.parseInt(args[2])); break; case "title": - progressBar.setString(args[2]); + String[] formatArgs = new String[args.length - 3]; + System.arraycopy(args, 3, formatArgs, 0, formatArgs.length); + progressBar.setString(translate(args[2], (Object[]) formatArgs)); break; case "indeterminate": progressBar.setIndeterminate(Boolean.parseBoolean(args[2])); @@ -488,17 +586,17 @@ private static void updateMemoryUsage0(long usage, long total) { memoryBar.setMaximum(totalMb); memoryBar.setValue(usageMb); - memoryBar.setString(usageMb + " MB / " + totalMb + " MB"); + memoryBar.setString(translate("modloadingscreen.memory.usage", usageMb, totalMb)); } private static void setLabel(JProgressBar progressBar, String typeName, String typeType, @Nullable String modName) { - final StringBuilder message = new StringBuilder("Loading '").append(typeName) - .append("' (").append(typeType).append(") \u2014 ") - .append(progressBar.getValue()).append('/').append(progressBar.getMaximum()); + final int current = progressBar.getValue(); + final int max = progressBar.getMaximum(); if (modName != null) { - message.append(" \u2014 ").append(modName); + progressBar.setString(translate("modloadingscreen.entrypoint.status_mod", typeName, typeType, current, max, modName)); + } else { + progressBar.setString(translate("modloadingscreen.entrypoint.status", typeName, typeType, current, max)); } - progressBar.setString(message.toString()); } private static void println(String message) { @@ -549,7 +647,6 @@ private static boolean sendIpc(int id, String... args) { return true; } - // IPC client public static void main(String[] args) { try { logFile = new PrintStream("ipc-client-log.txt"); @@ -576,7 +673,9 @@ public static void main(String[] args) { updateMemoryUsage0(Long.parseLong(packetArgs[0]), Long.parseLong(packetArgs[1])); break; case 4: - createCustomProgressBar(packetArgs[0], packetArgs[1], Integer.parseInt(packetArgs[2])); + String[] createArgs = new String[packetArgs.length - 3]; + System.arraycopy(packetArgs, 3, createArgs, 0, createArgs.length); + createCustomProgressBar(packetArgs[0], packetArgs[1], Integer.parseInt(packetArgs[2]), createArgs); break; case 5: customProgressBarOp(packetArgs); @@ -584,6 +683,9 @@ public static void main(String[] args) { case 6: setTitle(packetArgs[0]); break; + case 7: + setGameVersion(packetArgs[0]); + break; case 255: break mainLoop; } @@ -592,8 +694,8 @@ public static void main(String[] args) { } catch (Exception e) { println("Error in IPC client", e); JOptionPane.showMessageDialog( - dialog, "An error occurred in Mod Loading Screen's IPC client\n" + e, - "Mod Loading Screen IPC Client", JOptionPane.ERROR_MESSAGE + dialog, translate("modloadingscreen.error.ipc_client", e), + translate("modloadingscreen.error.ipc_client_title"), JOptionPane.ERROR_MESSAGE ); } close(); diff --git a/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java b/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java index 06fd58b..e3f8d33 100644 --- a/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java +++ b/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java @@ -11,19 +11,19 @@ public class EarlyLoadingAgent { public static void premain(String args, Instrumentation instrumentation) throws IOException { - System.out.println("[ModLoadingScreen] I just want to say... I'm loading *really* **extremely** early."); + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.early_loading")); System.setProperty("mod-loading-screen.loaded", "true"); final Path flatlafDestPath = Paths.get(".cache/mod-loading-screen/flatlaf.jar").toAbsolutePath(); Files.createDirectories(flatlafDestPath.getParent()); try (InputStream is = EarlyLoadingAgent.class.getClassLoader().getResourceAsStream(MlsConstants.FLATLAF_PATH)) { if (is == null) { - System.err.println("[ModLoadingScreen] [ERROR] flatlaf.jar not found! Aborting."); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.flatlaf_not_found")); return; } Files.copy(is, flatlafDestPath, StandardCopyOption.REPLACE_EXISTING); } - System.out.println("[ModLoadingScreen] Extracted flatlaf.jar"); + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.extracted_flatlaf")); instrumentation.appendToSystemClassLoaderSearch(new JarFile(flatlafDestPath.toFile())); ActualLoadingScreen.startLoadingScreen(false); diff --git a/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java b/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java index 5f37e3f..4ddfa10 100644 --- a/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java +++ b/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java @@ -74,7 +74,7 @@ static byte[] instrumentClass(String name, byte[] bytes) { synchronized (MlsTransformers.class) { if (!notifiedClassDump) { notifiedClassDump = true; - System.out.println("[ModLoadingScreen] Transformed class dumping is active"); + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.dump_active")); try { final Path dumpDir = Paths.get(".mlsDebugDump"); if (Files.isDirectory(dumpDir)) { @@ -93,7 +93,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx }); } } catch (Throwable t) { - System.err.println("[ModLoadingScreen] [ERROR] Failed to clear debug dump dir"); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dump_clear_failed")); } } } @@ -102,7 +102,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx try { final Collection> transformer = TRANSFORMERS.get(name); if (transformer != null) { - System.out.println("[ModLoadingScreen] Transforming " + name); + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.transforming", name)); final ClassReader reader = new ClassReader(bytes); final ClassNode clazz = new ClassNode(); reader.accept(clazz, 0); @@ -110,7 +110,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx try { part.accept(clazz); } catch (Exception e) { - System.err.println("[ModLoadingScreen] [ERROR] Transformer " + part + " for " + name + " failed"); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.transformer_failed", part, name)); e.printStackTrace(); } } @@ -123,14 +123,14 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx Files.createDirectories(dumpedPath.getParent()); Files.write(dumpedPath, result); } catch (Exception e) { - System.err.println("[ModLoadingScreen] [ERROR] Failed to dump class " + name); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dump_failed", name)); e.printStackTrace(); } } return result; } } catch (Throwable t) { - System.err.println("[ModLoadingScreen] [ERROR] Completely failed to transform " + name); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.transform_failed", name)); t.printStackTrace(); } return null; @@ -142,7 +142,7 @@ private static void instrumentFabricLoaderImplInvokeEntrypoints(ClassNode clazz) .findFirst() .orElse(null); if (method == null) { - System.out.println("[ModLoadingScreen] New-style FabricLoaderImpl.invokeEntrypoints not found. Assuming old Fabric."); + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.old_fabric_fallback")); return; } final ListIterator it = method.instructions.iterator(); @@ -155,7 +155,7 @@ private static void instrumentFabricLoaderImplInvokeEntrypoints(ClassNode clazz) if (insn.getOpcode() == Opcodes.ACONST_NULL) break; } it.previous(); - mainEntrypointHooks(it, false, true); + mainEntrypointHooks(it, false, true); maybeCloseAfter(it, true); } @@ -170,9 +170,6 @@ private static void instrumentEntrypointUtilsInvoke(ClassNode clazz, boolean onQ maybeCloseAfter(it, false); } - /** - * Inserts code for calling maybeCloseAfter, and leaves {@code it} pointing to the {@code RETURN}. - */ private static void maybeCloseAfter(ListIterator it, boolean instanceMethod) { while (it.hasNext()) { final AbstractInsnNode insn = it.next(); @@ -201,7 +198,6 @@ private static void instrumentEntrypointUtilsInvoke0(ClassNode clazz, boolean on private static void mainEntrypointHooks(ListIterator it, boolean onQuilt, boolean instanceMethod) { final int varOffset = instanceMethod ? 1 : 0; - //noinspection PointlessArithmeticExpression final int keyIndex = 0 + varOffset; final int typeIndex = 1 + varOffset; @@ -297,32 +293,20 @@ private static void instrumentModDiscovererDiscoverMods(ClassNode clazz, boolean if (insn.getOpcode() == Opcodes.CHECKCAST && ((TypeInsnNode)insn).desc.equals(BuiltinMod)) break; } - // BuiltinMod it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod BuiltinMod it.add(new FieldInsnNode(Opcodes.GETFIELD, BuiltinMod, "metadata", "L" + MOD_METADATA + ";")); - // BuiltinMod ModMetadata it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod ModMetadata ModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, MOD_METADATA, "getId", "()Ljava/lang/String;")); - // BuiltinMod ModMetadata String it.add(new InsnNode(Opcodes.SWAP)); - // BuiltinMod String ModMetadata it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod String ModMetadata ModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, MOD_METADATA, "getName", "()Ljava/lang/String;")); - // BuiltinMod String ModMetadata String it.add(new InsnNode(Opcodes.SWAP)); - // BuiltinMod String String ModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, MOD_METADATA, "getVersion", "()L" + FABRIC_VERSION + ";")); - // BuiltinMod String String Version it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, FABRIC_VERSION, "getFriendlyString", "()Ljava/lang/String;")); - // BuiltinMod String String String it.add(new MethodInsnNode( Opcodes.INVOKESTATIC, ACTUAL_LOADING_SCREEN, "setTitleFromMetadata", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V" )); - // BuiltinMod } private static void instrumentStandardQuiltPluginAddBuiltinMods(ClassNode clazz) { @@ -338,31 +322,19 @@ private static void instrumentStandardQuiltPluginAddBuiltinMods(ClassNode clazz) if (insn.getOpcode() == Opcodes.CHECKCAST && ((TypeInsnNode)insn).desc.equals(QUILT_BUILTIN_MOD)) break; } - // BuiltinMod it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod BuiltinMod it.add(new FieldInsnNode(Opcodes.GETFIELD, QUILT_BUILTIN_MOD, "metadata", "L" + INTERNAL_MOD_METADATA + ";")); - // BuiltinMod InternalModMetadata it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod InternalModMetadata InternalModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, INTERNAL_MOD_METADATA, "id", "()Ljava/lang/String;")); - // BuiltinMod InternalModMetadata String it.add(new InsnNode(Opcodes.SWAP)); - // BuiltinMod String InternalModMetadata it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod String InternalModMetadata InternalModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, INTERNAL_MOD_METADATA, "name", "()Ljava/lang/String;")); - // BuiltinMod String InternalModMetadata String it.add(new InsnNode(Opcodes.SWAP)); - // BuiltinMod String String InternalModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, INTERNAL_MOD_METADATA, "version", "()L" + QUILT_VERSION + ";")); - // BuiltinMod String String Version it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, QUILT_VERSION, "raw", "()Ljava/lang/String;")); - // BuiltinMod String String String it.add(new MethodInsnNode( Opcodes.INVOKESTATIC, ACTUAL_LOADING_SCREEN, "setTitleFromMetadata", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V" )); - // BuiltinMod } } diff --git a/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java b/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java index 68700fd..f0c4c94 100644 --- a/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java +++ b/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java @@ -27,16 +27,16 @@ public class ModLoadingScreen implements LanguageAdapter { @SuppressWarnings("unchecked") public T create(ModContainer mod, String value, Class type) throws LanguageAdapterException { if (type != PreLaunchEntrypoint.class) { - throw new LanguageAdapterException("Fake entrypoint only supported on PreLaunchEntrypoint"); + throw new LanguageAdapterException(ActualLoadingScreen.translate("modloadingscreen.error.fake_entrypoint_unsupported")); } return (T)(PreLaunchEntrypoint)() -> {}; } public static void init() throws Throwable { - System.out.println("[ModLoadingScreen] I just want to say... I'm loading *really* early."); + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.mod_loading_early")); if (System.setProperty("mod-loading-screen.loaded", "true") != null) { - System.err.println("[ModLoadingScreen] [WARN] Mod Loading Screen installed as both a mod and an agent."); - System.err.println("[ModLoadingScreen] [WARN] Please avoid doing this. To avoid issues, the mod has disabled itself."); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dual_install_warn1")); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dual_install_warn2")); return; } @@ -87,7 +87,7 @@ public static void init() throws Throwable { try { init(); } catch (Throwable t) { - System.err.println("[ModLoadingScreen] Failed to initialize loading screen. Aborting!"); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.init_failed")); throw new Error(t); } } diff --git a/src/main/resources/assets/mod-loading-screen/icon-fabric.png b/src/main/resources/assets/mod-loading-screen/icon-fabric.png new file mode 100644 index 0000000..bb3f0a4 Binary files /dev/null and b/src/main/resources/assets/mod-loading-screen/icon-fabric.png differ diff --git a/src/main/resources/assets/mod-loading-screen/icon-quilt.png b/src/main/resources/assets/mod-loading-screen/icon-quilt.png new file mode 100644 index 0000000..9206ed6 Binary files /dev/null and b/src/main/resources/assets/mod-loading-screen/icon-quilt.png differ diff --git a/src/main/resources/assets/mod-loading-screen/lang/en_us.json b/src/main/resources/assets/mod-loading-screen/lang/en_us.json new file mode 100644 index 0000000..2f68c26 --- /dev/null +++ b/src/main/resources/assets/mod-loading-screen/lang/en_us.json @@ -0,0 +1,45 @@ +{ + "modmenu.nameTranslation.mod-loading-screen": "Mod Loading Screen", + "modmenu.descriptionTranslation.mod-loading-screen": "A fancy loading screen that shows the progress of loading mods", + + "modloadingscreen.loader.fabric": "Fabric", + "modloadingscreen.loader.quilt": "Quilt", + "modloadingscreen.title.loading_loader": "Loading %s", + "modloadingscreen.title.loading_loader_game": "Loading %s %s", + + "modloadingscreen.feature.final_entrypoints": "Final Entrypoints", + "modloadingscreen.feature.headless_check": "Headless Check", + "modloadingscreen.feature.ipc_check": "IPC Check", + "modloadingscreen.feature.get_progress": "Get Progress", + "modloadingscreen.feature.open_check": "Open Check", + "modloadingscreen.feature.custom_progress_bars": "Custom Progress Bars", + "modloadingscreen.feature.unknown": "Unknown Feature", + + "modloadingscreen.title.loading_quilt": "Loading Quilt Loader", + "modloadingscreen.title.loading_fabric": "Loading Fabric Loader", + "modloadingscreen.title.loading_mod": "Loading %s %s", + + "modloadingscreen.memory.usage": "%d MB / %d MB", + + "modloadingscreen.entrypoint.status": "Loading '%s' (%s) \u2014 %d/%d", + "modloadingscreen.entrypoint.status_mod": "Loading '%s' (%s) \u2014 %d/%d \u2014 %s", + + "modloadingscreen.error.ipc_client": "An error occurred in Mod Loading Screen's IPC client\n%s", + "modloadingscreen.error.ipc_client_title": "Mod Loading Screen IPC Client", + "modloadingscreen.error.fake_entrypoint_unsupported": "Fake entrypoint only supported on PreLaunchEntrypoint", + + "modloadingscreen.log.early_loading": "[ModLoadingScreen] I just want to say... I'm loading *really* **extremely** early.", + "modloadingscreen.log.flatlaf_not_found": "[ModLoadingScreen] [ERROR] flatlaf.jar not found! Aborting.", + "modloadingscreen.log.extracted_flatlaf": "[ModLoadingScreen] Extracted flatlaf.jar", + "modloadingscreen.log.dump_active": "[ModLoadingScreen] Transformed class dumping is active", + "modloadingscreen.log.dump_clear_failed": "[ModLoadingScreen] [ERROR] Failed to clear debug dump dir", + "modloadingscreen.log.transforming": "[ModLoadingScreen] Transforming %s", + "modloadingscreen.log.transformer_failed": "[ModLoadingScreen] [ERROR] Transformer %s for %s failed", + "modloadingscreen.log.dump_failed": "[ModLoadingScreen] [ERROR] Failed to dump class %s", + "modloadingscreen.log.transform_failed": "[ModLoadingScreen] [ERROR] Completely failed to transform %s", + "modloadingscreen.log.old_fabric_fallback": "[ModLoadingScreen] New-style FabricLoaderImpl.invokeEntrypoints not found. Assuming old Fabric.", + "modloadingscreen.log.mod_loading_early": "[ModLoadingScreen] I just want to say... I'm loading *really* early.", + "modloadingscreen.log.dual_install_warn1": "[ModLoadingScreen] [WARN] Mod Loading Screen installed as both a mod and an agent.", + "modloadingscreen.log.dual_install_warn2": "[ModLoadingScreen] [WARN] Please avoid doing this. To avoid issues, the mod has disabled itself.", + "modloadingscreen.log.init_failed": "[ModLoadingScreen] Failed to initialize loading screen. Aborting!" +} diff --git a/src/main/resources/assets/mod-loading-screen/lang/zh_tw.json b/src/main/resources/assets/mod-loading-screen/lang/zh_tw.json new file mode 100644 index 0000000..837055f --- /dev/null +++ b/src/main/resources/assets/mod-loading-screen/lang/zh_tw.json @@ -0,0 +1,45 @@ +{ + "modmenu.nameTranslation.mod-loading-screen": "模組載入畫面", + "modmenu.descriptionTranslation.mod-loading-screen": "帶有模組載入進度的高級載入畫面", + + "modloadingscreen.loader.fabric": "Fabric", + "modloadingscreen.loader.quilt": "Quilt", + "modloadingscreen.title.loading_loader": "正在載入%s", + "modloadingscreen.title.loading_loader_game": "正在載入%s %s", + + "modloadingscreen.feature.final_entrypoints": "最終進入點", + "modloadingscreen.feature.headless_check": "無介面模式檢查", + "modloadingscreen.feature.ipc_check": "IPC 檢查", + "modloadingscreen.feature.get_progress": "讀取進度", + "modloadingscreen.feature.open_check": "開啟狀態檢查", + "modloadingscreen.feature.custom_progress_bars": "自訂進度條", + "modloadingscreen.feature.unknown": "未知特性", + + "modloadingscreen.title.loading_quilt": "正在載入 Quilt Loader", + "modloadingscreen.title.loading_fabric": "正在載入 Fabric Loader", + "modloadingscreen.title.loading_mod": "正在載入 %s %s", + + "modloadingscreen.memory.usage": "%d MB / %d MB", + + "modloadingscreen.entrypoint.status": "正在載入 '%s' (%s) \u2014 %d/%d", + "modloadingscreen.entrypoint.status_mod": "正在載入 '%s' (%s) \u2014 %d/%d \u2014 %s", + + "modloadingscreen.error.ipc_client": "模組載入畫面 的 IPC 客戶端發生錯誤\n%s", + "modloadingscreen.error.ipc_client_title": "模組載入畫面 IPC 客戶端", + "modloadingscreen.error.fake_entrypoint_unsupported": "偽進入點僅支援 PreLaunchEntrypoint", + + "modloadingscreen.log.early_loading": "[模組載入畫面] 我只想說... 我的載入時機真的 *非常* **極度** 早期。", + "modloadingscreen.log.flatlaf_not_found": "[模組載入畫面] [錯誤] 找不到 flatlaf.jar!正在中止。", + "modloadingscreen.log.extracted_flatlaf": "[模組載入畫面] 已解壓縮 flatlaf.jar", + "modloadingscreen.log.dump_active": "[模組載入畫面] 已啟用轉換類別轉儲功能", + "modloadingscreen.log.dump_clear_failed": "[模組載入畫面] [錯誤] 清空除錯轉儲目錄失敗", + "modloadingscreen.log.transforming": "[模組載入畫面] 正在轉換 %s", + "modloadingscreen.log.transformer_failed": "[模組載入畫面] [錯誤] %2$s 的轉換器 %1$s 執行失敗", + "modloadingscreen.log.dump_failed": "[模組載入畫面] [錯誤] 轉儲類別 %s 失敗", + "modloadingscreen.log.transform_failed": "[模組載入畫面] [錯誤] 完全無法轉換 %s", + "modloadingscreen.log.old_fabric_fallback": "[模組載入畫面] 未找到新版 FabricLoaderImpl.invokeEntrypoints,將視為舊版 Fabric。", + "modloadingscreen.log.mod_loading_early": "[模組載入畫面] 我只想說... 我的載入時機真的 *非常* 早期。", + "modloadingscreen.log.dual_install_warn1": "[模組載入畫面] [警告] 模組載入畫面 同時以模組和 Agent 方式安裝。", + "modloadingscreen.log.dual_install_warn2": "[模組載入畫面] [警告] 請避免這樣做。為避免問題,模組已自動停用。", + "modloadingscreen.log.init_failed": "[模組載入畫面] 初始化載入畫面失敗。正在中止!" +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index c706e95..fc501a6 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -7,10 +7,24 @@ "authors": [ "Gaming32" ], - "contact": {}, + "contributors": [ + "StarsShine11904" + ], + "contact": { + "homepage": "https://modrinth.com/mod/mod-loading-screen", + "sources": "https://github.com/Gaming32/mod-loading-screen", + "issues": "https://github.com/Gaming32/mod-loading-screen/issues" + }, "license": "MIT", "icon": "assets/mod-loading-screen/icon.png", - "environment": "*", + "environment": "client", + "custom": { + "modmenu": { + "badges": [ + "client" + ] + } + }, "depends": { "fabricloader": ">=0.12.0" },