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
241 changes: 241 additions & 0 deletions src/main/java/cam72cam/mod/loader/UMCModContainer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package cam72cam.mod.loader;

import cam72cam.mod.ModCore;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import net.minecraft.client.resources.FolderResourcePack;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.client.FMLFolderResourcePack;
import net.minecraftforge.fml.common.*;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.InvalidVersionSpecificationException;
import net.minecraftforge.fml.common.versioning.VersionRange;
import org.apache.logging.log4j.message.FormattedMessage;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class UMCModContainer extends DummyModContainer {
ModCandidate candidate;
File source;
ModCore.Mod instance;

String modId;
String displayName;
String mainClass;
String version;
List<String> authors;
List<String> description;
URL modURL;
String license;
Map<String, ArtifactVersion> dependencies;
File resourcesRoot;

ModMetadata meta;

private EventBus eventBus;

@Override
public String getModId() {
return modId;
}

@Override
public String getName() {
return displayName;
}

@Override
public String getVersion() {
return version;
}

@Override
public File getSource() {
return source;
}

@Override
public void setEnabledState(boolean enabled) {
//NO-OP as UMC mods cannot be disabled
}

@Override
public List<ArtifactVersion> getDependencies() {
return new ArrayList<>(dependencies.values());
}

@Override
public void bindMetadata(MetadataCollection mc) {
meta = new ModMetadata();
meta.modId = modId;
meta.name = displayName;
meta.version = version;
meta.autogenerated = false;
meta.authorList = authors;
meta.url = modURL == null ? "" : modURL.toString();
meta.description = String.join("\n", description);
meta.dependencies = new ArrayList<>(dependencies.values());
}

@Override
public ModMetadata getMetadata() {
return meta;
}

@Override
public boolean registerBus(EventBus bus, LoadController controller) {
FMLLog.log.debug("Enabling mod {}", getModId());
this.eventBus = bus;
eventBus.register(this);
return true;
}

@Override
public boolean matches(Object mod) {
return mod instanceof ModCore.Mod && mod == instance;
}


@Subscribe
public void constructMod(FMLConstructionEvent event) {
ModClassLoader modClassLoader = event.getModClassLoader();
try
{
modClassLoader.addFile(source);
}
catch (MalformedURLException e)
{
FormattedMessage message = new FormattedMessage("{} Failed to add file to classloader: {}", getModId(), source);
throw new LoaderException(message.getFormattedMessage(), e);
}
modClassLoader.clearNegativeCacheFor(candidate.getClassList());

//Only place I could think to add this...
MinecraftForge.preloadCrashClasses(event.getASMHarvestedData(), getModId(), candidate.getClassList());

Class<?> clazz;
try
{
clazz = Class.forName(mainClass, true, modClassLoader);
instance = (ModCore.Mod) clazz.newInstance();
ModCore.register(instance);
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
{
FormattedMessage message = new FormattedMessage("{} Failed load class: {}", getModId(), mainClass);
throw new LoaderException(message.getFormattedMessage(), e);
}
}

@Override
public Class<?> getCustomResourcePackClass()
{
return resourcesRoot != null ? DevFolderPack.class : null;
}

@Override
public Object getMod() {
return instance;
}

@Override
public String getDisplayVersion() {
return version;
}

@Override
public VersionRange acceptableMinecraftVersionRange() {
try {
return VersionRange.createFromVersionSpec("[1.7.10, )");
} catch (InvalidVersionSpecificationException e) {
throw new RuntimeException(e);
}
}

@Override
public boolean shouldLoadInEnvironment() {
//TODO Side only
return true;
}

@Override
public URL getUpdateUrl() {
return modURL;
}

@Override
public int getClassVersion() {
return 52; //Compatible with Java8
}

@Override
public String toString() {
return String.format("UMC Mod %s:%s", getName(), getVersion());
}

public static class DevFolderPack extends FolderResourcePack implements FMLContainerHolder {
private final ModContainer container;

public DevFolderPack(ModContainer container)
{
super(container instanceof UMCModContainer ? ((UMCModContainer) container).resourcesRoot : container.getSource());
this.container = container;
}

@Override
protected boolean hasResourceName(String name)
{
return super.hasResourceName(name);
}
@Override
public String getPackName()
{
return "DevFolderPack:"+container.getName();
}
@Override
protected InputStream getInputStreamByName(String resourceName) throws IOException
{
try
{
return super.getInputStreamByName(resourceName);
}
catch (IOException exception)
{
if ("pack.mcmeta".equals(resourceName)) {
//Generate a dummy pack.mcmeta
return new ByteArrayInputStream(("{\n" +
" \"pack\": {\n"+
" \"description\": \"UMC generated pack for "+container.getName()+"\",\n"+
" \"pack_format\": 2\n"+
"}\n" +
"}").getBytes(StandardCharsets.UTF_8));
} else {
throw exception;
}
}
}

@Override
public BufferedImage getPackImage() throws IOException
{
return ImageIO.read(getInputStreamByName(container.getMetadata().logoFile));
}

@Override
public ModContainer getFMLContainer()
{
return container;
}
}
}
94 changes: 94 additions & 0 deletions src/main/java/cam72cam/mod/loader/UMCModParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package cam72cam.mod.loader;

import cam72cam.mod.ModCore;
import com.google.gson.*;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionParser;

import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.function.Supplier;

public class UMCModParser {
private static final JsonParser parser = new JsonParser();

public static UMCModContainer parse(ModCandidate candidate, InputStream stream) throws NoSuchElementException {
return parse(candidate, stream, null);
}

public static UMCModContainer parse(ModCandidate candidate, InputStream stream, File resources) throws NoSuchElementException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
JsonElement root = parser.parse(reader);
JsonObject obj = root.getAsJsonObject();

UMCModContainer container = new UMCModContainer();

container.candidate = candidate;
container.source = candidate.getModContainer();
container.modId = getString(obj, "modId").orElseThrow(form(candidate, "modId"));
container.displayName = getString(obj, "displayName").orElseThrow(form(candidate, "displayName"));
container.mainClass = getString(obj, "mainClass").orElseThrow(form(candidate, "mainClass"));
container.version = getString(obj, "version").orElseThrow(form(candidate, "version"));
String modURL = getString(obj, "modURL").orElse("");
if (!modURL.isEmpty()) {
container.modURL = new URL(modURL);
}
container.license = getString(obj, "license").orElse("N/A");
container.resourcesRoot = resources;

container.authors = new ArrayList<>();
if (obj.has("authors") && obj.get("authors").isJsonArray()) {
JsonArray arr = obj.getAsJsonArray("authors");
for (JsonElement e : arr) {
container.authors.add(e.getAsString());
}
}

container.description = new ArrayList<>();
if (obj.has("description") && obj.get("description").isJsonArray()) {
JsonArray arr = obj.getAsJsonArray("description");
for (JsonElement e : arr) {
container.description.add(e.getAsString());
}
}

container.dependencies = new HashMap<>();
container.dependencies.put(ModCore.MODID, VersionParser.parseVersionReference(ModCore.MODID + "@" + "[1.2,1.3)"));
if (obj.has("dependencies") && obj.get("dependencies").isJsonObject()) {
JsonObject depsObj = obj.getAsJsonObject("dependencies");
String[] keys = new String[]{"default", ModCore.semanticVersion()};
for (String key : keys) {
if (depsObj.has(key)) {
JsonArray value = depsObj.getAsJsonArray(key);
for (JsonElement depElem : value.getAsJsonArray()) {
JsonObject depObj = depElem.getAsJsonObject();
ArtifactVersion artifact = VersionParser.parseVersionReference(
String.format("%s@%s",
getString(depObj, "modId").orElseThrow(form(candidate, "dependencies.modId")),
getString(depObj, "versionRange").orElse("[,]")));
container.dependencies.put(artifact.getLabel(), artifact);
}
}
}
}

container.bindMetadata(null);
return container;
} catch (IOException e) {
throw new RuntimeException("Failed to parse UMC mod", e);
}
}

