v1.0.15.1 — fix server re-extract of launcher zips; correct Forge launch command (-cp per version.json)
This commit is contained in:
+49
-28
@@ -70,42 +70,35 @@ public class LaunchCommandBuilder {
|
|||||||
List<String> allJvmArgs = manifest != null ? manifest.getJvmArguments() : new ArrayList<>();
|
List<String> allJvmArgs = manifest != null ? manifest.getJvmArguments() : new ArrayList<>();
|
||||||
List<String> allGameArgs = manifest != null ? manifest.getAllGameArguments() : new ArrayList<>();
|
List<String> allGameArgs = manifest != null ? manifest.getAllGameArguments() : new ArrayList<>();
|
||||||
|
|
||||||
// Build variable map for placeholder substitution (${classpath},
|
// Build variable map for placeholder substitution (${library_directory},
|
||||||
// ${natives_directory}, ${library_directory}, ${version_name}, ...)
|
// ${classpath_separator}, ${version_name}, ${natives_directory}, ...)
|
||||||
Map<String, String> vars = buildVariableMap(options);
|
Map<String, String> vars = buildVariableMap(options);
|
||||||
|
|
||||||
// Forge/NeoForge version.json defines its own -p / --module-path /
|
// Real Forge/NeoForge version.json (1.17+ / 47.x) declares a fixed -p
|
||||||
// --add-modules / --add-opens and references ${classpath}. We substitute
|
// module path of bootstrap jars and references ${library_directory} /
|
||||||
// ${classpath} once (only when referenced) and let the version.json
|
// ${classpath_separator} / ${version_name} — but it does NOT reference
|
||||||
// arguments drive the classpath/module path exactly as AstralRinth and the
|
// ${classpath} and does NOT contain -cp. To let the JVM find
|
||||||
// 1.0.13 launcher did. Do NOT inject a separate -DlegacyClassPath:
|
// cpw.mods.bootstraplauncher.BootstrapLauncher we must supply -cp
|
||||||
// modern Forge's BootstrapLauncher derives the MC-BOOTSTRAP layer from the
|
// ourselves (version jar + all libraries, exactly like the official
|
||||||
// module path, and a redundant -DlegacyClassPath duplicates every library
|
// launcher). If a version.json DOES reference ${classpath}, we substitute
|
||||||
// and can trigger split-package / duplicate-entry failures.
|
// the same full classpath into it first.
|
||||||
boolean needsClasspath = referencesClasspath(allJvmArgs) || referencesClasspath(allGameArgs);
|
String fullClasspath;
|
||||||
if (needsClasspath) {
|
|
||||||
String classpath;
|
|
||||||
if (manifest != null) {
|
if (manifest != null) {
|
||||||
classpath = buildClasspathFromManifest(manifest, false);
|
fullClasspath = buildClasspathFromManifest(manifest, true);
|
||||||
} else {
|
} else {
|
||||||
classpath = "";
|
fullClasspath = buildVanillaClasspath();
|
||||||
}
|
}
|
||||||
|
|
||||||
Path clientJar = findVersionJar();
|
boolean referencesClasspath = referencesClasspath(allJvmArgs) || referencesClasspath(allGameArgs);
|
||||||
if (clientJar != null && isValidJar(clientJar)) {
|
if (referencesClasspath) {
|
||||||
String sep = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":";
|
vars.put("classpath", writeClasspathFile(fullClasspath));
|
||||||
classpath = clientJar.toAbsolutePath().toString() + (classpath.isEmpty() ? "" : sep + classpath);
|
System.out.println(ZAnsi.green(" ${classpath} referenced, substituted full classpath"));
|
||||||
System.out.println(ZAnsi.green(" Added client jar: " + clientJar.getFileName()));
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.yellow(" Client jar not found, falling back to vanilla classpath"));
|
System.out.println(ZAnsi.cyan(" ${classpath} not referenced by version.json, not substituting"));
|
||||||
classpath = buildVanillaClasspath();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vars.put("classpath", writeClasspathFile(classpath));
|
boolean hasCpInJson = containsCpArg(allJvmArgs);
|
||||||
System.out.println(ZAnsi.green(" ${classpath} referenced, classpath built for substitution"));
|
boolean hasLibraryPathInJson = containsLibraryPathArg(allJvmArgs);
|
||||||
} else {
|
|
||||||
System.out.println(ZAnsi.cyan(" ${classpath} not referenced by version.json, skipping classpath build"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!allJvmArgs.isEmpty()) {
|
if (!allJvmArgs.isEmpty()) {
|
||||||
for (String arg : allJvmArgs) {
|
for (String arg : allJvmArgs) {
|
||||||
@@ -120,8 +113,11 @@ public class LaunchCommandBuilder {
|
|||||||
command.addAll(getJvmArguments(options));
|
command.addAll(getJvmArguments(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forge/NeoForge child version.json doesn't include -Djava.library.path
|
// Forge/NeoForge child version.json usually doesn't include
|
||||||
|
// -Djava.library.path (47.3.0 doesn't) — add it unless already present.
|
||||||
|
if (!hasLibraryPathInJson) {
|
||||||
command.add("-Djava.library.path=" + nativesDir.toAbsolutePath());
|
command.add("-Djava.library.path=" + nativesDir.toAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
// Append memory/GC args (always after version.json args, like AstralRinth)
|
// Append memory/GC args (always after version.json args, like AstralRinth)
|
||||||
int ramMB = options.getMaxMemory() > 0 ? options.getMaxMemory() : 4096;
|
int ramMB = options.getMaxMemory() > 0 ? options.getMaxMemory() : 4096;
|
||||||
@@ -139,6 +135,15 @@ public class LaunchCommandBuilder {
|
|||||||
command.addAll(options.getExtraJvmArgs());
|
command.addAll(options.getExtraJvmArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Real Forge/NeoForge version.json has no -cp, so the JVM would not find the
|
||||||
|
// main class. Add -cp with version jar + all libraries (official-launcher
|
||||||
|
// behaviour); skip only if the version.json itself declares -cp.
|
||||||
|
if (!hasCpInJson) {
|
||||||
|
command.add("-cp");
|
||||||
|
command.add(writeClasspathFile(fullClasspath));
|
||||||
|
System.out.println(ZAnsi.green(" Added -cp classpath for main class lookup"));
|
||||||
|
}
|
||||||
|
|
||||||
// Main class from version.json
|
// Main class from version.json
|
||||||
String mainClass = null;
|
String mainClass = null;
|
||||||
if (manifest != null) {
|
if (manifest != null) {
|
||||||
@@ -564,6 +569,22 @@ public class LaunchCommandBuilder {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean containsCpArg(List<String> args) {
|
||||||
|
if (args == null) return false;
|
||||||
|
for (String arg : args) {
|
||||||
|
if (arg != null && (arg.equals("-cp") || arg.equals("-classpath"))) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsLibraryPathArg(List<String> args) {
|
||||||
|
if (args == null) return false;
|
||||||
|
for (String arg : args) {
|
||||||
|
if (arg != null && arg.startsWith("-Djava.library.path=")) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private String buildClasspathFromManifest(VersionManifest manifest, boolean includeVersionJar) throws Exception {
|
private String buildClasspathFromManifest(VersionManifest manifest, boolean includeVersionJar) throws Exception {
|
||||||
List<String> paths = new ArrayList<>();
|
List<String> paths = new ArrayList<>();
|
||||||
Path librariesDir = instance.getPath().resolve("libraries");
|
Path librariesDir = instance.getPath().resolve("libraries");
|
||||||
|
|||||||
+54
-21
@@ -75,20 +75,36 @@ class LaunchCommandBuilderTest {
|
|||||||
createJarWithModule(dir.resolve("versions/1.20.1/1.20.1.jar"), "minecraft");
|
createJarWithModule(dir.resolve("versions/1.20.1/1.20.1.jar"), "minecraft");
|
||||||
createJar(dir.resolve("libraries/net/minecraft/client/1.20.1/client-1.20.1.jar"));
|
createJar(dir.resolve("libraries/net/minecraft/client/1.20.1/client-1.20.1.jar"));
|
||||||
|
|
||||||
// Forge child
|
// Forge child — real 1.20.1-47.3.0 format: -p is a fixed bootstrap list,
|
||||||
|
// NO ${classpath} and NO -cp (the launcher must add -cp itself).
|
||||||
String forgeJson = """
|
String forgeJson = """
|
||||||
{
|
{
|
||||||
"id": "1.20.1-forge-47.3.0",
|
"id": "1.20.1-forge-47.3.0",
|
||||||
"inheritsFrom": "1.20.1",
|
"inheritsFrom": "1.20.1",
|
||||||
"mainClass": "cpw.mods.bootstraplauncher.BootstrapLauncher",
|
"mainClass": "cpw.mods.bootstraplauncher.BootstrapLauncher",
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"jvm": ["-p", "${classpath}", "--add-modules=ALL-MODULE-PATH", "--add-opens=java.base/java.util.jar=ALL-UNNAMED", "--add-opens=java.base/java.lang.invoke=ALL-UNNAMED"],
|
"jvm": ["-Djava.net.preferIPv6Addresses=system",
|
||||||
"game": ["--launchTarget", "forgeclient", "--fml.forgeVersion", "47.3.0", "--fml.mcVersion", "1.20.1", "--fml.forgeGroup", "net.minecraftforge"]
|
"-DignoreList=bootstraplauncher,securejarhandler,asm-commons,asm-util,asm-analysis,asm-tree,asm,JarJarFileSystems,client-extra,fmlcore,javafmllanguage,lowcodelanguage,mclanguage,forge-,${version_name}.jar",
|
||||||
|
"-DmergeModules=jna-5.10.0.jar,jna-platform-5.10.0.jar",
|
||||||
|
"-DlibraryDirectory=${library_directory}",
|
||||||
|
"-p", "${library_directory}/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar${classpath_separator}${library_directory}/cpw/mods/securejarhandler/2.1.10/securejarhandler-2.1.10.jar",
|
||||||
|
"--add-modules", "ALL-MODULE-PATH",
|
||||||
|
"--add-opens", "java.base/java.util.jar=cpw.mods.securejarhandler",
|
||||||
|
"--add-opens", "java.base/java.lang.invoke=cpw.mods.securejarhandler"],
|
||||||
|
"game": ["--launchTarget", "forgeclient", "--fml.forgeVersion", "47.3.0",
|
||||||
|
"--fml.mcVersion", "1.20.1", "--fml.forgeGroup", "net.minecraftforge",
|
||||||
|
"--fml.mcpVersion", "20230612.114412"]
|
||||||
},
|
},
|
||||||
"libraries": [{"name": "net.minecraftforge:forge:1.20.1-47.3.0", "downloads": {"artifact": {"path": "net/minecraftforge/forge/1.20.1-47.3.0/forge-1.20.1-47.3.0.jar"}}}]
|
"libraries": [
|
||||||
|
{"name": "cpw.mods:bootstraplauncher:1.1.2", "downloads": {"artifact": {"path": "cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar"}}},
|
||||||
|
{"name": "cpw.mods:securejarhandler:2.1.10", "downloads": {"artifact": {"path": "cpw/mods/securejarhandler/2.1.10/securejarhandler-2.1.10.jar"}}},
|
||||||
|
{"name": "net.minecraftforge:forge:1.20.1-47.3.0", "downloads": {"artifact": {"path": "net/minecraftforge/forge/1.20.1-47.3.0/forge-1.20.1-47.3.0.jar"}}}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
writeVersionJson(dir.resolve("versions/" + versionId + "/" + versionId + ".json"), forgeJson);
|
writeVersionJson(dir.resolve("versions/" + versionId + "/" + versionId + ".json"), forgeJson);
|
||||||
|
createJar(dir.resolve("libraries/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar"));
|
||||||
|
createJar(dir.resolve("libraries/cpw/mods/securejarhandler/2.1.10/securejarhandler-2.1.10.jar"));
|
||||||
createJar(dir.resolve("libraries/net/minecraftforge/forge/1.20.1-47.3.0/forge-1.20.1-47.3.0.jar"));
|
createJar(dir.resolve("libraries/net/minecraftforge/forge/1.20.1-47.3.0/forge-1.20.1-47.3.0.jar"));
|
||||||
|
|
||||||
Files.createDirectories(dir.resolve("natives"));
|
Files.createDirectories(dir.resolve("natives"));
|
||||||
@@ -215,21 +231,35 @@ class LaunchCommandBuilderTest {
|
|||||||
|
|
||||||
List<String> command = builder.build(options);
|
List<String> command = builder.build(options);
|
||||||
|
|
||||||
// Parent's -cp must NOT appear (causes split-package with child's -p)
|
// Real Forge version.json has no -cp (and no ${classpath}), so the launcher MUST add
|
||||||
assertFalse(command.contains("-cp"), "Forge must not have -cp from parent");
|
// -cp with version jar + all libraries for the JVM to find BootstrapLauncher.
|
||||||
|
int cpIdx = command.indexOf("-cp");
|
||||||
|
assertTrue(cpIdx >= 0, "Launcher must add -cp for Forge (version.json has none)");
|
||||||
|
assertTrue(cpIdx + 1 < command.size(), "-cp must have a value");
|
||||||
|
String classpath = command.get(cpIdx + 1);
|
||||||
|
assertNotNull(classpath, "-cp value must not be null");
|
||||||
|
|
||||||
// -Djava.library.path must be added manually (not in child args)
|
// -Djava.library.path must be added manually (not in child args)
|
||||||
String expectedLibPath = "-Djava.library.path=" + tempDir.resolve("natives").toAbsolutePath();
|
String expectedLibPath = "-Djava.library.path=" + tempDir.resolve("natives").toAbsolutePath();
|
||||||
assertTrue(command.contains(expectedLibPath), "Forge must have -Djava.library.path");
|
assertTrue(command.contains(expectedLibPath), "Forge must have -Djava.library.path");
|
||||||
|
|
||||||
// Child's -p (module path) must be present
|
// Child's -p (module path) must be the fixed bootstrap list passed through unchanged
|
||||||
int pIndex = command.indexOf("-p");
|
int pIndex = command.indexOf("-p");
|
||||||
assertTrue(pIndex >= 0, "Forge must have -p");
|
assertTrue(pIndex >= 0, "Forge must have -p");
|
||||||
assertTrue(pIndex + 1 < command.size(), "-p must have a value");
|
assertTrue(pIndex + 1 < command.size(), "-p must have a value");
|
||||||
assertNotNull(command.get(pIndex + 1), "-p value must not be null");
|
String modulePath = command.get(pIndex + 1);
|
||||||
|
assertNotNull(modulePath, "-p value must not be null");
|
||||||
|
assertTrue(modulePath.contains("bootstraplauncher-1.1.2.jar"), "-p must contain bootstrap jar");
|
||||||
|
assertTrue(modulePath.contains("securejarhandler-2.1.10.jar"), "-p must contain bootstrap jar");
|
||||||
|
|
||||||
// Child's --add-modules must be present
|
// ${classpath} must NOT be substituted / referenced in the real-json JVM args
|
||||||
assertTrue(command.contains("--add-modules=ALL-MODULE-PATH"), "Forge must have --add-modules=ALL-MODULE-PATH");
|
assertFalse(classpath.contains("${classpath}"), "-cp must not contain unresolved ${classpath} placeholder");
|
||||||
|
|
||||||
|
// Child's --add-modules must be present (two-arg format)
|
||||||
|
int amIdx = command.indexOf("--add-modules");
|
||||||
|
assertTrue(amIdx >= 0, "Forge must have --add-modules");
|
||||||
|
assertTrue(amIdx + 1 < command.size(), "--add-modules must have a value");
|
||||||
|
assertEquals("ALL-MODULE-PATH", command.get(amIdx + 1), "--add-modules must be ALL-MODULE-PATH");
|
||||||
|
|
||||||
// Memory/GC args
|
// Memory/GC args
|
||||||
assertTrue(command.contains("-Xmx4096M"), "Forge must have memory args");
|
assertTrue(command.contains("-Xmx4096M"), "Forge must have memory args");
|
||||||
@@ -255,9 +285,10 @@ class LaunchCommandBuilderTest {
|
|||||||
|
|
||||||
List<String> command = builder.build(options);
|
List<String> command = builder.build(options);
|
||||||
|
|
||||||
// Find -p's classpath value
|
// Find -cp's classpath value (launcher-added; version.json has no -cp)
|
||||||
int pIdx = command.indexOf("-p");
|
int cpIdx = command.indexOf("-cp");
|
||||||
String cpValue = command.get(pIdx + 1);
|
assertTrue(cpIdx >= 0, "Launcher must add -cp for Forge");
|
||||||
|
String cpValue = command.get(cpIdx + 1);
|
||||||
|
|
||||||
// Version jar from ensureVersionJarForForge
|
// Version jar from ensureVersionJarForForge
|
||||||
assertTrue(cpValue.contains("1.20.1-forge-47.3.0.jar") || cpValue.contains("1.20.1.jar"),
|
assertTrue(cpValue.contains("1.20.1-forge-47.3.0.jar") || cpValue.contains("1.20.1.jar"),
|
||||||
@@ -400,8 +431,9 @@ class LaunchCommandBuilderTest {
|
|||||||
|
|
||||||
List<String> command = builder.build(options);
|
List<String> command = builder.build(options);
|
||||||
|
|
||||||
// Same checks as Forge
|
// NeoForge child jvm args reference ${classpath} (substituted into -p); version.json
|
||||||
assertFalse(command.contains("-cp"), "NeoForge must not have -cp from parent");
|
// declares no -cp, so the launcher adds -cp with version jar + all libraries.
|
||||||
|
assertTrue(command.contains("-cp"), "NeoForge must have -cp (added by launcher)");
|
||||||
String expectedLibPath = "-Djava.library.path=" + tempDir.resolve("natives").toAbsolutePath();
|
String expectedLibPath = "-Djava.library.path=" + tempDir.resolve("natives").toAbsolutePath();
|
||||||
assertTrue(command.contains(expectedLibPath), "NeoForge must have -Djava.library.path");
|
assertTrue(command.contains(expectedLibPath), "NeoForge must have -Djava.library.path");
|
||||||
assertTrue(command.contains("-p"), "NeoForge must have -p");
|
assertTrue(command.contains("-p"), "NeoForge must have -p");
|
||||||
@@ -494,27 +526,28 @@ class LaunchCommandBuilderTest {
|
|||||||
String modulePath = command.get(pIdx + 1);
|
String modulePath = command.get(pIdx + 1);
|
||||||
|
|
||||||
// Extract --add-modules, -Djava.library.path
|
// Extract --add-modules, -Djava.library.path
|
||||||
String addModules = null;
|
|
||||||
String libPath = null;
|
String libPath = null;
|
||||||
for (String arg : command) {
|
for (String arg : command) {
|
||||||
if (arg.startsWith("-Djava.library.path=")) {
|
if (arg.startsWith("-Djava.library.path=")) {
|
||||||
libPath = arg;
|
libPath = arg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
String addModules = null;
|
||||||
for (int i = 0; i < command.size(); i++) {
|
for (int i = 0; i < command.size(); i++) {
|
||||||
if ("--add-modules=ALL-MODULE-PATH".equals(command.get(i))) {
|
if ("--add-modules".equals(command.get(i)) && i + 1 < command.size()) {
|
||||||
addModules = command.get(i);
|
addModules = command.get(i + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertNotNull(addModules, "Must have --add-modules=ALL-MODULE-PATH");
|
assertNotNull(addModules, "Must have --add-modules");
|
||||||
|
assertEquals("ALL-MODULE-PATH", addModules, "--add-modules must be ALL-MODULE-PATH");
|
||||||
|
|
||||||
// Build minimal Java command that validates the module path
|
// Build minimal Java command that validates the module path
|
||||||
// java -Djava.library.path=<path> -p <classpath> --add-modules=ALL-MODULE-PATH -version
|
// java -Djava.library.path=<path> -p <classpath> --add-modules ALL-MODULE-PATH -version
|
||||||
ProcessBuilder pb = new ProcessBuilder(
|
ProcessBuilder pb = new ProcessBuilder(
|
||||||
"java",
|
"java",
|
||||||
libPath,
|
libPath,
|
||||||
"-p", modulePath,
|
"-p", modulePath,
|
||||||
"--add-modules=ALL-MODULE-PATH",
|
"--add-modules", addModules,
|
||||||
"-version"
|
"-version"
|
||||||
);
|
);
|
||||||
pb.redirectErrorStream(true);
|
pb.redirectErrorStream(true);
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>1.0.15</revision>
|
<revision>1.0.15</revision>
|
||||||
<hotfix>0</hotfix>
|
<hotfix>1</hotfix>
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|||||||
+38
-27
@@ -1377,6 +1377,37 @@ def generate_launcher_builds_meta():
|
|||||||
logger.warning(f"Failed to save meta.json: {e}")
|
logger.warning(f"Failed to save meta.json: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_version_meta(version_path: Path, version: str) -> dict:
|
||||||
|
"""Generate and cache meta.json for an extracted version directory."""
|
||||||
|
files = []
|
||||||
|
for file_path in version_path.rglob("*"):
|
||||||
|
if file_path.is_file() and file_path.name != "meta.json":
|
||||||
|
rel_path = str(file_path.relative_to(version_path))
|
||||||
|
stat = file_path.stat()
|
||||||
|
file_hash = calculate_file_hash(file_path)
|
||||||
|
files.append({
|
||||||
|
"path": rel_path,
|
||||||
|
"size": stat.st_size,
|
||||||
|
"hash": f"sha256:{file_hash}"
|
||||||
|
})
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
"version": version,
|
||||||
|
"type": "new",
|
||||||
|
"release_date": datetime.utcnow().isoformat(),
|
||||||
|
"files": files
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
meta_path = version_path / "meta.json"
|
||||||
|
with open(meta_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(meta, f, indent=2)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to save launcher meta for {version}: {e}")
|
||||||
|
|
||||||
|
return meta
|
||||||
|
|
||||||
|
|
||||||
def scan_launcher_version(version: str) -> Optional[dict]:
|
def scan_launcher_version(version: str) -> Optional[dict]:
|
||||||
"""Scan a launcher version directory and return meta"""
|
"""Scan a launcher version directory and return meta"""
|
||||||
# First check if meta exists in builds/ directly (for new format)
|
# First check if meta exists in builds/ directly (for new format)
|
||||||
@@ -1407,33 +1438,7 @@ def scan_launcher_version(version: str) -> Optional[dict]:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Generate meta
|
# Generate meta
|
||||||
files = []
|
return generate_version_meta(version_path, version)
|
||||||
for file_path in version_path.rglob("*"):
|
|
||||||
if file_path.is_file() and file_path.name != "meta.json":
|
|
||||||
rel_path = str(file_path.relative_to(version_path))
|
|
||||||
stat = file_path.stat()
|
|
||||||
file_hash = calculate_file_hash(file_path)
|
|
||||||
files.append({
|
|
||||||
"path": rel_path,
|
|
||||||
"size": stat.st_size,
|
|
||||||
"hash": f"sha256:{file_hash}"
|
|
||||||
})
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
"version": version,
|
|
||||||
"type": "new",
|
|
||||||
"release_date": datetime.utcnow().isoformat(),
|
|
||||||
"files": files
|
|
||||||
}
|
|
||||||
|
|
||||||
# Save meta
|
|
||||||
try:
|
|
||||||
with open(meta_path, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(meta, f, indent=2)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Failed to save launcher meta for {version}: {e}")
|
|
||||||
|
|
||||||
return meta
|
|
||||||
|
|
||||||
|
|
||||||
def parse_version_key(v: str) -> tuple:
|
def parse_version_key(v: str) -> tuple:
|
||||||
@@ -1492,6 +1497,12 @@ def extract_new_format_versions():
|
|||||||
# Extract all files
|
# Extract all files
|
||||||
zf.extractall(extract_dir)
|
zf.extractall(extract_dir)
|
||||||
|
|
||||||
|
# Generate meta.json cache for the extracted version. Without this the
|
||||||
|
# "already extracted" check above always fails: scan_launcher_version()
|
||||||
|
# short-circuits on BUILDS_DIR/meta.json for the latest version and never
|
||||||
|
# writes versions/<v>/meta.json, so every scan would re-extract the zip.
|
||||||
|
generate_version_meta(extract_dir, version)
|
||||||
|
|
||||||
logger.info(f"Extracted {zip_file.name} successfully")
|
logger.info(f"Extracted {zip_file.name} successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to extract {zip_file.name}: {e}")
|
logger.error(f"Failed to extract {zip_file.name}: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user