initial work on separating plugin types
This commit is contained in:
parent
32a95ce9cb
commit
f6715e36a2
|
@ -0,0 +1,15 @@
|
||||||
|
package dev.frogmc.frogloader.api.plugin;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.FrogLoader;
|
||||||
|
|
||||||
|
public interface FrogGamePlugin {
|
||||||
|
default void run() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isApplicable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void init(FrogLoader loader) throws Exception {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package dev.frogmc.frogloader.api.plugin;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public interface FrogModPlugin {
|
||||||
|
default String loadDirectory() {
|
||||||
|
return "mods";
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isApplicable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isFileApplicable(Path path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default ModProperties loadMod(Path path) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package dev.frogmc.frogloader.api.plugin;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import dev.frogmc.frogloader.api.FrogLoader;
|
import dev.frogmc.frogloader.api.FrogLoader;
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
package dev.frogmc.frogloader.impl;
|
package dev.frogmc.frogloader.impl;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.MethodType;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import dev.frogmc.frogloader.api.FrogLoader;
|
import dev.frogmc.frogloader.api.FrogLoader;
|
||||||
import dev.frogmc.frogloader.api.env.Env;
|
import dev.frogmc.frogloader.api.env.Env;
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
import dev.frogmc.frogloader.api.plugin.FrogGamePlugin;
|
||||||
|
import dev.frogmc.frogloader.api.plugin.FrogModPlugin;
|
||||||
import dev.frogmc.frogloader.api.plugin.FrogPlugin;
|
import dev.frogmc.frogloader.api.plugin.FrogPlugin;
|
||||||
import dev.frogmc.frogloader.impl.gui.LoaderGui;
|
import dev.frogmc.frogloader.impl.gui.LoaderGui;
|
||||||
import dev.frogmc.frogloader.impl.launch.MixinClassLoader;
|
import dev.frogmc.frogloader.impl.launch.MixinClassLoader;
|
||||||
|
@ -25,144 +17,214 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class FrogLoaderImpl implements FrogLoader {
|
public class FrogLoaderImpl implements FrogLoader {
|
||||||
public static final String MOD_FILE_EXTENSION = ".frogmod";
|
public static final String MOD_FILE_EXTENSION = ".frogmod";
|
||||||
private static final boolean DEV_ENV = Boolean.getBoolean(SystemProperties.DEVELOPMENT);
|
private static final boolean DEV_ENV = Boolean.getBoolean(SystemProperties.DEVELOPMENT);
|
||||||
@Getter
|
@Getter
|
||||||
private static FrogLoaderImpl instance;
|
private static FrogLoaderImpl instance;
|
||||||
@Getter
|
@Getter
|
||||||
private final String[] args;
|
private final String[] args;
|
||||||
@Getter
|
@Getter
|
||||||
private final Env env;
|
private final Env env;
|
||||||
private final Logger LOGGER = LoggerFactory.getLogger("FrogLoader");
|
private final Logger LOGGER = LoggerFactory.getLogger("FrogLoader");
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final List<FrogPlugin> plugins = new ArrayList<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final List<FrogGamePlugin> gamePlugins = new ArrayList<>();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final List<FrogPlugin> plugins = new ArrayList<>();
|
private final List<FrogModPlugin> modPlugins = new ArrayList<>();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Path gameDir, configDir, modsDir;
|
private final Path gameDir, configDir, modsDir;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final MixinClassLoader classloader;
|
private final MixinClassLoader classloader;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
private Map<String, ModProperties> mods;
|
private Map<String, ModProperties> mods;
|
||||||
private Collection<String> modIds;
|
private Collection<String> modIds;
|
||||||
|
|
||||||
|
|
||||||
private FrogLoaderImpl(String[] args, Env env) {
|
private FrogLoaderImpl(String[] args, Env env) {
|
||||||
instance = this;
|
instance = this;
|
||||||
this.classloader = (MixinClassLoader) this.getClass().getClassLoader();
|
this.classloader = (MixinClassLoader) this.getClass().getClassLoader();
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
|
|
||||||
gameDir = Paths.get(getArgumentOrElse("gameDir", "."));
|
gameDir = Paths.get(getArgumentOrElse("gameDir", "."));
|
||||||
configDir = gameDir.resolve("config");
|
configDir = gameDir.resolve("config");
|
||||||
modsDir = gameDir.resolve("mods");
|
modsDir = gameDir.resolve("mods");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(gameDir);
|
Files.createDirectories(gameDir);
|
||||||
Files.createDirectories(configDir);
|
Files.createDirectories(configDir);
|
||||||
Files.createDirectories(modsDir);
|
Files.createDirectories(modsDir);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.warn("Failed to create essential directories ", e);
|
LOGGER.warn("Failed to create essential directories ", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
discoverPlugins();
|
discoverPlugins();
|
||||||
advanceMixinState();
|
advanceMixinState();
|
||||||
mods = collectMods();
|
mods = collectMods();
|
||||||
modIds = collectModIds();
|
modIds = collectModIds();
|
||||||
LOGGER.info(ModUtil.getModList(mods.values()));
|
LOGGER.info(ModUtil.getModList(mods.values()));
|
||||||
LOGGER.info("Launching...");
|
LOGGER.info("Launching...");
|
||||||
plugins.forEach(FrogPlugin::run);
|
plugins.forEach(FrogPlugin::run);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
LoaderGui.execReport(CrashReportGenerator.writeReport(t, collectMods().values()), false);
|
LoaderGui.execReport(CrashReportGenerator.writeReport(t, collectMods().values()), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void run(String[] args, Env env) {
|
public static void run(String[] args, Env env) {
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
throw new IllegalStateException("Loader was started multiple times!");
|
throw new IllegalStateException("Loader was started multiple times!");
|
||||||
}
|
}
|
||||||
|
|
||||||
new FrogLoaderImpl(args, env);
|
new FrogLoaderImpl(args, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void advanceMixinState() {
|
private void advanceMixinState() {
|
||||||
try {
|
try {
|
||||||
MethodHandle m = MethodHandles.privateLookupIn(MixinEnvironment.class, MethodHandles.lookup()).findStatic(MixinEnvironment.class, "gotoPhase", MethodType.methodType(void.class, MixinEnvironment.Phase.class));
|
MethodHandle m = MethodHandles.privateLookupIn(MixinEnvironment.class, MethodHandles.lookup()).findStatic(MixinEnvironment.class, "gotoPhase", MethodType.methodType(void.class, MixinEnvironment.Phase.class));
|
||||||
m.invoke(MixinEnvironment.Phase.INIT);
|
m.invoke(MixinEnvironment.Phase.INIT);
|
||||||
m.invoke(MixinEnvironment.Phase.DEFAULT);
|
m.invoke(MixinEnvironment.Phase.DEFAULT);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void discoverPlugins() {
|
private void discoverModPlugins() {
|
||||||
ServiceLoader.load(FrogPlugin.class).forEach(plugin -> {
|
FrogModPlugin[] applicablePlugins = ServiceLoader
|
||||||
try {
|
.load(FrogModPlugin.class)
|
||||||
if (plugin.isApplicable()) {
|
.stream()
|
||||||
plugin.init(this);
|
.map(ServiceLoader.Provider::get)
|
||||||
plugins.add(plugin);
|
.filter(FrogModPlugin::isApplicable)
|
||||||
}
|
.toArray(FrogModPlugin[]::new);
|
||||||
} catch (Throwable e) {
|
|
||||||
LOGGER.error("Error during plugin initialisation: ", e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (plugins.isEmpty()) {
|
for (FrogModPlugin plugin : applicablePlugins) {
|
||||||
throw new IllegalStateException("No plugin applicable to the current state was found!");
|
try {
|
||||||
}
|
Collection<Path> paths = Discovery.find(gameDir.resolve(plugin.loadDirectory()), p->false, plugin::isFileApplicable);
|
||||||
}
|
paths.forEach(p -> {
|
||||||
|
try {
|
||||||
|
ModProperties mod = plugin.loadMod(p);
|
||||||
|
mods.put(mod.id(), mod);
|
||||||
|
modIds.add(mod.id());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOGGER.error("Error during mod initialisation: ", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
modPlugins.add(plugin);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOGGER.error("Error during plugin initialisation: ", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getArgument(String name) {
|
private void discoverGamePlugins() {
|
||||||
for (int i = 0; i < args.length - 1; i += 2) {
|
// this cast is safe
|
||||||
if (args[i].equals("--" + name)) {
|
FrogGamePlugin[] applicablePlugins = ServiceLoader
|
||||||
return args[i + 1];
|
.load(FrogGamePlugin.class)
|
||||||
}
|
.stream()
|
||||||
}
|
.map(ServiceLoader.Provider::get)
|
||||||
return "";
|
.filter(FrogGamePlugin::isApplicable)
|
||||||
}
|
.toArray(FrogGamePlugin[]::new);
|
||||||
|
if (applicablePlugins.length > 1) {
|
||||||
|
throw new IllegalStateException("Multiple applicable game plugins found!");
|
||||||
|
} else if (applicablePlugins.length == 0) {
|
||||||
|
throw new IllegalStateException("No applicable game plugin found!");
|
||||||
|
}
|
||||||
|
|
||||||
public String getArgumentOrElse(String name, String other) {
|
for (FrogGamePlugin plugin : applicablePlugins) {
|
||||||
String res = getArgument(name);
|
try {
|
||||||
if (res.isEmpty()) {
|
plugin.init(this);
|
||||||
return other;
|
gamePlugins.add(plugin);
|
||||||
}
|
} catch (Throwable e) {
|
||||||
return res;
|
LOGGER.error("Error during plugin initialisation: ", e);
|
||||||
}
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDevelopment() {
|
|
||||||
return DEV_ENV;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void discoverPlugins() {
|
||||||
public boolean isModLoaded(String id) {
|
ServiceLoader.load(FrogPlugin.class).forEach(plugin -> {
|
||||||
return modIds.contains(id);
|
try {
|
||||||
}
|
if (plugin.isApplicable()) {
|
||||||
|
plugin.init(this);
|
||||||
|
plugins.add(plugin);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOGGER.error("Error during plugin initialisation: ", e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@Override
|
if (plugins.isEmpty()) {
|
||||||
public Optional<ModProperties> getModProperties(String id) {
|
throw new IllegalStateException("No plugin applicable to the current state was found!");
|
||||||
return Optional.ofNullable(mods.get(id));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, ModProperties> collectMods() {
|
public String getArgument(String name) {
|
||||||
return plugins.stream().map(FrogPlugin::getMods).flatMap(Collection::stream)
|
for (int i = 0; i < args.length - 1; i += 2) {
|
||||||
.collect(Collectors.toMap(ModProperties::id, m -> m));
|
if (args[i].equals("--" + name)) {
|
||||||
}
|
return args[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
private Collection<String> collectModIds() {
|
public String getArgumentOrElse(String name, String other) {
|
||||||
return mods.keySet();
|
String res = getArgument(name);
|
||||||
}
|
if (res.isEmpty()) {
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<ModProperties> getMods() {
|
public boolean isDevelopment() {
|
||||||
return mods.values();
|
return DEV_ENV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isModLoaded(String id) {
|
||||||
|
return modIds.contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ModProperties> getModProperties(String id) {
|
||||||
|
return Optional.ofNullable(mods.get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, ModProperties> collectMods() {
|
||||||
|
return plugins.stream().map(FrogPlugin::getMods).flatMap(Collection::stream).collect(Collectors.toMap(ModProperties::id, m -> m));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<String> collectModIds() {
|
||||||
|
return mods.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<ModProperties> getMods() {
|
||||||
|
return mods.values();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,244 +0,0 @@
|
||||||
package dev.frogmc.frogloader.impl.plugin.game.minecraft;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.MethodType;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.nio.file.*;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import dev.frogmc.frogloader.api.FrogLoader;
|
|
||||||
import dev.frogmc.frogloader.api.extensions.PreLaunchExtension;
|
|
||||||
import dev.frogmc.frogloader.api.mod.ModDependencies;
|
|
||||||
import dev.frogmc.frogloader.api.mod.ModExtensions;
|
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
|
||||||
import dev.frogmc.frogloader.api.plugin.FrogPlugin;
|
|
||||||
import dev.frogmc.frogloader.impl.Discovery;
|
|
||||||
import dev.frogmc.frogloader.impl.FrogLoaderImpl;
|
|
||||||
import dev.frogmc.frogloader.impl.gui.LoaderGui;
|
|
||||||
import dev.frogmc.frogloader.impl.mixin.AWProcessor;
|
|
||||||
import dev.frogmc.frogloader.impl.mod.*;
|
|
||||||
import dev.frogmc.frogloader.impl.util.CrashReportGenerator;
|
|
||||||
import dev.frogmc.frogloader.impl.util.SystemProperties;
|
|
||||||
import dev.frogmc.thyroxine.Thyroxine;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.spongepowered.asm.mixin.Mixins;
|
|
||||||
|
|
||||||
public class Minecraft implements FrogPlugin {
|
|
||||||
|
|
||||||
protected static final String[] MINECRAFT_CLASSES = new String[]{
|
|
||||||
"net/minecraft/client/main/Main.class",
|
|
||||||
"net/minecraft/client/MinecraftApplet.class",
|
|
||||||
"net/minecraft/server/Main.class"
|
|
||||||
};
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger("Plugin/Minecraft");
|
|
||||||
protected final Collection<ModProperties> modProperties = new ArrayList<>();
|
|
||||||
protected Path gamePath;
|
|
||||||
protected String foundMainClass;
|
|
||||||
private String version;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isApplicable() {
|
|
||||||
gamePath = findGame();
|
|
||||||
return gamePath != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
||||||
@Override
|
|
||||||
public void init(FrogLoader loader) throws Exception {
|
|
||||||
if (gamePath == null) {
|
|
||||||
throw new IllegalStateException("Game not found yet!");
|
|
||||||
}
|
|
||||||
Path remappedGamePath = loader.getGameDir().resolve(".frogmc/remappedJars").resolve(version).resolve("game-" + version + "-remapped.jar");
|
|
||||||
|
|
||||||
if (!Files.exists(remappedGamePath.getParent())) {
|
|
||||||
try {
|
|
||||||
Files.createDirectories(remappedGamePath.getParent());
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOGGER.error("Failed to create directory", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modProperties.add(JavaModProperties.get());
|
|
||||||
modProperties.add(new ModPropertiesImpl("minecraft", "Minecraft", "/assets/minecraft/textures/block/grass_block_side.png",
|
|
||||||
MinecraftSemVerImpl.get(version), "MC-EULA",
|
|
||||||
Map.of("Mojang AB", Collections.singleton("Author")),
|
|
||||||
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
|
|
||||||
ModExtensions.of(Collections.emptyMap()), Collections.emptySet()));
|
|
||||||
|
|
||||||
Collection<Path> mods = Discovery.find(loader.getModsDir(), path ->
|
|
||||||
version.equals(path.getFileName().toString()), path ->
|
|
||||||
path.getFileName().toString().endsWith(FrogLoaderImpl.MOD_FILE_EXTENSION));
|
|
||||||
this.getClass().getClassLoader().resources(ModPropertiesReader.PROPERTIES_FILE_NAME).map(ModPropertiesReader::readFile)
|
|
||||||
.map(o -> o.orElse(null)).filter(Objects::nonNull).forEach(modProperties::add);
|
|
||||||
|
|
||||||
Map<Path, ModProperties> modPaths = new HashMap<>();
|
|
||||||
Path jijCache = getJijCacheDir();
|
|
||||||
for (Path mod : new HashSet<>(mods)) {
|
|
||||||
findJiJMods(mod, mods, modPaths, jijCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
modProperties.retainAll(new ModDependencyResolver(modProperties).solve());
|
|
||||||
} catch (ModDependencyResolver.BreakingModException e) {
|
|
||||||
LoaderGui.execBreakingDep(CrashReportGenerator.writeReport(e, modProperties), e, false);
|
|
||||||
} catch (ModDependencyResolver.UnfulfilledDependencyException e) {
|
|
||||||
LoaderGui.execUnfulfilledDep(CrashReportGenerator.writeReport(e, modProperties), e, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
mods.stream().filter(p -> modProperties.contains(modPaths.get(p))).map(path -> {
|
|
||||||
try {
|
|
||||||
return path.toUri().toURL();
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
LOGGER.warn("Failed to resolve url for {}", path, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).filter(Objects::nonNull).forEach(FrogLoaderImpl.getInstance().getClassloader()::addURL);
|
|
||||||
|
|
||||||
modProperties.forEach(props -> {
|
|
||||||
Object o = props.extensions().get(BuiltinExtensions.MIXIN_CONFIG);
|
|
||||||
if (o instanceof String name) {
|
|
||||||
Mixins.addConfiguration(name);
|
|
||||||
} else if (o instanceof Collection l) {
|
|
||||||
((Collection<String>) l).forEach(Mixins::addConfiguration);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!loader.isDevelopment()) {
|
|
||||||
if (!Files.exists(remappedGamePath)) {
|
|
||||||
Thyroxine.run(version, gamePath, remappedGamePath, true, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AWProcessor.load(modProperties);
|
|
||||||
|
|
||||||
var runtimePath = loader.isDevelopment() ? gamePath : remappedGamePath;
|
|
||||||
FrogLoaderImpl.getInstance().getClassloader().addURL(runtimePath.toUri().toURL());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void findJiJMods(Path mod, Collection<Path> mods, Map<Path, ModProperties> modPaths, Path jijCache) throws IOException {
|
|
||||||
Optional<ModProperties> opt = ModPropertiesReader.read(mod);
|
|
||||||
if (opt.isPresent()) {
|
|
||||||
ModProperties p = opt.get();
|
|
||||||
modProperties.add(p);
|
|
||||||
modPaths.put(mod, p);
|
|
||||||
List<List<UnmodifiableConfig>> entries = p.extensions().getOrDefault(BuiltinExtensions.INCLUDED_JARS, Collections.emptyList());
|
|
||||||
if (entries.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try (FileSystem fs = FileSystems.newFileSystem(mod)) {
|
|
||||||
for (List<UnmodifiableConfig> jars : entries) {
|
|
||||||
for (UnmodifiableConfig jar : jars) {
|
|
||||||
Path path = fs.getPath(jar.get("path")).toAbsolutePath();
|
|
||||||
Path extracted = jijCache.resolve((String) jar.get("id"));
|
|
||||||
if (!Files.exists(extracted)){
|
|
||||||
Files.createDirectories(jijCache);
|
|
||||||
Files.copy(path, extracted);
|
|
||||||
}
|
|
||||||
mods.add(extracted);
|
|
||||||
findJiJMods(extracted, mods, modPaths, jijCache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path getJijCacheDir(){
|
|
||||||
Path dir = FrogLoader.getInstance().getGameDir().resolve(".frogmc").resolve("jijcache");
|
|
||||||
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
||||||
try {
|
|
||||||
Files.walkFileTree(dir, new SimpleFileVisitor<>(){
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
Files.delete(file);
|
|
||||||
return super.visitFile(file, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
|
||||||
Files.delete(dir);
|
|
||||||
return super.postVisitDirectory(dir, exc);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOGGER.error("Failed to clear extracted jij mods!", e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<ModProperties> getMods() {
|
|
||||||
return modProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Path findGame() {
|
|
||||||
LOGGER.info("Locating game..");
|
|
||||||
String jar = System.getProperty(SystemProperties.MINECRAFT_GAME_JAR);
|
|
||||||
if (jar != null) {
|
|
||||||
Path p = Paths.get(jar);
|
|
||||||
if (checkLocation(p)) {
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String s : System.getProperty("java.class.path", "").split(File.pathSeparator)) {
|
|
||||||
Path p = Paths.get(s);
|
|
||||||
if (checkLocation(p)) {
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOGGER.warn("Could not locate game!");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean checkLocation(Path jar) {
|
|
||||||
if (!Files.exists(jar) || Files.isDirectory(jar)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try (FileSystem fs = FileSystems.newFileSystem(jar)) {
|
|
||||||
for (String n : MINECRAFT_CLASSES) {
|
|
||||||
if (Files.exists(fs.getPath(n)) && n.contains(FrogLoaderImpl.getInstance().getEnv().getIdentifier())) {
|
|
||||||
LOGGER.info("Found game: {}", jar);
|
|
||||||
foundMainClass = n.substring(0, n.length() - 6).replace("/", ".");
|
|
||||||
try {
|
|
||||||
version = FrogLoaderImpl.getInstance().getGson().fromJson(Files.readString(fs.getPath("version.json")), JsonObject.class).get("id").getAsString();
|
|
||||||
} catch (Exception e){
|
|
||||||
version = FrogLoaderImpl.getInstance().getArgument("version");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
if (foundMainClass != null) {
|
|
||||||
modProperties.forEach(props ->
|
|
||||||
props.extensions().runIfPresent(PreLaunchExtension.ID,
|
|
||||||
PreLaunchExtension.class, PreLaunchExtension::onPreLaunch));
|
|
||||||
LOGGER.info("Launching main class: {}", foundMainClass);
|
|
||||||
Class<?> mainClass = Class.forName(foundMainClass);
|
|
||||||
MethodHandle main = MethodHandles.publicLookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
|
|
||||||
main.invoke((Object) FrogLoaderImpl.getInstance().getArgs());
|
|
||||||
} else {
|
|
||||||
LOGGER.warn("Failed to locate main class!");
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
package dev.frogmc.frogloader.impl.plugin.game.minecraft;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import dev.frogmc.frogloader.api.FrogLoader;
|
||||||
|
import dev.frogmc.frogloader.api.plugin.FrogGamePlugin;
|
||||||
|
import dev.frogmc.frogloader.impl.FrogLoaderImpl;
|
||||||
|
import dev.frogmc.frogloader.impl.util.SystemProperties;
|
||||||
|
import dev.frogmc.thyroxine.Thyroxine;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.nio.file.*;
|
||||||
|
|
||||||
|
public class MinecraftGamePlugin implements FrogGamePlugin {
|
||||||
|
|
||||||
|
protected final String[] MINECRAFT_CLASSES = new String[]{
|
||||||
|
"net/minecraft/client/main/Main.class",
|
||||||
|
"net/minecraft/client/MinecraftApplet.class",
|
||||||
|
"net/minecraft/server/Main.class"
|
||||||
|
};
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("Plugin/Minecraft");
|
||||||
|
protected Path gamePath;
|
||||||
|
protected String foundMainClass;
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
protected boolean checkLocation(Path jar) {
|
||||||
|
if (!Files.exists(jar) || Files.isDirectory(jar)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try (FileSystem fs = FileSystems.newFileSystem(jar)) {
|
||||||
|
for (String n : MINECRAFT_CLASSES) {
|
||||||
|
if (Files.exists(fs.getPath(n)) && n.contains(FrogLoaderImpl.getInstance().getEnv().getIdentifier())) {
|
||||||
|
LOGGER.info("Found game: {}", jar);
|
||||||
|
foundMainClass = n.substring(0, n.length() - 6).replace("/", ".");
|
||||||
|
try {
|
||||||
|
version = FrogLoaderImpl.getInstance().getGson().fromJson(Files.readString(fs.getPath("version.json")), JsonObject.class).get("id").getAsString();
|
||||||
|
} catch (Exception e){
|
||||||
|
version = FrogLoaderImpl.getInstance().getArgument("version");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected Path findGame() {
|
||||||
|
LOGGER.info("Locating game..");
|
||||||
|
String jar = System.getProperty(SystemProperties.MINECRAFT_GAME_JAR);
|
||||||
|
if (jar != null) {
|
||||||
|
Path p = Paths.get(jar);
|
||||||
|
if (checkLocation(p)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String s : System.getProperty("java.class.path", "").split(File.pathSeparator)) {
|
||||||
|
Path p = Paths.get(s);
|
||||||
|
if (checkLocation(p)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOGGER.warn("Could not locate game!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicable() {
|
||||||
|
gamePath = findGame();
|
||||||
|
return gamePath != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
if (foundMainClass != null) {
|
||||||
|
LOGGER.info("Launching main class: {}", foundMainClass);
|
||||||
|
Class<?> mainClass = Class.forName(foundMainClass);
|
||||||
|
MethodHandle main = MethodHandles.publicLookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
|
||||||
|
main.invoke((Object) FrogLoaderImpl.getInstance().getArgs());
|
||||||
|
} else {
|
||||||
|
LOGGER.warn("Failed to locate main class!");
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FrogLoader loader) throws Exception {
|
||||||
|
if (gamePath == null) {
|
||||||
|
throw new IllegalStateException("Game not found!");
|
||||||
|
}
|
||||||
|
Path remappedGamePath = loader.getGameDir().resolve(".frogmc/remappedJars").resolve(version).resolve("game-" + version + "-remapped.jar");
|
||||||
|
|
||||||
|
if (!Files.exists(remappedGamePath.getParent())) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(remappedGamePath.getParent());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Failed to create directory", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loader.isDevelopment()) {
|
||||||
|
if (!Files.exists(remappedGamePath)) {
|
||||||
|
Thyroxine.run(version, gamePath, remappedGamePath, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var runtimePath = loader.isDevelopment() ? gamePath : remappedGamePath;
|
||||||
|
FrogLoaderImpl.getInstance().getClassloader().addURL(runtimePath.toUri().toURL());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package dev.frogmc.frogloader.impl.plugin.mod;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.extensions.PreLaunchExtension;
|
||||||
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
import dev.frogmc.frogloader.api.plugin.FrogModPlugin;
|
||||||
|
import dev.frogmc.frogloader.impl.mod.ModPropertiesImpl;
|
||||||
|
import dev.frogmc.frogloader.impl.mod.ModPropertiesReader;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class FrogmodModPlugin implements FrogModPlugin {
|
||||||
|
@Override
|
||||||
|
public boolean isApplicable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFileApplicable(Path path) {
|
||||||
|
if (!path.endsWith(".frogmod")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try (FileSystem fs = FileSystems.newFileSystem(path)) {
|
||||||
|
return fs.getPath("frog.mod.toml").toFile().exists();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModProperties loadMod(Path path) {
|
||||||
|
try (FileSystem fs = FileSystems.newFileSystem(path)) {
|
||||||
|
ModProperties prop = ModPropertiesReader.readFile(fs.getPath("frog.mod.toml").toUri().toURL()).orElseThrow(IOException::new);
|
||||||
|
prop.extensions().runIfPresent(PreLaunchExtension.ID, PreLaunchExtension.class, PreLaunchExtension::onPreLaunch);
|
||||||
|
return prop;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
dev.frogmc.frogloader.impl.plugin.game.minecraft.Minecraft
|
dev.frogmc.frogloader.impl.plugin.game.minecraft.MinecraftOld
|
Loading…
Reference in a new issue