private static Optional<String> getString(JsonObject obj, String member) {
if (obj.has(member) && obj.get(member).isJsonPrimitive()) {
return Optional.of(obj.get(member).getAsString());
}
return Optional.empty();
}

private static Supplier<NoSuchElementException> form(ModCandidate source, String field) {
return () -> new NoSuchElementException(String.format("Failed to get mandatory field '%s' in UMC mod %s", field, source.getModContainer().getName()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cam72cam.mod.mixin.feat.mod_loading;

import cam72cam.mod.ModCore;
import cam72cam.mod.loader.UMCModParser;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.DirectoryDiscoverer;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;

@Mixin(DirectoryDiscoverer.class)
public class MixinDirectoryDiscoverer {
@Inject(method = "discover", at = @At("HEAD"), remap = false, cancellable = true)
public void discover(ModCandidate candidate, ASMDataTable table, CallbackInfoReturnable<List<ModContainer>> cir) {
File classes = candidate.getModContainer();
//Only handle this in dev environment, players should always use jar
if (classes.getAbsolutePath().contains("build" + File.separator + "classes") && ModCore.isDevelopmentEnvironment()) {
//Tricky handling!
//I love LaunchWrapper
File resources;
try {
//Cleanroom
resources = (File) ModCandidate.class.getMethod("getResourcePathRoot").invoke(candidate);
} catch (Exception e) {
//Forge
resources = new File(classes.getAbsolutePath().replace("classes"+File.separator+"java", "resources"));
}
File umcMod = new File(resources, "umc.json");
if (resources.isDirectory() && umcMod.exists()) {
try (InputStream stream = Files.newInputStream(umcMod.toPath())) {
cir.setReturnValue(Collections.singletonList(UMCModParser.parse(candidate, stream, resources)));
} catch (Exception e) {
throw new RuntimeException("Failed to parse UMC mod", e);
}
}
}
}
}
Loading