First pass on "shareCaches".

dev/0.11
modmuss50 2020-06-27 20:18:32 +01:00
parent 2d7421d4ed
commit 32eb0bd3c8
9 changed files with 411 additions and 10 deletions

View File

@ -24,6 +24,7 @@
package net.fabricmc.loom; package net.fabricmc.loom;
import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -54,6 +55,8 @@ import net.fabricmc.loom.providers.MinecraftProvider;
import net.fabricmc.loom.providers.MappingsCache; import net.fabricmc.loom.providers.MappingsCache;
import net.fabricmc.loom.task.RemapJarTask; import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.task.RemapSourcesJarTask; import net.fabricmc.loom.task.RemapSourcesJarTask;
import net.fabricmc.loom.task.shared.RemapAllJarsTask;
import net.fabricmc.loom.task.shared.RemapAllSourcesTask;
import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.GroovyXmlUtil; import net.fabricmc.loom.util.GroovyXmlUtil;
import net.fabricmc.loom.util.LoomDependencyManager; import net.fabricmc.loom.util.LoomDependencyManager;
@ -64,7 +67,9 @@ import net.fabricmc.loom.util.mixin.JavaApInvoker;
import net.fabricmc.loom.util.mixin.KaptApInvoker; import net.fabricmc.loom.util.mixin.KaptApInvoker;
import net.fabricmc.loom.util.mixin.ScalaApInvoker; import net.fabricmc.loom.util.mixin.ScalaApInvoker;
import net.fabricmc.loom.util.FabricApiExtension; import net.fabricmc.loom.util.FabricApiExtension;
import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.DownloadUtil; import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.JarRemapper;
public class AbstractPlugin implements Plugin<Project> { public class AbstractPlugin implements Plugin<Project> {
protected Project project; protected Project project;
@ -268,6 +273,48 @@ public class AbstractPlugin implements Plugin<Project> {
} }
} }
SourceRemapper remapper = null;
Task parentTask = project1.getTasks().getByName("build");
if (extension.isShareCaches()) {
Project rootProject = project.getRootProject();
if (extension.isRootProject()) {
SourceRemapper sourceRemapper = new SourceRemapper(rootProject, false);
JarRemapper jarRemapper = new JarRemapper();
remapJarTask.jarRemapper = jarRemapper;
rootProject.getTasks().register("remapAllSources", RemapAllSourcesTask.class, task -> {
task.sourceRemapper = sourceRemapper;
task.doLast(t -> sourceRemapper.remapAll());
});
parentTask = rootProject.getTasks().getByName("remapAllSources");
rootProject.getTasks().register("remapAllJars", RemapAllJarsTask.class, task -> {
task.doLast(t -> {
try {
jarRemapper.remap();
} catch (IOException e) {
throw new RuntimeException("Failed to remap jars", e);
}
});
});
for (Project subProject : rootProject.getAllprojects()) {
subProject.getTasks().getByName("build").dependsOn(parentTask);
subProject.getTasks().getByName("build").dependsOn(rootProject.getTasks().getByName("remapAllJars"));
rootProject.getTasks().getByName("remapAllJars").dependsOn(subProject.getTasks().getByName("remapJar"));
}
} else {
parentTask = rootProject.getTasks().getByName("remapAllSources");
remapper = ((RemapAllSourcesTask) parentTask).sourceRemapper;
remapJarTask.jarRemapper = ((RemapJarTask) rootProject.getTasks().getByName("remapJar")).jarRemapper;
}
}
try { try {
AbstractArchiveTask sourcesTask = (AbstractArchiveTask) project1.getTasks().getByName("sourcesJar"); AbstractArchiveTask sourcesTask = (AbstractArchiveTask) project1.getTasks().getByName("sourcesJar");
@ -276,7 +323,12 @@ public class AbstractPlugin implements Plugin<Project> {
remapSourcesJarTask.setOutput(sourcesTask.getArchivePath()); remapSourcesJarTask.setOutput(sourcesTask.getArchivePath());
remapSourcesJarTask.doLast(task -> project1.getArtifacts().add("archives", remapSourcesJarTask.getOutput())); remapSourcesJarTask.doLast(task -> project1.getArtifacts().add("archives", remapSourcesJarTask.getOutput()));
remapSourcesJarTask.dependsOn(project1.getTasks().getByName("sourcesJar")); remapSourcesJarTask.dependsOn(project1.getTasks().getByName("sourcesJar"));
project1.getTasks().getByName("build").dependsOn(remapSourcesJarTask);
if (extension.isShareCaches()) {
remapSourcesJarTask.setSourceRemapper(remapper);
}
parentTask.dependsOn(remapSourcesJarTask);
} catch (UnknownTaskException e) { } catch (UnknownTaskException e) {
// pass // pass
} }

View File

@ -61,6 +61,7 @@ public class LoomGradleExtension {
public String customManifest = null; public String customManifest = null;
public File accessWidener = null; public File accessWidener = null;
public Function<String, Object> intermediaryUrl = mcVer -> "https://maven.fabricmc.net/net/fabricmc/intermediary/" + mcVer + "/intermediary-" + mcVer + "-v2.jar"; public Function<String, Object> intermediaryUrl = mcVer -> "https://maven.fabricmc.net/net/fabricmc/intermediary/" + mcVer + "/intermediary-" + mcVer + "-v2.jar";
public boolean shareCaches = false;
private List<Path> unmappedModsBuilt = new ArrayList<>(); private List<Path> unmappedModsBuilt = new ArrayList<>();
@ -338,4 +339,24 @@ public class LoomGradleExtension {
//Done like this to work around this possibly not being a java string... //Done like this to work around this possibly not being a java string...
return s -> intermediaryUrl.apply(s).toString(); return s -> intermediaryUrl.apply(s).toString();
} }
public boolean isRootProject() {
return project.getRootProject() == project;
}
public LoomGradleExtension getRootGradleExtension() {
if (isRootProject()) {
return this;
}
return project.getRootProject().getExtensions().getByType(LoomGradleExtension.class);
}
public LoomGradleExtension getSharedGradleExtension() {
return isShareCaches() ? getRootGradleExtension() : this;
}
public boolean isShareCaches() {
return shareCaches;
}
} }

