Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@

## 支持的版本

- Minecraft:1.6.1~1.21.10 都支持
- Minecraft:1.6.1~1.21.11、26.1~26.1.2 都支持
- Mod加载器:MinecraftForge、NeoForge、Fabric、Quilt 都支持
- Java:8~21 都支持
- Java:8~25 都支持

仅仅需要在mods文件夹中放置本Mod的jar文件即可,Mod本身与各主流Minecraft版本、Mod Loader、Java版本均兼容,Mod本身不需要进行任何版本隔离。

## 支持的「简体中文资源包」

为了尽可能实用,目前本Mod会根据游戏版本自动下载、合并、转换「简体中文资源包」。

- 官方资源:1.10.2、1.12.2、1.16、1.18、1.19、1.20、1.21
- 官方资源:1.10.2、1.12.2、1.16、1.18、1.19、1.20、1.21、26.1
- 合并转换:会合并加转换最近版本的一些资源包,尽可能做最大化的支持
- 特别说明:1.13开始将语言文件变化为json格式,所以不能将1.12.2的资源包用于1.13以上,反之同理

Expand All @@ -40,4 +40,4 @@
请使用Java 8及以上的JDK构建。
```shell
gradle clean shadowJar
```
```
22 changes: 21 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8
}

sourceSets {
named("main") {
java {
// The NeoForge entrypoint uses a compile-time annotation shim so this
// universal jar can still be built without a Java 25 NeoForge API jar.
srcDir("src/compat/java")
}
}
}

tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
Expand All @@ -34,6 +44,11 @@ tasks.shadowJar {
exclude("LICENSE")
}

tasks.withType<Jar> {
// The real API is supplied by NeoForge at runtime.
exclude("net/neoforged/**")
}

repositories {
mavenCentral()
maven("https://libraries.minecraft.net/")
Expand All @@ -48,7 +63,12 @@ configurations.configureEach {

dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
// This project sets isTransitive = false, so JUnit's own dependencies must be declared by hand.
testImplementation("org.junit.platform:junit-platform-commons:1.10.3")
testImplementation("org.opentest4j:opentest4j:1.3.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.3")
testRuntimeOnly("org.junit.platform:junit-platform-engine:1.10.3")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.3")
implementation("net.runelite.archive-patcher:archive-patcher-applier:1.2")
compileOnly("org.jetbrains:annotations:24.1.0")

Expand Down Expand Up @@ -103,4 +123,4 @@ curseforge {
gameVersionStrings.addAll(curseForgeSpecialVersions)
changelog = if (System.getenv("CHANGE_LOG") != null) System.getenv("CHANGE_LOG") else "No change log"
}
}
}
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version=3.7.1
minecraft=1.6.1,1.6.2,1.6.4,1.7.2,1.7.10,1.8,1.8.8,1.8.9,1.9,1.9.4,1.10,1.10.2,1.11,1.11.2,1.12,1.12.1,1.12.2,1.13.2,1.14,1.14.1,1.14.2,1.14.3,1.14.4,1.15,1.15.1,1.15.2,1.16,1.16.1,1.16.2,1.16.3,1.16.4,1.16.5,1.17,1.17.1,1.18,1.18.1,1.18.2,1.19,1.19.1,1.19.2,1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.3,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11
curseforge=NeoForge,Forge,Fabric,Quilt,Client,Java 8,Java 9,Java 10,Java 11,Java 12,Java 13,Java 14,Java 15,Java 16,Java 17,Java 18
version=3.7.2
minecraft=1.6.1,1.6.2,1.6.4,1.7.2,1.7.10,1.8,1.8.8,1.8.9,1.9,1.9.4,1.10,1.10.2,1.11,1.11.2,1.12,1.12.1,1.12.2,1.13.2,1.14,1.14.1,1.14.2,1.14.3,1.14.4,1.15,1.15.1,1.15.2,1.16,1.16.1,1.16.2,1.16.3,1.16.4,1.16.5,1.17,1.17.1,1.18,1.18.1,1.18.2,1.19,1.19.1,1.19.2,1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.3,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1,26.1.1,26.1.2
curseforge=NeoForge,Forge,Fabric,Quilt,Client,Java 8,Java 9,Java 10,Java 11,Java 12,Java 13,Java 14,Java 15,Java 16,Java 17,Java 18,Java 19,Java 20,Java 21,Java 22,Java 23,Java 24,Java 25
7 changes: 7 additions & 0 deletions src/compat/java/net/neoforged/api/distmarker/Dist.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.neoforged.api.distmarker;

/** Compile-time shim for the NeoForge distribution enum. */
public enum Dist {
CLIENT,
DEDICATED_SERVER
}
22 changes: 22 additions & 0 deletions src/compat/java/net/neoforged/fml/common/Mod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package net.neoforged.fml.common;

import net.neoforged.api.distmarker.Dist;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Compile-time shim for the NeoForge annotation. The class is excluded from
* the produced JAR, so NeoForge supplies the runtime annotation implementation.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Mod {
String value();

Dist[] dist() default {Dist.CLIENT, Dist.DEDICATED_SERVER};

String[] depends() default {};
}
27 changes: 25 additions & 2 deletions src/main/java/i18nupdatemod/I18nUpdateMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -29,7 +30,21 @@ public class I18nUpdateMod {

public static final Gson GSON = new Gson();

/**
* Older NeoForge can reach {@link #init} through both the ModLauncher service and the
* {@code @Mod} entrypoint; only the first one should do the work.
*/
private static final AtomicBoolean INITIALIZED = new AtomicBoolean();

public static boolean isInitialized() {
return INITIALIZED.get();
}

public static void init(Path minecraftPath, String minecraftVersion, String loader, @NotNull HashSet<String> modDomainsSet) {
if (!INITIALIZED.compareAndSet(false, true)) {
Log.debug("Already initialized, skipping duplicate entrypoint");
return;
}
try (InputStream is = I18nUpdateMod.class.getResourceAsStream("/i18nMetaData.json")) {
MOD_VERSION = GSON.fromJson(new InputStreamReader(is), JsonObject.class).get("version").getAsString();
} catch (Exception e) {
Expand Down Expand Up @@ -58,7 +73,7 @@ public static void init(Path minecraftPath, String minecraftVersion, String load

FileUtil.setResourcePackDirPath(minecraftPath.resolve("resourcepacks"));

int minecraftMajorVersion = Integer.parseInt(minecraftVersion.split("\\.")[1]);
int minecraftMajorVersion = getMinecraftMajorVersion(minecraftVersion);

try {
//Get asset
Expand Down Expand Up @@ -100,6 +115,14 @@ private static String getResourcePackDescription(List<GameAssetDetail.AssetDownl

}

private static int getMinecraftMajorVersion(String minecraftVersion) {
String[] parts = minecraftVersion.split("\\.");
if (parts.length >= 2 && "1".equals(parts[0])) {
return Integer.parseInt(parts[1]);
}
return Integer.parseInt(parts[0]);
}

public static String getLocalStoragePos(Path minecraftPath) {
Path userHome = Paths.get(System.getProperty("user.home"));
Path oldPath = userHome.resolve("." + MOD_ID);
Expand All @@ -124,4 +147,4 @@ public static String getLocalStoragePos(Path minecraftPath) {
).findFirst().orElse(xdgDataHome);
}

}
}
15 changes: 9 additions & 6 deletions src/main/java/i18nupdatemod/core/ResourcePackConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import i18nupdatemod.entity.GameMetaData;
import i18nupdatemod.util.FileUtil;
import i18nupdatemod.util.Log;
Expand Down Expand Up @@ -80,21 +82,22 @@ public void convert(GameMetaData metaData, String description, HashSet<String> m

private byte[] convertPackMeta(InputStream is, GameMetaData metaData, String description) {
PackMeta meta = GSON.fromJson(new InputStreamReader(is, StandardCharsets.UTF_8), PackMeta.class);
meta.pack.pack_format = metaData.useNewFormat() ? null : metaData.packFormat;
meta.pack.pack_format = metaData.useNewFormat() || metaData.packFormat == null
? null : new JsonPrimitive(metaData.packFormat);
meta.pack.min_format = metaData.useNewFormat() ? metaData.minFormat : null;
meta.pack.max_format = metaData.useNewFormat() ? metaData.maxFormat : null;
meta.pack.description = description;
meta.pack.description = new JsonPrimitive(description);
return GSON.toJson(meta).getBytes(StandardCharsets.UTF_8);
}

private static class PackMeta {
Pack pack;

private static class Pack {
Integer pack_format; // 改为 Integer,支持 null
Integer min_format;
Integer max_format;
String description;
JsonElement pack_format;
JsonElement min_format;
JsonElement max_format;
JsonElement description;
}
}
}
11 changes: 9 additions & 2 deletions src/main/java/i18nupdatemod/entity/GameMetaData.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package i18nupdatemod.entity;

import com.google.gson.JsonElement;

import java.util.List;

public class GameMetaData {
public String gameVersions;
public Integer packFormat, minFormat, maxFormat;
public Integer packFormat;
/** Supports both pre-26 integer formats and 26.1 structured formats. */
public JsonElement minFormat, maxFormat;
public List<String> convertFrom;

public boolean useNewFormat() { return minFormat != null && maxFormat != null; }
public boolean useNewFormat() {
return minFormat != null && maxFormat != null
&& !minFormat.isJsonNull() && !maxFormat.isJsonNull();
}
}
40 changes: 35 additions & 5 deletions src/main/java/i18nupdatemod/modlauncher/ModLauncherService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@

import static i18nupdatemod.I18nUpdateMod.GSON;

//1.13-latest
/**
* Forge 1.13+ (including Minecraft 26.1) and NeoForge up to Minecraft 1.21.x.
* <p>
* Forge still boots through ModLauncher on 26.1 (bootstrap 2.1.8 + modlauncher 10.2.6), so this
* service remains the Forge entrypoint there. NeoForge's FML 11 dropped ModLauncher entirely, so on
* NeoForge 26.1+ this service is never loaded and {@link i18nupdatemod.neoforge.NeoForgeMod} takes
* over instead.
*/
public class ModLauncherService implements ITransformationService {
@Override
public @NotNull String name() {
Expand Down Expand Up @@ -67,20 +74,43 @@ private String getMinecraftVersion() {
return args[i + 1];
}
}
} catch (Exception e) {
} catch (Exception | LinkageError e) {
Log.warning("Error getting minecraft version: %s", e);
}

// MinecraftForge 1.20.3~
// MinecraftForge 1.20.3~, and the only source on Forge 26.1 where --fml.mcversion is gone
// 1.20.3: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/fmlloader/src/main/java/net/minecraftforge/fml/loading/VersionInfo.java
try {
Class<?> clazz = Class.forName("net.minecraftforge.fml.loading.FMLLoader");
// Resolved without running static initializers: only the class's resource root is needed,
// and initializing it can fail with an Error that would escape into the loader.
Class<?> clazz = loadClass("net.minecraftforge.fml.loading.FMLLoader");
if (clazz == null) {
return null;
}
try (InputStream is = clazz.getResourceAsStream("/forge_version.json")) {
if (is == null) {
Log.warning("forge_version.json not found");
return null;
}
return GSON.fromJson(new InputStreamReader(is), JsonObject.class).get("mc").getAsString();
}
} catch (Exception e) {
} catch (Exception | LinkageError e) {
Log.warning("Error getting minecraft version: %s", e);
}
return null;
}

/**
* @return the class, or null when it is absent or cannot be loaded
*/
private static Class<?> loadClass(String name) {
try {
return Class.forName(name, false, ModLauncherService.class.getClassLoader());
} catch (ClassNotFoundException ignored) {
return null;
} catch (LinkageError e) {
Log.warning("Error loading %s: %s", name, e);
return null;
}
}
}
95 changes: 95 additions & 0 deletions src/main/java/i18nupdatemod/neoforge/NeoForgeMod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package i18nupdatemod.neoforge;

import i18nupdatemod.I18nUpdateMod;
import i18nupdatemod.util.Log;
import i18nupdatemod.util.ModUtil;
import i18nupdatemod.util.Reflection;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.fml.common.Mod;

import java.nio.file.Path;

/**
* NeoForge javafml entrypoint.
* <p>
* Required from FML 11 (Minecraft 26.1) onwards, where ModLauncher was removed and
* {@link i18nupdatemod.modlauncher.ModLauncherService} is therefore never invoked. Older NeoForge
* still loads this class from the same universal jar, and there FMLLoader exposes static
* {@code getGamePath()}/{@code versionInfo()} instead of the current instance methods, so both
* shapes are resolved reflectively.
*/
@Mod(value = I18nUpdateMod.MOD_ID, dist = Dist.CLIENT)
public final class NeoForgeMod {
private static final String FML_LOADER = "net.neoforged.fml.loading.FMLLoader";

public NeoForgeMod() {
try {
if (I18nUpdateMod.isInitialized()) {
// ModLauncherService already did the work on an older NeoForge.
Log.debug("Already initialized, skipping NeoForge entrypoint");
return;
}

Path gameDir = getGameDir();
if (gameDir == null) {
Log.warning("Minecraft path not found");
return;
}
Log.setMinecraftLogFile(gameDir);
String mcVersion = getMcVersion();
if (mcVersion == null) {
Log.warning("Minecraft version not found");
return;
}

I18nUpdateMod.init(gameDir, mcVersion, "NeoForge",
ModUtil.getModDomainsFromModsFolder(gameDir, mcVersion, "NeoForge"));
} catch (Exception e) {
Log.warning("Failed to initialize NeoForge entrypoint: " + e);
}
}

/**
* @return game directory, or null if neither API shape is present
*/
private static Path getGameDir() {
// FML 11+ (Minecraft 26.1+)
try {
return (Path) Reflection.clazz(FML_LOADER)
.get("getCurrent()")
.get("getGameDir()")
.get();
} catch (Exception ignored) {
}
// FML 1~4 (Minecraft 1.20.1~1.21.x)
try {
return (Path) Reflection.clazz(FML_LOADER).get("getGamePath()").get();
} catch (Exception ignored) {
}
return null;
}

/**
* @return Minecraft version, or null if neither API shape is present
*/
private static String getMcVersion() {
// FML 11+ (Minecraft 26.1+)
try {
return (String) Reflection.clazz(FML_LOADER)
.get("getCurrent()")
.get("getVersionInfo()")
.get("mcVersion()")
.get();
} catch (Exception ignored) {
}
// FML 1~4 (Minecraft 1.20.1~1.21.x)
try {
return (String) Reflection.clazz(FML_LOADER)
.get("versionInfo()")
.get("mcVersion()")
.get();
} catch (Exception ignored) {
}
return null;
}
}
Loading