diff --git a/modLoader/build.gradle b/modLoader/build.gradle new file mode 100644 index 0000000..b14b6fc --- /dev/null +++ b/modLoader/build.gradle @@ -0,0 +1,85 @@ +/* + * This file contains an application project which integrates the Fabric mod loader. + */ + +plugins { + // Apply the application plugin to add support for building a CLI application in Java. + id 'java-library' +} + +repositories { + // Maven Central for core dependencies + mavenCentral() + + // SpongePowered Maven repository for Mixins + maven { + url "https://repo.spongepowered.org/maven/" + } + // FabricMC Maven repository for Fabric Loader + maven { + url "https://maven.fabricmc.net/" + } +} + +sourceCompatibility = JavaVersion.VERSION_16 +targetCompatibility = JavaVersion.VERSION_16 + +version = '1.0.0' +group = "com.elitemastereric" +archivesBaseName = "modloader" + +dependencies { + api "com.google.guava:guava:21.0" + api "com.google.code.gson:gson:2.8.7" + + // Fabric dependencies + api "net.fabricmc:fabric-loader:0.13.3" + api "net.fabricmc:tiny-mappings-parser:0.2.2.14" + api "net.fabricmc:access-widener:2.1.0" + + // Mixin dependencies + api "org.ow2.asm:asm:9.2" + api "org.ow2.asm:asm-analysis:9.2" + api "org.ow2.asm:asm-commons:9.2" + api "org.ow2.asm:asm-tree:9.2" + api "org.ow2.asm:asm-util:9.2" + api 'org.spongepowered:mixin:0.8.5' + +} + +sourceSets { + main { + java { + srcDir 'src' + } + } +} + +jar { + manifest { + attributes( + 'Class-Path': configurations.runtimeClasspath.collect { it.getName() }.join(' '), + 'Specification-Version': 8.0, + 'Multi-Release': 'true' + ) + } +} + +task copyDependencies(type: Copy) { + group 'build' + from configurations.runtimeClasspath + into "build/libs/deps/" +} + +assemble { + group 'build' + dependsOn 'jar' + dependsOn 'copyDependencies' +} + +task buildAndCopy(type: Copy) { + group 'build' + dependsOn 'assemble' + from "build/libs/" + into "../run/" +} \ No newline at end of file diff --git a/modLoader/src/main/java/com/elitemastereric/modloader/AppGameProvider.java b/modLoader/src/main/java/com/elitemastereric/modloader/AppGameProvider.java new file mode 100644 index 0000000..0487594 --- /dev/null +++ b/modLoader/src/main/java/com/elitemastereric/modloader/AppGameProvider.java @@ -0,0 +1,230 @@ +package com.elitemastereric.modloader; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Collectors; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; + +import net.fabricmc.loader.impl.FormattedException; +import net.fabricmc.loader.impl.game.GameProvider; +import net.fabricmc.loader.impl.game.GameProviderHelper; +import net.fabricmc.loader.impl.game.patch.GameTransformer; +import net.fabricmc.loader.impl.launch.FabricLauncher; +import net.fabricmc.loader.impl.metadata.BuiltinModMetadata; +import net.fabricmc.loader.impl.metadata.ContactInformationImpl; +import net.fabricmc.loader.impl.util.Arguments; +import net.fabricmc.loader.impl.util.SystemProperties; +import net.fabricmc.loader.impl.util.log.Log; +import net.fabricmc.loader.impl.util.log.LogCategory; + +/* + * A custom GameProvider which grants Fabric Loader the necessary information to launch the app. + */ +public class AppGameProvider implements GameProvider { + public static final String CLIENT_ENTRYPOINT = "com.elitemastereric.helloworld.Launch"; + public static final String[] ENTRYPOINTS = { CLIENT_ENTRYPOINT }; + + public static final String PROPERTY_APP_DIRECTORY = "appDirectory"; + + private static final GameTransformer TRANSFORMER = new AppGameTransformer(); + + private Arguments arguments; + private Path appJar; + + /* + * Display an identifier for the app. + */ @Override + public String getGameId() { + return "helloworld"; + } + + /* + * Display a readable name for the app. + */ @Override + public String getGameName() { + return "Hello World"; + } + + /* + * Display a raw version string that may include build numbers or git hashes. + */ @Override + public String getRawGameVersion() { + return "1.0.0-abcdef"; + } + + /* + * Display a clean version string for display. + */ @Override + public String getNormalizedGameVersion() { + return "1.0.0"; + } + + /* + * Provides built-in mods, for example a mod that represents the app itself so + * that mods can depend on specific versions. + */ + @Override + public Collection getBuiltinMods() { + HashMap contactMap = new HashMap<>(); + contactMap.put("homepage", "https://elitemastereric.com/"); + + BuiltinModMetadata.Builder modMetadata = new BuiltinModMetadata.Builder(getGameId(), getNormalizedGameVersion()) + .setName(getGameName()) + .addAuthor("EliteMasterEric", contactMap) + .setContact(new ContactInformationImpl(contactMap)) + .setDescription("A simple Hello World app for Fabric Loader."); + + BuiltinMod mod = new BuiltinMod(Collections.singletonList(appJar), modMetadata.build()); + + return Collections.singletonList(mod); + } + + /* + * Provides the full class name of the app's entrypoint. + */ + @Override + public String getEntrypoint() { + return CLIENT_ENTRYPOINT; + } + + /* + * Provides the directory path where the app's resources (such as config) should + * be located + * This is where the `mods` folder will be located. + */ + @Override + public Path getLaunchDirectory() { + if (arguments == null) { + return Paths.get("."); + } + + return getLaunchDirectory(arguments); + } + + private static Path getLaunchDirectory(Arguments arguments) { + return Paths.get(arguments.getOrDefault(PROPERTY_APP_DIRECTORY, ".")); + } + + /* + * Return true if the app needs to be deobfuscated. + */ + @Override + public boolean isObfuscated() { + return false; + } + + @Override + public boolean requiresUrlClassLoader() { + return false; + } + + @Override + public boolean isEnabled() { + return true; + } + + /* + * Parse the arguments, locate the game directory, and return true if the game + * directory is valid. + */ + @Override + public boolean locateGame(FabricLauncher launcher, String[] args) { + this.arguments = new Arguments(); + this.arguments.parse(args); + + // Build a list of possible locations for the app JAR. + List appLocations = new ArrayList<>(); + // Respect "fabric.gameJarPath" if it is set. + if (System.getProperty(SystemProperties.GAME_JAR_PATH) != null) { + appLocations.add(System.getProperty(SystemProperties.GAME_JAR_PATH)); + } + // List out default locations. + appLocations.add("./helloworld-" + getNormalizedGameVersion() + ".jar"); + + // Filter the list of possible locations based on whether the file exists. + List existingAppLocations = appLocations.stream().map(p -> Paths.get(p).toAbsolutePath().normalize()) + .filter(Files::exists).toList(); + + // Filter the list of possible locations based on whether they contain the required entrypoints + GameProviderHelper.FindResult result = GameProviderHelper.findFirst(existingAppLocations, new HashMap<>(), true, ENTRYPOINTS); + + if (result == null || result.path == null) { + // Tell the user we couldn't find the app JAR. + String appLocationsString = appLocations.stream().map(p -> (String.format("* %s", Paths.get(p).toAbsolutePath().normalize()))) + .collect(Collectors.joining("\n")); + + Log.error(LogCategory.GAME_PROVIDER, "Could not locate the application JAR! We looked in: \n" + appLocationsString); + + return false; + } + + this.appJar = result.path; + + return true; + } + + /* + * Add additional configuration to the FabricLauncher, but do not launch your + * app. + */ + @Override + public void initialize(FabricLauncher launcher) { + TRANSFORMER.locateEntrypoints(launcher, appJar); + } + + /* + * Return a GameTransformer that does extra modification on the app's JAR. + */ + @Override + public GameTransformer getEntrypointTransformer() { + return TRANSFORMER; + } + + /* + * Called after transformers were initialized and mods were detected and loaded + * (but not initialized). + */ + @Override + public void unlockClassPath(FabricLauncher launcher) { + launcher.addToClassPath(appJar); + } + + /* + * Launch the app in this function. This MUST be done via reflection. + */ + @Override + public void launch(ClassLoader loader) { + try { + Class main = loader.loadClass(this.getEntrypoint()); + Method method = main.getMethod("main", String[].class); + + method.invoke(null, (Object) this.arguments.toArray()); + } catch (InvocationTargetException e) { + throw new FormattedException("App has crashed!", e.getCause()); + } catch (ReflectiveOperationException e) { + throw new FormattedException("Failed to launch App", e); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public Arguments getArguments() { + return this.arguments; + } + + @Override + public String[] getLaunchArguments(boolean sanitize) { + if (arguments == null) return new String[0]; + + String[] ret = arguments.toArray(); + return ret; + } +} diff --git a/modLoader/src/main/java/com/elitemastereric/modloader/AppGameTransformer.java b/modLoader/src/main/java/com/elitemastereric/modloader/AppGameTransformer.java new file mode 100644 index 0000000..6d6264d --- /dev/null +++ b/modLoader/src/main/java/com/elitemastereric/modloader/AppGameTransformer.java @@ -0,0 +1,14 @@ +package com.elitemastereric.modloader; + +import net.fabricmc.loader.impl.game.patch.GameTransformer; + +public class AppGameTransformer extends GameTransformer { + /* + * @param className The class name, + * @return The transformed class data. + */ + @Override + public byte[] transform(String name) { + return null; + } +} diff --git a/modLoader/src/main/java/com/elitemastereric/modloader/README.md b/modLoader/src/main/java/com/elitemastereric/modloader/README.md new file mode 100644 index 0000000..ad3d4b3 --- /dev/null +++ b/modLoader/src/main/java/com/elitemastereric/modloader/README.md @@ -0,0 +1 @@ +This is the app which performs the mod loading and allows for transformation of the target application. \ No newline at end of file diff --git a/modLoader/src/main/resources/META-INF/services/net.fabricmc.loader.impl.game.GameProvider b/modLoader/src/main/resources/META-INF/services/net.fabricmc.loader.impl.game.GameProvider new file mode 100644 index 0000000..5fd3af9 --- /dev/null +++ b/modLoader/src/main/resources/META-INF/services/net.fabricmc.loader.impl.game.GameProvider @@ -0,0 +1 @@ +com.elitemastereric.modloader.AppGameProvider \ No newline at end of file