View File

@ -120,6 +120,10 @@ public class MinecraftProvider extends DependencyProvider {
private void downloadMcJson(boolean offline) throws IOException { private void downloadMcJson(boolean offline) throws IOException {
File manifests = new File(getExtension().getUserCache(), "version_manifest.json"); File manifests = new File(getExtension().getUserCache(), "version_manifest.json");
if (getExtension().isShareCaches() && !getExtension().isRootProject() && manifests.exists() && !isRefreshDeps()) {
return;
}
if (offline) { if (offline) {
if (manifests.exists()) { if (manifests.exists()) {
//If there is the manifests already we'll presume that's good enough //If there is the manifests already we'll presume that's good enough
@ -171,6 +175,10 @@ public class MinecraftProvider extends DependencyProvider {
} }
private void downloadJars(Logger logger) throws IOException { private void downloadJars(Logger logger) throws IOException {
if (getExtension().isShareCaches() && !getExtension().isRootProject() && minecraftClientJar.exists() && minecraftServerJar.exists() && !isRefreshDeps()) {
return;
}
DownloadUtil.downloadIfChanged(new URL(versionInfo.downloads.get("client").url), minecraftClientJar, logger); DownloadUtil.downloadIfChanged(new URL(versionInfo.downloads.get("client").url), minecraftClientJar, logger);
DownloadUtil.downloadIfChanged(new URL(versionInfo.downloads.get("server").url), minecraftServerJar, logger); DownloadUtil.downloadIfChanged(new URL(versionInfo.downloads.get("server").url), minecraftServerJar, logger);
} }

View File

@ -26,6 +26,7 @@ package net.fabricmc.loom.task;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -38,6 +39,7 @@ import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
import org.gradle.jvm.tasks.Jar; import org.gradle.jvm.tasks.Jar;
import org.zeroturnaround.zip.ZipUtil;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.providers.MappingsProvider; import net.fabricmc.loom.providers.MappingsProvider;
@ -46,6 +48,8 @@ import net.fabricmc.loom.util.MixinRefmapHelper;
import net.fabricmc.loom.util.NestedJars; import net.fabricmc.loom.util.NestedJars;
import net.fabricmc.loom.util.TinyRemapperMappingsHelper; import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
import net.fabricmc.loom.util.accesswidener.AccessWidenerJarProcessor; import net.fabricmc.loom.util.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.util.JarRemapper;
import net.fabricmc.stitch.util.Pair;
import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper; import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.TinyUtils; import net.fabricmc.tinyremapper.TinyUtils;
@ -54,6 +58,7 @@ public class RemapJarTask extends Jar {
private RegularFileProperty input; private RegularFileProperty input;
private Property<Boolean> addNestedDependencies; private Property<Boolean> addNestedDependencies;
private Property<Boolean> remapAccessWidener; private Property<Boolean> remapAccessWidener;
public JarRemapper jarRemapper;
public RemapJarTask() { public RemapJarTask() {
super(); super();
@ -66,6 +71,14 @@ public class RemapJarTask extends Jar {
@TaskAction @TaskAction
public void doTask() throws Throwable { public void doTask() throws Throwable {
if (jarRemapper == null) {
doSingleRemap();
} else {
scheduleRemap();
}
}
public void doSingleRemap() throws Throwable {
Project project = getProject(); Project project = getProject();
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
Path input = this.getInput().getAsFile().get().toPath(); Path input = this.getInput().getAsFile().get().toPath();
@ -150,6 +163,81 @@ public class RemapJarTask extends Jar {
}*/ }*/
} }
public void scheduleRemap() throws Throwable {
Project project = getProject();
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
Path input = this.getInput().getAsFile().get().toPath();
Path output = this.getArchivePath().toPath();
if (!Files.exists(input)) {
throw new FileNotFoundException(input.toString());
}
MappingsProvider mappingsProvider = extension.getMappingsProvider();
String fromM = "named";
String toM = "intermediary";
if (extension.isRootProject()) {
Set<File> classpathFiles = new LinkedHashSet<>(
project.getConfigurations().getByName("compileClasspath").getFiles()
);
Path[] classpath = classpathFiles.stream()
.map(File::toPath)
.filter(Files::exists)
.toArray(Path[]::new);
jarRemapper.addToClasspath(classpath);
jarRemapper.addMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false));
}
File mixinMapFile = mappingsProvider.mappingsMixinExport;
Path mixinMapPath = mixinMapFile.toPath();
if (mixinMapFile.exists()) {
jarRemapper.addMappings(TinyUtils.createTinyMappingProvider(mixinMapPath, fromM, toM));
}
jarRemapper.scheduleRemap(input, output)
.supplyAccessWidener((remapData, remapper) -> {
if (getRemapAccessWidener().getOrElse(false) && extension.accessWidener != null) {
AccessWidenerJarProcessor accessWidenerJarProcessor = extension.getJarProcessorManager().getByType(AccessWidenerJarProcessor.class);
byte[] data;
try {
data = accessWidenerJarProcessor.getRemappedAccessWidener(remapper);
} catch (IOException e) {
throw new RuntimeException("Failed to remap access widener");
}
return Pair.of(accessWidenerJarProcessor.getAccessWidenerPath(remapData.output), data);
}
return null;
})
.complete((data, accessWidener) -> {
if (!Files.exists(output)) {
throw new RuntimeException("Failed to remap " + input + " to " + output + " - file missing!");
}
if (MixinRefmapHelper.addRefmapName(extension.getRefmapName(), extension.getMixinJsonVersion(), output)) {
project.getLogger().debug("Transformed mixin reference maps in output JAR!");
}
if (getAddNestedDependencies().getOrElse(false)) {
if (NestedJars.addNestedJars(project, output)) {
project.getLogger().debug("Added nested jar paths to mod json");
}
}
if (accessWidener != null) {
ZipUtil.replaceEntry(data.output.toFile(), accessWidener.getLeft(), accessWidener.getRight());
}
});
}
@InputFile @InputFile
public RegularFileProperty getInput() { public RegularFileProperty getInput() {
return input; return input;

View File

@ -28,6 +28,7 @@ import java.io.File;
import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
@ -37,10 +38,34 @@ public class RemapSourcesJarTask extends AbstractLoomTask {
private Object input; private Object input;
private Object output; private Object output;
private String direction = "intermediary"; private String direction = "intermediary";
private SourceRemapper sourceRemapper = null;
@TaskAction @TaskAction
public void remap() throws Exception { public void remap() throws Exception {
if (sourceRemapper == null) {
SourceRemapper.remapSources(getProject(), getInput(), getOutput(), direction.equals("named")); SourceRemapper.remapSources(getProject(), getInput(), getOutput(), direction.equals("named"));
} else {
sourceRemapper.scheduleRemapSources(getInput(), getOutput());
}
}
public String getDirection() {
return direction;
}
public RemapSourcesJarTask setDirection(String direction) {
this.direction = direction;
return this;
}
@Internal
public SourceRemapper getSourceRemapper() {
return sourceRemapper;
}
public RemapSourcesJarTask setSourceRemapper(SourceRemapper sourceRemapper) {
this.sourceRemapper = sourceRemapper;
return this;
} }
@InputFile @InputFile

View File

@ -0,0 +1,30 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task.shared;
import net.fabricmc.loom.task.AbstractLoomTask;
public class RemapAllJarsTask extends AbstractLoomTask {
}

View File

@ -0,0 +1,32 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task.shared;
import net.fabricmc.loom.task.AbstractLoomTask;
import net.fabricmc.loom.util.SourceRemapper;
public class RemapAllSourcesTask extends AbstractLoomTask {
public SourceRemapper sourceRemapper;
}

View File

@ -0,0 +1,141 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.stitch.util.Pair;
import net.fabricmc.tinyremapper.IMappingProvider;
import net.fabricmc.tinyremapper.InputTag;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
public class JarRemapper {
private final List<IMappingProvider> mappingProviders = new ArrayList<>();
private final Set<Path> classPath = new HashSet<>();
private final List<RemapData> remapData = new ArrayList<>();
public void addMappings(IMappingProvider mappingProvider) {
mappingProviders.add(mappingProvider);
}
public void addToClasspath(Path... paths) {
classPath.addAll(Arrays.asList(paths));
}
public RemapData scheduleRemap(Path input, Path output) {
RemapData data = new RemapData(input, output);
remapData.add(data);
return data;
}
public void remap() throws IOException {
TinyRemapper.Builder remapperBuilder = TinyRemapper.newRemapper();
mappingProviders.forEach(remapperBuilder::withMappings);
TinyRemapper remapper = remapperBuilder.build();
Path[] remapClasspath = classPath.stream()
.filter(path ->
remapData.stream().noneMatch(remapData -> remapData.input.equals(path))
)
.toArray(Path[]::new);
remapper.readClassPathAsync(remapClasspath);
for (RemapData data : remapData) {
InputTag tag = remapper.createInputTag();
data.tag = tag;
remapper.readInputsAsync(tag, data.input);
}
List<OutputConsumerPath> outputConsumers = new ArrayList<>();
for (RemapData data : remapData) {
OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(data.output).build();
outputConsumers.add(outputConsumer);
outputConsumer.addNonClassFiles(data.input);
data.processAccessWidener(remapper.getRemapper());
remapper.apply(outputConsumer, data.tag);
}
remapper.finish();
for (OutputConsumerPath outputConsumer : outputConsumers) {
outputConsumer.close();
}
remapData.forEach(RemapData::complete);
}
public static class RemapData {
public final Path input;
public final Path output;
BiFunction<RemapData, Remapper, Pair<String, byte[]>> accesWidenerSupplier;
BiConsumer<RemapData, Pair<String, byte[]>> onComplete;
private InputTag tag;
private Pair<String, byte[]> accessWidener;
public RemapData(Path input, Path output) {
this.input = input;
this.output = output;
}
public RemapData complete(BiConsumer<RemapData, Pair<String, byte[]>> onComplete) {
this.onComplete = onComplete;
return this;
}
public RemapData supplyAccessWidener(BiFunction<RemapData, Remapper, Pair<String, byte[]>> beforeFinish) {
this.accesWidenerSupplier = beforeFinish;
return this;
}
private void complete() {
if (onComplete != null) {
onComplete.accept(this, accessWidener);
}
}
private void processAccessWidener(Remapper remapper) {
if (accesWidenerSupplier != null) {
accessWidener = accesWidenerSupplier.apply(this, remapper);
}
}
}
}

View File

@ -124,13 +124,7 @@ public class AccessWidenerJarProcessor implements JarProcessor {
//Called when remapping the mod //Called when remapping the mod
public void remapAccessWidener(Path modJarPath, Remapper asmRemapper) throws IOException { public void remapAccessWidener(Path modJarPath, Remapper asmRemapper) throws IOException {
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, asmRemapper, "intermediary"); byte[] bytes = getRemappedAccessWidener(asmRemapper);
AccessWidener remapped = remapper.remap();
StringWriter writer = new StringWriter();
remapped.write(writer);
byte[] bytes = writer.toString().getBytes();
writer.close();
String path = getAccessWidenerPath(modJarPath); String path = getAccessWidenerPath(modJarPath);
@ -145,7 +139,17 @@ public class AccessWidenerJarProcessor implements JarProcessor {
} }
} }
private String getAccessWidenerPath(Path modJarPath) { public byte[] getRemappedAccessWidener(Remapper asmRemapper) throws IOException {
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, asmRemapper, "intermediary");
AccessWidener remapped = remapper.remap();
try (StringWriter writer = new StringWriter()) {
remapped.write(writer);
return writer.toString().getBytes();
}
}
public String getAccessWidenerPath(Path modJarPath) {
byte[] modJsonBytes = ZipUtil.unpackEntry(modJarPath.toFile(), "fabric.mod.json"); byte[] modJsonBytes = ZipUtil.unpackEntry(modJarPath.toFile(), "fabric.mod.json");
if (modJsonBytes == null) { if (modJsonBytes == null) {